diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java index 20497180d1..760e82ea46 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java @@ -17,7 +17,11 @@ package org.apache.nifi.security.util; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; +import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.Extension; @@ -57,16 +61,37 @@ import java.security.cert.CertificateFactory; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; public final class CertificateUtils { - private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class); private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not authenticated"; + private static final Map dnOrderMap = createDnOrderMap(); + + private static Map createDnOrderMap() { + Map orderMap = new HashMap<>(); + int count = 0; + orderMap.put(BCStyle.CN, count++); + orderMap.put(BCStyle.L, count++); + orderMap.put(BCStyle.ST, count++); + orderMap.put(BCStyle.O, count++); + orderMap.put(BCStyle.OU, count++); + orderMap.put(BCStyle.C, count++); + orderMap.put(BCStyle.STREET, count++); + orderMap.put(BCStyle.DC, count++); + orderMap.put(BCStyle.UID, count++); + return Collections.unmodifiableMap(orderMap); + } + public enum ClientAuth { NONE(0, "none"), WANT(1, "want"), @@ -349,6 +374,70 @@ public final class CertificateUtils { } } + /** + * Reorders DN to the order the elements appear in the RFC 2253 table + * + * https://www.ietf.org/rfc/rfc2253.txt + * + * String X.500 AttributeType + * ------------------------------ + * CN commonName + * L localityName + * ST stateOrProvinceName + * O organizationName + * OU organizationalUnitName + * C countryName + * STREET streetAddress + * DC domainComponent + * UID userid + * + * @param dn a possibly unordered DN + * @return the ordered dn + */ + public static String reorderDn(String dn) { + RDN[] rdNs = new X500Name(dn).getRDNs(); + Arrays.sort(rdNs, new Comparator() { + @Override + public int compare(RDN o1, RDN o2) { + AttributeTypeAndValue o1First = o1.getFirst(); + AttributeTypeAndValue o2First = o2.getFirst(); + + ASN1ObjectIdentifier o1Type = o1First.getType(); + ASN1ObjectIdentifier o2Type = o2First.getType(); + + Integer o1Rank = dnOrderMap.get(o1Type); + Integer o2Rank = dnOrderMap.get(o2Type); + if (o1Rank == null) { + if (o2Rank == null) { + int idComparison = o1Type.getId().compareTo(o2Type.getId()); + if (idComparison != 0) { + return idComparison; + } + return String.valueOf(o1Type).compareTo(String.valueOf(o2Type)); + } + return 1; + } else if (o2Rank == null) { + return -1; + } + return o1Rank - o2Rank; + } + }); + return new X500Name(rdNs).toString(); + } + + /** + * Reverses the X500Name in order make the certificate be in the right order + * [see http://stackoverflow.com/questions/7567837/attributes-reversed-in-certificate-subject-and-issuer/12645265] + * + * @param x500Name the X500Name created with the intended order + * @return the X500Name reversed + */ + private static X500Name reverseX500Name(X500Name x500Name) { + List rdns = Arrays.asList(x500Name.getRDNs()); + Collections.reverse(rdns); + return new X500Name(rdns.toArray(new RDN[rdns.size()])); + } + /** * Generates a self-signed {@link X509Certificate} suitable for use as a Certificate Authority. * @@ -368,10 +457,10 @@ public final class CertificateUtils { Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(certificateDurationDays)); X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( - new X500Name(dn), + reverseX500Name(new X500Name(dn)), BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, - new X500Name(dn), + reverseX500Name(new X500Name(dn)), subPubKeyInfo); // Set certificate extensions @@ -417,10 +506,10 @@ public final class CertificateUtils { Date endDate = new Date(startDate.getTime() + TimeUnit.DAYS.toMillis(days)); X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( - new X500Name(issuer.getSubjectDN().getName()), + reverseX500Name(new X500Name(issuer.getSubjectX500Principal().getName())), BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, - new X500Name(dn), + reverseX500Name(new X500Name(dn)), subPubKeyInfo); certBuilder.addExtension(Extension.subjectKeyIdentifier, false, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey)); diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy index 7f93f51c94..6727364257 100644 --- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy +++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy @@ -443,7 +443,7 @@ class CertificateUtilsTest extends GroovyTestCase { assertTrue(notBefore.after(inFuture(-1))); assertTrue(notBefore.before(inFuture(1))); - assertEquals(dn, x509Certificate.getIssuerDN().getName()); + assertEquals(dn, x509Certificate.getIssuerX500Principal().getName()); assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase()); assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm()); @@ -458,12 +458,12 @@ class CertificateUtilsTest extends GroovyTestCase { KeyPair issuerKeyPair = generateKeyPair(); X509Certificate issuer = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, "CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days); - String dn = "CN=testIssued,O=testOrg"; + String dn = "CN=testIssued, O=testOrg"; KeyPair keyPair = generateKeyPair(); X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, SIGNATURE_ALGORITHM, days); - assertEquals(dn, x509Certificate.getSubjectDN().toString()); - assertEquals(issuer.getSubjectDN().toString(), x509Certificate.getIssuerDN().toString()); + assertEquals(dn, x509Certificate.getSubjectX500Principal().toString()); + assertEquals(issuer.getSubjectX500Principal().toString(), x509Certificate.getIssuerX500Principal().toString()); assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey()); Date notAfter = x509Certificate.getNotAfter(); @@ -479,4 +479,22 @@ class CertificateUtilsTest extends GroovyTestCase { x509Certificate.verify(issuerKeyPair.getPublic()); } + + @Test + public void reorderShouldPutElementsInCorrectOrder() { + String cn = "CN=testcn"; + String l = "L=testl"; + String st = "ST=testst"; + String o = "O=testo"; + String ou = "OU=testou"; + String c = "C=testc"; + String street = "STREET=teststreet"; + String dc = "DC=testdc"; + String uid = "UID=testuid"; + String surname = "SURNAME=testsurname"; + String initials = "INITIALS=testinitials"; + String givenName = "GIVENNAME=testgivenname"; + assertEquals("$cn,$l,$st,$o,$ou,$c,$street,$dc,$uid,$surname,$givenName,$initials".toString(), + CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc")); + } } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java index b99b1af531..f06ac2d887 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/commandLine/ExitCode.java @@ -24,7 +24,6 @@ public enum ExitCode { SERVICE_ERROR, ERROR_PARSING_COMMAND_LINE, ERROR_GENERATING_CONFIG, - ERROR_SAME_KEY_AND_KEY_PASSWORD, ERROR_INCORRECT_NUMBER_OF_PASSWORDS, ERROR_PARSING_INT_ARG, ERROR_TOKEN_ARG_EMPTY, 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 new file mode 100644 index 0000000000..342e1e5cc6 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/StandaloneConfig.java @@ -0,0 +1,125 @@ +/* + * 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.configuration; + +import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory; + +import java.io.File; +import java.util.List; + +public class StandaloneConfig extends TlsConfig { + private File baseDir; + private NiFiPropertiesWriterFactory niFiPropertiesWriterFactory; + private List hostnames; + private List keyStorePasswords; + private List keyPasswords; + private List trustStorePasswords; + private List clientDns; + private List clientPasswords; + private boolean clientPasswordsGenerated; + private int httpsPort; + private boolean overwrite; + + public List getClientDns() { + return clientDns; + } + + public void setClientDns(List clientDns) { + this.clientDns = clientDns; + } + + public boolean isOverwrite() { + return overwrite; + } + + public void setOverwrite(boolean overwrite) { + this.overwrite = overwrite; + } + + public File getBaseDir() { + return baseDir; + } + + public void setBaseDir(File baseDir) { + this.baseDir = baseDir; + } + + public NiFiPropertiesWriterFactory getNiFiPropertiesWriterFactory() { + return niFiPropertiesWriterFactory; + } + + public void setNiFiPropertiesWriterFactory(NiFiPropertiesWriterFactory niFiPropertiesWriterFactory) { + this.niFiPropertiesWriterFactory = niFiPropertiesWriterFactory; + } + + public List getHostnames() { + return hostnames; + } + + public void setHostnames(List hostnames) { + this.hostnames = hostnames; + } + + public List getKeyStorePasswords() { + return keyStorePasswords; + } + + public void setKeyStorePasswords(List keyStorePasswords) { + this.keyStorePasswords = keyStorePasswords; + } + + public List getKeyPasswords() { + return keyPasswords; + } + + public void setKeyPasswords(List keyPasswords) { + this.keyPasswords = keyPasswords; + } + + public List getTrustStorePasswords() { + return trustStorePasswords; + } + + public void setTrustStorePasswords(List trustStorePasswords) { + this.trustStorePasswords = trustStorePasswords; + } + + public List getClientPasswords() { + return clientPasswords; + } + + public void setClientPasswords(List clientPasswords) { + this.clientPasswords = clientPasswords; + } + + public int getHttpsPort() { + return httpsPort; + } + + public void setHttpsPort(int httpsPort) { + this.httpsPort = httpsPort; + } + + public boolean isClientPasswordsGenerated() { + return clientPasswordsGenerated; + } + + public void setClientPasswordsGenerated(boolean clientPasswordsGenerated) { + this.clientPasswordsGenerated = clientPasswordsGenerated; + } +} diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java index 819b41b8d8..8aa3e40c57 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/configuration/TlsConfig.java @@ -17,6 +17,7 @@ package org.apache.nifi.toolkit.tls.configuration; +import org.apache.nifi.security.util.CertificateUtils; import org.apache.nifi.util.StringUtils; public class TlsConfig { @@ -43,7 +44,7 @@ public class TlsConfig { private int port = DEFAULT_PORT; public static String calcDefaultDn(String hostname) { - return "CN=" + hostname + ",OU=NIFI"; + return CertificateUtils.reorderDn("CN=" + hostname + ",OU=NIFI"); } public int getPort() { diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java index 5a5e049556..6215fc0e74 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/BaseTlsManager.java @@ -22,6 +22,7 @@ import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter; import org.apache.nifi.toolkit.tls.util.InputStreamFactory; import org.apache.nifi.toolkit.tls.util.OutputStreamFactory; import org.apache.nifi.toolkit.tls.util.PasswordUtil; +import org.apache.nifi.toolkit.tls.util.TlsHelper; import org.apache.nifi.util.StringUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -29,7 +30,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyStore; @@ -38,7 +38,6 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; @@ -50,6 +49,7 @@ public class BaseTlsManager { private final KeyStore keyStore; private final List> configurationWriters; private boolean differentKeyAndKeyStorePassword = false; + private boolean keyStorePasswordGenerated = false; public BaseTlsManager(TlsConfig tlsConfig) throws GeneralSecurityException, IOException { this(tlsConfig, new PasswordUtil(), FileInputStream::new); @@ -107,13 +107,14 @@ public class BaseTlsManager { String result = tlsConfig.getKeyStorePassword(); if (StringUtils.isEmpty(result)) { result = passwordUtil.generatePassword(); + keyStorePasswordGenerated = true; tlsConfig.setKeyStorePassword(result); } return result; } private KeyStore getInstance(String keyStoreType) throws KeyStoreException, NoSuchProviderException { - if (PKCS_12.equals(keyStoreType)) { + if (PKCS_12.equalsIgnoreCase(keyStoreType)) { return KeyStore.getInstance(keyStoreType, BouncyCastleProvider.PROVIDER_NAME); } else { return KeyStore.getInstance(keyStoreType); @@ -133,12 +134,9 @@ public class BaseTlsManager { return result; } - public void write(OutputStreamFactory outputStreamFactory) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { + public void write(OutputStreamFactory outputStreamFactory) throws IOException, GeneralSecurityException { String keyStorePassword = getKeyStorePassword(); - - try (OutputStream outputStream = outputStreamFactory.create(new File(tlsConfig.getKeyStore()))) { - keyStore.store(outputStream, keyStorePassword.toCharArray()); - } + tlsConfig.setKeyStorePassword(TlsHelper.writeKeyStore(keyStore, outputStreamFactory, new File(tlsConfig.getKeyStore()), keyStorePassword, keyStorePasswordGenerated)); for (ConfigurationWriter configurationWriter : configurationWriters) { configurationWriter.write(tlsConfig); diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java index 39faf28cd6..2fa0534814 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsCertificateAuthorityManager.java @@ -38,7 +38,7 @@ public class TlsCertificateAuthorityManager extends BaseTlsManager { if (entry == null) { TlsConfig tlsConfig = getTlsConfig(); KeyPair keyPair = TlsHelper.generateKeyPair(tlsConfig.getKeyPairAlgorithm(), tlsConfig.getKeySize()); - X509Certificate caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, tlsConfig.getDn(), tlsConfig.getSigningAlgorithm(), tlsConfig.getDays()); + X509Certificate caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, CertificateUtils.reorderDn(tlsConfig.getDn()), tlsConfig.getSigningAlgorithm(), tlsConfig.getDays()); entry = addPrivateKeyToKeyStore(keyPair, TlsToolkitStandalone.NIFI_KEY, caCert); } else if (!KeyStore.PrivateKeyEntry.class.isInstance(entry)) { throw new IOException("Expected " + TlsToolkitStandalone.NIFI_KEY + " alias to contain a private key entry"); diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java index 52ea5db80f..3113701814 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/manager/TlsClientManager.java @@ -22,6 +22,7 @@ import org.apache.nifi.toolkit.tls.manager.writer.ConfigurationWriter; import org.apache.nifi.toolkit.tls.util.InputStreamFactory; import org.apache.nifi.toolkit.tls.util.OutputStreamFactory; import org.apache.nifi.toolkit.tls.util.PasswordUtil; +import org.apache.nifi.toolkit.tls.util.TlsHelper; import org.apache.nifi.util.StringUtils; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; import org.bouncycastle.util.io.pem.PemWriter; @@ -34,10 +35,8 @@ import java.io.OutputStreamWriter; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -73,18 +72,18 @@ public class TlsClientManager extends BaseTlsManager { } @Override - public void write(OutputStreamFactory outputStreamFactory) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { + public void write(OutputStreamFactory outputStreamFactory) throws IOException, GeneralSecurityException { super.write(outputStreamFactory); String trustStorePassword = tlsClientConfig.getTrustStorePassword(); + boolean trustStorePasswordGenerated = false; if (StringUtils.isEmpty(trustStorePassword)) { trustStorePassword = getPasswordUtil().generatePassword(); - tlsClientConfig.setTrustStorePassword(trustStorePassword); + trustStorePasswordGenerated = true; } - try (OutputStream outputStream = outputStreamFactory.create(new File(tlsClientConfig.getTrustStore()))) { - trustStore.store(outputStream, trustStorePassword.toCharArray()); - } + trustStorePassword = TlsHelper.writeKeyStore(trustStore, outputStreamFactory, new File(tlsClientConfig.getTrustStore()), trustStorePassword, trustStorePasswordGenerated); + tlsClientConfig.setTrustStorePassword(trustStorePassword); for (ConfigurationWriter configurationWriter : configurationWriters) { configurationWriter.write(tlsClientConfig); diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java index 2c65964519..889f217cee 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java @@ -28,6 +28,7 @@ import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContextBuilder; +import org.apache.nifi.security.util.CertificateUtils; import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig; import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest; import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse; @@ -38,6 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.MessageDigest; @@ -73,7 +75,7 @@ public class TlsCertificateSigningRequestPerformer { private TlsCertificateSigningRequestPerformer(Supplier httpClientBuilderSupplier, String caHostname, String dn, String token, int port, String signingAlgorithm) { this.httpClientBuilderSupplier = httpClientBuilderSupplier; this.caHostname = caHostname; - this.dn = dn; + this.dn = CertificateUtils.reorderDn(dn); this.token = token; this.port = port; this.objectMapper = new ObjectMapper(); @@ -141,10 +143,10 @@ public class TlsCertificateSigningRequestPerformer { if (!tlsCertificateAuthorityResponse.hasCertificate()) { throw new IOException(EXPECTED_RESPONSE_TO_CONTAIN_CERTIFICATE); } - X509Certificate x509Certificate = TlsHelper.parseCertificate(tlsCertificateAuthorityResponse.getPemEncodedCertificate()); + X509Certificate x509Certificate = TlsHelper.parseCertificate(new StringReader(tlsCertificateAuthorityResponse.getPemEncodedCertificate())); x509Certificate.verify(caCertificate.getPublicKey()); if (logger.isInfoEnabled()) { - logger.info("Got certificate with dn " + x509Certificate.getSubjectDN()); + logger.info("Got certificate with dn " + x509Certificate.getSubjectX500Principal()); } return new X509Certificate[]{x509Certificate, caCertificate}; } catch (IOException e) { diff --git a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java index fb83498421..27d995ec03 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandler.java @@ -82,7 +82,11 @@ public class TlsCertificateAuthorityServiceHandler extends AbstractHandler { byte[] expectedHmac = TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey()); if (MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityRequest.getHmac())) { - X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(jcaPKCS10CertificationRequest.getSubject().toString(), + String dn = jcaPKCS10CertificationRequest.getSubject().toString(); + if (logger.isInfoEnabled()) { + logger.info("Received CSR with DN " + dn); + } + X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, jcaPKCS10CertificationRequest.getPublicKey(), caCert, keyPair, signingAlgorithm, days); writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()), TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK); 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 0136e64a52..c91e7fa43e 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 @@ -18,14 +18,17 @@ package org.apache.nifi.toolkit.tls.standalone; import org.apache.nifi.security.util.CertificateUtils; +import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig; import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig; import org.apache.nifi.toolkit.tls.configuration.TlsConfig; +import org.apache.nifi.toolkit.tls.manager.BaseTlsManager; import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager; import org.apache.nifi.toolkit.tls.manager.TlsClientManager; import org.apache.nifi.toolkit.tls.manager.writer.NifiPropertiesTlsClientConfigWriter; import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory; import org.apache.nifi.toolkit.tls.util.OutputStreamFactory; import org.apache.nifi.toolkit.tls.util.TlsHelper; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; import org.bouncycastle.util.io.pem.PemWriter; import org.slf4j.Logger; @@ -33,11 +36,14 @@ import org.slf4j.LoggerFactory; 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.cert.Certificate; import java.security.cert.X509Certificate; import java.util.List; @@ -57,17 +63,8 @@ public class TlsToolkitStandalone { this.outputStreamFactory = outputStreamFactory; } - public void createNifiKeystoresAndTrustStores(File baseDir, TlsConfig tlsConfig, NiFiPropertiesWriterFactory niFiPropertiesWriterFactory, List hostnames, List keyStorePasswords, - List keyPasswords, List trustStorePasswords, int httpsPort) throws GeneralSecurityException, IOException { - String signingAlgorithm = tlsConfig.getSigningAlgorithm(); - int days = tlsConfig.getDays(); - String keyPairAlgorithm = tlsConfig.getKeyPairAlgorithm(); - int keySize = tlsConfig.getKeySize(); - TlsCertificateAuthorityManager tlsCertificateAuthorityManager = new TlsCertificateAuthorityManager(tlsConfig); - KeyStore.PrivateKeyEntry privateKeyEntry = tlsCertificateAuthorityManager.getOrGenerateCertificateAuthority(); - X509Certificate certificate = (X509Certificate) privateKeyEntry.getCertificateChain()[0]; - KeyPair caKeyPair = new KeyPair(certificate.getPublicKey(), privateKeyEntry.getPrivateKey()); - + public void createNifiKeystoresAndTrustStores(StandaloneConfig standaloneConfig) throws GeneralSecurityException, IOException { + File baseDir = standaloneConfig.getBaseDir(); if (!baseDir.exists() && !baseDir.mkdirs()) { throw new IOException(baseDir + " doesn't exist and unable to create it."); } @@ -76,48 +73,107 @@ public class TlsToolkitStandalone { throw new IOException("Expected directory to output to"); } - if (logger.isInfoEnabled()) { - logger.info("Running standalone certificate generation with output directory " + baseDir + " and hostnames " + hostnames); - } + String signingAlgorithm = standaloneConfig.getSigningAlgorithm(); + int days = standaloneConfig.getDays(); + String keyPairAlgorithm = standaloneConfig.getKeyPairAlgorithm(); + int keySize = standaloneConfig.getKeySize(); File nifiCert = new File(baseDir, NIFI_CERT + ".pem"); - if (nifiCert.exists()) { - throw new IOException(nifiCert.getAbsolutePath() + " exists already."); - } - File nifiKey = new File(baseDir, NIFI_KEY + ".key"); - if (nifiKey.exists()) { - throw new IOException(nifiKey.getAbsolutePath() + " exists already."); - } - for (String hostname : hostnames) { - File hostDirectory = new File(baseDir, hostname); - if (hostDirectory.exists()) { - throw new IOException("Output destination for host " + hostname + " (" + hostDirectory.getAbsolutePath() + ") exists already."); + X509Certificate certificate; + KeyPair caKeyPair; + + List hostnames = standaloneConfig.getHostnames(); + if (logger.isInfoEnabled()) { + logger.info("Running standalone certificate generation with output directory " + baseDir); + } + if (nifiCert.exists()) { + if (!nifiKey.exists()) { + throw new IOException(nifiCert + " exists already, but " + nifiKey + " does not, we need both certificate and key to continue with an existing CA."); + } + try (FileReader pemEncodedCertificate = new FileReader(nifiCert)) { + certificate = TlsHelper.parseCertificate(pemEncodedCertificate); + } + try (FileReader pemEncodedKeyPair = new FileReader(nifiKey)) { + caKeyPair = TlsHelper.parseKeyPair(pemEncodedKeyPair); + } + + certificate.verify(caKeyPair.getPublic()); + if (!caKeyPair.getPublic().equals(certificate.getPublicKey())) { + throw new IOException("Expected " + nifiKey + " to correspond to CA certificate at " + nifiCert); + } + + if (logger.isInfoEnabled()) { + logger.info("Using existing CA certificate " + nifiCert + " and key " + nifiKey); + } + } else if (nifiKey.exists()) { + throw new IOException(nifiKey + " exists already, but " + nifiCert + " does not, we need both certificate and key to continue with an existing CA."); + } else { + TlsCertificateAuthorityManager tlsCertificateAuthorityManager = new TlsCertificateAuthorityManager(standaloneConfig); + KeyStore.PrivateKeyEntry privateKeyEntry = tlsCertificateAuthorityManager.getOrGenerateCertificateAuthority(); + certificate = (X509Certificate) privateKeyEntry.getCertificateChain()[0]; + caKeyPair = new KeyPair(certificate.getPublicKey(), privateKeyEntry.getPrivateKey()); + + try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiCert)))) { + pemWriter.writeObject(new JcaMiscPEMGenerator(certificate)); + } + + try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiKey)))) { + pemWriter.writeObject(new JcaMiscPEMGenerator(caKeyPair)); + } + + if (logger.isInfoEnabled()) { + logger.info("Generated new CA certificate " + nifiCert + " and key " + nifiKey); } } - try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiCert)))) { - pemWriter.writeObject(new JcaMiscPEMGenerator(certificate)); - } + List keyStorePasswords = standaloneConfig.getKeyStorePasswords(); + List keyPasswords = standaloneConfig.getKeyPasswords(); + List trustStorePasswords = standaloneConfig.getTrustStorePasswords(); + NiFiPropertiesWriterFactory niFiPropertiesWriterFactory = standaloneConfig.getNiFiPropertiesWriterFactory(); + int httpsPort = standaloneConfig.getHttpsPort(); + boolean overwrite = standaloneConfig.isOverwrite(); - try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(outputStreamFactory.create(nifiKey)))) { - pemWriter.writeObject(new JcaMiscPEMGenerator(caKeyPair)); + if (hostnames.isEmpty() && logger.isInfoEnabled()) { + logger.info("No " + TlsToolkitStandaloneCommandLine.HOSTNAMES_ARG + " specified, not generating any host certificates or configuration."); } - for (int i = 0; i < hostnames.size(); i++) { String hostname = hostnames.get(i); File hostDir = new File(baseDir, hostname); - if (!hostDir.mkdirs()) { + TlsClientConfig tlsClientConfig = new TlsClientConfig(standaloneConfig); + File keystore = new File(hostDir, "keystore." + tlsClientConfig.getKeyStoreType().toLowerCase()); + File truststore = new File(hostDir, "truststore." + tlsClientConfig.getTrustStoreType().toLowerCase()); + + if (hostDir.exists()) { + if (!hostDir.isDirectory()) { + throw new IOException(hostDir + " exists but is not a directory."); + } else if (overwrite) { + if (logger.isInfoEnabled()) { + logger.info("Overwriting any existing ssl configuration in " + hostDir); + } + keystore.delete(); + if (keystore.exists()) { + throw new IOException("Keystore " + keystore + " already exists and couldn't be deleted."); + } + truststore.delete(); + if (truststore.exists()) { + throw new IOException("Truststore " + truststore + " already exists and couldn't be deleted."); + } + } else { + throw new IOException(hostDir + " exists and overwrite is not set."); + } + } else if (!hostDir.mkdirs()) { throw new IOException("Unable to make directory: " + hostDir.getAbsolutePath()); + } else if (logger.isInfoEnabled()) { + logger.info("Writing new ssl configuration to " + hostDir); } - TlsClientConfig tlsClientConfig = new TlsClientConfig(tlsConfig); - tlsClientConfig.setKeyStore(new File(hostDir, "keystore." + tlsClientConfig.getKeyStoreType().toLowerCase()).getAbsolutePath()); + tlsClientConfig.setKeyStore(keystore.getAbsolutePath()); tlsClientConfig.setKeyStorePassword(keyStorePasswords.get(i)); tlsClientConfig.setKeyPassword(keyPasswords.get(i)); - tlsClientConfig.setTrustStore(new File(hostDir, "truststore." + tlsClientConfig.getTrustStoreType().toLowerCase()).getAbsolutePath()); + tlsClientConfig.setTrustStore(truststore.getAbsolutePath()); tlsClientConfig.setTrustStorePassword(trustStorePasswords.get(i)); TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig); KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize); @@ -132,8 +188,50 @@ public class TlsToolkitStandalone { } } + List clientDns = standaloneConfig.getClientDns(); + if (standaloneConfig.getClientDns().isEmpty() && logger.isInfoEnabled()) { + logger.info("No " + TlsToolkitStandaloneCommandLine.CLIENT_CERT_DN_ARG + " specified, not generating any client certificates."); + } + + List clientPasswords = standaloneConfig.getClientPasswords(); + for (int i = 0; i < clientDns.size(); i++) { + String reorderedDn = CertificateUtils.reorderDn(clientDns.get(i)); + String clientDnFile = getClientDnFile(reorderedDn); + File clientCertFile = new File(baseDir, clientDnFile + ".p12"); + + if (clientCertFile.exists()) { + if (overwrite) { + if (logger.isInfoEnabled()) { + logger.info("Overwriting existing client cert " + clientCertFile); + } + } else { + throw new IOException(clientCertFile + " exists and overwrite is not set."); + } + } else if (logger.isInfoEnabled()) { + logger.info("Generating new client certificate " + clientCertFile); + } + KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize); + X509Certificate clientCert = CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), certificate, caKeyPair, signingAlgorithm, days); + KeyStore keyStore = KeyStore.getInstance(BaseTlsManager.PKCS_12, BouncyCastleProvider.PROVIDER_NAME); + keyStore.load(null, null); + keyStore.setKeyEntry(NIFI_KEY, keyPair.getPrivate(), null, new Certificate[]{clientCert, certificate}); + String password = TlsHelper.writeKeyStore(keyStore, outputStreamFactory, clientCertFile, clientPasswords.get(i), standaloneConfig.isClientPasswordsGenerated()); + + try (FileWriter fileWriter = new FileWriter(new File(baseDir, clientDnFile + ".password"))) { + fileWriter.write(password); + } + + if (logger.isInfoEnabled()) { + logger.info("Successfully generated client certificate " + clientCertFile); + } + } + if (logger.isInfoEnabled()) { - logger.info("Successfully generated TLS configuration for all hosts"); + logger.info("tls-toolkit standalone completed successfully"); } } + + protected static String getClientDnFile(String clientDn) { + return clientDn.replace(',', '_').replace(' ', '_'); + } } 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 697148fcf8..602fce417c 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 @@ -21,7 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine; import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException; import org.apache.nifi.toolkit.tls.commandLine.ExitCode; -import org.apache.nifi.toolkit.tls.configuration.TlsConfig; +import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig; import org.apache.nifi.toolkit.tls.properties.NiFiPropertiesWriterFactory; import org.apache.nifi.toolkit.tls.util.PasswordUtil; import org.apache.nifi.toolkit.tls.util.TlsHelper; @@ -48,9 +48,12 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { public static final String KEY_PASSWORD_ARG = "keyPassword"; public static final String HOSTNAMES_ARG = "hostnames"; public static final String HTTPS_PORT_ARG = "httpsPort"; + public static final String OVERWRITE_ARG = "isOverwrite"; + public static final String CLIENT_CERT_DN_ARG = "clientCertDn"; + public static final String CLIENT_CERT_PASSWORD_ARG = "clientCertPassword"; public static final String DEFAULT_OUTPUT_DIRECTORY = "../" + Paths.get(".").toAbsolutePath().normalize().getFileName().toString(); - public static final int DEFAULT_HTTPS_PORT = 10443; + public static final int DEFAULT_HTTPS_PORT = 9091; public static final String DESCRIPTION = "Creates certificates and config files for nifi cluster."; @@ -64,6 +67,10 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { private List keyStorePasswords; private List keyPasswords; private List trustStorePasswords; + private List clientDns; + private List clientPasswords; + private boolean clientPasswordsGenerated; + private boolean overwrite; public TlsToolkitStandaloneCommandLine() { this(new PasswordUtil()); @@ -73,12 +80,15 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { super(DESCRIPTION); this.passwordUtil = passwordUtil; addOptionWithArg("o", OUTPUT_DIRECTORY_ARG, "The directory to output keystores, truststore, config files.", DEFAULT_OUTPUT_DIRECTORY); - addOptionWithArg("n", HOSTNAMES_ARG, "Comma separated list of hostnames.", TlsConfig.DEFAULT_HOSTNAME); + addOptionWithArg("n", HOSTNAMES_ARG, "Comma separated list of hostnames."); addOptionWithArg("p", HTTPS_PORT_ARG, "Https port to use.", DEFAULT_HTTPS_PORT); addOptionWithArg("f", NIFI_PROPERTIES_FILE_ARG, "Base nifi.properties file to update. (Embedded file identical to the one in a default NiFi install will be used if not specified.)"); addOptionWithArg("S", KEY_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)"); addOptionWithArg("K", KEY_PASSWORD_ARG, "Key password to use. Must either be one value or one for each host. (autogenerate if not specified)"); addOptionWithArg("P", TRUST_STORE_PASSWORD_ARG, "Keystore password to use. Must either be one value or one for each host. (autogenerate if not specified)"); + addOptionWithArg("C", CLIENT_CERT_DN_ARG, "Generate client certificate suitable for use in browser with specified DN. (Can be specified multiple times.)"); + addOptionWithArg("B", CLIENT_CERT_PASSWORD_ARG, "Password for client certificate. Must either be one value or one for each client DN. (autogenerate if not specified)"); + addOptionNoArg("O", OVERWRITE_ARG, "Overwrite existing host output."); } public static void main(String[] args) { @@ -90,9 +100,7 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { System.exit(e.getExitCode()); } try { - new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.getBaseDir(), tlsToolkitStandaloneCommandLine.createConfig(), - tlsToolkitStandaloneCommandLine.getNiFiPropertiesWriterFactory(), tlsToolkitStandaloneCommandLine.getHostnames(), tlsToolkitStandaloneCommandLine.getKeyStorePasswords(), - tlsToolkitStandaloneCommandLine.getKeyPasswords(), tlsToolkitStandaloneCommandLine.getTrustStorePasswords(), tlsToolkitStandaloneCommandLine.getHttpsPort()); + new TlsToolkitStandalone().createNifiKeystoresAndTrustStores(tlsToolkitStandaloneCommandLine.createConfig()); } catch (Exception e) { tlsToolkitStandaloneCommandLine.printUsage("Error creating generating tls configuration. (" + e.getMessage() + ")"); System.exit(ExitCode.ERROR_GENERATING_CONFIG.ordinal()); @@ -105,13 +113,29 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { CommandLine commandLine = super.doParse(args); String outputDirectory = commandLine.getOptionValue(OUTPUT_DIRECTORY_ARG, DEFAULT_OUTPUT_DIRECTORY); baseDir = new File(outputDirectory); - hostnames = Arrays.stream(commandLine.getOptionValue(HOSTNAMES_ARG, TlsConfig.DEFAULT_HOSTNAME).split(",")).map(String::trim).collect(Collectors.toList()); + + if (commandLine.hasOption(HOSTNAMES_ARG)) { + hostnames = Collections.unmodifiableList(Arrays.stream(commandLine.getOptionValue(HOSTNAMES_ARG).split(",")).map(String::trim).collect(Collectors.toList())); + } else { + hostnames = Collections.emptyList(); + } + + String[] clientDnValues = commandLine.getOptionValues(CLIENT_CERT_DN_ARG); + if (clientDnValues != null) { + clientDns = Collections.unmodifiableList(Arrays.stream(clientDnValues).collect(Collectors.toList())); + } else { + clientDns = Collections.emptyList(); + } + httpsPort = getIntValue(commandLine, HTTPS_PORT_ARG, DEFAULT_HTTPS_PORT); int numHosts = hostnames.size(); - keyStorePasswords = Collections.unmodifiableList(getPasswords(KEY_STORE_PASSWORD_ARG, commandLine, numHosts)); + keyStorePasswords = Collections.unmodifiableList(getPasswords(KEY_STORE_PASSWORD_ARG, commandLine, numHosts, HOSTNAMES_ARG)); keyPasswords = Collections.unmodifiableList(getKeyPasswords(commandLine, keyStorePasswords)); - trustStorePasswords = Collections.unmodifiableList(getPasswords(TRUST_STORE_PASSWORD_ARG, commandLine, numHosts)); + trustStorePasswords = Collections.unmodifiableList(getPasswords(TRUST_STORE_PASSWORD_ARG, commandLine, numHosts, HOSTNAMES_ARG)); + clientPasswords = Collections.unmodifiableList(getPasswords(CLIENT_CERT_PASSWORD_ARG, commandLine, clientDns.size(), CLIENT_CERT_DN_ARG)); + clientPasswordsGenerated = commandLine.getOptionValues(CLIENT_CERT_PASSWORD_ARG) == null; + overwrite = commandLine.hasOption(OVERWRITE_ARG); String nifiPropertiesFile = commandLine.getOptionValue(NIFI_PROPERTIES_FILE_ARG, ""); try { @@ -128,64 +152,50 @@ public class TlsToolkitStandaloneCommandLine extends BaseCommandLine { return commandLine; } - private List getPasswords(String arg, CommandLine commandLine, int numHosts) throws CommandLineParseException { + private List getPasswords(String arg, CommandLine commandLine, int num, String numArg) throws CommandLineParseException { String[] optionValues = commandLine.getOptionValues(arg); if (optionValues == null) { - return IntStream.range(0, numHosts).mapToObj(operand -> passwordUtil.generatePassword()).collect(Collectors.toList()); + return IntStream.range(0, num).mapToObj(operand -> passwordUtil.generatePassword()).collect(Collectors.toList()); } if (optionValues.length == 1) { - return IntStream.range(0, numHosts).mapToObj(value -> optionValues[0]).collect(Collectors.toList()); - } else if (optionValues.length == numHosts) { + return IntStream.range(0, num).mapToObj(value -> optionValues[0]).collect(Collectors.toList()); + } else if (optionValues.length == num) { return Arrays.stream(optionValues).collect(Collectors.toList()); } - return printUsageAndThrow("Expected either 1 value or " + numHosts + " (the number of hostnames) values for " + arg, ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS); + return printUsageAndThrow("Expected either 1 value or " + num + " (the number of " + numArg + ") values for " + arg, ExitCode.ERROR_INCORRECT_NUMBER_OF_PASSWORDS); } private List getKeyPasswords(CommandLine commandLine, List keyStorePasswords) throws CommandLineParseException { if (differentPasswordForKeyAndKeystore() || commandLine.hasOption(KEY_PASSWORD_ARG)) { - return getPasswords(KEY_PASSWORD_ARG, commandLine, keyStorePasswords.size()); + return getPasswords(KEY_PASSWORD_ARG, commandLine, keyStorePasswords.size(), HOSTNAMES_ARG); } return new ArrayList<>(keyStorePasswords); } - public File getBaseDir() { - return baseDir; - } + public StandaloneConfig createConfig() { + StandaloneConfig standaloneConfig = new StandaloneConfig(); - public List getHostnames() { - return hostnames; - } + standaloneConfig.setBaseDir(baseDir); + standaloneConfig.setNiFiPropertiesWriterFactory(niFiPropertiesWriterFactory); + standaloneConfig.setHostnames(hostnames); + standaloneConfig.setKeyStorePasswords(keyStorePasswords); + standaloneConfig.setKeyPasswords(keyPasswords); + standaloneConfig.setTrustStorePasswords(trustStorePasswords); + standaloneConfig.setHttpsPort(httpsPort); + standaloneConfig.setOverwrite(overwrite); + standaloneConfig.setClientDns(clientDns); + standaloneConfig.setClientPasswords(clientPasswords); + standaloneConfig.setClientPasswordsGenerated(clientPasswordsGenerated); - public int getHttpsPort() { - return httpsPort; - } + standaloneConfig.setCaHostname(getCertificateAuthorityHostname()); + standaloneConfig.setKeyStore("nifi-ca-" + KEYSTORE + getKeyStoreType().toLowerCase()); + standaloneConfig.setKeyStoreType(getKeyStoreType()); + standaloneConfig.setKeySize(getKeySize()); + standaloneConfig.setKeyPairAlgorithm(getKeyAlgorithm()); + standaloneConfig.setSigningAlgorithm(getSigningAlgorithm()); + standaloneConfig.setDays(getDays()); + standaloneConfig.initDefaults(); - public NiFiPropertiesWriterFactory getNiFiPropertiesWriterFactory() { - return niFiPropertiesWriterFactory; - } - - public List getKeyStorePasswords() { - return keyStorePasswords; - } - - public List getKeyPasswords() { - return keyPasswords; - } - - public List getTrustStorePasswords() { - return trustStorePasswords; - } - - public TlsConfig createConfig() throws IOException { - TlsConfig tlsConfig = new TlsConfig(); - tlsConfig.setCaHostname(getCertificateAuthorityHostname()); - tlsConfig.setKeyStore("nifi-ca-" + KEYSTORE + getKeyStoreType().toLowerCase()); - tlsConfig.setKeyStoreType(getKeyStoreType()); - tlsConfig.setKeySize(getKeySize()); - tlsConfig.setKeyPairAlgorithm(getKeyAlgorithm()); - tlsConfig.setSigningAlgorithm(getSigningAlgorithm()); - tlsConfig.setDays(getDays()); - tlsConfig.initDefaults(); - return tlsConfig; + return standaloneConfig; } } 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 0622b9e29d..05a5dcbd13 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,29 +17,38 @@ package org.apache.nifi.toolkit.tls.util; +import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; +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.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; 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 javax.security.auth.x500.X500Principal; +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.Security; @@ -47,10 +56,78 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class TlsHelper { + private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class); + private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128; + public static final String JCE_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html"; + public static final String ILLEGAL_KEY_SIZE = "illegal key size"; + private static boolean isUnlimitedStrengthCryptographyEnabled; + + // Evaluate an unlimited strength algorithm to determine if we support the capability we have on the system + static { + try { + isUnlimitedStrengthCryptographyEnabled = (Cipher.getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH); + } catch (NoSuchAlgorithmException e) { + // if there are issues with this, we default back to the value established + isUnlimitedStrengthCryptographyEnabled = false; + } + } + + private static void logTruncationWarning(File file) { + String fileToString = file.toString(); + String fileName = file.getName(); + logger.warn("**********************************************************************************"); + logger.warn(" WARNING!!!!"); + logger.warn("**********************************************************************************"); + logger.warn("Unlimited JCE Policy is not installed which means we cannot utilize a"); + logger.warn("PKCS12 password longer than 7 characters."); + logger.warn("Autogenerated password has been reduced to 7 characters."); + logger.warn(""); + logger.warn("Please strongly consider installing Unlimited JCE Policy at"); + logger.warn(JCE_URL); + logger.warn(""); + logger.warn("Another alternative is to add a stronger password with the openssl tool to the"); + logger.warn("resulting client certificate: " + fileToString); + logger.warn(""); + logger.warn("openssl pkcs12 -in '" + fileToString + "' -out '/tmp/" + fileName + "'"); + logger.warn("openssl pkcs12 -export -in '/tmp/" + fileName + "' -out '" + fileToString + "'"); + logger.warn("rm -f '/tmp/" + fileName + "'"); + logger.warn(""); + logger.warn("**********************************************************************************"); + + } + private TlsHelper() { } + public static boolean isUnlimitedStrengthCryptographyEnabled() { + return isUnlimitedStrengthCryptographyEnabled; + } + + public static String writeKeyStore(KeyStore keyStore, OutputStreamFactory outputStreamFactory, File file, String password, boolean generatedPassword) throws IOException, GeneralSecurityException { + try (OutputStream fileOutputStream = outputStreamFactory.create(file)) { + keyStore.store(fileOutputStream, password.toCharArray()); + } catch (IOException e) { + if (e.getMessage().toLowerCase().contains(ILLEGAL_KEY_SIZE) && !isUnlimitedStrengthCryptographyEnabled()) { + if (generatedPassword) { + file.delete(); + String truncatedPassword = password.substring(0, 7); + try (OutputStream fileOutputStream = outputStreamFactory.create(file)) { + keyStore.store(fileOutputStream, truncatedPassword.toCharArray()); + } + logTruncationWarning(file); + return truncatedPassword; + } else { + throw new GeneralSecurityException("Specified password for " + file + " too long to work without unlimited JCE policy installed." + + System.lineSeparator() + "Please see " + JCE_URL); + } + } else { + throw e; + } + } + return password; + } + public static void addBouncyCastleProvider() { Security.addProvider(new BouncyCastleProvider()); } @@ -90,13 +167,21 @@ public class TlsHelper { } } - public static X509Certificate parseCertificate(String pemEncodedCertificate) throws IOException, CertificateException { - try (PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCertificate))) { + public static X509Certificate parseCertificate(Reader pemEncodedCertificate) throws IOException, CertificateException { + return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(parsePem(X509CertificateHolder.class, pemEncodedCertificate)); + } + + public static KeyPair parseKeyPair(Reader pemEncodedKeyPair) throws IOException { + return new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getKeyPair(parsePem(PEMKeyPair.class, pemEncodedKeyPair)); + } + + public static T parsePem(Class clazz, Reader pemReader) throws IOException { + try (PEMParser pemParser = new PEMParser(pemReader)) { Object object = pemParser.readObject(); - if (!X509CertificateHolder.class.isInstance(object)) { - throw new IOException("Expected " + X509CertificateHolder.class); + if (!clazz.isInstance(object)) { + throw new IOException("Expected " + clazz); } - return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate((X509CertificateHolder) object); + return (T) object; } } @@ -105,7 +190,7 @@ public class TlsHelper { } public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn, KeyPair keyPair, String signingAlgorithm) throws OperatorCreationException { - JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(requestedDn), keyPair.getPublic()); + JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(requestedDn), keyPair.getPublic()); JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm); return new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate()))); } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java index 78815003af..ad5fa12686 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/TlsCertificateAuthorityTest.java @@ -107,7 +107,7 @@ public class TlsCertificateAuthorityTest { clientConfig = new TlsClientConfig(); clientConfig.setCaHostname("localhost"); - clientConfig.setDn("CN=otherHostname,OU=NIFI"); + clientConfig.setDn("OU=NIFI,CN=otherHostname"); clientConfig.setKeyStore(clientKeyStore); clientConfig.setTrustStore(clientTrustStore); clientConfig.setToken(myTestTokenUseSomethingStronger); @@ -227,7 +227,7 @@ public class TlsCertificateAuthorityTest { assertEquals(caCertificate, clientTrustStore.getCertificate(TlsToolkitStandalone.NIFI_CERT)); } - private void assertPrivateAndPublicKeyMatch(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + public static void assertPrivateAndPublicKeyMatch(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Signature signature = Signature.getInstance(TlsConfig.DEFAULT_SIGNING_ALGORITHM); signature.initSign(privateKey); byte[] bytes = "test string".getBytes(StandardCharsets.UTF_8); diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java index bf355fa0ee..09bc8f045d 100644 --- a/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityServiceHandlerTest.java @@ -145,7 +145,7 @@ public class TlsCertificateAuthorityServiceHandlerTest { tlsCertificateAuthorityServiceHandler.handle(null, baseRequest, httpServletRequest, httpServletResponse); assertEquals(Response.SC_OK, statusCode); assertArrayEquals(testCaHmac, getResponse().getHmac()); - X509Certificate certificate = TlsHelper.parseCertificate(getResponse().getPemEncodedCertificate()); + X509Certificate certificate = TlsHelper.parseCertificate(new StringReader(getResponse().getPemEncodedCertificate())); assertEquals(certificateKeyPair.getPublic(), certificate.getPublicKey()); assertEquals(new X500Name(requestedDn), new X500Name(certificate.getSubjectDN().toString())); certificate.verify(caCert.getPublicKey()); 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 fd45eb4789..82c67d9d2e 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 @@ -19,6 +19,8 @@ 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.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; @@ -31,6 +33,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.security.SecureRandom; +import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.Random; @@ -75,7 +78,7 @@ public class TlsToolkitStandaloneCommandLineTest { } @Test - public void testKeyAlgorithm() throws CommandLineParseException, IOException { + public void testKeyAlgorithm() throws CommandLineParseException { String testKeyAlgorithm = "testKeyAlgorithm"; tlsToolkitStandaloneCommandLine.parse("-a", testKeyAlgorithm); assertEquals(testKeyAlgorithm, tlsToolkitStandaloneCommandLine.createConfig().getKeyPairAlgorithm()); @@ -115,7 +118,7 @@ public class TlsToolkitStandaloneCommandLineTest { } @Test - public void testDays() throws CommandLineParseException, IOException { + public void testDays() throws CommandLineParseException { int testDays = 29; tlsToolkitStandaloneCommandLine.parse("-d", Integer.toString(testDays)); assertEquals(testDays, tlsToolkitStandaloneCommandLine.createConfig().getDays()); @@ -132,7 +135,7 @@ public class TlsToolkitStandaloneCommandLineTest { public void testOutputDirectory() throws CommandLineParseException { String testPath = File.separator + "fake" + File.separator + "path" + File.separator + "doesnt" + File.separator + "exist"; tlsToolkitStandaloneCommandLine.parse("-o", testPath); - assertEquals(testPath, tlsToolkitStandaloneCommandLine.getBaseDir().getPath()); + assertEquals(testPath, tlsToolkitStandaloneCommandLine.createConfig().getBaseDir().getPath()); } @Test @@ -142,7 +145,7 @@ public class TlsToolkitStandaloneCommandLineTest { tlsToolkitStandaloneCommandLine.parse("-n", nifi1 + " , " + nifi2); - List hostnames = tlsToolkitStandaloneCommandLine.getHostnames(); + List hostnames = tlsToolkitStandaloneCommandLine.createConfig().getHostnames(); assertEquals(2, hostnames.size()); assertEquals(nifi1, hostnames.get(0)); assertEquals(nifi2, hostnames.get(1)); @@ -152,7 +155,7 @@ public class TlsToolkitStandaloneCommandLineTest { public void testHttpsPort() throws CommandLineParseException { int testPort = 8998; tlsToolkitStandaloneCommandLine.parse("-p", Integer.toString(testPort)); - assertEquals(testPort, tlsToolkitStandaloneCommandLine.getHttpsPort()); + assertEquals(testPort, tlsToolkitStandaloneCommandLine.createConfig().getHttpsPort()); } @Test @@ -179,10 +182,10 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testNotSameKeyAndKeystorePassword() throws CommandLineParseException { - tlsToolkitStandaloneCommandLine.parse("-g"); - List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords(); - List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords(); - assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size()); + tlsToolkitStandaloneCommandLine.parse("-g", "-n", TlsConfig.DEFAULT_HOSTNAME); + List keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(); + List keyPasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords(); + assertEquals(1, tlsToolkitStandaloneCommandLine.createConfig().getHostnames().size()); assertEquals(1, keyStorePasswords.size()); assertEquals(1, keyPasswords.size()); assertNotEquals(keyStorePasswords.get(0), keyPasswords.get(0)); @@ -190,10 +193,10 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testSameKeyAndKeystorePassword() throws CommandLineParseException { - tlsToolkitStandaloneCommandLine.parse(); - List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords(); - List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords(); - assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size()); + tlsToolkitStandaloneCommandLine.parse("-n", TlsConfig.DEFAULT_HOSTNAME); + List keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(); + List keyPasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords(); + assertEquals(1, tlsToolkitStandaloneCommandLine.createConfig().getHostnames().size()); assertEquals(1, keyStorePasswords.size()); assertEquals(1, keyPasswords.size()); assertEquals(keyStorePasswords.get(0), keyPasswords.get(0)); @@ -202,19 +205,19 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testSameKeyAndKeystorePasswordWithKeystorePasswordSpecified() throws CommandLineParseException { String testPassword = "testPassword"; - tlsToolkitStandaloneCommandLine.parse("-S", testPassword); - List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords(); + tlsToolkitStandaloneCommandLine.parse("-S", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME); + List keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(); assertEquals(1, keyStorePasswords.size()); assertEquals(testPassword, keyStorePasswords.get(0)); - assertEquals(keyStorePasswords, tlsToolkitStandaloneCommandLine.getKeyPasswords()); + assertEquals(keyStorePasswords, tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords()); } @Test public void testSameKeyAndKeystorePasswordWithKeyPasswordSpecified() throws CommandLineParseException { String testPassword = "testPassword"; - tlsToolkitStandaloneCommandLine.parse("-K", testPassword); - List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords(); - assertNotEquals(tlsToolkitStandaloneCommandLine.getKeyStorePasswords(), keyPasswords); + tlsToolkitStandaloneCommandLine.parse("-K", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME); + List keyPasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords(); + assertNotEquals(tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(), keyPasswords); assertEquals(1, keyPasswords.size()); assertEquals(testPassword, keyPasswords.get(0)); } @@ -222,8 +225,8 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testKeyStorePasswordArg() throws CommandLineParseException { String testPassword = "testPassword"; - tlsToolkitStandaloneCommandLine.parse("-S", testPassword); - List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords(); + tlsToolkitStandaloneCommandLine.parse("-S", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME); + List keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(); assertEquals(1, keyStorePasswords.size()); assertEquals(testPassword, keyStorePasswords.get(0)); } @@ -233,7 +236,7 @@ public class TlsToolkitStandaloneCommandLineTest { String testPassword1 = "testPassword1"; String testPassword2 = "testPassword2"; tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-S", testPassword1, "-S", testPassword2); - List keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords(); + List keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords(); assertEquals(2, keyStorePasswords.size()); assertEquals(testPassword1, keyStorePasswords.get(0)); assertEquals(testPassword2, keyStorePasswords.get(1)); @@ -254,8 +257,8 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testKeyPasswordArg() throws CommandLineParseException { String testPassword = "testPassword"; - tlsToolkitStandaloneCommandLine.parse("-K", testPassword); - List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords(); + tlsToolkitStandaloneCommandLine.parse("-K", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME); + List keyPasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords(); assertEquals(1, keyPasswords.size()); assertEquals(testPassword, keyPasswords.get(0)); } @@ -265,7 +268,7 @@ public class TlsToolkitStandaloneCommandLineTest { String testPassword1 = "testPassword1"; String testPassword2 = "testPassword2"; tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-K", testPassword1, "-K", testPassword2); - List keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords(); + List keyPasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyPasswords(); assertEquals(2, keyPasswords.size()); assertEquals(testPassword1, keyPasswords.get(0)); assertEquals(testPassword2, keyPasswords.get(1)); @@ -286,8 +289,8 @@ public class TlsToolkitStandaloneCommandLineTest { @Test public void testTruststorePasswordArg() throws CommandLineParseException { String testPassword = "testPassword"; - tlsToolkitStandaloneCommandLine.parse("-P", testPassword); - List trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords(); + tlsToolkitStandaloneCommandLine.parse("-P", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME); + List trustStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getTrustStorePasswords(); assertEquals(1, trustStorePasswords.size()); assertEquals(testPassword, trustStorePasswords.get(0)); } @@ -297,7 +300,7 @@ public class TlsToolkitStandaloneCommandLineTest { String testPassword1 = "testPassword1"; String testPassword2 = "testPassword2"; tlsToolkitStandaloneCommandLine.parse("-n", "nifi1,nifi2", "-P", testPassword1, "-P", testPassword2); - List trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords(); + List trustStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getTrustStorePasswords(); assertEquals(2, trustStorePasswords.size()); assertEquals(testPassword1, trustStorePasswords.get(0)); assertEquals(testPassword2, trustStorePasswords.get(1)); @@ -315,8 +318,54 @@ public class TlsToolkitStandaloneCommandLineTest { } } + @Test + public void testClientDnDefault() throws CommandLineParseException { + tlsToolkitStandaloneCommandLine.parse(); + assertEquals(Collections.emptyList(), tlsToolkitStandaloneCommandLine.createConfig().getClientDns()); + } + + @Test + public void testClientDnSingle() throws CommandLineParseException { + String testCn = "OU=NIFI,CN=testuser"; + tlsToolkitStandaloneCommandLine.parse("-C", testCn); + List clientDns = tlsToolkitStandaloneCommandLine.createConfig().getClientDns(); + assertEquals(1, clientDns.size()); + assertEquals(testCn, clientDns.get(0)); + } + + @Test + public void testClientDnMulti() throws CommandLineParseException { + String testCn = "OU=NIFI,CN=testuser"; + String testCn2 = "OU=NIFI,CN=testuser2"; + tlsToolkitStandaloneCommandLine.parse("-C", testCn, "-C", testCn2); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createConfig(); + List clientDns = standaloneConfig.getClientDns(); + assertEquals(2, clientDns.size()); + assertEquals(testCn, clientDns.get(0)); + assertEquals(testCn2, clientDns.get(1)); + assertEquals(2, standaloneConfig.getClientPasswords().size()); + } + + @Test + public void testClientPasswordMulti() throws CommandLineParseException { + String testCn = "OU=NIFI,CN=testuser"; + String testCn2 = "OU=NIFI,CN=testuser2"; + String testPass1 = "testPass1"; + String testPass2 = "testPass2"; + tlsToolkitStandaloneCommandLine.parse("-C", testCn, "-C", testCn2, "-B", testPass1, "-B", testPass2); + StandaloneConfig standaloneConfig = tlsToolkitStandaloneCommandLine.createConfig(); + List clientDns = standaloneConfig.getClientDns(); + assertEquals(2, clientDns.size()); + assertEquals(testCn, clientDns.get(0)); + assertEquals(testCn2, clientDns.get(1)); + List clientPasswords = standaloneConfig.getClientPasswords(); + assertEquals(2, clientPasswords.size()); + assertEquals(testPass1, clientPasswords.get(0)); + assertEquals(testPass2, clientPasswords.get(1)); + } + private Properties getProperties() throws IOException { - NiFiPropertiesWriter niFiPropertiesWriter = tlsToolkitStandaloneCommandLine.getNiFiPropertiesWriterFactory().create(); + NiFiPropertiesWriter niFiPropertiesWriter = tlsToolkitStandaloneCommandLine.createConfig().getNiFiPropertiesWriterFactory().create(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); niFiPropertiesWriter.writeNiFiProperties(byteArrayOutputStream); Properties properties = new Properties(); 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 6746c40a36..af81eaeff4 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 @@ -18,27 +18,36 @@ 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.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.manager.BaseTlsManager; +import org.apache.nifi.toolkit.tls.service.TlsCertificateAuthorityTest; import org.apache.nifi.toolkit.tls.util.TlsHelperTest; import org.apache.nifi.util.NiFiProperties; +import org.bouncycastle.jce.provider.BouncyCastleProvider; 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.Permission; +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; @@ -95,18 +104,18 @@ public class TlsToolkitStandaloneTest { @Test public void testBadParse() { - runAndAssertExitCode(ExitCode.ERROR_PARSING_COMMAND_LINE.ordinal(), "--unknownArgument"); + runAndAssertExitCode(ExitCode.ERROR_PARSING_COMMAND_LINE, "--unknownArgument"); } @Test public void testHelp() { - runAndAssertExitCode(ExitCode.HELP.ordinal(), "-h"); - runAndAssertExitCode(ExitCode.HELP.ordinal(), "--help"); + runAndAssertExitCode(ExitCode.HELP, "-h"); + runAndAssertExitCode(ExitCode.HELP, "--help"); } @Test public void testDirOutput() throws Exception { - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath()); + 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); @@ -116,7 +125,7 @@ public class TlsToolkitStandaloneTest { @Test public void testDifferentArg() throws Exception { - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-g"); + 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); @@ -126,7 +135,7 @@ public class TlsToolkitStandaloneTest { @Test public void testFileArg() throws Exception { - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-f", TEST_NIFI_PROPERTIES); + 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); @@ -134,23 +143,36 @@ public class TlsToolkitStandaloneTest { } @Test - public void testHostnamesArgument() throws Exception { + public void testHostnamesArgumentOverwrite() throws Exception { String nifi1 = "nifi1"; String nifi2 = "nifi2"; String nifi3 = "nifi3"; - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-n", nifi1 + "," + nifi2 + "," + 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(0, "-o", tempDir.getAbsolutePath(), "-K", 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); @@ -160,7 +182,7 @@ public class TlsToolkitStandaloneTest { @Test public void testKeyStorePasswordArg() throws Exception { String testKeyStore = "testKeyStore"; - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-S", 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); @@ -170,13 +192,38 @@ public class TlsToolkitStandaloneTest { @Test public void testTrustStorePasswordArg() throws Exception { String testTrustStore = "testTrustStore"; - runAndAssertExitCode(0, "-o", tempDir.getAbsolutePath(), "-P", 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 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")); @@ -227,15 +274,40 @@ public class TlsToolkitStandaloneTest { assertEquals(rootCert, certificateChain[1]); certificateChain[1].verify(rootCert.getPublicKey()); certificateChain[0].verify(rootCert.getPublicKey()); + TlsCertificateAuthorityTest.assertPrivateAndPublicKeyMatch(privateKeyEntry.getPrivateKey(), certificateChain[0].getPublicKey()); return nifiProperties; } - private void runAndAssertExitCode(int exitCode, String... args) { + 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 lines = IOUtils.readLines(fileReader); + assertEquals(1, lines.size()); + password = lines.get(0); + } + + KeyStore keyStore = KeyStore.getInstance(BaseTlsManager.PKCS_12, BouncyCastleProvider.PROVIDER_NAME); + 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) { try { TlsToolkitStandaloneCommandLine.main(args); fail("Expecting exit code: " + exitCode); } catch (ExitException e) { - assertEquals(exitCode, e.getExitCode()); + assertEquals(exitCode, ExitCode.values()[e.getExitCode()]); } } 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 a900a4f07a..66ec890968 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 @@ -25,23 +25,32 @@ import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.operator.OperatorCreationException; +import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileReader; import java.io.IOException; 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.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; +import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.SecureRandom; +import java.security.Provider; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; @@ -51,9 +60,17 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; +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 { + private static final boolean originalUnlimitedCrypto = TlsHelper.isUnlimitedStrengthCryptographyEnabled(); + private int days; private int keySize; @@ -62,12 +79,33 @@ public class TlsHelperTest { private String signingAlgorithm; - private String keyStoreType; - - private SecureRandom secureRandom; - private KeyPairGenerator keyPairGenerator; + private KeyStore keyStore; + + @Mock + KeyStoreSpi keyStoreSpi; + + @Mock + Provider keyStoreProvider; + + @Mock + OutputStreamFactory outputStreamFactory; + + private ByteArrayOutputStream tmpFileOutputStream; + + private File file; + + private static void setUnlimitedCrypto(boolean value) { + try { + Field isUnlimitedStrengthCryptographyEnabled = TlsHelper.class.getDeclaredField("isUnlimitedStrengthCryptographyEnabled"); + isUnlimitedStrengthCryptographyEnabled.setAccessible(true); + isUnlimitedStrengthCryptographyEnabled.set(null, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public static KeyPair loadKeyPair(Reader reader) throws IOException { try (PEMParser pemParser = new PEMParser(reader)) { Object object = pemParser.readObject(); @@ -98,15 +136,25 @@ public class TlsHelperTest { } @Before - public void setup() throws NoSuchAlgorithmException { + public void setup() throws Exception { days = 360; keySize = 2048; keyPairAlgorithm = "RSA"; signingAlgorithm = "SHA1WITHRSA"; - keyStoreType = KeyStore.getDefaultType(); - secureRandom = mock(SecureRandom.class); keyPairGenerator = KeyPairGenerator.getInstance(keyPairAlgorithm); keyPairGenerator.initialize(keySize); + Constructor keyStoreConstructor = KeyStore.class.getDeclaredConstructor(KeyStoreSpi.class, Provider.class, String.class); + keyStoreConstructor.setAccessible(true); + keyStore = keyStoreConstructor.newInstance(keyStoreSpi, keyStoreProvider, "faketype"); + keyStore.load(null, null); + file = File.createTempFile("keystore", "file"); + when(outputStreamFactory.create(file)).thenReturn(tmpFileOutputStream); + } + + @After + public void tearDown() { + setUnlimitedCrypto(originalUnlimitedCrypto); + file.delete(); } private Date inFuture(int days) { @@ -127,7 +175,7 @@ public class TlsHelperTest { assertTrue(notBefore.after(inFuture(-1))); assertTrue(notBefore.before(inFuture(1))); - assertEquals(dn, x509Certificate.getIssuerDN().getName()); + assertEquals(dn, x509Certificate.getIssuerX500Principal().getName()); assertEquals(signingAlgorithm, x509Certificate.getSigAlgName()); assertEquals(keyPairAlgorithm, x509Certificate.getPublicKey().getAlgorithm()); @@ -139,12 +187,12 @@ public class TlsHelperTest { X509Certificate issuer = loadCertificate(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("rootCert.crt"))); KeyPair issuerKeyPair = loadKeyPair(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("rootCert.key"))); - String dn = "CN=testIssued,O=testOrg"; + String dn = "CN=testIssued, O=testOrg"; KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize); X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, signingAlgorithm, days); - assertEquals(dn, x509Certificate.getSubjectDN().toString()); - assertEquals(issuer.getSubjectDN().toString(), x509Certificate.getIssuerDN().toString()); + assertEquals(dn, x509Certificate.getSubjectX500Principal().toString()); + assertEquals(issuer.getSubjectX500Principal().toString(), x509Certificate.getIssuerX500Principal().toString()); assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey()); Date notAfter = x509Certificate.getNotAfter(); @@ -160,4 +208,83 @@ public class TlsHelperTest { x509Certificate.verify(issuerKeyPair.getPublic()); } + + @Test + public void testWriteKeyStoreSuccess() throws IOException, GeneralSecurityException { + setUnlimitedCrypto(false); + String testPassword = "testPassword"; + assertEquals(testPassword, TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, false)); + verify(keyStoreSpi, times(1)).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + } + + @Test + public void testWriteKeyStoreFailure() throws IOException, GeneralSecurityException { + setUnlimitedCrypto(false); + String testPassword = "testPassword"; + IOException ioException = new IOException("Fail"); + doThrow(ioException).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + try { + TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, true); + fail("Expected " + ioException); + } catch (IOException e) { + assertEquals(ioException, e); + } + } + + @Test + public void testWriteKeyStoreTruncate() throws IOException, GeneralSecurityException { + setUnlimitedCrypto(false); + String testPassword = "testPassword"; + String truncatedPassword = testPassword.substring(0, 7); + IOException ioException = new IOException(TlsHelper.ILLEGAL_KEY_SIZE); + doThrow(ioException).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + assertEquals(truncatedPassword, TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, true)); + verify(keyStoreSpi, times(1)).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + verify(keyStoreSpi, times(1)).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(truncatedPassword.toCharArray())); + } + + @Test + public void testWriteKeyStoreUnlimitedWontTruncate() throws GeneralSecurityException, IOException { + setUnlimitedCrypto(true); + String testPassword = "testPassword"; + IOException ioException = new IOException(TlsHelper.ILLEGAL_KEY_SIZE); + doThrow(ioException).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + try { + TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, true); + fail("Expected " + ioException); + } catch (IOException e) { + assertEquals(ioException, e); + } + } + + @Test + public void testWriteKeyStoreNoTruncate() throws IOException, GeneralSecurityException { + setUnlimitedCrypto(false); + String testPassword = "testPassword"; + IOException ioException = new IOException(TlsHelper.ILLEGAL_KEY_SIZE); + doThrow(ioException).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + try { + TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, false); + fail("Expected " + GeneralSecurityException.class); + } catch (GeneralSecurityException e) { + assertTrue("Expected exception to contain " + TlsHelper.JCE_URL, e.getMessage().contains(TlsHelper.JCE_URL)); + } + } + + @Test + public void testWriteKeyStoreTruncateFailure() throws IOException, GeneralSecurityException { + setUnlimitedCrypto(false); + String testPassword = "testPassword"; + String truncatedPassword = testPassword.substring(0, 7); + IOException ioException = new IOException(TlsHelper.ILLEGAL_KEY_SIZE); + IOException ioException2 = new IOException(TlsHelper.ILLEGAL_KEY_SIZE); + doThrow(ioException).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(testPassword.toCharArray())); + doThrow(ioException2).when(keyStoreSpi).engineStore(eq(tmpFileOutputStream), AdditionalMatchers.aryEq(truncatedPassword.toCharArray())); + try { + TlsHelper.writeKeyStore(keyStore, outputStreamFactory, file, testPassword, true); + fail("Expected " + ioException2); + } catch (IOException e) { + assertEquals(ioException2, e); + } + } } diff --git a/nifi-toolkit/nifi-toolkit-tls/src/test/resources/log4j.properties b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/log4j.properties new file mode 100644 index 0000000000..fc2aaf12c4 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-tls/src/test/resources/log4j.properties @@ -0,0 +1,22 @@ +# +# 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. +# + +log4j.rootLogger=INFO,console + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n \ No newline at end of file