-
-
Notifications
You must be signed in to change notification settings - Fork 593
[WIP] v2.x - Nokogiri Refactor Part 6 - Conversion of SignedDocument and final removal of REXML 🎉 #759
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
[WIP] v2.x - Nokogiri Refactor Part 6 - Conversion of SignedDocument and final removal of REXML 🎉 #759
Conversation
06a205c to
ae09439
Compare
…efactor' into signed-document-info-refactor
|
@ahacker1-securesaml, @p- can you help me review this PR? Thanks |
|
If we are refactoring, I just suggest we remove the Response/SignedDocument class, and make it into a function instead. This OOP style is extremely hard to audit. |
|
@pitbulk Yes I will help 👍 But I think I can't start before next week. |
|
Hi, yes I agree I am intending to make the code more functional. This PR was intended firstly to get all tests green on Nokogiri while changing relatively little. I am thinking something like separating
I need to think a bit more about exactly where the I may not have time to look at this further for a few weeks however. |
|
ok, ResponseValidator -> change to Assertion Validator, we don't care about the Response, since IdPs don't even sign it. Also don't mix this with the signature verification steps. Response (Parser) -> Since the individual assertions are signed, this should return a list of Signed Assertions, and not nesscarily the whole (unsigned) SAML Authentication response. And maybe we should do something like SignedAssertion class? Would remove the SignedDocument class. Let's replace this with something called SignedXML, which cryptographically verifies some bytes of XML at instantianation. Response parser can then accept SignedXML only. Hence, whatever is used for the Response class is always signed. |
|
@ahacker1-securesaml I agree with all those points, that sounds along the right lines. I will need more time to get my head around it, the code is really spaghetti today 🍝 and the real trick is getting all the tests to pass (fortunately the test suite is quite robust!!) |
| # Get the ID of the signed element | ||
| # @return [String] The ID of the signed element | ||
| def subject_id | ||
| # TODO: The error here is problematic, perhaps it can be checked elsewhere |
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.
I would not recommend exposing an the ID of signed Element. It can't be used for security decisions, since the ID is not guaranteed to be unique & is attacker controlled.
| # Get the Reference node | ||
| # @return [Nokogiri::XML::Element] The Reference node | ||
| def reference_node | ||
| signed_info_node.at_xpath('./ds:Reference', { 'ds' => RubySaml::XML::DSIG }) || |
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.
Load this from the canonicalized_signed_info, since only canonicalized_signed_info was authenticated
| return nil unless cert | ||
|
|
||
| fingerprint_alg = RubySaml::XML.hash_algorithm(algorithm).new | ||
| fingerprint_alg.hexdigest(cert.to_der).gsub(/[^a-zA-Z0-9]/, '').downcase |
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.
Authenticated = cert.to_der, should only use cert.to_der in future, and not cert
|
The main challenge is to: First, verify the structure, number of signatures, number of elements, etc
And once the signature is validated and we know we can trust the Assertion (because it had a Signature, or its Response has a Signature), use the right XML to keep validating rest of elements and make that the methods that retrieve info (attributes, nameId, etc use the right XML as well).
The main problem is that most of the tests use payloads with invalid signatures and in the current implementation, this is not a problem due the validation order. As making signature validation optional is something that we better don't even add in the code, I will try to start updating the |
|
@pitbulk making the signatures validatable in the tests would be a huge help to give us flexibility in refactoring. |
|
First step should be verifying signature IMO. After verifying signature, we can get some signed bytes of XML. Then Assertion re-parses the signed bytes of XML, and verifies the assertion data. That gives us much more flexibility, if we want to test some assertion on some unsigned data, we simply mock the payload as signed. |
|
@ahacker1-securesaml, If we identify that something is wrong in the XML (xsd validation, number of elements that we want to accept, etc) we save us of checking signatures on XMLs that we gonna consider already invalids. |
|
I can see the performance benefits there. However I feel strongly that we would be mixing the SAML Assertion processing logic and the Signature logic. Ideally we want to keep them separate as much as possible. And separating logic would help with security |
|
@ahacker1-securesaml, would you be able to add some context to the following?
Signing the |
|
Three ways SAML can be signed: Assertions alone: default for most Identity Providers In the responses alone case, I have seem some SAML libraries only verify signatures on SAML Assertions. So it would break their code. For the Assertions & Responses case: an attacker can trivially remove the signature on the response, and the SAML payload would still be valid. For assertions alone case: an attacker can just change whatever they want on the response i.e. statuscode ... So IMO, there's no point in verifying the properties of a SAML responses i.e. checking status code .... It would mean mixing unsigned data (Response) and the signed data (Assertion). That makes it really hard to analyze in terms of security, and has directly lead to vulnerabilities in libraries such as https://github.com/node-saml/node-saml My idea is to only verify whatever is in the (signed) Assertion, be it the AudienceRestriction. These are actually important for security AND we separate the signature verification logic into it's own module. I don't believe it should be part of verifying the assertion logic. |
|
We still support signed responses, we separate business logic from signature verification steps extracted_signature = extract_signature(untrusted_doc)
signed_bytes = get_signed_bytes(extracted_signature)
# if signed_bytes happens to be a Response, then we get the Assertion from ./saml:Assertion
assertion = ParseAssertion(signed_bytes)
AssertionValidator.validate(assertion) # business logic
return assertionwe just don't verify the business logic of a Response that the SAML spec supposedly requires. For example, verifying the StatusCode, or even the Version. |
|
It would definitely help us to get a library of all the vendor IDP SAML variations in the wild... maybe some other SAML lib already has one? As it stands the current RubySaml test suite is pretty good, it should protect us from a pretty wide range of possible regressions. |
|
In terms of security, I agree that if the Response is not signed, and the StatusCode is not "protected" anyone can modify it, but in terms of business, if I receive a StatusCode != Success, I don't need to do anything else, I will simply reject this SAMLResponse and don't grant access to the app, the same applies to malformed XMLs received or XMLs wellformed, but that we want to avoid and consider invalids (multiple Assertions, more than 2 Signatures). |
|
@johnnyshields This PR works fine with omniauth-saml. Anything specific I can help test? |
|
Just a heads up here--@pitbulk I'm not going to have the bandwidth for several months to take this PR any further--have to focus on running my company. So the last mile will have to be carried by someone else. |
|
Understood, thanks for your hard work! |
|
@pitbulk I think we should merge this branch into v2.x--or even master--regardless. I realize there are more refactors needed, but perhaps we can track those in a follow-up ticket? |
|
@johnnyshields, I was able to reorder the validations and adjust tests and payloads: |
|
@pitbulk I will release this branch to production tomorrow. I think after it runs stablely for a week we should look at releasing it as v2.x. It's not "perfect" but it's a heck of a lot better than the current v1.x main release. |
|
@johnnyshields @pitbulk anything I can do to help with v2.x? Happy to lend a hand here or elsewhere. |
|
@pitbulk I've been using this branch extensively in production for > 1 month without issue for 10+ different SAML integrations. Let's merge and release it--it's infinitely better than the current v1.x branch. |
|
Ok, I will review next week and merge and release a 2.0.0 with this code. Is anything else missing? |
|
@pitbulk no, nothing is missing, it's ready to release. |
|
Sorry for the delay!, I'm doing some final test this weekend and gonna be released next week. |
| In `2.0.0`, REXML has been replaced with Nokogiri. This change should be transparent | ||
| to most users, however, see note about Custom Metadata Fields below. | ||
| In `2.0.0`, REXML has been replaced with Nokogiri. As a result, there are minor differences | ||
| in how XML is generated, ncluding SAML requests and SP Metadata: |
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.
including
|
Wonderful news! |
This PR resolves the "Parser Differential" exploit vector mentioned below, by migrating to 100% Nokogiri.
https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/