-
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
.net core TLS server certificate with private key in TPM #109243
Comments
Since you're using an engine (and not a provider) you should be able to do it with .NET 8+ using (SafeEvpPKeyHandle keyHandle = SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("tpm2tss", "0x8100002"))
using (RSA rsa = new RSAOpenSsl(keyHandle))
using (X509Certificate2 certOnly = new X509Certificate2(path))
{
return certOnly.CopyWithPrivateKey(rsa);
} If instead you need an OSSL_PROVIDER (instead of an ENGINE) then you need .NET 9+, where it would look like using (SafeEvpPKeyHandle keyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2tss", "0x8100002"))
using (RSA rsa = new RSAOpenSsl(keyHandle))
using (X509Certificate2 certOnly = X509CertificateLoader.LoadCertificateFromFile(path))
{
return certOnly.CopyWithPrivateKey(rsa);
} |
so im using .net 8 and openssl version 1.1.1 and have this error when using the engine option as listed above
and with openssl we have the enginer availalbe Is there any other config needed to use this code |
Nothing special extra should be required. The argument passed to
Is there more than one copy of OpenSSL on your system? (Specifically, libssl.so and libcrypto.so). .NET is capable of using 1.1.1, but if we find 3.0 we prefer it. So if you have a libssl.so.3/libcrypto.so.3 then .NET is almost certainly choosing that version. You can ask what version we got by querying SafeEvpPKeyHandle.OpenSslVersion and printing that in hex... or use a debugger to see what specific copy of libcrypto.so got loaded. If you have more than one, it's possible that the version we're binding to has a different ENGINE resolver path than the one you're interacting with using the commandline tool. -- The next thing I can think of is that the ENGINE might be loading successfully, but the ENGINE is incorrectly reporting "no such engine" instead of "no such key" if the key name needs to be formatted in a manner other than what I speculated. That would probably require using a native debugger and seeing that our call to ENGINE_by_id succeeded, but our call to ENGINE_load_private_key failed. We just report whatever error OpenSSL and/or the ENGINE reported, I can't tell you if it's, y'know, accurate. |
My understanding is that engines are deprecated, but not removed, from OpenSSL 3 (which wants providers, but that support isn't available in .NET 8 and will come in .NET 9). However, the TPM2TSS engine installs in engines-1.1 rather than engines-3. Any idea how to get SafeEvpPKeyHandle.OpenPrivateKeyFromEngine("tpm2tss", "0x8100002") to be looking/loading the older engine using OpenSSL 3? |
I don't know if ENGINEs built against OSSL 1.1 will successfully load in OSSL 3. OSSL 3 changed a lot of the ABI, and while the ENGINE registration and callbacks might be stable, if they call into any other OSSL functions they might have issues. So, I've never tried to get OSSL3 to load from the engines-1.1 path, and don't know how to do it. If you have both OSSL3 and OSSL1.1 and want .NET to load the older one, you can set an environment variable: |
That
That's using the |
An update on the same system: looks like the openssl utility does work with the handle from the command line. But, as shown above, that same handle can't be read by the .NET program. sudo sh |
Your commandline information suggests you got password/PIN challenged, which the .NET API can't handle. Perhaps tpm2tss wanted to report "password required" and that got lost in "loading the key didn't work"? |
There's no password on the handle. In that script above, only "Enter" was pressed. At least the routine that created the private key at that handle did not explicitly apply a password to it. |
This is the script that was run to create the key/handle that is the target we want:
|
@bartonjs is there an option to pass the empty password in the SafeEvpPKeyHandle.OpenPrivateKeyFromEngine ? I dont see that based on the documentation but based on how the openssl response is where it expects an empty password, do we need to pass that in from .net as well ? |
No, as you noted it doesn't take any options beyond the ENGINE name and the key name. To identify that this is a null-vs-empty problem, you'll probably have to write a test directly against OpenSSL. It would look something like the following (which notably never cleans up anything it allocated)... which I wrote in this text box, so likely doesn't compile, but it's a start. int main(int argc, const char** argc)
{
OPENSSL_init_ssl(
OPENSSL_INIT_ADD_ALL_CIPHERS |
OPENSSL_INIT_ADD_ALL_DIGESTS |
OPENSSL_INIT_LOAD_CONFIG |
OPENSSL_INIT_NO_ATEXIT |
OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
OPENSSL_INIT_LOAD_SSL_STRINGS,
NULL);
ENGINE* engine = ENGINE_by_id("tpm2tss");
EVP_PKEY* pkey = NULL;
if (!engine)
{
printf("Engine did not load\n");
ERR_print_errors_fp(stdout);
return 1;
}
if (!ENGINE_init(engine))
{
printf("ENGINE_init failed.\n");
ERR_print_errors_fp(stdout);
return 1;
}
pkey = ENGINE_load_private_key(engine, "0x81000003", NULL, NULL);
if (pkey)
{
printf("Key loaded the easy way, hurrah!\n");
return 0;
}
printf("Key did not load the easy way.\n");
ERR_print_errors_fp(stdout);
printf("\n\n\n");
UI_METHOD* uimeth = UI_null();
pkey = ENGINE_load_private_key(engine, "0x81000003", uimeth, NULL);
if (pkey)
{
printf("Key loaded with UI_null\n");
return 0;
}
printf("Key did not load with UI_null.\n");
ERR_print_errors_fp(stdout);
printf("\n\n\n");
uimeth = UI_openssl();
pkey = ENGINE_load_private_key(engine, "0x81000003", uimeth, NULL);
if (pkey)
{
printf("Key loaded with UI_openssl\n");
return 0;
}
printf("Key did not load with UI_openssl.\n");
ERR_print_errors_fp(stdout);
printf("\n\n\n");
// Exercise to the reader: If you make it this far, you can try
// creating a custom UI_METHOD based on
// https://github.com/openssl/openssl/blob/b372b1f76450acdfed1e2301a39810146e28b02c/apps/apps.c#L183-L275
printf("Key never loaded :(\n");
return 1;
} |
Let me see if I can reproduce this and figure out what is going on. |
Okay, I got as far as @GuyWithDogs (great handle btw)
|
The problem is that we are passing in
the The The reason it works fro the OpenSSL command line is because it uses Slightly annoyingly, it's not strictly safe to pass |
@vcsjones I'm fine with making a UI_METHOD that "just doesn't do anything" if you think that's the best path forward. Or maybe we can convince tpm2tss to not call that an invalid state? 😄 |
Thanks @vcsjones for analyzing this further. we are happy to try this if you have a fix from a build or someway for us to try as we got stuck on this problem for a while. Its very promising that you got to the root cause! |
@appcodr in order to give you a test build of the native library I would need to know more about your environment and .NET version. The full output from |
@vcsjones I will grab that info on the target machine probably tomorrow. But its on .net 8 on Ubuntu 22.x on a x86 based processor. we tried as self contained app. our desire is to run on .net 8 |
@vcsjones Here is the info from the target machine where we want to run
|
Pull request #109706 will fix this for .NET 10. If you want to try this on .NET 8, you can do the following. This should be done on the same OS (Ubuntu 22.04) as you mentioned you are using .NET from.
|
Thanks @vcsjones . I see on the issue page that there is a backport comment. Is that in .net 8.0 ?
|
I followed the each step for local build but it gives some error. Here is the build logs. ./build.sh -rc release -c release -s clr+libs.native Restore was successful. Build FAILED. CSC : error CS2012: Cannot open '/home/dfs/Desktop/dotnetbuild/runtime/artifacts/obj/coreclr/System.Private.CoreLib/x64/Release/System.Private.CoreLib.dll' for writing -- 'Access to the path '/home/dfs/Desktop/dotnetbuild/runtime/artifacts/obj/coreclr/System.Private.CoreLib/x64/Release/System.Private.CoreLib.dll' is denied.' [/home/dfs/Desktop/dotnetbuild/runtime/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj] Time Elapsed 00:05:20.41 |
@serkanturhanxl based on |
It's a dedicated Linux machine I connected via rdp. dfs is the user name and the folder I created on desktop. Should I build in some specific folder? I ran under sudo and the error changed. |
@vcsjones here is the error log file it was mentioned on the console. |
@serkanturhanxl looking at the console output behind the folder, it looks like it wants zlib. You should do I forgot that .NET 8 had dependency on zlib, but .NET 9 removed the dependency, so is not listed as a build requirement anymore in our documentation. |
Thanks @vcsjones. The build worked and we are not getting the error we got with SafeEvpPKeyHandle.OpenPrivateKeyFromEngine before. So thats great news! But we are trying to use that cert for a TLS session and we get an error that
this is on OpenSSL version: 1010100F |
@appcodr can you put together a small example application that demonstrates that behavior? That looks like a disposed certificate is trying to be used somewhere. |
Okay, I think we got past the invalid handle error as you pointed out we had the object disposed. Now the connection handshake happens but fails with a wierd openssl error. we have set the TLS version to TLS 1.2, it might be that the certificate chain needs to be placed in a trusted root (Windows makes this easier :) ) . We are making a separate sample app and will share it with you soon,
|
@vcsjones also if you have a sample program that worked on .net 10 with the TPM on Ubuntu, can you share that as well please? |
If you mean the certificate on your side of the connection, that's never required on Linux (or, really "anything but Windows")... we just do the right thing. If you mean the certificate from the other side, you can add it to your system trust, or user trust, or use API to add it as contextual trust, or just write a custom validator and accept it there.
If you mean TLS in general, I disagree 😄. If you mean TLS with hardware-based private keys, yeah, .NET on Windows has had longer to work the problems out. |
@bartonjs the certificate i was referring to was the server certificate and we are using the TPM to store the private key. and yes I meant the TLS with TPM based private keys - windows abstracts it better. I think we are close now, only issue is the handshake error we are getting and if we get past it would be good. |
I created a sample Client and Server application.
Here are the exceptions I'm getting. From Client: Client connected. Any suggestion? |
To make sure things are deterministic, instead of subjecting them to the whims of the finalizer, you should After getting the certificate back from using (RSA rsa = cert.GetRSAPrivateKey())
{
byte[] sig = rsa.SignData(Array.Empty<byte>(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Console.WriteLine($"Signature of empty data: {Convert.ToHexString(sig)}");
} If that works, but SslStream does not, you'll probably want to run WireShark or some other network capture to see if the handshake failure had interesting details that didn't manifest in the exception. |
Another thing you can do run the These are very verbose log outputs that write to These logs will show commands sent and received to the TPM. Note if you are using |
@bartonjs The signature part seems working. Still failing on the Authentication part. I added a parameter to force it to use "enabledSslProtocols:System.Security.Authentication.SslProtocols.Tls12" the error changed a bit I think.
@vcsjones I tried to get extended logs but wasn't helpful. I did check with wireshark this is the error I see there also. |
Just to double-check, did you actually see this work with
Then actually make a request to it with
For myself, I appear to have reached the limits of what my TPM can do. It fails with:
The error codes map to
That was produced with I can use cryptographic primitives, like |
Here is the results for same commands as you mentioned. Server:
Client:
|
At this point I think we have identified that the issue you are running in to isn't related to .NET. It looks like you are getting the same "alert decrypt error" from I don't think there is a way for us to proceed with any assistance here until you can at least identify a scenario in which it does work with |
Thank you. |
Could you share your reason for that request? Currently you're the only one asking for it, and we've seen that it won't solve your scenario. So this would be a change in an incremental update that seemingly no one actually wants. In general "we should fix it just because it's broken" is vNext-only. We've already fixed it for .NET 10; but we'd need a reason to do servicing. (Right now, I'd take this into the meeting and they'd ask "Is this a security thing?" ("Not really, no.") "Is this something reported by a user, or did we find it ourselves?" ("User") "Have they verified it fixes their problem?" ("Sort of. They verified it fixes a problem, but their problem remains unsolved, and may be unsolvable...")... and I don't see it going well after that 😄) |
@bartonjs @vcsjones - I think we found the root cause of the error and it translates to the cipher suite picked by OpenSSL. So now in command line we are able to successfully connect use it by this parameter
But with .net core code, does specifying the cipher option in parameters get picked when using with openssl ? Probably need your guidance on how we achieve the command line parameters in our code. We tried this but we get errors indicating encryption not supported but that might be not specifying the options right
Appreciate your inputs on this! |
@appcodr |
Thanks @vcsjones . So specifying the options this way would carry down to openssl right ? |
$ openssl ciphers -v -stdname AES128-GCM-SHA256
TLS_AES_256_GCM_SHA384 - TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 - TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256 - TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
TLS_RSA_WITH_AES_128_GCM_SHA256 - AES128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(128) Mac=AEAD This output shows that the OpenSSL configuration name of "AES128-GCM-SHA256" maps to IANA's "TLS_RSA_WITH_AES_128_GCM_SHA256"; and we use the IANA names. |
Yep. Although I'd be remiss in saying you're choosing a ciphersuite that doesn't have the Perfect Forward Secrecy (PFS) characteristic. If that works for you, I recommend trying ECDHE-RSA-AES128-GCM-SHA256 (commandline) / TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (API). |
@vcsjones @bartonjs -we are very close but still not working! So when we try the openssl s_server command with AES_128_GCM_SHA256 and tpm handle and the ca chain and run our .net client application which was working in our device it successfully connects! So that ensures our key in tpm and access to it and openssl server being able to use it as well. Now with .net core, regardless of the options we pass we are getting the alert decrypt error. This is the successful openssl command we had
And the .net core equivalent options we tried are
With the .net core, the cipher selection now matches, but still fails to decrypt after the Client Key Exchange(checked in wireshark). When we run the same with Openssl post the Client key exchange packet we correctly see the New Session Ticket. Not sure if there are any permissions specifically needed for .net core executable when running(even though we run with 777 ) for it to correctly have the openssl tpm permissions. |
@appcodr Thank you for your efforts to validate this fix against your scenario. The fixes were approved for the February 2025 servicing releases of .NET 8 and .NET 9. |
I'm trying to do an equivalent of this command in a .net core library for doing a TLS server where the private key is in TPM with a reference of 0x8100001:
openssl s_server -cert rsa.crt -key 0x8100002-keyform engine -engine tpm2tss -accept 8
im trying for this to be running in Ubuntu on .net core. In Windows this is abstracted well by the cert store with crypto provider but the same doesn't exist in Ubuntu.
Does anyone have an example of using a package that works in Ubuntu? The below is the equivalent in windows that we use to get from cert store(the cert store abstracts the TPM access)
I also tried this example(#94493) with the 0x8100002 reference but it fails as well
The text was updated successfully, but these errors were encountered: