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

Reply via email to