Renopp
Renopp

Reputation: 21

Custom Certificate Store with generated X509Certificate2 in gRPC

I am attempting to create a gRPC server and client using ssl (with .NET 5 and VS2019). I want to use a generated X509Certificate2 as a root certificate to generate other client certificates. For that, I wrote a helper class CertificateUtil, following these threads: How can I create a self-signed certificate using C#? Generate and Sign Certificate Request using pure .net Framework.

Next, the root certificate should be registered as a custom trust store in the startup settings of the gRPC server, and the client should connect using the generated client certificate.

I have the following question:

Is it possible to register a custom trust store in gRPC?

Currently, I am getting the following errors: client: "Error starting gRPC call. HttpRequestException: The SSL connection could not be established, see inner exception. IOException: Received an unexpected EOF or 0 bytes from the transport stream." server: "The local security authority (LSA) is unreachable"

Steps to reproduce:

  1. Pull the following MWE: https://github.com/Renopph/GrpcServerClient
  2. Uncomment lines 10 and 11 in GprcCert/Program.cs and run. This should create two certificate files, GrpcServer.pfx and GrpcClient.pfx. Set both files' properties to Copy always. Do NOT register these certificates in your system's trust store.
  3. Place GrpcClient.pfx in the root of the GrpcClient project.
  4. Comment out lines 10 and 11, and uncomment line 12 in GprcCert/Program.cs.
  5. Right click the Solution, open Properties. Select "Multiple startup projects" and set both GrpcCertand GrpcClient to "Start". Then run the solution (should run GrpcCert first, then GrpcClient).
  6. The client and server both show the aforementioned errors.

I also tried leaving out the KestrelServerOptions in the Startup.cs of the server. This allowed any client to connect, even without the certificate.

Upvotes: 2

Views: 1094

Answers (1)

dan-kli
dan-kli

Reputation: 883

I will write up this answer, but as I already said I think it only answers half your questions. Regarding your question Is it possible to register a custom trust store in gRPC? I think the answer is yes, as long as you fulfill the TLS requirements of gRPC and the underlying certificate structure works, it should be possible. According to the MS Documentation for certificate authentication, the certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, therefore the Kestrel (which hosts the gRPC services) does not care if the (root) certificate comes from a custom trust store or the local machine store (or somewhere else).

It took me a while to get both my self-signed certs in the local machine store and my company's certs in the Trusted Root Certification Authorities Certificate Store to work with gRPC, therefore it's maybe easier to get your certificates first to a point where they just work with the root cert getting fetched from the local machine store, and then move it to a custom store.


This is the service I used to inject the certs on the client- and serverside (in .NET 6), I think you could extend it easily to fetch a cert from any other location (like a custom certificate store):

using System.Security.Cryptography.X509Certificates;

namespace Shared.Certificates
{
    public class CertificateService : ICertificateService
    {
        public X509Certificate2 GetCertificateFromLocalMachineStore(string friendlyName)
        {
            var store = GetLocalMachineCertificates();
            X509Certificate2 certificate = null;
            foreach (var cert in store.Cast<X509Certificate2>().Where(cert => cert.FriendlyName.Equals(friendlyName)))
            {
                certificate = cert;
            }
            return certificate;
        }

        private static X509Certificate2Collection GetLocalMachineCertificates()
        {
            var localMachineStore = new X509Store(StoreLocation.LocalMachine);
            localMachineStore.Open(OpenFlags.ReadOnly);
            var certificates = localMachineStore.Certificates;
            localMachineStore.Close();
            return certificates;
        }
    }
}

And the interface for the service, which you could extend for a method like public X509Certificate2 GetCertificateFromCustomTrustStore(string friendlyName), where you could fetch your cert from wherever you want to store them:

using System.Security.Cryptography.X509Certificates;

namespace Shared.Certificates
{
    public interface ICertificateService
    {
        X509Certificate2 GetCertificateFromLocalMachineStore(string friendlyName);
    }
}

Server-side certificate injection with the service from above:

CertificateService service = new CertificateService();
X509Certificate2 cert = service.GetCertificateFromLocalMachineStore("grpc_cert");

builder.WebHost.ConfigureKestrel(opt =>
{
    opt.ConfigureHttpsDefaults(h =>
    {
        h.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.AllowCertificate;
        h.CheckCertificateRevocation = false;
        h.ServerCertificate = cert;
    });
}

And client-side certificate injection with the service from above:

CertificateService service = new CertificateService();
X509Certificate2 cert = service.GetCertificateFromLocalMachineStore("grpc_cert");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);

Channel = GrpcChannel.ForAddress($"https://{address}:{port}", new GrpcChannelOptions
{
    HttpHandler = handler
});

If you want to, I can also provide you with the script that I used to generate my self-signed certificates, but I don't think they are of much use to you, since you have different certificates. Sadly I cannot help you more, I think your main problem is somewhere in your certificate structure, and I don't know too much about certs ...

Upvotes: 1

Related Questions