diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java index 656573db1a..b2d14585f1 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java @@ -48,7 +48,9 @@ public final class SslContextFactory { } /** - * Creates a SSLContext instance using the given information. + * Creates a SSLContext instance using the given information. The password for the key is assumed to be the same + * as the password for the keystore. If this is not the case, the {@link #createSslContext(String, char[], chart[], String, String, char[], String, ClientAuth, String)} + * method should be used instead * * @param keystore the full path to the keystore * @param keystorePasswd the keystore password @@ -74,13 +76,48 @@ public final class SslContextFactory { throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { + // Pass the keystore password as both the keystore password and the key password. + return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, truststore, truststorePasswd, truststoreType, clientAuth, protocol); + } + + /** + * Creates a SSLContext instance using the given information. + * + * @param keystore the full path to the keystore + * @param keystorePasswd the keystore password + * @param keystoreType the type of keystore (e.g., PKCS12, JKS) + * @param truststore the full path to the truststore + * @param truststorePasswd the truststore password + * @param truststoreType the type of truststore (e.g., PKCS12, JKS) + * @param clientAuth the type of client authentication + * @param protocol the protocol to use for the SSL connection + * + * @return a SSLContext instance + * @throws java.security.KeyStoreException if any issues accessing the keystore + * @throws java.io.IOException for any problems loading the keystores + * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown + * @throws java.security.cert.CertificateException if there is an issue with the certificate + * @throws java.security.UnrecoverableKeyException if the key is insufficient + * @throws java.security.KeyManagementException if unable to manage the key + */ + public static SSLContext createSslContext( + final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType, + final String truststore, final char[] truststorePasswd, final String truststoreType, + final ClientAuth clientAuth, final String protocol) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, + UnrecoverableKeyException, KeyManagementException { + // prepare the keystore final KeyStore keyStore = KeyStore.getInstance(keystoreType); try (final InputStream keyStoreStream = new FileInputStream(keystore)) { keyStore.load(keyStoreStream, keystorePasswd); } final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keystorePasswd); + if (keyPasswd == null) { + keyManagerFactory.init(keyStore, keystorePasswd); + } else { + keyManagerFactory.init(keyStore, keyPasswd); + } // prepare the truststore final KeyStore trustStore = KeyStore.getInstance(truststoreType); @@ -105,6 +142,33 @@ public final class SslContextFactory { } + /** + * Creates a SSLContext instance using the given information. This method assumes that the key password is + * the same as the keystore password. If this is not the case, use the {@link #createSslContext(String, char[], char[], String, String)} + * method instead. + * + * @param keystore the full path to the keystore + * @param keystorePasswd the keystore password + * @param keystoreType the type of keystore (e.g., PKCS12, JKS) + * @param protocol the protocol to use for the SSL connection + * + * @return a SSLContext instance + * @throws java.security.KeyStoreException if any issues accessing the keystore + * @throws java.io.IOException for any problems loading the keystores + * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown + * @throws java.security.cert.CertificateException if there is an issue with the certificate + * @throws java.security.UnrecoverableKeyException if the key is insufficient + * @throws java.security.KeyManagementException if unable to manage the key + */ + public static SSLContext createSslContext( + final String keystore, final char[] keystorePasswd, final String keystoreType, final String protocol) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, + UnrecoverableKeyException, KeyManagementException { + + // create SSL Context passing keystore password as the key password + return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, protocol); + } + /** * Creates a SSLContext instance using the given information. * @@ -122,7 +186,7 @@ public final class SslContextFactory { * @throws java.security.KeyManagementException if unable to manage the key */ public static SSLContext createSslContext( - final String keystore, final char[] keystorePasswd, final String keystoreType, final String protocol) + final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType, final String protocol) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { @@ -132,7 +196,11 @@ public final class SslContextFactory { keyStore.load(keyStoreStream, keystorePasswd); } final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keystorePasswd); + if (keyPasswd == null) { + keyManagerFactory.init(keyStore, keystorePasswd); + } else { + keyManagerFactory.init(keyStore, keyPasswd); + } // initialize the ssl context final SSLContext ctx = SSLContext.getInstance(protocol); diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java index 8fa9100b68..81be148ccf 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java @@ -21,6 +21,7 @@ import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnEnabled; import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyValue; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.Validator; @@ -96,6 +97,15 @@ public class StandardSSLContextService extends AbstractControllerService impleme .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) .build(); + static final PropertyDescriptor KEY_PASSWORD = new PropertyDescriptor.Builder() + .name("key-password") + .displayName("Key Password") + .description("The password for the key. If this is not specified, but the Keystore Filename, Password, and Type are specified, " + + "then the Keystore Password will be assumed to be the same as the Key Password.") + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(true) + .required(false) + .build(); public static final PropertyDescriptor SSL_ALGORITHM = new PropertyDescriptor.Builder() .name("SSL Protocol") .defaultValue("TLS") @@ -113,6 +123,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme List props = new ArrayList<>(); props.add(KEYSTORE); props.add(KEYSTORE_PASSWORD); + props.add(KEY_PASSWORD); props.add(KEYSTORE_TYPE); props.add(TRUSTSTORE); props.add(TRUSTSTORE_PASSWORD); @@ -223,6 +234,9 @@ public class StandardSSLContextService extends AbstractControllerService impleme private void verifySslConfig(final ValidationContext validationContext) throws ProcessException { final String protocol = validationContext.getProperty(SSL_ALGORITHM).getValue(); try { + final PropertyValue keyPasswdProp = validationContext.getProperty(KEY_PASSWORD); + final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; + final String keystoreFile = validationContext.getProperty(KEYSTORE).getValue(); if (keystoreFile == null) { SslContextFactory.createTrustSslContext( @@ -237,6 +251,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme SslContextFactory.createSslContext( validationContext.getProperty(KEYSTORE).getValue(), validationContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(), + keyPassword, validationContext.getProperty(KEYSTORE_TYPE).getValue(), protocol); return; @@ -245,6 +260,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme SslContextFactory.createSslContext( validationContext.getProperty(KEYSTORE).getValue(), validationContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(), + keyPassword, validationContext.getProperty(KEYSTORE_TYPE).getValue(), validationContext.getProperty(TRUSTSTORE).getValue(), validationContext.getProperty(TRUSTSTORE_PASSWORD).getValue().toCharArray(), @@ -260,19 +276,26 @@ public class StandardSSLContextService extends AbstractControllerService impleme public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException { final String protocol = configContext.getProperty(SSL_ALGORITHM).getValue(); try { + final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD); + final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; + final String keystoreFile = configContext.getProperty(KEYSTORE).getValue(); if (keystoreFile == null) { + // If keystore not specified, create SSL Context based only on trust store. return SslContextFactory.createTrustSslContext( configContext.getProperty(TRUSTSTORE).getValue(), configContext.getProperty(TRUSTSTORE_PASSWORD).getValue().toCharArray(), configContext.getProperty(TRUSTSTORE_TYPE).getValue(), protocol); } + final String truststoreFile = configContext.getProperty(TRUSTSTORE).getValue(); if (truststoreFile == null) { + // If truststore not specified, create SSL Context based only on key store. return SslContextFactory.createSslContext( configContext.getProperty(KEYSTORE).getValue(), configContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(), + keyPassword, configContext.getProperty(KEYSTORE_TYPE).getValue(), protocol); } @@ -280,6 +303,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme return SslContextFactory.createSslContext( configContext.getProperty(KEYSTORE).getValue(), configContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(), + keyPassword, configContext.getProperty(KEYSTORE_TYPE).getValue(), configContext.getProperty(TRUSTSTORE).getValue(), configContext.getProperty(TRUSTSTORE_PASSWORD).getValue().toCharArray(), @@ -326,6 +350,11 @@ public class StandardSSLContextService extends AbstractControllerService impleme return configContext.getProperty(KEYSTORE_PASSWORD).getValue(); } + @Override + public String getKeyPassword() { + return configContext.getProperty(KEY_PASSWORD).getValue(); + } + @Override public boolean isKeyStoreConfigured() { return getKeyStoreFile() != null && getKeyStorePassword() != null && getKeyStoreType() != null; @@ -371,8 +400,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme .build()); } else { try { - final boolean storeValid = CertificateUtils - .isStoreValid(file.toURI().toURL(), KeystoreType.valueOf(type), password.toCharArray()); + final boolean storeValid = CertificateUtils.isStoreValid(file.toURI().toURL(), KeystoreType.valueOf(type), password.toCharArray()); if (!storeValid) { results.add(new ValidationResult.Builder() .subject(keystoreDesc + " Properties") diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java index 1e22deed81..a7719148f4 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java @@ -109,7 +109,7 @@ public class SSLContextServiceTest { runner.assertValid(service); service = (SSLContextService) runner.getProcessContext().getControllerServiceLookup().getControllerService("test-good1"); Assert.assertNotNull(service); - SSLContextService sslService = (SSLContextService) service; + SSLContextService sslService = service; sslService.createSSLContext(ClientAuth.REQUIRED); sslService.createSSLContext(ClientAuth.WANT); sslService.createSSLContext(ClientAuth.NONE); @@ -160,4 +160,46 @@ public class SSLContextServiceTest { } } + @Test + public void testDifferentKeyPassword() { + try { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final SSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap(); + properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/diffpass-ks.jks"); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "storepassword"); + properties.put(StandardSSLContextService.KEY_PASSWORD.getName(), "keypassword"); + properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS"); + runner.addControllerService("test-diff-keys", service, properties); + runner.enableControllerService(service); + + runner.setProperty("SSL Context Svc ID", "test-diff-keys"); + runner.assertValid(); + Assert.assertNotNull(service); + Assert.assertTrue(service instanceof StandardSSLContextService); + SSLContextService sslService = service; + sslService.createSSLContext(ClientAuth.NONE); + } catch (Exception e) { + System.out.println(e); + Assert.fail("Should not have thrown a exception " + e.getMessage()); + } + } + + @Test + public void testDifferentKeyPasswordWithoutSpecifyingPassword() { + try { + final TestRunner runner = TestRunners.newTestRunner(TestProcessor.class); + final SSLContextService service = new StandardSSLContextService(); + final Map properties = new HashMap(); + properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/diffpass-ks.jks"); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "storepassword"); + properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS"); + runner.addControllerService("test-diff-keys", service, properties); + + runner.assertNotValid(service); + } catch (Exception e) { + System.out.println(e); + Assert.fail("Should not have thrown a exception " + e.getMessage()); + } + } } diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/diffpass-ks.jks b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/diffpass-ks.jks new file mode 100644 index 0000000000..c4bd59c655 Binary files /dev/null and b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/diffpass-ks.jks differ diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java index 13bab4ccde..94f8bda526 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java @@ -55,6 +55,8 @@ public interface SSLContextService extends ControllerService { public String getKeyStorePassword(); + public String getKeyPassword(); + public boolean isKeyStoreConfigured(); String getSslAlgorithm();