证书存储区中证书的私钥不可读

时间:2012-11-05 12:11:27

标签: c# certificate uac custom-action private-key

我想我有同样的问题like this guy,但我没有他/她那么幸运,因为提供的解决方案对我不起作用。

提供的解决方案在C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys(子目录中)和C:\Users\[Username]\AppData\Roaming\Microsoft\Crypto\RSA(和子目录)中查找文件 但是,由于我希望安装程序将应用程序安装到所有用户,因此自定义操作在SYSTEM - 用户下运行,这导致实际在C:\ProgramData\Application Data\Microsoft\Crypto\RSA\S-1-5-18中创建的文件。

当以管理员(右键单击 - >以管理员身份运行)运行“普通”应用程序执行完全相同的代码时,会在C:\Users\[Username]\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-1154405193-2177794320-4133247715-1000创建一个文件。

使用WIX自定义操作生成的证书似乎没有私钥(“密钥集不存在”),而“普通”应用程序生成的证书确实没有。

在查看文件的权限时,它们似乎没问题,即使它们不同(工作者确实包含SYSTEM用户),即使将SYSTEM添加到( “非工作”)文件我无法读取私钥,此处也出现相同的错误。

然后我使用FindPrivateKey util查找相应的文件,但我得到的只是"Unable to obtain private key file name"

好的,这里有什么? Windows如何存储SYSTEM用户存储的证书的私钥?也许没有创建任何私钥文件?为什么呢?

1 个答案:

答案 0 :(得分:14)

我通过谷歌几乎所有的东西得到了解决方案......据我了解有一些事情要做:

  1. 生成X509Certificate2
  2. Make sure the private key container is persistent (not temporary)
  3. Make sure to have acccess rules for authenticated users,因此他们可以看到私钥
  4. 所以最终的代码是:

    X509Certificate2 nonPersistentCert = CreateACertSomehow();
    
    // this is only required since there's no constructor for X509Certificate2 that uses X509KeyStorageFlags but a password
    // so we create a tmp password, which is not reqired to be secure since it's only used in memory
    // and the private key will be included (plain) in the final cert anyway
    const string TMP_PFX_PASSWORD = "password";
    
    // create a pfx in memory ...
    byte[] nonPersistentCertPfxBytes = nonPersistentCert.Export(X509ContentType.Pfx, TMP_PFX_PASSWORD);
    
    // ... to get an X509Certificate2 object with the X509KeyStorageFlags.PersistKeySet flag set
    X509Certificate2 serverCert = new X509Certificate2(nonPersistentCertPfxBytes, TMP_PFX_PASSWORD,
        X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); // use X509KeyStorageFlags.Exportable only if you want the private key to tbe exportable
    
    // get the private key, which currently only the SYSTEM-User has access to
    RSACryptoServiceProvider systemUserOnlyReadablePrivateKey = serverCert.PrivateKey as RSACryptoServiceProvider;
    
    // create cspParameters
    CspParameters cspParameters = new CspParameters(systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.ProviderType, 
        systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.ProviderName, 
        systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.KeyContainerName)
    {
        // CspProviderFlags.UseArchivableKey means the key is exportable, if you don't want that use CspProviderFlags.UseExistingKey instead
        Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseArchivableKey,
        CryptoKeySecurity = systemUserOnlyReadablePrivateKey.CspKeyContainerInfo.CryptoKeySecurity
    };
    
    // add the access rules
    cspParameters.CryptoKeySecurity.AddAccessRule(new CryptoKeyAccessRule(new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), CryptoKeyRights.GenericRead, AccessControlType.Allow));
    
    // create a new RSACryptoServiceProvider from the cspParameters and assign that as the private key
    RSACryptoServiceProvider allUsersReadablePrivateKey = new RSACryptoServiceProvider(cspParameters);
    serverCert.PrivateKey = allUsersReadablePrivateKey;
    
    // finally place it into the cert store
    X509Store rootStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    rootStore.Open(OpenFlags.ReadWrite);
    rootStore.Add(serverCert);
    rootStore.Close();
    
    // :)