Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SslStream not working with ephemeral keys #23749

Closed
henning-krause opened this issue Oct 5, 2017 · 41 comments
Closed

SslStream not working with ephemeral keys #23749

henning-krause opened this issue Oct 5, 2017 · 41 comments
Labels
area-System.Net.Security bug os-windows tracking-external-issue The issue is caused by external problem (e.g. OS) - nothing we can do to fix it directly
Milestone

Comments

@henning-krause
Copy link

This issue was opened as result of the discussion in issue #21761.

I have a .net core 2.0 application which hosts a service and secures it via SSL.

My key material is a a key pair where I have the certificate and the private keys in two files. So I load the cert and the keys from disk and use the X509Certificate2.CopyWithPrivateKey method to construct a certificate with a private key.

That works as it should. However, if I pass this certificate to an SslStream via SslStream.AuthenticateAsServer, I get this error:

System.ComponentModel.Win32Exception (0x80004005): No credentials are available in the security package
   at System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED scc)
   at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED secureCredential)
   at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
   at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
   at System.Net.Security.SecureChannel.GenerateToken(Byte[] input, Int32 offset, Int32 count, Byte[]& output)
   at System.Net.Security.SecureChannel.NextMessage(Byte[] incoming, Int32 offset, Int32 count)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslStream.BeginAuthenticateAsServer(X509Certificate serverCertificate, Boolean clientCertificateRequired, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation, AsyncCallback asyncCallback, Object asyncState)
   at System.Net.Security.SslStream.<>c__DisplayClass35_0.<AuthenticateAsServerAsync>b__0(AsyncCallback callback, Object state)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl(Func`3 beginMethod, Func`2 endFunction, Action`1 endAction, Object state, TaskCreationOptions creationOptions)
   at System.Threading.Tasks.TaskFactory.FromAsync(Func`3 beginMethod, Action`1 endMethod, Object state)
   at System.Net.Security.SslStream.AuthenticateAsServerAsync(X509Certificate serverCertificate, Boolean clientCertificateRequired, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation)

@bartonjs suggested that the SslStream on Windows might not support certificates with an ephemeral key, so this might be the issue here.

Everything works fine on Linux.

@davidsh
Copy link
Contributor

davidsh commented Oct 5, 2017

My key material is a a key pair where I have the certificate and the private keys in two files. So I load the cert and the keys from disk and use the X509Certificate2.CopyWithPrivateKey method to construct a certificate with a private key.

Can you please provide more details about how you are doing this? Please attach a source file showing how you load the files and create a PFX etc. and how you are calling SslStream APIs. In order to diagnose this, we need as much details as possible so that we are debugging the same APIs you are using.

@henning-krause
Copy link
Author

Sure:

	var publicCertificate = new X509Certificate2(File.ReadAllBytes("Path/to/Certificate");
	var key = DecodePrivateKey("Path/to/key"); // Using an ASN.1 Parser to decode the key
	var rsa = RSA.Create();
	rsa.ImportParameters(key.GetRsaParameters());
	cert = publicCertificate.CopyWithPrivateKey(rsa);
       
        // If I execute the follwowing two lines, everything works as expected. 
	/*var buffer = cert.Export(X509ContentType.Pfx, (string) null);
	cert = new X509Certificate2(buffer, (string) null);*/


        var sslStream = new SslStream(networkStream, false);
        await sslStream.AuthenticateAsServerAsync(cert, false, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, false);

@bartonjs
Copy link
Member

bartonjs commented Oct 5, 2017

@davidsh I think it's just

cert = new X509Certificate2("some.pfx", "somePassword", X509KeyStorageFlags.EphemeralKeySet);

Either SChannel just doesn't support keys loaded with PKCS12_NO_PERSIST_PRIVATE_KEY, or something in the interop code is assuming that the private key has a persisted/named existence.

@Drawaes
Copy link
Contributor

Drawaes commented Oct 6, 2017

Without looking at all and in my shaky memory on this. I am pretty sure the Schannel code needs to use a key out of the secure keystore in the kernel, which means it needs to be "persisted" ... this might just be a windows limitation

@Drawaes
Copy link
Contributor

Drawaes commented Oct 6, 2017

Actually, the way you made the cert with the key in memory, is it then considered "exportable" because its not in the store (even if temp there). The actual error code you are getting is the "I can't find your private key when I looked for it". I would say the issue is likely this

Schannel can't read the private key from the cert you provided for whatever reason (not in the correct format, not exportable or something not set correctly)

So it has a good cert, it looks for the private key it doesn't have in the store and fails with above error.

If you write the cert you have "made" with the private key to disk. Then test

  1. Can you import that PFX correctly into your store manually, and does it show the private key?
  2. If so can you load it from a .net program?
  3. Finally if all that works can you use it from SslStream?

Failure in any or none of those will narrow the issue to the exact problem

@bartonjs
Copy link
Member

bartonjs commented Oct 6, 2017

@Drawaes He already commented that if he exports it as a PFX and reimports it (not using EphemeralKeySet) that things work. The delta is just ephemeral private keys.

@Drawaes
Copy link
Contributor

Drawaes commented Oct 6, 2017

I was meaning load the cert, apply the delta of the keys and then save and load them again... but then I am probably missing context as it's late and I just noticed it came from another issue :)

(as a check not a solution... )

@Drawaes
Copy link
Contributor

Drawaes commented Oct 6, 2017

Anyway I would just say the issue is this

The page says it has to be in a store

https://msdn.microsoft.com/en-us/library/windows/desktop/aa379809(v=vs.85).aspx?f=255&MSPPError=-2147217396

@henning-krause
Copy link
Author

henning-krause commented Oct 13, 2017

I just tried to use an in-memory store (created by calling CertOpenStore with CERT_STORE_PROV_MEMORY as a parameter, and added the certificate.

It didn't work, though. Seems, persisted store is required.

@npnelson
Copy link

npnelson commented Feb 4, 2018

In case it helps raise the priority, I just spent over a day trying to figure out why I couldn't take a private key used for mutual TLS stored in Azure Key Vault and use it in my Azure App Services App (Windows based) to use SslStream.AuthenticateAsClientAsync()

At first, I tried var privateCert = new X509Certificate2(Convert.FromBase64String(<certstring>), (string)null, X509KeyStorageFlags.DefaultKeySet); but I realized that a Windows based Azure App Service doesn't like that because it tries to write the cert to disk, but the security context App Services runs under doesn't allow that.

So, I switched to var privateCert = new X509Certificate2(Convert.FromBase64String(<certstring>), (string)null, X509KeyStorageFlags.EphemeralKeySet); This results in a No credentials are available in the security package (when running locally or in Windows Azure App Service when calling SslStream.AuthenticateAsClientAsync())

Then, I finally stumbled here, which made me realize that EphemeralKeySet, Windows, and SSlStream.AuthenticateAsClientAsync() don't work together and it is a known issue.

I think this use case will become more common as Azure KeyVault gains in popularity due to its awesome certificate management features.

None of the workarounds I can come up with are attractive. I can't switch to Linux based Azure App Service without incurring additional costs because I have some Windows only apps running in my Azure App Services plan.

I was able to get this to work: https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/app-service/app-service-web-ssl-cert-load.md but that involves a lot of setup (as well as setup on each developer machine), leaking the private key to disk and a lot more maintenance when the certificate is renewed.

Please don't feel obligated to respond, I'm just documenting it here in case it helps triage or if it helps someone else trying the same thing.

@stephentoub
Copy link
Member

@bartonjs, is this something we can follow-up with Windows folks about?

@bartonjs
Copy link
Member

bartonjs commented Feb 4, 2018

I asked SChannel, they said it's supposed to work. But I haven't personally tried writing a native client and bisecting to find out where things go wrong.

@natemcmaster
Copy link
Contributor

I just ran into this as well in 2.1.0-rc1 trying to read a PEM encoded cert/private key from separate files (cref https://github.com/dotnet/corefx/issues/20414). SSL auth and X509Certificate2.CopyWithPrivateKey work fine on macOS/Linux, but fail on Windows. Any ideas if there are workarounds available?

natemcmaster referenced this issue in natemcmaster/dotnet-serve May 10, 2018
This won't work on Windows anyways due to dotnet/corefx#24454. Will reconsider adding this when/if dotnet/corefx#20414.
@bartonjs
Copy link
Member

@natemcmaster

using (X509Certificate2 certWithKey = certOnly.CopyWithPrivateKey(key))
{
    return new X509Certificate2(certWithKey.Export(X509ContentType.Pkcs12));
}

If you don't specify Ephemeral in the load on that one then it'll get a classic Perphemeral (persisted, with cleanup as long as there's no abnormal termination) private key.

FWIW, I'm pretty far along on #22020. A few more integration scenarios to work through on the API and then it should be in the home stretch.

@natemcmaster
Copy link
Contributor

Awesome, that works for now. Thanks @bartonjs!

@jhudsoncedaron
Copy link

Bug still exists in .NET Core 2.1.5

@karelz
Copy link
Member

karelz commented Feb 2, 2019

@jhudsoncedaron it is still open, not fixed, so that would align with that ...

@davidsh
Copy link
Contributor

davidsh commented Apr 21, 2019

I started debugging this a little bit.

The problem is not in SslStream. The problem is in the X509Certificate2 created. There is some problem when using X509KeyStorageFlags.EphemeralKeySet. The X509Certificate2.HasPrivateKey property is true. But there is something wrong with how the private key material is being stored.

SslStream internally calls AcquireCredentialsHandle with the PCCERT_CONTEXT of the X509Certificate2. But ACH returns SEC_E_NO_CREDENTIALS. After turning on SCHANNEL logging an error message can be seen in the System Event Log.

The TLS client credential's certificate does not have a private key information property attached to it. This most often occurs when a certificate is backed up incorrectly and then later restored. This message can also indicate a certificate enrollment failure.

Normally, the private key needs to be stored into a CSP and then that information is attached to the public key portion of the certificate:

After this, you have to update the CERT_KEY_PROV_INFO_PROP_ID property of the newly create PCCERT_CONTEXT using CertSetCertificateContextProperty with the parameters of the private key (container name, CSP name, key spec..).

See: https://social.msdn.microsoft.com/Forums/vstudio/en-US/9d906fe1-a5bf-4005-adb5-de8964e817b4/cryptoapi-import-private-key-in-pem-format

I found this article on StackOverflow. It implies that the private key must be stored in a particular CSP.

Afaik this error means that the SSPI SChannel package did not find the private key for the certificate or the certificate is not valid for SSL/TLS. Make sure the certificate/private key are loaded in the PROV_RSA_SCHANNEL Crypto provider (CSP), not in the Enhanced CSP.

I don't know if that is compatible with the memory based certificate store (CERT_STORE_PROV_MEMORY) implied by the use of X509KeyStorageFlags.EphemeralKeySet.

@davidsh
Copy link
Contributor

davidsh commented Apr 21, 2019

@bartonjs I think we need to get this fixed for .NET Core 3.0. Does my initial analysis help you at all in narrowing down what the problem might be? I think the fix needs to in System.Security.* somewhere in the Windows PAL layer.

@davidsh
Copy link
Contributor

davidsh commented Apr 21, 2019

I've refreshed the repro based on issue dotnet/corefx#30858:

C# Repro
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

namespace ssltest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"(Framework: {Path.GetDirectoryName(typeof(object).Assembly.Location)})");

            try
            {
                DoIt().Wait();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.WriteLine("Hit enter to exit");
            Console.ReadLine();
            return;
        }

        private async static Task DoIt()
        {
            X509Certificate2 cert = GetCert();
            var shHandler = new HttpClientHandler();

            if (cert != null)
            {
                Console.WriteLine(cert.HasPrivateKey);
                shHandler.ClientCertificates.Add(cert);

                var client = new HttpClient(shHandler);
                client.Timeout = TimeSpan.FromSeconds(10);
                var server = "https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx";
                HttpResponseMessage response = await client.GetAsync(server);
                Console.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}");

                string body = await response.Content.ReadAsStringAsync();
                Console.WriteLine(body);
            }
        }

        private static X509Certificate2 GetCert()
        {
            string fabrikam = "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";
            byte[] certBytes = Convert.FromBase64String(fabrikam);
            //X509KeyStorageFlags flags = X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.EphemeralKeySet;
            //X509KeyStorageFlags flags = 0;
            X509KeyStorageFlags flags = X509KeyStorageFlags.EphemeralKeySet;
            return new X509Certificate2(certBytes, string.Empty, flags);
        }
    }
}

@bartonjs
Copy link
Member

Does my initial analysis help you at all in narrowing down what the problem might be?

Nope, because you simply described what EphemeralKeySet does 😄.

An internal mail thread with the SChannel team said that PKCS12_NO_PERSIST_KEY (aka EphemeralKeySet) certs are supposed to work; so maybe there's just some extra flag we need to use. (But (IIRC) they said they just call CryptAcquireCertificatePrivateKey, which (IIRC) works with those certs).

@davidsh
Copy link
Contributor

davidsh commented Apr 21, 2019

Can you forward me offline the SChannel team email discussion?

Based on things I've seen discussing related things:
https://social.msdn.microsoft.com/Forums/en-US/88a60cf2-7af3-49ce-99da-d2bf3b50eba1/why-does-ncryptexportkey-return-ntenotsupported-cryptoapi-amp-crypto-ng-question

I wonder if there are restrictions on the kind of PKCS12 certificates that actually work with PKCS12_NO_PERSIST_KEY.

Perhaps we should escalate a bug to SChannel? The error in the EventLog claims there is no private key. But yet the PCCERT_CONTEXT thinks there is. Maybe there is a some restriction SChannel has regarding the kind of keys they work with.

@jhudsoncedaron
Copy link

Correct; it's a bug in SChannel. Epheremal private keys just don't work properly. There's a certain flag that you can pass that will cause the private key to be loaded into the keystore and removed on shutdown; however if your process crashes it's left in the keystore. Since the *nix platform is not affected you could theoretically resolve by linking against openssl.

@davidsh
Copy link
Contributor

davidsh commented Apr 22, 2019

Correct; it's a bug in SChannel. Epheremal private keys just don't work properly.

How do you know that?

We're working with the SCHANNEL team to narrow down the root cause. It may be a bug in SCHANNEL regardless the use of PKCS12_NO_PERSIST_KEY (maps to .NET X509KeyStorageFlags.EphemeralKeySet).

Since the *nix platform is not affected you could theoretically resolve by linking against openssl.

We currently don't plan to move away from SCHANNEL SSPI on Windows.

@jhudsoncedaron
Copy link

Correct; it's a bug in SChannel. Epheremal private keys just don't work properly.

How do you know that?

We hit this bug in 2013 and traced it right through the .NET Framework code into wincrypt.

There's some hints on the internet that you might have to set CRYPT_EXPORTABLE but I didn't have a way to test them back then.

@davidsh
Copy link
Contributor

davidsh commented Apr 23, 2019

After discussions with the SCHANNEL team, it has been confirmed that this won't work in the current versions of Windows due to SCHANNEL's cross-process architecture with LSASS.EXE. The in-memory TLS client certificate private key is not marshaled between SCHANNEL and LSASS. That is why SEC_E_NO_CREDENTIALS is returned from SCHANNEL AcquireCredentialHandle() call.

We are working with the SCHANNEL team to define a feature request for this but the timeline is uncertain.

@davidsh
Copy link
Contributor

davidsh commented Aug 8, 2019

Reference: Proposed Windows Deliverable #21554365. However, no clear timeline has been approved.

Closing this issue since the dependency is external.

@davidsh davidsh closed this as completed Aug 8, 2019
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@scegg
Copy link

scegg commented Apr 29, 2020

Still have this issue on dotnet core 3.1. Before fixing, this should be documented as a part of example or remark in CertificateRequest.Create and X509Certificate2 IMHO.

@bartonjs
Copy link
Member

@scegg The fix (when it comes) will be in the Windows OS, not in .NET. Since the only known problem with ephemeral private keys is SslStream on Windows I'm not sure that I'd expect a doc-warning on either of the APIs you mentioned... SslStream.AuthenticateAsServer(Async) would be a more likely target in my mind... but would you (did you) look there? If not, then there may not be a better place than this issue.

Though, one can hope that Windows will fix the underlying problem...

@scegg
Copy link

scegg commented Apr 29, 2020

@bartonjs FYI, I faced this problem when using a new created certificate object as client certificate used by HttpClient.

@bartonjs
Copy link
Member

@scegg I understand the scenario, but it's not actually anything to do with CertificateRequest. If the key object you gave to CertificateRequest for CreateSelfSigned (or explicitly used with CopyWithPrivateKey after calling Create) came from a persisted key (e.g. a named key from RSACng or RSACryptoServiceProvider) then SslStream would work. But if you create an in-memory-only/ephemeral key (e.g. RSA.Create()) then SslStream (on Windows) will fail with a weird error (because it actually does TLS out-of-process, and the other process can't use the in-memory-key from your process).

So the question is "where would someone look for documentation that also feels like an appropriate place for that documentation?" (e.g. it probably doesn't belong with dotnet run, even if someone would claim that's where they'd expect it).

If you found a sample that built a certificate with CertificateRequest using an ephemeral key and gave it to HttpClient or SslStream, we can certainly update that sample (if it's ours... it's not like we can police the world's blogs 😄).

@replaysMike
Copy link

replaysMike commented May 5, 2020

I too have hit this issue with using certificate based authentication in Kestrel. Switching to ephemeral keys breaks it and fails to negotiate TLS. I see that this is more of a Windows issue so I don't have much hope for a workaround any time soon. Unfortunately I have millions of file entries in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys and will have to resort to scripted deletions rather than a code based fix. Relying on calling Dispose on the certificate when loaded has proven very difficult in this scenario due to IOC complications and program terminations caused by IISExpress.

@ststeiger
Copy link

Bug still in the open with .NET 5.0 ...
Certificate is loaded like

        public static System.Security.Cryptography.X509Certificates.X509Certificate2 GetCert()
        {
            string cert = SecretManager.GetSecret<string>("ssl_cert");
            string key = SecretManager.GetSecret<string>("ssl_key");

            System.ReadOnlySpan<char> certSpan = System.MemoryExtensions.AsSpan(cert);
            System.ReadOnlySpan<char> keySpan = System.MemoryExtensions.AsSpan(key);


            System.Security.Cryptography.X509Certificates.X509Certificate2 certSslLoaded = System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromPem(certSpan, keySpan);
            return certSslLoaded;
        }

It works on LInux !
But on Windoze...


warn: Microsoft.AspNetCore.Server.Kestrel[0]
      Overriding address(es) 'https://localhost:5005'. Binding to endpoints defined in UseKestrel() instead.
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://[::]:5005
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\Stefan.Steiger\Documents\Visual Studio 2017\Projects\SelfSignedCertificateGenerator\TestApplicationHttps
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR3.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR2.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR4.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR5.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR6.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR7.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR8.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JR9.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JRA.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JRB.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
SNI Name: localhost
SNI Name: localhost
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JRC.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()
fail: Microsoft.AspNetCore.Server.Kestrel[0]
      Unhandled exception while processing 0HM4QLCD86JRD.
      System.ComponentModel.Win32Exception (0x8009030E): Im Sicherheitspaket sind keine Anmeldeinformationen verfügbar.
         at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
         at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)
         at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
         at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan`1 incomingBuffer)
         at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
         at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1.ExecuteAsync()

@henning-krause
Copy link
Author

@ststeiger This is because the error is located in Windows, not in .NET itself. So the Windows guys need to fix this.

@ststeiger
Copy link

ststeiger commented Dec 7, 2020

@henning-krause: I figured that much when it worked on Linux...
But it's been 3 years...

So to work around it:

return new System.Security.Cryptography.X509Certificates.X509Certificate2(
     thisCert.Value.Export(
        System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12
    )
);

@yetanotherchris
Copy link

yetanotherchris commented Dec 17, 2020

Minor change for @ststeiger 's work around is it's cert.Export. Looks like nobody tested loading SSL certs from PEM files on Windows 😏 Here's how I do it in .NET Core 3 (.NET 5 lets you load a single PEM file with the private key inside, so all the RSA private key code below is redundant)

public static class CertHelper
{
    // dotnet dev-certs https -ep $pwd/selfsigned.pem --format Pem -np
    public static X509Certificate2 GetCertificate()
    {
        X509Certificate2 sslCert = CreateFromPublicPrivateKey("certs/selfsigned.pem", "certs/selfsigned.key");
        
        // work around for Windows (WinApi) problems with PEMS, still in .NET 5
        return new X509Certificate2(sslCert.Export(X509ContentType.Pkcs12));
    }
    
    public static X509Certificate2 CreateFromPublicPrivateKey(string publicCert="certs/public.pem", string privateCert="certs/private.pem")
    {
        byte[] publicPemBytes = File.ReadAllBytes(publicCert);
        using var publicX509 = new X509Certificate2(publicPemBytes);
        var privateKeyText = File.ReadAllText(privateCert);
        var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
        var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);

        using RSA rsa = RSA.Create();
        if (privateKeyBlocks[0] == "BEGIN PRIVATE KEY")
        {
            rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
        }
        else if (privateKeyBlocks[0] == "BEGIN RSA PRIVATE KEY")
        {
            rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
        }
        X509Certificate2 keyPair = publicX509.CopyWithPrivateKey(rsa);
        return keyPair;
    }
}
    
public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .UseSerilog()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(options =>
            {
                options.ConfigureHttpsDefaults(adapterOptions =>
                {
                    adapterOptions.ServerCertificate = CertHelper.GetCertificate();
                });
            });

            webBuilder.UseStartup<Startup>();
        });
}

The code is also on my blog in this post, I'll add this to that post I think as it's a fairly common scenario to use PEMs instead of the archaic PFX horse that Microsoft tried flogging!

@wyatt-troia
Copy link

wyatt-troia commented Jan 8, 2021

I needed to also mark the cert as exportable in order to use cert.Export(). Bitwise combination of X509KeyStorageFlags.Exportable (4) and X509KeyStorageFlags.EphemeralKeySet (32) is 32 + 4 = 36

KeyVaultSecret secret = await secretClient.GetSecretAsync(secretName);
byte[] pfxBytes = Convert.FromBase64String(secret.Value);
var cert = new X509Certificate2(pfxBytes, String.Empty, (X509KeyStorageFlags)36);
return new X509Certificate2(cert.Export(X509ContentType.Pkcs12));

@ststeiger
Copy link

ststeiger commented Jan 10, 2021

Indeed, I stumbled over the same problem with Exportable, in the mean time.
But only if you create the certificate from pfx - from pem it works fine by default.

Am I right to assume that with X509KeyStorageFlags.EphemeralKeySet, it works without re-creating the certificate on each call ?
Interesting.

Also, I'd use X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet instead of 32 - I think it would be more human-readable. 32 might be faster though, if the compiler doesn't optimize this.

@ststeiger
Copy link

ststeiger commented Jan 10, 2021

Note that in the ServerCertificateSelector, you also need to handle the case that the website is called via internal or external IP, including IPv6, instead of SNI name.


        public static System.Security.Cryptography.X509Certificates.X509Certificate2 ServerCertificateSelector(
              System.Collections.Concurrent.ConcurrentDictionary<string, LetsEncryptData> certs
            , Microsoft.AspNetCore.Connections.ConnectionContext connectionContext
            , string name)
        {
            if (certs != null && certs.Count > 0)
            {
                if (string.IsNullOrEmpty(name))
                {
                    System.Net.IPEndPoint ipe = (System.Net.IPEndPoint)connectionContext.LocalEndPoint;
                    if (ipe.Address.IsIPv4MappedToIPv6)
                        name = ipe.Address.MapToIPv4().ToString();
                    else
                        name = ipe.Address.ToString();
                }
                
                if (certs.ContainsKey(name))
                    return certs[name].Certificate;
            
                return null;
            } // End if (certs != null && certs.Count > 0) 
            
            throw new System.IO.InvalidDataException("No certificate for name \"" + name + "\".");
        } // End Function ServerCertificateSelector 
        

@Ravenheart
Copy link

Please take into account that SmartCard based X509Certificate2 cannot be exported (the private key part) and this exporting and re-importing simply does not work as a workaround.

@ststeiger
Copy link

@Ravenheart: It works for me, as I have no SmartCard-based X509Certificate2.
Works on my machine.

@ghost ghost locked as resolved and limited conversation to collaborators Feb 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Security bug os-windows tracking-external-issue The issue is caused by external problem (e.g. OS) - nothing we can do to fix it directly
Projects
None yet
Development

No branches or pull requests