我想使用自签名证书来使用 gRPC dotnet,但是当我从客户端调用该服务时出现此错误:无法建立 HTTP/2 连接,因为服务器未完成 HTTP/2 握手。
我已经使用此脚本创建了 pfx 证书:
@echo off
set path="C:\Program Files\OpenSSL-Win64\bin"
#set OPENSSL_CONF=D:\programas\OpenSSL-Win64\OpenSSL-Win64\bin\openssl.cfg
#CA
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 36500 -key ca.key -out ca.crt -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"
#SERVER
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 36500 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
echo Remove passphrase from server key:
#openssl rsa -passin pass:1111 -in server.key -out server.key
#CLIENT
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 36500 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
echo Remove passphrase from client key:
#openssl rsa -passin pass:1111 -in client.key -out client.key
pause
Run Code Online (Sandbox Code Playgroud)
在我的服务中,我使用以下代码:
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps("server.pfx", "1111");
//listenOptions.UseHttps("<path to .pfx file>", "<certificate password>");
});
});
webBuilder.UseStartup<Startup>();
webBuilder.UseStartup<Startup>();
});
Run Code Online (Sandbox Code Playgroud)
在我的客户端中我有这样的代码:
X509Certificate2 miCertificado = new X509Certificate2("client.pfx", "1111");
HttpClientHandler miHandler = new HttpClientHandler();
miHandler.ClientCertificates.Add(miCertificado);
HttpClient miHttpClient = new HttpClient(miHandler);
GrpcChannelOptions misOpciones = new GrpcChannelOptions() { HttpClient = miHttpClient };
var miChannel = GrpcChannel.ForAddress("http://1.1.1.2:5001");
var miClient = MagicOnionClient.Create<IInterface>(miChannel);
ComponentesDto miDataResultado = await miClient.GetDataAsync();
Run Code Online (Sandbox Code Playgroud)
我不明白这怎么会成为问题。我究竟做错了什么?
谢谢。
这个问题已经快一年半了,从那以后,我做了很多尝试,直到我解决了我的问题。
我不记得我解决问题的具体方法,但如果需要的话我会根据评论修改这个答案。
我的解决方案是使用 .NET 6,这实际上是最后一个稳定版本,但我猜某些部分可以与 .NET 5 一起使用,但请记住,该解决方案使用的是 .NET 6。
握手中的一个重要部分是证书的创建,当它是自签名证书时更重要。我的解决方案适用于 .crt 文件,而不是我在原始问题中尝试使用的 pfx 文件。
关于服务器证书,一件重要的事情是它必须在 SAN 中具有服务器的 IP,或者服务器的 URL。您可以同时设置。例如,如果您使用记事本打开 .crt 文件,您将看到类似的内容:
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
44:C4:BD:F
X509v3 Authority Key Identifier:
keyid:0F:58:2
DirName:/CN=gRPC-CA
serial:22:A3
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
IP Address:192.168.1.100
Run Code Online (Sandbox Code Playgroud)
签名算法:sha512WithRSAEncryption
在最后 3 行中,您可以看到我在 SAN(主题备用名称)中拥有服务器的 IP。
为了创建证书,我使用了 easy rsa 3,它是一个可以更轻松地生成自签名证书的工具。您可以在这里找到该工具: https: //github.com/OpenVPN/easy-rsa,您可以在发布部分下载二进制文件: https: //github.com/OpenVPN/easy-rsa/releases。
一般步骤是:
1.- 将文件 vars.example 复制到同一文件夹中并将其命名为“vars”(不带可执行文件)。2.- 使用您需要的信息编辑 vars 文件。3.- 运行 EasyRSA-Start.bat 启动 Easy RSA 的 shell。4.- 运行 easyrsa init-pki 来初始化环境。重要提示:只执行一次,否则您将删除您可能拥有的所有证书。5.- 运行 easyrsa build-ca 创建 CA 证书。6.- 运行 easyrsa build-server-full 192.168.1.2 nopass 为服务器创建证书。重要提示:将 IP 更改为您服务器的 IP。7.- 运行 easyrsa build-client-full Cliente01 nopass 为客户端创建证书。重要提示:将名称 Cliente01 更改为您的证书的名称。您在此处输入的名称将是证书的通用名称。
所有证书均在 PKI 子文件夹中创建。
我在 ASP 应用程序中托管我的服务。使用最小的 API,在 ASP 项目的 program.cs 文件中,我必须以这种方式创建 X509 证书:
builder.WebHost.ConfigureKestrel((context, options) =>
{
string miStrCertificado = File.ReadAllText("certificados/server.crt");
string miStrKey = File.ReadAllText("certificados/server.key");
X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);
//it is needed to create a second certificate because if not, you will get an error.
//Here you can find information about the issue: https://github.com/dotnet/runtime/issues/74093
X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));
//For security, delete the first certificate.
miCertficadoX509.Dispose();
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(miCertificado2);
});
});
Run Code Online (Sandbox Code Playgroud)
这是 grpc 的客户端:
public async Task<List<FacturasDTO>> MymethodAsync()
{
try
{
//Con certificados
string miStrCertificado = System.IO.File.ReadAllText(@"Certificados\Client.crt");
string miStrClave = System.IO.File.ReadAllText(@"Certificados\Client.key");
string miStrCA = System.IO.File.ReadAllText(@"Certificados\ca.crt");
X509Certificate2 cert = X509Certificate2.CreateFromPem(miStrCertificado, miStrClave);
HttpClientHandler miHttpHandler = new HttpClientHandler();
miHttpHandler.ClientCertificates.Add(cert);
miHttpHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
miHttpHandler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation;
HttpClient httpClient = new(miHttpHandler);
GrpcChannel channel = GrpcChannel.ForAddress("https://20.30.40.50:5001", new GrpcChannelOptions
{
HttpClient = httpClient
});
var client = channel.CreateGrpcService<IFacturasService>();
var reply = await client.GetFacturasDTOAsync();
return reply.Facturas;
}
catch (RpcException ex)
{
throw;
}
catch (Exception ex)
{
throw;
}
}
/// <summary>
/// This method has to implement the logic that ensure that server certificate is a trust certificate.
/// This is needed because the server certificate is a self signed certificate.
/// </summary>
/// <param name="requestMessage"></param>
/// <param name="paramCertificadoServidor"></param>
/// <param name="chain"></param>
/// <param name="sslErrors"></param>
/// <returns></returns>
private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 paramCertificadoServidor, X509Chain chain, SslPolicyErrors sslErrors)
{
return true;
//GetCerHasString devuelve la huella
//if (paramCertificadoServidor.GetCertHashString() == "xxxxxxxxxxxxxxxx")
//{
// return true;
//}
//else
//{
// return sslErrors == SslPolicyErrors.None;
//}
//// It is possible inpect the certificate provided by server
//Console.WriteLine($"Requested URI: {requestMessage.RequestUri}");
//Console.WriteLine($"Effective date: {certificate.GetEffectiveDateString()}");
//Console.WriteLine($"Exp date: {certificate.GetExpirationDateString()}");
//Console.WriteLine($"Issuer: {certificate.Issuer}");
//Console.WriteLine($"Subject: {certificate.Subject}");
//// Based on the custom logic it is possible to decide whether the client considers certificate valid or not
//Console.WriteLine($"Errors: {sslErrors}");
//return sslErrors == SslPolicyErrors.None;
}
Run Code Online (Sandbox Code Playgroud)
请注意,客户端中的方法 ServerCertificateCustomValidation() 始终返回 true,用于测试,但您应该实现逻辑以确保服务器的证书是信任证书。我留下一些注释代码作为示例,如何检查它,但它没有经过测试,所以我无法确保它有效或是否是正确的方法。只是有一些想法。
我希望这可以解决问题。如果没有,请发表评论,我会更新解决方案。