Belvia
Belvia

Reputation: 129

Grpc Java SSL mutual authentication

I would like to know what needs to be set in the GrpcSslContext in order for Grpc Client to do SSL authentication with the server?

Currently, the following codes are working for the usual 1-way SSL authentication from the server to the client.

At the server,

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

At the client,

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

According to gRPC at https://github.com/grpc/grpc-java/blob/master/SECURITY.md,

If mutual authentication is desired this can also be supported by creating the appropriate SslContext.

I wonder if I had initialised the GrpcSslContexts correctly?

Any advice/comments are appreciated.

[Update]

Upon further troubleshooting, I noticed that the CertificateRequest message (as stated in https://en.wikipedia.org/wiki/Transport_Layer_Security#Client-authenticated_TLS_handshake), was never sent to the client to initiate the Client Authentication.

An excerpt of my server log is as follows: ......

*** 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;
....

I am beginning to suspect it could be an inherent bug in gRPC.

Upvotes: 3

Views: 5920

Answers (2)

Eric Anderson
Eric Anderson

Reputation: 26474

Edit: Support was added. Make your own SslContext and pass it to NettyChannelBuilder.sslContext(), making sure to request the client certificate via SslContextBuilder.clientAuth(). Then for each RPC check the ClientCall.getAttributes() and get the SSLSession via Grpc.TRANSPORT_ATTR_SSL_SESSION.


It does seem gRPC-Java does not have a way to request the client certificates on the server, which is necessary for mutual auth. It's not quite a bug given how SSL/TLS works, but more of a feature request.

The Java implementation has not yet worked to support client certificates as a full feature. That would include being able to retrieve the client certificate on the server. The ability to request client authentication would have to be part of that feature.

Client auth is something that grpc-Java has every intention of supporting, but hasn't been worked on yet.

Upvotes: 6

Daniel Heldt
Daniel Heldt

Reputation: 417

To update the existing answer, by now it is possible to access the SSLSession on the server side, which gives handle to the client certificate. the Security.md file of the github project (https://github.com/grpc/grpc-java/blob/master/SECURITY.md) gives an explicit example in the section Mutual TLS, how to do so.

At the time of writing, that file refers to a non-existing constant (ServerCall.SSL_SESSION_KEY), but I created an Issue for that, which contains a workaraound (https://github.com/grpc/grpc-java/issues/3013.). Now, after the issue got resolved, my workaround is not longer neccessary and you can follow the description of Security.md:

What you have to do, to get the client certificate:

Implement a 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);
    }
}

and plug it into your Server:

server = NettyServerBuilder.forPort(port)
                    .sslContext(context)
                    .addService(ServerInterceptors.intercept(new MyServiceImpl(), new MyInterceptor()))
                    .build();

The Security.md also tells you, how you can inject the SSLSession into the Context, to have some handle on it in your MyServiceImpl methods, implementing the remote procedures to be called.

Note that this is experimental API, so use it with care.

Upvotes: 6

Related Questions