blob: 8914a5f4a2ea6ad6a837a4deaa7dbb1515c5aeb7 [file] [log] [blame]
/*
* 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.security.util;
import org.apache.nifi.security.cert.builder.StandardCertificateBuilder;
import org.apache.nifi.security.configuration.KeyStoreConfiguration;
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* KeyStore Factory for creating temporary files primarily used for testing
*/
public class TemporaryKeyStoreBuilder {
private static final String KEY_PAIR_ALGORITHM = "RSA";
private static final int KEY_SIZE = 2048;
private static final int RANDOM_BYTES_LENGTH = 16;
private static final Base64.Encoder ENCODER = Base64.getEncoder().withoutPadding();
private static final String SIGNING_ALGORITHM = "SHA256withRSA";
private static final String DISTINGUISHED_NAME_FORMAT = "CN=%s";
private static final int CERTIFICATE_VALID_DAYS = 1;
private static final KeystoreType KEYSTORE_TYPE = KeystoreType.PKCS12;
private static final String KEY_STORE_EXTENSION = ".p12";
private static final String KEY_STORE_PREFIX = "TemporaryKeyStore-";
private static final String DEFAULT_HOSTNAME = "localhost";
private String hostname = DEFAULT_HOSTNAME;
private String trustStorePassword = generateSecureRandomPassword();
private String trustStoreType = KEYSTORE_TYPE.getType();
/**
* Set Hostname used for Certificate Common Name and DNS Subject Alternative Names
*
* @param hostname Hostname is required
* @return Builder
*/
public TemporaryKeyStoreBuilder hostname(final String hostname) {
this.hostname = Objects.requireNonNull(hostname, "Hostname required");
return this;
}
/**
* Set Trust Store Password used for protected generated Trust Store file
*
* @param trustStorePassword Trust Store Password is required
* @return Builder
*/
public TemporaryKeyStoreBuilder trustStorePassword(final String trustStorePassword) {
this.trustStorePassword = Objects.requireNonNull(trustStorePassword, "TrustStore Password required");
return this;
}
/**
* Set Trust Store Type used for storing Trust Store files
*
* @param trustStoreType Trust Store type must be a supported value for KeyStore.getInstance()
* @return Builder
*/
public TemporaryKeyStoreBuilder trustStoreType(final String trustStoreType) {
this.trustStoreType = Objects.requireNonNull(trustStoreType, "TrustStore Type required");
return this;
}
/**
* Build Temporary KeyStore and TrustStore with configured values and set files with File.deleteOnExit()
*
* @return TLS Configuration with KeyStore and TrustStore properties
*/
public TlsConfiguration build() {
final KeyPair keyPair = generateKeyPair();
final X509Certificate certificate = generateCertificate(hostname, keyPair);
final KeyStoreConfiguration keyStoreConfiguration = setKeyStore(keyPair.getPrivate(), certificate);
final KeyStoreConfiguration trustStoreConfiguration = setTrustStore(certificate);
return new StandardTlsConfiguration(
keyStoreConfiguration.getLocation(),
keyStoreConfiguration.getPassword(),
keyStoreConfiguration.getPassword(),
keyStoreConfiguration.getKeyStoreType(),
trustStoreConfiguration.getLocation(),
trustStoreConfiguration.getPassword(),
trustStoreConfiguration.getKeyStoreType(),
TlsPlatform.getLatestProtocol()
);
}
private KeyStoreConfiguration setKeyStore(final PrivateKey privateKey, final X509Certificate certificate) {
final KeyStore keyStore = getNewKeyStore(KEYSTORE_TYPE.getType());
final String password = generateSecureRandomPassword();
final String alias = UUID.randomUUID().toString();
try {
keyStore.setKeyEntry(alias, privateKey, password.toCharArray(), new Certificate[]{certificate});
} catch (final KeyStoreException e) {
throw new RuntimeException("Set Key Entry Failed", e);
}
final File keyStoreFile = storeKeyStore(keyStore, password.toCharArray());
return new KeyStoreConfiguration(keyStoreFile.getAbsolutePath(), password, KEYSTORE_TYPE.getType());
}
private KeyStoreConfiguration setTrustStore(final X509Certificate certificate) {
final KeyStore keyStore = getNewKeyStore(trustStoreType);
final String alias = UUID.randomUUID().toString();
try {
keyStore.setCertificateEntry(alias, certificate);
} catch (final KeyStoreException e) {
throw new RuntimeException("Set Certificate Entry Failed", e);
}
final File trustStoreFile = storeKeyStore(keyStore, trustStorePassword.toCharArray());
return new KeyStoreConfiguration(trustStoreFile.getAbsolutePath(), trustStorePassword, trustStoreType);
}
private File storeKeyStore(final KeyStore keyStore, final char[] password) {
try {
final File keyStoreFile = File.createTempFile(KEY_STORE_PREFIX, KEY_STORE_EXTENSION);
keyStoreFile.deleteOnExit();
try (final OutputStream outputStream = new FileOutputStream(keyStoreFile)) {
keyStore.store(outputStream, password);
}
return keyStoreFile;
} catch (final IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
throw new RuntimeException("Store KeyStore Failed", e);
}
}
private KeyStore getNewKeyStore(final String newKeyStoreType) {
try {
final KeyStore keyStore = KeyStoreUtils.getKeyStore(newKeyStoreType);
keyStore.load(null);
return keyStore;
} catch (final KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
throw new RuntimeException(String.format("Create KeyStore [%s] Failed", KEYSTORE_TYPE), e);
}
}
private X509Certificate generateCertificate(final String hostname, final KeyPair keyPair) {
final X500Principal distinguishedName = new X500Principal(String.format(DISTINGUISHED_NAME_FORMAT, hostname));
final List<String> dnsNames = Collections.singletonList(hostname);
return new StandardCertificateBuilder(keyPair, distinguishedName, Duration.ofDays(CERTIFICATE_VALID_DAYS))
.setDnsSubjectAlternativeNames(dnsNames)
.build();
}
private KeyPair generateKeyPair() {
try {
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
} catch (final NoSuchAlgorithmException e) {
throw new IllegalArgumentException(String.format("[%s] Algorithm not found", KEY_PAIR_ALGORITHM), e);
}
}
private String generateSecureRandomPassword() {
final SecureRandom secureRandom = new SecureRandom();
final byte[] randomBytes = new byte[RANDOM_BYTES_LENGTH];
secureRandom.nextBytes(randomBytes);
return ENCODER.encodeToString(randomBytes);
}
}