@@ -107,31 +107,27 @@ public function isValid($requestId = null)
107107 $ spData = $ this ->_settings ->getSPData ();
108108 $ spEntityId = $ spData ['entityId ' ];
109109
110- $ signedElements = array ();
111- if ($ this ->encrypted ) {
112- $ signNodes = $ this ->decryptedDocument ->getElementsByTagName ('Signature ' );
113- } else {
114- $ signNodes = $ this ->document ->getElementsByTagName ('Signature ' );
115- }
116- foreach ($ signNodes as $ signNode ) {
117- $ signedElements [] = $ signNode ->parentNode ->localName ;
118- }
119-
120- if (!empty ($ signedElements )) {
121- // Check SignedElements
122- if (!$ this ->validateSignedElements ($ signedElements )) {
123- throw new Exception ('Found an unexpected Signature Element. SAML Response rejected ' );
124- }
125- }
110+ $ signedElements = $ this ->processSignedElements ();
126111
127112 if ($ this ->_settings ->isStrict ()) {
128113 $ security = $ this ->_settings ->getSecurityData ();
129114
130115 if ($ security ['wantXMLValidation ' ]) {
116+ $ errorXmlMsg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd " ;
131117 $ res = OneLogin_Saml2_Utils::validateXML ($ this ->document , 'saml-schema-protocol-2.0.xsd ' , $ this ->_settings ->isDebugActive ());
132118 if (!$ res instanceof DOMDocument) {
133- throw new Exception ("Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd " );
119+ throw new Exception ($ errorXmlMsg );
120+ }
121+
122+ # If encrypted, check also the decrypted document
123+
124+ if ($ this ->encrypted ) {
125+ $ res = OneLogin_Saml2_Utils::validateXML ($ this ->decryptedDocument , 'saml-schema-protocol-2.0.xsd ' , $ this ->_settings ->isDebugActive ());
126+ if (!$ res instanceof DOMDocument) {
127+ throw new Exception ($ errorXmlMsg );
128+ }
134129 }
130+
135131 }
136132
137133 $ currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery ();
@@ -273,7 +269,7 @@ public function isValid($requestId = null)
273269 $ documentToValidate = $ this ->decryptedDocument ;
274270 if ($ this ->encrypted ) {
275271 $ documentToValidate = $ this ->decryptedDocument ;
276- $ encryptedIDNodes = OneLogin_Saml2_Utils::query ($ this ->decryptedDocument , '/samlp:Response/saml:EncryptedAssertion/saml: Assertion/saml:Subject/saml:EncryptedID ' );
272+ $ encryptedIDNodes = OneLogin_Saml2_Utils::query ($ this ->decryptedDocument , '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID ' );
277273 if ($ encryptedIDNodes ->length > 0 ) {
278274 throw new Exception ('Unsigned SAML Response that contains a signed and encrypted Assertion with encrypted nameId is not supported. ' );
279275 }
@@ -511,6 +507,71 @@ public function validateNumAssertions()
511507 return ($ assertionNodes ->length + $ encryptedAssertionNodes ->length == 1 );
512508 }
513509
510+ /**
511+ * Verifies the signature nodes:
512+ * - Checks that are Response or Assertion
513+ * - Check that IDs and reference URI are unique and consistent.
514+ *
515+ * @return array Signed element tags
516+ */
517+ public function processSignedElements ()
518+ {
519+ $ signedElements = array ();
520+ $ verifiedSeis = array ();
521+ $ verifiedIds = array ();
522+
523+ if ($ this ->encrypted ) {
524+ $ signNodes = $ this ->decryptedDocument ->getElementsByTagName ('Signature ' );
525+ } else {
526+ $ signNodes = $ this ->document ->getElementsByTagName ('Signature ' );
527+ }
528+ foreach ($ signNodes as $ signNode ) {
529+ $ signedElement = $ signNode ->parentNode ->localName ;
530+
531+ if ($ signedElement != 'Response ' && $ signedElement != 'Assertion ' ) {
532+ throw new Exception ('Invalid Signature Element ' .$ signedElement .' SAML Response rejected ' );
533+ }
534+
535+ # Check that reference URI matches the parent ID and no duplicate References or IDs
536+ $ idValue = $ signNode ->parentNode ->getAttribute ('ID ' );
537+ if (empty ($ idValue )) {
538+ throw new Exception ('Signed Element must contain an ID. SAML Response rejected ' );
539+ }
540+
541+ if (in_array ($ idValue , $ verifiedIds )) {
542+ throw new Exception ('Duplicated ID. SAML Response rejected ' );
543+ }
544+ $ verifiedIds [] = $ idValue ;
545+
546+ $ ref = $ signNode ->getElementsByTagName ('Reference ' );
547+ if (!empty ($ ref )) {
548+ $ ref = $ ref ->item (0 );
549+ $ sei = $ ref ->getAttribute ('URI ' );
550+ if (!empty ($ sei )) {
551+ $ sei = substr ($ sei , 1 );
552+
553+ if ($ sei != $ idValue ) {
554+ throw new Exception ('Found an invalid Signed Element. SAML Response rejected ' );
555+ }
556+
557+ if (in_array ($ sei , $ verifiedSeis )) {
558+ throw new Exception ('Duplicated Reference URI. SAML Response rejected ' );
559+ }
560+ $ verifiedSeis [] = $ sei ;
561+ }
562+ }
563+ $ signedElements [] = $ signedElement ;
564+ }
565+
566+ if (!empty ($ signedElements )) {
567+ // Check SignedElements
568+ if (!$ this ->validateSignedElements ($ signedElements )) {
569+ throw new Exception ('Found an unexpected Signature Element. SAML Response rejected ' );
570+ }
571+ }
572+ return $ signedElements ;
573+ }
574+
514575 /**
515576 * Verifies that the document is still valid according Conditions Element.
516577 *
@@ -580,8 +641,7 @@ protected function _queryAssertion($assertionXpath)
580641 $ xpath ->registerNamespace ('ds ' , OneLogin_Saml2_Constants::NS_DS );
581642 $ xpath ->registerNamespace ('xenc ' , OneLogin_Saml2_Constants::NS_XENC );
582643
583- $ assertionNode = $ this ->encrypted ? '/samlp:Response/saml:EncryptedAssertion/saml:Assertion '
584- : '/samlp:Response/saml:Assertion ' ;
644+ $ assertionNode = '/samlp:Response/saml:Assertion ' ;
585645 $ signatureQuery = $ assertionNode . '/ds:Signature/ds:SignedInfo/ds:Reference ' ;
586646 $ assertionReferenceNode = $ xpath ->query ($ signatureQuery )->item (0 );
587647 if (!$ assertionReferenceNode ) {
@@ -595,9 +655,9 @@ protected function _queryAssertion($assertionXpath)
595655 } else {
596656 $ id = substr ($ assertionReferenceNode ->attributes ->getNamedItem ('URI ' )->nodeValue , 1 );
597657 }
598- $ nameQuery = "/samlp:Response[@ID=' $ id']/ " .( $ this -> encrypted ? " saml:EncryptedAssertion/ " : "" ). " saml:Assertion " . $ assertionXpath ;
658+ $ nameQuery = "/samlp:Response[@ID=' $ id']/saml:Assertion " . $ assertionXpath ;
599659 } else {
600- $ nameQuery = "/samlp:Response/ " .( $ this -> encrypted ? " saml:EncryptedAssertion/ " : "" ). " saml:Assertion " . $ assertionXpath ;
660+ $ nameQuery = "/samlp:Response/saml:Assertion " . $ assertionXpath ;
601661 }
602662 } else {
603663 $ uri = $ assertionReferenceNode ->attributes ->getNamedItem ('URI ' )->nodeValue ;
@@ -668,11 +728,18 @@ protected function _decryptAssertion($dom)
668728 if (empty ($ objKey ->key )) {
669729 $ objKey ->loadKey ($ key );
670730 }
671- $ decrypt = $ objenc ->decryptNode ($ objKey , true );
672- if ($ decrypt instanceof DOMDocument) {
673- return $ decrypt ;
731+
732+ $ decrypted = $ objenc ->decryptNode ($ objKey , true );
733+
734+ if ($ decrypted instanceof DOMDocument) {
735+ return $ decrypted ;
674736 } else {
675- return $ decrypt ->ownerDocument ;
737+ $ encryptedAssertion = $ decrypted ->parentNode ;
738+ $ container = $ encryptedAssertion ->parentNode ;
739+
740+ $ container ->replaceChild ($ decrypted , $ encryptedAssertion );
741+
742+ return $ decrypted ->ownerDocument ;
676743 }
677744 }
678745
0 commit comments