From 3b757c8b2491cda11830631aed86430a7b8fc507 Mon Sep 17 00:00:00 2001 From: Roman Polunin Date: Mon, 5 Dec 2016 15:02:59 -0800 Subject: [PATCH] Version 1.0.6. Switched to NET462, cleaned up some code, verified NET462 support for CNG cert store. --- Build/Fedlet.1.0.nuspec | 8 +- Build/buildnuget.ps1 | 10 +- CHANGES.txt | 9 +- Fedlet.UnitTests/Fedlet.UnitTests.csproj | 2 +- Fedlet.UnitTests/Properties/AssemblyInfo.cs | 4 +- Fedlet/Fedlet.csproj | 2 +- Fedlet/Properties/AssemblyInfo.cs | 4 +- Fedlet/Saml2/ArtifactResponse.cs | 3 +- Fedlet/Saml2/AuthnResponse.cs | 3 +- Fedlet/Saml2/IdentityProvider.cs | 3 +- Fedlet/Saml2/LogoutRequest.cs | 5 +- Fedlet/Saml2/LogoutResponse.cs | 3 +- Fedlet/Saml2/Saml2Constants.cs | 15 -- Fedlet/Saml2/Saml2Utils.cs | 276 ++++++++++---------- Fedlet/Saml2/ServiceProvider.cs | 3 +- Fedlet/Saml2/ServiceProviderUtility.cs | 12 +- 16 files changed, 176 insertions(+), 186 deletions(-) diff --git a/Build/Fedlet.1.0.nuspec b/Build/Fedlet.1.0.nuspec index cbbc934..1b66294 100644 --- a/Build/Fedlet.1.0.nuspec +++ b/Build/Fedlet.1.0.nuspec @@ -2,7 +2,7 @@ RomanPolunin.SamlFedlet - 1.0.5 + 1.0.6 Roman Polunin Roman Polunin Fedlet, a SAML SSO Toolkit @@ -25,9 +25,9 @@ Forked that to make functionality and stability improvements. false SAML SSO Portions copyright (c) Roman Polunin 2015-2016. Copyright (c) Sun Microsystems 2019-2010 - Fedlet 1.0.5.0 - July 2016 -Fixed wrong attribute name for AssertionConsumerServiceIndex in the outgoing SAMLRequest. -Added default value for NameIDPolicyFormat ("unspecified") if not supplied by client code. + Fedlet 1.0.6.0 - Dec 2016 +Switched targeting to .NET 462. +Expects .Net framework to be able to work with CNG certs without additional hacks. diff --git a/Build/buildnuget.ps1 b/Build/buildnuget.ps1 index 1b70be5..ab63d01 100644 --- a/Build/buildnuget.ps1 +++ b/Build/buildnuget.ps1 @@ -4,14 +4,14 @@ Remove-Item -Path .\LICENSE.txt -Force Remove-Item -Path .\CHANGES.txt -Force Remove-Item -Path .\README -Force -md lib\Net45 -Copy-Item ..\Fedlet\bin\Release\Fedlet.dll .\lib\Net45 -Copy-Item ..\Fedlet\bin\Release\Fedlet.xml .\lib\Net45 -Copy-Item ..\Fedlet\bin\Release\Fedlet.pdb .\lib\Net45 +md lib\Net462 +Copy-Item ..\Fedlet\bin\Release\Fedlet.dll .\lib\Net462 +Copy-Item ..\Fedlet\bin\Release\Fedlet.xml .\lib\Net462 +Copy-Item ..\Fedlet\bin\Release\Fedlet.pdb .\lib\Net462 Copy-Item ..\NOTICES.txt .\ Copy-Item ..\LICENSE.txt .\ Copy-Item ..\CHANGES.txt .\ Copy-Item ..\README .\ -..\..\..\Nuget\nuget.exe pack Fedlet.1.0.nuspec \ No newline at end of file +..\..\Nuget\nuget.exe pack Fedlet.1.0.nuspec \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt index 016a6bd..3719889 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,11 @@ -Fedlet 1.0.5.0 - July 2016 +Fedlet 1.0.6.0 - Dec 2016 + +Switched targeting to .NET 462. +Expects .Net framework to be able to work with CNG certs and SHA256 without additional hacks. +Some code cleanup. + + +Fedlet 1.0.5.0 - July 2016 Fixed wrong attribute name for AssertionConsumerServiceIndex in the outgoing SAMLRequest. Added default value for NameIDPolicyFormat ("unspecified") if not supplied by client code. diff --git a/Fedlet.UnitTests/Fedlet.UnitTests.csproj b/Fedlet.UnitTests/Fedlet.UnitTests.csproj index 4c20571..a62b709 100644 --- a/Fedlet.UnitTests/Fedlet.UnitTests.csproj +++ b/Fedlet.UnitTests/Fedlet.UnitTests.csproj @@ -10,7 +10,7 @@ Properties Fedlet.UnitTests Fedlet.UnitTests - v4.6.1 + v4.6.2 512 diff --git a/Fedlet.UnitTests/Properties/AssemblyInfo.cs b/Fedlet.UnitTests/Properties/AssemblyInfo.cs index 588b321..07679c8 100644 --- a/Fedlet.UnitTests/Properties/AssemblyInfo.cs +++ b/Fedlet.UnitTests/Properties/AssemblyInfo.cs @@ -17,5 +17,5 @@ [assembly: Guid("75b42c20-1ed1-41fd-b5cd-e587a72c2a92")] -[assembly: AssemblyVersion("1.0.2.0")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] diff --git a/Fedlet/Fedlet.csproj b/Fedlet/Fedlet.csproj index 4337575..2e4e508 100644 --- a/Fedlet/Fedlet.csproj +++ b/Fedlet/Fedlet.csproj @@ -10,7 +10,7 @@ Properties Sun.Identity Fedlet - v4.6.1 + v4.6.2 512 false diff --git a/Fedlet/Properties/AssemblyInfo.cs b/Fedlet/Properties/AssemblyInfo.cs index b6d3009..f040e83 100644 --- a/Fedlet/Properties/AssemblyInfo.cs +++ b/Fedlet/Properties/AssemblyInfo.cs @@ -41,5 +41,5 @@ [assembly: Guid("2155869F-784E-4882-976E-61AB0F9642F3")] -[assembly: AssemblyVersion("1.0.5.0")] -[assembly: AssemblyFileVersion("1.0.5.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] \ No newline at end of file diff --git a/Fedlet/Saml2/ArtifactResponse.cs b/Fedlet/Saml2/ArtifactResponse.cs index bf10dfd..9fbf54a 100644 --- a/Fedlet/Saml2/ArtifactResponse.cs +++ b/Fedlet/Saml2/ArtifactResponse.cs @@ -26,6 +26,7 @@ */ using System; +using System.Security.Cryptography.Xml; using System.Xml; using System.Xml.XPath; using Sun.Identity.Properties; @@ -68,7 +69,7 @@ public ArtifactResponse(string artifactResponse) _xml = new XmlDocument {PreserveWhitespace = true}; _xml.LoadXml(artifactResponse); _nsMgr = new XmlNamespaceManager(_xml.NameTable); - _nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); _nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); diff --git a/Fedlet/Saml2/AuthnResponse.cs b/Fedlet/Saml2/AuthnResponse.cs index e5ed569..c65ffa1 100644 --- a/Fedlet/Saml2/AuthnResponse.cs +++ b/Fedlet/Saml2/AuthnResponse.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Globalization; +using System.Security.Cryptography.Xml; using System.Xml; using System.Xml.XPath; using Sun.Identity.Properties; @@ -54,7 +55,7 @@ public AuthnResponse(string samlResponse) _xml = new XmlDocument {PreserveWhitespace = true}; _xml.LoadXml(samlResponse); _nsMgr = new XmlNamespaceManager(_xml.NameTable); - _nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); _nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); } diff --git a/Fedlet/Saml2/IdentityProvider.cs b/Fedlet/Saml2/IdentityProvider.cs index cef92cf..51fb7f3 100644 --- a/Fedlet/Saml2/IdentityProvider.cs +++ b/Fedlet/Saml2/IdentityProvider.cs @@ -26,6 +26,7 @@ */ using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using Sun.Identity.Properties; @@ -78,7 +79,7 @@ public IdentityProvider(XmlDocument metadata, XmlDocument extendedMetadata, Saml _metadata = metadata; _metadataNsMgr = new XmlNamespaceManager(_metadata.NameTable); _metadataNsMgr.AddNamespace("md", "urn:oasis:names:tc:SAML:2.0:metadata"); - _metadataNsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _metadataNsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _extendedMetadata = extendedMetadata; _extendedMetadataNsMgr = new XmlNamespaceManager(_extendedMetadata.NameTable); diff --git a/Fedlet/Saml2/LogoutRequest.cs b/Fedlet/Saml2/LogoutRequest.cs index c0ab6c0..54b6dac 100644 --- a/Fedlet/Saml2/LogoutRequest.cs +++ b/Fedlet/Saml2/LogoutRequest.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Specialized; using System.Globalization; +using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using System.Xml.XPath; @@ -70,7 +71,7 @@ public LogoutRequest(string samlRequest) _xml = new XmlDocument {PreserveWhitespace = true}; _nsMgr = new XmlNamespaceManager(_xml.NameTable); - _nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); _nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); @@ -111,7 +112,7 @@ public LogoutRequest( _xml = new XmlDocument {PreserveWhitespace = true}; _nsMgr = new XmlNamespaceManager(_xml.NameTable); - _nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); _nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); diff --git a/Fedlet/Saml2/LogoutResponse.cs b/Fedlet/Saml2/LogoutResponse.cs index fed28d6..7cf73e6 100644 --- a/Fedlet/Saml2/LogoutResponse.cs +++ b/Fedlet/Saml2/LogoutResponse.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Specialized; +using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using System.Xml.XPath; @@ -68,7 +69,7 @@ public LogoutResponse(string samlResponse) _xml = new XmlDocument {PreserveWhitespace = true}; _nsMgr = new XmlNamespaceManager(_xml.NameTable); - _nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + _nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); _nsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); _nsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); diff --git a/Fedlet/Saml2/Saml2Constants.cs b/Fedlet/Saml2/Saml2Constants.cs index 0e0277c..927df42 100644 --- a/Fedlet/Saml2/Saml2Constants.cs +++ b/Fedlet/Saml2/Saml2Constants.cs @@ -200,21 +200,6 @@ public static class Saml2Constants /// public const string SignatureAlgorithm = "SigAlg"; - /// - /// Constant for the DSA type of signature algorithm. - /// - public const string SignatureAlgorithmDsa = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; - - /// - /// Constant for the RSA type of signature algorithm, SHA1. - /// - public const string SignatureAlgorithmRsaSha1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; - - /// - /// Constant for the RSA type of signature algorithm, SHA256. - /// - public const string SignatureAlgorithmRsaSha256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; - /// /// Constant for the Signature parameter. /// diff --git a/Fedlet/Saml2/Saml2Utils.cs b/Fedlet/Saml2/Saml2Utils.cs index 4c21db3..2a6109b 100644 --- a/Fedlet/Saml2/Saml2Utils.cs +++ b/Fedlet/Saml2/Saml2Utils.cs @@ -67,7 +67,7 @@ public Saml2Utils(IFedletCertificateFactory certificateFactory) /// String contained within the base64 encoded string. public string ConvertFromBase64(string value) { - byte[] byteArray = Convert.FromBase64String(value); + var byteArray = Convert.FromBase64String(value); return Encoding.UTF8.GetString(byteArray); } @@ -80,13 +80,13 @@ public string ConvertFromBase64(string value) public string ConvertFromBase64Decompress(string message) { // convert from base 64 - byte[] byteArray = Convert.FromBase64String(message); + var byteArray = Convert.FromBase64String(message); // inflate the gzip deflated message var streamReader = new StreamReader(new DeflateStream(new MemoryStream(byteArray), CompressionMode.Decompress)); // put in a string - string decompressedMessage = streamReader.ReadToEnd(); + var decompressedMessage = streamReader.ReadToEnd(); streamReader.Close(); return decompressedMessage; @@ -131,7 +131,7 @@ public string GenerateId() var random = new Random(); var byteArray = new byte[Saml2Constants.IdLength - 1]; random.NextBytes(byteArray); - string id = "A" + BitConverter.ToString(byteArray).Replace("-", string.Empty); + var id = "A" + BitConverter.ToString(byteArray).Replace("-", string.Empty); return id; } @@ -144,7 +144,7 @@ public string GenerateId() /// Current time in UTC, invariant culture format. public string GenerateIssueInstant() { - string issueInstant = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", DateTimeFormatInfo.InvariantInfo); + var issueInstant = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", DateTimeFormatInfo.InvariantInfo); return issueInstant; } @@ -219,7 +219,7 @@ public string CompressConvertToBase64UrlEncode(IXPathNavigable xml) { var xmlDoc = (XmlDocument) xml; - byte[] buffer = Encoding.UTF8.GetBytes(xmlDoc.OuterXml); + var buffer = Encoding.UTF8.GetBytes(xmlDoc.OuterXml); var memoryStream = new MemoryStream(); var compressedStream = new DeflateStream(memoryStream, CompressionMode.Compress, true); compressedStream.Write(buffer, 0, buffer.Length); @@ -230,8 +230,8 @@ public string CompressConvertToBase64UrlEncode(IXPathNavigable xml) memoryStream.Read(compressedBuffer, 0, compressedBuffer.Length); memoryStream.Close(); - string compressedBase64String = Convert.ToBase64String(compressedBuffer); - string compressedBase64UrlEncodedString = HttpUtility.UrlEncode(compressedBase64String); + var compressedBase64String = Convert.ToBase64String(compressedBuffer); + var compressedBase64UrlEncodedString = HttpUtility.UrlEncode(compressedBase64String); return compressedBase64UrlEncodedString; } @@ -245,7 +245,7 @@ public string CompressConvertToBase64UrlEncode(IXPathNavigable xml) public string UrlDecodeConvertFromBase64Decompress(string message) { // url decode it - string decodedMessage = HttpUtility.UrlDecode(message); + var decodedMessage = HttpUtility.UrlDecode(message); if (string.IsNullOrEmpty(decodedMessage)) { @@ -253,13 +253,13 @@ public string UrlDecodeConvertFromBase64Decompress(string message) } // convert from base 64 - byte[] byteArray = Convert.FromBase64String(decodedMessage); + var byteArray = Convert.FromBase64String(decodedMessage); // inflate the gzip deflated message var streamReader = new StreamReader(new DeflateStream(new MemoryStream(byteArray), CompressionMode.Decompress)); // put in a string - string decompressedMessage = streamReader.ReadToEnd(); + var decompressedMessage = streamReader.ReadToEnd(); streamReader.Close(); return decompressedMessage; @@ -293,10 +293,10 @@ public string SignQueryString(string certFriendlyName, string queryString) char[] queryStringSep = {'&'}; var queryParams = new NameValueCollection(); - foreach (string pairs in queryString.Split(queryStringSep)) + foreach (var pairs in queryString.Split(queryStringSep)) { - string key = pairs.Substring(0, pairs.IndexOf("=", StringComparison.Ordinal)); - string value = pairs.Substring(pairs.IndexOf("=", StringComparison.Ordinal) + 1); + var key = pairs.Substring(0, pairs.IndexOf("=", StringComparison.Ordinal)); + var value = pairs.Substring(pairs.IndexOf("=", StringComparison.Ordinal) + 1); queryParams[key] = value; } @@ -306,7 +306,7 @@ public string SignQueryString(string certFriendlyName, string queryString) throw new Saml2Exception(Resources.SignedQueryStringSigAlgMissing); } - X509Certificate2 cert = _certificateFactory.GetCertificateByFriendlyName(certFriendlyName); + var cert = _certificateFactory.GetCertificateByFriendlyName(certFriendlyName); if (cert == null) { throw new Saml2Exception(Resources.SignedQueryStringCertNotFound); @@ -317,59 +317,68 @@ public string SignQueryString(string certFriendlyName, string queryString) throw new Saml2Exception(Resources.SignedQueryStringCertHasNoPrivateKey); } - string signatureAlgorithm = HttpUtility.UrlDecode(queryParams[Saml2Constants.SignatureAlgorithm]); + var signatureAlgorithmUrl = HttpUtility.UrlDecode(queryParams[Saml2Constants.SignatureAlgorithm]); + var hashAlgorithmName = GetHashAlgorithmNameFromSignatureAlgorithmUrl(signatureAlgorithmUrl); - if (string.IsNullOrEmpty(signatureAlgorithm)) + using (var signingKey = cert.GetRSAPrivateKey()) { - signatureAlgorithm = Saml2Constants.SignatureAlgorithmRsaSha1; - } + var privateKey = signingKey; + var signature = privateKey.SignData(Encoding.UTF8.GetBytes(queryString), hashAlgorithmName, RSASignaturePadding.Pkcs1); - object csp; - switch (signatureAlgorithm) - { - case Saml2Constants.SignatureAlgorithmRsaSha1: csp = new SHA1CryptoServiceProvider(); break; - case Saml2Constants.SignatureAlgorithmRsaSha256: csp = new SHA256CryptoServiceProvider(); break; - default: throw new Saml2Exception(Resources.SignedQueryStringSigAlgNotSupported); - } + var encodedSignature = Convert.ToBase64String(signature); - var privateKey = GetSigningKey((RSACryptoServiceProvider)cert.PrivateKey, signatureAlgorithm); - byte[] signature = privateKey.SignData( - Encoding.UTF8.GetBytes(queryString), - csp); + var signedQueryString + = queryString + + "&" + Saml2Constants.Signature + + "=" + HttpUtility.UrlEncode(encodedSignature); - var encodedSignature = Convert.ToBase64String(signature); - - string signedQueryString - = queryString - + "&" + Saml2Constants.Signature - + "=" + HttpUtility.UrlEncode(encodedSignature); - - return signedQueryString; + return signedQueryString; + } } - /// - /// Signs the specified xml document with the certificate found in - /// the local machine matching the provided friendly name and - /// referring to the specified target reference ID. - /// - /// - /// Friendly Name of the X509Certificate to be retrieved - /// from the LocalMachine keystore and used to sign the xml document. - /// Be sure to have appropriate permissions set on the keystore. - /// - /// - /// XML document to be signed. - /// - /// - /// Reference element that will be specified as signed. - /// - /// - /// Flag to determine whether to include the public key in the - /// signed xml. - /// - /// Identifier of the signature method. - /// Identifier of the digest method. - public void SignXml(string certFriendlyName, XmlDocument xml, string targetReferenceId, + /// + /// Determines hash algorithm name based on signature algorithm. + /// + /// + /// + /// + /// + public static HashAlgorithmName GetHashAlgorithmNameFromSignatureAlgorithmUrl(string signatureAlgorithmUrl) + { + switch (signatureAlgorithmUrl) + { + case "http://www.w3.org/2000/09/xmldsig#dsa-sha1": return HashAlgorithmName.SHA1; + case SignedXml.XmlDsigRSASHA1Url: return HashAlgorithmName.SHA1; + case SignedXml.XmlDsigRSASHA256Url: return HashAlgorithmName.SHA256; + case SignedXml.XmlDsigRSASHA384Url: return HashAlgorithmName.SHA384; + case SignedXml.XmlDsigRSASHA512Url: return HashAlgorithmName.SHA512; + default: throw new Saml2Exception(Resources.SignedQueryStringSigAlgNotSupported); + } + } + + /// + /// Signs the specified xml document with the certificate found in + /// the local machine matching the provided friendly name and + /// referring to the specified target reference ID. + /// + /// + /// Friendly Name of the X509Certificate to be retrieved + /// from the LocalMachine keystore and used to sign the xml document. + /// Be sure to have appropriate permissions set on the keystore. + /// + /// + /// XML document to be signed. + /// + /// + /// Reference element that will be specified as signed. + /// + /// + /// Flag to determine whether to include the public key in the + /// signed xml. + /// + /// Identifier of the signature method. + /// Identifier of the digest method. + public void SignXml(string certFriendlyName, XmlDocument xml, string targetReferenceId, bool includePublicKey, string signatureMethod, string digestMethod) { if (string.IsNullOrEmpty(certFriendlyName)) @@ -393,69 +402,59 @@ public void SignXml(string certFriendlyName, XmlDocument xml, string targetRefer throw new Saml2Exception(Resources.SignedXmlCertNotFound); } - var signedXml = new SignedXml(xml) {SigningKey = GetSigningKey((RSACryptoServiceProvider)cert.PrivateKey, signatureMethod)}; - signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; - if (signatureMethod != null) - { - signedXml.SignedInfo.SignatureMethod = signatureMethod; - } - - if (includePublicKey) - { - var keyInfo = new KeyInfo(); - keyInfo.AddClause(new KeyInfoX509Data(cert)); - signedXml.KeyInfo = keyInfo; - } - - var reference = new Reference {Uri = "#" + targetReferenceId}; - - reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); - reference.AddTransform(new XmlDsigExcC14NTransform()); - if (digestMethod != null) + using (var signingKey = cert.GetRSAPrivateKey()) { - reference.DigestMethod = digestMethod; + var signedXml = new SignedXml(xml) + { + SigningKey = signingKey + }; + + signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; + if (signatureMethod != null) + { + signedXml.SignedInfo.SignatureMethod = signatureMethod; + } + + if (includePublicKey) + { + var keyInfo = new KeyInfo(); + keyInfo.AddClause(new KeyInfoX509Data(cert)); + signedXml.KeyInfo = keyInfo; + } + + var reference = new Reference {Uri = "#" + targetReferenceId}; + + reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); + reference.AddTransform(new XmlDsigExcC14NTransform()); + if (digestMethod != null) + { + reference.DigestMethod = digestMethod; + } + + signedXml.AddReference(reference); + signedXml.ComputeSignature(); + + var xmlSignature = signedXml.GetXml(); + + var nsMgr = new XmlNamespaceManager(xml.NameTable); + nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); + nsMgr.AddNamespace("saml", Saml2Constants.NamespaceSamlAssertion); + nsMgr.AddNamespace("samlp", Saml2Constants.NamespaceSamlProtocol); + + var issuerNode = TryGetNode(xml, nsMgr, "saml:Issuer"); + if (issuerNode != null) + { + RequireRootElement(xml).InsertAfter(xmlSignature, issuerNode); + } + else + { + // Insert as a child to the target reference id + var targetNode = RequireNode(xml, nsMgr, "//*[@ID='" + targetReferenceId + "']"); + targetNode.PrependChild(xmlSignature); + } } - - signedXml.AddReference(reference); - signedXml.ComputeSignature(); - - var xmlSignature = signedXml.GetXml(); - - var nsMgr = new XmlNamespaceManager(xml.NameTable); - nsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); - nsMgr.AddNamespace("saml", Saml2Constants.NamespaceSamlAssertion); - nsMgr.AddNamespace("samlp", Saml2Constants.NamespaceSamlProtocol); - - var issuerNode = TryGetNode(xml, nsMgr, "saml:Issuer"); - if (issuerNode != null) - { - RequireRootElement(xml).InsertAfter(xmlSignature, issuerNode); - } - else - { - // Insert as a child to the target reference id - var targetNode = RequireNode(xml, nsMgr, "//*[@ID='" + targetReferenceId + "']"); - targetNode.PrependChild(xmlSignature); - } } - private static RSACryptoServiceProvider GetSigningKey(RSACryptoServiceProvider algorithm, string signatureMethod) - { - if (algorithm.CspKeyContainerInfo.ProviderType == 1 - && signatureMethod != Saml2Constants.SignatureAlgorithmRsaSha1) - { - // TODO: review after .NET 4.6.2 comes out - they promise out-of-the-box support for SHA256. - // Up to and including .NET 4.6.1, X509Store will return certificates with keys wrapped into CSPs of type 1, aka "Microsoft Enhanced Cryptographic Provider v1.0". - // However, that CSP does not support SHA2 (SHA256 and others), - // which is why we have to re-create the key so it reincarnates in a CSP of type 24, aka "Microsoft Enhanced RSA and AES Cryptographic Provider". - var newalgorithm = new RSACryptoServiceProvider(); - newalgorithm.ImportParameters(algorithm.ExportParameters(true)); - return newalgorithm; - } - - return algorithm; - } - /// /// Validates a relay state URL with a list of allowed relay states, /// each expected to be written as a regular expression pattern. If @@ -522,7 +521,7 @@ public void ValidateSignedXml(X509Certificate2 cert, IXPathNavigable xmlDoc, IXP var foundValidSignedReference = false; foreach (Reference r in signedXml.SignedInfo.References) { - string referenceId = r.Uri.Substring(1); + var referenceId = r.Uri.Substring(1); if (referenceId == targetReferenceId) { foundValidSignedReference = true; @@ -559,10 +558,10 @@ public void ValidateSignedQueryString(X509Certificate2 cert, string queryString) char[] queryStringSep = {'&'}; var queryParams = new NameValueCollection(); - foreach (string pairs in queryString.Split(queryStringSep)) + foreach (var pairs in queryString.Split(queryStringSep)) { - string key = pairs.Substring(0, pairs.IndexOf("=", StringComparison.Ordinal)); - string value = pairs.Substring(pairs.IndexOf("=", StringComparison.Ordinal) + 1); + var key = pairs.Substring(0, pairs.IndexOf("=", StringComparison.Ordinal)); + var value = pairs.Substring(pairs.IndexOf("=", StringComparison.Ordinal) + 1); queryParams[key] = value; } @@ -577,7 +576,7 @@ public void ValidateSignedQueryString(X509Certificate2 cert, string queryString) throw new Saml2Exception(Resources.SignedQueryStringMissingSignature); } - var signatureAlgorithm = HttpUtility.UrlDecode(queryParams[Saml2Constants.SignatureAlgorithm]); + var signatureAlgorithmUrl = HttpUtility.UrlDecode(queryParams[Saml2Constants.SignatureAlgorithm]); var signature = HttpUtility.UrlDecode(queryParams[Saml2Constants.Signature]); // construct a new query string with specific sequence and no signature param @@ -598,26 +597,17 @@ public void ValidateSignedQueryString(X509Certificate2 cert, string queryString) newQueryString += "&" + Saml2Constants.SignatureAlgorithm + "=" + queryParams[Saml2Constants.SignatureAlgorithm]; - byte[] dataBuffer = Encoding.UTF8.GetBytes(newQueryString); - byte[] sigBuffer = Convert.FromBase64String(signature); - - if (string.IsNullOrEmpty(signatureAlgorithm)) - { - signatureAlgorithm = Saml2Constants.SignatureAlgorithmRsaSha1; - } - - object csp; - switch (signatureAlgorithm) - { - case Saml2Constants.SignatureAlgorithmRsaSha1: csp = new SHA1CryptoServiceProvider(); break; - case Saml2Constants.SignatureAlgorithmRsaSha256: csp = new SHA256CryptoServiceProvider(); break; - default: throw new Saml2Exception(Resources.SignedQueryStringSigAlgNotSupported); - } + var dataBuffer = Encoding.UTF8.GetBytes(newQueryString); + var sigBuffer = Convert.FromBase64String(signature); - var publicKey = GetSigningKey((RSACryptoServiceProvider)cert.PublicKey.Key, signatureAlgorithm); - if (!publicKey.VerifyData(dataBuffer, csp, sigBuffer)) + using (var signingKey = cert.GetRSAPublicKey()) { - throw new Saml2Exception(Resources.SignedQueryStringVerifyDataFailed); + var publicKey = signingKey; + var hashAlgorithmName = GetHashAlgorithmNameFromSignatureAlgorithmUrl(signatureAlgorithmUrl); + if (!publicKey.VerifyData(dataBuffer, sigBuffer, hashAlgorithmName, RSASignaturePadding.Pkcs1)) + { + throw new Saml2Exception(Resources.SignedQueryStringVerifyDataFailed); + } } } diff --git a/Fedlet/Saml2/ServiceProvider.cs b/Fedlet/Saml2/ServiceProvider.cs index 6a77ac9..f9f283a 100644 --- a/Fedlet/Saml2/ServiceProvider.cs +++ b/Fedlet/Saml2/ServiceProvider.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Globalization; +using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using Sun.Identity.Properties; @@ -174,7 +175,7 @@ public string SignatureMethod { const string xpath = "/mdx:EntityConfig/mdx:SPSSOConfig/mdx:Attribute[@name='signatureMethod']/mdx:Value"; var method = Saml2Utils.TryGetNodeText(_extendedMetadata, _extendedMetadataNsMgr, xpath); - return string.IsNullOrEmpty(method) ? Saml2Constants.SignatureAlgorithmRsaSha1 : method; + return string.IsNullOrEmpty(method) ? SignedXml.XmlDsigRSASHA256Url : method; } } diff --git a/Fedlet/Saml2/ServiceProviderUtility.cs b/Fedlet/Saml2/ServiceProviderUtility.cs index 791c5f2..e592184 100644 --- a/Fedlet/Saml2/ServiceProviderUtility.cs +++ b/Fedlet/Saml2/ServiceProviderUtility.cs @@ -33,6 +33,7 @@ using System.Linq; using System.Net; using System.Security.Cryptography; +using System.Security.Cryptography.Xml; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -200,7 +201,7 @@ public ArtifactResponse GetArtifactResponse(Artifact artifact) soapNsMgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); soapNsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); soapNsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); - soapNsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + soapNsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); var artifactResponseXml = Saml2Utils.RequireNode(soapResponse, soapNsMgr, "/soap:Envelope/soap:Body/samlp:ArtifactResponse").OuterXml; artifactResponse = new ArtifactResponse(artifactResponseXml); @@ -331,7 +332,7 @@ public LogoutRequest GetLogoutRequest(HttpContextBase context) soapNsMgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); soapNsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); soapNsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); - soapNsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + soapNsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); var requestXml = Saml2Utils.RequireNode(soapRequest, soapNsMgr, "/soap:Envelope/soap:Body/samlp:LogoutRequest"); samlRequest = requestXml.OuterXml; @@ -1072,7 +1073,7 @@ public void SendSoapLogoutRequest(LogoutRequest logoutRequest, string idpEntityI soapNsMgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); soapNsMgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); soapNsMgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); - soapNsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + soapNsMgr.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl); var logoutResponseXml = Saml2Utils.RequireNode(soapResponse, soapNsMgr, "/soap:Envelope/soap:Body/samlp:LogoutResponse").OuterXml; var logoutResponse = new LogoutResponse(logoutResponseXml); @@ -1656,11 +1657,12 @@ private void CheckCircleOfTrust(string idpEntityId) /// private IIdentityProvider GetIdpFromArtifact(Artifact artifact) { - SHA1 sha1 = new SHA1CryptoServiceProvider(); + // must use SHA1 here + SHA1 sha = new SHA1CryptoServiceProvider(); foreach (var idpEntityId in IdentityProviders.Keys) { - var idpEntityIdHashed = BitConverter.ToString(sha1.ComputeHash(Encoding.UTF8.GetBytes(idpEntityId))); + var idpEntityIdHashed = BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(idpEntityId))); idpEntityIdHashed = idpEntityIdHashed.Replace("-", string.Empty); if (idpEntityIdHashed == artifact.SourceId)