Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIFI-6026 - First commit which adds a new tls-toolkit mode called Key… #3340

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file not shown.
Expand Up @@ -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

Expand Down Expand Up @@ -90,4 +91,12 @@ public List<InstanceDefinition> getInstanceDefinitions() {
public void setInstanceDefinitions(List<InstanceDefinition> instanceDefinitions) {
this.instanceDefinitions = instanceDefinitions;
}

public void setSplitKeystore(boolean splitKeystore) {
this.splitKeystore = splitKeystore;
}

public boolean isSplitKeystore() {
return this.splitKeystore;
}
}
Expand Up @@ -18,18 +18,25 @@
package org.apache.nifi.toolkit.tls.standalone;

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;
import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.KeyStoreUtils;
Expand Down Expand Up @@ -66,6 +73,23 @@ public TlsToolkitStandalone(OutputStreamFactory outputStreamFactory) {
this.outputStreamFactory = outputStreamFactory;
}

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 splitKeystore(StandaloneConfig standaloneConfig) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(standaloneConfig.getKeyStore()), standaloneConfig.getKeyStorePassword().toCharArray());
if(standaloneConfig.getKeyPassword() == null || standaloneConfig.getKeyPassword().isEmpty()) {
splitKeystore(keyStore, standaloneConfig.getKeyStorePassword().toCharArray(), standaloneConfig.getBaseDir());
} else {
splitKeystore(keyStore, standaloneConfig.getKeyPassword().toCharArray(), standaloneConfig.getBaseDir());
}
}

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();
Expand Down Expand Up @@ -215,6 +239,7 @@ public void createNifiKeystoresAndTrustStores(StandaloneConfig standaloneConfig)
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);
}
Expand Down
Expand Up @@ -60,6 +60,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("."));

Expand All @@ -86,10 +87,14 @@ protected static String calculateDefaultOutputDirectory(Path currentPath) {
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());
Expand All @@ -113,6 +118,8 @@ protected TlsToolkitStandaloneCommandLine(PasswordUtil passwordUtil) {
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("splitKeystore", 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.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providing the "splitKeystore" string as the first argument to this call means that -splitKeystore and --splitKeystore are both supported here. Please pass null as the first argument instead, otherwise this leads to ambiguity and breaks consistency.


}

public static void main(String[] args) {
Expand All @@ -122,12 +129,24 @@ public static void main(String[] args) {
} 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());
}

Expand Down Expand Up @@ -168,6 +187,17 @@ protected CommandLine doParse(String... args) throws CommandLineParseException {
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, "");
Expand Down Expand Up @@ -243,4 +273,15 @@ public StandaloneConfig createConfig() {

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;
}
}
Expand Up @@ -18,21 +18,29 @@
package org.apache.nifi.toolkit.tls.util;

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;
import javax.crypto.Cipher;
import javax.crypto.Mac;
Expand Down Expand Up @@ -69,6 +77,7 @@
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemWriter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -146,6 +155,60 @@ public static String writeKeyStore(KeyStore keyStore, OutputStreamFactory output
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);
Expand Down
Expand Up @@ -27,7 +27,9 @@
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.TemporaryFolder;
import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations;

import java.io.ByteArrayInputStream;
Expand All @@ -37,6 +39,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -50,6 +53,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
Expand All @@ -58,11 +62,15 @@ public class TlsToolkitStandaloneCommandLineTest {
private SecureRandom secureRandom;
private TlsToolkitStandaloneCommandLine tlsToolkitStandaloneCommandLine;

@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();

@Before
public void setup() {
secureRandom = mock(SecureRandom.class);
doAnswer(new ForwardsInvocations(new Random())).when(secureRandom).nextBytes(any(byte[].class));
tlsToolkitStandaloneCommandLine = new TlsToolkitStandaloneCommandLine(new PasswordUtil(secureRandom));

}

@Test
Expand Down Expand Up @@ -445,4 +453,75 @@ private Properties getProperties() throws IOException {
properties.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
return properties;
}

@Test
public void testSplitKeystore() throws Exception {

String keyPass = "changeit";
String keystorePass = "changeit";
File folder = tempFolder.newFolder("splitKeystoreOutputDir");
tlsToolkitStandaloneCommandLine.parse("-splitKeystore", getClass().getClassLoader().getResource("keystore.jks").getFile(), "-S", keystorePass, "-K", keyPass, "-o", folder.getPath());
StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();
System.out.println(standaloneConfig.getKeyPassword());

assertTrue(standaloneConfig.isSplitKeystore());
assertEquals(keyPass, standaloneConfig.getKeyPassword());
assertEquals(keystorePass, standaloneConfig.getKeyStorePassword());
TlsToolkitStandalone toolkit = new TlsToolkitStandalone();
toolkit.splitKeystore(standaloneConfig);

assertTrue(folder.listFiles().length > 0);
for(File file : folder.listFiles()) {
assertTrue(file.length() > 0);
}
}

@Test(expected = CommandLineParseException.class)
public void testSplitKeystoreMissingPasswords() throws Exception {

File folder = tempFolder.newFolder("splitKeystoreOutputDir");
tlsToolkitStandaloneCommandLine.parse("-splitKeystore", getClass().getClassLoader().getResource("keystore.jks").getFile(), "-o", folder.getPath());
StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();

TlsToolkitStandalone toolkit = new TlsToolkitStandalone();
toolkit.splitKeystore(standaloneConfig);
}

@Test
public void testSplitKeystoreWithKeystoreAndKeyPasswordTheSame() throws Exception {

String keystorePass = "changeit";
File folder = tempFolder.newFolder("splitKeystoreOutputDir");
tlsToolkitStandaloneCommandLine.parse("-splitKeystore", getClass().getClassLoader().getResource("keystore.jks").getFile(), "-S", keystorePass, "-o", folder.getPath());
StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();

TlsToolkitStandalone toolkit = new TlsToolkitStandalone();
toolkit.splitKeystore(standaloneConfig);
}

@Test(expected = UnrecoverableKeyException.class)
public void testSplitKeystoreWrongKeyPass() throws Exception {

String keyPass = "wrongpass";
String keystorePass = "changeit";
File folder = tempFolder.newFolder("splitKeystoreOutputDir");
tlsToolkitStandaloneCommandLine.parse("-splitKeystore", getClass().getClassLoader().getResource("keystore.jks").getFile(), "-S", keystorePass, "-K", keyPass, "-o", folder.getPath());
StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();

TlsToolkitStandalone toolkit = new TlsToolkitStandalone();
toolkit.splitKeystore(standaloneConfig);
}

@Test(expected = IOException.class)
public void testSplitKeystoreWrongKeystorePass() throws Exception {

String keyPass = "changeit";
String keystorePass = "wrongpass";
File folder = tempFolder.newFolder("splitKeystoreOutputDir");
tlsToolkitStandaloneCommandLine.parse("-splitKeystore", getClass().getClassLoader().getResource("keystore.jks").getFile(), "-S", keystorePass, "-K", keyPass, "-o", folder.getPath());
StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createSplitKeystoreConfig();

TlsToolkitStandalone toolkit = new TlsToolkitStandalone();
toolkit.splitKeystore(standaloneConfig);
}
}