1

我在使用已安装的证书签署 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;

存储位置 CurrentUser

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,我只能获取 PrivateKey。这看起来不是实现签名的正确方法。

使用证书文件

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

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

我怎样才能让第一种方法(LocalMachine)起作用?推荐/最佳实践方法是什么?

作为参考,以下代码用于签署 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));
}
4

1 回答 1

0

LocalMachine商店版本

代替

var SigningKey = myCert.PrivateKey;

var SigningKey = myCert.GetRSAPrivateKey();

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

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

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

(替代方案:升级到 Windows 10,操作系统在 CNG 桥接器中添加了 CAPI 来解决此问题)

当前用户商店版本

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

PFX 版本

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

于 2017-11-03T17:38:52.907 回答