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 fdea4c5 NIFI-6026 - First commit which adds a new tls-toolkit mode called Keystore. Should instead integrate the functionality into standalone mode. NIFI-6026 - Updated splitKeystore to use standalone mode with a -splitKeystore argument. NIFI-6026 - Removed unused file and references. NIFI-6026 - Removed some code that is not necessary after doing some argument checking in the command line parsing. NIFI-6026 - Made some small changes to only require keystore password if keystore [...] fdea4c5 is described below commit fdea4c54dfb1b66073aa1ff603b5e814721ac5a7 Author: thenatog <thena...@gmail.com> AuthorDate: Sun Feb 24 21:13:49 2019 -0500 NIFI-6026 - First commit which adds a new tls-toolkit mode called Keystore. Should instead integrate the functionality into standalone mode. NIFI-6026 - Updated splitKeystore to use standalone mode with a -splitKeystore argument. NIFI-6026 - Removed unused file and references. NIFI-6026 - Removed some code that is not necessary after doing some argument checking in the command line parsing. NIFI-6026 - Made some small changes to only require keystore password if keystore and key passwords are the same. Added some more tests. NIFI-6026 - Added some more unit tests as per Andy's request. Also added a check for empty keystores. Made tests a bit cleaner. NIFI-6026 - Added empty keystore used by unit tests. NIFI-6026 Fixed minor formatting and checkstyle issues. This closes #3340. Signed-off-by: Andy LoPresto <alopre...@apache.org> --- .../src/test/resources/keystore.jks | Bin 0 -> 3088 bytes .../tls/configuration/StandaloneConfig.java | 9 ++ .../tls/standalone/TlsToolkitStandalone.java | 59 ++++++-- .../TlsToolkitStandaloneCommandLine.java | 76 ++++++++--- .../apache/nifi/toolkit/tls/util/TlsHelper.java | 104 +++++++++++--- .../TlsToolkitStandaloneCommandLineTest.java | 149 ++++++++++++++++++--- .../nifi/toolkit/tls/util/TlsHelperTest.java | 147 +++++++++++++++----- .../src/test/resources/empty-keystore.jks | Bin 0 -> 32 bytes .../src/test/resources/keystore.jks | Bin 0 -> 3975 bytes 9 files changed, 437 insertions(+), 107 deletions(-) diff --git a/nifi-commons/nifi-security-utils/src/test/resources/keystore.jks b/nifi-commons/nifi-security-utils/src/test/resources/keystore.jks new file mode 100644 index 0000000..246fe88 Binary files /dev/null and b/nifi-commons/nifi-security-utils/src/test/resources/keystore.jks differ diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/StandaloneConfig.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/StandaloneConfig.java index d8e86347..4fd6af5 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/StandaloneConfig.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/StandaloneConfig.java @@ -32,6 +32,7 @@ public class StandaloneConfig extends TlsConfig { private List<String> clientPasswords; private boolean clientPasswordsGenerated; private boolean overwrite; + private boolean splitKeystore; // TODO: A lot of these fields are null and cause NPEs in {@link TlsToolkitStandalone} when not executed with expected input @@ -90,4 +91,12 @@ public class StandaloneConfig extends TlsConfig { public void setInstanceDefinitions(List<InstanceDefinition> instanceDefinitions) { this.instanceDefinitions = instanceDefinitions; } + + public void setSplitKeystore(boolean splitKeystore) { + this.splitKeystore = splitKeystore; + } + + public boolean isSplitKeystore() { + return this.splitKeystore; + } } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java index ffe4c5d..7407e5d 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandalone.java @@ -17,20 +17,6 @@ package org.apache.nifi.toolkit.tls.standalone; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.SignatureException; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; import org.apache.nifi.security.util.CertificateUtils; import org.apache.nifi.security.util.KeyStoreUtils; import org.apache.nifi.security.util.KeystoreType; @@ -50,6 +36,28 @@ import org.bouncycastle.util.io.pem.PemWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + public class TlsToolkitStandalone { public static final String NIFI_KEY = "nifi-key"; public static final String NIFI_CERT = "nifi-cert"; @@ -66,6 +74,28 @@ public class TlsToolkitStandalone { this.outputStreamFactory = outputStreamFactory; } + public void splitKeystore(StandaloneConfig standaloneConfig) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream(standaloneConfig.getKeyStore()), standaloneConfig.getKeyStorePassword().toCharArray()); + + if(keyStore.size() == 0) { + throw new KeyStoreException("Provided keystore " + standaloneConfig.getKeyStore() + " was empty. No cert/key pairs to output to file."); + } + + if(standaloneConfig.getKeyPassword() == null || standaloneConfig.getKeyPassword().isEmpty()) { + splitKeystore(keyStore, standaloneConfig.getKeyStorePassword().toCharArray(), standaloneConfig.getBaseDir()); + } else { + splitKeystore(keyStore, standaloneConfig.getKeyPassword().toCharArray(), standaloneConfig.getBaseDir()); + } + } + + private void splitKeystore(KeyStore keyStore, char[] keyPassphrase, File outputDirectory) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { + HashMap<String, Certificate> certificates = TlsHelper.extractCerts(keyStore); + HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, keyPassphrase); + TlsHelper.outputCertsAsPem(certificates, outputDirectory, ".crt"); + TlsHelper.outputKeysAsPem(keys, outputDirectory, ".key"); + } + public void createNifiKeystoresAndTrustStores(StandaloneConfig standaloneConfig) throws GeneralSecurityException, IOException { // TODO: This 200 line method should be refactored, as it is difficult to test the various validations separately from the filesystem interaction and generation logic File baseDir = standaloneConfig.getBaseDir(); @@ -215,6 +245,7 @@ public class TlsToolkitStandalone { tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"), hostname, instanceDefinition.getNumber())); tlsClientManager.write(outputStreamFactory); + if (logger.isInfoEnabled()) { logger.info("Successfully generated TLS configuration for " + hostname + " " + hostIdentifierNumber + " in " + hostDir); } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java index f11ad7b..f4c4d8b 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLine.java @@ -17,18 +17,6 @@ package org.apache.nifi.toolkit.tls.standalone; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; import org.apache.nifi.toolkit.tls.commandLine.BaseTlsToolkitCommandLine; import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException; @@ -42,6 +30,19 @@ import org.apache.nifi.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + /** * Command line parser for a StandaloneConfig object and a main entry point to invoke the parser and run the standalone generator */ @@ -60,6 +61,7 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { public static final String NIFI_DN_SUFFIX_ARG = "nifiDnSuffix"; public static final String SUBJECT_ALTERNATIVE_NAMES_ARG = "subjectAlternativeNames"; public static final String ADDITIONAL_CA_CERTIFICATE_ARG = "additionalCACertificate"; + public static final String SPLIT_KEYSTORE_ARG = "splitKeystore"; public static final String DEFAULT_OUTPUT_DIRECTORY = calculateDefaultOutputDirectory(Paths.get(".")); @@ -86,10 +88,14 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { private List<String> clientPasswords; private boolean clientPasswordsGenerated; private boolean overwrite; + private boolean splitKeystore = false; + private String splitKeystoreFile; private String dnPrefix; private String dnSuffix; private String domainAlternativeNames; private String additionalCACertificatePath; + private String keyPassword; + private String keyStorePassword; public TlsToolkitStandaloneCommandLine() { this(new PasswordUtil()); @@ -113,6 +119,8 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { addOptionWithArg(null, NIFI_DN_SUFFIX_ARG, "String to append to hostname(s) when determining DN.", TlsConfig.DEFAULT_DN_SUFFIX); addOptionNoArg("O", OVERWRITE_ARG, "Overwrite existing host output."); addOptionWithArg(null, ADDITIONAL_CA_CERTIFICATE_ARG, "Path to additional CA certificate (used to sign toolkit CA certificate) in PEM format if necessary"); + addOptionWithArg(null, SPLIT_KEYSTORE_ARG, "Split out a given keystore into its unencrypted key and certificates. Use -S and -K to specify the keystore and key passwords."); + } public static void main(String[] args) { @@ -122,12 +130,24 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { } catch (CommandLineParseException e) { System.exit(e.getExitCode().ordinal()); } - try { - new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig()); - } catch (Exception e) { - tlsToolkitStandaloneCommandLine.printUsage("Error generating TLS configuration. (" + e.getMessage() + ")"); - System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal()); + + if(tlsToolkitStandaloneCommandLine.splitKeystore) { + StandaloneConfig conf = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + try { + new TlsToolkitStandalone().splitKeystore(conf); + } catch (Exception e) { + tlsToolkitStandaloneCommandLine.printUsage("Error splitting keystore. (" + e.getMessage() + ")"); + System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal()); + } + } else { + try { + new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig()); + } catch (Exception e) { + tlsToolkitStandaloneCommandLine.printUsage("Error generating TLS configuration. (" + e.getMessage() + ")"); + System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal()); + } } + System.exit(ExitCode.SUCCESS.ordinal()); } @@ -168,6 +188,17 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { clientPasswordsGenerated = commandLine.getOptionValues(CLIENT_CERT_PASSWORD_ARG) == null; overwrite = commandLine.hasOption(OVERWRITE_ARG); + if(commandLine.hasOption(SPLIT_KEYSTORE_ARG)) { + if(commandLine.hasOption(KEY_STORE_PASSWORD_ARG)) { + splitKeystoreFile = commandLine.getOptionValue(SPLIT_KEYSTORE_ARG); + keyStorePassword = commandLine.getOptionValue(KEY_STORE_PASSWORD_ARG); + keyPassword = commandLine.getOptionValue(KEY_PASSWORD_ARG); + splitKeystore = true; + } else { + printUsageAndThrow("-splitKeystore specified but no keyStorePassword supplied.", ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS); + } + } + additionalCACertificatePath = commandLine.getOptionValue(ADDITIONAL_CA_CERTIFICATE_ARG); String nifiPropertiesFile = commandLine.getOptionValue(NIFI_PROPERTIES_FILE_ARG, ""); @@ -243,4 +274,15 @@ public class TlsToolkitStandaloneCommandLine extends BaseTlsToolkitCommandLine { return standaloneConfig; } + + public StandaloneConfig createSplitKeystoreConfig() { + StandaloneConfig splitKeystoreConfig = new StandaloneConfig(); + splitKeystoreConfig.setBaseDir(baseDir); + splitKeystoreConfig.setKeyPassword(keyPassword); + splitKeystoreConfig.setKeyStorePassword(keyStorePassword); + splitKeystoreConfig.setKeyStore(splitKeystoreFile); + splitKeystoreConfig.setSplitKeystore(splitKeystore); + + return splitKeystoreConfig; + } } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java index 20bc2d9..3d6d5c5 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/util/TlsHelper.java @@ -17,26 +17,6 @@ package org.apache.nifi.toolkit.tls.util; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.DERNull; @@ -63,6 +43,7 @@ import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; @@ -72,6 +53,35 @@ import org.bouncycastle.util.io.pem.PemWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; + public class TlsHelper { private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class); private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128; @@ -146,6 +156,60 @@ public class TlsHelper { return password; } + public static HashMap<String, Certificate> extractCerts(KeyStore keyStore) throws KeyStoreException { + HashMap<String, Certificate> certs = new HashMap<>(); + Enumeration<String> certAliases = keyStore.aliases(); + while(certAliases.hasMoreElements()) { + String alias = certAliases.nextElement(); + certs.put(alias, keyStore.getCertificate(alias)); + } + return certs; + } + + public static HashMap<String, Key> extractKeys(KeyStore keyStore, char[] privKeyPass) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { + HashMap<String, Key> keys = new HashMap<>(); + Enumeration<String> keyAliases = keyStore.aliases(); + while(keyAliases.hasMoreElements()) { + String alias = keyAliases.nextElement(); + Key key = keyStore.getKey(alias, privKeyPass); + if(key != null) { + keys.put(alias, key); + } else { + logger.warn("Key does not exist: Certificate with alias '" + alias + "' had no private key."); + } + } + return keys; + } + + public static void outputCertsAsPem(HashMap<String, Certificate> certs, File directory, String extension) { + certs.forEach((String alias, Certificate cert)->{ + try { + TlsHelper.outputAsPem(cert, alias, directory, extension); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + public static void outputKeysAsPem(HashMap<String, Key> keys, File directory, String extension) { + keys.forEach((String alias, Key key) -> { + try { + TlsHelper.outputAsPem(key, alias, directory, extension); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private static void outputAsPem(Object pemObj, String filename, File directory, String extension) throws IOException { + OutputStream outputStream = new FileOutputStream(new File(directory, TlsHelper.escapeFilename(filename) + extension)); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); + JcaPEMWriter pemWriter = new JcaPEMWriter(outputStreamWriter); + JcaMiscPEMGenerator pemGen = new JcaMiscPEMGenerator(pemObj); + pemWriter.writeObject(pemGen); + pemWriter.close(); + } + private static KeyPairGenerator createKeyPairGenerator(String algorithm, int keySize) throws NoSuchAlgorithmException { KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm); instance.initialize(keySize); diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java index 0fe004a..18f2e40 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneCommandLineTest.java @@ -17,18 +17,15 @@ package org.apache.nifi.toolkit.tls.standalone; -import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException; -import org.apache.nifi.toolkit.tls.commandLine.ExitCode; -import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition; -import org.apache.nifi.toolkit.tls.configuration.InstanceIdentifier; -import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig; -import org.apache.nifi.toolkit.tls.configuration.TlsConfig; -import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter; -import org.apache.nifi.toolkit.tls.util.PasswordUtil; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -36,7 +33,9 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyStoreException; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -44,25 +43,44 @@ import java.util.Properties; import java.util.Random; import java.util.function.Function; import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; +import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException; +import org.apache.nifi.toolkit.tls.commandLine.ExitCode; +import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition; +import org.apache.nifi.toolkit.tls.configuration.InstanceIdentifier; +import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig; +import org.apache.nifi.toolkit.tls.configuration.TlsConfig; +import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriter; +import org.apache.nifi.toolkit.tls.util.PasswordUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations; public class TlsToolkitStandaloneCommandLineTest { private SecureRandom secureRandom; private TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine; + + final String CHANGEIT = "changeit"; + final String keyPass = CHANGEIT; + final String keystorePass = CHANGEIT; + final String wrongPass = "wrongpass"; + private File outputFolder = null; + final String keystoreFile = getClass().getClassLoader().getResource("keystore.jks").getFile(); + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + @Before - public void setup() { + public void setup() throws IOException { + secureRandom = mock(SecureRandom.class); doAnswer(new ForwardsInvocations(new Random())).when(secureRandom).nextBytes(any(byte[].class)); tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine(new PasswordUtil(secureRandom)); + outputFolder = tempFolder.newFolder("splitKeystoreOutputDir"); } @Test @@ -445,4 +463,91 @@ public class TlsToolkitStandaloneCommandLineTest { properties.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); return properties; } + + @Test + public void testSplitKeystore() throws Exception { + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", keystoreFile, "-S", keystorePass, "-K", keyPass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + assertTrue(standaloneConfig.isSplitKeystore()); + assertEquals(keyPass, standaloneConfig.getKeyPassword()); + assertEquals(keystorePass, standaloneConfig.getKeyStorePassword()); + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + + assertTrue(outputFolder.listFiles().length == 3); + + // Validity checking of the output is done in TlsHelperTest + for(File file : outputFolder.listFiles()) { + assertTrue(file.length() > 0); + } + } + + @Test(expected = CommandLineParseException.class) + public void testSplitKeystoreMissingPasswords() throws Exception { + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", keystoreFile, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + + @Test + public void testSplitKeystoreWithSameKeystoreAndKeyPassword() throws Exception { + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", keystoreFile, "-S", keystorePass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + + @Test(expected = UnrecoverableKeyException.class) + public void testSplitKeystoreWrongKeyPass() throws Exception { + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", keystoreFile, "-S", keystorePass, "-K", wrongPass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + + @Test(expected = IOException.class) + public void testSplitKeystoreWrongKeystorePass() throws Exception { + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", keystoreFile, "-S", wrongPass, "-K", keyPass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Test + public void testSplitKeystoreNoKeystore() throws Exception { + expectedEx.expect(CommandLineParseException.class); + expectedEx.expectMessage("Error parsing command line. (Missing argument for option: splitKeystore)"); + + tlsToolkitStandaloneCommandLine.parse("-splitKeystore", "-S", keystorePass, "-K", keyPass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + + @Test + public void testSplitKeystoreEmptyKeystore() throws Exception { + expectedEx.expect(KeyStoreException.class); + expectedEx.expectMessage("was empty. No cert/key pairs to output to file."); + + tlsToolkitStandaloneCommandLine.parse( + "-splitKeystore", new File("src/test/resources/empty-keystore.jks").getPath(), "-S", keystorePass, "-K", keyPass, "-o", outputFolder.getPath()); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig(); + + assertTrue(standaloneConfig.isSplitKeystore()); + assertEquals(keyPass, standaloneConfig.getKeyPassword()); + assertEquals(keystorePass, standaloneConfig.getKeyStorePassword()); + TlsToolkitStandalone toolkit = new TlsToolkitStandalone(); + toolkit.splitKeystore(standaloneConfig); + } + } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java index e7efabb..5ed4e91 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/util/TlsHelperTest.java @@ -17,68 +17,80 @@ package org.apache.nifi.toolkit.tls.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.security.util.CertificateUtils; +import org.apache.nifi.toolkit.tls.configuration.TlsConfig; +import org.bouncycastle.asn1.pkcs.Attribute; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; import java.security.PublicKey; +import java.security.Security; import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.security.util.CertificateUtils; -import org.apache.nifi.toolkit.tls.configuration.TlsConfig; -import org.bouncycastle.asn1.pkcs.Attribute; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.Extensions; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TlsHelperTest { @@ -98,6 +110,8 @@ public class TlsHelperTest { private KeyStore keyStore; + private String password = "changeit"; + @Mock KeyStoreSpi keyStoreSpi; @@ -107,6 +121,9 @@ public class TlsHelperTest { @Mock OutputStreamFactory outputStreamFactory; + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private ByteArrayOutputStream tmpFileOutputStream; private File file; @@ -448,4 +465,66 @@ public class TlsHelperTest { assertEquals("CN=testuser_OU=NiFi_Organisation", escapedClientDn); } + private KeyStore setupKeystore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException { + + KeyStore ks = KeyStore.getInstance("JKS"); + InputStream readStream = getClass().getClassLoader().getResourceAsStream("keystore.jks"); + ks.load(readStream, password.toCharArray()); + + return ks; + } + + @Test + public void testOutputToFileTwoCertsAsPem() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + File folder = tempFolder.newFolder("splitKeystoreOutputDir"); + + KeyStore keyStore = setupKeystore(); + HashMap<String, Certificate> certs = TlsHelper.extractCerts(keyStore); + TlsHelper.outputCertsAsPem(certs, folder,".crt"); + + assertEquals(folder.listFiles().length, 2); + + for(File file : folder.listFiles()) { + X509Certificate certFromFile = loadCertificate(file); + assertTrue(certs.containsValue(certFromFile)); + X509Certificate originalCert = (X509Certificate) certs.get(file.getName().split("\\.")[0]); + assertTrue(originalCert.equals(certFromFile)); + assertArrayEquals(originalCert.getSignature(), certFromFile.getSignature()); + } + } + + // Keystore contains two certificates, but one key. This is to test the edge case where a certificate does not have a key. + @Test + public void testOutputToFileOneKeyAsPem() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + File folder = tempFolder.newFolder("splitKeystoreOutputDir"); + KeyStore keyStore = setupKeystore(); + HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, password.toCharArray()); + TlsHelper.outputKeysAsPem(keys, folder, ".key"); + + for(File file : folder.listFiles()) { + BufferedReader br = new BufferedReader(new FileReader(file)); + PEMParser pemParser = new PEMParser(br); + PEMKeyPair key = (PEMKeyPair) pemParser.readObject(); + assertArrayEquals(keys.get(file.getName().split("\\.")[0]).getEncoded(), key.getPrivateKeyInfo().getEncoded()); + } + } + + @Test + public void testExtractCerts() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { + KeyStore keyStore = setupKeystore(); + HashMap<String, Certificate> certs = TlsHelper.extractCerts(keyStore); + assertEquals(2, certs.size()); + certs.forEach((String p, Certificate q) -> assertEquals("X.509", q.getType())); + } + + @Test + public void testExtractKeys() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + KeyStore keyStore = setupKeystore(); + HashMap<String, Key> keys = TlsHelper.extractKeys(keyStore, password.toCharArray()); + assertEquals(1, keys.size()); + keys.forEach((String alias, Key key) -> assertEquals("PKCS#8", key.getFormat())); + } + + } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/empty-keystore.jks b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/empty-keystore.jks new file mode 100644 index 0000000..c408465 Binary files /dev/null and b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/empty-keystore.jks differ diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/keystore.jks b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/keystore.jks new file mode 100644 index 0000000..1eed4dd Binary files /dev/null and b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/keystore.jks differ