I opted to use acme4j to create a letsencrypt certificate. So far it seems to have worked perfectly and I have some java code that creates a registration, responds to a challenge an ultimately presents me with a x509 certificate for my domain (along with a 'certificate chain'). The code is integrated nicely into my java application and doesn't require any downtime for certificate renewal. Awesome.
From here I'm a bit stuck. My application is a just a main app that has an embedded undertow webserver that I instantiate programatically. In order to create an https listener I need to create an SSLContext object. I've saved the x509 certificate that I got from letsencrypt to disk so it can be reused:
...
X509Certificate x509 = cert.download();
Path pemFile = pathTo(domain + ".pem");
try (Writer writer = Files.newBufferedWriter(pemFile); JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(writer)) {
jcaPEMWriter.writeObject(x509);
}
And then on start up my application reloads that certificate and passes it into the undertow web server:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream finStream = new FileInputStream(certFile.toFile());
X509Certificate x509Certificate = (X509Certificate)cf.generateCertificate(finStream);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry("someAlias", x509Certificate);
TrustManagerFactory instance = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
instance.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, instance.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
Undertow.Builder builder = Undertow.builder();
builder.addHttpsListener(httpsPort, ipAddress, sslContext);
The app starts, can't see any errors or warnings until I try and hit an https endpoint where Chrome just shows _ERR_CONNECTION_CLOSED_. I turned on -Djavax.net.debug=all
to try and see whats going on:
%% Initialized: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, fatal error: 40: no cipher suites in common
javax.net.ssl.SSLHandshakeException: no cipher suites in common
%% Invalidated: [Session-12, SSL_NULL_WITH_NULL_NULL]
XNIO-1 task-12, SEND TLSv1.1 ALERT: fatal, description = handshake_failure
XNIO-1 task-12, WRITE: TLSv1.1 Alert, length = 2
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common
XNIO-1 I/O-2, called closeInbound()
XNIO-1 I/O-2, fatal: engine already closed. Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
2016-09-08 08:34:46,861 DEBUG [io] - UT005013: An IOException occurred
java.io.IOException: javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
I'm trying to come up with a pure java solution here. Something that is repeatable, in code and can be tested and checked in to source control. I want to avoid having to do any out-of-jvm machine level set up if possible.
After lots of hackery and reading, it seems like I need to use the keytool
and some combination of the certificate I was issued, along with the certificate chain I was issued, along with the root certificate and some/none/all of the intermediate letsencrypt certificates! Seriously?
I tried following the instructions here from the section titled "7.3.1.3. Using an existing Certificate" but only to end up with exactly the same error.
Any help would be greatly appreciated.