Grpc Java SSL双向认证

3
我想知道在GrpcSslContext中需要设置什么,以便Grpc客户端可以与服务器进行SSL身份验证?
当前,以下代码可用于通常从服务器到客户端的1-way SSL身份验证。
在服务器端,
SslContext sslContext = GrpcSslContexts.forServer(new File(pathToOwnCertPemFile), new File(pathToOwnPrivateKeyPemFile)).trustManager(new File(pathToClientCertPemFile)).build();

ServerImpl server = NettyServerBuilder
        .forPort(port)
        .sslContext(sslContext)
        .addService(MyGrpc.bindService(new MyGrpcService()))
        .build().start();

在客户端,
SslContext sslContext = GrpcSslContexts.forClient().trustManager(new File(pathToServerCertPemFile)).keyManager(new File(pathToOwnCertPemFile), new File(pathToOwnPrivateKeyPemFile)).build();

ChannelImpl channel = NettyChannelBuilder.forAddress(host, port)
                .negotiationType(NegotiationType.TLS)
                .sslContext(sslContext).build();

blockingStub = MyGrpc.newBlockingStub(channel);
asyncStub = MyGrpc.newStub(channel);

根据https://github.com/grpc/grpc-java/blob/master/SECURITY.md的gRPC,

如果需要双向认证,可以通过创建适当的SslContext来支持。

我想知道我是否已正确初始化了GrpcSslContexts?
感谢任何建议/评论。
[更新]
在进一步排除故障时,我注意到未向客户端发送CertificateRequest消息(如https://en.wikipedia.org/wiki/Transport_Layer_Security#Client-authenticated_TLS_handshake中所述),以启动客户端身份验证。
我的服务器日志摘录如下:......
*** ECDH ServerKeyExchange
Signature Algorithm SHA512withRSA
Server key: Sun EC public key, 256 bits
public x coord: 81392923578261760187813715443713168545877454618233337093852615933913992434989
public y coord: 26389586381130695169212775668808794166799180199461581135201001980310825571555
parameters: secp256r1 NIST P-256, X9.62 prime256v1
*** ServerHelloDone
[write] MD5 and SHA1 hashes: len = 1617
0000: 02 00 00 56 03 03 55 DF 34 10 9C 73 B5 00 C2 70 ...V..U.4..s...p
0010: FD B8 CC 36 5B 83 87 70 5B 74 A3 D2 AD B7 75 3B ...6[..p[t....u;
....

我开始怀疑这可能是gRPC中固有的bug。

2个回答

6

编辑:已经添加了支持。制作自己的SslContext并将其传递给NettyChannelBuilder.sslContext(),确保通过SslContextBuilder.clientAuth()请求客户端证书。然后对于每个RPC,检查ClientCall.getAttributes()并通过Grpc.TRANSPORT_ATTR_SSL_SESSION获取SSLSession


似乎gRPC-Java没有办法在服务器上请求客户端证书,这对于双向身份验证是必需的。这不完全是一个错误,因为SSL/TLS的工作原理如此,但更像是一个功能请求。

Java实现尚未完全支持客户端证书作为一项功能。这将包括能够在服务器上检索客户端证书。请求客户端身份验证的能力必须是该功能的一部分。

客户端认证是grpc-Java有意支持的内容,但尚未得到进一步开发。


6
更新现有答案,现在可以在服务器端访问SSLSession,这提供了对客户端证书的处理。github项目的Security.md文件(https://github.com/grpc/grpc-java/blob/master/SECURITY.md)在Mutual TLS部分中给出了一个明确的示例,说明如何实现。
在撰写本文时,该文件引用了一个不存在的常量(ServerCall.SSL_SESSION_KEY),但我为此创建了一个问题,其中包含一个解决方法(https://github.com/grpc/grpc-java/issues/3013)。现在,在问题得到解决后,我的解决方法已不再必要,您可以按照Security.md的描述进行操作:
要获取客户端证书,您需要执行以下操作:
实现一个ServerInterceptor
public class MyInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        String subjectDn = null;
        try {
            SSLSession sslSession = (SSLSession) call.attributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION););
            // sslSession.getPeerCertificates(); contains the certificates, you are looking for 
            }
        } catch (SSLPeerUnverifiedException e) {
            // do what you need to do
        }
        return next.startCall(call, headers);
    }
}

将其插入到您的服务器中:

server = NettyServerBuilder.forPort(port)
                    .sslContext(context)
                    .addService(ServerInterceptors.intercept(new MyServiceImpl(), new MyInterceptor()))
                    .build();
Security.md 文件还告诉您如何将 SSLSession 注入上下文,以便在实现远程调用的 MyServiceImpl 方法中对其进行处理。
请注意,这是试验性 API,因此请小心使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接