【发布时间】: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