diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index db56d6e0dfc..8dfe6f69b7a 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -1407,8 +1407,10 @@ 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.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**) @@ -1420,8 +1422,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.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 169fb9e5639..f4798af64ae 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 @@ static FileKeyStoreLoader.Builder getBuilderForKey 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 8be7c376917..d8709d7bde0 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. @@ -31,12 +29,7 @@ private JKSFileLoader( 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 ecc1e5f4c12..69283f31f9d 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,12 +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"); + PKCS12(".p12"), + BCFKS(".bcfks"); private final String defaultFileExtension; @@ -55,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) { @@ -69,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('.'); @@ -100,7 +102,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 35e89a18935..8903f288e95 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. @@ -31,12 +29,7 @@ private PKCS12FileLoader( 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 b32ee073038..8f068f7befc 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 @@ -35,12 +35,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 @@ -61,7 +66,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 24c7f351bdd..52cb5fe6f0c 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 @@ -419,9 +419,9 @@ public static KeyStore loadTrustStore( * @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. */ @@ -455,9 +455,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..868be2e7544 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BCFKSFileLoaderTest.java @@ -0,0 +1,161 @@ +/** + * 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 feeed810345..87c4c6d26bb 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 @@ -30,6 +30,7 @@ public void testGetPropertyValue() { assertEquals("PEM", KeyStoreFileType.PEM.getPropertyValue()); assertEquals("JKS", KeyStoreFileType.JKS.getPropertyValue()); assertEquals("PKCS12", KeyStoreFileType.PKCS12.getPropertyValue()); + assertEquals("BCFKS", KeyStoreFileType.BCFKS.getPropertyValue()); } @Test @@ -37,6 +38,7 @@ public void testFromPropertyValue() { assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("PEM")); assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("JKS")); assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("PKCS12")); + assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("BCFKS")); assertNull(KeyStoreFileType.fromPropertyValue("")); assertNull(KeyStoreFileType.fromPropertyValue(null)); } @@ -46,6 +48,7 @@ public void testFromPropertyValueIgnoresCase() { assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("pem")); assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("jks")); assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("pkcs12")); + assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("bcfks")); assertNull(KeyStoreFileType.fromPropertyValue("")); assertNull(KeyStoreFileType.fromPropertyValue(null)); } @@ -63,6 +66,8 @@ public void testFromFilename() { assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.pem")); assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromFilename("mykey.p12")); assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.p12")); + assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromFilename("mykey.bcfks")); + assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.bcfks")); } @Test(expected = IllegalArgumentException.class) @@ -76,6 +81,7 @@ public void testFromPropertyValueOrFileName() { assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValueOrFileName("JKS", "prod.key")); assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValueOrFileName("PEM", "prod.key")); assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValueOrFileName("PKCS12", "prod.key")); + assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValueOrFileName("BCFKS", "prod.key")); // Falls back to filename detection if no property value 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 71fde1e6c8c..32380268c17 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 @@ -53,6 +53,7 @@ public class X509TestContext { private File trustStoreJksFile; private File trustStorePemFile; private File trustStorePkcs12File; + private File trustStoreBcfksFile; private final X509KeyType keyStoreKeyType; private final KeyPair keyStoreKeyPair; @@ -62,6 +63,7 @@ public class X509TestContext { private File keyStoreJksFile; private File keyStorePemFile; private File keyStorePkcs12File; + private File keyStoreBcfksFile; private final Boolean hostnameVerification; @@ -160,7 +162,9 @@ public File getTrustStoreFile(KeyStoreFileType storeFileType) throws IOException return getTrustStorePemFile(); case PKCS12: return getTrustStorePkcs12File(); - default: + case BCFKS: + return getTrustStoreBcfksFile(); + default: throw new IllegalArgumentException("Invalid trust store type: " + storeFileType + ", must be one of: " @@ -210,6 +214,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; } @@ -235,9 +256,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. */ @@ -249,6 +270,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 @@ -303,6 +326,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: @@ -314,8 +355,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, KeyStoreFileType keyStoreFileType, KeyStoreFileType trustStoreFileType) throws IOException { 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 4f169689743..fb1371a3da7 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 @@ -72,7 +72,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 { @@ -325,6 +325,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 { char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray(); trustStore.load(null, keyPasswordChars); @@ -351,7 +369,7 @@ private static byte[] certToTrustStoreBytes(X509Certificate cert, String keyPass public static byte[] certAndPrivateKeyToJavaKeyStoreBytes( X509Certificate cert, 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); } /** @@ -368,10 +386,29 @@ public static byte[] certAndPrivateKeyToJavaKeyStoreBytes( public static byte[] certAndPrivateKeyToPKCS12Bytes( X509Certificate cert, PrivateKey privateKey, String keyPassword) throws IOException, GeneralSecurityException { KeyStore keyStore = KeyStore.getInstance("PKCS12"); - return certAndPrivateKeyToPKCS12Bytes(cert, privateKey, keyPassword, keyStore); + return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore); + } + + /** + * 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[] certAndPrivateKeyToPKCS12Bytes( + 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);