knox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kris...@apache.org
Subject [knox] branch master updated: KNOX-1801 - Master secret is incorrectly assumed when a custom truststore is not specified when clientauth is enabled (#63)
Date Tue, 05 Mar 2019 20:17:25 GMT
This is an automated email from the ASF dual-hosted git repository.

krisden pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 1262230  KNOX-1801 - Master secret is incorrectly assumed when a custom truststore
is not specified when clientauth is enabled (#63)
1262230 is described below

commit 1262230a1d4b96d098524fd301fc2bb062415137
Author: Robert Levas <rlevas@apache.org>
AuthorDate: Tue Mar 5 15:17:21 2019 -0500

    KNOX-1801 - Master secret is incorrectly assumed when a custom truststore is not specified
when clientauth is enabled (#63)
---
 .../org/apache/knox/gateway/GatewayMessages.java   |   9 +
 .../org/apache/knox/gateway/GatewayServer.java     |   7 +-
 .../gateway/services/DefaultGatewayServices.java   |   1 -
 .../services/security/impl/JettySSLService.java    | 131 +++---
 .../security/impl/JettySSLServiceTest.java         | 478 +++++++++++++++++++++
 .../src/test/resources/keystores/readme.txt        |  40 ++
 .../{ => keystores}/server-truststore.jks          | Bin
 .../knox/gateway/services/security/SSLService.java |   8 +-
 8 files changed, 586 insertions(+), 88 deletions(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index 6f0ade5..74d662d 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -169,6 +169,12 @@ public interface GatewayMessages {
   @Message( level = MessageLevel.INFO, text = "Creating keystore for the gateway instance."
)
   void creatingKeyStoreForGateway();
 
+  @Message( level = MessageLevel.ERROR, text = "Unable to obtain the password for the keystore
for the gateway instance: {0}" )
+  void failedToGetPasswordForGatewayIdentityKeystore(Exception e);
+
+  @Message( level = MessageLevel.ERROR, text = "Unable to obtain the password for the gateway
identity key: {0}" )
+  void failedToGetPassphraseForGatewayIdentityKey(Exception e);
+
   @Message( level = MessageLevel.INFO, text = "Keystore for the gateway instance found -
no need to create one." )
   void keyStoreForGatewayFoundNotCreating();
 
@@ -178,6 +184,9 @@ public interface GatewayMessages {
   @Message( level = MessageLevel.INFO, text = "Credential store found for the cluster: {0}
- no need to create one." )
   void credentialStoreForClusterFoundNotCreating(String clusterName);
 
+  @Message( level = MessageLevel.ERROR, text = "Unable to obtain the password for the gateway
truststore using the alias {0}: {1}" )
+  void failedToGetPasswordForGatewayTruststore(String alias, Exception e);
+
   @Message( level = MessageLevel.DEBUG, text = "Received request: {0} {1}" )
   void receivedRequest( String method, String uri );
 
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index e4a9e76..e13c14c 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -41,6 +41,7 @@ import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.registry.ServiceRegistry;
+import org.apache.knox.gateway.services.security.AliasServiceException;
 import org.apache.knox.gateway.services.security.SSLService;
 import org.apache.knox.gateway.services.topology.TopologyService;
 import org.apache.knox.gateway.topology.Application;
@@ -377,7 +378,7 @@ public class GatewayServer {
   private Connector createConnector(final Server server,
       final GatewayConfig config, final int port, final String topologyName)
       throws IOException, CertificateException, NoSuchAlgorithmException,
-      KeyStoreException {
+      KeyStoreException, AliasServiceException {
 
     ServerConnector connector;
 
@@ -401,7 +402,7 @@ public class GatewayServer {
       httpsConfig.setSecurePort( connectorPort );
       httpsConfig.addCustomizer( new SecureRequestCustomizer() );
       SSLService ssl = services.getService(GatewayServices.SSL_SERVICE);
-      SslContextFactory sslContextFactory = (SslContextFactory)ssl.buildSslContextFactory(
config.getIdentityKeystorePath(), config.getIdentityKeystoreType(), config.getIdentityKeyAlias()
);
+      SslContextFactory sslContextFactory = (SslContextFactory)ssl.buildSslContextFactory(
config );
       connector = new ServerConnector( server, sslContextFactory, new HttpConnectionFactory(
httpsConfig ) );
     } else {
       connector = new ServerConnector( server );
@@ -693,7 +694,7 @@ public class GatewayServer {
     return errorHandler;
   }
 
-  private WebAppContext createWebAppContext( Topology topology, File warFile, String warPath
) throws IOException, ZipException, TransformerException, SAXException, ParserConfigurationException
{
+  private WebAppContext createWebAppContext( Topology topology, File warFile, String warPath
) {
     String topoName = topology.getName();
     WebAppContext context = new WebAppContext();
     String contextPath;
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
index 9bd669a..34ea43a 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
@@ -101,7 +101,6 @@ public class DefaultGatewayServices implements GatewayServices {
     JettySSLService ssl = new JettySSLService();
     ssl.setAliasService(alias);
     ssl.setKeystoreService(ks);
-    ssl.setMasterService(ms);
     ssl.init(config, options);
     services.put(SSL_SERVICE, ssl);
 
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/JettySSLService.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/JettySSLService.java
index 1e07823..180c1b8 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/JettySSLService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/JettySSLService.java
@@ -17,15 +17,7 @@
  */
 package org.apache.knox.gateway.services.security.impl;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
 import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
@@ -42,7 +34,6 @@ import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.AliasServiceException;
 import org.apache.knox.gateway.services.security.KeystoreService;
 import org.apache.knox.gateway.services.security.KeystoreServiceException;
-import org.apache.knox.gateway.services.security.MasterService;
 import org.apache.knox.gateway.services.security.SSLService;
 import org.apache.knox.gateway.util.X500PrincipalParser;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -53,21 +44,8 @@ public class JettySSLService implements SSLService {
   private static final String GATEWAY_CREDENTIAL_STORE_NAME = "__gateway";
   private static GatewayMessages log = MessagesFactory.get( GatewayMessages.class );
 
-  private MasterService ms;
   private KeystoreService ks;
   private AliasService as;
-  private List<String> sslIncludeCiphers;
-  private List<String> sslExcludeCiphers;
-  private List<String> sslExcludeProtocols;
-  private boolean clientAuthNeeded;
-  private boolean trustAllCerts;
-  private String truststorePath;
-  private String trustStoreType;
-  private boolean clientAuthWanted;
-
-  public void setMasterService(MasterService ms) {
-    this.ms = ms;
-  }
 
   public void setAliasService(AliasService as) {
     this.as = as;
@@ -95,7 +73,7 @@ public class JettySSLService implements SSLService {
         log.credentialStoreForGatewayFoundNotCreating();
       }
     } catch (KeystoreServiceException e) {
-      throw new ServiceLifecycleException("Keystore was not loaded properly - the provided
(or persisted) master secret may not match the password for the keystore.", e);
+      throw new ServiceLifecycleException("Keystore was not loaded properly - the provided
password may not match the password for the keystore.", e);
     }
 
     try {
@@ -108,9 +86,6 @@ public class JettySSLService implements SSLService {
         } catch (AliasServiceException e) {
           throw new ServiceLifecycleException("Error accessing credential store for the gateway.",
e);
         }
-        if (passphrase == null) {
-          passphrase = ms.getMasterSecret();
-        }
         ks.addSelfSignedCertForGateway(config.getIdentityKeyAlias(), passphrase);
       }
       else {
@@ -120,15 +95,6 @@ public class JettySSLService implements SSLService {
     } catch (KeystoreServiceException e) {
       throw new ServiceLifecycleException("The identity keystore was not loaded properly
- the provided password may not match the password for the keystore.", e);
     }
-
-    sslIncludeCiphers = config.getIncludedSSLCiphers();
-    sslExcludeCiphers = config.getExcludedSSLCiphers();
-    sslExcludeProtocols = config.getExcludedSSLProtocols();
-    clientAuthNeeded = config.isClientAuthNeeded();
-    clientAuthWanted = config.isClientAuthWanted();
-    truststorePath = config.getTruststorePath();
-    trustAllCerts = config.getTrustAllCerts();
-    trustStoreType = config.getTruststoreType();
   }
 
   private void logAndValidateCertificate(GatewayConfig config) throws ServiceLifecycleException
{
@@ -165,64 +131,74 @@ public class JettySSLService implements SSLService {
   }
 
   @Override
-  public Object buildSslContextFactory(String keystoreFileName, String keystoreType, String
alias) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException
{
+  public Object buildSslContextFactory(GatewayConfig config) throws AliasServiceException
{
+    String identityKeystorePath = config.getIdentityKeystorePath();
+    String identityKeystoreType = config.getIdentityKeystoreType();
+    String identityKeyAlias = config.getIdentityKeyAlias();
+
     SslContextFactory sslContextFactory = new SslContextFactory( true );
-    sslContextFactory.setCertAlias( alias );
-    sslContextFactory.setKeyStoreType(keystoreType);
-    sslContextFactory.setKeyStorePath(keystoreFileName);
-    char[] master = ms.getMasterSecret();
+    sslContextFactory.setCertAlias( identityKeyAlias );
+    sslContextFactory.setKeyStoreType(identityKeystoreType);
+    sslContextFactory.setKeyStorePath(identityKeystorePath );
 
-    char[] keystorePasswordChars = null;
+    char[] keystorePasswordChars;
     try {
       keystorePasswordChars = as.getGatewayIdentityKeystorePassword();
     } catch (AliasServiceException e) {
-      log.creatingKeyStoreForGateway();
-      // nop - default passphrase will be used
+      log.failedToGetPasswordForGatewayIdentityKeystore(e);
+      throw e;
     }
-    if(keystorePasswordChars == null) {
-      // If a keystore password was not set, use the master password
-      keystorePasswordChars = master;
+    if(keystorePasswordChars != null) {
+      sslContextFactory.setKeyStorePassword(new String(keystorePasswordChars));
     }
-    sslContextFactory.setKeyStorePassword(new String(keystorePasswordChars));
 
-    char[] keypass = null;
+    char[] keypass;
     try {
       keypass = as.getGatewayIdentityPassphrase();
     } catch (AliasServiceException e) {
-      // nop - default passphrase will be used
+      log.failedToGetPassphraseForGatewayIdentityKey(e);
+      throw e;
     }
-    if (keypass == null) {
-      // there has been no alias created for the key - let's assume it is the same as the
keystore password
-      keypass = master;
+    if(keypass != null) {
+      sslContextFactory.setKeyManagerPassword(new String(keypass));
     }
-    sslContextFactory.setKeyManagerPassword(new String(keypass));
 
-    String truststorePassword;
+    boolean clientAuthNeeded = config.isClientAuthNeeded();
+    boolean clientAuthWanted = config.isClientAuthWanted();
     if (clientAuthNeeded || clientAuthWanted) {
+      String truststorePath = config.getTruststorePath();
+      String trustStoreType;
+      char[] truststorePassword;
+
       if (truststorePath != null) {
-        char[] truststorePwd = null;
+        trustStoreType = config.getTruststoreType();
+
         try {
-          truststorePwd = as.getPasswordFromAliasForGateway(GATEWAY_TRUSTSTORE_PASSWORD);
+          truststorePassword = as.getPasswordFromAliasForGateway(GATEWAY_TRUSTSTORE_PASSWORD);
         } catch (AliasServiceException e) {
-          // nop - master secret will be used
-        }
-        if (truststorePwd != null) {
-          truststorePassword = new String(truststorePwd);
-        }
-        else {
-          truststorePassword = new String(master);
+          log.failedToGetPasswordForGatewayTruststore(GATEWAY_TRUSTSTORE_PASSWORD, e);
+          throw e;
         }
-        sslContextFactory.setTrustStore(loadKeyStore(truststorePath, trustStoreType, truststorePassword.toCharArray()));
-        sslContextFactory.setTrustStorePassword(truststorePassword);
-        sslContextFactory.setTrustStoreType(trustStoreType);
       }
       else {
         // when clientAuthIsNeeded but no truststore provided
         // default to the server's keystore and details
-        sslContextFactory.setTrustStore(loadKeyStore(keystoreFileName, keystoreType, master));
-        sslContextFactory.setTrustStorePassword(new String(master));
-        sslContextFactory.setTrustStoreType(keystoreType);
+        truststorePath = identityKeystorePath;
+        trustStoreType = identityKeystoreType;
+
+        try {
+          truststorePassword = as.getGatewayIdentityKeystorePassword();
+        } catch (AliasServiceException e) {
+          log.failedToGetPasswordForGatewayTruststore(config.getIdentityKeystorePasswordAlias(),
e);
+          throw e;
+        }
+      }
+
+      sslContextFactory.setTrustStorePath(truststorePath);
+      if(truststorePassword != null) {
+        sslContextFactory.setTrustStorePassword(new String(truststorePassword));
       }
+      sslContextFactory.setTrustStoreType(trustStoreType);
     }
     if (clientAuthNeeded) {
       sslContextFactory.setNeedClientAuth( clientAuthNeeded );
@@ -230,13 +206,20 @@ public class JettySSLService implements SSLService {
     else {
       sslContextFactory.setWantClientAuth( clientAuthWanted );
     }
-    sslContextFactory.setTrustAll( trustAllCerts );
+
+    sslContextFactory.setTrustAll( config.getTrustAllCerts() );
+
+    List<String> sslIncludeCiphers = config.getIncludedSSLCiphers();
     if (sslIncludeCiphers != null && !sslIncludeCiphers.isEmpty()) {
       sslContextFactory.setIncludeCipherSuites( sslIncludeCiphers.toArray(new String[0])
);
     }
+
+    List<String> sslExcludeCiphers = config.getExcludedSSLCiphers();
     if (sslExcludeCiphers != null && !sslExcludeCiphers.isEmpty()) {
       sslContextFactory.setExcludeCipherSuites( sslExcludeCiphers.toArray(new String[0])
);
     }
+
+    List<String> sslExcludeProtocols = config.getExcludedSSLProtocols();
     if (sslExcludeProtocols != null && !sslExcludeProtocols.isEmpty()) {
       sslContextFactory.setExcludeProtocols( sslExcludeProtocols.toArray(new String[0]) );
     }
@@ -250,12 +233,4 @@ public class JettySSLService implements SSLService {
   @Override
   public void stop() throws ServiceLifecycleException {
   }
-
-  private static KeyStore loadKeyStore( String fileName, String storeType, char[] storePass
) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
-    KeyStore keystore = KeyStore.getInstance(storeType);
-    try (InputStream is = Files.newInputStream(Paths.get(fileName))) {
-      keystore.load(is, storePass);
-    }
-    return keystore;
-  }
 }
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/JettySSLServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/JettySSLServiceTest.java
new file mode 100644
index 0000000..628076b
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/JettySSLServiceTest.java
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *
+ */
+
+package org.apache.knox.gateway.services.security.impl;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.KeystoreService;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.UnrecoverableKeyException;
+
+public class JettySSLServiceTest {
+  @Test
+  public void TestBuildSslContextFactoryOnlyIdentityKeystore() throws Exception {
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeystorePassword = "horton".toCharArray();
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(false, false, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(identityKeystorePassword).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getKeyStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getKeyStoreType());
+    assertNotNull(sslContextFactory.getKeyStore());
+
+    assertNull(sslContextFactory.getTrustStorePath());
+    assertNull(sslContextFactory.getTrustStoreType());
+
+    // If the truststore is not set, by default the identity keystore is used by Jetty.
+    assertEquals(sslContextFactory.getKeyStore().size(), sslContextFactory.getTrustStore().size());
+    assertTrue(sslContextFactory.getTrustStore().containsAlias(identityKeyAlias));
+
+    verify(config, aliasService, keystoreService);
+  }
+
+  @Test(expected = AliasServiceException.class)
+  public void TestBuildSslContextFactoryOnlyIdentityKeystoreErrorGettingPassword() throws
Exception {
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(false, false, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andThrow(new AliasServiceException(null)).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    sslService.buildSslContextFactory(config);
+
+    fail("AliasServiceException should have been thrown");
+  }
+
+  @Test
+  public void TestBuildSslContextFactoryOnlyIdentityKeystoreNullKeystorePassword() throws
Exception {
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(false, false, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(null).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getKeyStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getKeyStoreType());
+    assertNotNull(sslContextFactory.getKeyStore());
+
+    assertNull(sslContextFactory.getTrustStorePath());
+    assertNull(sslContextFactory.getTrustStoreType());
+
+    // If the truststore is not set, by default the identity keystore is used by Jetty.
+    assertEquals(sslContextFactory.getKeyStore().size(), sslContextFactory.getTrustStore().size());
+    assertTrue(sslContextFactory.getTrustStore().containsAlias(identityKeyAlias));
+
+    verify(config, aliasService, keystoreService);
+
+    // Note: The key password is used if the keystore password is not set; and vice versa
+  }
+
+  @Test(expected = UnrecoverableKeyException.class)
+  public void TestBuildSslContextFactoryOnlyIdentityKeystoreNullKeyPassword() throws Exception
{
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(false, false, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(null).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(null).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    fail("UnrecoverableKeyException should have been thrown");
+  }
+
+  @Test
+  public void TestBuildSslContextFactoryImplicitTrustStore() throws Exception {
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeystorePassword = "horton".toCharArray();
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(true, false, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(identityKeystorePassword).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getKeyStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getKeyStoreType());
+    assertNotNull(sslContextFactory.getKeyStore());
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getTrustStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getTrustStoreType());
+
+    // The truststore is expected to be the same as the identity keystore
+    assertEquals(sslContextFactory.getKeyStore().size(), sslContextFactory.getTrustStore().size());
+    assertTrue(sslContextFactory.getTrustStore().containsAlias(identityKeyAlias));
+
+    verify(config, aliasService, keystoreService);
+  }
+
+  @Test
+  public void TestBuildSslContextFactoryExplicitTrustStore() throws Exception {
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeystorePassword = "horton".toCharArray();
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+    char[] truststorePassword = "horton".toCharArray();
+
+    GatewayConfig config = createGatewayConfig(true, true, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(identityKeystorePassword).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+    expect(aliasService.getPasswordFromAliasForGateway(eq("gateway-truststore-password"))).andReturn(truststorePassword).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getKeyStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getKeyStoreType());
+    assertNotNull(sslContextFactory.getKeyStore());
+
+    assertEquals(truststorePath.toUri().toString(), sslContextFactory.getTrustStorePath());
+    assertEquals(truststoreType, sslContextFactory.getTrustStoreType());
+    assertNotNull(sslContextFactory.getTrustStore());
+
+    // The truststore is expected to be different than the identity keystore
+    assertTrue(sslContextFactory.getKeyStore().containsAlias(identityKeyAlias));
+    assertFalse(sslContextFactory.getTrustStore().containsAlias(identityKeyAlias));
+
+    verify(config, aliasService, keystoreService);
+  }
+
+  @Test(expected = AliasServiceException.class)
+  public void TestBuildSslContextFactoryExplicitTrustStoreErrorGettingPassword() throws Exception
{
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeystorePassword = "horton".toCharArray();
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(true, true, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(identityKeystorePassword).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+    expect(aliasService.getPasswordFromAliasForGateway(eq("gateway-truststore-password"))).andThrow(new
AliasServiceException(null)).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    sslService.buildSslContextFactory(config);
+
+    fail("AliasServiceException should have been thrown");
+  }
+
+  @Test
+  public void TestBuildSslContextFactoryExplicitTrustStoreNullPassword() throws Exception
{
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    char[] identityKeystorePassword = "horton".toCharArray();
+    char[] identityKeyPassphrase = "horton".toCharArray();
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(true, true, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(identityKeystorePassword).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(identityKeyPassphrase).atLeastOnce();
+    expect(aliasService.getPasswordFromAliasForGateway(eq("gateway-truststore-password"))).andReturn(null).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    assertEquals(identityKeystorePath.toUri().toString(), sslContextFactory.getKeyStorePath());
+    assertEquals(identityKeystoreType, sslContextFactory.getKeyStoreType());
+    assertNotNull(sslContextFactory.getKeyStore());
+
+    assertEquals(truststorePath.toUri().toString(), sslContextFactory.getTrustStorePath());
+    assertEquals(truststoreType, sslContextFactory.getTrustStoreType());
+    assertNotNull(sslContextFactory.getTrustStore());
+
+    // The truststore is expected to be different than the identity keystore
+    assertTrue(sslContextFactory.getKeyStore().containsAlias(identityKeyAlias));
+    assertFalse(sslContextFactory.getTrustStore().containsAlias(identityKeyAlias));
+
+    verify(config, aliasService, keystoreService);
+
+    // Note: The keystore password is used if the truststore password is not set
+  }
+
+  @Test(expected = UnrecoverableKeyException.class)
+  public void TestBuildSslContextFactoryExplicitTrustStoreNullPasswords() throws Exception
{
+    String basedir = System.getProperty("basedir");
+    if (basedir == null) {
+      basedir = new File(".").getCanonicalPath();
+    }
+
+    Path identityKeystorePath = Paths.get(basedir, "target", "test-classes", "keystores",
"server-keystore.jks");
+    String identityKeystoreType = "jks";
+    String identityKeyAlias = "server";
+    Path truststorePath = Paths.get(basedir, "target", "test-classes", "keystores", "server-truststore.jks");
+    String truststoreType = "jks";
+
+    GatewayConfig config = createGatewayConfig(true, true, identityKeystorePath, identityKeystoreType,
identityKeyAlias, truststorePath, truststoreType);
+
+    AliasService aliasService = createMock(AliasService.class);
+    expect(aliasService.getGatewayIdentityKeystorePassword()).andReturn(null).atLeastOnce();
+    expect(aliasService.getGatewayIdentityPassphrase()).andReturn(null).atLeastOnce();
+    expect(aliasService.getPasswordFromAliasForGateway(eq("gateway-truststore-password"))).andReturn(null).atLeastOnce();
+
+    KeystoreService keystoreService = createMock(KeystoreService.class);
+
+    replay(config, aliasService, keystoreService);
+
+    JettySSLService sslService = new JettySSLService();
+    sslService.setAliasService(aliasService);
+    sslService.setKeystoreService(keystoreService);
+
+    Object result = sslService.buildSslContextFactory(config);
+    assertNotNull(result);
+    assertTrue(result instanceof SslContextFactory);
+
+    SslContextFactory sslContextFactory = (SslContextFactory) result;
+    sslContextFactory.start();
+
+    fail("UnrecoverableKeyException should have been thrown");
+  }
+
+  private GatewayConfig createGatewayConfig(boolean isClientAuthNeeded, boolean isExplicitTruststore,
+                                            Path identityKeystorePath, String identityKeystoreType,
+                                            String identityKeyAlias, Path truststorePath,
String truststoreType) {
+    GatewayConfig config = createMock(GatewayConfig.class);
+    expect(config.getIdentityKeystorePath()).andReturn(identityKeystorePath.toString()).atLeastOnce();
+    expect(config.getIdentityKeystoreType()).andReturn(identityKeystoreType).atLeastOnce();
+    expect(config.getIdentityKeyAlias()).andReturn(identityKeyAlias).atLeastOnce();
+
+    if (isClientAuthNeeded) {
+      expect(config.isClientAuthNeeded()).andReturn(true).atLeastOnce();
+
+      if (isExplicitTruststore) {
+        expect(config.getTruststorePath()).andReturn(truststorePath.toString()).atLeastOnce();
+        expect(config.getTruststoreType()).andReturn(truststoreType).atLeastOnce();
+      } else {
+        expect(config.getTruststorePath()).andReturn(null).atLeastOnce();
+      }
+    } else {
+      expect(config.isClientAuthNeeded()).andReturn(false).atLeastOnce();
+    }
+
+    expect(config.isClientAuthWanted()).andReturn(false).atLeastOnce();
+    expect(config.getTrustAllCerts()).andReturn(false).atLeastOnce();
+    expect(config.getIncludedSSLCiphers()).andReturn(null).atLeastOnce();
+    expect(config.getExcludedSSLCiphers()).andReturn(null).atLeastOnce();
+    expect(config.getExcludedSSLProtocols()).andReturn(null).atLeastOnce();
+    return config;
+  }
+
+}
\ No newline at end of file
diff --git a/gateway-server/src/test/resources/keystores/readme.txt b/gateway-server/src/test/resources/keystores/readme.txt
new file mode 100644
index 0000000..865c0fb
--- /dev/null
+++ b/gateway-server/src/test/resources/keystores/readme.txt
@@ -0,0 +1,40 @@
+##########################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##########################################################################
+
+server-keystore.jks
+  Keystore password: horton
+  identity key alias: server
+  identity key password: horton
+
+----
+
+server-truststore.jks
+  Keystore password: horton
+  trusted cert alias: client
+
+----
+
+testSigningKeyName.jks
+  Keystore password: testSigningKeyPassphrase
+  Signing key alias: testSigningKeyAlias
+  Signing key password: testSigningKeyPassphrase
+
+  keytool -genkey -alias testSigningKeyAlias -keyalg RSA -keystore testSigningKeyName.jks
\
+    -storepass testSigningKeyPassphrase -keypass testSigningKeyPassphrase -keysize 2048 \
+    -dname 'CN=testSigningKey,OU=example,O=Apache,L=US,ST=CA,C=US' -noprompt
+
diff --git a/gateway-server/src/test/resources/server-truststore.jks b/gateway-server/src/test/resources/keystores/server-truststore.jks
similarity index 100%
rename from gateway-server/src/test/resources/server-truststore.jks
rename to gateway-server/src/test/resources/keystores/server-truststore.jks
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/SSLService.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/SSLService.java
index 3a6874d..6b135de 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/SSLService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/SSLService.java
@@ -17,13 +17,9 @@
  */
 package org.apache.knox.gateway.services.security;
 
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-
+import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.Service;
 
 public interface SSLService extends Service {
-  Object buildSslContextFactory(String keystoreFileName, String keystoreType, String alias)
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException;
+  Object buildSslContextFactory(GatewayConfig config) throws AliasServiceException;
 }


Mime
View raw message