使用已安装的 X.509 证书签署 xml 文档

Signing xml document with a installed X.509 certificate

我在使用安装的证书签署 xml 文档时遇到问题。我尝试使用证书文件 (.pfx) 在 X509Certificate2 的 LocalMachine、CurrentUser 和 Initialize 实例中安装证书。每个人都有自己的问题;我的偏好是使用安装在 LocalMachine 商店中的证书。下面我概述了三种方法以及每种方法的注意事项:

StoreLocation LocalMachine - 首选方法

var certStore = new X509Store(StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbPrint, true);
var myCert = certCollection[0];

// I can get the correct certificate but the following line throws "Invalid provider type specified." error
var SigningKey = myCert.PrivateKey;

StoreLocation 当前用户

var certStore = new X509Store(StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbPrint, true);
var myCert = certCollection[0];

// I can get the correct certificate but the following line throws "Keyset does not exist" error
var SigningKey = myCert.PrivateKey;

如果我将权限更改为以下文件夹 %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys,我只能获取私钥。这看起来不是实现签名的正确方法。

使用证书文件

var certificateFile = @"C:\CertificateFolder\AuthorizedCertificate.pfx";
var myCert = new X509Certificate2(certificateFile, password, X509KeyStorageFlags.UserKeySet);

此方法有效,但我必须提供证书文件和密码,这是不可取的。

如何让第一个方法 (LocalMachine) 起作用? recommended/best 练习方法是什么?

作为参考,以下代码用于签署 xml 文档

private void SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
    // Create a SignedXml object.
    SignedXml signedXml = new SignedXml(xmlDoc);

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

    // Create a reference to be signed.
    Reference reference = new Reference();
    reference.Uri = "";

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

    // Include the public key of the certificate in the assertion.
    signedXml.KeyInfo = new KeyInfo();
    signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.WholeChain));

    // 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));
}

本地机器商店版本

替换

var SigningKey = myCert.PrivateKey;

var SigningKey = myCert.GetRSAPrivateKey();

(s/RSA/DSA/视情况而定)

PrivateKey 属性 只能 return 可转换为 RSACryptoServiceProvider 或 DSACryptoServiceProvider 的密钥。 “指定的提供商类型无效”表示您的私钥存储在 CNG 中,而不是 CAPI。

这仅在您安装了 .NET 4.6.2 或更高版本时才有效,因为那时 SignedXml 及其助手 类 中的某些限制(关于非 RSACryptoServiceProvider RSA)已得到修复。

(备选方案:升级到 Windows 10,其中 OS 添加了一个 CAPI 到 CNG 桥来解决这个问题)

当前用户存储版本

此版本失败,因为当您从 PFX 导入证书时,您使用 MachineKeySet 导入了它(或者您没有指定 UserKeySet,并且它之前是从计算机密钥存储中导出的)。用户存储中的证书副本表明它的私钥存在于机器存储中。而且,出于某种原因,您无权访问它。 (“出于某种原因”,因为通常这表明您无法添加它...)

PFX 版本

这是可行的,因为 PFX 表示密钥应存储在 CAPI CSP 中(PFX 携带大量元数据),从而允许证书私钥 属性 发挥作用。