summaryrefslogtreecommitdiffstats
path: root/jdisc_http_service
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2017-10-06 17:11:45 +0200
committerBjørn Christian Seime <bjorncs@oath.com>2017-10-09 10:54:43 +0200
commit002861a2b09d6e1899bebda11eeeac66c164b82a (patch)
tree6949d7af29b57fa2099d464933c5c4b69ac3c83b /jdisc_http_service
parent37948412c0e3d0170250989a886c44b274e34fc9 (diff)
Open-source PEM keystore for JDisc
Diffstat (limited to 'jdisc_http_service')
-rw-r--r--jdisc_http_service/pom.xml10
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java13
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreFactory.java17
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java316
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java20
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java53
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/package-info.java5
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java19
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java95
-rw-r--r--jdisc_http_service/src/test/resources/pem/test.crt88
-rw-r--r--jdisc_http_service/src/test/resources/pem/test.key27
11 files changed, 597 insertions, 66 deletions
diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml
index 9da06709533..d8c0b0bc29c 100644
--- a/jdisc_http_service/pom.xml
+++ b/jdisc_http_service/pom.xml
@@ -16,6 +16,16 @@
<name>${project.artifactId}</name>
<dependencies>
<dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>provided</scope>
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
index d11b8f0516b..96180f48229 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
@@ -10,14 +10,14 @@ import com.yahoo.jdisc.http.ConnectorConfig.Ssl.PemKeyStore;
import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.ssl.ReaderForPath;
import com.yahoo.jdisc.http.ssl.SslKeyStore;
-import com.yahoo.jdisc.http.ssl.SslKeyStoreFactory;
+import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.ConnectionFactory;
-import org.eclipse.jetty.server.ServerConnectionStatistics;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnectionStatistics;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -55,13 +55,11 @@ public class ConnectorFactory {
private final static Logger log = Logger.getLogger(ConnectorFactory.class.getName());
private final ConnectorConfig connectorConfig;
- private final SslKeyStoreFactory sslKeyStoreFactory;
private final SecretStore secretStore;
@Inject
- public ConnectorFactory(ConnectorConfig connectorConfig, SslKeyStoreFactory sslKeyStoreFactory, SecretStore secretStore) {
+ public ConnectorFactory(ConnectorConfig connectorConfig, SecretStore secretStore) {
this.connectorConfig = connectorConfig;
- this.sslKeyStoreFactory = sslKeyStoreFactory;
this.secretStore = secretStore;
if (connectorConfig.ssl().enabled())
@@ -254,8 +252,9 @@ public class ConnectorFactory {
try (KeyStoreReaderForPath certificateReader = new KeyStoreReaderForPath(pemKeyStore.certificatePath());
KeyStoreReaderForPath keyReader = new KeyStoreReaderForPath(pemKeyStore.keyPath())) {
- SslKeyStore keyStore = sslKeyStoreFactory.createKeyStore(certificateReader.readerForPath,
- keyReader.readerForPath);
+ SslKeyStore keyStore = new PemSslKeyStore(
+ new com.yahoo.jdisc.http.ssl.pem.PemKeyStore.KeyStoreLoadParameter(
+ certificateReader.readerForPath, keyReader.readerForPath));
return keyStore.loadJavaKeyStore();
} catch (Exception e) {
throw new RuntimeException("Failed setting up key store for " + pemKeyStore.keyPath() + ", " + pemKeyStore.certificatePath(), e);
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreFactory.java
deleted file mode 100644
index 2d659df1cce..00000000000
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreFactory.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.jdisc.http.ssl;
-
-import java.nio.file.Paths;
-
-/**
- * A factory for SSL key stores.
- *
- * @author bratseth
- */
-public interface SslKeyStoreFactory {
-
- SslKeyStore createKeyStore(ReaderForPath certificateFile, ReaderForPath keyFile);
-
- SslKeyStore createTrustStore(ReaderForPath certificateFile);
-
-}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java
new file mode 100644
index 00000000000..21272f202ea
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java
@@ -0,0 +1,316 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl.pem;
+
+import com.google.common.base.Preconditions;
+import com.yahoo.jdisc.http.ssl.ReaderForPath;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMException;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStore.LoadStoreParameter;
+import java.security.KeyStore.ProtectionParameter;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+
+import static com.yahoo.jdisc.http.server.jetty.Exceptions.throwUnchecked;
+
+/**
+ * Exposes keys and certificates from unencrypted PEM keystore.
+ *
+ * @author Tony Vaagenes
+ * @author bjorncs
+ */
+public class PemKeyStore extends KeyStoreSpi {
+
+ private static String KEY_ALIAS = "KEY";
+
+ static List<String> aliases = Collections.emptyList();
+ static Map<String, String> attributes = Collections.emptyMap();
+ private static final BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
+
+ @GuardedBy("this")
+ private StoreRole storeRole;
+ @GuardedBy("this")
+ private Key privateKey;
+ @GuardedBy("this")
+ private final Map<String, Certificate> aliasToCertificate = new LinkedHashMap<>();
+
+
+ public PemKeyStore() {}
+
+
+ /**
+ * The user is responsible for closing any readers given in the parameter.
+ */
+ @Override
+ public synchronized void engineLoad(LoadStoreParameter parameter) throws IOException {
+ if (storeRole != null)
+ throw new IllegalStateException("Already initialized.");
+
+ if (parameter instanceof KeyStoreLoadParameter) {
+ storeRole = new KeyStoreRole();
+ loadKeyStore((KeyStoreLoadParameter) parameter);
+ } else if (parameter instanceof TrustStoreLoadParameter) {
+ storeRole = new TrustStoreRole();
+ loadTrustStore((TrustStoreLoadParameter) parameter);
+ } else {
+ throw new IllegalArgumentException("Expected key store or trust store load parameter, got " + parameter.getClass());
+ }
+ }
+
+ private void loadTrustStore(TrustStoreLoadParameter parameter) throws IOException {
+ withPemParser(parameter.certificateReader, this::loadCertificates);
+ }
+
+ private void loadKeyStore(KeyStoreLoadParameter parameter) throws IOException{
+ withPemParser(parameter.keyReader, this::loadPrivateKey);
+ withPemParser(parameter.certificateReader, this::loadCertificates);
+ }
+
+ private static void withPemParser(ReaderForPath reader, Consumer<PEMParser> f) throws IOException {
+ try {
+ //parser.close() will close the underlying reader,
+ //which we want to avoid.
+ //See engineLoad comment.
+ PEMParser parser = new PEMParser(reader.reader);
+ f.accept(parser);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed loading pem key store " + reader.path, e);
+ }
+ }
+
+ private void loadPrivateKey(PEMParser parser) {
+ try {
+ Object object = parser.readObject();
+ PrivateKeyInfo privateKeyInfo;
+ if (object instanceof PEMKeyPair) { // Legacy PKCS1
+ privateKeyInfo = ((PEMKeyPair) object).getPrivateKeyInfo();
+ } else if (object instanceof PrivateKeyInfo) { // PKCS8
+ privateKeyInfo = (PrivateKeyInfo) object;
+ } else {
+ throw new UnsupportedOperationException(
+ "Expected " + PrivateKeyInfo.class + " or " + PEMKeyPair.class + ", got " + object.getClass());
+ }
+
+ Object nextObject = parser.readObject();
+ if (nextObject != null) {
+ throw new UnsupportedOperationException(
+ "Expected a single private key, but found a second element " + nextObject.getClass());
+ }
+
+ setPrivateKey(privateKeyInfo);
+ } catch (Exception e) {
+ throw throwUnchecked(e);
+ }
+ }
+
+ private synchronized void setPrivateKey(PrivateKeyInfo privateKey) throws PEMException {
+ JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(bouncyCastleProvider);
+ this.privateKey = converter.getPrivateKey(privateKey);
+ }
+
+ private void loadCertificates(PEMParser parser) {
+ try {
+ Object pemObject;
+ while ((pemObject = parser.readObject()) != null) {
+ addCertificate(pemObject);
+ }
+
+ if (aliasToCertificate.isEmpty())
+ throw new RuntimeException("No certificates available");
+ } catch (Exception e) {
+ throw throwUnchecked(e);
+ }
+ }
+
+ private synchronized void addCertificate(Object pemObject) throws CertificateException {
+ if (pemObject instanceof X509CertificateHolder) {
+ JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(bouncyCastleProvider);
+ String alias = "cert-" + aliasToCertificate.size();
+ aliasToCertificate.put(alias, converter.getCertificate((X509CertificateHolder) pemObject));
+ } else {
+ throw new UnsupportedOperationException("Expected X509 certificate, got " + pemObject.getClass());
+ }
+ }
+
+ @Override
+ public synchronized Enumeration<String> engineAliases() {
+ return Collections.enumeration(storeRole.engineAliases());
+
+ }
+
+ @Override
+ public synchronized boolean engineIsKeyEntry(String alias) {
+ return KEY_ALIAS.equals(alias);
+ }
+
+ @Override
+ public synchronized Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
+ Preconditions.checkArgument(KEY_ALIAS.equals(alias));
+ return privateKey;
+ }
+
+ @Override
+ public synchronized boolean engineIsCertificateEntry(String alias) {
+ return aliasToCertificate.containsKey(alias);
+ }
+
+
+ @Override
+ public synchronized Certificate engineGetCertificate(String alias) {
+ return aliasToCertificate.get(alias);
+ }
+
+ @Override
+ public synchronized Certificate[] engineGetCertificateChain(String alias) {
+ Preconditions.checkArgument(KEY_ALIAS.equals(alias));
+ return aliasToCertificate.values().toArray(new Certificate[aliasToCertificate.size()]);
+ }
+
+
+ @Override
+ public synchronized boolean engineContainsAlias(String alias) {
+ return storeRole.engineContainsAlias(alias);
+ }
+
+ @Override
+ public synchronized int engineSize() {
+ return storeRole.engineSize();
+ }
+
+ @Override
+ public synchronized String engineGetCertificateAlias(final Certificate certificate) {
+ for (Entry<String, Certificate> entry : aliasToCertificate.entrySet()) {
+ if (entry.getValue() == certificate)
+ return entry.getKey();
+ }
+
+ return null;
+ }
+
+ @Override
+ public synchronized Date engineGetCreationDate(String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void engineDeleteEntry(String alias) throws KeyStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public synchronized void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public synchronized void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
+ throw new UnsupportedOperationException();
+ }
+
+ private interface StoreRole {
+ Collection<String> engineAliases();
+ boolean engineContainsAlias(String alias);
+ int engineSize();
+ }
+
+ private class KeyStoreRole implements StoreRole {
+ @Override
+ public Collection<String> engineAliases() {
+ return Collections.singletonList(KEY_ALIAS);
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias) {
+ return KEY_ALIAS.equals(alias);
+ }
+
+ @Override
+ public int engineSize() {
+ return 1;
+ }
+ }
+
+ private class TrustStoreRole implements StoreRole{
+ @Override
+ public Collection<String> engineAliases() {
+ return aliasToCertificate.keySet();
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias) {
+ return aliasToCertificate.containsKey(alias);
+ }
+
+ @Override
+ public int engineSize() {
+ return aliasToCertificate.size();
+ }
+ }
+
+ public static class PemLoadStoreParameter implements LoadStoreParameter {
+ private PemLoadStoreParameter() {}
+
+ @Override
+ public ProtectionParameter getProtectionParameter() {
+ return null;
+ }
+ }
+
+ public static final class KeyStoreLoadParameter extends PemLoadStoreParameter {
+ public final ReaderForPath certificateReader;
+ public final ReaderForPath keyReader;
+
+ public KeyStoreLoadParameter(ReaderForPath certificateReader, ReaderForPath keyReader) {
+ this.certificateReader = certificateReader;
+ this.keyReader = keyReader;
+ }
+ }
+
+ public static final class TrustStoreLoadParameter extends PemLoadStoreParameter {
+ public final ReaderForPath certificateReader;
+
+ public TrustStoreLoadParameter(ReaderForPath certificateReader) {
+ this.certificateReader = certificateReader;
+ }
+ }
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java
new file mode 100644
index 00000000000..c1fcf8c33bf
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java
@@ -0,0 +1,20 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl.pem;
+
+import java.security.Provider;
+
+/**
+ * @author Tony Vaagenes
+ */
+public class PemKeyStoreProvider extends Provider {
+
+ public static final String name = "PEMKeyStoreProvider";
+ public static final double version = 1;
+ public static final String description = "Provides PEM keystore support";
+
+ public PemKeyStoreProvider() {
+ super(name, version, description);
+ putService(new Service(this, "KeyStore", "PEM", PemKeyStore. class.getName(), PemKeyStore.aliases, PemKeyStore.attributes));
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java
new file mode 100644
index 00000000000..bf91f0eb259
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java
@@ -0,0 +1,53 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl.pem;
+
+import com.yahoo.jdisc.http.ssl.SslKeyStore;
+import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.KeyStoreLoadParameter;
+import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.PemLoadStoreParameter;
+import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.TrustStoreLoadParameter;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.cert.CertificateException;
+
+/**
+ * Responsible for creating pem key stores.
+ *
+ * @author Tony Vaagenes
+ */
+public class PemSslKeyStore extends SslKeyStore {
+
+ static {
+ Security.addProvider(new PemKeyStoreProvider());
+ }
+
+ private static final String keyStoreType = "PEM";
+ private final PemLoadStoreParameter loadParameter;
+ private KeyStore keyStore;
+
+ public PemSslKeyStore(KeyStoreLoadParameter loadParameter) {
+ this.loadParameter = loadParameter;
+ }
+
+ public PemSslKeyStore(TrustStoreLoadParameter loadParameter) {
+ this.loadParameter = loadParameter;
+ }
+
+ @Override
+ public KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+ if (getKeyStorePassword().isPresent()) {
+ throw new UnsupportedOperationException("PEM key store with password is currently not supported. Please file a feature request.");
+ }
+
+ //cached since Reader(in loadParameter) can only be used one time.
+ if (keyStore == null) {
+ keyStore = KeyStore.getInstance(keyStoreType);
+ keyStore.load(loadParameter);
+ }
+ return keyStore;
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/package-info.java
new file mode 100644
index 00000000000..4358f05a90e
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+package com.yahoo.jdisc.http.ssl.pem;
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
index 5e4614a7804..1200a06be2c 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
@@ -11,9 +11,6 @@ import com.yahoo.jdisc.http.ConnectorConfig.Builder;
import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.server.jetty.ConnectorFactory;
import com.yahoo.jdisc.http.server.jetty.TestDrivers;
-import com.yahoo.jdisc.http.ssl.ReaderForPath;
-import com.yahoo.jdisc.http.ssl.SslKeyStore;
-import com.yahoo.jdisc.http.ssl.SslKeyStoreFactory;
/**
* Guice module for test ConnectorFactories
@@ -49,21 +46,7 @@ public class ConnectorFactoryRegistryModule implements Module {
private static class StaticKeyDbConnectorFactory extends ConnectorFactory {
public StaticKeyDbConnectorFactory(ConnectorConfig connectorConfig) {
- super(connectorConfig, new ThrowingSslKeyStoreFactory(), new MockSecretStore());
- }
-
- }
-
- private static final class ThrowingSslKeyStoreFactory implements SslKeyStoreFactory {
-
- @Override
- public SslKeyStore createKeyStore(ReaderForPath certificateFile, ReaderForPath keyFile) {
- throw new UnsupportedOperationException("A SSL key store factory component is not available");
- }
-
- @Override
- public SslKeyStore createTrustStore(ReaderForPath certificateFile) {
- throw new UnsupportedOperationException("A SSL key store factory component is not available");
+ super(connectorConfig, new MockSecretStore());
}
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
index 49656775dc0..7a03d805864 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
@@ -1,29 +1,36 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.server.jetty;
+import com.google.common.collect.ImmutableMap;
import com.yahoo.jdisc.Metric;
-import com.yahoo.jdisc.http.CertificateStore;
import com.yahoo.jdisc.http.ConnectorConfig;
-import com.yahoo.jdisc.http.HttpRequest;
import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.ssl.ReaderForPath;
+import com.yahoo.jdisc.http.ssl.SslContextFactory;
import com.yahoo.jdisc.http.ssl.SslKeyStore;
-import com.yahoo.jdisc.http.ssl.SslKeyStoreFactory;
+import com.yahoo.jdisc.http.ssl.pem.PemKeyStore;
+import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.testng.annotations.Test;
+import javax.net.ssl.SSLContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.Map;
-import static com.yahoo.jdisc.http.ConnectorConfig.*;
+import static com.yahoo.jdisc.http.ConnectorConfig.Ssl;
import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.JKS;
import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.PEM;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -44,8 +51,7 @@ public class ConnectorFactoryTest {
new Ssl.PemKeyStore.Builder()
.keyPath("nonEmpty"))));
- ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSslKeyStoreFactory(),
- new ThrowingSecretStore());
+ ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSecretStore());
}
@Test(expectedExceptions = IllegalArgumentException.class)
@@ -57,8 +63,7 @@ public class ConnectorFactoryTest {
.keyStoreType(PEM)
.keyStorePath("nonEmpty")));
- ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSslKeyStoreFactory(),
- new ThrowingSecretStore());
+ ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSecretStore());
}
@Test
@@ -66,7 +71,6 @@ public class ConnectorFactoryTest {
Server server = new Server();
try {
ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(new ConnectorConfig.Builder()),
- new ThrowingSslKeyStoreFactory(),
new ThrowingSecretStore());
ConnectorFactory.JDiscServerConnector connector =
(ConnectorFactory.JDiscServerConnector)factory.createConnector(new DummyMetric(), server, null, Collections.emptyMap());
@@ -94,7 +98,7 @@ public class ConnectorFactoryTest {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(0));
- ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(new ConnectorConfig.Builder()), new ThrowingSslKeyStoreFactory(), new ThrowingSecretStore());
+ ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(new ConnectorConfig.Builder()), new ThrowingSecretStore());
ConnectorFactory.JDiscServerConnector connector = (ConnectorFactory.JDiscServerConnector) factory.createConnector(new DummyMetric(), server, serverChannel, Collections.emptyMap());
server.addConnector(connector);
server.setHandler(new HelloWorldHandler());
@@ -113,6 +117,63 @@ public class ConnectorFactoryTest {
}
}
+ @Test
+ public void pre_bound_keystore_file_channels_are_used() throws Exception {
+ Path pemKeyStoreDirectory = Paths.get("src/test/resources/pem/");
+
+ Path certificateFile = pemKeyStoreDirectory.resolve("test.crt");
+ Path privateKeyFile = pemKeyStoreDirectory.resolve("test.key");
+
+ Server server = new Server();
+ try {
+ ServerSocketChannel serverChannel = ServerSocketChannel.open();
+ serverChannel.socket().bind(new InetSocketAddress(0));
+
+ String fakeCertificatePath = "ensure-certificate-path-is-not-used-to-open-the-file";
+ String fakeKeyPath = "ensure-key-path-is-not-used-to-open-the-file";
+
+ ConnectorConfig.Builder builder = new ConnectorConfig.Builder();
+ builder.ssl(
+ new Ssl.Builder().
+ enabled(true).
+ keyStoreType(PEM).
+ pemKeyStore(new Ssl.PemKeyStore.Builder().
+ certificatePath(fakeCertificatePath).
+ keyPath(fakeKeyPath)));
+
+ FileChannel certificateChannel = FileChannel.open(certificateFile, StandardOpenOption.READ);
+ FileChannel privateKeyChannel = FileChannel.open(privateKeyFile, StandardOpenOption.READ);
+
+ Map<Path, FileChannel> keyStoreChannels = ImmutableMap.<Path, FileChannel>builder().
+ put(Paths.get(fakeCertificatePath), certificateChannel).
+ put(Paths.get(fakeKeyPath), privateKeyChannel).
+ build();
+
+
+ ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(builder), new ThrowingSecretStore());
+ ConnectorFactory.JDiscServerConnector connector = (ConnectorFactory.JDiscServerConnector) factory.createConnector(new DummyMetric(), server, serverChannel, keyStoreChannels);
+ server.addConnector(connector);
+ server.setHandler(new HelloWorldHandler());
+ server.start();
+
+ SslKeyStore trustStore = new PemSslKeyStore(
+ new PemKeyStore.TrustStoreLoadParameter(
+ new ReaderForPath(Files.newBufferedReader(certificateFile), certificateFile)));
+
+ SSLContext clientSslContext = SslContextFactory.newInstanceFromTrustStore(trustStore).getServerSSLContext();
+ SimpleHttpClient client = new SimpleHttpClient(clientSslContext, connector.getLocalPort(), false);
+ SimpleHttpClient.RequestExecutor ex = client.newGet("/ignored");
+ SimpleHttpClient.ResponseValidator val = ex.execute();
+ val.expectContent(equalTo("Hello world"));
+ } finally {
+ try {
+ server.stop();
+ } catch (Exception e) {
+ //ignore
+ }
+ }
+ }
+
private static class HelloWorldHandler extends AbstractHandler {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
@@ -139,20 +200,6 @@ public class ConnectorFactoryTest {
private static class DummyContext implements Metric.Context {
}
- private static final class ThrowingSslKeyStoreFactory implements SslKeyStoreFactory {
-
- @Override
- public SslKeyStore createKeyStore(ReaderForPath certificateFile, ReaderForPath keyFile) {
- throw new UnsupportedOperationException("A SSL key store factory component is not available");
- }
-
- @Override
- public SslKeyStore createTrustStore(ReaderForPath certificateFile) {
- throw new UnsupportedOperationException("A SSL key store factory component is not available");
- }
-
- }
-
private static final class ThrowingSecretStore implements SecretStore {
@Override
diff --git a/jdisc_http_service/src/test/resources/pem/test.crt b/jdisc_http_service/src/test/resources/pem/test.crt
new file mode 100644
index 00000000000..fb132a454e2
--- /dev/null
+++ b/jdisc_http_service/src/test/resources/pem/test.crt
@@ -0,0 +1,88 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4660 (0x1234)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=US, ST=California, L=Sunnyvale, O=Yahoo Inc., OU=Information Technology, CN=darkmoist-lm.trondheim.corp.yahoo.com
+ Validity
+ Not Before: Sep 2 10:32:37 2014 GMT
+ Not After : Aug 7 10:32:37 2019 GMT
+ Subject: C=US, ST=California, O=Yahoo Inc., OU=Information Technology, CN=darkmoist-lm.trondheim.corp.yahoo.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d4:cd:7b:e0:29:9e:cd:01:21:26:ae:60:e3:3a:
+ 0d:19:6e:b1:ae:49:f3:9f:37:45:7d:77:95:b0:6d:
+ 63:ef:b3:7c:e8:29:15:ad:8b:b6:40:b6:c5:12:1e:
+ 7e:c9:6b:75:15:2f:31:30:2a:6b:1c:00:bb:b3:a7:
+ 31:ab:84:e5:32:52:1d:3e:bb:7d:71:f0:ff:9f:21:
+ b8:9d:cb:6a:65:34:de:cc:22:81:a2:53:0f:7b:9c:
+ d8:a9:b6:a5:3d:8b:31:5e:b1:cb:da:51:12:0e:68:
+ 64:6a:2e:4a:c1:50:ee:0c:6d:a1:30:6b:3f:1c:97:
+ 37:76:fd:03:8a:1a:55:1d:7e:2d:14:fb:24:09:4e:
+ a6:04:cf:f8:f9:bb:01:78:f5:7f:c7:b5:3a:52:76:
+ ce:4d:79:4f:83:59:84:90:a5:ef:58:25:bd:95:d6:
+ f5:90:bf:fa:8b:4b:9f:d1:63:d1:75:2c:c8:00:de:
+ 2d:72:0e:a6:d8:48:ed:36:87:63:21:7d:77:d3:93:
+ 9e:12:f0:69:11:a1:90:63:2f:f9:6b:5d:a6:d2:65:
+ 91:7c:ad:5d:6a:4f:63:79:21:a4:7b:7d:8c:2c:a4:
+ 48:3c:d1:9e:a7:66:6c:d8:9c:ce:c9:54:fa:0e:1f:
+ fd:28:25:7a:ea:e7:4c:2c:86:11:45:a5:dc:b7:5e:
+ fa:97
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 64:31:6B:9A:8E:FF:27:ED:E2:F4:4F:30:D5:A6:0D:45:9C:29:D3:81
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/ST=California/L=Sunnyvale/O=Yahoo Inc./OU=Information Technology/CN=darkmoist-lm.trondheim.corp.yahoo.com
+ serial:9B:F0:C8:38:83:81:2B:C3
+
+ Signature Algorithm: md5WithRSAEncryption
+ 81:82:99:e9:b1:04:d3:f4:49:c3:b4:49:8a:0a:9a:49:29:51:
+ d3:f0:03:0e:2f:d5:7a:2c:44:65:74:15:de:36:41:e3:d3:c3:
+ 69:ff:99:0a:dc:fb:a7:26:c2:3f:a0:40:a6:51:32:47:02:d8:
+ c5:35:ac:f6:e5:c2:65:7a:90:cc:a1:58:4f:1e:8b:7c:e7:77:
+ 07:c2:15:41:38:0f:f7:ca:bd:fb:3e:22:27:0d:90:b5:6f:a7:
+ 2c:10:1c:31:d6:9b:c0:53:db:a8:65:5a:06:97:1a:62:4e:e5:
+ 7f:98:57:8a:60:d6:db:f8:57:ca:ea:f0:44:d0:9e:4c:bb:48:
+ 1c:b4:5f:0f:b4:26:c7:f1:ca:61:f3:7b:21:03:4f:f2:e6:46:
+ 04:ea:88:7d:0f:41:24:32:a5:07:57:3c:6f:e1:a6:ca:12:b0:
+ c1:8c:50:a7:e1:68:80:9b:63:83:e2:de:e5:3c:30:2e:06:12:
+ 66:4c:6c:f8:55:88:62:00:1e:72:4b:ea:78:88:0c:31:95:e5:
+ 38:fa:78:78:a8:e9:80:3f:42:63:e6:37:f7:4b:47:ff:38:0a:
+ 3e:83:7c:ef:70:ea:43:24:06:45:51:3e:f5:ef:6e:ef:99:bc:
+ 47:70:3f:8b:d0:8f:a8:e7:50:3f:c7:94:27:fb:24:bf:c4:8c:
+ db:a5:86:6c
+-----BEGIN CERTIFICATE-----
+MIIEvjCCA6agAwIBAgICEjQwDQYJKoZIhvcNAQEEBQAwgZwxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5ueXZhbGUxEzARBgNV
+BAoTCllhaG9vIEluYy4xHzAdBgNVBAsTFkluZm9ybWF0aW9uIFRlY2hub2xvZ3kx
+LjAsBgNVBAMTJWRhcmttb2lzdC1sbS50cm9uZGhlaW0uY29ycC55YWhvby5jb20w
+HhcNMTQwOTAyMTAzMjM3WhcNMTkwODA3MTAzMjM3WjCBiDELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTCkNhbGlmb3JuaWExEzARBgNVBAoTCllhaG9vIEluYy4xHzAdBgNV
+BAsTFkluZm9ybWF0aW9uIFRlY2hub2xvZ3kxLjAsBgNVBAMTJWRhcmttb2lzdC1s
+bS50cm9uZGhlaW0uY29ycC55YWhvby5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDUzXvgKZ7NASEmrmDjOg0ZbrGuSfOfN0V9d5WwbWPvs3zoKRWt
+i7ZAtsUSHn7Ja3UVLzEwKmscALuzpzGrhOUyUh0+u31x8P+fIbidy2plNN7MIoGi
+Uw97nNiptqU9izFescvaURIOaGRqLkrBUO4MbaEwaz8clzd2/QOKGlUdfi0U+yQJ
+TqYEz/j5uwF49X/HtTpSds5NeU+DWYSQpe9YJb2V1vWQv/qLS5/RY9F1LMgA3i1y
+DqbYSO02h2MhfXfTk54S8GkRoZBjL/lrXabSZZF8rV1qT2N5IaR7fYwspEg80Z6n
+ZmzYnM7JVPoOH/0oJXrq50wshhFFpdy3XvqXAgMBAAGjggEaMIIBFjAJBgNVHRME
+AjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0
+ZTAdBgNVHQ4EFgQUZDFrmo7/J+3i9E8w1aYNRZwp04EwgbsGA1UdIwSBszCBsKGB
+oqSBnzCBnDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNV
+BAcTCVN1bm55dmFsZTETMBEGA1UEChMKWWFob28gSW5jLjEfMB0GA1UECxMWSW5m
+b3JtYXRpb24gVGVjaG5vbG9neTEuMCwGA1UEAxMlZGFya21vaXN0LWxtLnRyb25k
+aGVpbS5jb3JwLnlhaG9vLmNvbYIJAJvwyDiDgSvDMA0GCSqGSIb3DQEBBAUAA4IB
+AQCBgpnpsQTT9EnDtEmKCppJKVHT8AMOL9V6LERldBXeNkHj08Np/5kK3PunJsI/
+oECmUTJHAtjFNaz25cJlepDMoVhPHot853cHwhVBOA/3yr37PiInDZC1b6csEBwx
+1pvAU9uoZVoGlxpiTuV/mFeKYNbb+FfK6vBE0J5Mu0gctF8PtCbH8cph83shA0/y
+5kYE6oh9D0EkMqUHVzxv4abKErDBjFCn4WiAm2OD4t7lPDAuBhJmTGz4VYhiAB5y
+S+p4iAwxleU4+nh4qOmAP0Jj5jf3S0f/OAo+g3zvcOpDJAZFUT71727vmbxHcD+L
+0I+o51A/x5Qn+yS/xIzbpYZs
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/jdisc_http_service/src/test/resources/pem/test.key b/jdisc_http_service/src/test/resources/pem/test.key
new file mode 100644
index 00000000000..91335afc9a7
--- /dev/null
+++ b/jdisc_http_service/src/test/resources/pem/test.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1M174CmezQEhJq5g4zoNGW6xrknznzdFfXeVsG1j77N86CkV
+rYu2QLbFEh5+yWt1FS8xMCprHAC7s6cxq4TlMlIdPrt9cfD/nyG4nctqZTTezCKB
+olMPe5zYqbalPYsxXrHL2lESDmhkai5KwVDuDG2hMGs/HJc3dv0DihpVHX4tFPsk
+CU6mBM/4+bsBePV/x7U6UnbOTXlPg1mEkKXvWCW9ldb1kL/6i0uf0WPRdSzIAN4t
+cg6m2EjtNodjIX1305OeEvBpEaGQYy/5a12m0mWRfK1dak9jeSGke32MLKRIPNGe
+p2Zs2JzOyVT6Dh/9KCV66udMLIYRRaXct176lwIDAQABAoIBAQCd9opls391nckF
+9ZtmEMl4f3rVbX+ySE0E/afX9tugKxQlIZo94N/A2esfsBNdYK7gss9IebRYbRLo
+IMv2Dgg0ek/LKVHNKqAVd+qa90xbJAvebB7eZ9muYJdUI4g1TwWuzTwNKvDEUSl4
+yDQlm/WYtCha0MFgb790TAw8j59u68f2qPJVtIQ+EAEB7dTvIt7tOHV9dlcNWL/8
+uPx3NXsi4Nq0m06zF6TTjUmvQjks+Ai/GHLWeNwUPBTfmR7QrCYLFlyuECYt6p2J
+aJMwGYhVORlRMa3LCxkk/7s/Ebxif3qtjZBe19NdePa8zxX/kKDE4/L1LUXaC2aN
++l8rMIxBAoGBAOoX6yOghHC6nRMkVJaPIEaBbyjV/2yeFo56Xe6sGnFGGgv4ZDRB
+0DAiiCKYKenfAcijZ+YmHazSIYI+EB9A6PK1j67JudyG55wtx85sBAGag9pJT0Ep
+lYfWfJZThgTm1kQobp3oblQo6ZAH8NLeH2084OFhoMiQr8z+ObGfDNr7AoGBAOi3
+guH6tXGE3I8Z0OPrhJLRM5Kno5pqDQOFi+85cm5+AcV06wM+Je33K9LJGQYHJ04N
+LJii5aOG+Vs/n2SplYl/3u52fEia+N9u1sc4iXeBi9e7COidjFPeIAX0CI9gGIt7
+x2sa8/WMZiQTqa9MbQF4psYcyWyK3WDQfWbNo8wVAoGBAOS15bhzNbJlwN1Y24QV
+5jS8dPxyyBE5C1S83VU4tMUC9qPHVS9xNZQxyMvz2s9yYG3EqNhFWSzmSHLVbC78
+3htzpCPjV0HMVDFU0SguhGOEsVnt0g8aL8v9lM/SXtgfKCyDTD/fPRvgtQFRoMqE
+1jOGDThmiA4svnYL1BZkDM1NAoGBANH6CvlVmnO0GsJv28BbGILUikEwS3kfaWCd
+Fhci8XJq9bQxe3+wis69b+hAFPkQaVGOp4eNq8AyIDpKHMraDRhErWTiud9VHWuU
++exFwht3YzOjCjXBOgXObXyRpUugvGTWqaelaSxMozi4GSoXvl9OesRU4xWx8m/R
+juS8dafFAoGASRntDyZBQR58yGDVGTIK6QDIRdmN6QcBQS0wiCSsHl4b9Q7Ve/em
+/qRf7xMdzFejAWkB2LD68HbskzVQmAN0VCPMTZjKsPPmxxgcXfIdghBfWNhXXzal
+KV1kiIb8cHHdXZxGRZpOQFCs2oOrQE99jMgYtVmuIXEErz9pssaEhxo=
+-----END RSA PRIVATE KEY-----