diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/pebble/PebbleHttpConnector.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/pebble/PebbleHttpConnector.java index 640bc486..2b9c3af9 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/pebble/PebbleHttpConnector.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/pebble/PebbleHttpConnector.java @@ -20,7 +20,9 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; @@ -28,12 +30,15 @@ import org.shredzone.acme4j.connector.HttpConnector; import org.shredzone.acme4j.connector.NetworkSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * {@link HttpConnector} to be used for Pebble. Pebble uses a static, self-signed SSL * certificate. */ public class PebbleHttpConnector extends HttpConnector { + private static final Logger LOG = LoggerFactory.getLogger(PebbleHttpConnector.class); private static final AtomicReference SSL_CONTEXT_REF = new AtomicReference<>(); public PebbleHttpConnector(NetworkSettings settings) { @@ -53,9 +58,11 @@ public HttpClient.Builder createClientBuilder() { */ protected SSLContext createSSLContext() { if (SSL_CONTEXT_REF.get() == null) { - try (var in = getClass().getResourceAsStream("/org/shredzone/acme4j/provider/pebble/pebble.truststore")) { - var keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(in, "acme4j".toCharArray()); + try { + var keystore = readPemFile("/pebble.minica.pem") + .or(() -> readPemFile("/META-INF/pebble.minica.pem")) + .or(() -> readPemFile("/org/shredzone/acme4j/provider/pebble/pebble.minica.pem")) + .orElseThrow(() -> new RuntimeException("Could not find a Pebble root certificate")); var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keystore); @@ -63,12 +70,38 @@ protected SSLContext createSSLContext() { var sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); SSL_CONTEXT_REF.set(sslContext); - } catch (IOException | KeyStoreException | CertificateException - | NoSuchAlgorithmException | KeyManagementException ex) { + } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException ex) { throw new RuntimeException("Could not create truststore", ex); } } return Objects.requireNonNull(SSL_CONTEXT_REF.get()); } + /** + * Reads a PEM file from a resource, and returns a {@link KeyStore} that uses this + * certificate as root CA. + * + * @param resource + * Resource name + * @return A {@link KeyStore} if the resource could be read successfully, otherwise + * empty. + */ + private Optional readPemFile(String resource) { + try (var in = getClass().getResourceAsStream(resource)) { + if (in == null) { + return Optional.empty(); + } + var cf = CertificateFactory.getInstance("X.509"); + var cert = cf.generateCertificate(in); + var keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null, "acme4j".toCharArray()); + keystore.setCertificateEntry("pebble", cert); + return Optional.of(keystore); + } catch (IOException | KeyStoreException | CertificateException + | NoSuchAlgorithmException ex) { + LOG.error("Failed to read PEM from resource '{}'", resource, ex); + return Optional.empty(); + } + } + } diff --git a/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.minica.pem b/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.minica.pem new file mode 100644 index 00000000..a69a4c41 --- /dev/null +++ b/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.minica.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIIJOLbes8sTr4wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMTcx +MjA2MTk0MjEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyNGUyZGIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5WgZNoVJandj43kkLyU50vzCZ +alozvdRo3OFiKoDtmqKPNWRNO2hC9AUNxTDJco51Yc42u/WV3fPbbhSznTiOOVtn +Ajm6iq4I5nZYltGGZetGDOQWr78y2gWY+SG078MuOO2hyDIiKtVc3xiXYA+8Hluu +9F8KbqSS1h55yxZ9b87eKR+B0zu2ahzBCIHKmKWgc6N13l7aDxxY3D6uq8gtJRU0 +toumyLbdzGcupVvjbjDP11nl07RESDWBLG1/g3ktJvqIa4BWgU2HMh4rND6y8OD3 +Hy3H8MY6CElL+MOCbFJjWqhtOxeFyZZV9q3kYnk9CAuQJKMEGuN4GU6tzhW1AgMB +AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAF85v +d40HK1ouDAtWeO1PbnWfGEmC5Xa478s9ddOd9Clvp2McYzNlAFfM7kdcj6xeiNhF +WPIfaGAi/QdURSL/6C1KsVDqlFBlTs9zYfh2g0UXGvJtj1maeih7zxFLvet+fqll +xseM4P9EVJaQxwuK/F78YBt0tCNfivC6JNZMgxKF59h0FBpH70ytUSHXdz7FKwix +Mfn3qEb9BXSk0Q3prNV5sOV3vgjEtB4THfDxSz9z3+DepVnW3vbbqwEbkXdk3j82 +2muVldgOUgTwK8eT+XdofVdntzU/kzygSAtAQwLJfn51fS1GvEcYGBc1bDryIqmF +p9BI7gVKtWSZYegicA== +-----END CERTIFICATE----- diff --git a/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.truststore b/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.truststore deleted file mode 100644 index 103478a2..00000000 Binary files a/acme4j-client/src/main/resources/org/shredzone/acme4j/provider/pebble/pebble.truststore and /dev/null differ