diff --git a/hal-core/src/se/hal/HalServer.java b/hal-core/src/se/hal/HalServer.java index e45ffe4e..dc349b7a 100644 --- a/hal-core/src/se/hal/HalServer.java +++ b/hal-core/src/se/hal/HalServer.java @@ -20,6 +20,8 @@ import zutil.plugin.PluginData; import zutil.plugin.PluginManager; import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; @@ -80,37 +82,46 @@ public class HalServer { // Initialize External HttpServer // ------------------------------------ - if (HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_PORT) && - HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_DOMAIN)) { - AcmeClient acme; - HttpServer tmpHttpServer = null; + if (HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_DOMAIN) && HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_PORT)) { + String externalServerUrl = "https://" + HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_DOMAIN) + + ":" + HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_PORT); - if ("dns".equals(HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE, ""))) { - acme = new AcmeClient(new HalAcmeDataStore(), new AcmeManualDnsChallengeFactory(), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING); - } else if ("http".equals(HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE, "http"))) { - tmpHttpServer = new HttpServer(80); - tmpHttpServer.start(); + HalAcmeDataStore acmeDataStore = new HalAcmeDataStore(); + X509Certificate certificate = acmeDataStore.getCertificate(); - acme = new AcmeClient(new HalAcmeDataStore(), new AcmeHttpChallengeFactory(tmpHttpServer), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING); - } else { - throw new IllegalArgumentException("Unknown config value for " + HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE + ": " + - HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE)); + if (!AcmeClient.isCertificateValid(certificate)) { + // Prepare ACME Client + AcmeClient acme; + HttpServer tmpHttpServer = null; + + if ("dns".equals(HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE, ""))) { + acme = new AcmeClient(acmeDataStore, new AcmeManualDnsChallengeFactory(), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING); + } else if ("http".equals(HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE, "http"))) { + tmpHttpServer = new HttpServer(80); + tmpHttpServer.start(); + + acme = new AcmeClient(acmeDataStore, new AcmeHttpChallengeFactory(tmpHttpServer), AcmeClient.ACME_SERVER_LETSENCRYPT_STAGING); + } else { + throw new IllegalArgumentException("Unknown config value for " + externalServerUrl); + } + + // Request certificate and start the external webserver + + acme.addDomain(HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_DOMAIN)); + acme.prepareRequest(); + certificate = acme.requestCertificate(); + acmeDataStore.storeCertificate(certificate); + + // Cleanup + if (tmpHttpServer != null) { + tmpHttpServer.close(); + } } - acme.addDomain(HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_DOMAIN)); - acme.prepareRequest(); - Certificate certificate = acme.requestCertificate(); - httpExternal = new HttpServer(HalContext.getIntegerProperty(CONFIG_HTTP_EXTERNAL_PORT), certificate); httpExternal.start(); - // Cleanup - - if ("http".equals(HalContext.getStringProperty(HalContext.CONFIG_HTTP_EXTERNAL_ACME_TYPE, "http"))) { - tmpHttpServer.close(); - } - - logger.info("External https server up and running at: https://" + HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_DOMAIN) + ":" + HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_PORT)); + logger.info("External https server up and running at: " + externalServerUrl); } else { logger.warning("Missing '" + CONFIG_HTTP_EXTERNAL_PORT + "' and '" + CONFIG_HTTP_EXTERNAL_DOMAIN + "' configuration, will not setup external http server."); return; diff --git a/hal-core/src/se/hal/util/HalAcmeDataStore.java b/hal-core/src/se/hal/util/HalAcmeDataStore.java index 461448e7..f5c23654 100644 --- a/hal-core/src/se/hal/util/HalAcmeDataStore.java +++ b/hal-core/src/se/hal/util/HalAcmeDataStore.java @@ -1,30 +1,53 @@ package se.hal.util; +import org.shredzone.acme4j.toolbox.AcmeUtils; import org.shredzone.acme4j.util.KeyPairUtils; import se.hal.HalContext; +import zutil.io.StringInputStream; import zutil.log.LogUtil; import zutil.net.acme.AcmeDataStore; import zutil.parser.Base64Decoder; import zutil.parser.Base64Encoder; import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; import java.security.KeyPair; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.logging.Level; import java.util.logging.Logger; public class HalAcmeDataStore implements AcmeDataStore { private static final Logger logger = LogUtil.getLogger(); - private static final String CONFIG_HTTP_EXTERNAL_USER_KEY = "hal_core.http_external_user_key"; - private static final String CONFIG_HTTP_EXTERNAL_DOMAIN_KEY = "hal_core.http_external_domain_key"; + private static final String CONFIG_HTTP_EXTERNAL_ACCOUNT_LOCATION = "hal_core.http_external_account_location"; + private static final String CONFIG_HTTP_EXTERNAL_ACCOUNT_KEY = "hal_core.http_external_account_key"; + private static final String CONFIG_HTTP_EXTERNAL_DOMAIN_KEY = "hal_core.http_external_domain_key"; + private static final String CONFIG_HTTP_EXTERNAL_CERTIFICATE = "hal_core.http_external_certificate"; + @Override - public KeyPair loadUserKeyPair() { - return loadKeyPair(CONFIG_HTTP_EXTERNAL_USER_KEY); + public URL getAccountLocation() { + try { + if (HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_ACCOUNT_LOCATION)) + return new URL(HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_ACCOUNT_LOCATION)); + } catch (MalformedURLException e) { + logger.log(Level.SEVERE, "Unable to create account URL.", e); + } + + return null; } @Override - public KeyPair loadDomainKeyPair() { + public KeyPair getAccountKeyPair() { + return loadKeyPair(CONFIG_HTTP_EXTERNAL_ACCOUNT_KEY); + } + + @Override + public KeyPair getDomainKeyPair() { return loadKeyPair(CONFIG_HTTP_EXTERNAL_DOMAIN_KEY); } @@ -43,8 +66,9 @@ public class HalAcmeDataStore implements AcmeDataStore { @Override - public void storeUserKeyPair(KeyPair keyPair) { - storeKeyPair(keyPair, CONFIG_HTTP_EXTERNAL_USER_KEY); + public void storeAccountKeyPair(URL accountLocation, KeyPair accountKeyPair) { + HalContext.setProperty(CONFIG_HTTP_EXTERNAL_ACCOUNT_LOCATION, accountLocation.toString()); + storeKeyPair(accountKeyPair, CONFIG_HTTP_EXTERNAL_ACCOUNT_KEY); } @Override @@ -63,4 +87,30 @@ public class HalAcmeDataStore implements AcmeDataStore { logger.log(Level.SEVERE, "Was unable to store KeyPair to DB.", e); } } + + + @Override + public X509Certificate getCertificate() { + if (HalContext.containsProperty(CONFIG_HTTP_EXTERNAL_CERTIFICATE)) { + try { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) factory.generateCertificate( + new StringInputStream(HalContext.getStringProperty(CONFIG_HTTP_EXTERNAL_CERTIFICATE))); + return certificate; + } catch (CertificateException e) { + logger.log(Level.SEVERE, "Was unable to read certificate from DB.", e); + } + } + return null; + } + + @Override + public void storeCertificate(X509Certificate certificate) { + try (StringWriter out = new StringWriter()) { + AcmeUtils.writeToPem(certificate.getEncoded(), AcmeUtils.PemLabel.CERTIFICATE, out); + HalContext.setProperty(CONFIG_HTTP_EXTERNAL_CERTIFICATE, out.toString()); + } catch (IOException | CertificateEncodingException e) { + logger.log(Level.SEVERE, "Was unable to store certificate to DB.", e); + } + } }