I observed this on 4.x. When a SAML Response includes a signed <saml:Assertion> containing an <saml:EncryptedID>, the signature check fails.
The problem stems from OneLogin\Saml2\Response::decryptAssertion(), which decrypts the <saml:EncryptedID> right after decrypting the <saml:EncryptedAssertion>. This looks like an optimization to cache the decrypted NameID for later use in getNameIdData(), but it breaks things.
There's two issues:
-
The assertion is altered before validating the signature. Since the original signature was generated with the EncryptedID in place, any modification (like decryption) before validation invalidates the digest.
-
The decryptNameId() method sends the wrong key type to Utils::decryptElement(), which causes an algorithm mismatch error. For example:
- The
decryptAssertion() method is called. It successfully decrypts the main <saml:EncryptedAssertion> block.
- It then finds the
<saml:EncryptedID> inside the now-decrypted assertion.
- It calls a new method,
decryptNameId(), to handle this inner decryption.
- The
decryptNameId() method correctly uses the private key to decrypt the session key that is wrapped inside the <saml:EncryptedID>.
- However, it then passes this newly-decrypted session key (which uses
aes128-cbc) to the Utils::decryptElement() function.
- The
Utils::decryptElement() function expects the private key (which uses rsa-oaep-mgf1p) to start its own process of decrypting the session key. When it sees it has been given a key of the wrong type, it throws the "Algorithm mismatch" error.
Seems like the new code is trying to do the same decryption step twice, passing the result of the first attempt as the input to the second.
Fix:
Defer the EncryptedID decryption until getNameIdData() runs, after the signature has been verified. Add memoization in getNameIdData() so the decryption happens once and is reused.
--- a/src/Saml2/Response.php
+++ b/src/Saml2/Response.php
@@ -55,6 +55,13 @@
* @var int
*/
private $_validSCDNotOnOrAfter;
+
+/**
+ * Cached NameID Data
+ *
+ * @var array
+ */
+private $_nameIdData = array();
@@ -500,6 +507,10 @@
*/
public function getNameIdData()
{
+ if (!empty($this->_nameIdData)) {
+ return $this->_nameIdData;
+ }
+
$encryptedIdDataEntries = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData');
if ($encryptedIdDataEntries->length == 1) {
@@ -548,7 +559,8 @@
}
}
- return $nameIdData;
+ $this->_nameIdData = $nameIdData;
+ return $this->_nameIdData;
}
@@ -1013,10 +1025,8 @@
if ($encryptedID) {
// decrypt the encryptedID
$this->encryptedNameId = true;
- $encryptedData = $encryptedID->getElementsByTagName('EncryptedData')->item(0);
- $nameId = $this->decryptNameId($encryptedData, $pem);
- Utils::treeCopyReplace($encryptedID, $nameId);
+ // Decryption of NameID is handled in getNameIdData() after signature validation.
}
if ($encData->parentNode instanceof DOMDocument) {
I observed this on 4.x. When a SAML Response includes a signed
<saml:Assertion>containing an<saml:EncryptedID>, the signature check fails.The problem stems from
OneLogin\Saml2\Response::decryptAssertion(), which decrypts the<saml:EncryptedID>right after decrypting the<saml:EncryptedAssertion>. This looks like an optimization to cache the decrypted NameID for later use ingetNameIdData(), but it breaks things.There's two issues:
The assertion is altered before validating the signature. Since the original signature was generated with the
EncryptedIDin place, any modification (like decryption) before validation invalidates the digest.The
decryptNameId()method sends the wrong key type toUtils::decryptElement(), which causes an algorithm mismatch error. For example:decryptAssertion()method is called. It successfully decrypts the main<saml:EncryptedAssertion>block.<saml:EncryptedID>inside the now-decrypted assertion.decryptNameId(), to handle this inner decryption.decryptNameId()method correctly uses the private key to decrypt the session key that is wrapped inside the<saml:EncryptedID>.aes128-cbc) to theUtils::decryptElement()function.Utils::decryptElement()function expects the private key (which usesrsa-oaep-mgf1p) to start its own process of decrypting the session key. When it sees it has been given a key of the wrong type, it throws the "Algorithm mismatch" error.Seems like the new code is trying to do the same decryption step twice, passing the result of the first attempt as the input to the second.
Fix:
Defer the
EncryptedIDdecryption untilgetNameIdData()runs, after the signature has been verified. Add memoization ingetNameIdData()so the decryption happens once and is reused.