From aa4d4189c40380554295af730746957438cf2616 Mon Sep 17 00:00:00 2001 From: Mark Payne Date: Wed, 3 Aug 2016 10:49:17 -0400 Subject: [PATCH 1/3] NIFI-2446: Add option to specify key password when different than keystore password --- .../nifi/security/util/SslContextFactory.java | 76 ++++++++++++++++++- .../nifi/ssl/StandardSSLContextService.java | 26 ++++++- .../apache/nifi/ssl/SSLContextService.java | 2 + 3 files changed, 98 insertions(+), 6 deletions(-) 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 656573db1a72..b2d14585f17a 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 static enum ClientAuth { } /** - * 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 static SSLContext createSslContext( 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 static SSLContext createSslContext( } + /** + * 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 static SSLContext createSslContext( * @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 static SSLContext createSslContext( 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 8fa9100b6897..70fc0cc6ebb6 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.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 @@ private void verifySslConfig(final ValidationContext validationContext) throws P 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 SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessEx 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 String getKeyStorePassword() { 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 @@ private static Collection validateStore(final Map Date: Wed, 3 Aug 2016 13:58:28 -0400 Subject: [PATCH 2/3] NIFI-2466: Added unit test to verify changes; fixed validation --- .../nifi/ssl/StandardSSLContextService.java | 5 ++ .../nifi/ssl/SSLContextServiceTest.java | 44 +++++++++++++++++- .../src/test/resources/diffpass-ks.jks | Bin 0 -> 2246 bytes 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/diffpass-ks.jks 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 70fc0cc6ebb6..0912dd426ba4 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 @@ -233,6 +233,9 @@ protected Collection customValidate(ValidationContext validati private void verifySslConfig(final ValidationContext validationContext) throws ProcessException { final String protocol = validationContext.getProperty(SSL_ALGORITHM).getValue(); try { + final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD); + final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; + final String keystoreFile = validationContext.getProperty(KEYSTORE).getValue(); if (keystoreFile == null) { SslContextFactory.createTrustSslContext( @@ -247,6 +250,7 @@ private void verifySslConfig(final ValidationContext validationContext) throws P SslContextFactory.createSslContext( validationContext.getProperty(KEYSTORE).getValue(), validationContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(), + keyPassword, validationContext.getProperty(KEYSTORE_TYPE).getValue(), protocol); return; @@ -255,6 +259,7 @@ private void verifySslConfig(final ValidationContext validationContext) throws P 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(), 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 1e22deed814e..a7719148f4c5 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 void testGood() throws InitializationException { 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 void testGoodKeyOnly() { } } + @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 0000000000000000000000000000000000000000..c4bd59c6554ba69f722bb36e7c3bcccbe4f15ea0 GIT binary patch literal 2246 zcmchY`8(8$7sux_i)|1Mi7-U6W`;0jUt&bLV;PcdW-?{TURf_jMp3rD32BKfGV(dCqg5_c_lwyWCwa2m}J}2k>w3UGgE5 zg9xFa`v8w~Uwj4v!N4dAbQdMeBcR3uKmk>-C;-4fPzrSRqVq$fV10siD>dO|=_Byt zn-{9+`I&uP4wd8pTK`I;?xo>FZ)KYzF%h2$hli7ouZbiWOXJ6=FH3U^O}DVTf$VsJ zWcTj25xW@SaC3aB&`AG9$4k93$k17~Ha>Oe>^z>X>dzDg-qjrzBXh^G96st zMCxEtTP!dBrsLw*tBlhV(Z`yrGkP=ssuVcMYAOC0&19Q;P+qKy%XG9Pb=y*yV+D^> zHqL??26l!(pRnaNgB-m{WR*90C&Mn=q^q9MDVN-;cY6}9d?G0| zOec7zedlX;4^u^9%RNQ1Q`NROh1|eG?&V2MYoAnnUM!Y?NYJ+VYC;Wg&UTa~+c6aI%$zXoD7BawBwD6f}@0}^_m83_MdFN-?o&F z;iD~wGVvP?qNn>cgo6h|s2reGsV{o$n@G7-;}V`N|F)lg_dv|=_j7W^Xh&`CS{Mu5 zJIXq9`_3zAvtG)?mG2guqVNgUsNCvux__H`$(0xp+c}GjDgGM8W(z}3>8-frrdeED zmT09u>?R?#YY&Ty?O#Xv`HgV>)oapnQc)QLD^L>~hDQ;g9;HbK^Q!iH%D7!FCv5tWUE%sKeZ)SnE9Jj`2Mm zEyJ`=FT*nAaYT*E&G?(M41DFg{7=fVHV0YLqnHJ$$!fQ1O7-MhAH6?f+QJ3~6RN0IMfyA{9o zY9o2iwExWb41kr%)TeO>Um)$MNQQ8DXm=Pq_7yrWZ)z1ZA)fXZ?7A}ZTUgicbCq}O zm$TAGic%#^x7I2sFt37in!;ka{&E~WD5$BmC@#aruSl(97UDWK)2H;pDca-Bhw1xn zIWm#T5oU?^RKGKieb!8|%X>C7Rnc9RS^B_k;M)CDm$?g?yO5YVIk)kK6^L}s>5lwm zEiYYbgG#~$Yg_9*qA1i_otcnEyfMu;={7$m$aYhu%X{*-#;ADmR9t@`D?d@Rquk9t zXVGDVClROtr7Q`(AN9xe;H>X5gi4PSsXR6C~J#g$jb?0jvhI^+S#QNlPfvZP|Jhn4yst%I%XviWSg zB^Ay;(6S3N({tGq)$swv{#kLYwZ*5z8{vPciD$8=X8WpFyy&4sek5_d;6$e*dPl+z zf-a&3ml+xxj0kNltR$T$rI|SSHR)ElV^_!6Pcxl0{-Ybt`)mWYH);$#2@$|UTAzfK zTE@N|N-H(1t(DnT2`~HRh*t%v;kFkT7I3UWwTH^s;PqEt!r}EW@m<{*4Tjk3D6KfP zpy85UsltQlyKfNhqUR)}Fpc;1jo0cUa4*Wo^Suo8cn6nKNnKbA$A51l%;kg*>J$hB zi9%5zH&7I?CmjL?z+h;Eh2k1Yh=*U5iluG{0RS8ZLiwZND1IJ@6O;!5c5?Wi;6s4T zP0`{g#Lo;bLePN_5Jr$Y7Z~6}BA^fK*VxAb2p-cQ0_lo3S`2mYCx9d1{{?dAeJp*@ z2T>wFDFngqA4nqjUkMBiL&-`Ap>mLJ>g|BipZ#P90m~sBbF3ULrL9Zk`5hU)>h*f#?rrQa8vs=WBc?FKe`|u{g!(!ChAPp&kzH z^A=J{-7vx3%C@vyf9Tx(R$SuKtG?<60p|967gbb7KEDr5*0S<64^LS>YA z2}D^gfgu10D3C`XQ8N3Jlo5s=f{ImQTyC_8SKq1#C7vU6KTW)lX;}TMiTgXXZz2Fv zMn4K{YcH(1^CuP^I%9I{bs|UNlg; Date: Wed, 3 Aug 2016 16:01:11 -0400 Subject: [PATCH 3/3] NIFI-2466: Fixed NPE --- .../java/org/apache/nifi/ssl/StandardSSLContextService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 0912dd426ba4..81be148ccfb2 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 @@ -98,7 +98,8 @@ public class StandardSSLContextService extends AbstractControllerService impleme .sensitive(true) .build(); static final PropertyDescriptor KEY_PASSWORD = new PropertyDescriptor.Builder() - .name("Key Password") + .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) @@ -233,7 +234,7 @@ protected Collection customValidate(ValidationContext validati private void verifySslConfig(final ValidationContext validationContext) throws ProcessException { final String protocol = validationContext.getProperty(SSL_ALGORITHM).getValue(); try { - final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD); + final PropertyValue keyPasswdProp = validationContext.getProperty(KEY_PASSWORD); final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; final String keystoreFile = validationContext.getProperty(KEYSTORE).getValue();