-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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
Multiple enveloped signatures #67010
Conversation
Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones Issue DetailsHandle multiple enveloped XML signatures in the same document. This is fairly common for Saml2 implementations.
|
src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Xml/tests/SignedXmlTest.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Reference.cs
Outdated
Show resolved
Hide resolved
@AndersAbel What issue did you have running tests locally? It should work 🙂 |
@danmoseley I didn't read the instructions on setting up the environment well enough. Once I found my way to https://github.com/dotnet/runtime/blob/main/docs/workflow/building/libraries/README.md it got clearer and it now works. |
I might need some help here on the build and what is expected to pass or not. On the .NET Framework target, is the .NET Framework version of SignedXml used? In that case it is expected to fail as that bug was present already in the .NET Framework version of SignedXml. Should I exclude those tests for the .NET Framework target? I need some feedback on how to proceed. Once the failure are sorted out I'll remove the reflection again before merge. I can leave them commented out if anyone needs them in future troubleshooting, but I don't think reflection belongs in the live test harness. |
@AndersAbel the easiest way to find out which dll you're using is under the debugger (in VS open the Modules window). Simply put
|
Thanks. That's the reason. I'll comment out the reflection stuff and make the PR ready for final review. |
Looks like the build failed due to some infrastructure issues and that it is now unavailable. The contents are ready for review. |
/azp run |
I see it running? |
@danmoseley That's a stale run that has been trying to complete for 2 days... Thanks for the trick, I'll close and reopen. |
@@ -260,12 +260,20 @@ public void LoadXml(XmlElement value!!) | |||
// let the transform read the children of the transformElement for data | |||
transform.LoadInnerXml(transformElement.ChildNodes); | |||
// Hack! this is done to get around the lack of here() function support in XPath | |||
if (transform is XmlDsigEnvelopedSignatureTransform) | |||
if (transform is XmlDsigEnvelopedSignatureTransform | |||
&& _uri != null && (_uri.Length == 0 || _uri[0] == '#')) |
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.
should we throw if _uri
somehow ends up null?
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.
Null means "whole document"... if I recall correctly?
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.
No, I don't think so. Null means "custom data source application or first transform should now about". In that case an enveloped signature transform makes no sense and should become a no-op. It is not illegal to include an enveloped signature transform where it is not needed. So with null, I think the reasonable thing to do is to just ignore.
@bartonjs an empty Uri means whole document, and that is taken care of in the code.
//void Main() | ||
//{ | ||
// var xml = "<root><x ID=\"a\"/><x ID=\"b\"><x ID=\"c\"><x ID=\"y\"/></x></x></root>"; | ||
// | ||
// var xd = new XmlDocument(); | ||
// xd.LoadXml(xml); | ||
// | ||
// Sign(xd, "c", "y"); | ||
// Sign(xd, "b", "b"); | ||
// Sign(xd, "a", "a"); | ||
//} | ||
// | ||
//void Sign(XmlDocument xd, string id, string signaturePlacement) | ||
//{ | ||
// var sx = new SignedXml(xd); | ||
// var key = RSA.Create(); | ||
// sx.SigningKey = key; | ||
// | ||
// var reference = new Reference("#" + id); | ||
// reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); | ||
// reference.AddTransform(new XmlDsigExcC14NTransform()); | ||
// sx.AddReference(reference); | ||
// | ||
// sx.ComputeSignature(); | ||
// sx.KeyInfo.AddClause(new RSAKeyValue(key)); | ||
// | ||
// var node = xd.SelectSingleNode("//x[@ID=\'" + signaturePlacement + "']"); | ||
// | ||
// var signatureElement = sx.GetXml(); | ||
// | ||
// node.AppendChild(sx.GetXml()); | ||
//} |
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.
Why not calculate it once and assign in a static field?
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 think I prefer the approach of using the pregenerated value. Makes it easier to understand what's going on, and is more like a conformance test.
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 agree with @bartonjs (Because he agrees with me ;) I think that the pregenerated value as it is makes it easier to read the code and understand the test. Including the generation code as a comment is a convenience to any future programmer that want to alter the test case, so they can easily reproduce the current generated data.
XmlNode referenceTarget = | ||
_uri.Length == 0 | ||
? transformElement.OwnerDocument | ||
: SignedXml.GetIdElement(transformElement.OwnerDocument, Utils.GetIdFromLocalUri(_uri, out bool _)); |
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.
we should also test for case where reference is incorrect (i.e. someone tampered with the doc)
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.
True. While the behavior we're fixing is "we always return failure in this case", let's make sure that we haven't turned complex nesting into so now it "always succeeds, regardless of signature correctness"
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.
This fix is about the loading of the signature data and not the actual validation. While the test coverage can certainly be improved, I think that is a different task. I do have some validation tests in https://github.com/Sustainsys/Saml2/blob/c466d3bbf12479944e350b649f5b5c825616007b/Tests/Tests.Shared/XmlHelpersTests.cs#L171 that I could carry over. Right now they are testing my wrapper around SignedXml, but essentially they are testing SignedXml.
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.
This fix is about the loading of the signature data and not the actual validation.
Sure. But from a black-box perspective we now have a test that shows CheckSignature()
can return true
with complex nesting. Returning false
when it should is arguably more important than returning true
when it should, so we should have a test that ensures that this change (from a black box perspective) isn't creating a hole.
Essentially something like load the same document as the positive case, and via a [Theory]
input change each of a/b/c/y (one per case) to add some extra attribute (or child node, whatever), and show that CheckSignature() returns false. It doesn't need to care about full matrix testing all of the algorithms, just that we haven't turned complex nesting from "never works" into "free pass".
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.
Ok, I'll add that.
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.
Done.
@@ -260,12 +260,20 @@ public void LoadXml(XmlElement value!!) | |||
// let the transform read the children of the transformElement for data | |||
transform.LoadInnerXml(transformElement.ChildNodes); | |||
// Hack! this is done to get around the lack of here() function support in XPath | |||
if (transform is XmlDsigEnvelopedSignatureTransform) | |||
if (transform is XmlDsigEnvelopedSignatureTransform | |||
&& _uri != null && (_uri.Length == 0 || _uri[0] == '#')) |
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.
make sure to cover case where _uri
is null (I assume it's when Uri
attribute is missing) or point to a test case covering it
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.
The existing test LoadEnvelopedSignatureTransforms already tests the flow where _uri == null
during loading.
Tests works locally, but not on build server. Temporarily using some dark reflection magic to check the internal index that the fix is about to see the values as run on the build server.
- Skip test on .NET Framework target as it will fail. - Comment out reflection-based tests. - Add comment with test data generation code for future coders to reference
365c6b9
to
8697078
Compare
- Test for empty reference URI, which implies whole document
- Positive and negative tests for tampered data in doc with multiple enveloped signatures
77d0289
to
8a6bbe8
Compare
I've added some more tests and I think I've now handled all the review comments. Please let me know if there is anything more you need to be able to merge this. |
Thanks, @AndersAbel |
public void CheckSignatureDetectsTamperedDataOnMultipleEnvelopedSignatures( | ||
string signatureParent, string tamperNode, bool expected) | ||
{ | ||
var tampered = multipleSignaturesXml.Replace($"ID=\"{tamperNode}\"", $"ID=\"{tamperNode}\" Hackerz=\"true\""); |
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'm still missing a case like:
- replace uri with some nonexisting ID
- replace uri with some existing ID but different than original
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.
Added test in #67501 and it actually caught a bug/changed behaviour that I also fixed in that PR.
[InlineData("a", "b", true)] | ||
[InlineData("b", "b", false)] | ||
[InlineData("b", "c", false)] | ||
[InlineData("y", "b", true)] |
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.
why are some of these true?
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.
Because the altered note is outside the validated content for the signature.
Handle multiple enveloped XML signatures in the same document. This is fairly common for Saml2 implementations.
Fixes #27500