无法建立 HTTP/2 连接,因为服务器未完成 HTTP/2 握手

Álv*_*cía 6 .net c# grpc

我想使用自签名证书来使用 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)

我不明白这怎么会成为问题。我究竟做错了什么?

谢谢。

Álv*_*cía 0

这个问题已经快一年半了,从那以后,我做了很多尝试,直到我解决了我的问题。

我不记得我解决问题的具体方法,但如果需要的话我会根据评论修改这个答案。

我的解决方案是使用 .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,用于测试,但您应该实现逻辑以确保服务器的证书是信任证书。我留下一些注释代码作为示例,如何检查它,但它没有经过测试,所以我无法确保它有效或是否是正确的方法。只是有一些想法。

我希望这可以解决问题。如果没有,请发表评论,我会更新解决方案。