/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.toolkit.tls.standalone;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.toolkit.tls.SystemExitCapturer;
import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import org.apache.nifi.toolkit.tls.service.TlsCertificateAuthorityTest;
import org.apache.nifi.toolkit.tls.util.TlsHelperTest;
import org.apache.nifi.util.NiFiProperties;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
public class TlsToolkitStandaloneTest {
public static final String NIFI_FAKE_PROPERTY = "nifi.fake.property";
public static final String FAKE_VALUE = "fake value";
public static final String TEST_NIFI_PROPERTIES = "src/test/resources/localhost/nifi.properties";
private SystemExitCapturer systemExitCapturer;
private File tempDir;
@Before
public void setup() throws IOException {
tempDir = File.createTempFile("tls-test", UUID.randomUUID().toString());
if (!tempDir.delete()) {
throw new IOException("Couldn't delete " + tempDir);
}
if (!tempDir.mkdirs()) {
throw new IOException("Couldn't make directory " + tempDir);
}
systemExitCapturer = new SystemExitCapturer();
}
@After
public void teardown() throws IOException {
systemExitCapturer.close();
FileUtils.deleteDirectory(tempDir);
}
@Test
public void testBadParse() {
runAndAssertExitCode(ExitCode.ERROR_PARSING_COMMAND_LINE, "--unknownArgument");
}
@Test
public void testHelp() {
runAndAssertExitCode(ExitCode.HELP, "-h");
runAndAssertExitCode(ExitCode.HELP, "--help");
}
@Test
public void testDirOutput() throws Exception {
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertNull(nifiProperties.get("nifi.fake.property"));
assertEquals(nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD), nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
}
@Test
public void testDifferentArg() throws Exception {
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-g", "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertNull(nifiProperties.get("nifi.fake.property"));
assertNotEquals(nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD), nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
}
@Test
public void testFileArg() throws Exception {
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-f", TEST_NIFI_PROPERTIES, "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertEquals(FAKE_VALUE, nifiProperties.get(NIFI_FAKE_PROPERTY));
}
@Test
public void testHostnamesArgumentOverwrite() throws Exception {
String nifi1 = "nifi1";
String nifi2 = "nifi2";
String nifi3 = "nifi3";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi1 + "," + nifi2);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi3);
checkHostDirAndReturnNifiProperties(nifi1, x509Certificate);
checkHostDirAndReturnNifiProperties(nifi2, x509Certificate);
checkHostDirAndReturnNifiProperties(nifi3, x509Certificate);
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-O", "-n", nifi3);
checkHostDirAndReturnNifiProperties(nifi3, x509Certificate);
}
@Test
public void testHostnamesArgumentNoOverwrite() throws Exception {
String nifi = "nifi";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
checkHostDirAndReturnNifiProperties(nifi, x509Certificate);
runAndAssertExitCode(ExitCode.ERROR_GENERATING_CONFIG, "-o", tempDir.getAbsolutePath(), "-n", nifi);
}
@Test
public void testKeyPasswordArg() throws Exception {
String testKey = "testKey";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-K", testKey, "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertEquals(testKey, nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
}
@Test
public void testKeyStorePasswordArg() throws Exception {
String testKeyStore = "testKeyStore";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-S", testKeyStore, "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertEquals(testKeyStore, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD));
}
@Test
public void testTrustStorePasswordArg() throws Exception {
String testTrustStore = "testTrustStore";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-P", testTrustStore, "-n", TlsConfig.DEFAULT_HOSTNAME);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
assertEquals(testTrustStore, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
}
@Test
public void testDnArgs() throws Exception {
String nifiDnPrefix = "O=apache, CN=";
String nifiDnSuffix = ", OU=nifi";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", TlsConfig.DEFAULT_HOSTNAME,
"--" + TlsToolkitStandaloneCommandLine.NIFI_DN_PREFIX_ARG, nifiDnPrefix, "--" + TlsToolkitStandaloneCommandLine.NIFI_DN_SUFFIX_ARG, nifiDnSuffix);
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, nifiDnPrefix, nifiDnSuffix, x509Certificate);
}
@Test
public void testKeyStoreTypeArg() throws Exception {
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", TlsConfig.DEFAULT_HOSTNAME, "-T", KeystoreType.PKCS12.toString().toLowerCase(),
"-K", "change", "-S", "change", "-P", "change");
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate);
}
@Test
public void testClientDnsArg() throws Exception {
String clientDn = "OU=NIFI,CN=testuser";
String clientDn2 = "OU=NIFI,CN=testuser2";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-C", clientDn, "-C", clientDn2, "-B", "pass1", "-P", "pass2");
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
checkClientCert(clientDn, x509Certificate);
checkClientCert(clientDn2, x509Certificate);
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-O", "-C", clientDn2, "-B", "pass3");
checkClientCert(clientDn2, x509Certificate);
}
@Test
public void testClientDnsArgNoOverwrite() throws Exception {
String clientDn = "OU=NIFI,CN=testuser";
runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-C", clientDn, "-B", "passwor");
X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM);
checkClientCert(clientDn, x509Certificate);
runAndAssertExitCode(ExitCode.ERROR_GENERATING_CONFIG, "-o", tempDir.getAbsolutePath(), "-C", clientDn);
}
private X509Certificate checkLoadCertPrivateKey(String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, CertificateException {
KeyPair keyPair = TlsHelperTest.loadKeyPair(new File(tempDir, TlsToolkitStandalone.NIFI_KEY + ".key"));
assertEquals(algorithm, keyPair.getPrivate().getAlgorithm());
assertEquals(algorithm, keyPair.getPublic().getAlgorithm());
X509Certificate x509Certificate = TlsHelperTest.loadCertificate(new File(tempDir, TlsToolkitStandalone.NIFI_CERT + ".pem"));
assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey());
return x509Certificate;
}
private Properties checkHostDirAndReturnNifiProperties(String hostname, X509Certificate rootCert) throws Exception {
return checkHostDirAndReturnNifiProperties(hostname, TlsConfig.DEFAULT_DN_PREFIX, TlsConfig.DEFAULT_DN_SUFFIX, rootCert);
}
private Properties checkHostDirAndReturnNifiProperties(String hostname, String dnPrefix, String dnSuffix, X509Certificate rootCert) throws Exception {
File hostDir = new File(tempDir, hostname);
Properties nifiProperties = new Properties();
try (InputStream inputStream = new FileInputStream(new File(hostDir, TlsToolkitStandalone.NIFI_PROPERTIES))) {
nifiProperties.load(inputStream);
}
String trustStoreType = nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
assertEquals(KeystoreType.JKS.toString().toLowerCase(), trustStoreType.toLowerCase());
KeyStore trustStore = KeyStoreUtils.getTrustStore(trustStoreType);
try (InputStream inputStream = new FileInputStream(new File(hostDir, "truststore." + trustStoreType))) {
trustStore.load(inputStream, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray());
}
String trustStoreFilename = BaseCommandLine.TRUSTSTORE + trustStoreType;
assertEquals("./conf/" + trustStoreFilename, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
Certificate certificate = trustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT);
assertEquals(rootCert, certificate);
String keyStoreType = nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
String keyStoreFilename = BaseCommandLine.KEYSTORE + keyStoreType;
File keyStoreFile = new File(hostDir, keyStoreFilename);
assertEquals("./conf/" + keyStoreFilename, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE));
KeyStore keyStore = KeyStoreUtils.getKeyStore(keyStoreType);
char[] keyStorePassword = nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray();
try (InputStream inputStream = new FileInputStream(keyStoreFile)) {
keyStore.load(inputStream, keyStorePassword);
}
char[] keyPassword = nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray();
if (keyPassword == null || keyPassword.length == 0) {
keyPassword = keyStorePassword;
}
KeyStore.Entry entry = keyStore.getEntry(TlsToolkitStandalone.NIFI_KEY, new KeyStore.PasswordProtection(keyPassword));
assertEquals(KeyStore.PrivateKeyEntry.class, entry.getClass());
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;
Certificate[] certificateChain = privateKeyEntry.getCertificateChain();
assertEquals(2, certificateChain.length);
assertEquals(rootCert, certificateChain[1]);
certificateChain[1].verify(rootCert.getPublicKey());
certificateChain[0].verify(rootCert.getPublicKey());
TlsConfig tlsConfig = new TlsConfig();
tlsConfig.setDnPrefix(dnPrefix);
tlsConfig.setDnSuffix(dnSuffix);
assertEquals(tlsConfig.calcDefaultDn(hostname), CertificateUtils.convertAbstractX509Certificate(certificateChain[0]).getSubjectX500Principal().getName());
TlsCertificateAuthorityTest.assertPrivateAndPublicKeyMatch(privateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey());
return nifiProperties;
}
private void checkClientCert(String clientDn, X509Certificate rootCert) throws Exception {
String clientDnFile = TlsToolkitStandalone.getClientDnFile(CertificateUtils.reorderDn(clientDn));
String password;
try (FileReader fileReader = new FileReader(new File(tempDir, clientDnFile + ".password"))) {
List<String> lines = IOUtils.readLines(fileReader);
assertEquals(1, lines.size());
password = lines.get(0);
}
KeyStore keyStore = KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString());
try (FileInputStream fileInputStream = new FileInputStream(new File(tempDir, clientDnFile + ".p12"))) {
keyStore.load(fileInputStream, password.toCharArray());
}
PrivateKey privateKey = (PrivateKey) keyStore.getKey(TlsToolkitStandalone.NIFI_KEY, new char[0]);
Certificate[] certificateChain = keyStore.getCertificateChain(TlsToolkitStandalone.NIFI_KEY);
assertEquals(2, certificateChain.length);
assertEquals(rootCert, certificateChain[1]);
certificateChain[1].verify(rootCert.getPublicKey());
certificateChain[0].verify(rootCert.getPublicKey());
PublicKey publicKey = certificateChain[0].getPublicKey();
TlsCertificateAuthorityTest.assertPrivateAndPublicKeyMatch(privateKey, publicKey);
}
private void runAndAssertExitCode(ExitCode exitCode, String... args) {
systemExitCapturer.runAndAssertExitCode(() -> TlsToolkitStandaloneCommandLine.main(args), exitCode);
}
}