【问题标题】:XML verification fails, SignedXml.CheckSignature always returns falseXML 验证失败,SignedXml.CheckSignature 总是返回 false
【发布时间】:2017-01-14 11:18:42
【问题描述】:

我正在尝试按照 Microsoft 指令验证已签名的 XML 文档,但 CheckSignature 始终返回 false。我正在使用.NET 4.5。最初我没有使用 C14 转换,但它不起作用,它被建议作为 .NET 4.5 中的解决方案但不起作用。请注意,保留空格对于签名和验证都是正确的。

在代码 sn-ps 中,为了简单起见,我删除了错误检查代码。 是的,我检查了类似的问题,但没有找到答案。

证书(.PFX 和 .CER)

签名和验证是通过文件(而不是存储)上的证书完成的:

  • 使用 PluralSight SelfCert 自签名证书工具
  • 使用受密码保护的可导出私钥创建了用于数字签名的个人自签名证书。我可以使用 certmgr.msc 在我的个人商店中查看证书
  • 我通过签名包含公钥和私钥的证书 (.PFX) 保存
  • 我使用 certmgr.msc 导出证书没有私钥,所以只有公钥。这仅用于验证,并已保存到 .CER 文件中

到目前为止一切顺利,证书很好。个人商店显示我的签名证书以及证书和密钥图标。

签署 XML 文档

现在为了签署 XML,我正在使用以下代码:

static void SignXML(XmlDocument xmlDoc, RSA Key)
{
        SignedXml signedXml = new SignedXml(xmlDoc);
        if (Properties.Settings.Default.UseC14Transform)
        {
            // http://stackoverflow.com/questions/13632630/signedxml-checksignature-fails-in-net-4-but-it-works-in-net-3-5-3-or-2
            signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
        }

        // Add the key to the SignedXml document.
        signedXml.SigningKey = Key;

        // Create a reference to be signed.
        Reference reference = new Reference();
        reference.Uri = "";
        if (Properties.Settings.Default.UseC14Transform)
        {
            reference.AddTransform(new XmlDsigExcC14NTransform());  // required to match doc
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
        }
        else
        {
            // Add an enveloped transformation to the reference.
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
        }

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

        // Compute the signature.
        signedXml.ComputeSignature();

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

        // Append the element to the XML document.
        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
}

签名代码是这样调用的:

XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
using (StringWriter _writer = new StringWriter())
{
    XmlSerializer serializer = new XmlSerializer(typeof(Models.Protected), new Type[] { param1.GetType() });
    serializer.Serialize(_writer, param1);
    doc.LoadXml(_writer.ToString());
}
// get RSA key from certificate (.PFX file)
 X509Certificate2 cert = new X509Certificate2(certPrivateKeyFilePath, certFilePwd);
RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)cert.PrivateKey;
 SignXML(doc, rsaKey);
 return Convert.ToBase64String(Encoding.UTF8.GetBytes(doc.OuterXml));

XML 签名验证

我的验证方式如下:

static Boolean VerifyXml(XmlDocument Doc, X509Certificate2 cert)
{
    RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)cert.PublicKey.Key;
    SignedXml signedXml = new SignedXml(Doc);
    XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
    signedXml.LoadXml((XmlElement)nodeList[0]);
    return signedXml.CheckSignature(rsaKey);
}

依次调用如下:

Models.Protected mdl = null;
try
{   // The certificate is in a .CER file, only has PUBLIC key
    X509Certificate2 cert = new X509Certificate2(certPubKeyFilePath);
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.PreserveWhitespace = true; 
      xmlDoc.LoadXml(DocEncoding.GetString(Convert.FromBase64String(b64String)));
    if (VerifyXml(xmlDoc, cert)
    {
         XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
         xmlDoc.DocumentElement.RemoveChild(nodeList[0]);
         XmlSerializer _serializer = new XmlSerializer(typeof(Models.Protected), new Type[] { typeof(Models.Protected) });
         using (StringReader _reader = new StringReader(_safeXML))
                {
                    _safe = (Models.Protected)_serializer.Deserialize(_reader);
                }
    }
 }
 catch { /* something here */ }

    return _safe;
}

杂项

正在签名的对象是一个可序列化的 Models.Protected 类。我已经验证它可以正确序列化,所以问题不存在。

签名过程会生成一个具有预期 XML 签名的有效 XL 文件,然后将其转换为 Base64 字符串。

验证过程读取一个 Base64 字符串,我已验证该字符串与生成的字符串相同。解码时读取的同一 B64 字符串生成的签名 XML 文档与签名完成时生成的文档完全相同。

生成的两个签名 XML 的 MD5 哈希与验证之前获得的签名 XML 的 MD5 哈希相同。因此它们是绝对平等的,它们的签名也是如此。

因此,我无法理解为什么 CheckSignature 会返回 false,即使内容相同并且证书已正确生成(.PFX 和 .CER)。任何地方都不会抛出异常,它只是返回 false。

谁能发现我做错了什么

【问题讨论】:

  • 我刚刚尝试了您的代码,其中唯一的修改是从(固定的)RSAParameters 构建 RSA 对象并使用固定的输入字符串,它工作正常;都具有独占的 c14n 和默认的。因此,您可能想检查您是否真的在双方都使用了相同的证书。例如,获取 RSA 对象,调用 ExportParameters(false) 并打印 Modulus 值。 (这里可以进行大量清理工作,但我的测试表明您的代码可以正常工作,这表明存在数据问题)。如果做不到这一点,请从您的序列化程序中提供一个示例输入字符串。
  • @bartonjs 我该死!我按照你的建议做了,模数不一样!很奇怪,因为我从商店中的证书生成了 .CER。但是,我没有在 SelfCert PluralSight 实用程序中切换到 .NET 3.5(不知道这是否有作用),然后我没有使用它在商店中保存证书,而是使用保存的 PFX 文件手动安装它在商店里。然后我去商店把它出口了。问题就这样解决了。现在它可以正确验证了。
  • 很高兴我能帮上忙 :)

标签: .net xml .net-4.5 x509 xml-signature


【解决方案1】:

感谢@bartonjs 的精彩提示,我可以验证在签名中找到的模数并验证证书是否相同。正如我所提到的,我在出现问题时和在新的(已修复的)情况下都使用了 PluralSight 免费的 SelfCert GUI。

没有对代码进行任何更改,问题出在证书上,即使用于验证的 .CER 文件是从存储的证书中生成的。

差异是可操作的。如果我让 SelfCert.exe 在商店中安装证书,验证方法将失败。另一方面,如果在生成 .PFX 文件后,我确实使用其 GUI 上的“保存”按钮,而是使用生成的 .PFX 文件手动在商店中安装证书及其私有密钥,然后才使用存储的证书将其导出为 .CER 文件。现在模数相同,签名/验证过程顺利进行,没有失败。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-09
    • 2022-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多