如何为 gRPC 启用服务器端 SSL?

Posted

技术标签:

【中文标题】如何为 gRPC 启用服务器端 SSL?【英文标题】:How to enable server side SSL for gRPC? 【发布时间】:2016-10-09 10:24:14 【问题描述】:

gRPC 新手,找不到任何关于如何在服务器端启用 SSL 的示例。我使用 openssl 生成了一个密钥对,但它抱怨私钥无效。

D0608 16:18:31.390303 Grpc.Core.Internal.UnmanagedLibrary Attempting to load native library "...\grpc_csharp_ext.dll"
D0608 16:18:31.424331 Grpc.Core.Internal.NativeExtension gRPC native library loaded successfully.
E0608 16:18:43.307324 0 ..\src\core\lib\tsi\ssl_transport_security.c:644: Invalid private key.
E0608 16:18:43.307824 0 ..\src\core\lib\security\security_connector.c:821: Handshaker factory creation failed with TSI_INVALID_ARGUMENT.
E0608 16:18:43.307824 0 ..\src\core\ext\transport\chttp2\server\secure\server_secure_chttp2.c:188: Unable to create secure server with credentials of type Ssl.

这是我的代码

var keypair = new KeyCertificatePair(
            File.ReadAllText(@"root-ca.pem"),
            File.ReadAllText(@"ssl-private.key"));
SslServerCredentials creds = new SslServerCredentials(new List<KeyCertificatePair>() keypair);
Server server = new Server

    Services =  GrpcTest.BindService(new GrpcTestImpl()) ,
    Ports =  new ServerPort("127.0.0.1", Port, creds) 
;

【问题讨论】:

【参考方案1】:

这就是我所做的。

使用OpenSSL,生成具有以下内容的证书:

@echo off
set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg   

echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096

echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"

echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server.key 4096

echo Generate server signing request:
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%COMPUTERNAME%"

echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

echo Remove passphrase from server key:
openssl rsa -passin pass:1111 -in server.key -out server.key

echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client.key 4096

echo Generate client signing request:
openssl req -passin pass:1111 -new -key client.key -out client.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%CLIENT-COMPUTERNAME%"

echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

echo Remove passphrase from client key:
openssl rsa -passin pass:1111 -in client.key -out client.key

将密码 1111 更改为您喜欢的任何内容

服务器:

var cacert = File.ReadAllText(@"ca.crt");
var servercert = File.ReadAllText(@"server.crt");
var serverkey = File.ReadAllText(@"server.key");
var keypair = new KeyCertificatePair(servercert, serverkey);
var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>()  keypair , cacert, false);

var server = new Server

    Services =  GrpcTest.BindService(new GrpcTestImpl(writeToDisk)) ,
    Ports =  new ServerPort("0.0.0.0", 555, sslCredentials) 
;
server.Start();

客户:

var cacert = File.ReadAllText(@"ca.crt");
var clientcert = File.ReadAllText(@"client.crt");
var clientkey = File.ReadAllText(@"client.key");
var ssl = new SslCredentials(cacert, new KeyCertificatePair(clientcert, clientkey));
channel = new Channel("localhost", 555, ssl);
client = new GrpcTest.GrpcTestClient(channel);

如果“localhost”不起作用,请改用主机名。

【讨论】:

重要提示:用服务器机器的域名替换 %COMPUTERNAME% 如果它只有 IP 地址,并且您不能授予它域名,请尝试 1) %COMPUTERNAME% = my-server 2) 编辑 / etc/hosts 在客户端机器上添加行 111.111.111.111 my-server 其中 111.111.111.111 是服务器机器的 IP 地址 @NikolayPakudin 执行上述操作后,客户端出现以下错误,StatusCode=Unavailable, Detail="DNS resolution failed"。知道如何解决这个问题吗? @ÁlvaroGarcía 您需要能够从客户端对服务器名称进行 DNS 查找。对于 Windows 机器,你可以执行PING COMPUTERNAME,但它可能无法使用 DNS 解析。如上面 Nikolays 评论中所述,您需要确保 nslookup SERVERNAME 在您的客户端计算机上工作。 作为一个简单的解决方法,您可以指定服务器的 ip 地址,并添加频道选项以覆盖服务器的名称,例如:var options = new List&lt;ChannelOption&gt; new ChannelOption(ChannelOptions.SslTargetNameOverride, "ACTUAL-HOSTNAME") ; var channel = new Channel("1.2.3.4", 555, ssl, options); 您的批处理文件提到了 openssl.cfg。我的系统中没有这样的文件。里面应该有什么? (样本内容)。【参考方案2】:

如果证书颁发机构 (CA) 和证书签名请求 (CSR) 的使用对您的任务来说过于复杂,您可以使用自签名证书。

假设有 1 个服务器和 2 个(或更多)客户端。

在客户端1执行:

openssl req -x509 -newkey rsa:4096 -nodes -keyout client.key -out client.crt -days 3650 -subj '/CN=client1' # generate client1 cert and key
sudo bash -c 'echo "192.168.1.101 my.server" >> /etc/hosts' # create domain for server - if necessary only
scp client.crt server-user@my.server:/path/to/certs/client1.crt # copy public cert client1 to server machine

在客户端2执行:

openssl req -x509 -newkey rsa:4096 -nodes -keyout client.key -out client.crt -days 3650 -subj '/CN=client2' # generate client2 cert and key
sudo bash -c 'echo "192.168.1.101 my.server" >> /etc/hosts' # create domain for server- if necessary only
scp client.crt server-user@my.server:/path/to/certs/client2.crt # copy public cert client2 to server machine

在服务器上执行:

openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -days 3650 -subj '/CN=my.server' # generate server cert and key
scp server.crt client1-user@client1-addr:/path/to/certs # copy public cert server to client1 machine
scp server.crt client2-user@client2-addr:/path/to/certs # copy public cert server to client2 machine
cat client1.crt client2.crt > client.crt # combine client certs into the single file

服务器代码:

var clientCert = File.ReadAllText(Path.Combine(certPath, "client.crt"));
var serverCert = File.ReadAllText(Path.Combine(certPath, "server.crt"));
var serverKey = File.ReadAllText(Path.Combine(certPath, "server.key"));
var keyPair = new KeyCertificatePair(serverCert, serverKey);
var credentials = new SslServerCredentials(new List<KeyCertificatePair>  keyPair , clientCert, true);

var server = new Server

    Services =  MyService.BindService(new MyAdminService()) ,
    Ports =  new ServerPort("0.0.0.0", 54321, credentials) 
;

客户端代码:

var serverCert = File.ReadAllText(Path.Combine(_certPath, "server.crt"));
var clientCert = File.ReadAllText(Path.Combine(_certPath, "client.crt"));
var clientKey = File.ReadAllText(Path.Combine(_certPath, "client.key"));
var credentials = new SslCredentials(serverCert, new KeyCertificatePair(clientCert, clientKey));

var channel = new Channel("my.server:54321", credentials);    
var client = new MyService.MyServiceClient(channel);

重要!

要使用 TLS 证书,请在生成服务器证书时使用域名。

客户端证书可以使用任何唯一的字符串。

域名应至少包含 1 个点 (.),例如my.servermy.server.customzone

如果使用像 my-server 这样的***域,它会导致等待很长时间才能解决它(对我来说总是大约 76 秒)。

优点: - 无需生成 CSR,将其传递给带有 CA 的机器,在那里签名并复制回原始机器

缺点: - 添加新客户端需要向服务器添加证书

【讨论】:

【参考方案3】:

如果您尝试了 @qmo 的建议但仍然无法正常工作,并且您收到同样的错误提示 "StatusCode=Unavailable, Detail="DNS resolution failed" 我通过添加我的主机文件中的新记录(对于 Windows,位于 C:\Windows\System32\drivers\etc 中)。

127.0.0.1 DESKTOP-QNCI7UN

其中 DESKTOP-QNCI7UN 是我机器的名称。然后在我正在使用的客户端中:

channel = new Channel("DESKTOP-QNCI7UN", 50000, ssl);

使用“locahost”无法正常工作。因此,通过使用客户端中的机器名称 + 在主机文件中添加记录解决了这个问题。

【讨论】:

以上是关于如何为 gRPC 启用服务器端 SSL?的主要内容,如果未能解决你的问题,请参考以下文章

Jetty v8.x - 如何为HTTPS配置/启用SSL以及创建Windows服务的步骤

如何为 HTTPS (SSL) 配置 Codeigniter?

如何为 Web Socket 实现服务器端错误处理

如何为自己的WordPress站点安装SSL证书开启https访问

会话管理:如何为 REST 服务生成身份验证令牌? (球衣)

如何为 AWS 放大 GraphQL API 添加服务器端业务逻辑?