diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index 96310ba4ac5..67c24e7775f 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -985,9 +985,11 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp * *ssl.keyStore.type* and *ssl.quorum.keyStore.type* : (Java system properties: **zookeeper.ssl.keyStore.type** and **zookeeper.ssl.quorum.keyStore.type**) **New in 3.5.5:** - Specifies the file format of client and quorum keystores. Values: JKS, PEM, PKCS12 or null (detect by filename). - Default: null - + Specifies the file format of client and quorum keystores. Values: JKS, PEM, PKCS12 or null (detect by filename). + Default: null. + **New in 3.5.10, 3.6.3, 3.7.0:** + The format BCFKS was added. + * *ssl.trustStore.location* and *ssl.trustStore.password* and *ssl.quorum.trustStore.location* and *ssl.quorum.trustStore.password* : (Java system properties: **zookeeper.ssl.trustStore.location** and **zookeeper.ssl.trustStore.password** and **zookeeper.ssl.quorum.trustStore.location** and **zookeeper.ssl.quorum.trustStore.password**) **New in 3.5.5:** @@ -998,8 +1000,10 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp * *ssl.trustStore.type* and *ssl.quorum.trustStore.type* : (Java system properties: **zookeeper.ssl.trustStore.type** and **zookeeper.ssl.quorum.trustStore.type**) **New in 3.5.5:** - Specifies the file format of client and quorum trustStores. Values: JKS, PEM, PKCS12 or null (detect by filename). - Default: null + Specifies the file format of client and quorum trustStores. Values: JKS, PEM, PKCS12 or null (detect by filename). + Default: null. + **New in 3.5.10, 3.6.3, 3.7.0:** + The format BCFKS was added. * *ssl.protocol* and *ssl.quorum.protocol* : (Java system properties: **zookeeper.ssl.protocol** and **zookeeper.ssl.quorum.protocol**) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/BCFKSFileLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/BCFKSFileLoader.java new file mode 100644 index 00000000000..d59f40a2056 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/BCFKSFileLoader.java @@ -0,0 +1,39 @@ +/** + * 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.zookeeper.common; + + +/** + * Implementation of {@link FileKeyStoreLoader} that loads from BCKFS files. + */ +class BCFKSFileLoader extends StandardTypeFileKeyStoreLoader { + private BCFKSFileLoader(String keyStorePath, + String trustStorePath, + String keyStorePassword, + String trustStorePassword) { + super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.BCFKS); + } + + static class Builder extends FileKeyStoreLoader.Builder { + @Override + BCFKSFileLoader build() { + return new BCFKSFileLoader(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + } + } +} diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java index 0a03d627c6a..1de416198cf 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java @@ -38,6 +38,8 @@ public class FileKeyStoreLoaderBuilderProvider { return new PEMFileLoader.Builder(); case PKCS12: return new PKCS12FileLoader.Builder(); + case BCFKS: + return new BCFKSFileLoader.Builder(); default: throw new AssertionError( "Unexpected StoreFileType: " + type.name()); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java index cf11736dd5e..3cb13b77d61 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java @@ -18,8 +18,6 @@ package org.apache.zookeeper.common; -import java.security.KeyStore; -import java.security.KeyStoreException; /** * Implementation of {@link FileKeyStoreLoader} that loads from JKS files. @@ -29,12 +27,7 @@ private JKSFileLoader(String keyStorePath, String trustStorePath, String keyStorePassword, String trustStorePassword) { - super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); - } - - @Override - protected KeyStore keyStoreInstance() throws KeyStoreException { - return KeyStore.getInstance("JKS"); + super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.JKS); } static class Builder extends FileKeyStoreLoader.Builder { diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java index cc490131dd4..2f3a707bf01 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java @@ -20,10 +20,13 @@ /** * This enum represents the file type of a KeyStore or TrustStore. - * Currently, JKS (Java keystore), PEM, and PKCS12 types are supported. + * Currently, JKS (Java keystore), PEM, PKCS12, and BCFKS types are supported. */ public enum KeyStoreFileType { - JKS(".jks"), PEM(".pem"), PKCS12(".p12"); + JKS(".jks"), + PEM(".pem"), + PKCS12(".p12"), + BCFKS(".bcfks"); private final String defaultFileExtension; @@ -53,7 +56,7 @@ public String getDefaultFileExtension() { * @return the KeyStoreFileType, or null if * propertyValue is null or empty. * @throws IllegalArgumentException if propertyValue is not - * one of "JKS", "PEM", "PKCS12", or empty/null. + * one of "JKS", "PEM", "BCFKS", "PKCS12", or empty/null. */ public static KeyStoreFileType fromPropertyValue(String propertyValue) { if (propertyValue == null || propertyValue.length() == 0) { @@ -67,11 +70,12 @@ public static KeyStoreFileType fromPropertyValue(String propertyValue) { * If the file name ends with ".jks", returns StoreFileType.JKS. * If the file name ends with ".pem", returns StoreFileType.PEM. * If the file name ends with ".p12", returns StoreFileType.PKCS12. + * If the file name ends with ".bckfs", returns StoreFileType.BCKFS. * Otherwise, throws an IllegalArgumentException. * @param filename the filename of the key store or trust store file. * @return a KeyStoreFileType. * @throws IllegalArgumentException if the filename does not end with - * ".jks", ".pem", or "p12". + * ".jks", ".pem", "p12" or "bcfks". */ public static KeyStoreFileType fromFilename(String filename) { int i = filename.lastIndexOf('.'); @@ -99,7 +103,7 @@ public static KeyStoreFileType fromFilename(String filename) { * propertyValue is null or empty. * @return a KeyStoreFileType. * @throws IllegalArgumentException if propertyValue is not - * one of "JKS", "PEM", "PKCS12", or empty/null. + * one of "JKS", "PEM", "PKCS12", "BCFKS", or empty/null. * @throws IllegalArgumentException if propertyValueis empty * or null and the type could not be determined from the file name. */ diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java index 402ecd85eb6..8fb201390bc 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java @@ -18,8 +18,6 @@ package org.apache.zookeeper.common; -import java.security.KeyStore; -import java.security.KeyStoreException; /** * Implementation of {@link FileKeyStoreLoader} that loads from PKCS12 files. @@ -29,12 +27,7 @@ private PKCS12FileLoader(String keyStorePath, String trustStorePath, String keyStorePassword, String trustStorePassword) { - super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); - } - - @Override - protected KeyStore keyStoreInstance() throws KeyStoreException { - return KeyStore.getInstance("PKCS12"); + super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.PKCS12); } static class Builder extends FileKeyStoreLoader.Builder { diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java index 3a8cb2e711f..100c7dce335 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java @@ -34,11 +34,17 @@ abstract class StandardTypeFileKeyStoreLoader extends FileKeyStoreLoader { private static final char[] EMPTY_CHAR_ARRAY = new char[0]; - StandardTypeFileKeyStoreLoader(String keyStorePath, - String trustStorePath, - String keyStorePassword, - String trustStorePassword) { + protected final SupportedStandardKeyFormat format; + + protected enum SupportedStandardKeyFormat { + JKS, PKCS12, BCFKS + } + + + StandardTypeFileKeyStoreLoader(String keyStorePath, String trustStorePath, String keyStorePassword, + String trustStorePassword, SupportedStandardKeyFormat format) { super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + this.format = format; } @Override @@ -59,7 +65,9 @@ public KeyStore loadTrustStore() throws IOException, GeneralSecurityException { } } - protected abstract KeyStore keyStoreInstance() throws KeyStoreException; + private KeyStore keyStoreInstance() throws KeyStoreException { + return KeyStore.getInstance(format.name()); + } private static char[] passwordStringToCharArray(String password) { return password == null ? EMPTY_CHAR_ARRAY : password.toCharArray(); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java index 6e06d8436c2..5a6d70651bd 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java @@ -380,9 +380,9 @@ public SSLContextAndOptions createSSLContextAndOptions(ZKConfig config) throws S * @param keyStoreLocation the location of the key store file. * @param keyStorePassword optional password to decrypt the key store. If * empty, assumes the key store is not encrypted. - * @param keyStoreTypeProp must be JKS, PEM, or null. If null, attempts to - * autodetect the key store type from the file - * extension (.jks / .pem). + * @param keyStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If null, + * attempts to autodetect the key store type from + * the file extension (e.g. .jks / .pem). * @return the key manager. * @throws KeyManagerException if something goes wrong. */ @@ -425,9 +425,9 @@ public static X509KeyManager createKeyManager( * @param trustStorePassword optional password to decrypt the trust store * (only applies to JKS trust stores). If empty, * assumes the trust store is not encrypted. - * @param trustStoreTypeProp must be JKS, PEM, or null. If null, attempts - * to autodetect the trust store type from the - * file extension (.jks / .pem). + * @param trustStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If + * null, attempts to autodetect the trust store + * type from the file extension (e.g. .jks / .pem). * @param crlEnabled enable CRL (certificate revocation list) checks. * @param ocspEnabled enable OCSP (online certificate status protocol) * checks. diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/BCFKSFileLoaderTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BCFKSFileLoaderTest.java new file mode 100644 index 00000000000..c3ee49839e9 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BCFKSFileLoaderTest.java @@ -0,0 +1,160 @@ +/** + * 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.zookeeper.common; + +import java.io.IOException; +import java.security.KeyStore; +import java.util.Collection; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +@RunWith(Parameterized.class) +public class BCFKSFileLoaderTest extends BaseX509ParameterizedTestCase { + + + @Parameterized.Parameters + public static Collection params() { + return BaseX509ParameterizedTestCase.defaultParams(); + } + + public BCFKSFileLoaderTest( + final X509KeyType caKeyType, + final X509KeyType certKeyType, + final String keyPassword, + final Integer paramIndex) { + super(paramIndex, () -> { + try { + return X509TestContext.newBuilder() + .setTempDir(tempDir) + .setKeyStorePassword(keyPassword) + .setKeyStoreKeyType(certKeyType) + .setTrustStorePassword(keyPassword) + .setTrustStoreKeyType(caKeyType) + .build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Test + public void testLoadKeyStore() throws Exception { + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + KeyStore ks = new BCFKSFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + Assert.assertEquals(1, ks.size()); + } + + @Test(expected = Exception.class) + public void testLoadKeyStoreWithWrongPassword() throws Exception { + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword("wrong password") + .build() + .loadKeyStore(); + } + + @Test(expected = IOException.class) + public void testLoadKeyStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setKeyStorePath(path + ".does_not_exist") + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadKeyStoreWithNullFilePath() throws Exception { + new BCFKSFileLoader.Builder() + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = IOException.class) + public void testLoadKeyStoreWithWrongFileType() throws Exception { + // Trying to load a PEM file with BCFKS loader should fail + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test + public void testLoadTrustStore() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + KeyStore ts = new BCFKSFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + Assert.assertEquals(1, ts.size()); + } + + @Test(expected = Exception.class) + public void testLoadTrustStoreWithWrongPassword() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword("wrong password") + .build() + .loadTrustStore(); + } + + @Test(expected = IOException.class) + public void testLoadTrustStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setTrustStorePath(path + ".does_not_exist") + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadTrustStoreWithNullFilePath() throws Exception { + new BCFKSFileLoader.Builder() + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test(expected = IOException.class) + public void testLoadTrustStoreWithWrongFileType() throws Exception { + // Trying to load a PEM file with BCFKS loader should fail + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(); + new BCFKSFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + +} diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java index 9265af90b55..8521e2844f5 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java @@ -28,6 +28,7 @@ public void testGetPropertyValue() { Assert.assertEquals("PEM", KeyStoreFileType.PEM.getPropertyValue()); Assert.assertEquals("JKS", KeyStoreFileType.JKS.getPropertyValue()); Assert.assertEquals("PKCS12", KeyStoreFileType.PKCS12.getPropertyValue()); + Assert.assertEquals("BCFKS", KeyStoreFileType.BCFKS.getPropertyValue()); } @Test @@ -35,6 +36,7 @@ public void testFromPropertyValue() { Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("PEM")); Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("JKS")); Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("PKCS12")); + Assert.assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("BCFKS")); Assert.assertNull(KeyStoreFileType.fromPropertyValue("")); Assert.assertNull(KeyStoreFileType.fromPropertyValue(null)); } @@ -44,6 +46,7 @@ public void testFromPropertyValueIgnoresCase() { Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("pem")); Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("jks")); Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("pkcs12")); + Assert.assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("bcfks")); Assert.assertNull(KeyStoreFileType.fromPropertyValue("")); Assert.assertNull(KeyStoreFileType.fromPropertyValue(null)); } @@ -67,6 +70,10 @@ public void testFromFilename() { KeyStoreFileType.fromFilename("mykey.p12")); Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.p12")); + Assert.assertEquals(KeyStoreFileType.BCFKS, + KeyStoreFileType.fromFilename("mykey.bcfks")); + Assert.assertEquals(KeyStoreFileType.BCFKS, + KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.bcfks")); } @Test(expected = IllegalArgumentException.class) @@ -86,6 +93,9 @@ public void testFromPropertyValueOrFileName() { Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValueOrFileName( "PKCS12", "prod.key")); + Assert.assertEquals(KeyStoreFileType.BCFKS, + KeyStoreFileType.fromPropertyValueOrFileName( + "BCFKS", "prod.key")); // Falls back to filename detection if no property value Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValueOrFileName("", "prod.jks")); diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java index 3a899f44c79..fd1f7f2d134 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java @@ -54,6 +54,7 @@ public class X509TestContext { private File trustStoreJksFile; private File trustStorePemFile; private File trustStorePkcs12File; + private File trustStoreBcfksFile; private final X509KeyType keyStoreKeyType; private final KeyPair keyStoreKeyPair; @@ -63,6 +64,7 @@ public class X509TestContext { private File keyStoreJksFile; private File keyStorePemFile; private File keyStorePkcs12File; + private File keyStoreBcfksFile; private final Boolean hostnameVerification; @@ -176,6 +178,8 @@ public File getTrustStoreFile(KeyStoreFileType storeFileType) throws IOException return getTrustStorePemFile(); case PKCS12: return getTrustStorePkcs12File(); + case BCFKS: + return getTrustStoreBcfksFile(); default: throw new IllegalArgumentException("Invalid trust store type: " + storeFileType + ", must be one of: " + Arrays.toString(KeyStoreFileType.values())); @@ -231,6 +235,23 @@ private File getTrustStorePkcs12File() throws IOException { return trustStorePkcs12File; } + private File getTrustStoreBcfksFile() throws IOException { + if (trustStoreBcfksFile == null) { + File trustStoreBcfksFile = File.createTempFile( + TRUST_STORE_PREFIX, KeyStoreFileType.BCFKS.getDefaultFileExtension(), tempDir); + trustStoreBcfksFile.deleteOnExit(); + try (final FileOutputStream trustStoreOutputStream = new FileOutputStream(trustStoreBcfksFile)) { + byte[] bytes = X509TestHelpers.certToBCFKSTrustStoreBytes(trustStoreCertificate, trustStorePassword); + trustStoreOutputStream.write(bytes); + trustStoreOutputStream.flush(); + } catch (GeneralSecurityException e) { + throw new IOException(e); + } + this.trustStoreBcfksFile = trustStoreBcfksFile; + } + return trustStoreBcfksFile; + } + public X509KeyType getKeyStoreKeyType() { return keyStoreKeyType; } @@ -256,9 +277,9 @@ public boolean isKeyStoreEncrypted() { } /** - * Returns the path to the key store file in the given format (JKS or PEM). Note that the file is created lazily, + * Returns the path to the key store file in the given format (JKS, PEM, ...). Note that the file is created lazily, * the first time this method is called. The key store file is temporary and will be deleted on exit. - * @param storeFileType the store file type (JKS or PEM). + * @param storeFileType the store file type (JKS, PEM, ...). * @return the path to the key store file. * @throws IOException if there is an error creating the key store file. */ @@ -270,6 +291,8 @@ public File getKeyStoreFile(KeyStoreFileType storeFileType) throws IOException { return getKeyStorePemFile(); case PKCS12: return getKeyStorePkcs12File(); + case BCFKS: + return getKeyStoreBcfksFile(); default: throw new IllegalArgumentException("Invalid key store type: " + storeFileType + ", must be one of: " + Arrays.toString(KeyStoreFileType.values())); @@ -332,6 +355,24 @@ private File getKeyStorePkcs12File() throws IOException { return keyStorePkcs12File; } + private File getKeyStoreBcfksFile() throws IOException { + if (keyStoreBcfksFile == null) { + File keyStoreBcfksFile = File.createTempFile( + KEY_STORE_PREFIX, KeyStoreFileType.BCFKS.getDefaultFileExtension(), tempDir); + keyStoreBcfksFile.deleteOnExit(); + try (final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreBcfksFile)) { + byte[] bytes = X509TestHelpers.certAndPrivateKeyToBCFKSBytes( + keyStoreCertificate, keyStoreKeyPair.getPrivate(), keyStorePassword); + keyStoreOutputStream.write(bytes); + keyStoreOutputStream.flush(); + } catch (GeneralSecurityException e) { + throw new IOException(e); + } + this.keyStoreBcfksFile = keyStoreBcfksFile; + } + return keyStoreBcfksFile; + } + /** * Sets the SSL system properties such that the given X509Util object can be used to create SSL Contexts that * will use the trust store and key store files created by this test context. Example usage: @@ -343,8 +384,8 @@ private File getKeyStorePkcs12File() throws IOException { * SSLContext ctx = x509Util.getDefaultSSLContext(); * * @param x509Util the X509Util. - * @param keyStoreFileType the store file type to use for the key store (JKS or PEM). - * @param trustStoreFileType the store file type to use for the trust store (JKS or PEM). + * @param keyStoreFileType the store file type to use for the key store (JKS, PEM, ...). + * @param trustStoreFileType the store file type to use for the trust store (JKS, PEM, ...). * @throws IOException if there is an error creating the key store file or trust store file. */ public void setSystemProperties(X509Util x509Util, diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java index 9e8dcf62018..c5949a4040d 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java @@ -73,7 +73,7 @@ /** * This class contains helper methods for creating X509 certificates and key pairs, and for serializing them - * to JKS or PEM files. + * to JKS, PEM or other keystore type files. */ public class X509TestHelpers { private static final Logger LOG = LoggerFactory.getLogger(X509TestHelpers.class); @@ -369,6 +369,24 @@ public static byte[] certToPKCS12TrustStoreBytes( return certToTrustStoreBytes(cert, keyPassword, trustStore); } + /** + * Encodes the given X509Certificate as a BCFKS TrustStore, optionally protecting the cert with a password (though + * it's unclear why one would do this since certificates only contain public information and do not need to be + * kept secret). Returns the byte array encoding of the trust store, which may be written to a file and loaded to + * instantiate the trust store at a later point or in another process. + * @param cert the certificate to serialize. + * @param keyPassword an optional password to encrypt the trust store. If empty or null, the cert will not be encrypted. + * @return the serialized bytes of the BCFKS trust store. + * @throws IOException + * @throws GeneralSecurityException + */ + public static byte[] certToBCFKSTrustStoreBytes( + X509Certificate cert, + String keyPassword) throws IOException, GeneralSecurityException { + KeyStore trustStore = KeyStore.getInstance("BCFKS"); + return certToTrustStoreBytes(cert, keyPassword, trustStore); + } + private static byte[] certToTrustStoreBytes(X509Certificate cert, String keyPassword, KeyStore trustStore) throws IOException, GeneralSecurityException { @@ -399,7 +417,7 @@ public static byte[] certAndPrivateKeyToJavaKeyStoreBytes( PrivateKey privateKey, String keyPassword) throws IOException, GeneralSecurityException { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - return certAndPrivateKeyToPKCS12Bytes(cert, privateKey, keyPassword, keyStore); + return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore); } /** @@ -418,14 +436,30 @@ public static byte[] certAndPrivateKeyToPKCS12Bytes( PrivateKey privateKey, String keyPassword) throws IOException, GeneralSecurityException { KeyStore keyStore = KeyStore.getInstance("PKCS12"); - return certAndPrivateKeyToPKCS12Bytes(cert, privateKey, keyPassword, keyStore); + return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore); } - private static byte[] certAndPrivateKeyToPKCS12Bytes( - X509Certificate cert, - PrivateKey privateKey, - String keyPassword, - KeyStore keyStore) throws IOException, GeneralSecurityException { + /** + * Encodes the given X509Certificate and private key as a BCFKS KeyStore, optionally protecting the private key + * (and possibly the cert?) with a password. Returns the byte array encoding of the key store, which may be written + * to a file and loaded to instantiate the key store at a later point or in another process. + * @param cert the X509 certificate to serialize. + * @param privateKey the private key to serialize. + * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted. + * @return the serialized bytes of the BCFKS key store. + * @throws IOException + * @throws GeneralSecurityException + */ + public static byte[] certAndPrivateKeyToBCFKSBytes( + X509Certificate cert, + PrivateKey privateKey, + String keyPassword) throws IOException, GeneralSecurityException { + KeyStore keyStore = KeyStore.getInstance("BCFKS"); + return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore); + } + + private static byte[] certAndPrivateKeyToBytes( + X509Certificate cert, PrivateKey privateKey, String keyPassword, KeyStore keyStore) throws IOException, GeneralSecurityException { char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray(); keyStore.load(null, keyPasswordChars); keyStore.setKeyEntry(