【问题标题】:How to set read permission on the private key file of X.509 certificate from .NET如何从 .NET 对 X.509 证书的私钥文件设置读取权限
【发布时间】:2009-01-08 20:07:29
【问题描述】:

这里是将 pfx 添加到 Cert 存储的代码。

X509Store store = new X509Store( StoreName.My, StoreLocation.LocalMachine );
store.Open( OpenFlags.ReadWrite );
X509Certificate2 cert = new X509Certificate2( "test.pfx", "password" );
store.Add( cert );
store.Close();

但是,我找不到设置 NetworkService 访问私钥的权限的方法。

谁能解释一下?提前致谢。

【问题讨论】:

    标签: c# .net ssl ssl-certificate


    【解决方案1】:

    这个答案来晚了,但我想把它发布给其他在这里搜索的人:

    我发现一篇 MSDN 博客文章给出了使用 CryptoKeySecurity here 的解决方案,下面是 C# 解决方案的示例:

    var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
    if (rsa != null)
    {
        // Modifying the CryptoKeySecurity of a new CspParameters and then instantiating
        // a new RSACryptoServiceProvider seems to be the trick to persist the access rule.
        // cf. http://blogs.msdn.com/b/cagatay/archive/2009/02/08/removing-acls-from-csp-key-containers.aspx
        var cspParams = new CspParameters(rsa.CspKeyContainerInfo.ProviderType, rsa.CspKeyContainerInfo.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName)
        {
            Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore,
            CryptoKeySecurity = rsa.CspKeyContainerInfo.CryptoKeySecurity
        };
    
        cspParams.CryptoKeySecurity.AddAccessRule(new CryptoKeyAccessRule(sid, CryptoKeyRights.GenericRead, AccessControlType.Allow));
    
        using (var rsa2 = new RSACryptoServiceProvider(cspParams))
        {
            // Only created to persist the rule change in the CryptoKeySecurity
        }
    }
    

    我使用 SecurityIdentifier 来识别帐户,但 NTAccount 也可以。

    【讨论】:

    • 我更喜欢这个解决方案,因为代码更少,不需要弄乱文件路径。
    • 在我的情况下,我的密钥已经存在,我想重新添加它并获得权限。这无声无息地失败了,因为C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys 中的私钥文件与证书“分离”了。解决方法是创建带有以下标志的证书:X509Certificate2 cert = new X509Certificate2(pathToCert, "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); 请参阅此answer
    • 对于某些证书,我认为那些使用 Crytography Next Gen (CNG) 提供商制作的证书,上面的代码找不到私钥。在这种情况下,这可能会有所帮助:stackoverflow.com/a/22146915
    • 在 Visual Studio 中调试时,var rsa2 = new RSACryptoServiceProvider(cspParams) 行中出现 UnauthorizedAccessException。这很奇怪,因为该进程是以管理员身份运行的(我添加了一个 app.manifest 文件以确保这一点)。
    • 我已确认从提升的控制台执行应用程序时遇到相同的异常。
    【解决方案2】:

    如果这对其他人有帮助,我在 Powershell 中写下了 Jim Flood 的答案

    function Set-PrivateKeyPermissions {
    param(
    [Parameter(Mandatory=$true)][string]$thumbprint,
    [Parameter(Mandatory=$false)][string]$account = "NT AUTHORITY\NETWORK SERVICE"
    )
    #Open Certificate store and locate certificate based on provided thumbprint
    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
    $store.Open("ReadWrite")
    $cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}
    
    #Create new CSP object based on existing certificate provider and key name
    $csp = New-Object System.Security.Cryptography.CspParameters($cert.PrivateKey.CspKeyContainerInfo.ProviderType, $cert.PrivateKey.CspKeyContainerInfo.ProviderName, $cert.PrivateKey.CspKeyContainerInfo.KeyContainerName)
    
    # Set flags and key security based on existing cert
    $csp.Flags = "UseExistingKey","UseMachineKeyStore"
    $csp.CryptoKeySecurity = $cert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
    $csp.KeyNumber = $cert.PrivateKey.CspKeyContainerInfo.KeyNumber
    
    # Create new access rule - could use parameters for permissions, but I only needed GenericRead
    $access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
    # Add access rule to CSP object
    $csp.CryptoKeySecurity.AddAccessRule($access)
    
    #Create new CryptoServiceProvider object which updates Key with CSP information created/modified above
    $rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)
    
    #Close certificate store
    $store.Close()
    
    }
    

    请注意,帐户参数也可以采用“DOMAIN\USER”的形式(不仅仅是内置名称) - 我在我的环境中对此进行了测试,它会自动将其转换为适当的 SID

    【讨论】:

    • 当我尝试这个时,它在“..CryptoKeySecurity.AddAccessRule($access)”调用中死亡。看起来 CryptoKeySecurity 属性对我来说是空的。这可能仅在私钥存储在文件系统中(而不是在注册表中)时才有效。
    • 我在加载 $cert 后添加了一个检查,以确保 .PrivateKey 属性不为空
    【解决方案3】:

    要以编程方式进行,您必须做三件事:

    1. 获取私钥文件夹的路径。

    2. 获取该文件夹中私钥的文件名。

    3. 为该文件添加权限。

    请参阅this post 了解所有这三个方面的一些示例代码(特别是查看“AddAccessToCertificate”方法)。

    【讨论】:

    • 您在使用远程桌面时是否可以使用它?当我访问它时我可以看到它,但我的程序完成后它就消失了。
    【解决方案4】:

    您可以使用作为Windows Server 2003 Resource Kit Tools 一部分提供的WinHttpCertCfg.exe tool

    示例:

    winhttpcertcfg -g -c LOCAL_MACHINE\My -s test -a NetworkService
    


    或者,您可以使用 WCF SDK 附带的Find Private Key tool 来查找证书私钥文件在磁盘上的位置。然后,您可以简单地使用 ACL 对文件设置正确的权限。

    示例:

    FindPrivateKey My LocalMachine -n "CN=test"
    

    【讨论】:

      【解决方案5】:

      根据@russ 的回答,

      此版本同时支持密钥存储提供程序和传统加密服务提供程序。

      function Set-PrivateKeyPermissions {
          param(
              [Parameter(Mandatory=$true)]
              [string]$thumbprint,
              [Parameter(Mandatory=$true)]
              [string]$account
          )
      
          #Open Certificate store and locate certificate based on provided thumbprint
          $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
          $store.Open("ReadWrite")
          $cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}
      
          if ($cert.PrivateKey -Eq $null) {
              # Probably using Key Storage Provider rather than crypto service provider
              $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
              if ($rsaCert -Eq $null) {
                  throw "Private key on certificate $($cert.Subject) not available"
              }
      
              $fileName = $rsaCert.key.UniqueName
              $path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
              $permissions = Get-Acl -Path $path
      
              $access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($account, "FullControl", "Allow")
              $permissions.AddAccessRule($access_rule)
              Set-Acl -Path $path -AclObject $permissions
          } else {
              #Create new CSP object based on existing certificate provider and key name
              $csp = New-Object System.Security.Cryptography.CspParameters($cert.PrivateKey.CspKeyContainerInfo.ProviderType, $cert.PrivateKey.CspKeyContainerInfo.ProviderName, $cert.PrivateKey.CspKeyContainerInfo.KeyContainerName)
      
              # Set flags and key security based on existing cert
              $csp.Flags = "UseExistingKey","UseMachineKeyStore"
              $csp.CryptoKeySecurity = $cert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
              $csp.KeyNumber = $cert.PrivateKey.CspKeyContainerInfo.KeyNumber
      
              # Create new access rule - could use parameters for permissions, but I only needed GenericRead
              $access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
              # Add access rule to CSP object
              $csp.CryptoKeySecurity.AddAccessRule($access)
      
              #Create new CryptoServiceProvider object which updates Key with CSP information created/modified above
              $rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)
          }
      
          #Close certificate store
          $store.Close()
      }
      

      【讨论】:

        【解决方案6】:

        如果有人感兴趣,这是我为 windows server 2008 找到的解决方案:http://technet.microsoft.com/en-us/library/ee662329.aspx

        基本上,我必须向需要使用 MMC 工具访问证书的服务授予权限。像魅力一样工作。

        【讨论】:

          【解决方案7】:

          如果CertificatePrivateKeyRSACng 类型,你可以走这条路:

          对于本地机器:

           var rsaPrivateKey = certificate.GetRSAPrivateKey();
           var privateKey = rsaPrivateKey as RSACng;
           var keyUniqueName = privateKey.Key.UniqueName;
           var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
           var keyPath = Path.Combine(folderPath, "Microsoft", "Crypto", "RSA", "MachineKeys", keyUniqueName);
           var fileInfo = new FileInfo(keyPath);
           var accessControl = fileInfo.GetAccessControl();
           accessControl.AddAccessRule(new FileSystemAccessRule(new NTAccount("<account>"), FileSystemRights.FullControl, AccessControlType.Allow));
           fileInfo.SetAccessControl(accessControl);
          

          如果是用户证书,请使用 Environment.SpecialFolder.ApplicationData 并在文件夹 roaming\microsoft\crypto\rsa 中找到 sid

          【讨论】:

          • 我试过这个。但是我找不到keyPath。我所做的是以编程方式将证书添加到商店,然后在检查证书是否存在之后立即检查该路径是否存在。你有迹象表明它可能在其他地方吗?
          • 经过大量谷歌搜索后,我找到了这个 SO 答案 stackoverflow.com/a/49924679/582061,它把我带到了这个微软页面,该页面记录了证书隐藏的所有位置 docs.microsoft.com/nb-no/windows/win32/seccng/…。正如您在最后一条评论中所说,证书可以在路径 %APPDATA%\Microsoft\Crypto\RSA\User SID 下找到。 SID 可以这样找到:using System.DirectoryServices.AccountManagement; UserPrincipal.Current?.Sid?.Value
          • 另一个注意事项:我尝试使用唯一名称并搜索文件夹,但从未成功。这可能是因为这些文件和文件夹是隐藏的。所以你必须手动去寻找文件。
          • @StianStandahl 我能够以两种方式找到它。你迭代过所有的 sid 吗?对我来说,它位于roaming。你是如何安装证书的?
          • 嗨。它们使用 X509Store 代码 api 安装在 C# 代码中。从我所做的第二条评论中,我通过在代码中添加断点并通过调试窗口查找它来找到 SID。然后我从提供的微软链接(有意义的地方)手动查看了这些地方。这样问题就解决了! :)
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-02
          • 1970-01-01
          • 2011-07-09
          • 1970-01-01
          • 2019-07-02
          • 1970-01-01
          • 2015-05-16
          相关资源
          最近更新 更多