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

NET 6.0 - mutual TLS with private key stored in TPM2 #94493

Closed
adadurov opened this issue Nov 7, 2023 · 7 comments
Closed

NET 6.0 - mutual TLS with private key stored in TPM2 #94493

adadurov opened this issue Nov 7, 2023 · 7 comments

Comments

@adadurov
Copy link

adadurov commented Nov 7, 2023

First of all, I apologize if asking for guidance here is not the right thing to do.

I am presented with a business/security requirement to use mTLS (with Private Key stored in TPM2 and the public part stored in a file) as a way to authenticate connections from my NET 6.0 app running on Debian as a service (a management agent) to the management server. I am aware of the latest-and-greatest .NET 8.0 SafeEvpKeyHandle.OpenPrivateKeyFromEnigne, however I am currently locked to .NET 6.0 due to corporate policy to delay migration to the new LTS version.

My research:

  • Learned about X509Certificate2.CopyWithPrivateKey(evp), which looks like the way to go
  • Read a lot of issues on GitHub and StackOverflow concerning TPM2 and HSM for X509Certificate.
  • Got a bit confused by this SO question mentioning that one needs a certificate produced by X509Store for SslStream to work. This looks like a risk to me, 'cause most issues I saw on GitHub and SO only go as far as signing the data using a certificate with PK stored in HSM/TPM, but don't refer to TLS/SslStream.
  • Tried to learn about OpenSSL 3.0 Engines and Providers

What I tried:

  • Use Pkcs11Interop package over libpkcs11 to access PK token and its methods (with some success)

Next steps (assumed):

  • Somehow create SafeEvpKeyHandle from OpenSSL handle of the PK and perform a test with HttpClient.

Before I continue this effort, I'd be most greateful for some guidance as to whether my goal (mTLS/SslStream via HttpClient using a client certificate with Private Key stored on TPM2.0) is actually achievable on .NET 6 or .NET 8? If yes, which direction could be the most promising in this situation? P/Invoke into libopenssl to get the key handle? Anything else?

If anyone could share a reference to an external learning resource on the topic, that would be awesome!

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Nov 7, 2023
@ghost
Copy link

ghost commented Nov 7, 2023

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

First of all, I apologize if asking for guidance here is not the right thing to do.

I am presented with a business/security requirement to use mTLS with (Private Key stored in TPM2 and the public part stored in a file) as a way to authenticate connections from my NET 6.0 app running on Debian as a service (a management agent) to the management server. I am aware of the latest-and-greatest .NET 8.0 SafeEvpKeyHandle.OpenPrivateKeyFromEnigne, however I am currently locked to .NET 6.0 due to corporate policy to delay migration to the new LTS version.

My research:

  • Learned about X509Certificate2.CopyWithPrivateKey(evp), which looks like the way to go
  • Read a lot of issues on GitHub and StackOverflow concerning TPM2 and HSM for X509Certificate.
  • Got a bit confused by this SO question mentioning that one needs a certificate produced by X509Store for SslStream to work. This looks like a risk to me, 'cause most issues I saw on GitHub and SO only go as far as signing the data using a certificate with PK stored in HSM/TPM, but don't refer to TLS/SslStream.
  • Tried to learn about OpenSSL 3.0 Engines and Providers

What I tried:

  • Use Pkcs11Interop package over libpkcs11 to access PK token and its methods (with some success)

Next steps (assumed):

  • Somehow create SafeEvpKeyHandle from OpenSSL handle of the PK and perform a test with HttpClient.

Before I continue this effort, I'd be most greateful for some guidance as to whether my goal (mTLS/SslStream via HttpClient using a client certificate with Private Key stored on TPM2.0) is actually achievable on .NET 6 or .NET 8? If yes, which direction could be the most promising in this situation? P/Invoke into libopenssl to get the key handle? Anything else?

If anyone could share a reference to an external learning resource on the topic, that would be awesome!

Author: adadurov
Assignees: -
Labels:

area-System.Security

Milestone: -

@adadurov adadurov changed the title NET 6.0 - mutual TLS with private key stored in TPM2 (as a client certificate) NET 6.0 - mutual TLS with private key stored in TPM2 Nov 8, 2023
@bartonjs
Copy link
Member

bartonjs commented Nov 8, 2023

Got a bit confused by this SO question mentioning that one needs a certificate produced by X509Store for SslStream to work.

The answer is somewhere between outdated, Windows-biased, and wrong. CopyWithPrivateKey makes this workable on both Windows and Linux.

Before I continue this effort, I'd be most greateful for some guidance as to whether my goal is actually achievable on .NET 6 or .NET 8?

It is. It has probably worked since .NET Core 2.0 (when CopyWithPrivateKey was introduced); and I know of people having gotten it to work on either Core 2.0 or Core 3.1 (but not with public source code to my recollection). So it's definitely achievable. In .NET 8, if there's an ENGINE provider for TPM keys it should be fairly easy since there's now platform API to help with the boilerplate.

Next steps (assumed): Somehow create SafeEvpKeyHandle from OpenSSL handle of the PK

Yeah. Basically you need to do

private static X509Certificate2 GetClientCert(string path, ???)
{
    using SafeEvpPKeyHandle keyHandle = ???;
    using RSAOpenSsl rsa = new RSAOpenSsl(keyHandle);
    using X509Certificate2 certOnly = new X509Certificate2(path);

    return certOnly.CopyWithPrivateKey(rsa);
}

And the returned value from that can be used with SslStream (provided it's a proper client (or server) certificate (as appropriate), of course). ???, of course, being "some amount of code I don't know"

@adadurov
Copy link
Author

adadurov commented Nov 8, 2023

@bartonjs , thanks a ton for responding so fast!
I'm now getting to my PoC, full of hopes.
:-)

@adadurov
Copy link
Author

adadurov commented Nov 8, 2023

@bartonjs , thanks again!

I made it work on a Debian, based on your directions. In production I guess I have to be careful about the certificate's and its supporting objects' lifecycle, disposing them at the proper times.
Here is the gist of my PoC code -- https://gist.github.com/adadurov/292818c4da0301f16f5922820da410d0

Closing the issue.

@adadurov adadurov closed this as completed Nov 8, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Nov 8, 2023
@Soren-Knudsen
Copy link

@adadurov: I am encountering a similar issue where the key is stored in an Azure KeyVault HSM and marked as non-exportable. Could you guide me towards implementing a similar solution? Essentially, I have the public part of the certificate stored in a database and the private key in the HSM. My goal is to perform client certificate authentication using HttpClient.

@adadurov
Copy link
Author

adadurov commented Dec 3, 2023

@Soren-Knudsen , did you check the gist https://gist.github.com/adadurov/292818c4da0301f16f5922820da410d0 ? If that doesn't work for you, what is the error you are facing?

@Soren-Knudsen
Copy link

Thanks for replaying @adadurov: After some more research, I troubled on the https://github.com/microsoft/AzureKeyVaultManagedHSMEngine repository, which seems to enable me to offload all the cryptographic operations to the key vault.

@github-actions github-actions bot locked and limited conversation to collaborators Jan 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants