/* * 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.junit.BeforeClass; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; 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 static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class KeyStoreUtilsTest { public static final String SIGNING_ALGORITHM = "SHA256withRSA"; public static final int DURATION_DAYS = 365; public static final char[] BAD_TEST_PASSWORD_DONT_USE_THIS = "changek".toCharArray(); public static final char[] BAD_KEY_STORE_TEST_PASSWORD_DONT_USE_THIS = "changes".toCharArray(); public static final String ALIAS = "alias"; private static KeyPair caCertKeyPair; private static X509Certificate caCertificate; private static KeyPair issuedCertificateKeyPair; private static X509Certificate issuedCertificate; @BeforeClass public static void generateKeysAndCertificates() throws NoSuchAlgorithmException, CertificateException { caCertKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); issuedCertificateKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); caCertificate = CertificateUtils.generateSelfSignedX509Certificate(caCertKeyPair, "CN=testca,O=Apache,OU=NiFi", SIGNING_ALGORITHM, DURATION_DAYS); issuedCertificate = CertificateUtils.generateIssuedCertificate("CN=testcert,O=Apache,OU=NiFi", issuedCertificateKeyPair.getPublic(), caCertificate, caCertKeyPair, SIGNING_ALGORITHM, DURATION_DAYS); } @Test public void testJksKeyStoreRoundTrip() throws GeneralSecurityException, IOException { testKeyStoreRoundTrip(() -> KeyStoreUtils.getKeyStore(KeystoreType.JKS.toString().toLowerCase())); } @Test public void testPkcs12KeyStoreBcRoundTrip() throws GeneralSecurityException, IOException { testKeyStoreRoundTrip(() -> KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString().toLowerCase())); } @Test public void testPkcs12KeyStoreRoundTripBcReload() throws GeneralSecurityException, IOException { // Pkcs12 Bouncy Castle needs same key and keystore password to interoperate with Java provider testKeyStoreRoundTrip(() -> KeyStore.getInstance(KeystoreType.PKCS12.toString().toLowerCase()), () -> KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString().toLowerCase()), BAD_KEY_STORE_TEST_PASSWORD_DONT_USE_THIS); } @Test public void testJksTrustStoreRoundTrip() throws GeneralSecurityException, IOException { testTrustStoreRoundTrip(() -> KeyStoreUtils.getTrustStore(KeystoreType.JKS.toString().toLowerCase())); } @Test public void testPkcs12TrustStoreBcRoundTrip() throws GeneralSecurityException, IOException { testTrustStoreRoundTrip(() -> KeyStoreUtils.getTrustStore(KeystoreType.PKCS12.toString().toLowerCase())); } @Test public void testPkcs12TrustStoreRoundTripBcReload() throws GeneralSecurityException, IOException { testTrustStoreRoundTrip(() -> KeyStore.getInstance(KeystoreType.PKCS12.toString().toLowerCase()), () -> KeyStoreUtils.getTrustStore(KeystoreType.PKCS12.toString().toLowerCase())); } private void testTrustStoreRoundTrip(KeyStoreSupplier keyStoreSupplier) throws GeneralSecurityException, IOException { testTrustStoreRoundTrip(keyStoreSupplier, keyStoreSupplier); } private void testTrustStoreRoundTrip(KeyStoreSupplier initialKeyStoreSupplier, KeyStoreSupplier reloadKeyStoreSupplier) throws GeneralSecurityException, IOException { KeyStore trustStore = initialKeyStoreSupplier.get(); trustStore.load(null, null); trustStore.setCertificateEntry(ALIAS, caCertificate); KeyStore roundTrip = roundTrip(trustStore, reloadKeyStoreSupplier); assertEquals(caCertificate, roundTrip.getCertificate(ALIAS)); } private void testKeyStoreRoundTrip(KeyStoreSupplier keyStoreSupplier) throws GeneralSecurityException, IOException { testKeyStoreRoundTrip(keyStoreSupplier, keyStoreSupplier, BAD_TEST_PASSWORD_DONT_USE_THIS); } private void testKeyStoreRoundTrip(KeyStoreSupplier initialKeyStoreSupplier, KeyStoreSupplier reloadKeyStoreSupplier, char[] keyPassword) throws GeneralSecurityException, IOException { KeyStore keyStore = initialKeyStoreSupplier.get(); keyStore.load(null, null); keyStore.setKeyEntry(ALIAS, issuedCertificateKeyPair.getPrivate(), keyPassword, new Certificate[]{issuedCertificate, caCertificate}); KeyStore roundTrip = roundTrip(keyStore, reloadKeyStoreSupplier); KeyStore.Entry entry = roundTrip.getEntry(ALIAS, new KeyStore.PasswordProtection(keyPassword)); assertTrue(entry instanceof KeyStore.PrivateKeyEntry); KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry; Certificate[] certificateChain = privateKeyEntry.getCertificateChain(); assertArrayEquals(new Certificate[]{issuedCertificate, caCertificate}, certificateChain); assertEquals(issuedCertificateKeyPair.getPrivate(), privateKeyEntry.getPrivateKey()); assertEquals(issuedCertificateKeyPair.getPublic(), certificateChain[0].getPublicKey()); } private KeyStore roundTrip(KeyStore keyStore, KeyStoreSupplier keyStoreSupplier) throws GeneralSecurityException, IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); keyStore.store(byteArrayOutputStream, BAD_KEY_STORE_TEST_PASSWORD_DONT_USE_THIS); KeyStore result = keyStoreSupplier.get(); result.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), BAD_KEY_STORE_TEST_PASSWORD_DONT_USE_THIS); return result; } private interface KeyStoreSupplier { KeyStore get() throws GeneralSecurityException; } }