I am writing a generic gRPC Kotlin client with the intention that the underlying implementation can be Netty or OkHttp depending on what is on the classpath. This clients needs to perform mutual TLS so I'm doing something like this:
val tlsBuilder: TlsChannelCredentials.Builder = TlsChannelCredentials.newBuilder()
tlsBuilder.keyManager(clientCrt, clientKey)
channelCredentials = tlsBuilder.build()
the clientKey file is an RSA key generated by OpenSSL and has a header like so:
-----BEGIN
RSA PRIVATE KEY-----
The OkHttpChannelBuilder ends up calling the static method getPrivateKey(InputStream is) on io.grpc.util.CertificateUtils in order to load this key. However that tries to interpret the header like so:
while ((line = reader.readLine()) != null) {
if ("-----BEGIN PRIVATE KEY-----".equals(line)) {
break;
}
}
which doesn't work as the actual header contains "RSA" so the "equals" call never matches and it then creates a decodedKeyBytes which is an empty byte array and
when this is later converted to a KeySpec it throws an exception like so:
java.security.InvalidKeyException: Missing key encoding
If I compare this to how the Netty provider/builder does this, it ends up going down a completely different path and delegating the loading of the private key to the method
readPrivateKey(InputStream in) in io.grpc.netty.shaded.io.netty.handler.ssl.PemReader. This doesn't do an exact string match on the key header but instead uses a regular
expression something like so:
private static final Pattern KEY_HEADER = Pattern.compile("-+BEGIN\\s[^-\\r\\n]*PRIVATE\\s+KEY[^-\\r\\n]*-+(?:\\s|\\r|\\n)+");
Matcher m = KEY_HEADER.matcher(content);
There is the same pattern with matching the footer. If I manually remove the word "RSA" from my key's header and footer everything works for both implementations.
So my question is, is this a bug in OkHttp in that it's using an incorrect method to load the private key, or is this a bug in grpc-java where it should be more lenient with the key
header and footer?
I'm happy to create an issue and can also look into creating a test case and possibly even attempting to fix this, I just wanted to check in here first.
Thanks,
Adrian