dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.24k stars 4.73k forks source link

Is it possible to create a x509Certificate2 with the CA, client.cert and client.key? #74250

Closed ComptonAlvaro closed 2 years ago

ComptonAlvaro commented 2 years ago

am trying to run the gRPC default project that is created in visual studio. I choose the gRPC ASP template.

I am trying to use self-signed certificates to connect to the service.

When I use grpui to connect, I have to specify the CA certificate, the client certificate and the client key to connect. I need to specify the CA certificate to verify the server certificate because it is a self-signed certificate.

Then I want to connect from a WPF application where I need to create a X509Certificate2 object.

The way I know how to create is this:

var cert = X509Certificate2.CreateFromPem(paramCertificado, paramKey);

but when I try to connect, I get an error that tells that the client closed the connection. I can see this message in the log of the service, when the client try to call to a method in the service.

How I can connect from grpcui when I specify the CA certificate? I tried to connect with grpcui without specifying the CA certificate. Then I get this error: "certificate signed by unknown authority".

So I am sure that I have to create the X509Certificate2 by specifying the CA certificate, but I don't know how to create this kind of certificates using the CA certificate and the client certificates.

Thanks.

ghost commented 2 years ago

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

Issue Details
am trying to run the gRPC default project that is created in visual studio. I choose the gRPC ASP template. I am trying to use self-signed certificates to connect to the service. When I use grpui to connect, I have to specify the CA certificate, the client certificate and the client key to connect. I need to specify the CA certificate to verify the server certificate because it is a self-signed certificate. Then I want to connect from a WPF application where I need to create a X509Certificate2 object. The way I know how to create is this: `var cert = X509Certificate2.CreateFromPem(paramCertificado, paramKey);` but when I try to connect, I get an error that tells that the client closed the connection. I can see this message in the log of the service, when the client try to call to a method in the service. How I can connect from grpcui when I specify the CA certificate? I tried to connect with grpcui without specifying the CA certificate. Then I get this error: "certificate signed by unknown authority". So I am sure that I have to create the X509Certificate2 by specifying the CA certificate, but I don't know how to create this kind of certificates using the CA certificate and the client certificates. Thanks.
Author: ComptonAlvaro
Assignees: -
Labels: `area-System.Security`, `untriaged`
Milestone: -
wfurt commented 2 years ago

You can create and use SslStreamCertificateContext. It allows you to create certificate with ket and bundle intermediate certificates. Alternatively, you can add intermediate certificates to StoreName.CertificateAuthority.

Just a note that the validation may still fail with unknown/untrusted CA even if server sends it. You would need to put root (or intermediate CA) to trusted store.

ComptonAlvaro commented 2 years ago

@wfurt Well, I would like to avoid to have to put the CA in the certificate store in windows, because I guess this will make me to have to install in all the clients.

Also if I want to develop a common library for all the clients (MAUI, WPF and so), how could I put the CA in the trusted CAs in android?

If it would be possible, I would like to find a general solution, because I thought that now that MAUI can use .NET 6 I could use all its resuorces, but it doesn't seem that.

wfurt commented 2 years ago

While we try to minimize platform differences, Android is certainly special beast. The context should work for the server IMHO e.g. server should send the leaf certificate and intermediates as it should per RFC. Note that .NET client & server, verification respect AIA entries e.g. you can point at location where the parent certificate is located and .NET security framework will try to download it to complete the chain.

But back to the question above: X509Certificate2 represents exactly one certificate and there is no way to embed more to it. If you need to manipulate trust, you will need to find different way.

bartonjs commented 2 years ago

I am trying to use self-signed certificates ... I have to specify the CA certificate,

Self-signed and CA are mutually exclusive, unless you mean that you've made your own root certificate (all roots are special self-signed certificates) and you're trying to use SslStream with this private CA.

In that case, the best you can do is with SslStreamCertificateContext.Create(serverCert, collectionOfTheRestOfTheChain) to help give SslStream the data it needs to send the certificate chain over the wire. However, that won't send your self-signed root, because root certificates aren't supposed to be transmitted in a TLS handshake (if the other party trusts the root they'd already know what the root was, and if they don't know what it is they wouldn't trust it).

If the other party is also a .NET application and you haven't already trusted your private root on those system you can register for the remote certificate validation callback and do your own logic to determine that the chain is trustworthy; but if the other party is out of your control then your best bet is to use publicly trusted certificates (such as Let's Encrypt for a TLS server certificate; for a client cert you'll need to do something that the server accepts).

ComptonAlvaro commented 2 years ago

@bartonjs Yes, when I told CA I mean I created my own root certificate with which I created and signed the certificates of the server and the client.

So I was searching some way to can use the CA to verify that the public certificate of the server is signed but the same CA than the public certificate of the client. So I could considerate a valid server certificate. I guess it is the same that grpcui does when I have to indicate the root certificate and the client certificate.

In my case, all clients are .NET, so I could try to implement the validation callback with ServerCertificateCustomValidationCallback. But I have too see how to do, because I get the public certificate of the server, but I don't get the root certificate. So I can't compare if they are signed but the same CA. But I could include the public certificate of the server in the clients and compare the hash string of both, but I don't know if this ensure that they are the same certificates and are signed but the same root.

wfurt commented 2 years ago

closing as answered

bartonjs commented 2 years ago
static bool CertificateValidationCallback(
    object sender,
    X509Certificate? certificate,
    X509Chain? chain,
    SslPolicyErrors sslPolicyErrors)
{
    sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;

    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
    chain.ChainPolicy.CustomTrustStore.Add(s_privateRoot);

    // Any other chain policy changes you want here.

    if (!chain.Build((X509Certificate2)certificate))
    {
        sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
    }

    // Do not accept the cert if anything is still wrong with it.
    return sslPolicyErrors == 0;
}