blob: f41d5970d6f4d30759c573f758b772dea22186eb [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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
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.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class KeyStoreUtilsTest {
private static final String SIGNING_ALGORITHM = "SHA256withRSA";
private static final int DURATION_DAYS = 365;
private static final char[] KEY_PASSWORD = UUID.randomUUID().toString().toCharArray();
private static final char[] STORE_PASSWORD = UUID.randomUUID().toString().toCharArray();
private static final String ALIAS = "alias";
private static final String KEY_ALGORITHM = "RSA";
private static final String HOSTNAME = "localhost";
private static final String SUBJECT_DN = String.format("CN=%s", HOSTNAME);
private static final String SECRET_KEY_ALGORITHM = "AES";
private static final String KEY_PROTECTION_ALGORITHM = "PBEWithHmacSHA256AndAES_256";
private static final String HYPHEN_SEPARATOR = "-";
private static KeyPair keyPair;
private static X509Certificate certificate;
private static SecretKey secretKey;
@BeforeClass
public static void generateKeysAndCertificates() throws NoSuchAlgorithmException, CertificateException {
keyPair = KeyPairGenerator.getInstance(KEY_ALGORITHM).generateKeyPair();
certificate = CertificateUtils.generateSelfSignedX509Certificate(keyPair, SUBJECT_DN, SIGNING_ALGORITHM, DURATION_DAYS);
final byte[] encodedKey = StringUtils.remove(UUID.randomUUID().toString(), HYPHEN_SEPARATOR).getBytes(StandardCharsets.UTF_8);
secretKey = new SecretKeySpec(encodedKey, SECRET_KEY_ALGORITHM);
}
@Test
public void testCreateTlsConfigAndNewKeystoreTruststore() throws GeneralSecurityException, IOException {
final File keyStoreFile = File.createTempFile(KeyStoreUtilsTest.class.getSimpleName(), ".keystore.p12");
keyStoreFile.deleteOnExit();
final File trustStoreFile = File.createTempFile(KeyStoreUtilsTest.class.getSimpleName(), ".truststore.p12");
trustStoreFile.deleteOnExit();
final String password = UUID.randomUUID().toString();
final String keyStoreType = KeystoreType.PKCS12.getType();
final TlsConfiguration requested = new StandardTlsConfiguration(
keyStoreFile.getAbsolutePath(),
password,
password,
keyStoreType,
trustStoreFile.getAbsolutePath(),
password,
keyStoreType,
TlsConfiguration.TLS_PROTOCOL
);
final TlsConfiguration configuration = KeyStoreUtils.createTlsConfigAndNewKeystoreTruststore(requested, 1, new String[] { HOSTNAME });
final File keystoreFile = new File(configuration.getKeystorePath());
assertTrue("Keystore File not found", keystoreFile.exists());
keystoreFile.deleteOnExit();
final File truststoreFile = new File(configuration.getTruststorePath());
assertTrue("Truststore File not found", truststoreFile.exists());
truststoreFile.deleteOnExit();
assertEquals("Keystore Type not matched", KeystoreType.PKCS12, configuration.getKeystoreType());
assertEquals("Truststore Type not matched", KeystoreType.PKCS12, configuration.getTruststoreType());
assertTrue("Keystore not valid", KeyStoreUtils.isStoreValid(keystoreFile.toURI().toURL(), configuration.getKeystoreType(), configuration.getKeystorePassword().toCharArray()));
assertTrue("Truststore not valid", KeyStoreUtils.isStoreValid(truststoreFile.toURI().toURL(), configuration.getTruststoreType(), configuration.getTruststorePassword().toCharArray()));
}
@Test
public void testKeystoreTypesPrivateKeyEntry() throws GeneralSecurityException, IOException {
for (final KeystoreType keystoreType : KeystoreType.values()) {
final KeyStore sourceKeyStore = KeyStoreUtils.getKeyStore(keystoreType.getType());
final KeyStore destinationKeyStore = KeyStoreUtils.getKeyStore(keystoreType.getType());
assertKeyEntryStoredLoaded(sourceKeyStore, destinationKeyStore);
}
}
@Test
public void testKeystoreTypesCertificateEntry() throws GeneralSecurityException, IOException {
for (final KeystoreType keystoreType : KeystoreType.values()) {
final KeyStore sourceKeyStore = KeyStoreUtils.getKeyStore(keystoreType.getType());
final KeyStore destinationKeyStore = KeyStoreUtils.getKeyStore(keystoreType.getType());
assertCertificateEntryStoredLoaded(sourceKeyStore, destinationKeyStore);
}
}
@Test
public void testKeystoreTypesSecretKeyEntry() throws GeneralSecurityException, IOException {
for (final KeystoreType keystoreType : KeystoreType.values()) {
if (KeyStoreUtils.isSecretKeyEntrySupported(keystoreType)) {
final KeyStore sourceKeyStore = KeyStoreUtils.getSecretKeyStore(keystoreType.getType());
final KeyStore destinationKeyStore = KeyStoreUtils.getSecretKeyStore(keystoreType.getType());
try {
assertSecretKeyStoredLoaded(sourceKeyStore, destinationKeyStore);
} catch (final GeneralSecurityException e) {
throw new GeneralSecurityException(String.format("Keystore Type [%s] Failed", keystoreType), e);
}
}
}
}
private void assertCertificateEntryStoredLoaded(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
sourceKeyStore.load(null, null);
sourceKeyStore.setCertificateEntry(ALIAS, certificate);
final KeyStore copiedKeyStore = copyKeyStore(sourceKeyStore, destinationKeyStore);
assertEquals(String.format("[%s] Certificate not matched", sourceKeyStore.getType()), certificate, copiedKeyStore.getCertificate(ALIAS));
}
private void assertKeyEntryStoredLoaded(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
sourceKeyStore.load(null, null);
final Certificate[] certificateChain = new Certificate[]{certificate};
sourceKeyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), KEY_PASSWORD, certificateChain);
final KeyStore copiedKeyStore = copyKeyStore(sourceKeyStore, destinationKeyStore);
final KeyStore.Entry entry = copiedKeyStore.getEntry(ALIAS, new KeyStore.PasswordProtection(KEY_PASSWORD));
assertTrue(String.format("[%s] Private Key entry not found", sourceKeyStore.getType()), entry instanceof KeyStore.PrivateKeyEntry);
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;
final Certificate[] entryCertificateChain = privateKeyEntry.getCertificateChain();
assertArrayEquals(String.format("[%s] Certificate Chain not matched", sourceKeyStore.getType()), certificateChain, entryCertificateChain);
assertEquals(String.format("[%s] Private Key not matched", sourceKeyStore.getType()), keyPair.getPrivate(), privateKeyEntry.getPrivateKey());
assertEquals(String.format("[%s] Public Key not matched", sourceKeyStore.getType()), keyPair.getPublic(), entryCertificateChain[0].getPublicKey());
}
private void assertSecretKeyStoredLoaded(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
sourceKeyStore.load(null, null);
final KeyStore.ProtectionParameter protection = getProtectionParameter(sourceKeyStore.getType());
sourceKeyStore.setEntry(ALIAS, new KeyStore.SecretKeyEntry(secretKey), protection);
final KeyStore copiedKeyStore = copyKeyStore(sourceKeyStore, destinationKeyStore);
final KeyStore.Entry entry = copiedKeyStore.getEntry(ALIAS, protection);
assertTrue(String.format("[%s] Secret Key entry not found", sourceKeyStore.getType()), entry instanceof KeyStore.SecretKeyEntry);
}
private KeyStore copyKeyStore(final KeyStore sourceKeyStore, final KeyStore destinationKeyStore) throws GeneralSecurityException, IOException {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
sourceKeyStore.store(byteArrayOutputStream, STORE_PASSWORD);
destinationKeyStore.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), STORE_PASSWORD);
return destinationKeyStore;
}
private KeyStore.ProtectionParameter getProtectionParameter(final String keyStoreType) {
if (KeystoreType.PKCS12.getType().equals(keyStoreType)) {
// Select Key Protection Algorithm for PKCS12 to avoid unsupported algorithm on Java 1.8.0.292
return new KeyStore.PasswordProtection(KEY_PASSWORD, KEY_PROTECTION_ALGORITHM, null);
} else {
return new KeyStore.PasswordProtection(KEY_PASSWORD);
}
}
}