In Java, what is the simplest way to create an SSLContext with just a PEM file?

satnam picture satnam · Apr 25, 2018 · Viewed 10.8k times · Source

I used LetsEncrypt's CertBot to generate PEM files for free. In other languages it is easy to start an HTTPS server using just a couple lines of code and the PEM/key files. The solutions I have found so far in java are overly complex and I'm looking for something simpler.

  1. I do not want to use java's command-line "keytool". I just want to drag and drop my PEM/key files into my eclipse, and programatically start up an HTTPS server using an SSLContext.
  2. I do not want to include massive external libraries like BouncyCastle. See the following link for a supposed solution using BouncyCastle: How to build a SSLSocketFactory from PEM certificate and key without converting to keystore?

Is there a better/easier way to do this?

Answer

Robert picture Robert · May 1, 2018

The following code shows in general how create a SSLContext for an HTTPS server by parsing a PEM file that has multiple entries, e.g. several certificates and one RSA PRIVATE KEY. However it is incomplete because plain Java 8 is unable to parse the PKCS#1 RSA private key data. Therefore it seems that your wish to do it without any library is not possible. At least BouncyCastle for parsing the PKCS#1 data is required (and then the PEM parser of BouncyCastle could be used, too).

private SSLContext createSslContext() throws Exception {
    URL url = getClass().getResource("/a.pem");
    InputStream in = url.openStream();
    String pem = new String(in.readAllBytes(), StandardCharsets.UTF_8);
    Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN ([^-]+)---*$([^-]+)^---*END[^-]+-+$");
    Matcher m = parse.matcher(pem);
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    Decoder decoder = Base64.getMimeDecoder();
    List<Certificate> certList = new ArrayList<>(); // java.security.cert.Certificate

    PrivateKey privateKey = null;

    int start = 0;
    while (m.find(start)) {
        String type = m.group(1);
        String base64Data = m.group(2);
        byte[] data = decoder.decode(base64Data);
        start += m.group(0).length();
        type = type.toUpperCase();
        if (type.contains("CERTIFICATE")) {
            Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(data));
            certList.add(cert);
        } else if (type.contains("RSA PRIVATE KEY")) {
            // TODO: load and parse PKCS1 data structure to get the RSA private key  
            privateKey = ...
        } else {
            System.err.println("Unsupported type: " + type);
        }

    }
    if (privateKey == null)
        throw new RuntimeException("RSA private key not found in PEM file");

    char[] keyStorePassword = new char[0];

    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(null, null);

    int count = 0;
    for (Certificate cert : certList) {
        keyStore.setCertificateEntry("cert" + count, cert);
        count++;
    }
    Certificate[] chain = certList.toArray(new Certificate[certList.size()]);
    keyStore.setKeyEntry("key", privateKey, keyStorePassword, chain);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("RSA");
    kmf.init(keyStore, keyStorePassword);
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
    return sslContext;
}