-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Comments
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. |
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); |
@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. |
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 |
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
Failure in any or none of those will narrow the issue to the exact problem |
@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. |
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... ) |
Anyway I would just say the issue is this The page says it has to be in a store |
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. |
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 At first, I tried So, I switched to Then, I finally stumbled here, which made me realize that EphemeralKeySet, Windows, and 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. |
@bartonjs, is this something we can follow-up with Windows folks about? |
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. |
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? |
This won't work on Windows anyways due to dotnet/corefx#24454. Will reconsider adding this when/if dotnet/corefx#20414.
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. |
Awesome, that works for now. Thanks @bartonjs! |
Bug still exists in .NET Core 2.1.5 |
@jhudsoncedaron it is still open, not fixed, so that would align with that ... |
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 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.
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:
I found this article on StackOverflow. It implies that the private key must be stored in a particular 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. |
@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. |
I've refreshed the repro based on issue dotnet/corefx#30858: C# Reprousing 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);
}
}
} |
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). |
Can you forward me offline the SChannel team email discussion? Based on things I've seen discussing related things: 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. |
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. |
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).
We currently don't plan to move away from SCHANNEL SSPI on Windows. |
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. |
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. |
Reference: Proposed Windows Deliverable #21554365. However, no clear timeline has been approved. Closing this issue since the dependency is external. |
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. |
@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... |
@bartonjs FYI, I faced this problem when using a new created certificate object as client certificate used by HttpClient. |
@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 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 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 😄). |
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 |
…elf-signed certs cref dotnet/runtime#23749
Bug still in the open with .NET 5.0 ...
It works on LInux !
|
@ststeiger This is because the error is located in Windows, not in .NET itself. So the Windows guys need to fix this. |
@henning-krause: I figured that much when it worked on Linux... So to work around it:
|
Minor change for @ststeiger 's work around is it's
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! |
I needed to also mark the cert as exportable in order to use
|
Indeed, I stumbled over the same problem with Exportable, in the mean time. Am I right to assume that with X509KeyStorageFlags.EphemeralKeySet, it works without re-creating the certificate on each call ? 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. |
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.
|
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. |
@Ravenheart: It works for me, as I have no SmartCard-based X509Certificate2. |
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:
@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.
The text was updated successfully, but these errors were encountered: