8

Using VS 2008 with .Net Framework 3.5 on windows 2003 server.

We have implemented SSO with SAML for security. We work at service provider end where we validate the Signed XML SAML Assertuib token generated from client's system. As of now whatever signed documents we came across were using the Signature Algorithm "rsa-sha1", but now we have new customer who sends a file with the signature algorithm as "rsa-sha256" and here is the problem started.

public static string VerifySignature()
{
    if (m_xmlDoc == null)
        return "Could not load XMLDocument ";

    try
    {
        XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
        nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
        XmlElement sigElt = (XmlElement)m_xmlDoc.SelectSingleNode(
            "//dsig:Signature", nsm);

        // Load the signature for verification
        SignedXml sig = new SignedXml(m_xmlDoc);
        sig.LoadXml(sigElt);

        if (!sig.CheckSignature())
            return "Invalid Signature";
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
    return string.Empty;
}

Now, when I try the same code for this new customer (with signature algorithm rsa-sha256h) - this is not working and I am getting the error "SignatureDescription could not be created for the signature algorithm supplied."

Going through many blogs and articles in last 2-3 days, I came to know that SignedXml does not support sha256. Fine. But what next. Somewhere its mentioned that use the WIF, I have also checked & tried this.

I am also trying to use RSAPKCS1SignatureDeformatter's VerifySignature method. But not really sure what are the two parameters to be passed.

MatthewMartin
  • 32,326
  • 33
  • 105
  • 164
Sandeep
  • 81
  • 1
  • 1
  • 2
  • I was under the impression that encryption algorithm was supported because it supports the X509Certificate2 object. The sig.CheckSignature(...) method has an overload that takes 2 parameters, the X509Certificate2 and bool. Have you tried using it and passing in the cert and true? – Randall Borck May 30 '13 at 18:07

4 Answers4

9

Dotnet 4.6.2+ has the newer sha hashes built in. For dotnet 4 +, to get access to rsa-sha512, rsa-sha384, and rsa-sha256, you should include this code someplace.

/// <summary>Declare the signature type for rsa-sha512</summary>
public class RsaPkCs1Sha512SignatureDescription : SignatureDescription
{
    public RsaPkCs1Sha512SignatureDescription()
    {
        KeyAlgorithm = typeof(RSACryptoServiceProvider).FullName;
        DigestAlgorithm = typeof(SHA512CryptoServiceProvider).FullName;
        FormatterAlgorithm = typeof(RSAPKCS1SignatureFormatter).FullName;
        DeformatterAlgorithm = typeof(RSAPKCS1SignatureDeformatter).FullName;
    }

    public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
    {
        var sigProcessor = (AsymmetricSignatureDeformatter)CryptoConfig.CreateFromName(DeformatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA512");
        return sigProcessor;
    }

    public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
    {
        var sigProcessor =
            (AsymmetricSignatureFormatter)CryptoConfig.CreateFromName(FormatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA512");
        return sigProcessor;
    }
}

/// <summary>Declare the signature type for rsa-sha384</summary>
public class RsaPkCs1Sha384SignatureDescription : SignatureDescription {
    public RsaPkCs1Sha384SignatureDescription()
    {
        KeyAlgorithm = typeof(RSACryptoServiceProvider).FullName;
        DigestAlgorithm = typeof(SHA384CryptoServiceProvider).FullName;
        FormatterAlgorithm = typeof(RSAPKCS1SignatureFormatter).FullName;
        DeformatterAlgorithm = typeof(RSAPKCS1SignatureDeformatter).FullName;
    }

    public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
    {
        var sigProcessor = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA384");
        return sigProcessor;
    }

    public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
    {
        var sigProcessor =
            (AsymmetricSignatureFormatter)CryptoConfig.CreateFromName(FormatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA384");
        return sigProcessor;
    }
}

/// <summary>Declare the signature type for rsa-sha256</summary>
public class RsaPkCs1Sha256SignatureDescription : SignatureDescription
{
    public RsaPkCs1Sha256SignatureDescription()
    {
        KeyAlgorithm = typeof(RSACryptoServiceProvider).FullName;
        DigestAlgorithm = typeof(SHA256CryptoServiceProvider).FullName;
        FormatterAlgorithm = typeof(RSAPKCS1SignatureFormatter).FullName;
        DeformatterAlgorithm = typeof(RSAPKCS1SignatureDeformatter).FullName;
    }

    public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
    {
        var sigProcessor =
            (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA256");
        return sigProcessor;
    }

    public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
    {
        var sigProcessor =
            (AsymmetricSignatureFormatter)CryptoConfig.CreateFromName(FormatterAlgorithm);
        sigProcessor.SetKey(key);
        sigProcessor.SetHashAlgorithm("SHA256");
        return sigProcessor;
    }
}

Then, you should activate these sig descriptions by calling code like this. You only need to call it once, so you can call it from a static constructor if you wish.

    CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha512SignatureDescription),
        "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512");
    CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha384SignatureDescription),
        "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384");
    CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha256SignatureDescription),
        "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");

Tip o' the hat to Microsoft's Carlos Lopez and BitSchupster and Andrew on SO.

Community
  • 1
  • 1
O. Jones
  • 103,626
  • 17
  • 118
  • 172
  • 3
    ....and..... framework 4.6.2 takes care of this problem without any need for extra code. – O. Jones Aug 04 '16 at 22:31
  • @OllieJones - do you happen to know if your code will break in a later and subsequent move to target 4.6.2? – Stephan G Sep 29 '16 at 23:05
  • 1
    @StephanG I've made the move to 4.6.1 without problems. I understand this code is no longer needed in 4.6.2, because they've added the new hashes. See the X509 section of https://blogs.msdn.microsoft.com/dotnet/2016/08/02/announcing-net-framework-4-6-2/ . I have not verified that fact. – O. Jones Sep 30 '16 at 13:21
4

For .net 4 and earlier, I found that the following works once you add the Security.Cryptography from http://clrsecurity.codeplex.com/

(Note X509CertificateFinder is my own, looks for the signature certificate in the certificate store by thumbprint)

        /// <summary>
        /// Validate an XmlDocuments signature
        /// </summary>
        /// <param name="xnlDoc"> The saml response with the signature elemenet to validate </param>
        /// <returns> True if signature can be validated with certificate </returns>
        public bool ValidateX509CertificateSignature(XmlDocument xnlDoc)
        {
            XmlNodeList XMLSignatures = xnlDoc.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#");

            // Checking If the Response or the Assertion has been signed once and only once.
            if (XMLSignatures.Count != 1) return false;

            var signedXmlDoc = new SignedXml(xnlDoc);
            signedXmlDoc.LoadXml((XmlElement)XMLSignatures[0]);

            var certFinder = new X509CertificateFinder();
            var foundCert = certFinder.GetSignatureCertificate();

            CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
            return signedXmlDoc.CheckSignature(foundCert,false);
        }
AndyM
  • 3,574
  • 6
  • 39
  • 45
  • 2
    Note that from .NET 4.5 you don't need to install any 3rd party libraries unlike in .net 4.0 and earlier. You can just add a reference to System.Deployment and call `System.Security.Cryptography.CryptoConfig.AddAlgorithm(typeof (RSAPKCS1SHA256SignatureDescription), RsaSha256Namespace);` once within your process, after which the code in the original question will work with SHA256 hashes. See also http://blogs.msdn.com/b/winsdk/archive/2015/11/15/using-sha256-with-signedxml.aspx – Rory Feb 12 '16 at 22:20
  • ` const string RsaSha256Namespace = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";` – maddoxej May 27 '16 at 18:53
2

This qualifies for "simple" but perhaps not "solution" :) For the few clients we've encountered this with, we've asked them to change their IdP to sign using SHA-1. They are able to change it, and when they do it works.

Not a technical solution, but it has worked 'in the field' so I'd thought I'd mention it.

mlhDev
  • 2,235
  • 1
  • 22
  • 43
0

Just update it to .NET framework 4.6.01590 or higher and it will support up to SHA-512 without any code change.

Vijay Bansal
  • 737
  • 7
  • 10