Grpc .Net客户端无法通过SSL连接到服务器

时间:2019-09-26 21:40:00

标签: grpc

无法连接到此链接中提到的greeter grpc服务-来自使用grpc.core库(Grpc.Core.2.24.0和{{1}从.net框架应用编写的greeter客户端的https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0 }。

下面是我的客户代码。它适用于非SSL,但不适用于SSL

具有非SSL的客户端代码(有效)

Grpc.Core.Api.2.24.0

具有SSL的客户端代码(无法连接)

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();

以下是我收到的SSL错误 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();

我尝试使用在同一链接(https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0)中提到的.net核心应用程序客户端,该客户端可与SSL和非SSL一起使用,但不能直接使用grp库。我的客户端是.Net框架客户端,这就是我无法使用.net库连接到grpc服务的原因。 .net grpc库仅受.net核心应用程序支持。

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

预期结果-服务器的响应

实际结果-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();

3 个答案:

答案 0 :(得分:6)

我在.NET Framework c上创建了一个工作客户端,并在本地主机上的.NET Core上安装了服务器:

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());
    }
}

但是我尚未在远程计算机上成功完成此工作(例如,其中ip 192.168.1.7是服务器地址,而客户端地址是192.168.1.2)

答案 1 :(得分:1)

我通过在客户端中使用pem格式的服务器证书来使用SSL端口。

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

这意味着VS 2019中的Asp.NETCore模板使用开发证书 与%AppData%\ASP.NET\Https\ProjectName.pfx上的pfx文件一起使用 密码= %AppData%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json {:Kestrel:Certificates:Development:Password} Value 您可以从UserSecretsId获取ProjectName.csproj的ID。对于每个ASP.NET Core项目,这都是不同的。

我使用以下命令将pfx密码组合转换为pem

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

这将提示您输入pfx密码。使用以上secrets.json中的密码。

提供一些密码来生成certificate.pem(至少4个字母)。

复制此cerificate.pem,以供gRPC .NET Framework客户端访问并在其中使用

SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"))

用于生产方案

使用来自证书签名机构的有效证书,并在ASP.NET Core Server中使用pfx,在.NET Framework客户端中使用pem。

答案 2 :(得分:1)

我没有在客户端保存 pem 就让它工作了(如果客户端和服务器在不同的机器上,则事件发生)。

首先,目标/主机名(用于创建通道的名称)必须与服务器证书中的 CN(通用名称)相匹配,这一点非常重要,这里的棘手部分是 区分大小写

e.q: 证书的 CN 是 SV-XXX-DEV-01 并且您指定 sv-xxx-dev-01 这将不起作用并且您得到以下错误:

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

所以这是我的解决方案(当然这可以优化,不应该在一个类中(关注点分离),但更容易理解这一点。

    public static async Task Main(string[] args)
            {
                await FullFrameworkSample();
            }
    
            private static async Task FullFrameworkSample()
            {
                Uri host = new Uri("https://sv-xxx-dev-cpu-01:44301");
                int port = host.Port;
    
                (string publicKeyInPemFormat, string commonName) = await GetCertificateInformationFromServer(host);
    
                //note: in the full framework implementation it's very important that the casing of the target is correct (the same casing as in the CN name of the certificate)
                string target = $"{commonName}:{port}";
    
                //note: thats only needed in our case, because we have a server side interceptor, that checks if the secureKey is valid.
                CallCredentials credentials = CallCredentials.FromInterceptor((context, metadata) =>
                                                                              {
                                                                                  metadata.Add("SecurityTokenId", "someSecureKey");
    
                                                                                  return Task.CompletedTask;
                                                                              });
    
                ChannelCredentials channelCredentials = ChannelCredentials.Create(new SslCredentials(publicKeyInPemFormat), credentials);
    
                Channel channel = new Channel(target, channelCredentials);
    
                ProjectInlayDataService.ProjectInlayDataServiceClient client = new ProjectInlayDataService.ProjectInlayDataServiceClient(channel);
    
                GetProjectInlayDataResponse result = await client.GetProjectInlayDataAsync(new GetProjectInlayDataRequest());                                                                                        
    
                await channel.ShutdownAsync();
    
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
    
            private static async Task<(string PublicKeyInPemFormat, string CommonName)> GetCertificateInformationFromServer(Uri host)
            {
                Regex commonNameRegex = new Regex("CN=([\\w\\-.]*),?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    
                StringBuilder builder = new StringBuilder();
                const string newline = "\n";
    
                X509Certificate certFromServer;
    
                using (HttpClient client = new HttpClient())
                {
                    using (HttpResponseMessage _ = await client.GetAsync(host))
                    {
                       //get the certificate from the server, so we don't need to store the pem.
                        certFromServer = ServicePointManager.FindServicePoint(host).Certificate;
                        if (certFromServer == null)
                            throw new InvalidOperationException($"Could not get certificate from server ({host}).");
                    }
                }
    
                Match match = commonNameRegex.Match(certFromServer.Subject);
                if (!match.Success)
                    throw new InvalidOperationException($"Could not extract CN (Common Name) from server certificate ({certFromServer.Subject}).");
    
                string commonName = match.Groups[1].Captures[0].Value;
    
                X509Certificate2 certificate = new X509Certificate2(certFromServer);
                string pem = ExportToPem(certificate);
    
                builder.AppendLine(
                    "# Issuer: " + certificate.Issuer + newline +
                    "# Subject: " + certificate.Subject + newline +
                    "# Label: " + certificate.FriendlyName + newline +
                    "# Serial: " + certificate.SerialNumber + newline +
                    "# SHA1 Fingerprint: " + certificate.GetCertHashString() + newline +
                    pem + newline);
    
                return (builder.ToString(), commonName);
            }
    
            /// <summary>
            /// Export a certificate to a PEM format string
            /// </summary>
            /// <param name="cert">The certificate to export</param>
            /// <returns>A PEM encoded string</returns>
            private 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();
            }
    }