-
-
Notifications
You must be signed in to change notification settings - Fork 498
Description
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
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:- 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 theUtils::decryptElement()function. - The
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.
- The
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) {