diff --git a/Kentor.AuthServices.TestHelpers/SignedXmlHelper.cs b/Kentor.AuthServices.TestHelpers/SignedXmlHelper.cs index d3ab65cd9..2392b54ee 100644 --- a/Kentor.AuthServices.TestHelpers/SignedXmlHelper.cs +++ b/Kentor.AuthServices.TestHelpers/SignedXmlHelper.cs @@ -19,12 +19,12 @@ public class SignedXmlHelper (new X509SecurityToken(TestCert)) .CreateKeyIdentifierClause())); - public static string SignXml(string xml) + public static string SignXml(string xml, bool includeKeyInfo = false, bool preserveWhitespace = true) { - var xmlDoc = new XmlDocument { PreserveWhitespace = true }; + var xmlDoc = new XmlDocument { PreserveWhitespace = preserveWhitespace }; xmlDoc.LoadXml(xml); - xmlDoc.Sign(TestCert); + xmlDoc.Sign(TestCert, includeKeyInfo); return xmlDoc.OuterXml; } diff --git a/Kentor.AuthServices.Tests/Saml2P/Saml2ResponseTests.cs b/Kentor.AuthServices.Tests/Saml2P/Saml2ResponseTests.cs index a83ca3985..c8e5b0e85 100644 --- a/Kentor.AuthServices.Tests/Saml2P/Saml2ResponseTests.cs +++ b/Kentor.AuthServices.Tests/Saml2P/Saml2ResponseTests.cs @@ -211,6 +211,39 @@ public void Saml2Response_GetClaims_CorrectSignedSingleAssertionInResponseMessag a.ShouldNotThrow(); } + [TestMethod] + public void Saml2Response_GetClaims_CorrectSignedSingleAssertion_WithKeyInfo_InResponseMessage() + { + var response = + @" + https://idp.example.com + + + + {0} + "; + + var assertion = + @" + https://idp.example.com + + SomeUser + + + + "; + + var signedAssertion = SignedXmlHelper.SignXml(string.Format(assertion, Guid.NewGuid()), true, false); + var signedResponse = string.Format(response, signedAssertion); + + Action a = () => Saml2Response.Read(signedResponse).GetClaims(Options.FromConfiguration); + a.ShouldNotThrow(); + } + [TestMethod] [Ignore] public void Saml2Response_GetClaims_CorrectSignedMultipleAssertionInResponseMessage() @@ -258,6 +291,51 @@ public void Saml2Response_GetClaims_CorrectSignedMultipleAssertionInResponseMess a.ShouldNotThrow(); } + [TestMethod] + public void Saml2Response_GetClaims_CorrectSignedMultipleAssertion_WithKeyInfo_InResponseMessage() + { + var response = + @" + https://idp.example.com + + + + {0} + {1} + "; + + var assertion1 = @" + https://idp.example.com + + SomeUser + + + + "; + + var assertion2 = @" + https://idp.example.com + + SomeUser2 + + + + "; + + var signedAssertion1 = SignedXmlHelper.SignXml(string.Format(assertion1, Guid.NewGuid()), true, false); + var signedAssertion2 = SignedXmlHelper.SignXml(string.Format(assertion2, Guid.NewGuid()), true, false); + var signedResponse = string.Format(response, signedAssertion1, signedAssertion2); + + Action a = () => Saml2Response.Read(signedResponse).GetClaims(Options.FromConfiguration); + a.ShouldNotThrow(); + } + [TestMethod] public void Saml2Response_GetClaims_ThrowsOnMultipleAssertionInUnsignedResponseMessageButNotAllSigned() { diff --git a/Kentor.AuthServices/XmlDocumentExtensions.cs b/Kentor.AuthServices/XmlDocumentExtensions.cs index 769cdf373..e3a15b6c3 100644 --- a/Kentor.AuthServices/XmlDocumentExtensions.cs +++ b/Kentor.AuthServices/XmlDocumentExtensions.cs @@ -19,6 +19,19 @@ public static class XmlDocumentExtensions /// Certificate to use when signing. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")] public static void Sign(this XmlDocument xmlDocument, X509Certificate2 cert) + { + Sign(xmlDocument, cert, false); + } + + /// + /// Sign an xml document with the supplied cert. + /// + /// XmlDocument to be signed. The signature is + /// added as a node in the document, right after the Issuer node. + /// Certificate to use when signing. + /// Include public key in signed output. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")] + public static void Sign(this XmlDocument xmlDocument, X509Certificate2 cert, bool includeKeyInfo) { if (xmlDocument == null) { @@ -48,6 +61,13 @@ public static void Sign(this XmlDocument xmlDocument, X509Certificate2 cert) signedXml.AddReference(reference); signedXml.ComputeSignature(); + if (includeKeyInfo) + { + var keyInfo = new KeyInfo(); + keyInfo.AddClause(new KeyInfoX509Data(cert)); + signedXml.KeyInfo = keyInfo; + } + xmlDocument.DocumentElement.InsertAfter( xmlDocument.ImportNode(signedXml.GetXml(), true), xmlDocument.DocumentElement["Issuer", Saml2Namespaces.Saml2Name]);