Search code examples
flutterdartgrpcgrpc-dart

Dart gRPC TLS certificates with PEMs


I'm having a bit of trouble sorting out how to adapt my Dart gRPC client to use the same TLS settings that are working with my Go client. I've already validated that I can interface with the server suppling the correct CA cert, client cert and client key. In Go I'm using:

    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,
    }

Just supplying that in case it helps demonstrate what's working. In Dart I'm doing this:

  ChannelCredentials credentials = ChannelCredentials.secure(
    certificates: utf8.encode(grpcCertificate),
    onBadCertificate: (certificate, host) {
      return host == apiURL + ':' + apiPort.toString();
    },
  );

grpcCertificate contains the contents of client-key.pem. I suspect this is not correct. I'm not very skilled with certificates like this so I'm a bit at a loss. What value should I be supplying to certificates to achieve a successful handshake with the server?

From the above it seems like I need to parse my PEMs into X.509. In Go that's super easy, not sure how to handle this in Dart.

Edit: I've made a bit of progress:

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

The server seems to hate this less and spits out:

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)

Thanks.


Solution

  • I ended up receiving somewhat of a proper answer on the grpc-dart issues page. The solution looks something like this:

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