Builder API for Certificates and Keystores

So I've been writing a bunch on JSSE and JCA:

To make life a little easier on myself, I've written a fluent builder API for creating X.509 certificates, keystores and SSLContext. It is available at https://github.com/tersesystems/securitybuilder and you add it to your project with either Maven:

<dependency>
    <groupId>com.tersesystems.securitybuilder</groupId>
    <artifactId>securitybuilder</artifactId>
    <version>1.0.0</version><!-- see badge for latest version -->
</dependency>

or SBT:

libraryDependencies += "com.tersesystems.securitybuilder" % "securitybuilder" % "1.0.0"

Best way to describe the builder API is to take a look at the X509CertificateCreator:

public class X509CertificateCreatorTest {
  @Test
  public void testFunctionalStyle() throws Exception {
    FinalStage<RSAKeyPair> keyPairCreator = KeyPairCreator.creator().withRSA().withKeySize(2048);
    RSAKeyPair rootKeyPair = keyPairCreator.create();
    RSAKeyPair intermediateKeyPair = keyPairCreator.create();
    RSAKeyPair eePair = keyPairCreator.create();

    IssuerStage<RSAPrivateKey> creator =
        X509CertificateCreator.creator().withSHA256withRSA().withDuration(Duration.ofDays(365));

    String issuer = "CN=letsencrypt.derp,O=Root CA";
    X509Certificate[] chain =
        creator
            .withRootCA(issuer, rootKeyPair, 2)
            .chain(
                rootKeyPair.getPrivate(),
                rootCreator ->
                    rootCreator
                        .withPublicKey(intermediateKeyPair.getPublic())
                        .withSubject("OU=intermediate CA")
                        .withCertificateAuthorityExtensions(0)
                        .chain(
                            intermediateKeyPair.getPrivate(),
                            intCreator ->
                                intCreator
                                    .withPublicKey(eePair.getPublic())
                                    .withSubject("CN=tersesystems.com")
                                    .withEndEntityExtensions()
                                    .chain()))
            .create();

    PrivateKeyStore privateKeyStore =
        PrivateKeyStore.create("tersesystems.com", eePair.getPrivate(), chain);
    TrustStore trustStore = TrustStore.create(singletonList(chain[2]), cert -> "letsencrypt.derp");

    try {
      final PKIXCertPathValidatorResult result = CertificateChainValidator.validator()
          .withAnchor(new TrustAnchor(issuer, rootKeyPair.getPublic(), null))
          .withCertificates(chain)
          .validate();
      final PublicKey subjectPublicKey = result.getPublicKey();
      assertThat(subjectPublicKey).isEqualTo(eePair.getPublic());
    } catch (final CertPathValidatorException cpve) {
      fail("Cannot test exception", cpve);
    }

    SSLContext sslContext =
        SSLContextBuilder.builder()
            .withTLS()
            .withKeyManager(
                KeyManagerBuilder.builder()
                    .withSunX509()
                    .withPrivateKeyStore(privateKeyStore)
                    .build())
            .withTrustManager(
                TrustManagerBuilder.builder()
                    .withDefaultAlgorithm()
                    .withTrustStore(trustStore)
                    .build())
            .build();
    assertThat(sslContext).isNotNull();
  }
}

The fluent API is staged builder pattern, so it's easy to autocomplete, and the defaults are sane. There are fixes for setting up specific passwords in KeyManagerKeyStoreBuilder, and keypair creation returns a KeyPair of the given type so you don't need to cast:

final RSAKeyPair keyPair = KeyPairCreator.creator().withRSA().withKeySize(2048).create();
RSAPublicKey publicKey = keyPair.getPublic();

There's only one dependency, on throwable-interfaces, but it's not exposed publically (it's used for passing suppliers around internally.)

Threads on Reddit for reference.

Comments