grpc / grpc-dart

The Dart language implementation of gRPC.
https://pub.dev/packages/grpc
Apache License 2.0
860 stars 271 forks source link

Doc example or clarification on grpc-dart and TLS #501

Closed davedbase closed 3 years ago

davedbase commented 3 years ago

I'm having trouble sorting out how to properly enable TLS. I have an example of it working successfully in Go but can't seem to factor the right Dart implementation.

Library Version

grpc: ^3.0.0

Details

I have a full operational gRPC server written in Go. I've written a Go client to prove the implementation is correct and operational. Instructions were followed here: https://dev.to/techschoolguru/how-to-secure-grpc-connection-with-ssl-tls-in-go-4ph to ensure correct setup. Note: I didn't use the passphrase part of the certificates.

In Go my client looks like so:

pemServerCA, err := ioutil.ReadFile("pems/ca-cert.pem")
if err != nil {
    return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(pemServerCA) {
    return nil, fmt.Errorf("failed to add server CA's certificate")
}
// Load client's certificate and private key
clientCert, err := tls.LoadX509KeyPair("pems/client-cert.pem", "pems/client-key.pem")
if err != nil {
    return nil, err
}
// Create the credentials and return it
config := &tls.Config{
    Certificates: []tls.Certificate{clientCert},
    RootCAs:      certPool,
}

In essence I'm producing an x509 pair and applying my root CA. Trying to replicate or adapt this to Dart without an example or docs has been painful. The solution I've factored is just using the client-cert.pem and throwing it into ChannelCredentials like so:

List<int> list = grpcCertificate.codeUnits;
Uint8List cert = Uint8List.fromList(list);
ChannelCredentials credentials = ChannelCredentials.secure(
    certificates: cert,
    authority: 'localhost',
    onBadCertificate: (certificate, host) {
    return host == apiURL + ':' + apiPort.toString();
    },
);

I feel like I'm obviously not doing this properly and it mostly comes down to the fact that I don't believe I can use the pem in this format. The problem is that it's unclear what certificates actually accepts? If I feed it the above cert at least the handshake happens and I get a response from the server:

flutter: gRPC Error (code: 14, codeName: UNAVAILABLE, message: Error connecting: TlsException: Failure trusting builtin roots (OS Error:
    BAD_PKCS12_DATA(pkcs8_x509.c:645), errno = 0), details: null, rawResponse: null)

Any guidance or direction to a solution similar that can help explain or solve how to feed in the PEM or convert the PEM to the right format would be appreciated. I'm clearly not a TLS expert so really going at this blindly.

David

mraleph commented 3 years ago
List<int> list = grpcCertificate.codeUnits;
Uint8List cert = Uint8List.fromList(list);

Seems suspect to me. Have you tried to just load it with File(...).readAsBytesSync()? Exception indicates that whatever you gave to the ChannelCredentials.secure fails to parse.

Note however that ChannelCredentials.secure(certificates: smth) is equivalent of config := &tls.Config{ RootCAs: smth, }, it allows you to configure trusted roots and not the certificates that would be used for authentication with the server.

If you want to supply certificates from the client to the server then you need to do some manual work. I think something like this (untested):

class MyChannelCredentials extends ChannelCredentials {
  final Uint8List? certificateChain;
  final Uint8List? privateKey;

  MyChannelCredentials({
    Uint8List? trustedRoots,
    this.certificateChain,
    this.privateKey,
    String? authority,
    BadCertificateHandler? onBadCertificate,
  }) : super.secure(
            certificates: trustedRoots,
            authority: authority,
            onBadCertificate: onBadCertificate);

  @override
  SecurityContext get securityContext {
    final ctx = super.securityContext;
    if (certificateChain != null) {
      ctx.useCertificateChainBytes(certificateChain);
    }
    if (privateKey != null) {
      ctx.usePrivateKeyBytes(privateKey);
    }
    return ctx;
  }
}

final cred = MyChannelCredentials(
  trustedRoots: File('pems/ca-cert.pem').readAsBytesSync(),
  certificateChain: File('pems/client-cert.pem').readAsBytesSync(),
  privateKey: File('pems/client-key.pem').readAsBytesSync(),
  authority: 'localhost',
);