diff options
Diffstat (limited to 'jdisc_http_service')
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----- |