NIFI-2526 - DN order, multiple standalone runs, client certificates

- Logic for sorting DN, reversing X500Names before using them to generate certificate
- Logging reordered dn
- Accounting for limited crypto pkcs12, allowing password specification for client certificates
- Updating tests to work with or without jce unlimited
- Loading keystore for test in try-with

This closes #824.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Bryan Rosander 2016-08-09 11:11:21 -04:00 committed by Bryan Bende
parent fd0dd51ff5
commit fa5da543e6
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
19 changed files with 881 additions and 183 deletions

View File

@ -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<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
private static Map<ASN1ObjectIdentifier, Integer> createDnOrderMap() {
Map<ASN1ObjectIdentifier, Integer> 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<RDN>() {
@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<RDN> 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));

View File

@ -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"));
}
}

View File

@ -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,

View File

@ -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<String> hostnames;
private List<String> keyStorePasswords;
private List<String> keyPasswords;
private List<String> trustStorePasswords;
private List<String> clientDns;
private List<String> clientPasswords;
private boolean clientPasswordsGenerated;
private int httpsPort;
private boolean overwrite;
public List<String> getClientDns() {
return clientDns;
}
public void setClientDns(List<String> 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<String> getHostnames() {
return hostnames;
}
public void setHostnames(List<String> hostnames) {
this.hostnames = hostnames;
}
public List<String> getKeyStorePasswords() {
return keyStorePasswords;
}
public void setKeyStorePasswords(List<String> keyStorePasswords) {
this.keyStorePasswords = keyStorePasswords;
}
public List<String> getKeyPasswords() {
return keyPasswords;
}
public void setKeyPasswords(List<String> keyPasswords) {
this.keyPasswords = keyPasswords;
}
public List<String> getTrustStorePasswords() {
return trustStorePasswords;
}
public void setTrustStorePasswords(List<String> trustStorePasswords) {
this.trustStorePasswords = trustStorePasswords;
}
public List<String> getClientPasswords() {
return clientPasswords;
}
public void setClientPasswords(List<String> 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;
}
}

View File

@ -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() {

View File

@ -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<ConfigurationWriter<TlsConfig>> 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<TlsConfig> configurationWriter : configurationWriters) {
configurationWriter.write(tlsConfig);

View File

@ -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");

View File

@ -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<TlsClientConfig> configurationWriter : configurationWriters) {
configurationWriter.write(tlsClientConfig);

View File

@ -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<HttpClientBuilder> 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) {

View File

@ -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);

View File

@ -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<String> hostnames, List<String> keyStorePasswords,
List<String> keyPasswords, List<String> 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<String> 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<String> keyStorePasswords = standaloneConfig.getKeyStorePasswords();
List<String> keyPasswords = standaloneConfig.getKeyPasswords();
List<String> 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<String> clientDns = standaloneConfig.getClientDns();
if (standaloneConfig.getClientDns().isEmpty() && logger.isInfoEnabled()) {
logger.info("No " + TlsToolkitStandaloneCommandLine.CLIENT_CERT_DN_ARG + " specified, not generating any client certificates.");
}
List<String> 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(' ', '_');
}
}

View File

@ -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<String> keyStorePasswords;
private List<String> keyPasswords;
private List<String> trustStorePasswords;
private List<String> clientDns;
private List<String> 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<String> getPasswords(String arg, CommandLine commandLine, int numHosts) throws CommandLineParseException {
private List<String> 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<String> getKeyPasswords(CommandLine commandLine, List<String> 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<String> 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<String> getKeyStorePasswords() {
return keyStorePasswords;
}
public List<String> getKeyPasswords() {
return keyPasswords;
}
public List<String> 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;
}
}

View File

@ -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> T parsePem(Class<T> 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())));
}

View File

@ -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);

View File

@ -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());

View File

@ -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<String> hostnames = tlsToolkitStandaloneCommandLine.getHostnames();
List<String> 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<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
List<String> keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size());
tlsToolkitStandaloneCommandLine.parse("-g", "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords();
List<String> 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<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
List<String> keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
assertEquals(1, tlsToolkitStandaloneCommandLine.getHostnames().size());
tlsToolkitStandaloneCommandLine.parse("-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.createConfig().getKeyStorePasswords();
List<String> 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<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
tlsToolkitStandaloneCommandLine.parse("-S", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> 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<String> keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
assertNotEquals(tlsToolkitStandaloneCommandLine.getKeyStorePasswords(), keyPasswords);
tlsToolkitStandaloneCommandLine.parse("-K", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> 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<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
tlsToolkitStandaloneCommandLine.parse("-S", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> 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<String> keyStorePasswords = tlsToolkitStandaloneCommandLine.getKeyStorePasswords();
List<String> 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<String> keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
tlsToolkitStandaloneCommandLine.parse("-K", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> 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<String> keyPasswords = tlsToolkitStandaloneCommandLine.getKeyPasswords();
List<String> 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<String> trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords();
tlsToolkitStandaloneCommandLine.parse("-P", testPassword, "-n", TlsConfig.DEFAULT_HOSTNAME);
List<String> 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<String> trustStorePasswords = tlsToolkitStandaloneCommandLine.getTrustStorePasswords();
List<String> 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<String> 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<String> 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<String> clientDns = standaloneConfig.getClientDns();
assertEquals(2, clientDns.size());
assertEquals(testCn, clientDns.get(0));
assertEquals(testCn2, clientDns.get(1));
List<String> 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();

View File

@ -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<String> 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()]);
}
}

View File

@ -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<KeyStore> 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);
}
}
}

View File

@ -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