This is an automated email from the ASF dual-hosted git repository.
alopresto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/master by this push:
new 4ec9155 NIFI-6770 - Set validator to Validator.VALID to allow empty
password for truststores. Added no-password keystore for tests System NiFi
truststore now allows a passwordless truststore. Added a unit test to prove
this. Forgot no-password-truststore.jks file for the unit test. Refactored
utility method from CertificateUtils to KeyStoreUtils. Added utility methods to
verify keystore and key passwords. Added unit tests. Implemented different
keystore and truststore validation [...]
4ec9155 is described below
commit 4ec9155cbc1796c0fc31893cdd91733b7f9cec45
Author: Nathan Gough <[email protected]>
AuthorDate: Thu Oct 17 13:09:08 2019 -0400
NIFI-6770 - Set validator to Validator.VALID to allow empty password for
truststores.
Added no-password keystore for tests
System NiFi truststore now allows a passwordless truststore. Added a unit
test to prove this.
Forgot no-password-truststore.jks file for the unit test.
Refactored utility method from CertificateUtils to KeyStoreUtils.
Added utility methods to verify keystore and key passwords.
Added unit tests.
Implemented different keystore and truststore validation logic.
Refactored internal custom validation in StandardSSLContextService.
Added unit test resource for keystore with different key and keystore
passwords.
Added unit test to generate passwordless truststore for
https://nifi.apache.org for live testing.
Resolved NPE in SSLContext generation in StandardSSLContextService
Added unit test to generate passwordless truststore for localhost for
InvokeHTTP testing.
Resolved TrustManagerFactoryImpl initialization error.
Fixed unit test without proper cleanup which caused RAT failures.
Co-authored-by: Andy LoPresto <[email protected]>
This closes #3823.
Signed-off-by: Andy LoPresto <[email protected]>
---
.../nifi/security/util/CertificateUtils.java | 223 +++++--------
.../apache/nifi/security/util/KeyStoreUtils.java | 105 +++++-
.../security/util/KeyStoreUtilsGroovyTest.groovy | 144 ++++++++
.../framework/security/util/SslContextFactory.java | 1 -
.../security/util/SslContextFactoryTest.java | 22 +-
.../src/test/resources/no-password-truststore.jks | Bin 0 -> 911 bytes
.../nifi/processors/standard/InvokeHTTP.java | 95 +++---
.../processors/standard/TestInvokeHttpSSL.java | 16 +-
.../nifi/processors/standard/TestListenHTTP.java | 3 +-
.../src/test/resources/truststore.no-password.jks | Bin 0 -> 917 bytes
.../ssl/StandardRestrictedSSLContextService.java | 6 -
.../apache/nifi/ssl/StandardSSLContextService.java | 362 +++++++++++++--------
.../nifi/ssl/StandardSSLContextServiceTest.groovy | 105 +++++-
.../org/apache/nifi/ssl/SSLContextServiceTest.java | 36 +-
.../test/resources/keystore-with-key-password.jks | Bin 0 -> 2012 bytes
.../src/test/resources/no-password-truststore.jks | Bin 0 -> 911 bytes
16 files changed, 748 insertions(+), 370 deletions(-)
diff --git
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index 4d80336..d51e951 100644
---
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -16,6 +16,33 @@
*/
package org.apache.nifi.security.util;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.net.Socket;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -47,38 +74,6 @@ import
org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocket;
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.net.Socket;
-import java.net.URL;
-import java.security.KeyPair;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
public final class CertificateUtils {
private static final Logger logger =
LoggerFactory.getLogger(CertificateUtils.class);
private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not
authenticated";
@@ -138,48 +133,6 @@ public final class CertificateUtils {
}
/**
- * Returns true if the given keystore can be loaded using the given
keystore type and password. Returns false otherwise.
- *
- * @param keystore the keystore to validate
- * @param keystoreType the type of the keystore
- * @param password the password to access the keystore
- * @return true if valid; false otherwise
- */
- public static boolean isStoreValid(final URL keystore, final KeystoreType
keystoreType, final char[] password) {
-
- if (keystore == null) {
- throw new IllegalArgumentException("keystore may not be null");
- } else if (keystoreType == null) {
- throw new IllegalArgumentException("keystore type may not be
null");
- } else if (password == null) {
- throw new IllegalArgumentException("password may not be null");
- }
-
- BufferedInputStream bis = null;
- final KeyStore ks;
- try {
-
- // load the keystore
- bis = new BufferedInputStream(keystore.openStream());
- ks = KeyStoreUtils.getKeyStore(keystoreType.name());
- ks.load(bis, password);
-
- return true;
-
- } catch (Exception e) {
- return false;
- } finally {
- if (bis != null) {
- try {
- bis.close();
- } catch (final IOException ioe) {
- logger.warn("Failed to close input stream", ioe);
- }
- }
- }
- }
-
- /**
* Extracts the username from the specified DN. If the username cannot be
extracted because the CN is in an unrecognized format, the entire CN is
returned. If the CN cannot be extracted because
* the DN is in an unrecognized format, the entire DN is returned.
*
@@ -243,7 +196,7 @@ public final class CertificateUtils {
/**
* Returns the DN extracted from the peer certificate (the server DN if
run on the client; the client DN (if available) if run on the server).
- *
+ * <p>
* If the client auth setting is WANT or NONE and a client certificate is
not present, this method will return {@code null}.
* If the client auth is NEED, it will throw a {@link
CertificateException}.
*
@@ -263,10 +216,10 @@ public final class CertificateUtils {
if (clientMode) {
logger.debug("This socket is in client mode, so attempting to
extract certificate from remote 'server' socket");
- dn = extractPeerDNFromServerSSLSocket(sslSocket);
+ dn = extractPeerDNFromServerSSLSocket(sslSocket);
} else {
logger.debug("This socket is in server mode, so attempting to
extract certificate from remote 'client' socket");
- dn = extractPeerDNFromClientSSLSocket(sslSocket);
+ dn = extractPeerDNFromClientSSLSocket(sslSocket);
}
}
@@ -275,7 +228,7 @@ public final class CertificateUtils {
/**
* Returns the DN extracted from the client certificate.
- *
+ * <p>
* If the client auth setting is WANT or NONE and a certificate is not
present (and {@code respectClientAuth} is {@code true}), this method will
return {@code null}.
* If the client auth is NEED, it will throw a {@link
CertificateException}.
*
@@ -286,34 +239,34 @@ public final class CertificateUtils {
private static String extractPeerDNFromClientSSLSocket(SSLSocket
sslSocket) throws CertificateException {
String dn = null;
- /** The clientAuth value can be "need", "want", or "none"
- * A client must send client certificates for need, should for
want, and will not for none.
- * This method should throw an exception if none are provided for
need, return null if none are provided for want, and return null (without
checking) for none.
- */
-
- ClientAuth clientAuth = getClientAuthStatus(sslSocket);
- logger.debug("SSL Socket client auth status: {}", clientAuth);
-
- if (clientAuth != ClientAuth.NONE) {
- try {
- final Certificate[] certChains =
sslSocket.getSession().getPeerCertificates();
- if (certChains != null && certChains.length > 0) {
- X509Certificate x509Certificate =
convertAbstractX509Certificate(certChains[0]);
- dn = x509Certificate.getSubjectDN().getName().trim();
- logger.debug("Extracted DN={} from client
certificate", dn);
- }
- } catch (SSLPeerUnverifiedException e) {
- if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
- logger.error("The incoming request did not contain
client certificates and thus the DN cannot" +
- " be extracted. Check that the other endpoint
is providing a complete client certificate chain");
- }
- if (clientAuth == ClientAuth.WANT) {
- logger.warn("Suppressing missing client certificate
exception because client auth is set to 'want'");
- return dn;
- }
- throw new CertificateException(e);
+ /** The clientAuth value can be "need", "want", or "none"
+ * A client must send client certificates for need, should for want,
and will not for none.
+ * This method should throw an exception if none are provided for
need, return null if none are provided for want, and return null (without
checking) for none.
+ */
+
+ ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+ logger.debug("SSL Socket client auth status: {}", clientAuth);
+
+ if (clientAuth != ClientAuth.NONE) {
+ try {
+ final Certificate[] certChains =
sslSocket.getSession().getPeerCertificates();
+ if (certChains != null && certChains.length > 0) {
+ X509Certificate x509Certificate =
convertAbstractX509Certificate(certChains[0]);
+ dn = x509Certificate.getSubjectDN().getName().trim();
+ logger.debug("Extracted DN={} from client certificate",
dn);
}
+ } catch (SSLPeerUnverifiedException e) {
+ if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+ logger.error("The incoming request did not contain client
certificates and thus the DN cannot" +
+ " be extracted. Check that the other endpoint is
providing a complete client certificate chain");
+ }
+ if (clientAuth == ClientAuth.WANT) {
+ logger.warn("Suppressing missing client certificate
exception because client auth is set to 'want'");
+ return dn;
+ }
+ throw new CertificateException(e);
}
+ }
return dn;
}
@@ -328,20 +281,20 @@ public final class CertificateUtils {
String dn = null;
if (socket instanceof SSLSocket) {
final SSLSocket sslSocket = (SSLSocket) socket;
- try {
- final Certificate[] certChains =
sslSocket.getSession().getPeerCertificates();
- if (certChains != null && certChains.length > 0) {
- X509Certificate x509Certificate =
convertAbstractX509Certificate(certChains[0]);
- dn = x509Certificate.getSubjectDN().getName().trim();
- logger.debug("Extracted DN={} from server
certificate", dn);
- }
- } catch (SSLPeerUnverifiedException e) {
- if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
- logger.error("The server did not present a certificate
and thus the DN cannot" +
- " be extracted. Check that the other endpoint
is providing a complete certificate chain");
- }
- throw new CertificateException(e);
+ try {
+ final Certificate[] certChains =
sslSocket.getSession().getPeerCertificates();
+ if (certChains != null && certChains.length > 0) {
+ X509Certificate x509Certificate =
convertAbstractX509Certificate(certChains[0]);
+ dn = x509Certificate.getSubjectDN().getName().trim();
+ logger.debug("Extracted DN={} from server certificate",
dn);
+ }
+ } catch (SSLPeerUnverifiedException e) {
+ if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+ logger.error("The server did not present a certificate and
thus the DN cannot" +
+ " be extracted. Check that the other endpoint is
providing a complete certificate chain");
}
+ throw new CertificateException(e);
+ }
}
return dn;
}
@@ -403,9 +356,9 @@ public final class CertificateUtils {
/**
* Reorders DN to the order the elements appear in the RFC 2253 table
- *
+ * <p>
* https://www.ietf.org/rfc/rfc2253.txt
- *
+ * <p>
* String X.500 AttributeType
* ------------------------------
* CN commonName
@@ -496,7 +449,7 @@ public final class CertificateUtils {
* @param signingAlgorithm the signing algorithm to use for the
{@link X509Certificate}
* @param certificateDurationDays the duration in days for which the
{@link X509Certificate} should be valid
* @return a self-signed {@link X509Certificate} suitable for use as a
Certificate Authority
- * @throws CertificateException if there is an generating the new
certificate
+ * @throws CertificateException if there is an generating the new
certificate
*/
public static X509Certificate generateSelfSignedX509Certificate(KeyPair
keyPair, String dn, String signingAlgorithm, int certificateDurationDays)
throws CertificateException {
@@ -538,12 +491,12 @@ public final class CertificateUtils {
/**
* Generates an issued {@link X509Certificate} from the given issuer
certificate and {@link KeyPair}
*
- * @param dn the distinguished name to use
- * @param publicKey the public key to issue the certificate to
- * @param issuer the issuer's certificate
- * @param issuerKeyPair the issuer's keypair
+ * @param dn the distinguished name to use
+ * @param publicKey the public key to issue the certificate to
+ * @param issuer the issuer's certificate
+ * @param issuerKeyPair the issuer's keypair
* @param signingAlgorithm the signing algorithm to use
- * @param days the number of days it should be valid for
+ * @param days the number of days it should be valid for
* @return an issued {@link X509Certificate} from the given issuer
certificate and {@link KeyPair}
* @throws CertificateException if there is an error issuing the
certificate
*/
@@ -555,13 +508,13 @@ public final class CertificateUtils {
/**
* Generates an issued {@link X509Certificate} from the given issuer
certificate and {@link KeyPair}
*
- * @param dn the distinguished name to use
- * @param publicKey the public key to issue the certificate to
- * @param extensions extensions extracted from the CSR
- * @param issuer the issuer's certificate
- * @param issuerKeyPair the issuer's keypair
+ * @param dn the distinguished name to use
+ * @param publicKey the public key to issue the certificate to
+ * @param extensions extensions extracted from the CSR
+ * @param issuer the issuer's certificate
+ * @param issuerKeyPair the issuer's keypair
* @param signingAlgorithm the signing algorithm to use
- * @param days the number of days it should be valid for
+ * @param days the number of days it should be valid for
* @return an issued {@link X509Certificate} from the given issuer
certificate and {@link KeyPair}
* @throws CertificateException if there is an error issuing the
certificate
*/
@@ -594,7 +547,7 @@ public final class CertificateUtils {
certBuilder.addExtension(Extension.extendedKeyUsage, false, new
ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth,
KeyPurposeId.id_kp_serverAuth}));
// (3) subjectAlternativeName
- if(extensions != null &&
extensions.getExtension(Extension.subjectAlternativeName) != null) {
+ if (extensions != null &&
extensions.getExtension(Extension.subjectAlternativeName) != null) {
certBuilder.addExtension(Extension.subjectAlternativeName,
false, extensions.getExtensionParsedValue(Extension.subjectAlternativeName));
}
@@ -607,15 +560,15 @@ public final class CertificateUtils {
/**
* Returns true if the two provided DNs are equivalent, regardless of the
order of the elements. Returns false if one or both are invalid DNs.
- *
+ * <p>
* Example:
- *
+ * <p>
* CN=test1, O=testOrg, C=US compared to CN=test1, O=testOrg, C=US -> true
* CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test1, C=US -> true
* CN=test1, O=testOrg, C=US compared to CN=test2, O=testOrg, C=US -> false
* CN=test1, O=testOrg, C=US compared to O=testOrg, CN=test2, C=US -> false
* CN=test1, O=testOrg, C=US compared to -> false
- * compared to -> true
+ * compared to -> true
*
* @param dn1 the first DN to compare
* @param dn2 the second DN to compare
diff --git
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
index 6b52009..d763b09 100644
---
a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
+++
b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java
@@ -17,15 +17,19 @@
package org.apache.nifi.security.util;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.UnrecoverableKeyException;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.Security;
-
public class KeyStoreUtils {
private static final Logger logger =
LoggerFactory.getLogger(KeyStoreUtils.class);
@@ -83,4 +87,97 @@ public class KeyStoreUtils {
}
return getKeyStore(trustStoreType);
}
+
+ /**
+ * Returns true if the given keystore can be loaded using the given
keystore type and password. Returns false otherwise.
+ *
+ * @param keystore the keystore to validate
+ * @param keystoreType the type of the keystore
+ * @param password the password to access the keystore
+ * @return true if valid; false otherwise
+ */
+ public static boolean isStoreValid(final URL keystore, final KeystoreType
keystoreType, final char[] password) {
+
+ if (keystore == null) {
+ throw new IllegalArgumentException("Keystore may not be null");
+ } else if (keystoreType == null) {
+ throw new IllegalArgumentException("Keystore type may not be
null");
+ } else if (password == null) {
+ throw new IllegalArgumentException("Password may not be null");
+ }
+
+ BufferedInputStream bis = null;
+ final KeyStore ks;
+ try {
+
+ // Load the keystore
+ bis = new BufferedInputStream(keystore.openStream());
+ ks = KeyStoreUtils.getKeyStore(keystoreType.name());
+ ks.load(bis, password);
+
+ return true;
+
+ } catch (Exception e) {
+ return false;
+ } finally {
+ if (bis != null) {
+ try {
+ bis.close();
+ } catch (final IOException ioe) {
+ logger.warn("Failed to close input stream", ioe);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given keystore can be loaded using the given
keystore type and password and the default
+ * (first) alias can be retrieved with the key-specific password. Returns
false otherwise.
+ *
+ * @param keystore the keystore to validate
+ * @param keystoreType the type of the keystore
+ * @param password the password to access the keystore
+ * @param keyPassword the password to access the specific key
+ * @return true if valid; false otherwise
+ */
+ public static boolean isKeyPasswordCorrect(final URL keystore, final
KeystoreType keystoreType, final char[] password, final char[] keyPassword) {
+
+ if (keystore == null) {
+ throw new IllegalArgumentException("Keystore may not be null");
+ } else if (keystoreType == null) {
+ throw new IllegalArgumentException("Keystore type may not be
null");
+ } else if (password == null) {
+ throw new IllegalArgumentException("Password may not be null");
+ }
+
+ BufferedInputStream bis = null;
+ final KeyStore ks;
+ try {
+
+ // Load the keystore
+ bis = new BufferedInputStream(keystore.openStream());
+ ks = KeyStoreUtils.getKeyStore(keystoreType.name());
+ ks.load(bis, password);
+
+ // Determine the default alias
+ String alias = ks.aliases().nextElement();
+ try {
+ Key privateKeyEntry = ks.getKey(alias, keyPassword);
+ return true;
+ } catch (UnrecoverableKeyException e) {
+ logger.warn("Tried to access a key in keystore " + keystore +
" with a key password that failed");
+ return false;
+ }
+ } catch (Exception e) {
+ return false;
+ } finally {
+ if (bis != null) {
+ try {
+ bis.close();
+ } catch (final IOException ioe) {
+ logger.warn("Failed to close input stream", ioe);
+ }
+ }
+ }
+ }
}
diff --git
a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/KeyStoreUtilsGroovyTest.groovy
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/KeyStoreUtilsGroovyTest.groovy
new file mode 100644
index 0000000..7a76348
--- /dev/null
+++
b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/KeyStoreUtilsGroovyTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * 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.nifi.security.util
+
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import javax.net.ssl.HttpsURLConnection
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import java.security.KeyStore
+import java.security.cert.Certificate
+
+@RunWith(JUnit4.class)
+class KeyStoreUtilsGroovyTest extends GroovyTestCase {
+ private static final Logger logger =
LoggerFactory.getLogger(KeyStoreUtilsGroovyTest.class)
+
+ private static final File KEYSTORE_FILE = new
File("src/test/resources/keystore.jks")
+ private static final String KEYSTORE_PASSWORD = "passwordpassword"
+ private static final String KEY_PASSWORD = "keypassword"
+ private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
+
+ @BeforeClass
+ static void setUpOnce() {
+ logger.metaClass.methodMissing = { String name, args ->
+ logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+ }
+ }
+
+ @Before
+ void setUp() {
+
+ }
+
+ @After
+ void tearDown() {
+
+ }
+
+ @Test
+ void testShouldVerifyKeystoreIsValid() {
+ // Arrange
+
+ // Act
+ boolean keystoreIsValid =
KeyStoreUtils.isStoreValid(KEYSTORE_FILE.toURI().toURL(), KEYSTORE_TYPE,
KEYSTORE_PASSWORD.toCharArray())
+
+ // Assert
+ assert keystoreIsValid
+ }
+
+ @Test
+ void testShouldVerifyKeystoreIsNotValid() {
+ // Arrange
+
+ // Act
+ boolean keystoreIsValid =
KeyStoreUtils.isStoreValid(KEYSTORE_FILE.toURI().toURL(), KEYSTORE_TYPE,
KEYSTORE_PASSWORD.reverse().toCharArray())
+
+ // Assert
+ assert !keystoreIsValid
+ }
+
+ @Test
+ void testShouldVerifyKeyPasswordIsValid() {
+ // Arrange
+
+ // Act
+ boolean keyPasswordIsValid =
KeyStoreUtils.isKeyPasswordCorrect(KEYSTORE_FILE.toURI().toURL(),
KEYSTORE_TYPE, KEYSTORE_PASSWORD.toCharArray(), KEYSTORE_PASSWORD.toCharArray())
+
+ // Assert
+ assert keyPasswordIsValid
+ }
+
+ @Test
+ void testShouldVerifyKeyPasswordIsNotValid() {
+ // Arrange
+
+ // Act
+ boolean keyPasswordIsValid =
KeyStoreUtils.isKeyPasswordCorrect(KEYSTORE_FILE.toURI().toURL(),
KEYSTORE_TYPE, KEYSTORE_PASSWORD.toCharArray(),
KEYSTORE_PASSWORD.reverse().toCharArray())
+
+ // Assert
+ assert !keyPasswordIsValid
+ }
+
+ @Test
+ @Ignore("Used to create passwordless truststore file for testing
NIFI-6770")
+ void createPasswordlessTruststore() {
+ // Retrieve the public certificate from https://nifi.apache.org
+ String hostname = "nifi.apache.org"
+ SSLSocketFactory factory =
HttpsURLConnection.getDefaultSSLSocketFactory()
+ SSLSocket socket = (SSLSocket) factory.createSocket(hostname, 443)
+ socket.startHandshake()
+ List<Certificate> certs = socket.session.peerCertificateChain as
List<Certificate>
+ Certificate nodeCert =
CertificateUtils.formX509Certificate(certs.first().encoded)
+
+ // Create a JKS truststore containing that cert as a trustedCertEntry
and do not put a password on the truststore
+ KeyStore truststore = KeyStore.getInstance("JKS")
+ // Explicitly set the second parameter to empty to avoid a password
+ truststore.load(null, "".chars)
+ truststore.setCertificateEntry("nifi.apache.org", nodeCert)
+
+ // Save the truststore to disk
+ FileOutputStream fos = new
FileOutputStream("target/nifi.apache.org.ts.jks")
+ truststore.store(fos, "".chars)
+ }
+
+ @Test
+ @Ignore("Used to create passwordless truststore file for testing
NIFI-6770")
+ void createLocalPasswordlessTruststore() {
+ KeyStore truststoreWithPassword = KeyStore.getInstance("JKS")
+ truststoreWithPassword.load(new
FileInputStream("/Users/alopresto/Workspace/nifi/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/truststore.jks"),
"passwordpassword".chars)
+ Certificate nodeCert =
truststoreWithPassword.getCertificate("nifi-cert")
+
+ // Create a JKS truststore containing that cert as a trustedCertEntry
and do not put a password on the truststore
+ KeyStore truststore = KeyStore.getInstance("JKS")
+ // Explicitly set the second parameter to empty to avoid a password
+ truststore.load(null, "".chars)
+ truststore.setCertificateEntry("nifi.apache.org", nodeCert)
+
+ // Save the truststore to disk
+ FileOutputStream fos = new
FileOutputStream("/Users/alopresto/Workspace/nifi/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/truststore.no-password.jks")
+ truststore.store(fos, "".chars)
+ }
+}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextFactory.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextFactory.java
index 8f43339..6159264 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextFactory.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextFactory.java
@@ -98,7 +98,6 @@ public final class SslContextFactory {
private static boolean hasTruststoreProperties(final NiFiProperties props)
{
return
(StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))
- &&
StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD))
&&
StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)));
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/java/org/apache/nifi/framework/security/util/SslContextFactoryTest.java
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/java/org/apache/nifi/framework/security/util/SslContextFactoryTest.java
index 93b2c8f..6bcaa9f 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/java/org/apache/nifi/framework/security/util/SslContextFactoryTest.java
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/java/org/apache/nifi/framework/security/util/SslContextFactoryTest.java
@@ -16,29 +16,30 @@
*/
package org.apache.nifi.framework.security.util;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.util.NiFiProperties;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import java.io.File;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
/**
*/
public class SslContextFactoryTest {
private NiFiProperties mutualAuthProps;
private NiFiProperties authProps;
+ private NiFiProperties noPasswordTruststore;
@Before
public void setUp() throws Exception {
final File ksFile = new
File(SslContextFactoryTest.class.getResource("/keystore.jks").toURI());
final File trustFile = new
File(SslContextFactoryTest.class.getResource("/truststore.jks").toURI());
+ final File noPasswordTrustFile = new
File(SslContextFactoryTest.class.getResource("/no-password-truststore.jks").toURI());
authProps = mock(NiFiProperties.class);
when(authProps.getProperty(NiFiProperties.SECURITY_KEYSTORE)).thenReturn(ksFile.getAbsolutePath());
@@ -53,6 +54,13 @@ public class SslContextFactoryTest {
when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)).thenReturn("passwordpassword");
+ noPasswordTruststore = mock(NiFiProperties.class);
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE)).thenReturn(ksFile.getAbsolutePath());
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)).thenReturn("passwordpassword");
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE)).thenReturn(noPasswordTrustFile.getAbsolutePath());
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
+
when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)).thenReturn("");
}
@Test
@@ -65,4 +73,8 @@ public class SslContextFactoryTest {
SslContextFactory.createSslContext(authProps);
}
+ @Test
+ public void testCreateSslContextWithNoPasswordTruststore() {
+
Assert.assertNotNull(SslContextFactory.createSslContext(noPasswordTruststore));
+ }
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/no-password-truststore.jks
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/no-password-truststore.jks
new file mode 100644
index 0000000..aa1ce5d
Binary files /dev/null and
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/no-password-truststore.jks
differ
diff --git
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java
index 0fe5dab..c344410 100644
---
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java
+++
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java
@@ -16,12 +16,54 @@
*/
package org.apache.nifi.processors.standard;
+import static org.apache.commons.lang3.StringUtils.trimToEmpty;
+
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.google.common.io.Files;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Proxy;
+import java.net.Proxy.Type;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
import java.security.Principal;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Credentials;
import okhttp3.MediaType;
@@ -53,8 +95,8 @@ import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.processor.ProcessContext;
-import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.ProcessorInitializationContext;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
@@ -68,50 +110,6 @@ import org.apache.nifi.stream.io.StreamUtils;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.Proxy;
-import java.net.Proxy.Type;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static org.apache.commons.lang3.StringUtils.trimToEmpty;
-
@SupportsBatching
@Tags({"http", "https", "rest", "client"})
@InputRequirement(Requirement.INPUT_ALLOWED)
@@ -686,8 +684,13 @@ public final class InvokeHTTP extends AbstractProcessor {
final String truststorePass = sslService.getTrustStorePassword();
final String truststoreType = sslService.getTrustStoreType();
+ char[] truststorePasswordChars = new char[0];
+ if (StringUtils.isNotBlank(truststorePass)) {
+ truststorePasswordChars = truststorePass.toCharArray();
+ }
+
KeyStore truststore = KeyStore.getInstance(truststoreType);
- truststore.load(new FileInputStream(truststoreLocation),
truststorePass.toCharArray());
+ truststore.load(new FileInputStream(truststoreLocation),
truststorePasswordChars);
trustManagerFactory.init(truststore);
}
diff --git
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestInvokeHttpSSL.java
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestInvokeHttpSSL.java
index 5ef2576..0dce0f0 100644
---
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestInvokeHttpSSL.java
+++
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestInvokeHttpSSL.java
@@ -17,19 +17,18 @@
package org.apache.nifi.processors.standard;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.nifi.processors.standard.util.TestInvokeHttpCommon;
-import org.apache.nifi.web.util.TestServer;
import org.apache.nifi.ssl.StandardSSLContextService;
import org.apache.nifi.util.TestRunners;
+import org.apache.nifi.web.util.TestServer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Executes the same tests as TestInvokeHttp but with one-way SSL enabled.
The Jetty server created for these tests
* will not require client certificates and the client will not use keystore
properties in the SSLContextService.
@@ -46,7 +45,7 @@ public class TestInvokeHttpSSL extends TestInvokeHttpCommon {
// don't commit this with this property enabled, or any 'mvn test'
will be really verbose
//
System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.standard",
"debug");
- // create the SSL properties, which basically store keystore /
trustore information
+ // create the SSL properties, which basically store keystore /
truststore information
// this is used by the StandardSSLContextService and the Jetty Server
serverSslProperties = createServerSslProperties(false);
sslProperties = createClientSslProperties(false);
@@ -138,8 +137,9 @@ public class TestInvokeHttpSSL extends TestInvokeHttpCommon
{
private static Map<String, String> getTruststoreProperties() {
final Map<String, String> map = new HashMap<>();
- map.put(StandardSSLContextService.TRUSTSTORE.getName(),
"src/test/resources/truststore.jks");
- map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(),
"passwordpassword");
+ map.put(StandardSSLContextService.TRUSTSTORE.getName(),
"src/test/resources/truststore.no-password.jks");
+ // Commented this line to test passwordless truststores for NIFI-6770
+ // map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(),
"passwordpassword");
map.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");
return map;
}
diff --git
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
index b45a0c0..0dee76c 100644
---
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
+++
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java
@@ -92,6 +92,7 @@ public class TestListenHTTP {
@After
public void teardown() {
proc.shutdownHttpServer();
+ new
File("/Users/alopresto/Workspace/nifi/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/my-file-text.txt").delete();
}
@Test
@@ -482,7 +483,7 @@ public class TestListenHTTP {
return bytes;
}
private File createTextFile(String fileName, String... lines) throws
IOException {
- File file = new File(fileName);
+ File file = new File("target/" + fileName);
file.deleteOnExit();
for (String string : lines) {
Files.append(string, file, Charsets.UTF_8);
diff --git
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/truststore.no-password.jks
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/truststore.no-password.jks
new file mode 100644
index 0000000..51f8f61
Binary files /dev/null and
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/truststore.no-password.jks
differ
diff --git
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java
index 4ad7bbe..f17c9bf 100644
---
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java
+++
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java
@@ -22,7 +22,6 @@ import java.util.List;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.processor.util.StandardValidators;
/**
@@ -73,9 +72,4 @@ public class StandardRestrictedSSLContextService extends
StandardSSLContextServi
public String getSslAlgorithm() {
return configContext.getProperty(RESTRICTED_SSL_ALGORITHM).getValue();
}
-
- @Override
- protected String getSSLProtocolForValidation(final ValidationContext
validationContext) {
- return
validationContext.getProperty(RESTRICTED_SSL_ALGORITHM).getValue();
- }
}
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 6b267ac..3f6be26 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
@@ -37,9 +37,10 @@ import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.reporting.InitializationException;
-import org.apache.nifi.security.util.CertificateUtils;
+import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.SslContextFactory;
+import org.apache.nifi.util.StringUtils;
@Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12",
"pkcs12", "pkcs", "tls"})
@CapabilityDescription("Standard implementation of the SSLContextService.
Provides the ability to configure "
@@ -70,7 +71,8 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
.name("Truststore Password")
.description("The password for the Truststore")
.defaultValue(null)
- .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .addValidator(Validator.VALID)
+ .required(false)
.sensitive(true)
.build();
public static final PropertyDescriptor KEYSTORE = new
PropertyDescriptor.Builder()
@@ -150,18 +152,6 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
}
throw new InitializationException(sb.toString());
}
-
- if (countNulls(context.getProperty(KEYSTORE).getValue(),
- context.getProperty(KEYSTORE_PASSWORD).getValue(),
- context.getProperty(KEYSTORE_TYPE).getValue(),
- context.getProperty(TRUSTSTORE).getValue(),
- context.getProperty(TRUSTSTORE_PASSWORD).getValue(),
- context.getProperty(TRUSTSTORE_TYPE).getValue()) >= 4) {
- throw new InitializationException(this + " does not have the
KeyStore or the TrustStore populated");
- }
-
- // verify that the filename, password, and type match
- createSSLContext(ClientAuth.REQUIRED);
}
@Override
@@ -171,21 +161,17 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
}
private static Validator createFileExistsAndReadableValidator() {
- return new Validator() {
- // Not using the FILE_EXISTS_VALIDATOR because the default is to
- // allow expression language
- @Override
- public ValidationResult validate(String subject, String input,
ValidationContext context) {
- final File file = new File(input);
- final boolean valid = file.exists() && file.canRead();
- final String explanation = valid ? null : "File " + file + "
does not exist or cannot be read";
- return new ValidationResult.Builder()
- .subject(subject)
- .input(input)
- .valid(valid)
- .explanation(explanation)
- .build();
- }
+ // Not using the FILE_EXISTS_VALIDATOR because the default is to allow
expression language
+ return (subject, input, context) -> {
+ final File file = new File(input);
+ final boolean valid = file.exists() && file.canRead();
+ final String explanation = valid ? null : "File " + file + " does
not exist or cannot be read";
+ return new ValidationResult.Builder()
+ .subject(subject)
+ .input(input)
+ .valid(valid)
+ .explanation(explanation)
+ .build();
};
}
@@ -210,32 +196,6 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
results.addAll(validateStore(validationContext.getProperties(),
KeystoreValidationGroup.KEYSTORE));
results.addAll(validateStore(validationContext.getProperties(),
KeystoreValidationGroup.TRUSTSTORE));
- if (countNulls(validationContext.getProperty(KEYSTORE).getValue(),
- validationContext.getProperty(KEYSTORE_PASSWORD).getValue(),
- validationContext.getProperty(KEYSTORE_TYPE).getValue(),
- validationContext.getProperty(TRUSTSTORE).getValue(),
- validationContext.getProperty(TRUSTSTORE_PASSWORD).getValue(),
- validationContext.getProperty(TRUSTSTORE_TYPE).getValue())
- >= 4) {
- results.add(new ValidationResult.Builder()
- .subject(this.getClass().getSimpleName() + " : " +
getIdentifier())
- .valid(false)
- .explanation("Does not have the KeyStore or the TrustStore
populated")
- .build());
- }
- if (results.isEmpty()) {
- // verify that the filename, password, and type match
- try {
- verifySslConfig(validationContext);
- } catch (ProcessException e) {
- results.add(new ValidationResult.Builder()
- .subject(getClass().getSimpleName() + " : " +
getIdentifier())
- .valid(false)
- .explanation(e.getMessage())
- .build());
- }
- }
-
isValidated = results.isEmpty();
return results;
@@ -250,64 +210,21 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
return VALIDATION_CACHE_EXPIRATION;
}
- protected String getSSLProtocolForValidation(final ValidationContext
validationContext) {
- return validationContext.getProperty(SSL_ALGORITHM).getValue();
- }
-
- private void verifySslConfig(final ValidationContext validationContext)
throws ProcessException {
- final String protocol = getSSLProtocolForValidation(validationContext);
- 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(
- validationContext.getProperty(TRUSTSTORE).getValue(),
-
validationContext.getProperty(TRUSTSTORE_PASSWORD).getValue().toCharArray(),
-
validationContext.getProperty(TRUSTSTORE_TYPE).getValue(),
- protocol);
- return;
- }
- final String truststoreFile =
validationContext.getProperty(TRUSTSTORE).getValue();
- if (truststoreFile == null) {
- SslContextFactory.createSslContext(
- validationContext.getProperty(KEYSTORE).getValue(),
-
validationContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(),
- keyPassword,
-
validationContext.getProperty(KEYSTORE_TYPE).getValue(),
- protocol);
- return;
- }
-
- 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(),
- validationContext.getProperty(TRUSTSTORE_TYPE).getValue(),
-
org.apache.nifi.security.util.SslContextFactory.ClientAuth.REQUIRED,
- protocol);
- } catch (final Exception e) {
- throw new ProcessException(e);
- }
- }
-
@Override
public SSLContext createSSLContext(final ClientAuth clientAuth) throws
ProcessException {
final String protocol = getSslAlgorithm();
try {
final PropertyValue keyPasswdProp =
configContext.getProperty(KEY_PASSWORD);
+ final PropertyValue truststorePasswordProp =
configContext.getProperty(TRUSTSTORE_PASSWORD);
final char[] keyPassword = keyPasswdProp.isSet() ?
keyPasswdProp.getValue().toCharArray() : null;
+ final char[] truststorePassword = truststorePasswordProp.isSet() ?
truststorePasswordProp.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(),
+ truststorePassword,
configContext.getProperty(TRUSTSTORE_TYPE).getValue(),
protocol);
}
@@ -329,7 +246,7 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
keyPassword,
configContext.getProperty(KEYSTORE_TYPE).getValue(),
configContext.getProperty(TRUSTSTORE).getValue(),
-
configContext.getProperty(TRUSTSTORE_PASSWORD).getValue().toCharArray(),
+ truststorePassword,
configContext.getProperty(TRUSTSTORE_TYPE).getValue(),
org.apache.nifi.security.util.SslContextFactory.ClientAuth.valueOf(clientAuth.name()),
protocol);
@@ -350,12 +267,13 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
@Override
public String getTrustStorePassword() {
- return configContext.getProperty(TRUSTSTORE_PASSWORD).getValue();
+ PropertyValue truststorePassword =
configContext.getProperty(TRUSTSTORE_PASSWORD);
+ return truststorePassword.isSet() ? truststorePassword.getValue() : "";
}
@Override
public boolean isTrustStoreConfigured() {
- return getTrustStoreFile() != null && getTrustStorePassword() != null
&& getTrustStoreType() != null;
+ return getTrustStoreFile() != null && getTrustStoreType() != null;
}
@Override
@@ -388,71 +306,227 @@ public class StandardSSLContextService extends
AbstractControllerService impleme
return configContext.getProperty(SSL_ALGORITHM).getValue();
}
+ /**
+ * Returns a list of {@link ValidationResult}s for the provided
+ * keystore/truststore properties. Called during
+ * {@link #customValidate(ValidationContext)}.
+ *
+ * @param properties the map of component properties
+ * @param keyStoreOrTrustStore an enum {@link KeystoreValidationGroup}
indicating keystore or truststore because logic is different
+ * @return the list of validation results (empty means valid)
+ */
private static Collection<ValidationResult> validateStore(final
Map<PropertyDescriptor, String> properties,
final
KeystoreValidationGroup keyStoreOrTrustStore) {
- final Collection<ValidationResult> results = new ArrayList<>();
-
- final String filename;
- final String password;
- final String type;
+ List<ValidationResult> results;
if (keyStoreOrTrustStore == KeystoreValidationGroup.KEYSTORE) {
- filename = properties.get(KEYSTORE);
- password = properties.get(KEYSTORE_PASSWORD);
- type = properties.get(KEYSTORE_TYPE);
+ results = validateKeystore(properties);
} else {
- filename = properties.get(TRUSTSTORE);
- password = properties.get(TRUSTSTORE_PASSWORD);
- type = properties.get(TRUSTSTORE_TYPE);
+ results = validateTruststore(properties);
+ }
+
+ if (keystorePropertiesEmpty(properties) &&
truststorePropertiesEmpty(properties)) {
+ results.add(new
ValidationResult.Builder().valid(false).explanation("Either the keystore and/or
truststore must be populated").subject("Keystore/truststore
properties").build());
}
- final String keystoreDesc = (keyStoreOrTrustStore ==
KeystoreValidationGroup.KEYSTORE) ? "Keystore" : "Truststore";
+ return results;
+ }
+
+ private static boolean keystorePropertiesEmpty(Map<PropertyDescriptor,
String> properties) {
+ return StringUtils.isBlank(properties.get(KEYSTORE)) &&
StringUtils.isBlank(properties.get(KEYSTORE_PASSWORD)) &&
StringUtils.isBlank(properties.get(KEYSTORE_TYPE));
+ }
+
+ private static boolean truststorePropertiesEmpty(Map<PropertyDescriptor,
String> properties) {
+ return StringUtils.isBlank(properties.get(TRUSTSTORE)) &&
StringUtils.isBlank(properties.get(TRUSTSTORE_PASSWORD)) &&
StringUtils.isBlank(properties.get(TRUSTSTORE_TYPE));
+ }
+
+ /**
+ * Returns the count of {@code null} objects in the parameters. Used for
keystore/truststore validation.
+ *
+ * @param objects a variable array of objects, some of which can be null
+ * @return the count of provided objects which were null
+ */
+ private static int countNulls(Object... objects) {
+ int count = 0;
+ for (final Object x : objects) {
+ if (x == null) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Returns a list of {@link ValidationResult}s for keystore validity
checking. Ensures none or all of the properties
+ * are populated; if populated, validates the keystore file on disk and
password as well.
+ *
+ * @param properties the component properties
+ * @return the list of validation results (empty is valid)
+ */
+ private static List<ValidationResult> validateKeystore(final
Map<PropertyDescriptor, String> properties) {
+ final List<ValidationResult> results = new ArrayList<>();
+
+ final String filename = properties.get(KEYSTORE);
+ final String password = properties.get(KEYSTORE_PASSWORD);
+ final String keyPassword = properties.get(KEY_PASSWORD);
+ final String type = properties.get(KEYSTORE_TYPE);
final int nulls = countNulls(filename, password, type);
if (nulls != 3 && nulls != 0) {
- results.add(new
ValidationResult.Builder().valid(false).explanation("Must set either 0 or 3
properties for " + keystoreDesc)
- .subject(keystoreDesc + " Properties").build());
+ results.add(new
ValidationResult.Builder().valid(false).explanation("Must set either 0 or 3
properties for Keystore")
+ .subject("Keystore Properties").build());
} else if (nulls == 0) {
// all properties were filled in.
- final File file = new File(filename);
- if (!file.exists() || !file.canRead()) {
- results.add(new ValidationResult.Builder()
- .valid(false)
- .subject(keystoreDesc + " Properties")
- .explanation("Cannot access file " +
file.getAbsolutePath())
- .build());
- } else {
- try {
- final boolean storeValid =
CertificateUtils.isStoreValid(file.toURI().toURL(), KeystoreType.valueOf(type),
password.toCharArray());
- if (!storeValid) {
- results.add(new ValidationResult.Builder()
- .subject(keystoreDesc + " Properties")
- .valid(false)
- .explanation("Invalid KeyStore Password or
Type specified for file " + filename)
- .build());
- }
- } catch (MalformedURLException e) {
+ List<ValidationResult> fileValidationResults =
validateKeystoreFile(filename, password, keyPassword, type);
+ results.addAll(fileValidationResults);
+ }
+
+ // If nulls == 3, no values were populated, so just return
+
+ return results;
+ }
+
+
+ /**
+ * Returns a list of {@link ValidationResult}s for truststore validity
checking. Ensures none of the properties
+ * are populated or at least filename and type are populated; if
populated, validates the truststore file on disk
+ * and password as well.
+ *
+ * @param properties the component properties
+ * @return the list of validation results (empty is valid)
+ */
+ private static List<ValidationResult> validateTruststore(final
Map<PropertyDescriptor, String> properties) {
+ String filename = properties.get(TRUSTSTORE);
+ String password = properties.get(TRUSTSTORE_PASSWORD);
+ String type = properties.get(TRUSTSTORE_TYPE);
+
+ List<ValidationResult> results = new ArrayList<>();
+
+ if (!StringUtils.isBlank(filename) && !StringUtils.isBlank(type)) {
+ // In this case both the filename and type are populated, which is
sufficient
+ results.addAll(validateTruststoreFile(filename, password, type));
+ } else {
+ // The filename or type are blank; all values must be unpopulated
for this to be valid
+ if (!StringUtils.isBlank(filename) || !StringUtils.isBlank(type)) {
+ results.add(new
ValidationResult.Builder().valid(false).explanation("If the truststore filename
or type are set, both must be populated").subject("Truststore
Properties").build());
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Returns a list of {@link ValidationResult}s when validating an actual
JKS or PKCS12 file on disk. Verifies the
+ * file permissions and existence, and attempts to open the file given the
provided password.
+ *
+ * @param filename the path of the file on disk
+ * @param password the file password
+ * @param type the type (JKS or PKCS12)
+ * @return the list of validation results (empty is valid)
+ */
+ private static List<ValidationResult> validateTruststoreFile(String
filename, String password, String type) {
+ List<ValidationResult> results = new ArrayList<>();
+
+ final File file = new File(filename);
+ if (!file.exists() || !file.canRead()) {
+ results.add(new ValidationResult.Builder()
+ .valid(false)
+ .subject("Truststore Properties")
+ .explanation("Cannot access file " +
file.getAbsolutePath())
+ .build());
+ } else {
+ char[] passwordChars = new char[0];
+ if (!StringUtils.isBlank(password)) {
+ passwordChars = password.toCharArray();
+ }
+ try {
+ final boolean storeValid =
KeyStoreUtils.isStoreValid(file.toURI().toURL(), KeystoreType.valueOf(type),
passwordChars);
+ if (!storeValid) {
results.add(new ValidationResult.Builder()
- .subject(keystoreDesc + " Properties")
+ .subject("Truststore Properties")
.valid(false)
- .explanation("Malformed URL from file: " + e)
+ .explanation("Invalid truststore password or type
specified for file " + filename)
.build());
}
+
+ } catch (MalformedURLException e) {
+ results.add(new ValidationResult.Builder()
+ .subject("Truststore Properties")
+ .valid(false)
+ .explanation("Malformed URL from file: " + e)
+ .build());
}
}
return results;
}
- private static int countNulls(Object... objects) {
- int count = 0;
- for (final Object x : objects) {
- if (x == null) {
- count++;
+ /**
+ * Returns a list of {@link ValidationResult}s when validating an actual
JKS or PKCS12 file on disk. Verifies the
+ * file permissions and existence, and attempts to open the file given the
provided (keystore or key) password.
+ *
+ * @param filename the path of the file on disk
+ * @param password the file password
+ * @param keyPassword the (optional) key-specific password
+ * @param type the type (JKS or PKCS12)
+ * @return the list of validation results (empty is valid)
+ */
+ private static List<ValidationResult> validateKeystoreFile(String
filename, String password, String keyPassword, String type) {
+ List<ValidationResult> results = new ArrayList<>();
+
+ final File file = new File(filename);
+ if (!file.exists() || !file.canRead()) {
+ results.add(new ValidationResult.Builder()
+ .valid(false)
+ .subject("Keystore Properties")
+ .explanation("Cannot access file " +
file.getAbsolutePath())
+ .build());
+ } else {
+ char[] passwordChars = new char[0];
+ if (!StringUtils.isBlank(password)) {
+ passwordChars = password.toCharArray();
+ }
+ try {
+ final boolean storeValid =
KeyStoreUtils.isStoreValid(file.toURI().toURL(), KeystoreType.valueOf(type),
passwordChars);
+ if (!storeValid) {
+ results.add(new ValidationResult.Builder()
+ .subject("Keystore Properties")
+ .valid(false)
+ .explanation("Invalid keystore password or type
specified for file " + filename)
+ .build());
+ }
+
+ // The key password can be explicitly set (and can be the same
as the
+ // keystore password or different), or it can be left blank.
In the event
+ // it's blank, the keystore password will be used
+ char[] keyPasswordChars = new char[0];
+ if (StringUtils.isBlank(keyPassword) ||
keyPassword.equals(password)) {
+ keyPasswordChars = passwordChars;
+ }
+ if (!StringUtils.isBlank(keyPassword)) {
+ keyPasswordChars = keyPassword.toCharArray();
+ }
+
+ boolean keyPasswordValid =
KeyStoreUtils.isKeyPasswordCorrect(file.toURI().toURL(),
KeystoreType.valueOf(type), passwordChars, keyPasswordChars);
+ if (!keyPasswordValid) {
+ results.add(new ValidationResult.Builder()
+ .subject("Keystore Properties")
+ .valid(false)
+ .explanation("Invalid key password specified for
file " + filename)
+ .build());
+ }
+
+ } catch (MalformedURLException e) {
+ results.add(new ValidationResult.Builder()
+ .subject("Keystore Properties")
+ .valid(false)
+ .explanation("Malformed URL from file: " + e)
+ .build());
}
}
- return count;
+ return results;
}
public enum KeystoreValidationGroup {
diff --git
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/groovy/org/apache/nifi/ssl/StandardSSLContextServiceTest.groovy
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/groovy/org/apache/nifi/ssl/StandardSSLContextServiceTest.groovy
index 19c3568..06d5709 100644
---
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/groovy/org/apache/nifi/ssl/StandardSSLContextServiceTest.groovy
+++
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/groovy/org/apache/nifi/ssl/StandardSSLContextServiceTest.groovy
@@ -38,6 +38,7 @@ import org.junit.runners.JUnit4
import org.slf4j.Logger
import org.slf4j.LoggerFactory
+import javax.net.ssl.SSLContext
import java.security.Security
import static groovy.test.GroovyAssert.shouldFail
@@ -48,10 +49,12 @@ class StandardSSLContextServiceTest {
private static final String KEYSTORE_PATH =
"src/test/resources/keystore.jks"
private static final String TRUSTSTORE_PATH =
"src/test/resources/truststore.jks"
+ private static final String NO_PASSWORD_TRUSTSTORE_PATH =
"src/test/resources/no-password-truststore.jks"
private static final String TRUSTSTORE_PATH_WITH_EL =
"\${someAttribute}/truststore.jks"
private static final String KEYSTORE_PASSWORD = "passwordpassword"
private static final String TRUSTSTORE_PASSWORD = "passwordpassword"
+ private static final String TRUSTSTORE_NO_PASSWORD = ""
private static final String KEYSTORE_TYPE = "JKS"
private static final String TRUSTSTORE_TYPE = "JKS"
@@ -101,6 +104,106 @@ class StandardSSLContextServiceTest {
}
@Test
+ void testTruststoreWithNoPasswordIsValid() {
+ // Arrange
+ TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
+ String controllerServiceId = "ssl-context"
+ final SSLContextService sslContextService = new
StandardSSLContextService()
+ runner.addControllerService(controllerServiceId, sslContextService)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE, NO_PASSWORD_TRUSTSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_PASSWORD, TRUSTSTORE_NO_PASSWORD)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
+ runner.enableControllerService(sslContextService)
+
+ // Act
+ runner.assertValid(sslContextService)
+
+ // Assert
+ final MockProcessContext processContext = (MockProcessContext)
runner.getProcessContext()
+ assert
processContext.getControllerServiceProperties(sslContextService).get(StandardSSLContextService.TRUSTSTORE,
"") == NO_PASSWORD_TRUSTSTORE_PATH
+ }
+
+ @Test
+ void testTruststoreWithNullPasswordIsValid() {
+ // Arrange
+ TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
+ String controllerServiceId = "ssl-context"
+ final SSLContextService sslContextService = new
StandardSSLContextService()
+ runner.addControllerService(controllerServiceId, sslContextService)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE, NO_PASSWORD_TRUSTSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_PASSWORD, null as String)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
+ runner.enableControllerService(sslContextService)
+
+ // Act
+ runner.assertValid(sslContextService)
+
+ // Assert
+ final MockProcessContext processContext = (MockProcessContext)
runner.getProcessContext()
+ assert
processContext.getControllerServiceProperties(sslContextService).get(StandardSSLContextService.TRUSTSTORE,
"") == NO_PASSWORD_TRUSTSTORE_PATH
+ }
+
+ @Test
+ void testTruststoreWithMissingPasswordIsValid() {
+ // Arrange
+ TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
+ String controllerServiceId = "ssl-context"
+ final SSLContextService sslContextService = new
StandardSSLContextService()
+ runner.addControllerService(controllerServiceId, sslContextService)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE, NO_PASSWORD_TRUSTSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
+ runner.enableControllerService(sslContextService)
+
+ // Act
+ runner.assertValid(sslContextService)
+
+ // Assert
+ final MockProcessContext processContext = (MockProcessContext)
runner.getProcessContext()
+ assert
processContext.getControllerServiceProperties(sslContextService).get(StandardSSLContextService.TRUSTSTORE,
"") == NO_PASSWORD_TRUSTSTORE_PATH
+ }
+
+ @Test
+ void testShouldConnectWithPasswordlessTruststore() {
+ // Arrange
+ TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
+ String controllerServiceId = "ssl-context"
+ final SSLContextService sslContextService = new
StandardSSLContextService()
+ runner.addControllerService(controllerServiceId, sslContextService)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE, NO_PASSWORD_TRUSTSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
+ runner.enableControllerService(sslContextService)
+ runner.assertValid(sslContextService)
+
+ // Act
+ SSLContext sslContext =
sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE)
+
+ // Assert
+ assert sslContext
+ }
+
+ @Test
+ void testShouldConnectWithPasswordlessTruststoreWhenKeystorePresent() {
+ // Arrange
+ TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
+ String controllerServiceId = "ssl-context"
+ final SSLContextService sslContextService = new
StandardSSLContextService()
+ runner.addControllerService(controllerServiceId, sslContextService)
+ runner.setProperty(sslContextService,
StandardSSLContextService.KEYSTORE, KEYSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.KEYSTORE_PASSWORD, KEYSTORE_PASSWORD)
+ runner.setProperty(sslContextService,
StandardSSLContextService.KEYSTORE_TYPE, KEYSTORE_TYPE)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE, NO_PASSWORD_TRUSTSTORE_PATH)
+ runner.setProperty(sslContextService,
StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE)
+ runner.enableControllerService(sslContextService)
+ runner.assertValid(sslContextService)
+
+ // Act
+ SSLContext sslContext =
sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE)
+
+ // Assert
+ assert sslContext
+ }
+
+ @Test
void testShouldNotValidateExpressionLanguageInFileValidator() {
// Arrange
TestRunner runner = TestRunners.newTestRunner(TestProcessor.class)
@@ -117,7 +220,7 @@ class StandardSSLContextServiceTest {
}
// Assert
- assert msg =~ "Cannot enable Controller Service SSLContextService.*
because it is in an invalid state: 'Truststore Filename'.* is invalid because
File.* does not exist or cannot be read";
+ assert msg =~ "Cannot enable Controller Service SSLContextService.*
because it is in an invalid state: 'Truststore Filename'.* is invalid because
File.* does not exist or cannot be read"
runner.assertNotValid(sslContextService)
}
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 b98824c..7b304aa 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
@@ -33,9 +33,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-
import javax.net.ssl.SSLContext;
-
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
@@ -60,32 +58,34 @@ public class SSLContextServiceTest {
private final String TRUSTSTORE_PATH = "src/test/resources/truststore.jks";
private final String DIFFERENT_PASS_KEYSTORE_PATH =
"src/test/resources/keystore-different-password.jks";
private final String DIFFERENT_KEYSTORE_PASSWORD = "differentpassword";
+ private static final String KEYSTORE_WITH_KEY_PASSWORD_PATH =
"src/test/resources/keystore-with-key-password.jks";
+
@Rule
public TemporaryFolder tmp = new TemporaryFolder(new
File("src/test/resources"));
@Test
- public void testBad1() throws InitializationException {
+ public void testShouldFailToAddControllerServiceWithNoProperties() throws
InitializationException {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
- runner.addControllerService("test-bad1", service, properties);
+ runner.addControllerService("test-no-properties", service, properties);
runner.assertNotValid(service);
}
@Test
- public void testBad2() throws InitializationException {
+ public void testShouldFailToAddControllerServiceWithoutKeystoreType()
throws InitializationException {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
properties.put(StandardSSLContextService.KEYSTORE.getName(),
KEYSTORE_PATH);
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
KEYSTORE_AND_TRUSTSTORE_PASSWORD);
- runner.addControllerService("test-bad2", service, properties);
+ runner.addControllerService("test-no-keystore-type", service,
properties);
runner.assertNotValid(service);
}
@Test
- public void testBad3() throws InitializationException {
+ public void testShouldFailToAddControllerServiceWithOnlyTruststorePath()
throws InitializationException {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
@@ -93,12 +93,12 @@ public class SSLContextServiceTest {
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
KEYSTORE_AND_TRUSTSTORE_PASSWORD);
properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(),
JKS_TYPE);
properties.put(StandardSSLContextService.TRUSTSTORE.getName(),
TRUSTSTORE_PATH);
- runner.addControllerService("test-bad3", service, properties);
+ runner.addControllerService("test-no-truststore-password-or-type",
service, properties);
runner.assertNotValid(service);
}
@Test
- public void testBad4() throws InitializationException {
+ public void testShouldFailToAddControllerServiceWithWrongPasswords()
throws InitializationException {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
@@ -108,13 +108,13 @@ public class SSLContextServiceTest {
properties.put(StandardSSLContextService.TRUSTSTORE.getName(),
TRUSTSTORE_PATH);
properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(),
"wrongpassword");
properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(),
JKS_TYPE);
- runner.addControllerService("test-bad4", service, properties);
+ runner.addControllerService("test-wrong-passwords", service,
properties);
runner.assertNotValid(service);
}
@Test
- public void testBad5() throws InitializationException {
+ public void testShouldFailToAddControllerServiceWithNonExistentFiles()
throws InitializationException {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
@@ -124,7 +124,7 @@ public class SSLContextServiceTest {
properties.put(StandardSSLContextService.TRUSTSTORE.getName(),
TRUSTSTORE_PATH);
properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(),
KEYSTORE_AND_TRUSTSTORE_PASSWORD);
properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(),
JKS_TYPE);
- runner.addControllerService("test-bad5", service, properties);
+ runner.addControllerService("test-keystore-file-does-not-exist",
service, properties);
runner.assertNotValid(service);
}
@@ -297,8 +297,8 @@ public class SSLContextServiceTest {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
- properties.put(StandardSSLContextService.KEYSTORE.getName(),
DIFFERENT_PASS_KEYSTORE_PATH);
-
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
DIFFERENT_KEYSTORE_PASSWORD);
+ properties.put(StandardSSLContextService.KEYSTORE.getName(),
KEYSTORE_WITH_KEY_PASSWORD_PATH);
+
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
KEYSTORE_AND_TRUSTSTORE_PASSWORD);
properties.put(StandardSSLContextService.KEY_PASSWORD.getName(),
"keypassword");
properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(),
JKS_TYPE);
runner.addControllerService("test-diff-keys", service, properties);
@@ -307,9 +307,7 @@ public class SSLContextServiceTest {
runner.setProperty("SSL Context Svc ID", "test-diff-keys");
runner.assertValid();
Assert.assertNotNull(service);
- assertTrue(service instanceof StandardSSLContextService);
- SSLContextService sslService = service;
- sslService.createSSLContext(ClientAuth.NONE);
+ service.createSSLContext(ClientAuth.NONE);
} catch (Exception e) {
System.out.println(e);
Assert.fail("Should not have thrown a exception " +
e.getMessage());
@@ -327,8 +325,8 @@ public class SSLContextServiceTest {
final TestRunner runner =
TestRunners.newTestRunner(TestProcessor.class);
final SSLContextService service = new StandardSSLContextService();
final Map<String, String> properties = new HashMap<>();
- properties.put(StandardSSLContextService.KEYSTORE.getName(),
DIFFERENT_PASS_KEYSTORE_PATH);
-
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
DIFFERENT_KEYSTORE_PASSWORD);
+ properties.put(StandardSSLContextService.KEYSTORE.getName(),
KEYSTORE_WITH_KEY_PASSWORD_PATH);
+
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(),
KEYSTORE_AND_TRUSTSTORE_PASSWORD);
properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(),
JKS_TYPE);
runner.addControllerService("test-diff-keys", service, properties);
diff --git
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/keystore-with-key-password.jks
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/keystore-with-key-password.jks
new file mode 100644
index 0000000..56216d9
Binary files /dev/null and
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/keystore-with-key-password.jks
differ
diff --git
a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/no-password-truststore.jks
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/no-password-truststore.jks
new file mode 100644
index 0000000..aa1ce5d
Binary files /dev/null and
b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/resources/no-password-truststore.jks
differ