参考 KeyInfo 为 XML 创建数字签名

Create Digital Signature for XML with reference to KeyInfo

我需要根据 ISO20022 标准为 XML 创建数字签名。

该示例显示了 3 个引用,其中一个用于 KeyInfo 元素(具有 #_33d232d2-4591-4b49-b28d-3cb825fbeaa4 URI 的那个)。

<ds:Signature
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/2000/09/xmldsig#rsa-sha1"/>
    <ds:Reference URI="#_33d232d2-4591-4b49-b28d-3cb825fbeaa4">
        <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>h9toHGSlK/x1zE7egK0yEj06W2D9wAEK/VAuiwU8+R8=</ds:DigestValue>
    </ds:Reference>
    <ds:Reference Type="http://uri.etsi.org/01903/v1.3.2#SignedProperties" URI="#_aba0ee84-5f37-499e-a8e8-caa7f398341c-signedprops">
        <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>Ot7tqqOtgtguRadTQi0fh5FU3XL/4/mHIv7Eoy67t/s=</ds:DigestValue>
    </ds:Reference>
    <ds:Reference>
        <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>1ZZln0/NzN/eB1wIrxyp/c3SOjKWnk00Lh1bKTXlTAE=</ds:DigestValue>
    </ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>VRn+Q7K6snvKrFPwtH302iKPjAx1k97TKIvjysdH+/I8EMyzWg20gZ1fO1gjKk245nfzXIsiuoVIZJtBKNSE9Tp+VXegJxyAoXx1bz8fMZIbdjjhXaYzdx2yCGh9Fllrbg+y9RZy9VvG7sLQeu91gOge7GHNIxO6jck96yVsY8k=</ds:SignatureValue>
<ds:KeyInfo Id="_33d232d2-4591-4b49-b28d-3cb825fbeaa4">
    <ds:X509Data>
        <ds:X509IssuerSerial>
            <ds:X509IssuerName>C=SE, O=CMA Small Systems AB, CN=Test CA</ds:X509IssuerName>
            <ds:X509SerialNumber>12345678</ds:X509SerialNumber>
        </ds:X509IssuerSerial>
    </ds:X509Data>
</ds:KeyInfo>
<ds:Object>
    <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
        <xades:SignedProperties Id="_aba0ee84-5f37-499e-a8e8-caa7f398341csignedprops">
            <xades:SignedSignatureProperties>
                <xades:SigningTime>2019-08-23T19:01:41+12:00</xades:SigningTime>
                </xades:SignedSignatureProperties>
            </xades:SignedProperties>
        </xades:QualifyingProperties>
    </ds:Object>
</ds:Signature>

然而,当我尝试对 .NET 6 执行相同操作时,出现以下异常

System.Security.Cryptography.CryptographicException: Malformed reference element.
   at System.Security.Cryptography.Xml.Reference.CalculateHashValue(XmlDocument document, CanonicalXmlNodeList refList)
   at System.Security.Cryptography.Xml.Reference.UpdateHashValue(XmlDocument document, CanonicalXmlNodeList refList)
   at System.Security.Cryptography.Xml.SignedXml.BuildDigestedReferences()
   at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()

检查 SignedXml class,BuildDigestedReferences 方法中的 CanonicalXmlNodeList 似乎缺少 KeyInfo。有什么解决办法吗?


这是我签署 XML

的代码
static void SignXmlWithCert(XmlDocument doc, X509Certificate2 cert)
{
    const string signedPropsIdSuffix = "-signedprops";

    var signedXml = new SignedXml(doc)
    {
        SigningKey = cert.GetRSAPrivateKey()
    };
    signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
    signedXml.SignedInfo.SignatureMethod        = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    var idKeyInfo = "_" + Guid.NewGuid();
    var idKeyInfoProps = "_" + Guid.NewGuid() + signedPropsIdSuffix;
    
    #region keyinfo

    var keyInfo = new KeyInfo();
    var keydata = new KeyInfoX509Data(cert, X509IncludeOption.None);
    keydata.AddIssuerSerial(cert.Issuer, cert.SerialNumber);
    keyInfo.AddClause(keydata);
    keyInfo.Id        = idKeyInfo;
    signedXml.KeyInfo = keyInfo;

    #endregion keyinfo

    #region References
    
    var transform = new XmlDsigEnvelopedSignatureTransform() { Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#" };
    var references = new List<Reference>();
    
    // first reference
    var keyInfoReference = new Reference();
    keyInfoReference.Uri          = "#" + keyInfo.Id;
    keyInfoReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    keyInfoReference.AddTransform(transform);
    references.Add(keyInfoReference);

    //second reference
    var signaturePropertiesReference = new Reference();
    signaturePropertiesReference.Type         = "http://uri.etsi.org/01903/v1.3.2#SignedProperties";
    signaturePropertiesReference.Uri          = "#" + idKeyInfoProps;
    signaturePropertiesReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    signaturePropertiesReference.AddTransform(transform);
    references.Add(signaturePropertiesReference);

    //third reference
    var documentReference = new Reference();
    documentReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    documentReference.AddTransform(transform);
    references.Add(documentReference);

    foreach (var reference in references)
    {
        signedXml.AddReference(reference);
    }

    #endregion

    #region 4. Set up <ds:Object> with <QualifiyingProperties> inside that includes SigningTime

    var URI = "http://uri.etsi.org/01903/v1.3.2#";
    var qualifyingPropertiesRoot = doc.CreateElement("xades", "QualifyingProperties", URI);
    
    var signaturePropertiesRoot = doc.CreateElement("xades", "SignedProperties", URI);
    signaturePropertiesRoot.SetAttribute("Id", idKeyInfoProps);
    
    var SignedSignatureProperties = doc.CreateElement("xades", "SignedSignatureProperties", URI);
    var timestamp = doc.CreateElement("xades", "SigningTime", URI);
    timestamp.InnerText = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz");    // primero de la lista
    
    signaturePropertiesRoot.AppendChild(SignedSignatureProperties);
    SignedSignatureProperties.AppendChild(timestamp);
    qualifyingPropertiesRoot.AppendChild(signaturePropertiesRoot);
    
    var qualifyingPropertiesObject = new DataObject
    {
        Data = qualifyingPropertiesRoot.SelectNodes("."),
        Id   = idKeyInfoProps
    };

    signedXml.AddObject(qualifyingPropertiesObject);
    #endregion

    signedXml.ComputeSignature();
}

it seems that KeyInfo is missing from the CanonicalXmlNodeList in the BuildDigestedReferences method.

那是因为它不是文档的一部分。

Is there any way around this?

将其添加到文档中。我没有尝试手动构建它,而是修改了您的代码以制作两个不同的 SignedXml 对象,一个将节点放入待签名的对象中,另一个进行最终签名。

注释掉的代码行是我必须更改或删除以使您的代码段正常工作的行。

static void SignXmlWithCert(XmlDocument doc, X509Certificate2 cert)
{
    const string signedPropsIdSuffix = "-signedprops";

    var signedXml = new SignedXml(doc)
    {
        SigningKey = cert.GetRSAPrivateKey()
    };
    signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
    signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

    var idKeyInfo = "_" + Guid.NewGuid();
    var idKeyInfoProps = "_" + Guid.NewGuid() + signedPropsIdSuffix;

    #region keyinfo

    var keyInfo = new KeyInfo();
    var keydata = new KeyInfoX509Data(cert, X509IncludeOption.None);
    keydata.AddIssuerSerial(cert.Issuer, cert.SerialNumber);
    keyInfo.AddClause(keydata);
    keyInfo.Id = idKeyInfo;
    signedXml.KeyInfo = keyInfo;

    #endregion keyinfo

    #region References

    //var transform = new XmlDsigEnvelopedSignatureTransform() { Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#" };
    var transform = new XmlDsigExcC14NTransform();
    var references = new List<Reference>();

    // first reference
    var keyInfoReference = new Reference();
    keyInfoReference.Uri = "#" + keyInfo.Id;
    keyInfoReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    keyInfoReference.AddTransform(transform);
    references.Add(keyInfoReference);

    //second reference
    var signaturePropertiesReference = new Reference();
    signaturePropertiesReference.Type = "http://uri.etsi.org/01903/v1.3.2#SignedProperties";
    signaturePropertiesReference.Uri = "#" + idKeyInfoProps;
    signaturePropertiesReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    signaturePropertiesReference.AddTransform(transform);
    references.Add(signaturePropertiesReference);

    //third reference
    var documentReference = new Reference();
    documentReference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    // The code in the question didn't assign Uri, and since no transform did an inherent
    // node resolution, signing failed.
    documentReference.Uri = "";
    documentReference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
    documentReference.AddTransform(transform);
    references.Add(documentReference);

    foreach (Reference reference in references)
    {
        signedXml.AddReference(reference);
    }

    #endregion

    #region 4. Set up <ds:Object> with <QualifiyingProperties> inside that includes SigningTime

    var URI = "http://uri.etsi.org/01903/v1.3.2#";
    XmlElement qualifyingPropertiesRoot = doc.CreateElement("xades", "QualifyingProperties", URI);

    XmlElement signaturePropertiesRoot = doc.CreateElement("xades", "SignedProperties", URI);
    signaturePropertiesRoot.SetAttribute("Id", idKeyInfoProps);

    XmlElement SignedSignatureProperties = doc.CreateElement("xades", "SignedSignatureProperties", URI);
    XmlElement timestamp = doc.CreateElement("xades", "SigningTime", URI);
    timestamp.InnerText = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz");    // primero de la lista

    signaturePropertiesRoot.AppendChild(SignedSignatureProperties);
    SignedSignatureProperties.AppendChild(timestamp);
    qualifyingPropertiesRoot.AppendChild(signaturePropertiesRoot);

    var qualifyingPropertiesObject = new DataObject
    {
        Data = qualifyingPropertiesRoot.SelectNodes("."),
        //Id = idKeyInfoProps
    };

    signedXml.AddObject(qualifyingPropertiesObject);
    #endregion

    SignedXml tmp = new SignedXml(doc)
    {
        SigningKey = signedXml.SigningKey,
        KeyInfo = signedXml.KeyInfo,
    };

    foreach (DataObject obj in signedXml.Signature.ObjectList)
    {
        tmp.AddObject(obj);
    }
    
    tmp.AddReference(new Reference(""));
    tmp.ComputeSignature();

    XmlTextWriter prettyOut = new XmlTextWriter(System.Console.Out);
    prettyOut.Formatting = Formatting.Indented;

    Console.WriteLine("Original document");
    doc.WriteTo(prettyOut);
    Console.WriteLine();
    Console.WriteLine();

    XmlElement elem = tmp.GetXml();
    doc.DocumentElement.AppendChild(elem);
    Console.WriteLine("Stage 1 signed");
    doc.WriteTo(prettyOut);
    Console.WriteLine();
    Console.WriteLine();

    signedXml.ComputeSignature();
    doc.DocumentElement.RemoveChild(elem);
    doc.DocumentElement.AppendChild(signedXml.GetXml());
    Console.WriteLine("Stage 2 signed");
    doc.WriteTo(prettyOut);
}