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..70fc0cc6eb 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,14 @@ 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") + .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 +122,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); @@ -260,19 +270,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 +297,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 +344,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 +394,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-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();