2

是否可以在不将 URI 属性传递给<Reference>标记的情况下签署 XML 文档?

我能够<Invoice>使用KeyInfoX509Data证书和使用 C# 的 XAdEs 格式签署整个 XML 文档类型。签名有效,SignedXml.CheckSignature()返回true。问题是负责验证签名的代理不支持空属性,例如:

<ds:Reference URI=""> // Here is the problem
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>rDCBSbPc1QFATbQeP+77oskLJ3Tw6aog61bqGtyglXs=</ds:DigestValue>
        </ds:Reference>

我决定在将文档发送到代理之前删除 URI 属性,但没有它SignedXml.CheckSignature()返回false。没有 URI,签名无效。

我尝试在调用之前删除 URI 属性SignedXml.ComputeSignature(),但出现以下异常:

An XmlDocument context is required for enveloped transforms.

这意味着在使用XmlDsigEnvelopedSignatureTransform类生成签名时,URI 属性是必需的。

然后,我尝试为<Invoice>标签设置一个 Id 属性:

<Invoice Id="id-dsig-123456"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../oasis/maindoc/UBL-Invoice-2.1.xsd"
     xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
     xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
     xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">

这意味着签名仍将通过在标签<Invoice>上搜索 Id 属性来验证整个文档:<Reference>

<ds:Reference URI="#id-dsig-123456">

生成签名并SignedXml.CheckSignature()返回 true,但代理不允许<Invoice>标签上的任何属性并使签名无效。

我需要像这样生成签名

<ds:Signature Id="id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
              xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
        <ds:Reference> ***// Without URI***
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>rDCBSbPc1QFATbQeP+77oskLJ3Tw6aog61bqGtyglXs=</ds:DigestValue>
        </ds:Reference>
        <ds:Reference URI="#xades-id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
                      Type="http://uri.etsi.org/01903#SignedProperties">
            <ds:Transforms>
                <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>xBW/yL+uQJa3KrCXjdEPlFEJns7ZnM2pHs0Y5nLNvMM=</ds:DigestValue>
        </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>XDTgRB3qyzLPMBPzfYeuwGSOz5JN52cdstIMHo3IXc8jGWp5JWFbOTD7Nj7QHZB0z5mULXgL7/eWfR5KkrT12aRHWPHocCbLfBgAi1xjNYGW1aNNxhnn4fSZSg4KrWS9oMoxkEKM2IrbJ++PgIM4+89MZA4kEtyih4WKkENnlE+w4RDnstjirA5viLZQEBDegPtGqS9ybrVej5QkF5/fy8HpZKsEl4oWPIWSrTQk7G3d4oDo/d2AU8XQgvcht+LkttJF4PJRq3AiyqwlJjoNSbt1R7NWYEJp+IulWEU0pdvW7LcVjrLO8yIkoHTWtyhbP20Zi3zLHxP9uPpMD37tzQ==</ds:SignatureValue>
    <ds:KeyInfo>
        <ds:X509Data>
            <ds:X509Certificate>Certificate raw content...</ds:X509Certificate>
        </ds:X509Data>
    </ds:KeyInfo>
    <ds:Object>
        <xades:QualifyingProperties Target="#id-a086e4d3-ee02-458c-8429-eafe54eb6f6b"
                                    xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
            <xades:SignedProperties Id="xades-id-a086e4d3-ee02-458c-8429-eafe54eb6f6b">
                <xades:SignedSignatureProperties>
                    <xades:SigningTime>2021-06-05T02:52:49Z</xades:SigningTime>
                    <xades:SigningCertificate>
                        <xades:Cert>
                            <xades:CertDigest>
                                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                                <ds:DigestValue>LgjnhFUgsN4oTt4wjtzF+/7GYHEkTLVijMpjlMjfZ2E=</ds:DigestValue>
                            </xades:CertDigest>
                            <xades:IssuerSerial>
                                <ds:X509IssuerName>Certificate data....</ds:X509IssuerName>
                                <ds:X509SerialNumber>Serial number...</ds:X509SerialNumber>
                            </xades:IssuerSerial>
                        </xades:Cert>
                    </xades:SigningCertificate>
                </xades:SignedSignatureProperties>
            </xades:SignedProperties>
        </xades:QualifyingProperties>
    </ds:Object>
</ds:Signature>

要生成签名,我使用以下代码。该类PrefixedSignedXml继承自SignedXml并用于ds:在使用方法时设置“”前缀SignedXml

private PrefixedSignedXml GeneratePrefixedSignedXml(XmlDocument xmlDocument, X509Certificate2 certificate)
    {
        Log.Information("[Begin] - GeneratePrefixedSignedXml");

        Sha256SignatureDescription.Register();

        var keyInfo = new KeyInfo();
        var cspParams = new CspParameters(24) { KeyContainerName = PrefixedSignedXml.XmlKeyContainerName };
        var rsaKey = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };

        keyInfo.AddClause(new KeyInfoX509Data(certificate));

        var signedXml = new PrefixedSignedXml(xmlDocument) { SigningKey = rsaKey };

        signedXml.KeyInfo = keyInfo;
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
        signedXml.SignedInfo.SignatureMethod = PrefixedSignedXml.XmlRsaSignatureMethod;

        var signatureReference = new Reference
        {
            Uri = "", // This needs to be null
            DigestMethod = PrefixedSignedXml.XmlRsaDigestMethod,
        };

        signatureReference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        signedXml.AddReference(signatureReference);

        Log.Information("[End] - GeneratePrefixedSignedXml");

        return signedXml;
    }

这是PrefixedSignedXml

public class PrefixedSignedXml : SignedXml
{
    public const string XmlDsigSignatureProperties = "http://uri.etsi.org/01903#SignedProperties";
    public const string XadesProofOfApproval = "http://uri.etsi.org/01903/v1.2.2#ProofOfApproval";
    public const string XadesNamespaceUrl = "http://uri.etsi.org/01903/v1.3.2#";
    public const string XmlRsaSignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
    public const string XmlRsaDigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    public const string DsaSignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
    public const string XmlKeyContainerName = "XML_DSIG_RSA_KEY";

    private const string CryptographicXmlLoadKeyFailedException = "Cryptography_Xml_LoadKeyFailed";
    private const string CryptographicXmlCreatedKeyFailedException = "Cryptography_Xml_CreatedKeyFailed";
    private const string CryptographicXmlSignatureDescriptionNotCreatedException = "Cryptography_Xml_SignatureDescriptionNotCreated";
    private const string CryptographicExceptionXmlCreateHashAlgorithmFailed = "Cryptography_Xml_CreateHashAlgorithmFailed";
    private const string MethodInfoName = "BuildDigestedReferences";

    public XmlElement PropertiesNode { get; set; }

    private readonly List<DataObject> _dataObjects = new List<DataObject>();

    public PrefixedSignedXml(XmlDocument document)
        : base(document) { }

    public override XmlElement GetIdElement(XmlDocument document, string idValue)
    {
        if (string.IsNullOrEmpty(idValue))
            return null;

        var xmlElement = base.GetIdElement(document, idValue);

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

            xmlElement = document.SelectSingleNode("//*[@Id=\"" + idValue + "\"]", nsManager) as XmlElement;
        }

        if (xmlElement != null)
            return xmlElement;

        if (_dataObjects.Count == 0)
            return null;

        foreach (var dataObject in _dataObjects)
        {
            var nodeWithSameId = XmlHelper.FindNodeWithAttributeValueIn(dataObject.Data, "Id", idValue);

            if (nodeWithSameId != null)
                return nodeWithSameId;
        }

        return null;
    }

    public new void AddObject(DataObject dataObject)
    {
        base.AddObject(dataObject);
        _dataObjects.Add(dataObject);
    }

    public void ComputeSignature(string prefix)
    {
        this.BuildDigestedReferences();

        var signingKey = this.SigningKey;

        if (signingKey == null)
            throw new CryptographicException(CryptographicXmlLoadKeyFailedException);

        if (this.SignedInfo.SignatureMethod == null)
        {
            if ((signingKey is DSA))
                this.SignedInfo.SignatureMethod = DsaSignatureMethod;

            if (!(signingKey is RSA))
                throw new CryptographicException(CryptographicXmlCreatedKeyFailedException);

            if (this.SignedInfo.SignatureMethod == null)
                this.SignedInfo.SignatureMethod = XmlRsaSignatureMethod;
        }

        if (!(CryptoConfig.CreateFromName(this.SignedInfo.SignatureMethod) is SignatureDescription description))
            throw new CryptographicException(CryptographicXmlSignatureDescriptionNotCreatedException);

        var hash = description.CreateDigest();

        if (hash == null)
            throw new CryptographicException(CryptographicExceptionXmlCreateHashAlgorithmFailed);

        this.GetC14NDigest(hash, prefix);
        this.m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
    }

    public XmlElement GetXml(string prefix)
    {
        var xmlElement = this.GetXml();

        XmlHelper.SetPrefix(prefix, xmlElement);

        return xmlElement;
    }

    private void BuildDigestedReferences()
    {
        var type = typeof(SignedXml);

        var methodInfo = type.GetMethod(MethodInfoName, BindingFlags.NonPublic | BindingFlags.Instance);

        methodInfo.Invoke(this, new object[] { });
    }

    private byte[] GetC14NDigest(HashAlgorithm hash, string prefix)
    {
        var canonicalizationMethodObject = this.SignedInfo.CanonicalizationMethodObject;

        var document = new XmlDocument
        {
            PreserveWhitespace = true
        };

        document.AppendChild(document.ImportNode(this.SignedInfo.GetXml(), true));

        XmlHelper.SetPrefix(prefix, document.DocumentElement);

        canonicalizationMethodObject.LoadInput(document);

        return canonicalizationMethodObject.GetDigestedOutput(hash);
    }
}

这是用于添加 XAdEs 属性的方法:

private void AddXadesProperties(XmlDocument document, PrefixedSignedXml xadesSignedXml, X509Certificate2 signingCertificate)
    {
        Log.Information("[Begin] - AddXadesProperties");

        var parametersSignature = new Reference
        {
            Uri = $"#{SignaturePropertiesId}{SignatureId}",
            Type = PrefixedSignedXml.XmlDsigSignatureProperties,
            DigestMethod = PrefixedSignedXml.XmlRsaDigestMethod
        };

        parametersSignature.AddTransform(new XmlDsigExcC14NTransform());
        xadesSignedXml.AddReference(parametersSignature);

        // <Object>
        var objectNode = document.CreateElement(XmlDsPrefix, "Object", SignedXml.XmlDsigNamespaceUrl);

        // <Object><QualifyingProperties>
        var qualifyingPropertiesNode = document.CreateElement(XadesPrefix, "QualifyingProperties", PrefixedSignedXml.XadesNamespaceUrl);

        qualifyingPropertiesNode.SetAttribute("Target", $"#{SignatureId}");
        objectNode.AppendChild(qualifyingPropertiesNode);

        // <Object><QualifyingProperties><SignedProperties>
        var signedPropertiesNode = document.CreateElement(XadesPrefix, "SignedProperties", PrefixedSignedXml.XadesNamespaceUrl);

        signedPropertiesNode.SetAttribute("Id", $"{SignaturePropertiesId}{SignatureId}");
        qualifyingPropertiesNode.AppendChild(signedPropertiesNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties>
        var signedSignaturePropertiesNode = document.CreateElement(XadesPrefix, "SignedSignatureProperties", PrefixedSignedXml.XadesNamespaceUrl);

        signedPropertiesNode.AppendChild(signedSignaturePropertiesNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties></SigningTime>
        var signingTime = document.CreateElement(XadesPrefix, "SigningTime", PrefixedSignedXml.XadesNamespaceUrl);

        signingTime.InnerText = $"{DateTime.UtcNow:s}Z";
        signedSignaturePropertiesNode.AppendChild(signingTime);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate>
        var signingCertificateNode = document.CreateElement(XadesPrefix, "SigningCertificate", PrefixedSignedXml.XadesNamespaceUrl);

        signedSignaturePropertiesNode.AppendChild(signingCertificateNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert>
        var certNode = document.CreateElement(XadesPrefix, "Cert", PrefixedSignedXml.XadesNamespaceUrl);

        signingCertificateNode.AppendChild(certNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest>
        var certDigestNode = document.CreateElement(XadesPrefix, "CertDigest", PrefixedSignedXml.XadesNamespaceUrl);

        certNode.AppendChild(certDigestNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest></DigestMethod>
        var digestMethod = document.CreateElement(XmlDsPrefix, "DigestMethod", SignedXml.XmlDsigNamespaceUrl);
        var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm");

        digestMethodAlgorithmAtribute.InnerText = PrefixedSignedXml.XmlRsaDigestMethod;
        digestMethod.Attributes.Append(digestMethodAlgorithmAtribute);
        certDigestNode.AppendChild(digestMethod);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest></DigestMethod>
        var digestValue = document.CreateElement(XmlDsPrefix, "DigestValue", SignedXml.XmlDsigNamespaceUrl);

        digestValue.InnerText = GenerateCertificateHash256Value(signingCertificate);
        certDigestNode.AppendChild(digestValue);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial>
        var issuerSerialNode = document.CreateElement(XadesPrefix, "IssuerSerial", PrefixedSignedXml.XadesNamespaceUrl);

        certNode.AppendChild(issuerSerialNode);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial></X509IssuerName>
        var x509IssuerName = document.CreateElement(XmlDsPrefix, "X509IssuerName", SignedXml.XmlDsigNamespaceUrl);

        x509IssuerName.InnerText = signingCertificate.IssuerName.Name;
        issuerSerialNode.AppendChild(x509IssuerName);

        // <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial></X509SerialNumber>
        var x509SerialNumber = document.CreateElement(XmlDsPrefix, "X509SerialNumber", SignedXml.XmlDsigNamespaceUrl);

        x509SerialNumber.InnerText = ToDecimalSerialNumberString(signingCertificate.SerialNumber);
        issuerSerialNode.AppendChild(x509SerialNumber);

        var dataObject = new DataObject { Data = qualifyingPropertiesNode.SelectNodes(".") };

        xadesSignedXml.AddObject(dataObject);

        Log.Information("[End] - AddXadesProperties");
    }

之后,ComputeSignature()生成摘要和初始签名,然后我将<ds:SignedInfo>标签传递给另一台生成最终<ds:SignatureValue>. 最后,我将 传递<SignatureValue><ds:Signature>自身,附加到XmlDocument并发送给代理。

有谁知道是否可以在不将 URI 属性传递给<Reference>标签的情况下签署 XML 文档?

项目是使用 .NetFramework 4.6 的 ClassLibrary。

我面临这个问题一段时间,任何帮助将不胜感激。

4

1 回答 1

0

事实证明我一直都是对的。在这种*<Reference URI="#id-value">*情况下,标签是必需的,并且代理已更新以支持此格式。

感谢@jdweng的支持。

于 2021-07-23T12:12:10.930 回答