Signature Validation Failure With EncryptedID In SAML Assertions A Solution
Hey guys! Ever stumbled upon a pesky issue where your signature validation fails when dealing with SAML assertions containing an EncryptedID? I recently ran into this in version 4.2.0, and it was quite the head-scratcher. Let's dive into the problem, understand why it happens, and explore a solid fix.
Understanding the Issue
When a SAML Response includes a signed <saml:Assertion>
that contains a <saml:EncryptedID>
, the signature check can fail. This isn't just a minor inconvenience; it's a critical issue that can break the entire authentication process. Let’s break down what's happening under the hood.
The root cause lies within the OneLogin\Saml2\Response::decryptAssertion()
method. This method decrypts the <saml:EncryptedID>
immediately after decrypting the <saml:EncryptedAssertion>
. At first glance, this might seem like an optimization to cache the decrypted NameID for later use in getNameIdData()
. However, this seemingly innocent optimization introduces two significant problems that lead to signature validation failures.
1. Assertion Modification Before Validation
The first and most critical issue is that the assertion gets altered before its signature is validated. Remember, the original signature was generated with the <saml:EncryptedID>
in its encrypted form. Any modification to the assertion—such as decrypting the ID—before validation effectively invalidates the digest. It’s like signing a document and then changing it before verifying the signature; the signature no longer matches the content.
To put it simply, the signature is calculated based on the encrypted version of the ID, but the validation is performed on the decrypted version. This mismatch is a surefire way to make the signature verification fail. This is a critical point to grasp because SAML's security model relies heavily on the integrity of these signatures. If we can't trust the signature, we can't trust the assertion, and we're left with a major security vulnerability.
2. Algorithm Mismatch in Decryption
The second issue stems from the decryptNameId()
method sending the wrong key type to Utils::decryptElement()
, leading to an algorithm mismatch error. Let’s walk through an example to illustrate this:
- The
decryptAssertion()
method is initially called. It successfully decrypts the main<saml:EncryptedAssertion>
block. So far, so good! - The method then encounters the
<saml:EncryptedID>
inside the now-decrypted assertion. This is where things start to go awry. - To handle this inner decryption, a new method,
decryptNameId()
, is invoked. - Inside
decryptNameId()
, the private key is correctly used to decrypt the session key wrapped inside the<saml:EncryptedID>
. This step itself is correct, and the session key is successfully retrieved. - However, the problem arises when this newly-decrypted session key (which uses
aes128-cbc
) is passed to theUtils::decryptElement()
function. - The
Utils::decryptElement()
function expects the private key (which usesrsa-oaep-mgf1p
) to start its process of decrypting the session key. It's like trying to use a wrench when you need a screwdriver. The function is expecting one type of key but receiving another. - Consequently, when
Utils::decryptElement()
detects this key type mismatch, it throws the dreaded