Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grpc .Net client fails to connect to server with SSL

Tags:

c#

grpc

Unable to connect to the greeter grpc service mentioned in this link - https://learn.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0 from a greeter client which is written from a .net framework app using grpc.core libraries(Grpc.Core.2.24.0 and Grpc.Core.Api.2.24.0).

Below is my client code. It works with non SSL but not with SSL

Client code with non SSL(this works)

var channel = new Channel("localhost:5000", ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

Client code with SSL(this fails to connect )

SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

The error I get with SSL is:

Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'

I tried with .net core app client mentioned in the same link(https://learn.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0) that works with SSL and non SSL but not by using the grp libraries directly. My client is a .Net framework client that is the reason I can't use .net libraries for connecting to grpc service. .Net grpc libraries are supported only from .net core app.

SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

Expected result - response from the server

Actual result - Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'

like image 852
Sabitha Avatar asked Dec 13 '22 10:12

Sabitha


2 Answers

I made a working client on the .NET Framework c with a server on .NET Core on localhost:

static async Task Main(string[] args)
{    
    string s = GetRootCertificates();
    var channel_creds = new SslCredentials(s);
    var channel = new Channel("localhost",50051, channel_creds);
    var client = new Informer.InformerClient(channel);
    await GetPing(client);
}

public static string GetRootCertificates()
{
    StringBuilder builder = new StringBuilder();
    X509Store store = new X509Store(StoreName.Root);
    store.Open(OpenFlags.ReadOnly);
    foreach (X509Certificate2 mCert in store.Certificates)
    {
        builder.AppendLine(
            "# Issuer: " + mCert.Issuer.ToString() + "\n" +
            "# Subject: " + mCert.Subject.ToString() + "\n" +
            "# Label: " + mCert.FriendlyName.ToString() + "\n" +
            "# Serial: " + mCert.SerialNumber.ToString() + "\n" +
            "# SHA1 Fingerprint: " + mCert.GetCertHashString().ToString() + "\n" +
            ExportToPEM(mCert) + "\n");
    }
    return builder.ToString();
}

/// <summary>
/// Export a certificate to a PEM format string
/// </summary>
/// <param name="cert">The certificate to export</param>
/// <returns>A PEM encoded string</returns>
public static string ExportToPEM(X509Certificate cert)
{
    StringBuilder builder = new StringBuilder();            

    builder.AppendLine("-----BEGIN CERTIFICATE-----");
    builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
    builder.AppendLine("-----END CERTIFICATE-----");

    return builder.ToString();
}

private static async Task GetPing(Informer.InformerClient client)
{
    Console.WriteLine("Getting ping...");
    try
    {
        Metadata headers = null;
        var response = await client.GetServerPingAsync(new Empty(), headers);
        string result = "Nan";
        if (response.PingResponse_ == 1)
            result = "Ok!";
        Console.WriteLine($"Ping say: {result }");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error get server ping." + Environment.NewLine + ex.ToString());
    }
}

But I have not yet succeeded in making this work on remote machines (for example, where ip 192.168.1.7 is the server address and the client address is 192.168.1.2)

like image 164
Alexandr Meshkov Avatar answered Dec 22 '22 00:12

Alexandr Meshkov


I got it working with SSL port by using the Server's certificate in pem format in the client.

SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));
var channel = new Channel("localhost", 5001, secureCredentials);

A bit of explanation, Asp.NETCore template in VS 2019 uses a development certificate with pfx file at %AppData%\ASP.NET\Https\ProjectName.pfx and password = %AppData%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json {:Kestrel:Certificates:Development:Password} Value You can get the UserSecretsId id from the ProjectName.csproj. This will be different for each ASP.NET Core Project.

We just need the public key of the certificate as a certificate.pem file to communicate securely over gRPC. Use the command below to extract publickey from pfx

openssl pkcs12 -in "<DiskLocationOfPfx>\ProjectName.pfx" -nokeys -out "<TargetLocation>\certifcate.pem"

Copy this cerificate.pem for the gRPC .NET Framework client to use.

SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("<DiskLocationTo the Folder>/certificate.pem"))
var channel = new Channel("localhost", 5001, secureCredentials);

Note that port 5001 I used is the SSL port of my ASP.NET Core application.

For Production Scenarios

Use a valid certificate from certificate signing authority and use same certificate in ASP.NET Core Server and .NET Framework client as pfx and pem respectively.

Or Using Self signed certificate

Using Self signed certificates are a valid option for most microservices that communicate between our own microservices. We may not need an authority signed certificate. One problem we may face with using self signed certificate is that the certificate may be issued to some target DNS name and our gRPC server may be running somewhere else and secure connection cannot be established.

Use gRPC Target Name override keys to override the ssl target name validation.

   List<ChannelOption> channelOptions = new List<ChannelOption>()
   {
       new ChannelOption("grpc.ssl_target_name_override", <DNS to which our certificate is issued to>),
   };
   SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));

   var channel = new Channel("localhost", 5001, secureCredentials, channelOptions);
like image 35
Jins Peter Avatar answered Dec 21 '22 22:12

Jins Peter