Creating KeyStore programmatically, from LetsEncrypt certificate
See original GitHub issueI am trying to use Javalin (I love the project and the API!!) to make secure WebSockets + HTTPS REST API run at the same port.
Previously I have been using TooTallNate/Java-WebSocket for using WebSockets, they also have an example of how to create the KeyStore programmatically which if you follow the links ends up at a Stack Overflow answer. The issue with TooTallNate/Java-WebSocket is that it is only a WebSocket server and therefore I would need to use two different ports for WebSockets + HTTPS REST API.
So I tried to combine the TooTallNate/Java-WebSocket example above with the Javalin example and ended up with the following code: (ServerConfig
is just my configuration object containing configuration for which ports to use and where to find the certificate files)
public static Javalin javalin(ServerConfig serverConfig) {
return Javalin.create().server(() -> createServer(serverConfig));
}
private static Server createServer(ServerConfig serverConfig) {
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(serverConfig.webSocketPort);
if (serverConfig.useSecureWebsockets()) {
ServerConnector sslConnector = new ServerConnector(server, createSslContextFactory(serverConfig));
sslConnector.setPort(serverConfig.webSocketPortSSL);
server.setConnectors(new Connector[]{sslConnector, connector});
} else {
server.setConnectors(new Connector[]{connector});
}
return server;
}
private static SslContextFactory createSslContextFactory(ServerConfig serverConfig) {
String pathTo = serverConfig.certificatePath;
String keyPassword = serverConfig.certificatePassword;
try {
byte[] certBytes = parseDERFromPEM(Files.readAllBytes(new File(pathTo + File.separator + "cert.pem").toPath()), "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
byte[] keyBytes = parseDERFromPEM(Files.readAllBytes(new File(pathTo + File.separator + "privkey.pem").toPath()), "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
X509Certificate cert = generateCertificateFromDER(certBytes);
RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("cert-alias", cert);
keystore.setKeyEntry("key-alias", key, keyPassword.toCharArray(), new X509Certificate[]{cert});
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStore(keystore);
sslContextFactory.setKeyStorePassword(keyPassword);
return sslContextFactory;
} catch (IOException | KeyStoreException | InvalidKeySpecException | NoSuchAlgorithmException | CertificateException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
String data = new String(pem);
String[] tokens = data.split(beginDelimiter);
tokens = tokens[1].split(endDelimiter);
return DatatypeConverter.parseBase64Binary(tokens[0]);
}
private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(spec);
}
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
This however ends up with me getting “ERR_SSL_VERSION_OR_CIPHER_MISMATCH” whenever I try to connect with HTTPS to a REST endpoint or when connecting with WebSockets.
I understand that it might be me that is the problem here and not Javalin, but I am hoping for any assistance.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:4
- Comments:11 (6 by maintainers)
Top GitHub Comments
I found the solution myself!
Change:
KeyStore keystore = KeyStore.getInstance("JKS");
to:
KeyStore keystore = KeyStore.getInstance("PKCS12");
Done!
Now for another request: Document this. I bet more people than I would be interested in not having to do the KeyStore generation manually. (Or is what I’m doing here not recommended?)
@tipsy Might take a while but yes, I could do that!