Signed XML signature verification for SSO SAML (Using sha256)
Asked Answered
F

4

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.

Fayola answered 24/5, 2013 at 6:2 Comment(1)
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?Relique
I
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.

Incubation answered 4/3, 2016 at 15:24 Comment(3)
....and..... framework 4.6.2 takes care of this problem without any need for extra code.Incubation
@OllieJones - do you happen to know if your code will break in a later and subsequent move to target 4.6.2?Lepton
@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 blogs.msdn.microsoft.com/dotnet/2016/08/02/… . I have not verified that fact.Incubation
O
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);
        }
Oestradiol answered 18/8, 2014 at 23:11 Comment(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 blogs.msdn.com/b/winsdk/archive/2015/11/15/…Betroth
` const string RsaSha256Namespace = "w3.org/2001/04/xmldsig-more#rsa-sha256";`Fatality
N
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.

Nelan answered 10/2, 2014 at 16:43 Comment(1)
I've had customers refuse to make that change, sigh.Incubation
L
0

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

Leanoraleant answered 31/7, 2017 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.