diff --git a/nifi-toolkit/nifi-toolkit-tls/pom.xml b/nifi-toolkit/nifi-toolkit-tls/pom.xml index 8912388b98..7eb6806553 100644 --- a/nifi-toolkit/nifi-toolkit-tls/pom.xml +++ b/nifi-toolkit/nifi-toolkit-tls/pom.xml @@ -90,11 +90,6 @@ org.slf4j jcl-over-slf4j - - org.codehaus.groovy - groovy-test - test - diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy b/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy deleted file mode 100644 index 9ab3d463d6..0000000000 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneGroovyTest.groovy +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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.nifi.security.cert.builder.StandardCertificateBuilder -import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig -import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator -import org.bouncycastle.util.io.pem.PemWriter -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir - -import javax.security.auth.x500.X500Principal -import java.nio.file.Files -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.SignatureException -import java.security.cert.X509Certificate -import java.time.Duration - -import static org.junit.jupiter.api.Assertions.assertThrows - -class TlsToolkitStandaloneGroovyTest { - private final String TEST_SRC_DIR = "src/test/resources/" - private final String DEFAULT_KEY_PAIR_ALGORITHM = "RSA" - - @Test - void testShouldVerifyCertificateSignatureWhenSelfSigned(@TempDir File tempDir) { - // Arrange - - // Create a temp directory for this test and populate it with the nifi-cert.pem and nifi-key.key files - File baseDir = createBaseDirAndPopulateWithCAFiles(tempDir) - - // Make a standalone config which doesn't trigger any keystore generation and just has a self-signed cert and key - StandaloneConfig standaloneConfig = new StandaloneConfig() - standaloneConfig.setBaseDir(baseDir) - standaloneConfig.setInstanceDefinitions([]) - standaloneConfig.setClientDns([]) - standaloneConfig.initDefaults() - - TlsToolkitStandalone standalone = new TlsToolkitStandalone() - - // Act - standalone.createNifiKeystoresAndTrustStores(standaloneConfig) - - // Assert - - // The test will fail with an exception if the certificate is not signed by a known certificate - } - - /** - * The certificate under examination is self-signed, but there is another signing cert which will be iterated over first, fail, and then the self-signed signature will be validated. - */ - @Test - void testShouldVerifyCertificateSignatureWithMultipleSigningCerts(@TempDir File tempDir) { - // Create a temp directory for this test and populate it with the nifi-cert.pem and nifi-key.key files - File baseDir = createBaseDirAndPopulateWithCAFiles(tempDir) - - // Create a different cert and persist it to the base dir - X509Certificate otherCert = generateX509Certificate() - File otherCertFile = writeCertificateToPEMFile(otherCert, "${baseDir.path}/other.pem") - - // Make a standalone config which doesn't trigger any keystore generation and just has a self-signed cert and key - StandaloneConfig standaloneConfig = new StandaloneConfig() - standaloneConfig.setBaseDir(baseDir) - standaloneConfig.setInstanceDefinitions([]) - standaloneConfig.setClientDns([]) - standaloneConfig.initDefaults() - - // Inject the additional CA cert path - standaloneConfig.setAdditionalCACertificate(otherCertFile.path) - - TlsToolkitStandalone standalone = new TlsToolkitStandalone() - - // Act - standalone.createNifiKeystoresAndTrustStores(standaloneConfig) - } - - /** - * The certificate under examination is signed with the external signing cert. - */ - @Test - void testShouldVerifyCertificateSignatureWithAdditionalSigningCert(@TempDir File baseDir) { - // Create a root CA, create an intermediate CA, use the root to sign the intermediate and then provide the root - KeyPair rootKeyPair = generateKeyPair() - X509Certificate rootCert = generateX509Certificate("CN=Root CA", rootKeyPair) - - File rootCertFile = writeCertificateToPEMFile(rootCert, "${baseDir.path}/root.pem") - - KeyPair intermediateKeyPair = generateKeyPair() - X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1)) - .setSubject(new X500Principal("CN=Intermediate CA")) - .setSubjectPublicKey(intermediateKeyPair.getPublic()) - .build() - - File intermediateCertFile = writeCertificateToPEMFile(intermediateCert, "${baseDir.path}/nifi-cert.pem") - - // Write the private key of the intermediate cert to nifi-key.key - File intermediateKeyFile = writePrivateKeyToFile(intermediateKeyPair, "${baseDir}/nifi-key.key") - - // Make a standalone config which doesn't trigger any keystore generation and just has a signed cert and key - StandaloneConfig standaloneConfig = new StandaloneConfig() - standaloneConfig.setBaseDir(baseDir) - standaloneConfig.setInstanceDefinitions([]) - standaloneConfig.setClientDns([]) - standaloneConfig.initDefaults() - - // Inject the additional CA cert path - standaloneConfig.setAdditionalCACertificate(rootCertFile.path) - - TlsToolkitStandalone standalone = new TlsToolkitStandalone() - - // Act - standalone.createNifiKeystoresAndTrustStores(standaloneConfig) - } - - @Test - void testShouldNotVerifyCertificateSignatureWithWrongSigningCert(@TempDir File baseDir) { - // Create a root CA, create an intermediate CA, use the root to sign the intermediate and then do not provide the root - KeyPair rootKeyPair = generateKeyPair() - X509Certificate rootCert = generateX509Certificate("CN=Root CA", rootKeyPair) - - KeyPair intermediateKeyPair = generateKeyPair() - X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1)) - .setSubject(new X500Principal("CN=Intermediate CA")) - .setSubjectPublicKey(intermediateKeyPair.getPublic()) - .build() - - File intermediateCertFile = writeCertificateToPEMFile(intermediateCert, "${baseDir.path}/nifi-cert.pem") - - // Write the private key of the intermediate cert to nifi-key.key - File intermediateKeyFile = writePrivateKeyToFile(intermediateKeyPair, "${baseDir.path}/nifi-key.key") - - // Make a standalone config which doesn't trigger any keystore generation and just has a signed cert and key - StandaloneConfig standaloneConfig = new StandaloneConfig() - standaloneConfig.setBaseDir(baseDir) - standaloneConfig.setInstanceDefinitions([]) - standaloneConfig.setClientDns([]) - standaloneConfig.initDefaults() - - TlsToolkitStandalone standalone = new TlsToolkitStandalone() - - assertThrows(SignatureException.class, () -> standalone.createNifiKeystoresAndTrustStores(standaloneConfig)) - } - - private static File writePrivateKeyToFile(KeyPair intermediateKeyPair, String destination) { - File intermediateKeyFile = new File(destination) - PemWriter pemWriter = new PemWriter(new FileWriter(intermediateKeyFile)) - pemWriter.writeObject(new JcaMiscPEMGenerator(intermediateKeyPair)) - pemWriter.close() - intermediateKeyFile - } - - private File createBaseDirAndPopulateWithCAFiles(File baseDir) { - populateBaseDirWithCAFiles(baseDir) - } - - private File populateBaseDirWithCAFiles(File baseDir) { - File certificateFile = new File(TEST_SRC_DIR, "rootCert.crt") - File keyFile = new File(TEST_SRC_DIR, "rootCert.key") - File destinationCertFile = new File(baseDir.path, "nifi-cert.pem") - Files.copy(certificateFile.toPath(), destinationCertFile.toPath()) - File destinationKeyFile = new File(baseDir.path, "nifi-key.key") - Files.copy(keyFile.toPath(), destinationKeyFile.toPath()) - - baseDir - } - - /** - * Returns an {@link X509Certificate} with the provided DN and default algorithms. The validity period is only 1 day. - * - * @param dn the DN (defaults to {@code CN=Test Certificate}) - * @return the X509Certificate - */ - private X509Certificate generateX509Certificate(String dn = "CN=Test Certificate", KeyPair keyPair = generateKeyPair()) { - new StandardCertificateBuilder(keyPair, new X500Principal(dn), Duration.ofDays(1)).build() - } - - private KeyPair generateKeyPair() { - KeyPairGenerator instance = KeyPairGenerator.getInstance(DEFAULT_KEY_PAIR_ALGORITHM) - instance.initialize(2048) - instance.generateKeyPair() - } - - /** - * Writes the provided {@link X509Certificate} to the specified file in PEM format. - * - * @param certificate the certificate - * @param destination the path to write the certificate in PEM format - * @return the file - */ - private static File writeCertificateToPEMFile(X509Certificate certificate, String destination) { - File certificateFile = new File(destination) - PemWriter pemWriter = new PemWriter(new FileWriter(certificateFile)) - pemWriter.writeObject(new JcaMiscPEMGenerator(certificate)) - pemWriter.close() - - certificateFile - } -} diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/util/TlsHelperGroovyTest.groovy b/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/util/TlsHelperGroovyTest.groovy deleted file mode 100644 index 4004bfb76a..0000000000 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/groovy/org/apache/nifi/toolkit/tls/util/TlsHelperGroovyTest.groovy +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.util - -import org.bouncycastle.crypto.params.RSAKeyParameters -import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test - -import javax.security.auth.x500.X500Principal -import java.security.KeyPair -import java.security.PrivateKey -import java.security.Security -import java.security.cert.X509Certificate - -class TlsHelperGroovyTest { - - @BeforeAll - static void setProvider() { - System.setProperty("org.bouncycastle.rsa.allow_unsafe_mod","true") - Security.addProvider(new BouncyCastleProvider()) - BCRSAPublicKey badPublicKey = new BCRSAPublicKey(new RSAKeyParameters(false, new BigInteger("3", 10), new BigInteger("1", 10))) - } - - @Test - void testShouldVerifyCertificateSignatureWhenSelfSigned() { - File certificateFile = new File("src/test/resources/rootCert.crt") - FileReader certReader = new FileReader(certificateFile) - X509Certificate certificate = TlsHelper.parseCertificate(certReader) - - boolean isCertificateSigned = TlsHelper.verifyCertificateSignature(certificate, [certificate]) - assert isCertificateSigned - } - - @Test - void testShouldVerifyCertificateSignatureWithMultipleSigningCerts() { - File certificateFile = new File("src/test/resources/rootCert.crt") - FileReader certReader = new FileReader(certificateFile) - X509Certificate certificate = TlsHelper.parseCertificate(certReader) - - X509Certificate mockCertificate = [ - getSubjectX500Principal: { -> new X500Principal("CN=Mock Certificate") }, - getPublicKey : { -> badPublicKey } - ] as X509Certificate - - boolean isCertificateSigned = TlsHelper.verifyCertificateSignature(certificate, [mockCertificate, certificate]) - assert isCertificateSigned - } - - @Test - void testShouldNotVerifyCertificateSignatureWithNoSigningCerts() { - File certificateFile = new File("src/test/resources/rootCert.crt") - FileReader certReader = new FileReader(certificateFile) - X509Certificate certificate = TlsHelper.parseCertificate(certReader) - - boolean isCertificateSigned = TlsHelper.verifyCertificateSignature(certificate, []) - assert !isCertificateSigned - } - - @Test - void testShouldNotVerifyCertificateSignatureWithWrongSigningCert() { - File certificateFile = new File("src/test/resources/rootCert.crt") - FileReader certReader = new FileReader(certificateFile) - X509Certificate certificate = TlsHelper.parseCertificate(certReader) - - X509Certificate mockCertificate = [ - getSubjectX500Principal: { -> new X500Principal("CN=Mock Certificate") }, - getPublicKey : { -> badPublicKey } - ] as X509Certificate - - boolean isCertificateSigned = TlsHelper.verifyCertificateSignature(certificate, [mockCertificate]) - assert !isCertificateSigned - } - - @Test - void testParseKeyPairFromReaderShouldHandlePKCS8PrivateKey() { - File keyFile = new File("src/test/resources/rootCert-pkcs8.key") - FileReader keyReader = new FileReader(keyFile) - - final KeyPair expectedKeyPair = TlsHelper.parseKeyPairFromReader(new FileReader(new File ("src/test/resources/rootCert.key"))) - final PrivateKey EXPECTED_PRIVATE_KEY = expectedKeyPair.getPrivate() - - KeyPair keyPair = TlsHelper.parseKeyPairFromReader(keyReader) - assert keyPair.private == EXPECTED_PRIVATE_KEY - } -} diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java index 974ee849d3..d4bd6f839b 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/standalone/TlsToolkitStandaloneTest.java @@ -17,77 +17,79 @@ 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.KeystoreType; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.nifi.security.cert.builder.StandardCertificateBuilder; import org.apache.nifi.security.util.KeyStoreUtils; +import org.apache.nifi.security.util.KeystoreType; import org.apache.nifi.toolkit.tls.SystemExitCapturer; import org.apache.nifi.toolkit.tls.commandLine.BaseTlsToolkitCommandLine; import org.apache.nifi.toolkit.tls.commandLine.ExitCode; -import org.apache.nifi.toolkit.tls.configuration.TlsConfig; 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.service.TlsCertificateAuthorityTest; import org.apache.nifi.toolkit.tls.util.TlsHelper; import org.apache.nifi.toolkit.tls.util.TlsHelperTest; import org.apache.nifi.util.NiFiProperties; import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; +import org.bouncycastle.util.io.pem.PemWriter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.junit.jupiter.api.io.TempDir; +import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Properties; -import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; 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"; - public static final Logger logger = LoggerFactory.getLogger(TlsToolkitStandaloneTest.class); private SystemExitCapturer systemExitCapturer; + @TempDir private File tempDir; @BeforeEach 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(); } @AfterEach - public void teardown() throws IOException { + public void teardown() { systemExitCapturer.close(); - FileUtils.deleteDirectory(tempDir); } @Test @@ -104,7 +106,7 @@ public class TlsToolkitStandaloneTest { @Test public void testDirOutput() throws Exception { runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", TlsConfig.DEFAULT_HOSTNAME); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertNull(nifiProperties.get("nifi.fake.property")); @@ -114,7 +116,7 @@ public class TlsToolkitStandaloneTest { @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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertNull(nifiProperties.get("nifi.fake.property")); @@ -124,7 +126,7 @@ public class TlsToolkitStandaloneTest { @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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertEquals(FAKE_VALUE, nifiProperties.get(NIFI_FAKE_PROPERTY)); @@ -137,7 +139,7 @@ public class TlsToolkitStandaloneTest { String nifi3 = "nifi3"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi1 + "," + nifi2); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi3); checkHostDirAndReturnNifiProperties(nifi1, x509Certificate); @@ -152,7 +154,7 @@ public class TlsToolkitStandaloneTest { String nifi = "nifi"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nifi); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); checkHostDirAndReturnNifiProperties(nifi, x509Certificate); runAndAssertExitCode(ExitCode.ERROR_GENERATING_CONFIG, "-o", tempDir.getAbsolutePath(), "-n", nifi); } @@ -161,7 +163,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertEquals(testKey, nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD)); @@ -171,7 +173,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertEquals(testKeyStore, nifiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)); @@ -181,7 +183,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Properties nifiProperties = checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); assertEquals(testTrustStore, nifiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)); @@ -193,7 +195,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, nifiDnPrefix, nifiDnSuffix, x509Certificate); } @@ -202,7 +204,7 @@ public class TlsToolkitStandaloneTest { final String certificateAuthorityHostname = "certificate-authority"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", TlsConfig.DEFAULT_HOSTNAME, "-T", KeystoreType.PKCS12.toString().toLowerCase(), "-K", "change", "-S", "change", "-P", "change", "-c", certificateAuthorityHostname); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); checkHostDirAndReturnNifiProperties(TlsConfig.DEFAULT_HOSTNAME, x509Certificate); } @@ -211,7 +213,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); checkClientCert(clientDn, x509Certificate); checkClientCert(clientDn2, x509Certificate); @@ -224,7 +226,7 @@ public class TlsToolkitStandaloneTest { 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); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); checkClientCert(clientDn, x509Certificate); @@ -236,7 +238,7 @@ public class TlsToolkitStandaloneTest { String hostname = "static.nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", hostname); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Certificate[] certificateChain = loadCertificateChain(hostname, x509Certificate); X509Certificate clientCert = (X509Certificate) certificateChain[0]; Collection> clientSaNames = clientCert.getSubjectAlternativeNames(); @@ -255,7 +257,7 @@ public class TlsToolkitStandaloneTest { runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", hostname, "--subjectAlternativeName", san); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Certificate[] certificateChain = loadCertificateChain(hostname, x509Certificate); X509Certificate clientCert = (X509Certificate) certificateChain[0]; Collection> clientSaNames = clientCert.getSubjectAlternativeNames(); @@ -273,7 +275,7 @@ public class TlsToolkitStandaloneTest { String san = "alternative.nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", san); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Stream hostIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{nodeNames})); for (InstanceIdentifier hostInstance : (Iterable) hostIds::iterator) { @@ -295,7 +297,7 @@ public class TlsToolkitStandaloneTest { String saNames = "alternative[1-2].nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", saNames); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Stream hostIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{nodeNames})); Stream sansIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{saNames})); @@ -331,7 +333,7 @@ public class TlsToolkitStandaloneTest { String saNames = "alternative[3-4].nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", saNames); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Stream hostIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{nodeNames})); Stream sansIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{saNames})); @@ -367,7 +369,7 @@ public class TlsToolkitStandaloneTest { String saNames = "alternative[5-7].nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", saNames); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Stream hostIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{nodeNames})); Stream sansIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{saNames})); @@ -400,7 +402,7 @@ public class TlsToolkitStandaloneTest { String saNames = "alternative[2-1].nifi.apache.org"; runAndAssertExitCode(ExitCode.SUCCESS, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", saNames); - X509Certificate x509Certificate = checkLoadCertPrivateKey(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM); + X509Certificate x509Certificate = checkLoadCertPrivateKey(); Stream hostIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{nodeNames})); Stream sansIds = InstanceIdentifier.createIdentifiers(Arrays.stream(new String[]{saNames})); @@ -438,11 +440,122 @@ public class TlsToolkitStandaloneTest { runAndAssertExitCode(ExitCode.ERROR_PARSING_INT_ARG, "-o", tempDir.getAbsolutePath(), "-n", nodeNames, "--subjectAlternativeName", saNames); } - private X509Certificate checkLoadCertPrivateKey(String algorithm) throws IOException, CertificateException { + @Test + void testShouldVerifyCertificateSignatureWhenSelfSigned() throws Exception { + // Create a temp directory for this test and populate it with the nifi-cert.pem and nifi-key.key files + populateTempDirWithCAFiles(); + + // Make a standalone config which doesn't trigger any keystore generation and just has a self-signed cert and key + StandaloneConfig standaloneConfig = new StandaloneConfig(); + standaloneConfig.setBaseDir(tempDir); + standaloneConfig.setInstanceDefinitions(new ArrayList<>()); + standaloneConfig.setClientDns(new ArrayList<>()); + standaloneConfig.initDefaults(); + + TlsToolkitStandalone standalone = new TlsToolkitStandalone(); + // The test will fail with an exception if the certificate is not signed by a known certificate + assertDoesNotThrow(() -> standalone.createNifiKeystoresAndTrustStores(standaloneConfig)); + } + + /** + * The certificate under examination is self-signed, but there is another signing cert which will be iterated over first, fail, and then the self-signed signature will be validated. + */ + @Test + void testShouldVerifyCertificateSignatureWithMultipleSigningCerts() throws Exception { + // Populate temp directory for this test with the nifi-cert.pem and nifi-key.key files + populateTempDirWithCAFiles(); + + // Create a different cert and persist it to the base dir + X509Certificate otherCert = generateX509Certificate(); + File otherCertFile = writeCertificateToPEMFile(otherCert, tempDir.getPath() + "/other.pem"); + + // Make a standalone config which doesn't trigger any keystore generation and just has a self-signed cert and key + StandaloneConfig standaloneConfig = new StandaloneConfig(); + standaloneConfig.setBaseDir(tempDir); + standaloneConfig.setInstanceDefinitions(new ArrayList<>()); + standaloneConfig.setClientDns(new ArrayList<>()); + standaloneConfig.initDefaults(); + + // Inject the additional CA cert path + standaloneConfig.setAdditionalCACertificate(otherCertFile.getPath()); + TlsToolkitStandalone standalone = new TlsToolkitStandalone(); + + assertDoesNotThrow(() -> standalone.createNifiKeystoresAndTrustStores(standaloneConfig)); + } + + /** + * The certificate under examination is signed with the external signing cert. + */ + @Test + void testShouldVerifyCertificateSignatureWithAdditionalSigningCert() throws Exception { + // Create a root CA, create an intermediate CA, use the root to sign the intermediate and then provide the root + KeyPair rootKeyPair = generateKeyPair(); + X509Certificate rootCert = generateX509Certificate("CN=Root CA", rootKeyPair); + + File rootCertFile = writeCertificateToPEMFile(rootCert, tempDir.getPath() + "/root.pem"); + + KeyPair intermediateKeyPair = generateKeyPair(); + X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1)) + .setSubject(new X500Principal("CN=Intermediate CA")) + .setSubjectPublicKey(intermediateKeyPair.getPublic()) + .build(); + + //intermediateCertFile + writeCertificateToPEMFile(intermediateCert, tempDir.getPath() + "/nifi-cert.pem"); + + // Write the private key of the intermediate cert to nifi-key.key + //intermediateKeyFile + writePrivateKeyToFile(intermediateKeyPair, tempDir.getPath() + "/nifi-key.key"); + + // Make a standalone config which doesn't trigger any keystore generation and just has a signed cert and key + StandaloneConfig standaloneConfig = new StandaloneConfig(); + standaloneConfig.setBaseDir(tempDir); + standaloneConfig.setInstanceDefinitions(new ArrayList<>()); + standaloneConfig.setClientDns(new ArrayList<>()); + standaloneConfig.initDefaults(); + + // Inject the additional CA cert path + standaloneConfig.setAdditionalCACertificate(rootCertFile.getPath()); + TlsToolkitStandalone standalone = new TlsToolkitStandalone(); + + assertDoesNotThrow(() -> standalone.createNifiKeystoresAndTrustStores(standaloneConfig)); + } + + @Test + void testShouldNotVerifyCertificateSignatureWithWrongSigningCert() throws Exception { + // Create a root CA, create an intermediate CA, use the root to sign the intermediate and then do not provide the root + KeyPair rootKeyPair = generateKeyPair(); + X509Certificate rootCert = generateX509Certificate("CN=Root CA", rootKeyPair); + + KeyPair intermediateKeyPair = generateKeyPair(); + X509Certificate intermediateCert = new StandardCertificateBuilder(rootKeyPair, rootCert.getIssuerX500Principal(), Duration.ofDays(1)) + .setSubject(new X500Principal("CN=Intermediate CA")) + .setSubjectPublicKey(intermediateKeyPair.getPublic()) + .build(); + + //intermediateCertFile + writeCertificateToPEMFile(intermediateCert, tempDir.getPath() + "/nifi-cert.pem"); + + // Write the private key of the intermediate cert to nifi-key.key + //intermediateKeyFile + writePrivateKeyToFile(intermediateKeyPair, tempDir.getPath() + "/nifi-key.key"); + + // Make a standalone config which doesn't trigger any keystore generation and just has a signed cert and key + StandaloneConfig standaloneConfig = new StandaloneConfig(); + standaloneConfig.setBaseDir(tempDir); + standaloneConfig.setInstanceDefinitions(new ArrayList<>()); + standaloneConfig.setClientDns(new ArrayList<>()); + standaloneConfig.initDefaults(); + TlsToolkitStandalone standalone = new TlsToolkitStandalone(); + + assertThrows(SignatureException.class, () -> standalone.createNifiKeystoresAndTrustStores(standaloneConfig)); + } + + private X509Certificate checkLoadCertPrivateKey() throws IOException, CertificateException { KeyPair keyPair = TlsHelperTest.loadKeyPair(new File(tempDir, TlsToolkitStandalone.NIFI_KEY + ".key")); - assertEquals(algorithm, keyPair.getPrivate().getAlgorithm()); - assertEquals(algorithm, keyPair.getPublic().getAlgorithm()); + assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, keyPair.getPrivate().getAlgorithm()); + assertEquals(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, keyPair.getPublic().getAlgorithm()); X509Certificate x509Certificate = TlsHelperTest.loadCertificate(new File(tempDir, TlsToolkitStandalone.NIFI_CERT + ".pem")); assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey()); @@ -485,7 +598,7 @@ public class TlsToolkitStandaloneTest { } char[] keyPassword = nifiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray(); - if (keyPassword == null || keyPassword.length == 0) { + if(ArrayUtils.isEmpty(keyPassword)) { keyPassword = keyStorePassword; } @@ -529,7 +642,6 @@ public class TlsToolkitStandaloneTest { certificateChain[0].verify(rootCert.getPublicKey()); PublicKey publicKey = certificateChain[0].getPublicKey(); TlsCertificateAuthorityTest.assertPrivateAndPublicKeyMatch(privateKey, publicKey); - } private Certificate[] loadCertificateChain(String hostname, X509Certificate rootCert) throws Exception { @@ -550,4 +662,65 @@ public class TlsToolkitStandaloneTest { private void runAndAssertExitCode(ExitCode exitCode, String... args) { systemExitCapturer.runAndAssertExitCode(() -> TlsToolkitStandaloneCommandLine.main(args), exitCode); } + + private void populateTempDirWithCAFiles() throws Exception { + final String testSrcDir = "src/test/resources/"; + File certificateFile = new File(testSrcDir, "rootCert.crt"); + File keyFile = new File(testSrcDir, "rootCert.key"); + File destinationCertFile = new File(tempDir, "nifi-cert.pem"); + Files.copy(certificateFile.toPath(), destinationCertFile.toPath()); + File destinationKeyFile = new File(tempDir, "nifi-key.key"); + Files.copy(keyFile.toPath(), destinationKeyFile.toPath()); + } + + /** + * Returns an {@link X509Certificate} with the provided DN and default algorithms. The validity period is only 1 day. + * DN (defaults to {@code CN=Test Certificate}) + * @return the X509Certificate + */ + private static X509Certificate generateX509Certificate() throws Exception { + return generateX509Certificate("CN=Test Certificate", generateKeyPair()); + } + + /** + * Returns an {@link X509Certificate} with the provided DN and default algorithms. The validity period is only 1 day. + * + * @param dn the DN (defaults to {@code CN=Test Certificate}) + * @return the X509Certificate + */ + private static X509Certificate generateX509Certificate(String dn, KeyPair keyPair) { + return new StandardCertificateBuilder(keyPair, new X500Principal(dn), Duration.ofDays(1)).build(); + } + + private static KeyPair generateKeyPair() throws Exception { + final String defaultKeyPairAlgorithm = "RSA"; + KeyPairGenerator instance = KeyPairGenerator.getInstance(defaultKeyPairAlgorithm); + instance.initialize(2048); + + return instance.generateKeyPair(); + } + + /** + * Writes the provided {@link X509Certificate} to the specified file in PEM format. + * + * @param certificate the certificate + * @param destination the path to write the certificate in PEM format + * @return the file + */ + private static File writeCertificateToPEMFile(X509Certificate certificate, String destination) throws Exception { + return writePem(certificate, destination); + } + + private static File writePem(Object object, String destination) throws Exception{ + File destinationFile = new File(destination); + try(PemWriter pemWriter = new PemWriter(new FileWriter(destinationFile))) { + pemWriter.writeObject(new JcaMiscPEMGenerator(object)); + } + + return destinationFile; + } + + private static void writePrivateKeyToFile(KeyPair intermediateKeyPair, String destination) throws Exception { + writePem(intermediateKeyPair, destination); + } } 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 191be74c91..738cefbd0e 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 @@ -34,14 +34,13 @@ import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.bouncycastle.util.IPAddress; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.security.auth.x500.X500Principal; import java.io.BufferedReader; @@ -58,6 +57,7 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.UnrecoverableKeyException; @@ -67,6 +67,7 @@ import java.security.cert.X509Certificate; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -75,31 +76,19 @@ import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class TlsHelperTest { - public static final Logger logger = LoggerFactory.getLogger(TlsHelperTest.class); - + private static final String PASSWORD = "changeit"; + private static X509Certificate rootCert; private int days; - private int keySize; - private String keyPairAlgorithm; - private String signingAlgorithm; - - private KeyPairGenerator keyPairGenerator; - - private final String password = "changeit"; - - @Mock(lenient = true) - OutputStreamFactory outputStreamFactory; - - private File file; - public static KeyPair loadKeyPair(final Reader reader) throws IOException { try (PEMParser pemParser = new PEMParser(reader)) { Object object = pemParser.readObject(); @@ -128,19 +117,16 @@ public class TlsHelperTest { } } + @BeforeAll + static void setUpBeforeAll() throws Exception { + rootCert = TlsHelper.parseCertificate(new FileReader("src/test/resources/rootCert.crt")); + } + @BeforeEach public void setup() throws Exception { days = 360; keySize = 2048; keyPairAlgorithm = "RSA"; - signingAlgorithm = "SHA256withRSA"; - keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm); - keyPairGenerator.initialize(keySize); - - file = File.createTempFile("keystore", "file"); - file.deleteOnExit(); - ByteArrayOutputStream tmpFileOutputStream = new ByteArrayOutputStream(); - when(outputStreamFactory.create(file)).thenReturn(tmpFileOutputStream); } private Date inFuture(int days) { @@ -149,13 +135,8 @@ public class TlsHelperTest { @Test public void testTokenLengthInCalculateHmac() throws GeneralSecurityException { - List badTokens = new ArrayList<>(); - List goodTokens = new ArrayList<>(); - badTokens.add(null); - badTokens.add(""); - badTokens.add("123"); - goodTokens.add("0123456789abcdefghijklm"); - goodTokens.add("0123456789abcdef"); + List badTokens = Arrays.asList(null, "", "123"); + List goodTokens = Arrays.asList("0123456789abcdefghijklm", "0123456789abcdef"); String dn = "CN=testDN,O=testOrg"; X509Certificate x509Certificate = new StandardCertificateBuilder(TlsHelper.generateKeyPair(keyPairAlgorithm, keySize), new X500Principal(dn), Duration.ofDays(days)).build(); @@ -193,14 +174,15 @@ public class TlsHelperTest { assertTrue(notBefore.before(inFuture(1))); assertEquals(dn, x509Certificate.getIssuerX500Principal().getName()); - assertEquals(signingAlgorithm, x509Certificate.getSigAlgName()); + assertEquals("SHA256withRSA", x509Certificate.getSigAlgName()); assertEquals(keyPairAlgorithm, x509Certificate.getPublicKey().getAlgorithm()); x509Certificate.checkValidity(); } @Test - public void testWriteKeyStoreSuccess() throws IOException, GeneralSecurityException { + public void testWriteKeyStoreSuccess(@Mock OutputStreamFactory outputStreamFactory, @TempDir File file) throws IOException, GeneralSecurityException { + when(outputStreamFactory.create(file)).thenReturn(new ByteArrayOutputStream()); String testPassword = "testPassword"; final KeyStore keyStore = setupKeystore(); assertEquals(testPassword, TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, false)); @@ -209,32 +191,29 @@ public class TlsHelperTest { @Test public void testShouldIncludeSANFromCSR() throws Exception { // Arrange - final List SAN_ENTRIES = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org"); - final int SAN_COUNT = SAN_ENTRIES.size(); - final String DN = "CN=localhost"; + final List sanEntries = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org"); + final int sanCount = sanEntries.size(); + final String dn = "CN=localhost"; + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm); + keyPairGenerator.initialize(keySize); KeyPair keyPair = keyPairGenerator.generateKeyPair(); - logger.info("Generating CSR with DN: " + DN); // Act - JcaPKCS10CertificationRequest csrWithSan = TlsHelper.generateCertificationRequest(DN, SAN_ENTRIES, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM); - logger.info("Created CSR with SAN: " + SAN_ENTRIES); - String testCsrPem = TlsHelper.pemEncodeJcaObject(csrWithSan); - logger.info("Encoded CSR as PEM: " + testCsrPem); + JcaPKCS10CertificationRequest csrWithSan = TlsHelper.generateCertificationRequest(dn, sanEntries, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM); // Assert String subjectName = csrWithSan.getSubject().toString(); - logger.info("CSR Subject Name: " + subjectName); - assert subjectName.equals(DN); + assertEquals(dn, subjectName); List extractedSans = extractSanFromCsr(csrWithSan); - assert extractedSans.size() == SAN_COUNT + 1; - List formattedSans = SAN_ENTRIES.stream() + assertEquals(sanCount + 1, extractedSans.size()); + List formattedSans = sanEntries.stream() .map(s -> (IPAddress.isValid(s) ? "IP Address: " + new GeneralName(GeneralName.iPAddress, s).getName() : "DNS: " + s)) .collect(Collectors.toList()); - assert extractedSans.containsAll(formattedSans); + assertTrue(extractedSans.containsAll(formattedSans)); // We check that the SANs also contain the CN - assert extractedSans.contains("DNS: localhost"); + assertTrue(extractedSans.contains("DNS: localhost")); } private List extractSanFromCsr(JcaPKCS10CertificationRequest csr) { @@ -246,7 +225,6 @@ public class TlsHelperTest { GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); GeneralName[] names = gns.getNames(); for (GeneralName name : names) { - logger.info("Type: " + name.getTagNo() + " | Name: " + name.getName()); String title = ""; if (name.getTagNo() == GeneralName.dNSName) { title = "DNS"; @@ -325,7 +303,7 @@ public class TlsHelperTest { private KeyStore setupKeystore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException { KeyStore ks = KeyStore.getInstance("JKS"); try (InputStream readStream = getClass().getClassLoader().getResourceAsStream("keystore.jks")) { - ks.load(readStream, password.toCharArray()); + ks.load(readStream, PASSWORD.toCharArray()); } return ks; } @@ -338,7 +316,7 @@ public class TlsHelperTest { HashMap certs = TlsHelper.extractCerts(keyStore); TlsHelper.outputCertsAsPem(certs, folder,".crt"); - assertEquals(folder.listFiles().length, 2); + assertEquals(2, folder.listFiles().length); for (File file : folder.listFiles()) { X509Certificate certFromFile = loadCertificate(file); @@ -354,7 +332,7 @@ public class TlsHelperTest { @Test public void testOutputToFileOneKeyAsPem(@TempDir final File folder) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { KeyStore keyStore = setupKeystore(); - HashMap keys = TlsHelper.extractKeys(keyStore, password.toCharArray()); + HashMap keys = TlsHelper.extractKeys(keyStore, PASSWORD.toCharArray()); TlsHelper.outputKeysAsPem(keys, folder, ".key"); for (File file : folder.listFiles()) { @@ -379,8 +357,39 @@ public class TlsHelperTest { @Test public void testExtractKeys() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { KeyStore keyStore = setupKeystore(); - HashMap keys = TlsHelper.extractKeys(keyStore, password.toCharArray()); + HashMap keys = TlsHelper.extractKeys(keyStore, PASSWORD.toCharArray()); assertEquals(1, keys.size()); keys.forEach((String alias, Key key) -> assertEquals("PKCS#8", key.getFormat())); } + + @Test + public void testShouldVerifyCertificateSignatureWhenSelfSigned() { + assertTrue(TlsHelper.verifyCertificateSignature(rootCert, Collections.singletonList(rootCert))); + } + + @Test + public void testShouldVerifyCertificateSignatureWithMultipleSigningCerts(@Mock X509Certificate mockCertificate) { + when(mockCertificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Mock Certificate")); + assertTrue(TlsHelper.verifyCertificateSignature(rootCert, Arrays.asList(mockCertificate, rootCert))); + } + + @Test + public void testShouldNotVerifyCertificateSignatureWithNoSigningCerts() { + assertFalse(TlsHelper.verifyCertificateSignature(rootCert, new ArrayList<>())); + } + + @Test + public void testShouldNotVerifyCertificateSignatureWithWrongSigningCert(@Mock X509Certificate mockCertificate) { + when(mockCertificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Mock Certificate")); + assertFalse(TlsHelper.verifyCertificateSignature(rootCert, Collections.singletonList(mockCertificate))); + } + + @Test + public void testParseKeyPairFromReaderShouldHandlePKCS8PrivateKey() throws Exception { + final KeyPair expectedKeyPair = TlsHelper.parseKeyPairFromReader(new FileReader("src/test/resources/rootCert.key")); + final PrivateKey expectedPrivateKey = expectedKeyPair.getPrivate(); + final KeyPair keyPair = TlsHelper.parseKeyPairFromReader(new FileReader("src/test/resources/rootCert-pkcs8.key")); + + assertEquals(expectedPrivateKey, keyPair.getPrivate()); + } }