/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.toolkit.tls.standalone;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.security.util.KeyStoreUtils;
import org.apache.nifi.toolkit.tls.configuration.InstanceDefinition;
import org.apache.nifi.toolkit.tls.configuration.StandaloneConfig;
import org.apache.nifi.toolkit.tls.configuration.TlsClientConfig;
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.asn1.x509.Extensions;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger;
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;
public class TlsToolkitStandalone {
public static final String NIFI_KEY = "nifi-key";
public static final String NIFI_CERT = "nifi-cert";
public static final String NIFI_PROPERTIES = "nifi.properties";
private final Logger logger = LoggerFactory.getLogger(TlsToolkitStandalone.class);
private final OutputStreamFactory outputStreamFactory;
public TlsToolkitStandalone() {
this(FileOutputStream::new);
}
public TlsToolkitStandalone(OutputStreamFactory outputStreamFactory) {
this.outputStreamFactory = outputStreamFactory;
}
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.");
}
if (!baseDir.isDirectory()) {
throw new IOException("Expected directory to output to");
}
String signingAlgorithm = standaloneConfig.getSigningAlgorithm();
int days = standaloneConfig.getDays();
String keyPairAlgorithm = standaloneConfig.getKeyPairAlgorithm();
int keySize = standaloneConfig.getKeySize();
File nifiCert = new File(baseDir, NIFI_CERT + ".pem");
File nifiKey = new File(baseDir, NIFI_KEY + ".key");
X509Certificate certificate;
KeyPair caKeyPair;
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);
}
}
NiFiPropertiesWriterFactory niFiPropertiesWriterFactory = standaloneConfig.getNiFiPropertiesWriterFactory();
boolean overwrite = standaloneConfig.isOverwrite();
List<InstanceDefinition> instanceDefinitions = standaloneConfig.getInstanceDefinitions();
if (instanceDefinitions.isEmpty() && logger.isInfoEnabled()) {
logger.info("No " + TlsToolkitStandaloneCommandLine.HOSTNAMES_ARG + " specified, not generating any host certificates or configuration.");
}
for (InstanceDefinition instanceDefinition : instanceDefinitions) {
String hostname = instanceDefinition.getHostname();
File hostDir;
int hostIdentifierNumber = instanceDefinition.getInstanceIdentifier().getNumber();
if (hostIdentifierNumber == 1) {
hostDir = new File(baseDir, hostname);
} else {
hostDir = new File(baseDir, hostname + "_" + hostIdentifierNumber);
}
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.setKeyStore(keystore.getAbsolutePath());
tlsClientConfig.setKeyStorePassword(instanceDefinition.getKeyStorePassword());
tlsClientConfig.setKeyPassword(instanceDefinition.getKeyPassword());
tlsClientConfig.setTrustStore(truststore.getAbsolutePath());
tlsClientConfig.setTrustStorePassword(instanceDefinition.getTrustStorePassword());
TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig);
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
Extensions sanDnsExtensions = StringUtils.isBlank(tlsClientConfig.getDomainAlternativeNames())
? null : TlsHelper.createDomainAlternativeNamesExtensions(tlsClientConfig.getDomainAlternativeNames());
tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, CertificateUtils.generateIssuedCertificate(tlsClientConfig.calcDefaultDn(hostname),
keyPair.getPublic(), sanDnsExtensions, certificate, caKeyPair, signingAlgorithm, days), certificate);
tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"),
hostname, instanceDefinition.getNumber()));
tlsClientManager.write(outputStreamFactory);
if (logger.isInfoEnabled()) {
logger.info("Successfully generated TLS configuration for " + hostname + " " + hostIdentifierNumber + " in " + hostDir);
}
}
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(), null, certificate, caKeyPair, signingAlgorithm, days);
KeyStore keyStore = KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString());
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("tls-toolkit standalone completed successfully");
}
}
protected static String getClientDnFile(String clientDn) {
return clientDn.replace(',', '_').replace(' ', '_');
}
}