C# Support for RSA SHA 256 signing for individual

2019-02-15 18:28发布

问题:

I have encountered a blocker with the .NET Framework version 4.5 to do with signing of XML with digital signatures.

My problem is based around the need to sign individual XML elements with X.509 certificate with the RSA SHA-256 algorithm. I have read many .NET posts on this topic and it appears that there is a solution originally developed in the CLR Security project RSAPKCS1SHA256SignatureDescription.cs class. RSAPKCS1SHA256SignatureDescription has of course since been incorporated into the .net runtime and as of .NET 4.5 is now available under the distributed binary System.Deployment.dll. I have attempted the above solution in .NET to sign specific XML elements with RSA SHA-256 however have yet to have any success.

I am trying to sign a SOAP message complying with the Oasis ebms standard with a WSSE Token. Please be aware that the class is written to cater for Soap With Attachments (SwA) and signing individual attachments. My code is as follows

My code is the following:

using System;
using System.Collections.Generic;
using System.IO;
using System.IdentityModel.Tokens;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Deployment.Internal.CodeSigning;

namespace TestCSharpX509CertificateRSSHA256
{
    public class SignatureSupportUtility
    {

    private bool IsSignatureContentTransform
    {
        get
        {
          return true;
          //get IsSignatureContentTransform                
        }
    }

       public SignatureSupportUtility()
       {
          Register();
       }


    private static void Register()
    {
        CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
    }

    private void Sign(Message message, string[] elementIdsToSign, string[] attachmentsToSign, string wssNamespace, X509Certificate2 certificate)
    {
       //Prepare XML to encrypt and sign
       var element = this.PrepareEncyrptSign(message);

            bool signEntireDocument = true;
            string elementToBeSigned = string.Empty;
            var signedMessage = new XmlDocument();
            signedMessage.AppendChild(signedMessage.ImportNode(element, true));

            SignatureType signAs = SignatureType.InternallyDetached;
            signedMessage.PreserveWhitespace = false;

            OverrideSignedXml signedXml = new OverrideSignedXml(signedMessage);
            signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

            if (elementIdsToSign != null && elementIdsToSign.Length > 0)
            {
                bool isContentTransform = this.IsSignatureContentTransform;

                foreach (string s in elementIdsToSign)
                {
                    // Create a reference to be signed.
                    Reference reference = new Reference(string.Format("#{0}", s));
                    reference.AddTransform(new XmlDsigExcC14NTransform());
                    reference.DigestMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";


                    // Add the reference to the SignedXml object.
                    signedXml.AddReference(reference);
                }

                signEntireDocument = false;
            }

            // Reference attachments to sign
            if (attachmentsToSign != null && attachmentsToSign.Length > 0)
            {
                bool isContentTransform = this.IsSignatureContentTransform;

                foreach (string attachmentId in attachmentsToSign)
                {
                    // Create a reference to be signed.
                    Reference reference = new Reference(string.Format("{0}{1}", Constants.CidUriScheme, attachmentId));
                    reference.DigestMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

                    if (isContentTransform)
                    {
                        AttachmentContentSignatureTransform env = new AttachmentContentSignatureTransform();
                        reference.AddTransform(env);
                    }
                    else
                    {
                        AttachmentCompleteSignatureTransform env = new AttachmentCompleteSignatureTransform();
                        reference.AddTransform(env);
                    }

                    // Add the reference to the SignedXml object.
                    signedXml.AddReference(reference);
                }

                signEntireDocument = false;
            }

            if (signEntireDocument)
            {
                Reference reference = new Reference();
                reference.Uri = "";
                reference.DigestMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

                XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                reference.AddTransform(env);
                signedXml.AddReference(reference);
                signAs = SignatureType.Enveloped;
            }

            string x509CertificateReferenceId = string.Format("{0}-{1}", Constants.IdAttributeName, Guid.NewGuid().ToString("N"));
            KeyInfo keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509SecurityTokenReference(string.Format("#{0}", x509CertificateReferenceId), wssNamespace));
            signedXml.KeyInfo = keyInfo;
           signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

           RSA  key = (RSACryptoServiceProvider)certificate.PrivateKey;
           signedXML.SigningKey = key;
            CidWebRequest.Message = message;

            signedXml.ComputeSignature();
            var xmlSignature = signedXml.GetXml();
            XmlDocument unsignedEnvelopeDoc = new XmlDocument();
            unsignedEnvelopeDoc.LoadXml(message.MessageAsString); }}} 
using System;
using System.Collections.Generic;
using System.IO;
using System.IdentityModel.Tokens;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Deployment.Internal.CodeSigning;


namespace TestCSharpX509CertificateRSSHA256
{
public sealed class OverrideSignedXml : SignedXml
{
    public OverrideSignedXml()
        : base()
    {
    }

    public OverrideSignedXml(XmlDocument doc)
        : base(doc)
    {
    }

    public override XmlElement GetIdElement(XmlDocument document, string idValue)
    {
        XmlElement element = base.GetIdElement(document, idValue);

        if (element == null)
        {
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable);
            nsmgr.AddNamespace("wsu", ="http://docs.oasis-open. org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

            element = document.SelectSingleNode("//*[@wsu:Id=\"" + idValue + "\"]", nsmgr) as XmlElement;
        }

        return element;
    }
}

}

The Sign method in my SignatureSupportUtility class should be adequate to sign individual XML elements or the whole message, however I keep receiving a Cryptography exception claiming that the SHA-256 is not supported. I think this exception should not be valid observing that the RSAPKCS1SHA256SignatureDescription.cs is registered. However observing that the SignedXML class does not include the namespace for SHA-256 and only SHA-128 I am beginning to doubt if the SHA 256 is supported regardless of registration.

Could someone please advise me on how best to resolve my issue and be able to sign XML with an X.509 certificate via RSA SHA 256 algorithm?

回答1:

I am also looking at the Oasis ebms stuff.

I cannot find the article I took this from but I used that class that is in 4.5:

public class RsaPkCs1Sha256SignatureDescription : SignatureDescription
{
    public RsaPkCs1Sha256SignatureDescription()
    {
        KeyAlgorithm = "System.Security.Cryptography.RSACryptoServiceProvider";
        DigestAlgorithm = "System.Security.Cryptography.SHA256Managed";
        FormatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureFormatter";
        DeformatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureDeformatter";
    }

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

and then use something like this to sign (have edited some irrelevant bits out):

    public XmlElement SignDocument(XmlDocument doc, List<string> idsToSign)
    {
        CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha256SignatureDescription), @"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");

        var cspParams = new CspParameters(24) { KeyContainerName = "XML_DISG_RSA_KEY" };
        var key = new RSACryptoServiceProvider(cspParams);
        key.FromXmlString(_x509SecurityToken.Certificate.PrivateKey.ToXmlString(true));

        var signer = new SoapSignedXml(doc) { SigningKey = key };

        signer.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

        var keyInfo = new KeyInfo();
        keyInfo.AddClause(new SecurityTokenReference(_x509SecurityToken, SecurityTokenReference.SerializationOptions.Embedded));

        signer.KeyInfo = keyInfo;
        signer.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

        var cn14Transform = new XmlDsigExcC14NTransform();
        string referenceDigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";

        foreach (string id in idsToSign)
        {
            var reference = new Reference("#" + id);
            reference.AddTransform(cn14Transform);
            reference.DigestMethod = referenceDigestMethod;
            signer.AddReference(reference);
        }

        signer.ComputeSignature();

        return signer.GetXml();
    }

seems to work and verifies OK at the other end. Tested with Holodeck the other day and I think it failed on a timestamp that was missing from the signature elements.

However, the signing of attachments seems to be a real problem in .NET - I don't think the relevant transforms are supported at all.



回答2:

Unfortunately Andrew's answer is not applicable when the private key cannot be exported.

I am using a smart-card and so far I have found no way of using SignedXML with SHA-256. This feature seems to be broken in the current implementation of RSACryptoServiceProvider.

The only solution in my opinion would be to switch from CSP to PKCS#11 and then use BouncyCastle.Net. And rewrite everything.