.NET Core X509Certificate2 的使用(在 Windows/IIS、Docker、Linux 下)

17

我一直在尝试在.NET Core API中使用证书。

基本上,我需要在运行于IIS和Docker的.NET Core Web API中使用它们。

我需要使用的证书是:

Microsoft.AspNetCore.DataProtection

public void ConfigureServices(IServiceCollection services)
{
  services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeystorePath))
    .ProtectKeysWithCertificate
    (
      new X509Certificate2(dataProtectionCertificatePath, dataProtectionCertificateSecret)
    );
}

Kestrel选项设置SSL证书

public static IWebHost BuildWebHost(string[] args) =>
  WebHost.CreateDefaultBuilder(args)
    .UseKestrel
    (
      options =>
        {
          options.Listen(address, port, listenOptions =>
            {
              listenOptions.UseHttps(new X509Certificate2(sslCertificatePath, sslCertifciateSecret));
            }
          );
        }
      )
      // other setup
     ;

IdentityServer4.SigningCredentials

注意:此代码在VS2017起的开发机上运行正常,但在Windows 2008 R2 IIS测试服务器上会抛出这些异常。
services.AddIdentityServer()
  .AddSigningCredential
  (
    new X509Certificate2(tokenCertificatePath, tokenCertificatePassphrase)
  )
  // other setup
  ;

所有这三种方法都需要放置证书文件,使用构造函数加载它,传递密钥,然后开始执行。
讽刺地是,这太容易了。
因此,我创建了一个包含证书的certs子目录。添加了密钥的设置。验证所有值是否按预期加载/创建。验证文件是否位于预期位置并因此存在。简而言之,类似以下内容:
string dataProtectionKeystorePath = System.Path.Combine(Environment.ContentRootPath, "keystore");
string dataProtectionCertificatePath = System.Path.Combine(Environment.ContentRootPath, "certs", "keystore.pfx");
string dataProtectionSecret = Configuration.GetSection("CertificateSecrets").GetValue<string>("keystoreSecret", null);

string tokenCertificatePath = System.Path.Combine(Environment.ContentRootPath, "certs", "token.pfx");
string tokenCertificateSecret = Configuration.GetSection("CertificateSecrets").GetValue<string>("tokenSecret", null);

string sslCertificatePath = System.Path.Combine(Environment.ContentRootPath, "certs", "ssl.pfx");
string sslCertificateSecret = Configuration.GetSection("CertificateSecrets").GetValue<string>("tokenSecret", null);

他们说让我们玩得开心。所以让我们去部署。

我最后得到的是这些异常:

数据保护异常:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
      Creating key {39560783-e349-475e-8e3f-748abb8c6c8b} with creation date 2018-11-16 08:01:49Z, activation date 2018-11-16 08:01:49Z, and expiration date 2019-02-14 08:01:49Z.
info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
      Writing data to file '[intentionally removed for post]'.
fail: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[24]
      An exception occurred while processing the key element '<key id="39560783-e349-475e-8e3f-748abb8c6c8b" version="1" />'.
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Keyset does not exist
   at Internal.NativeCrypto.CapiHelper.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeProvHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeKeyHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 keySize, CspParameters parameters, Boolean useDefaultKeySize)
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParameters parameters)
   at Internal.Cryptography.Pal.CertificatePal.<>c.<GetRSAPrivateKey>b__61_0(CspParameters csp)
   at Internal.Cryptography.Pal.CertificatePal.GetPrivateKey[T](Func`2 createCsp, Func`2 createCng)
   at Internal.Cryptography.Pal.CertificatePal.GetRSAPrivateKey()
   at Internal.Cryptography.Pal.CertificateExtensionsCommon.GetPrivateKey[T](X509Certificate2 certificate, Predicate`1 matchesConstraints)
   at System.Security.Cryptography.X509Certificates.RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2 certificate)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.DecryptEncryptedKey(EncryptedKey encryptedKey)
   at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
   at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[12]
      Key {39560783-e349-475e-8e3f-748abb8c6c8b} is ineligible to be the default key because its CreateEncryptor method failed.
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Keyset does not exist
   at Internal.NativeCrypto.CapiHelper.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeProvHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeKeyHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 keySize, CspParameters parameters, Boolean useDefaultKeySize)
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParameters parameters)
   at Internal.Cryptography.Pal.CertificatePal.<>c.<GetRSAPrivateKey>b__61_0(CspParameters csp)
   at Internal.Cryptography.Pal.CertificatePal.GetPrivateKey[T](Func`2 createCsp, Func`2 createCng)
   at Internal.Cryptography.Pal.CertificatePal.GetRSAPrivateKey()
   at Internal.Cryptography.Pal.CertificateExtensionsCommon.GetPrivateKey[T](X509Certificate2 certificate, Predicate`1 matchesConstraints)
   at System.Security.Cryptography.X509Certificates.RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2 certificate)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.DecryptEncryptedKey(EncryptedKey encryptedKey)
   at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
   at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.DeferredKey.<>c__DisplayClass1_0.<GetLazyDescriptorDelegate>b__0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.get_Descriptor()
   at Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(IKey key)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.CreateEncryptor()
   at Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver.CanCreateAuthenticatedEncryptor(IKey key)
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[12]
      Key {39560783-e349-475e-8e3f-748abb8c6c8b} is ineligible to be the default key because its CreateEncryptor method failed.
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Keyset does not exist
   at Internal.NativeCrypto.CapiHelper.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeProvHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider.get_SafeKeyHandle()
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 keySize, CspParameters parameters, Boolean useDefaultKeySize)
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParameters parameters)
   at Internal.Cryptography.Pal.CertificatePal.<>c.<GetRSAPrivateKey>b__61_0(CspParameters csp)
   at Internal.Cryptography.Pal.CertificatePal.GetPrivateKey[T](Func`2 createCsp, Func`2 createCng)
   at Internal.Cryptography.Pal.CertificatePal.GetRSAPrivateKey()
   at Internal.Cryptography.Pal.CertificateExtensionsCommon.GetPrivateKey[T](X509Certificate2 certificate, Predicate`1 matchesConstraints)
   at System.Security.Cryptography.X509Certificates.RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2 certificate)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.GetKeyFromCert(EncryptedKey encryptedKey, KeyInfoX509Data keyInfo)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.EncryptedXmlWithCertificateKeys.DecryptEncryptedKey(EncryptedKey encryptedKey)
   at System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, String symmetricAlgorithmUri)
   at System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument()
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement)
   at Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.DeferredKey.<>c__DisplayClass1_0.<GetLazyDescriptorDelegate>b__0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
   at System.Lazy`1.CreateValue()
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.get_Descriptor()
   at Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(IKey key)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.CreateEncryptor()
   at Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver.CanCreateAuthenticatedEncryptor(IKey key)

Identity Server 4签名凭据的异常

Application startup exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: An internal error occurred
   at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
   at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at AuthServer.Startup.ConfigureServices(IServiceCollection services) in [intentionally removed for post]\Startup.cs:line 136
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
crit: Microsoft.AspNetCore.Hosting.Internal.WebHost[6]
      Application startup exception
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: An internal error occurred
   at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
   at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at AuthServer.Startup.ConfigureServices(IServiceCollection services) in [intentionally removed for post]\Startup.cs:line 136
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()

SSL证书异常

Unhandled Exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: An internal error occurred
   at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
   at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at AuthServer.Program.<>c.<BuildWebHost>b__1_3(ListenOptions listenOptions) in [intentionally removed for post]\Program.cs:line 58
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.Listen(IPEndPoint endPoint, Action`1 configure)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.Listen(IPAddress address, Int32 port, Action`1 configure)
   at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options)
   at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
   at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0.<Get>b__0()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
   at System.Lazy`1.CreateValue()
   at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
   at Microsoft.Extensions.Options.OptionsManager`1.Get(String name)
   at Microsoft.Extensions.Options.OptionsManager`1.get_Value()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.CreateServiceContext(IOptions`1 options, ILoggerFactory loggerFactory)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer..ctor(IOptions`1 options, ITransportFactory transportFactory, ILoggerFactory loggerFactory)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureServer()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.StartAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token, String shutdownMessage)
   at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token)
   at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
   at MyProject.Program.Main(String[] args) in [intentionally removed for post]\Program.cs:line 47

面对这个证书异常已经有几个月了,我搜索了很多并且尝试了各种方法(根据搜索结果、在Github上提出的问题等),但我想到我可能错过了一些重要的东西。
我尝试使用StorageFlags,但没有找到一个有效的组合。
该项目应该在Windows(IIS)、docker和可能的Linux(Ubuntu、Debian)上运行。这就是为什么我们决定将证书放在一个(跨多个实例的)子目录中的原因。所以我发现那些建议将证书安装到微软证书存储中的帖子是不行的,怎么在Docker和Linux上行得通呢?
由于我倾向于使用的所有证书都受到影响,我认为问题可能是我对如何使用证书有一个重大的误解。
谁能帮助我找出我错过的主要点并最终使证书运行?我需要配置什么吗?如果需要,应该配置什么?

调查摘要:

我确定这些文件存在:是的,否则会出现异常{{exception}}。

System.Security.Cryptography.CryptographicException: System cannot find specified file.

我确定密码是正确的:是的,否则就会出现异常{{exception}}。

Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The specified network password is not correct.

文件权限:感谢CheshireCat的提醒。在Windows Server 2008 R2 IIS 7.5上,我检查了证书子目录的文件权限,针对用户DefaultAppPool(直到现在才意识到应该是不同的用户)。用户DefaultAppPool被授予了certs子目录的完全访问文件权限如此链接所示
通常,IS4签名凭据异常发生在应用程序启动时。使用X509KeyStorageFlags.MachineKeySet不会在启动时抛出异常,但会显示登录窗口,并在登录后抛出异常。没有令牌返回,但会创建一个会话,以便用户可以修改ASP NET Identity帐户设置。
证书过期:证书可能会过期。数据保护似乎不会检查过期日期,因此即使证书过期,也可以继续使用。Identity Server 4签名凭据仍然可以使用,但如果证书过期,ASP.NET Core令牌验证将引发未经授权的异常。

问题解决方案

经过一年半的时间,最终的解决方案是:使用有点瑕疵的(自签名/自创建的)证书。

可以使用以下代码检查证书是否有效:

  static void Main(string[] args)
  {
    X509Certificate2 cert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "cert.pfx"), "password");
    Console.WriteLine("cert private key: " + cert.PrivateKey);
  }

如果您看到以下输出,则证书是好的,否则您会像上面描述的那样得到异常。
cert private key: System.Security.Cryptography.RSACng

创建开发证书

使用dotnet工具dev-certs。此调用将打开提示,然后将本地主机SSL证书导入到CurrentUser证书存储中(可通过MMC查看)。程序中不需要进行任何代码更改。

dotnet dev-certs https --trust

“损坏”的证书是使用makecert创建的。我转换到了OpenSSL,但也可以使用New-SelfSignedCertificate工具(如果您使用Win 10)。
如果正确安装并将OpenSSL添加到PATH变量中,则以下批处理文件将创建有效的证书(需要用户交互!)。
  openssl genrsa 2048 > private.pem
  openssl req -x509 -days 365 -new -key private.pem -out public.pem
  openssl pkcs12 -export -in public.pem -inkey private.pem -out cert.pfx
  REM openssl pkcs12 -info -in cert.pfx
  certutil -dump cert.pfx
  PAUSE

本地开发设置(Win 7,VS 2017,IIS Express/Kestrel)

如同 Cheshire-Cat 所描述的那样(再次感谢),所有部分的代码在 .NET Core 2.1 下均按预期工作,包括数据保护和 Identity Server 4 的签名凭据。


部署到Windows 2008 R2上的IIS 7.5

继续挑战。将证书在本地开发机器上运行并部署到IIS时会出现“新”的异常。

  • 检查了IIS上的文件权限(DefaultAppPool具有完全权限)
  • 使用的证书与在本地机器上工作的证书相同
  • 未安装任何使用的证书(不在开发机器上,也不在IIS机器上),因此应该使用实际的文件。

日志显示基本上是良好的设置。

  using data protection keystore path C:\inetpub\wwwroot\auth\keystore
  data protection keystore is protected with certificate at path 'C:\inetpub\wwwroot\auth\certs\keystore.pfx'
  loading keystore certificate from file 'C:\inetpub\wwwroot\auth\certs\keystore.pfx'
  loading keystore certificate with passphrase ********


  Application startup exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: An internal error occurred
     at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
     at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
     at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
     at [SomeOfMyNamespaces].X509Certificate2Loader.LoadFromFile(String certName, String certPath, String certPassphrase, ILogger logger) in [intentionally-blanked]\X509Certificate2Loader.cs:line 57
     at [SomeOfMyNamespaces].DataProtectionExtensions.AddDataProtection(IServiceCollection services, IConfiguration config, IHostingEnvironment environment, String applicationName, String applicationVersion, ILogger logger) in [intentionally-blanked]\DataProtectionExtensions.cs:line 207
     at [SomeOfMyNamespaces].Startup.ConfigureServices(IServiceCollection services) in [intentionally-blanked]\Startup.cs:line 96
  --- End of stack trace from previous location where exception was thrown ---
     at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
  --- End of stack trace from previous location where exception was thrown ---
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
  crit: Microsoft.AspNetCore.Hosting.Internal.WebHost[6]
        Application startup exception
  Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: An internal error occurred
     at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
     at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
     at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
     at [SomeOfMyNamespaces].X509Certificate2Loader.LoadFromFile(String certName, String certPath, String certPassphrase, ILogger logger) in [intentionally-blanked]\X509Certificate2Loader.cs:line 57
     at [SomeOfMyNamespaces].DataProtectionExtensions.AddDataProtection(IServiceCollection services, IConfiguration config, IHostingEnvironment environment, String applicationName, String applicationVersion, ILogger logger) in [intentionally-blanked]\DataProtectionExtensions.cs:line 207
     at [SomeOfMyNamespaces].Startup.ConfigureServices(IServiceCollection services) in [intentionally-blanked]\Startup.cs:line 96
  --- End of stack trace from previous location where exception was thrown ---
     at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
  --- End of stack trace from previous location where exception was thrown ---
     at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()

参考的第57行是:

  cert = new X509Certificate2(certPath, certPassphrase);

对于Identity Server 4签名凭据,同样会出现异常情况。

为了帮助我在Windows Server 2008 R2上运行此程序,我再次提供100美元的赏金。

适用于IIS、Docker(以及本地开发)的解决方案:使用此方法,既不会导致IIS也不会导致Docker设置出现问题:

cert = new X509Certificate2(certPath, certPassphrase, X509KeyStorageFlags.MachineKeySet);

请记住,如果您正在运行多个实例,则必须将密钥存储在同一位置,以便所有实例都可以访问密钥。 - Linda Lawton - DaImTo
我也尝试过。但是一直出现不安全的错误,所以无法使用它们。我自己尝试了几个月,最终放弃并使用Redis进行数据保护。 - Linda Lawton - DaImTo
我在研究中发现的一些结果表明答案是“这个问题已经被问了很多次,所以我们不回答了”。看起来比预期的更困难。此外,似乎有一个错误将在.NET Core 2.2中得到修复,这可能会影响我的/我们的数据保护问题。 - monty
你认为这可能会导致所有3个问题吗? - monty
什么?他们答应我可以同时开始3个赏金任务....想要为每个设置50美元的赏金...但是无法开始其他两个赏金任务.... - monty
显示剩余7条评论
1个回答

15

我在我的IS4项目中实际运行的代码是这样的:

X509Certificate2 certificate = null;

// Load certificate from Certificate Store using the configured Thumbprint
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
    store.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, appConfiguration.CertificateThumbprint, false);

    if (certificates.Count > 0)
        certificate = certificates[0];
}

// Fallback to load certificate from local file
if (certificate == null)
{
    string path = Path.Combine("C:\\Certificates", appConfiguration.CertificateFilename);

    try
    {
        certificate = new X509Certificate2(path, "CertificateSecret123$");
        logger.LogInformation($"Found from file {certificate.Thumbprint}");
    }
    catch (Exception ex)
    {
        logger.LogError(ex, $"Certificate file error {path}");
        certificate = null;
    }
}

if (certificate == null)
    throw new Exception($"Certificate {appConfiguration.CertificateThumbprint} not found.");

builder.AddSigningCredential(certificate);

如果我更改指纹以强制代码从本地文件查找证书,我将获得预期的结果:

Result

更新2018/11/19

由于在找不到证书文件的情况下,X509Certificate2类构造函数会抛出异常,因此我纠正了代码中的一个bug。因此,实际上,在certificate = new X509Certificate2...之后的先前的if(certificate == null)控制将永远不会被执行。

使代码正常工作的步骤如下:

  1. 创建证书并将其放入目录中
  2. 检查运行代码的用户所在文件夹的目录权限。
    • 如果您在开发计算机上,请检查运行VS的用户是否具有访问文件所在位置的权限。
    • 如果您在IIS上,请检查托管应用程序的AppPool和它所执行的用户。它需要在证书目录上拥有权限。
  3. 使用示例代码从文件中加载证书。确保路径的正确性。
  4. 使用指纹从存储库加载证书。要获取证书指纹,您可以:
    • 使用PowerShell命令Get-ChildItem -path cert:\LocalMachine\My,指定您已在存储库中注册证书的正确位置。
    • 使用Windows资源管理器,在.pfx文件上右键单击->打开->在MMC控制台中定位您的文件->双击它->详细信息->Thumbprint。 重要提示:当从预览文本框中复制/粘贴Thumbprint值时,Windows中存在一个众所周知的丑陋错误。当您粘贴该值时,在指纹开头添加了一个隐藏字符。因此,请先将其复制/粘贴到文本编辑器中,然后通过箭头键靠近第一个字符进行检查。还请参见这里

说实话,我不知道那个在Docker上能否工作。如果你说它可以工作,那就是一个选项。 - monty
1
@monty 请注意文件权限!运行您的代码的用户,本例中我猜测是 ApplicationPoolIdentity(如果您没有更改它),需要访问证书路径。进行检查。 - Cheshire Cat
@CheshireCat “有趣”的事情是,使用我在问题中发布的代码,签名凭据在我的开发机器上可以正常工作,但在IIS上会抛出异常。我尝试使用默认应用程序池用户设置的完全访问权限,但仍然会出现相同的内部错误。关于如何设置日志,请参考此链接:https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#securing-resources - monty
1
但是文件权限提示很好。我一直以为会是不同的用户。 - monty
我如何确定证书的Thumbprint? - monty
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接