ssl 使用智能卡签名的XML声明文件数字签名无效

insrf1ej  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(156)

智能卡的XML文件DSig(数字签名)无效

我正在尝试使用智能卡签署XML文件。我使用的物理智能卡称为SafeNet eTokenYubikey from Yubico。我也使用全数字智能卡。对于这个问题,我将使用SafeNet eToken。此智能卡上有一个证书,当您插入USB记忆棒时,该证书在Windows Certificate Store中可见。它是一个RSA Certificate,有公钥和私钥。您可以将证书安装到机器上,但是您需要USB记忆棒作为私钥。此外,您还需要私钥的PIN,并且必须安装正确的驱动程序。
现在我有了一个C#方法,它通过使用保存XML文件的路径将其加载到XmlDocument对象并将其作为参数传递。另外,我使用智能卡的名称从Windows Certificate Store获取证书,并将此证书作为参数传递。
当我执行该方法时,SafeNet eToken的客户端启动并要求输入PIN。当正确输入PIN时,文件将使用智能卡的私钥进行签名。XML文件被保存在我写的路径,当我读它一切看起来很好。可以看到,XML文件已签名,并且正典化也有效。
但是,当我尝试在Chilkat Tools上验证XML数字签名时,我每次都得到一条错误消息,说我的签名无效。我尝试了.NET本身和其他一些工具的验证方法,我总是得到相同的结果。签名无效。我用哪张智能卡都没关系。看起来它可以与所有人一起工作,但是在最后签名无效。
现在我很难找出为什么会发生这种情况。看看答案,找出原因。

/// <summary>
        /// Signs an XMLDocument with additional Keyinfo with a Smart Card. 
        /// The Smart Card can be physical like a Yubikey from Yubico or completely digitally.
        /// Access to the private key of the certificate is mandatory.
        /// </summary>
        /// <param name="claimsDocument"></param>
        /// <param name="cert"></param>
        /// <returns></returns>
        public void SignClaimsDocument(XmlDocument claimsDocument, X509Certificate2 cert)
        {
            // Variables
            const string outputPath = @"C:\Example\User\Files\signed_claims";
            
            SignedXml signedClaims = new SignedXml(claimsDocument)
            {
                // Add the key to the SignedXml document.
                SigningKey = cert.GetRSAPrivateKey()
            };

            // Create a reference to be signed. Letting the Uri variable empty means that the whole XML will be signed.
            Reference reference = new Reference
            {
                Uri = ""
            };

            // Add an enveloped transformation to the reference.
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

            // Add canonicalization transformation to the reference using C14N Type.
            reference.AddTransform(new XmlDsigC14NTransform());

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

            // Include the public key of the certificate in the assertion.
            KeyInfo keyinfo = new KeyInfo();

            // Add Keyinfo
            KeyInfoX509Data kiData = new KeyInfoX509Data();
            kiData.AddSubjectName(cert.SubjectName.Name);
            kiData.AddCertificate(cert);
            keyinfo.AddClause(kiData);

            // RSAKeyValue
            RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
            RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
            keyinfo.AddClause(rkv);

            signedClaims.KeyInfo = keyinfo;

            try
            {
                // Computes the signature using signedClaims.SigningKey. This happens because we give no parameter to the ComputeSignature method.
                signedClaims.ComputeSignature();
            }
            catch (CryptographicException cex)
            {
                Console.WriteLine(cex);
                throw cex;
            }

            // Get the XML representation of the signature and save
            // it to an XmlElement object.
            XmlElement xmlDigitalSignature = signedClaims.GetXml();

            // Appends the element to the XML document.
            claimsDocument.DocumentElement?.AppendChild(claimsDocument.ImportNode(xmlDigitalSignature, true));

            // Saves the signed XML Claims File at the given path
            claimsDocument.Save(outputPath + "_" + DateTime.Now.ToString("mm/HH-dd/MM/yyyy") + ".xml");
    
        }
h43kikqp

h43kikqp1#

Signature无效原因

您需要知道,更改已签名XML文件的内容当然会使签名无效。过了一会儿,我发现问题出现在我的方法的最后一行。
当保存XmlDocument类型的claimsDocument变量时,它使用XmlDocument类中的.Save(outputPath)方法。
在我的方法中看起来像这样:

// Saves the signed XML Claims File at the given path
claimsDocument.Save(outputPath + "_" + DateTime.Now.ToString("mm/HH-dd/MM/yyyy") + ".xml");

当我们检查.Save(outputPath)方法的代码时,我们看到一些东西解释了一切。

保存方式:

/// <summary>Saves the XML document to the specified file. If the specified file exists, this method overwrites it.</summary>
    /// <param name="filename">The location of the file where you want to save the document. </param>
    /// <exception cref="T:System.Xml.XmlException">The operation would not result in a well formed XML document (for example, no document element or duplicate XML declarations). </exception>
    public virtual void Save(string filename)
    {
      if (this.DocumentElement == null)
        throw new XmlException("Xml_InvalidXmlDocument", Res.GetString("Xdom_NoRootEle"));
      XmlDOMTextWriter w = new XmlDOMTextWriter(filename, this.TextEncoding);
      try
      {
        if (!this.preserveWhitespace)
          w.Formatting = Formatting.Indented;
        this.WriteTo((XmlWriter) w);
        w.Flush();
      }
      finally
      {
        w.Close();
      }
    }

这里可以看到if子句,它检查preserveWhitespace bool变量是否为false。默认情况下,这个变量是false,if-clause中的代码将被执行。在这个if子句中,XML文件被格式化/美化。这些只是空白和新行。
问题是空格和新行都是字符,由于这种格式化发生在签名之后,XML中的数据被更改,使签名无效。
在使用.Save(outputPath)方法之前,只需将preserveWhitespace设置为true一行,就可以轻松解决这个问题。

如下:

// Preservers whitespace to avoid formatting after signing XML Claims File. Otherwise the Signature turn be invalid. 
claimsDocument.PreserveWhitespace = true;

完全固定方式:

/// <summary>
        /// Signs an XMLDocument with additional Keyinfo with a Smart Card. 
        /// The Smart Card can be physical like a Yubikey from Yubico or completely digitally.
        /// Access to the private key of the certificate is mandatory.
        /// </summary>
        /// <param name="claimsDocument"></param>
        /// <param name="cert"></param>
        /// <returns></returns>
        public void SignClaimsDocument(XmlDocument claimsDocument, X509Certificate2 cert)
        {
            // Variables
            const string outputPath = @"C:\Example\User\Files\signed_claims";
            
            SignedXml signedClaims = new SignedXml(claimsDocument)
            {
                // Add the key to the SignedXml document.
                SigningKey = cert.GetRSAPrivateKey()
            };

            // Create a reference to be signed. Letting the Uri variable empty means that the whole XML will be signed.
            Reference reference = new Reference
            {
                Uri = ""
            };

            // Add an enveloped transformation to the reference.
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

            // Add canonicalization transformation to the reference using C14N Type.
            reference.AddTransform(new XmlDsigC14NTransform());

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

            // Include the public key of the certificate in the assertion.
            KeyInfo keyinfo = new KeyInfo();

            // Add Keyinfo
            KeyInfoX509Data kiData = new KeyInfoX509Data();
            kiData.AddSubjectName(cert.SubjectName.Name);
            kiData.AddCertificate(cert);
            keyinfo.AddClause(kiData);

            // RSAKeyValue
            RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
            RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
            keyinfo.AddClause(rkv);

            signedClaims.KeyInfo = keyinfo;

            try
            {
                // Computes the signature using signedClaims.SigningKey. This happens because we give no parameter to the ComputeSignature method.
                signedClaims.ComputeSignature();
            }
            catch (CryptographicException cex)
            {
                Console.WriteLine(cex);
                throw cex;
            }

            // Get the XML representation of the signature and save
            // it to an XmlElement object.
            XmlElement xmlDigitalSignature = signedClaims.GetXml();

            // Appends the element to the XML document.
            claimsDocument.DocumentElement?.AppendChild(claimsDocument.ImportNode(xmlDigitalSignature, true));
    
            // Preservers whitespace to avoid formatting after signing XML Claims File.    
            // Otherwise the Signature would turn invalid because the data inside the XML would change after the Signing. 
            claimsDocument.PreserveWhitespace = true;

            // Saves the signed XML Claims File at the given path
            claimsDocument.Save(outputPath + "_" + DateTime.Now.ToString("mm/HH-dd/MM/yyyy") + ".xml");
    
        }

现在,由于PreserveWhitespace变量是true,签名后没有格式化,因此签名的XML文件没有修改,这意味着签名是有效的。Chilkat XML DSig Verification批准签名有效,我也尝试了其他所有工具。此外,来自我国的API验证签名有效,这是最重要的。

**快速摘要:**默认情况下,.NET中XmlDocument类的保存方法本身会美化XML文件。由于签名过程发生在我们保存已签名的XML文件之前,因此格式化发生在签名之后,这使得签名无效。即使格式化只添加新行和空格,这些也被视为字符。因此,您需要将要签名和保存的XmlDocument对象的bool variable PreserveWhitespace设置为true

相关问题