-
-
Notifications
You must be signed in to change notification settings - Fork 233
Refactor to support encrypted assertions #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # 2.10.1 | ||
|
|
||
| * Changes to libsaml.ts verifySignature. This is an internal function, but we still document changes | ||
| - Does not raise error when signature is missing/invalid. Instead it now returns false. This is to simplify logic | ||
| - When there are encrypted assertions, returns the entire response, as the "verifiedAssertionNode" | ||
|
|
||
| * Fix logic around handling encrypted assertions |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -366,7 +366,7 @@ const libSaml = () => { | |
| * - The first element is `true` if the signature is valid, `false` otherwise. | ||
| * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found. | ||
| */ | ||
| verifySignature(xml: string, opts: SignatureVerifierOptions) { | ||
| verifySignature(xml: string, opts: SignatureVerifierOptions) : [boolean, string | null] { | ||
| const { dom } = getContext(); | ||
| const doc = dom.parseFromString(xml); | ||
|
|
||
|
|
@@ -395,10 +395,9 @@ const libSaml = () => { | |
|
|
||
| // guarantee to have a signature in saml response | ||
| if (selection.length === 0) { | ||
| throw new Error('ERR_ZERO_SIGNATURE'); | ||
| return [false, null]; // we return false now | ||
| } | ||
|
|
||
|
|
||
| // need to refactor later on | ||
| for (const signatureNode of selection){ | ||
| const sig = new SignedXml(); | ||
|
|
@@ -457,44 +456,50 @@ const libSaml = () => { | |
|
|
||
| sig.loadSignature(signatureNode); | ||
|
|
||
| doc.removeChild(signatureNode); | ||
|
|
||
| verified = sig.checkSignature(doc.toString()); | ||
|
|
||
| // immediately throw error when any one of the signature is failed to get verified | ||
| if (!verified) { | ||
| throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE'); | ||
| continue; | ||
| // throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE'); | ||
| } | ||
|
Comment on lines
459
to
465
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confusingly,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We upgraded from 3.0 xml-crypto to 6.0. In 3.0 they used to throw error, however the 6.0 they did some changes to return false. When I gave the security patches for xml-crypto 6.0.1, I had to keep it to return false. The downside being for libraries like samlify which were previously on 3.0, it now returns false.
I'm not sure what is the best way here. You can propose patches in the libsaml.verifySignature on how to handle errors. |
||
| // attempt is made to get the signed Reference as a string(); | ||
| // note, we don't have access to the actual signedReferences API unfortunately | ||
| // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used) | ||
| if (!(sig.getReferences().length >= 1)) { | ||
| // Require there to be at least one reference that was signed | ||
| if (!(sig.getSignedReferences().length >= 1)) { | ||
| throw new Error('NO_SIGNATURE_REFERENCES') | ||
| } | ||
| const signedVerifiedXML = sig.getSignedReferences()[0]; | ||
| const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement; | ||
| // process the verified signature: | ||
| // case 1, rootSignedDoc is a response: | ||
| if (rootNode.localName === 'Response') { | ||
|
|
||
| // try getting the Xml from the first assertion | ||
| const assertions = select( | ||
| "./*[local-name()='Assertion']", | ||
| rootNode | ||
| ); | ||
|
|
||
| const encryptedAssertions = select( | ||
| "./*[local-name()='EncryptedAssertion']", | ||
| rootNode | ||
| ); | ||
| // now we can process the assertion as an assertion | ||
| if (assertions.length === 1) { | ||
| return [true, assertions[0].toString()]; | ||
| } else if (encryptedAssertions.length >= 1) { | ||
| return [true, rootNode.toString()]; // we need to return a Response node, which will be decrypted later | ||
| } else { | ||
| // something has gone seriously wrong here. | ||
| // we don't have any assertion to give back | ||
| return [true, null] | ||
| } | ||
| } else if (rootNode.localName === 'Assertion') { | ||
| return [true, rootNode.toString()]; | ||
| } else { | ||
| return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null | ||
| } | ||
| }; | ||
| return [false, null]; // we didn't verify anything, none of the signatures are valid | ||
|
|
||
| // something has gone seriously wrong if we are still here | ||
| throw new Error('ERR_ZERO_SIGNATURE'); | ||
|
|
||
| /* | ||
| // response must be signed, either entire document or assertion | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ahacker1-securesaml I didn't catch this on first review, but I'm having test failures now; is there a reason why you didn't overwrite
samlContenton this line? As is,samlContenthas anAssertionnode for encrypt-then-sign flows, but still has theEncryptedAssertionnode for sign-then-encrypt flows.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
Thanks for reviewing. Can you share your test failure. I'm not getting any test errors when I run
yarn testin my local machine.I don't think overwriting the samlContent will change the behavior.
Here's how libsaml extracts the fields, given an assertion, and a response node.
https://github.com/SecureSAML/samlifyfork/blob/59bf3df3cf2c7882613c674f25ccb41e03f75472/src/extractor.ts#L100-L148
Currently samlContent stores a Response node (that contains an assertion or encrypted assertion nodes).
In the first case:
This is actually the response node with decrypted asesertion
In second case, where I didn't change samlContent, samlContent would be the original SAML Response with a Response with encrypted Assertion
Even with a response with EncryptedAssertion, we can extract the fields for the Response node. What matters is extracting the fields for the assertion. As you can see:
The assertion in the extracted fields is the decrypted and signed assertion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the test failures I'm seeing are in my applications that use samlify. I wasn't referring to tests in this lib. Although I'll argue this feature is missing test coverage currently.
And the extracted fields returned in
FlowResultare working as expected in all cases.The part that I'm calling out is the
samlContentproperty ofFlowResult. It never contained the EncryptedAssertion before.Having access to the decoded and decrypted XML content, after calling
parseLoginResponse, is extremely helpful when debugging payloads from third parties.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification. I do agree that the samlContent should be the decrypted response, i.e. set the samlContent to results[0] in the second branch.
Thanks again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @tngan @mastermatt , just released the fix here:
#575