Skip to content

Latest commit

 

History

History
234 lines (190 loc) · 9.09 KB

README.md

File metadata and controls

234 lines (190 loc) · 9.09 KB

OkHttp TLS

Approachable APIs for using TLS.

A HeldCertificate is a certificate and its private key. Use the builder to create a self-signed certificate that a test server can use for HTTPS:

HeldCertificate localhostCertificate = new HeldCertificate.Builder()
    .addSubjectAlternativeName("localhost")
    .build();

HandshakeCertificates keeps the certificates for a TLS handshake. Use its builder to define which certificates the HTTPS server returns to its clients. The returned instance can create an SSLSocketFactory that implements this policy:

HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
    .heldCertificate(localhostCertificate)
    .build();
MockWebServer server = new MockWebServer();
server.useHttps(serverCertificates.sslSocketFactory(), false);

HandshakeCertificates also works for clients where its job is to define which root certificates to trust. In this simplified example we trust the server's self-signed certificate:

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
    .addTrustedCertificate(localhostCertificate.certificate())
    .build();
OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
    .build();

With a server that holds a certificate and a client that trusts it we have enough for an HTTPS handshake. The best part of this example is that we don't need to make our test code insecure with a a fake HostnameVerifier or X509TrustManager.

Certificate Authorities

The above example uses a self-signed certificate. This is convenient for testing but not representative of real-world HTTPS deployment. To get closer to that we can use HeldCertificate to generate a trusted root certificate, an intermediate certificate, and a server certificate. We use certificateAuthority(int) to create certificates that can sign other certificates. The int specifies how many intermediate certificates are allowed beneath it in the chain.

HeldCertificate rootCertificate = new HeldCertificate.Builder()
    .certificateAuthority(1)
    .build();

HeldCertificate intermediateCertificate = new HeldCertificate.Builder()
    .certificateAuthority(0)
    .signedBy(rootCertificate)
    .build();

HeldCertificate serverCertificate = new HeldCertificate.Builder()
    .addSubjectAlternativeName("localhost")
    .signedBy(intermediateCertificate)
    .build();

To serve this configuration the server needs to provide its clients with a chain of certificates starting with its own and including everything up-to but not including the root. We don't need to include root certificates because the client already has them.

HandshakeCertificates serverHandshakeCertificates = new HandshakeCertificates.Builder()
    .heldCertificate(serverCertificate, intermediateCertificate.certificate())
    .build();

The client only needs to know the trusted root certificate. It checks the server's certificate by validating the signatures within the chain.

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
    .addTrustedCertificate(rootCertificate.certificate())
    .build();
OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
    .build();

Client Authentication

The above scenario is representative of most TLS set ups: the client uses certificates to validate the identity of a server. The converse is also possible. Here we create a server that authenticates a client and a client that authenticates a server.

// Create the root for client and server to trust. We could also use different roots for each!
HeldCertificate rootCertificate = new HeldCertificate.Builder()
    .certificateAuthority(0)
    .build();

// Create a server certificate and a server that uses it.
HeldCertificate serverCertificate = new HeldCertificate.Builder()
    .commonName("ingen")
    .addSubjectAlternativeName(server.getHostName())
    .signedBy(rootCertificate)
    .build();
HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
    .addTrustedCertificate(rootCertificate.certificate())
    .heldCertificate(serverCertificate)
    .build();
MockWebServer server = new MockWebServer();
server.useHttps(serverCertificates.sslSocketFactory(), false);
server.requestClientAuth();
server.enqueue(new MockResponse());

// Create a client certificate and a client that uses it.
HeldCertificate clientCertificate = new HeldCertificate.Builder()
    .commonName("ianmalcolm")
    .signedBy(rootCertificate)
    .build();
HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
    .addTrustedCertificate(rootCertificate.certificate())
    .heldCertificate(clientCertificate)
    .build();
OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
    .build();

// Connect 'em all together. Certificates are exchanged in the handshake.
Call call = client.newCall(new Request.Builder()
    .url(server.url("/"))
    .build());
Response response = call.execute();
System.out.println(response.handshake().peerPrincipal());
RecordedRequest recordedRequest = server.takeRequest();
System.out.println(recordedRequest.getHandshake().peerPrincipal());

This handshake is successful because each party has prearranged to trust the root certificate that signs the other party's chain.

Well-Known Certificate Authorities

In these examples we've prearranged which root certificates to trust. But for regular HTTPS on the Internet this set of trusted root certificates is usually provided by default by the host platform. Such a set typically includes many root certificates from well-known certificate authorities like Entrust and Verisign.

This is the behavior you'll get with your OkHttpClient if you don't specifically configure HandshakeCertificates. Or you can do it explicitly with addPlatformTrustedCertificates():

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
    .addPlatformTrustedCertificates()
    .build();
OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
    .build();

PEM files

You can encode a HeldCertificate in PEM format:

HeldCertificate heldCertificate = ...
System.out.println(heldCertificate.certificatePem())
-----BEGIN CERTIFICATE-----
MIIBSjCB8aADAgECAgEBMAoGCCqGSM49BAMCMC8xLTArBgNVBAMTJDJiYWY3NzVl
LWE4MzUtNDM5ZS1hYWE2LTgzNmNiNDlmMGM3MTAeFw0xODA3MTMxMjA0MzJaFw0x
ODA3MTQxMjA0MzJaMC8xLTArBgNVBAMTJDJiYWY3NzVlLWE4MzUtNDM5ZS1hYWE2
LTgzNmNiNDlmMGM3MTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDmlOiZ3dxA2
zw1KwqGNsKVUZbkUVj5cxV1jDbSTvTlOjSj6LR0Ovys9RFdrjcbbMLWvSvMQgHch
k8Q50c6Kb34wCgYIKoZIzj0EAwIDSAAwRQIhAJkXiCbIR3zxuH5SQR5PEAPJ+ntg
msOSMaAKwAePESf+AiBlxbEu6YpHt1ZJoAhMAv6raYnwSp/A94eJGlJynQ0igQ==
-----END CERTIFICATE-----

You can also do so with the private key. Be careful with these!

HeldCertificate heldCertificate = ...
System.out.println(heldCertificate.privateKeyPkcs8Pem())
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgQbYDQiewSnregm9e
IjXEHQgc6w3ELHdnH1houEUom9CgCgYIKoZIzj0DAQehRANCAAQ5pTomd3cQNs8N
SsKhjbClVGW5FFY+XMVdYw20k705To0o+i0dDr8rPURXa43G2zC1r0rzEIB3IZPE
OdHOim9+
-----END PRIVATE KEY-----

Recommendations

Typically servers need a held certificate plus a chain of intermediates. Servers only need the private key for their own certificate. The chain served by a server doesn't need the root certificate.

The trusted roots don't need to be the same for client and server when using client authentication. Clients might rely on the platform certificates and servers might use a private organization-specific certificate authority.

By default HeldCertificate instances expire after 24 hours. Use duration() to adjust.

By default server certificates need to identify which hostnames they're trusted for. You may add as many as necessary with addSubjectAlternativeName(). This mechanism also supports a very limited form of wildcards *.example.com where the * must be first and doesn't match nested subdomains.

By default certificates use fast and secure 256-bit ECDSA keys. For interoperability with very old clients use HeldCertificate.Builder.rsa2048().

Download

implementation("com.squareup.okhttp3:okhttp-tls:4.12.0")