ZOOKEEPER-3173: Quorum TLS - support PEM trust/key stores Add support for loading key and trust stores from PEM files.
Unfortunately, this PR includes 2 JIRAs, because it was quite difficult to untangle the two features as they were developed at the same time originally: - ZOOKEEPER-3173: Quorum TLS - support PEM trust/key stores - ZOOKEEPER-3175: Quorum TLS - test improvements ## Added support for PEM formatted key stores and trust stores - key store and trust store files can now be in PEM format as well as JKS. - Added config properties to tell ZK what type of trust/key store to load: - `zookeeper.ssl.keyStore.type` and `zookeeper.ssl.trustStore.type` for ClientX509Util - `zookeeper.ssl.quorum.keyStore.type` and `zookeeper.ssl.quorum.trustStore.type` for QuorumX509Util - store type properties could have the values "JKS", "PEM", or not set - leaving the type properties unset will cause auto-detection of the store type based on the file extension (".jks" or ".pem") ## Added test utilities for easily creating X509 certs and using them in unit tests - added new class `X509TestContext` and its friend, `X509TestHelpers` - rewrote some existing unit tests to use these classes, and added new tests that use them - some existing tests (i.e. `QuorumSSLTest`) should probably be ported to use this as well, haven't got around to it yet Author: Ilya Maykov <il...@fb.com> Reviewers: an...@apache.org Closes #678 from ivmaykov/ZOOKEEPER-3173 Project: http://git-wip-us.apache.org/repos/asf/zookeeper/repo Commit: http://git-wip-us.apache.org/repos/asf/zookeeper/commit/03286f29 Tree: http://git-wip-us.apache.org/repos/asf/zookeeper/tree/03286f29 Diff: http://git-wip-us.apache.org/repos/asf/zookeeper/diff/03286f29 Branch: refs/heads/master Commit: 03286f29d29f4c1d3496ce87d1441df91422220e Parents: e5fc122 Author: Ilya Maykov <il...@fb.com> Authored: Tue Nov 6 17:29:49 2018 -0800 Committer: Andor Molnar <an...@apache.org> Committed: Tue Nov 6 17:29:49 2018 -0800 ---------------------------------------------------------------------- NOTICE.txt | 6 + .../zookeeper/common/FileKeyStoreLoader.java | 77 +++ .../FileKeyStoreLoaderBuilderProvider.java | 45 ++ .../apache/zookeeper/common/JKSFileLoader.java | 94 ++++ .../zookeeper/common/KeyStoreFileType.java | 114 +++++ .../apache/zookeeper/common/KeyStoreLoader.java | 52 ++ .../apache/zookeeper/common/PEMFileLoader.java | 64 +++ .../org/apache/zookeeper/common/X509Util.java | 201 +++++--- .../org/apache/zookeeper/common/ZKConfig.java | 4 + .../server/auth/X509AuthenticationProvider.java | 39 +- .../org/apache/zookeeper/util/PemReader.java | 236 +++++++++ .../common/BaseX509ParameterizedTestCase.java | 109 ++++ .../FileKeyStoreLoaderBuilderProviderTest.java | 46 ++ .../zookeeper/common/JKSFileLoaderTest.java | 165 +++++++ .../zookeeper/common/KeyStoreFileTypeTest.java | 90 ++++ .../zookeeper/common/PEMFileLoaderTest.java | 156 ++++++ .../apache/zookeeper/common/X509KeyType.java | 26 + .../zookeeper/common/X509TestContext.java | 492 +++++++++++++++++++ .../zookeeper/common/X509TestHelpers.java | 402 +++++++++++++++ .../apache/zookeeper/common/X509UtilTest.java | 352 ++++++++----- .../apache/zookeeper/util/PemReaderTest.java | 137 ++++++ 21 files changed, 2676 insertions(+), 231 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/NOTICE.txt ---------------------------------------------------------------------- diff --git a/NOTICE.txt b/NOTICE.txt index 5689ab6..9ce75ba 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -3,3 +3,9 @@ Copyright 2009-2014 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). + +This product includes software components originally +developed for Airlift (https://github.com/airlift/airlift), +licensed under the Apache 2.0 license. The licensing terms +for Airlift code can be found at: +https://github.com/airlift/airlift/blob/master/LICENSE http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoader.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoader.java new file mode 100644 index 0000000..6cc37b6 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoader.java @@ -0,0 +1,77 @@ +/** + * 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.util.Objects; + +/** + * Base class for instances of {@link KeyStoreLoader} which load the key/trust + * stores from files on a filesystem. + */ +abstract class FileKeyStoreLoader implements KeyStoreLoader { + final String keyStorePath; + final String trustStorePath; + final String keyStorePassword; + final String trustStorePassword; + + FileKeyStoreLoader(String keyStorePath, + String trustStorePath, + String keyStorePassword, + String trustStorePassword) { + this.keyStorePath = keyStorePath; + this.trustStorePath = trustStorePath; + this.keyStorePassword = keyStorePassword; + this.trustStorePassword = trustStorePassword; + } + + /** + * Base class for builder pattern used by subclasses. + * @param <T> the subtype of FileKeyStoreLoader created by the Builder. + */ + static abstract class Builder<T extends FileKeyStoreLoader> { + String keyStorePath; + String trustStorePath; + String keyStorePassword; + String trustStorePassword; + + Builder() {} + + Builder<T> setKeyStorePath(String keyStorePath) { + this.keyStorePath = Objects.requireNonNull(keyStorePath); + return this; + } + + Builder<T> setTrustStorePath(String trustStorePath) { + this.trustStorePath = Objects.requireNonNull(trustStorePath); + return this; + } + + Builder<T> setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = Objects.requireNonNull(keyStorePassword); + return this; + } + + Builder<T> setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = Objects.requireNonNull(trustStorePassword); + return this; + } + + abstract T build(); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..bcbefe2 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java @@ -0,0 +1,45 @@ +/** + * 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.util.Objects; + +public class FileKeyStoreLoaderBuilderProvider { + /** + * Returns a {@link FileKeyStoreLoader.Builder} that can build a loader + * which loads keys and certs from files of the given + * {@link KeyStoreFileType}. + * + * @param type the file type to load keys/certs from. + * @return a new Builder. + */ + static FileKeyStoreLoader.Builder<? extends FileKeyStoreLoader> + getBuilderForKeyStoreFileType(KeyStoreFileType type) { + switch (Objects.requireNonNull(type)) { + case JKS: + return new JKSFileLoader.Builder(); + case PEM: + return new PEMFileLoader.Builder(); + default: + throw new AssertionError( + "Unexpected StoreFileType: " + type.name()); + } + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..f391c7b --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java @@ -0,0 +1,94 @@ +/** + * 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.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link FileKeyStoreLoader} that loads from JKS files. + */ +class JKSFileLoader extends FileKeyStoreLoader { + private static final Logger LOG = LoggerFactory.getLogger(JKSFileLoader.class); + + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + private static final String JKS_KEY_STORE_TYPE = "JKS"; + + private JKSFileLoader(String keyStorePath, + String trustStorePath, + String keyStorePassword, + String trustStorePassword) { + super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + } + + @Override + public KeyStore loadKeyStore() throws IOException, GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(JKS_KEY_STORE_TYPE); + InputStream inputStream = null; + try { + inputStream = new FileInputStream(new File(keyStorePath)); + ks.load(inputStream, passwordStringToCharArray(keyStorePassword)); + return ks; + } finally { + forceClose(inputStream); + } + } + + @Override + public KeyStore loadTrustStore() throws IOException, GeneralSecurityException { + KeyStore ts = KeyStore.getInstance(JKS_KEY_STORE_TYPE); + InputStream inputStream = null; + try { + inputStream = new FileInputStream(new File(trustStorePath)); + ts.load(inputStream, passwordStringToCharArray(trustStorePassword)); + return ts; + } finally { + forceClose(inputStream); + } + } + + private char[] passwordStringToCharArray(String password) { + return password == null ? EMPTY_CHAR_ARRAY : password.toCharArray(); + } + + private void forceClose(InputStream stream) { + if (stream == null) { + return; + } + try { + stream.close(); + } catch (IOException e) { + LOG.info("Failed to close key store input stream", e); + } + } + + static class Builder extends FileKeyStoreLoader.Builder<JKSFileLoader> { + @Override + JKSFileLoader build() { + return new JKSFileLoader(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..f006468 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java @@ -0,0 +1,114 @@ +/** + * 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; + +/** + * This enum represents the file type of a KeyStore or TrustStore. + * Currently, JKS (java keystore) and PEM types are supported. + */ +public enum KeyStoreFileType { + // TODO: consider adding support for PKCS12 + JKS(".jks"), PEM(".pem"); + + private final String defaultFileExtension; + + KeyStoreFileType(String defaultFileExtension) { + this.defaultFileExtension = defaultFileExtension; + } + + /** + * The property string that specifies that a key store or trust store + * should use this store file type. + */ + public String getPropertyValue() { + return this.name(); + } + + /** + * The file extension that is associated with this file type. + */ + public String getDefaultFileExtension() { + return defaultFileExtension; + } + + /** + * Converts a property value to a StoreFileType enum. If the property value + * is <code>null</code> or an empty string, returns <code>null</code>. + * @param propertyValue the property value. + * @return the KeyStoreFileType, or <code>null</code> if + * <code>propertyValue</code> is <code>null</code> or empty. + * @throws IllegalArgumentException if <code>propertyValue</code> is not + * one of "JKS", "PEM", or empty/null. + */ + public static KeyStoreFileType fromPropertyValue(String propertyValue) { + if (propertyValue == null || propertyValue.length() == 0) { + return null; + } + return KeyStoreFileType.valueOf(propertyValue.toUpperCase()); + } + + /** + * Detects the type of KeyStore / TrustStore file from the file extension. + * If the file name ends with ".jks", returns <code>StoreFileType.JKS</code>. + * If the file name ends with ".pem", returns <code>StoreFileType.PEM</code>. + * 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" or ".pem". + */ + public static KeyStoreFileType fromFilename(String filename) { + int i = filename.lastIndexOf('.'); + if (i >= 0) { + String extension = filename.substring(i); + for (KeyStoreFileType storeFileType : KeyStoreFileType.values()) { + if (storeFileType.getDefaultFileExtension().equals(extension)) { + return storeFileType; + } + } + } + throw new IllegalArgumentException( + "Unable to auto-detect store file type from file name: " + filename); + } + + /** + * If <code>propertyValue</code> is not null or empty, returns the result + * of <code>KeyStoreFileType.fromPropertyValue(propertyValue)</code>. Else, + * returns the result of <code>KeyStoreFileType.fromFileName(filename)</code>. + * @param propertyValue property value describing the KeyStoreFileType, or + * null/empty to auto-detect the type from the file + * name. + * @param filename file name of the key store file. The file extension is + * used to auto-detect the KeyStoreFileType when + * <code>propertyValue</code> is null or empty. + * @return a KeyStoreFileType. + * @throws IllegalArgumentException if <code>propertyValue</code> is not + * one of "JKS", "PEM", or empty/null. + * @throws IllegalArgumentException if <code>propertyValue</code>is empty + * or null and the type could not be determined from the file name. + */ + public static KeyStoreFileType fromPropertyValueOrFileName(String propertyValue, + String filename) { + KeyStoreFileType result = KeyStoreFileType.fromPropertyValue(propertyValue); + if (result == null) { + result = KeyStoreFileType.fromFilename(filename); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreLoader.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreLoader.java new file mode 100644 index 0000000..ad5d9d0 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreLoader.java @@ -0,0 +1,52 @@ +/** + * 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.GeneralSecurityException; +import java.security.KeyStore; + +/** + * An interface for an object that can load key stores or trust stores. + */ +interface KeyStoreLoader { + /** + * Loads a KeyStore which contains at least one private key and the + * associated X509 cert chain. + * + * @return a new KeyStore + * @throws IOException if loading the key store fails due to an IO error, + * such as "file not found". + * @throws GeneralSecurityException if loading the key store fails due to + * a security error, such as "unsupported crypto algorithm". + */ + KeyStore loadKeyStore() throws IOException, GeneralSecurityException; + + /** + * Loads a KeyStore which contains at least one X509 cert chain for a + * trusted Certificate Authority (CA). + * + * @return a new KeyStore + * @throws IOException if loading the trust store fails due to an IO error, + * such as "file not found". + * @throws GeneralSecurityException if loading the trust store fails due to + * a security error, such as "unsupported crypto algorithm". + */ + KeyStore loadTrustStore() throws IOException, GeneralSecurityException; +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/PEMFileLoader.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/PEMFileLoader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/PEMFileLoader.java new file mode 100644 index 0000000..637e8a8 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/PEMFileLoader.java @@ -0,0 +1,64 @@ +/** + * 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.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.Optional; + +import org.apache.zookeeper.util.PemReader; + +/** + * Implementation of {@link FileKeyStoreLoader} that loads from PEM files. + */ +class PEMFileLoader extends FileKeyStoreLoader { + private PEMFileLoader(String keyStorePath, + String trustStorePath, + String keyStorePassword, + String trustStorePassword) { + super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + } + + @Override + public KeyStore loadKeyStore() throws IOException, GeneralSecurityException { + Optional<String> passwordOption; + if (keyStorePassword == null || keyStorePassword.length() == 0) { + passwordOption = Optional.empty(); + } else { + passwordOption = Optional.of(keyStorePassword); + } + File file = new File(keyStorePath); + return PemReader.loadKeyStore(file, file, passwordOption); + } + + @Override + public KeyStore loadTrustStore() throws IOException, GeneralSecurityException { + return PemReader.loadTrustStore(new File(trustStorePath)); + } + + + static class Builder extends FileKeyStoreLoader.Builder<PEMFileLoader> { + @Override + PEMFileLoader build() { + return new PEMFileLoader(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword); + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java ---------------------------------------------------------------------- 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 2112c70..5b97ac6 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 @@ -18,8 +18,17 @@ package org.apache.zookeeper.common; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.CertPathTrustManagerParameters; import javax.net.ssl.KeyManager; @@ -33,26 +42,12 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.Socket; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Security; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.cert.PKIXBuilderParameters; -import java.security.cert.X509CertSelector; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; import org.apache.zookeeper.common.X509Exception.KeyManagerException; import org.apache.zookeeper.common.X509Exception.SSLContextException; import org.apache.zookeeper.common.X509Exception.TrustManagerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Utility code for X509 handling @@ -83,8 +78,10 @@ public abstract class X509Util { private String cipherSuitesProperty = getConfigPrefix() + "ciphersuites"; private String sslKeystoreLocationProperty = getConfigPrefix() + "keyStore.location"; private String sslKeystorePasswdProperty = getConfigPrefix() + "keyStore.password"; + private String sslKeystoreTypeProperty = getConfigPrefix() + "keyStore.type"; private String sslTruststoreLocationProperty = getConfigPrefix() + "trustStore.location"; private String sslTruststorePasswdProperty = getConfigPrefix() + "trustStore.password"; + private String sslTruststoreTypeProperty = getConfigPrefix() + "trustStore.type"; private String sslHostnameVerificationEnabledProperty = getConfigPrefix() + "hostnameVerification"; private String sslCrlEnabledProperty = getConfigPrefix() + "crl"; private String sslOcspEnabledProperty = getConfigPrefix() + "ocsp"; @@ -121,6 +118,10 @@ public abstract class X509Util { return sslKeystorePasswdProperty; } + public String getSslKeystoreTypeProperty() { + return sslKeystoreTypeProperty; + } + public String getSslTruststoreLocationProperty() { return sslTruststoreLocationProperty; } @@ -129,6 +130,10 @@ public abstract class X509Util { return sslTruststorePasswdProperty; } + public String getSslTruststoreTypeProperty() { + return sslTruststoreTypeProperty; + } + public String getSslHostnameVerificationEnabledProperty() { return sslHostnameVerificationEnabledProperty; } @@ -167,47 +172,49 @@ public abstract class X509Util { KeyManager[] keyManagers = null; TrustManager[] trustManagers = null; - String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty); - String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty); + String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty, ""); + String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty, ""); + String keyStoreTypeProp = config.getProperty(sslKeystoreTypeProperty); // There are legal states in some use cases for null KeyManager or TrustManager. - // But if a user wanna specify one, location and password are required. + // But if a user wanna specify one, location is required. Password defaults to empty string if it is not + // specified by the user. - if (keyStoreLocationProp == null && keyStorePasswordProp == null) { + if (keyStoreLocationProp.isEmpty()) { LOG.warn(getSslKeystoreLocationProperty() + " not specified"); } else { - if (keyStoreLocationProp == null) { - throw new SSLContextException(getSslKeystoreLocationProperty() + " not specified"); - } - if (keyStorePasswordProp == null) { - throw new SSLContextException(getSslKeystorePasswdProperty() + " not specified"); - } try { keyManagers = new KeyManager[]{ - createKeyManager(keyStoreLocationProp, keyStorePasswordProp)}; + createKeyManager(keyStoreLocationProp, keyStorePasswordProp, keyStoreTypeProp)}; } catch (KeyManagerException keyManagerException) { throw new SSLContextException("Failed to create KeyManager", keyManagerException); + } catch (IllegalArgumentException e) { + throw new SSLContextException("Bad value for " + sslKeystoreTypeProperty + ": " + keyStoreTypeProp, e); } } - String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty); - String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty); + String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty, ""); + String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty, ""); + String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty); boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty); boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty); boolean sslServerHostnameVerificationEnabled = - config.getBoolean(this.getSslHostnameVerificationEnabledProperty(),true); - boolean sslClientHostnameVerificationEnabled = sslServerHostnameVerificationEnabled && shouldVerifyClientHostname(); + config.getBoolean(this.getSslHostnameVerificationEnabledProperty(), true); + boolean sslClientHostnameVerificationEnabled = + sslServerHostnameVerificationEnabled && shouldVerifyClientHostname(); - if (trustStoreLocationProp == null) { + if (trustStoreLocationProp.isEmpty()) { LOG.warn(getSslTruststoreLocationProperty() + " not specified"); } else { try { trustManagers = new TrustManager[]{ - createTrustManager(trustStoreLocationProp, trustStorePasswordProp, sslCrlEnabled, sslOcspEnabled, + createTrustManager(trustStoreLocationProp, trustStorePasswordProp, trustStoreTypeProp, sslCrlEnabled, sslOcspEnabled, sslServerHostnameVerificationEnabled, sslClientHostnameVerificationEnabled)}; } catch (TrustManagerException trustManagerException) { throw new SSLContextException("Failed to create TrustManager", trustManagerException); + } catch (IllegalArgumentException e) { + throw new SSLContextException("Bad value for " + sslTruststoreTypeProperty + ": " + trustStoreTypeProp, e); } } @@ -221,17 +228,38 @@ public abstract class X509Util { } } - public static X509KeyManager createKeyManager(String keyStoreLocation, String keyStorePassword) + /** + * Creates a key manager by loading the key store from the given file of + * the given type, optionally decrypting it using the given password. + * @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). + * @return the key manager. + * @throws KeyManagerException if something goes wrong. + */ + public static X509KeyManager createKeyManager( + String keyStoreLocation, + String keyStorePassword, + String keyStoreTypeProp) throws KeyManagerException { - FileInputStream inputStream = null; + if (keyStorePassword == null) { + keyStorePassword = ""; + } try { - char[] keyStorePasswordChars = keyStorePassword.toCharArray(); - File keyStoreFile = new File(keyStoreLocation); - KeyStore ks = KeyStore.getInstance("JKS"); - inputStream = new FileInputStream(keyStoreFile); - ks.load(inputStream, keyStorePasswordChars); + KeyStoreFileType storeFileType = + KeyStoreFileType.fromPropertyValueOrFileName( + keyStoreTypeProp, keyStoreLocation); + KeyStore ks = FileKeyStoreLoaderBuilderProvider + .getBuilderForKeyStoreFileType(storeFileType) + .setKeyStorePath(keyStoreLocation) + .setKeyStorePassword(keyStorePassword) + .build() + .loadKeyStore(); KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); - kmf.init(ks, keyStorePasswordChars); + kmf.init(ks, keyStorePassword.toCharArray()); for (KeyManager km : kmf.getKeyManagers()) { if (km instanceof X509KeyManager) { @@ -239,38 +267,58 @@ public abstract class X509Util { } } throw new KeyManagerException("Couldn't find X509KeyManager"); - - } catch (IOException|CertificateException|UnrecoverableKeyException|NoSuchAlgorithmException|KeyStoreException - keyManagerCreationException) { - throw new KeyManagerException(keyManagerCreationException); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException ioException) { - LOG.info("Failed to close key store input stream", ioException); - } - } + } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { + throw new KeyManagerException(e); } } - public static X509TrustManager createTrustManager(String trustStoreLocation, String trustStorePassword, - boolean crlEnabled, boolean ocspEnabled, - final boolean serverHostnameVerificationEnabled, - final boolean clientHostnameVerificationEnabled) + /** + * Creates a trust manager by loading the trust store from the given file + * of the given type, optionally decrypting it using the given password. + * @param trustStoreLocation the location of the trust store file. + * @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 crlEnabled enable CRL (certificate revocation list) checks. + * @param ocspEnabled enable OCSP (online certificate status protocol) + * checks. + * @param serverHostnameVerificationEnabled if true, verify hostnames of + * remote servers that client + * sockets created by this + * X509Util connect to. + * @param clientHostnameVerificationEnabled if true, verify hostnames of + * remote clients that server + * sockets created by this + * X509Util accept connections + * from. + * @return the trust manager. + * @throws TrustManagerException if something goes wrong. + */ + public static X509TrustManager createTrustManager( + String trustStoreLocation, + String trustStorePassword, + String trustStoreTypeProp, + boolean crlEnabled, + boolean ocspEnabled, + final boolean serverHostnameVerificationEnabled, + final boolean clientHostnameVerificationEnabled) throws TrustManagerException { - FileInputStream inputStream = null; + if (trustStorePassword == null) { + trustStorePassword = ""; + } try { - File trustStoreFile = new File(trustStoreLocation); - KeyStore ts = KeyStore.getInstance("JKS"); - inputStream = new FileInputStream(trustStoreFile); - if (trustStorePassword != null) { - char[] trustStorePasswordChars = trustStorePassword.toCharArray(); - ts.load(inputStream, trustStorePasswordChars); - } else { - ts.load(inputStream, null); - } - + KeyStoreFileType storeFileType = + KeyStoreFileType.fromPropertyValueOrFileName( + trustStoreTypeProp, trustStoreLocation); + KeyStore ts = FileKeyStoreLoaderBuilderProvider + .getBuilderForKeyStoreFileType(storeFileType) + .setTrustStorePath(trustStoreLocation) + .setTrustStorePassword(trustStorePassword) + .build() + .loadTrustStore(); PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector()); if (crlEnabled || ocspEnabled) { pbParams.setRevocationEnabled(true); @@ -294,17 +342,8 @@ public abstract class X509Util { } } throw new TrustManagerException("Couldn't find X509TrustManager"); - } catch (IOException|CertificateException|NoSuchAlgorithmException|InvalidAlgorithmParameterException|KeyStoreException - trustManagerCreationException) { - throw new TrustManagerException(trustManagerCreationException); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException ioException) { - LOG.info("failed to close TrustStore input stream", ioException); - } - } + } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { + throw new TrustManagerException(e); } } http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java index dc24b19..01bac69 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java @@ -116,10 +116,14 @@ public class ZKConfig { System.getProperty(x509Util.getSslKeystoreLocationProperty())); properties.put(x509Util.getSslKeystorePasswdProperty(), System.getProperty(x509Util.getSslKeystorePasswdProperty())); + properties.put(x509Util.getSslKeystoreTypeProperty(), + System.getProperty(x509Util.getSslKeystoreTypeProperty())); properties.put(x509Util.getSslTruststoreLocationProperty(), System.getProperty(x509Util.getSslTruststoreLocationProperty())); properties.put(x509Util.getSslTruststorePasswdProperty(), System.getProperty(x509Util.getSslTruststorePasswdProperty())); + properties.put(x509Util.getSslTruststoreTypeProperty(), + System.getProperty(x509Util.getSslTruststoreTypeProperty())); properties.put(x509Util.getSslHostnameVerificationEnabledProperty(), System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty())); properties.put(x509Util.getSslCrlEnabledProperty(), http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java index 8a699ce..d0ca079 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java @@ -27,11 +27,11 @@ import javax.security.auth.x500.X500Principal; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.common.ClientX509Util; -import org.apache.zookeeper.common.ZKConfig; import org.apache.zookeeper.common.X509Exception; import org.apache.zookeeper.common.X509Exception.KeyManagerException; import org.apache.zookeeper.common.X509Exception.TrustManagerException; import org.apache.zookeeper.common.X509Util; +import org.apache.zookeeper.common.ZKConfig; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.server.ServerCnxn; import org.slf4j.Logger; @@ -70,46 +70,37 @@ public class X509AuthenticationProvider implements AuthenticationProvider { ZKConfig config = new ZKConfig(); X509Util x509Util = new ClientX509Util(); - String keyStoreLocation = config.getProperty(x509Util.getSslKeystoreLocationProperty()); - String keyStorePassword = config.getProperty(x509Util.getSslKeystorePasswdProperty()); + String keyStoreLocation = config.getProperty(x509Util.getSslKeystoreLocationProperty(), ""); + String keyStorePassword = config.getProperty(x509Util.getSslKeystorePasswdProperty(), ""); + String keyStoreTypeProp = config.getProperty(x509Util.getSslKeystoreTypeProperty()); - boolean crlEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslCrlEnabledProperty())); - boolean ocspEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslOcspEnabledProperty())); - boolean hostnameVerificationEnabled = Boolean.parseBoolean(System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty())); + boolean crlEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty())); + boolean ocspEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslOcspEnabledProperty())); + boolean hostnameVerificationEnabled = Boolean.parseBoolean( + config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty())); X509KeyManager km = null; X509TrustManager tm = null; - if (keyStoreLocation == null && keyStorePassword == null) { + if (keyStoreLocation.isEmpty()) { LOG.warn("keystore not specified for client connection"); } else { - if (keyStoreLocation == null) { - throw new X509Exception("keystore location not specified for client connection"); - } - if (keyStorePassword == null) { - throw new X509Exception("keystore password not specified for client connection"); - } try { - km = X509Util.createKeyManager(keyStoreLocation, keyStorePassword); + km = X509Util.createKeyManager(keyStoreLocation, keyStorePassword, keyStoreTypeProp); } catch (KeyManagerException e) { LOG.error("Failed to create key manager", e); } } - String trustStoreLocation = config.getProperty(x509Util.getSslTruststoreLocationProperty()); - String trustStorePassword = config.getProperty(x509Util.getSslTruststorePasswdProperty()); + String trustStoreLocation = config.getProperty(x509Util.getSslTruststoreLocationProperty(), ""); + String trustStorePassword = config.getProperty(x509Util.getSslTruststorePasswdProperty(), ""); + String trustStoreTypeProp = config.getProperty(x509Util.getSslTruststoreTypeProperty()); - if (trustStoreLocation == null && trustStorePassword == null) { + if (trustStoreLocation.isEmpty()) { LOG.warn("Truststore not specified for client connection"); } else { - if (trustStoreLocation == null) { - throw new X509Exception("Truststore location not specified for client connection"); - } - if (trustStorePassword == null) { - throw new X509Exception("Truststore password not specified for client connection"); - } try { tm = X509Util.createTrustManager( - trustStoreLocation, trustStorePassword, crlEnabled, ocspEnabled, + trustStoreLocation, trustStorePassword, trustStoreTypeProp, crlEnabled, ocspEnabled, hostnameVerificationEnabled, false); } catch (TrustManagerException e) { LOG.error("Failed to create trust manager", e); http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java b/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java new file mode 100644 index 0000000..25017ea --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java @@ -0,0 +1,236 @@ +/** + * 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.util; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.security.auth.x500.X500Principal; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.util.Base64.getMimeDecoder; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static javax.crypto.Cipher.DECRYPT_MODE; + +/** + * Note: this class is copied from io.airlift.security.pem.PemReader (see + * https://github.com/airlift/airlift/blob/master/security/src/main/java/io/airlift/security/pem/PemReader.java) with + * permission of the authors, to avoid adding an extra library dependency to Zookeeper. + * The file was copied from commit hash 86348546af43217f4d04a0cdad624b0ae4751c2c. + * + * The following modifications have been made to the original source code: + * <ul> + * <li>imports have been rearranged to match Zookeeper import order style.</li> + * <li>The dependency on <code>com.google.common.io.Files.asCharSource</code> has been removed.</li> + * <li>A dependency on <code>java.nio.file.Files</code> has been added.</li> + * </ul> + */ +public final class PemReader +{ + private static final Pattern CERT_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*CERTIFICATE[^-]*-+", // Footer + CASE_INSENSITIVE); + + private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer + CASE_INSENSITIVE); + + private static final Pattern PUBLIC_KEY_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*PUBLIC\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*PUBLIC\\s+KEY[^-]*-+", // Footer + CASE_INSENSITIVE); + + private PemReader() {} + + public static KeyStore loadTrustStore(File certificateChainFile) + throws IOException, GeneralSecurityException + { + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + + List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile); + for (X509Certificate certificate : certificateChain) { + X500Principal principal = certificate.getSubjectX500Principal(); + keyStore.setCertificateEntry(principal.getName("RFC2253"), certificate); + } + return keyStore; + } + + public static KeyStore loadKeyStore(File certificateChainFile, File privateKeyFile, Optional<String> keyPassword) + throws IOException, GeneralSecurityException + { + PrivateKey key = loadPrivateKey(privateKeyFile, keyPassword); + + List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile); + if (certificateChain.isEmpty()) { + throw new CertificateException("Certificate file does not contain any certificates: " + certificateChainFile); + } + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + keyStore.setKeyEntry("key", key, keyPassword.orElse("").toCharArray(), certificateChain.toArray(new Certificate[0])); + return keyStore; + } + + public static List<X509Certificate> readCertificateChain(File certificateChainFile) + throws IOException, GeneralSecurityException + { + String contents = new String(Files.readAllBytes(certificateChainFile.toPath()), US_ASCII); + return readCertificateChain(contents); + } + + public static List<X509Certificate> readCertificateChain(String certificateChain) + throws CertificateException + { + Matcher matcher = CERT_PATTERN.matcher(certificateChain); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + List<X509Certificate> certificates = new ArrayList<>(); + + int start = 0; + while (matcher.find(start)) { + byte[] buffer = base64Decode(matcher.group(1)); + certificates.add((X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(buffer))); + start = matcher.end(); + } + + return certificates; + } + + public static PrivateKey loadPrivateKey(File privateKeyFile, Optional<String> keyPassword) + throws IOException, GeneralSecurityException + { + String privateKey = new String(Files.readAllBytes(privateKeyFile.toPath()), US_ASCII); + return loadPrivateKey(privateKey, keyPassword); + } + + public static PrivateKey loadPrivateKey(String privateKey, Optional<String> keyPassword) + throws IOException, GeneralSecurityException + { + Matcher matcher = PRIVATE_KEY_PATTERN.matcher(privateKey); + if (!matcher.find()) { + throw new KeyStoreException("did not find a private key"); + } + byte[] encodedKey = base64Decode(matcher.group(1)); + + PKCS8EncodedKeySpec encodedKeySpec; + if (keyPassword.isPresent()) { + EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); + SecretKey secretKey = keyFactory.generateSecret(new PBEKeySpec(keyPassword.get().toCharArray())); + + Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); + cipher.init(DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); + + encodedKeySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); + } + else { + encodedKeySpec = new PKCS8EncodedKeySpec(encodedKey); + } + + // this code requires a key in PKCS8 format which is not the default openssl format + // to convert to the PKCS8 format you use : openssl pkcs8 -topk8 ... + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(encodedKeySpec); + } + catch (InvalidKeySpecException ignore) { + } + + try { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePrivate(encodedKeySpec); + } + catch (InvalidKeySpecException ignore) { + } + + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePrivate(encodedKeySpec); + } + + public static PublicKey loadPublicKey(File publicKeyFile) + throws IOException, GeneralSecurityException + { + String publicKey = new String(Files.readAllBytes(publicKeyFile.toPath()), US_ASCII); + return loadPublicKey(publicKey); + } + + public static PublicKey loadPublicKey(String publicKey) + throws GeneralSecurityException + { + Matcher matcher = PUBLIC_KEY_PATTERN.matcher(publicKey); + if (!matcher.find()) { + throw new KeyStoreException("did not find a public key"); + } + String data = matcher.group(1); + byte[] encodedKey = base64Decode(data); + + X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(encodedKey); + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(encodedKeySpec); + } + catch (InvalidKeySpecException ignore) { + } + + try { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePublic(encodedKeySpec); + } + catch (InvalidKeySpecException ignore) { + } + + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePublic(encodedKeySpec); + } + + private static byte[] base64Decode(String base64) + { + return getMimeDecoder().decode(base64.getBytes(US_ASCII)); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java new file mode 100644 index 0000000..73b9196 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java @@ -0,0 +1,109 @@ +/** + * 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.File; +import java.io.IOException; +import java.security.Security; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.test.ClientBase; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Base class for parameterized unit tests that use X509TestContext for testing + * different X509 parameter combinations (CA key type, cert key type, with/without + * a password, with/without hostname verification, etc). + * + * This base class takes care of setting up / cleaning up the test environment, + * and caching the X509TestContext objects used by the tests. + */ +public abstract class BaseX509ParameterizedTestCase extends ZKTestCase { + /** + * Default parameters suitable for most subclasses. See example usage + * in {@link X509UtilTest}. + * @return an array of parameter combinations to test with. + */ + public static Collection<Object[]> defaultParams() { + ArrayList<Object[]> result = new ArrayList<>(); + int paramIndex = 0; + for (X509KeyType caKeyType : X509KeyType.values()) { + for (X509KeyType certKeyType : X509KeyType.values()) { + for (String keyPassword : new String[]{"", "pa$$w0rd"}) { + result.add(new Object[]{caKeyType, certKeyType, keyPassword, paramIndex++}); + } + } + } + return result; + } + + /** + * Because key generation and writing / deleting files is kind of expensive, we cache the certs and on-disk files + * between test cases. None of the test cases modify any of this data so it's safe to reuse between tests. This + * caching makes all test cases after the first one for a given parameter combination complete almost instantly. + */ + protected static Map<Integer, X509TestContext> cachedTestContexts; + protected static File tempDir; + + protected X509TestContext x509TestContext; + + @BeforeClass + public static void setUpBaseClass() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + cachedTestContexts = new HashMap<>(); + tempDir = ClientBase.createEmptyTestDir(); + } + + @AfterClass + public static void cleanUpBaseClass() { + Security.removeProvider("BC"); + cachedTestContexts.clear(); + cachedTestContexts = null; + try { + FileUtils.deleteDirectory(tempDir); + } catch (IOException e) { + // ignore + } + } + + /** + * Constructor. See example usage in {@link X509UtilTest}. + * + * @param paramIndex the index under which the X509TestContext should be cached. + * @param contextSupplier a function that creates and returns the X509TestContext + * for the current index if one is not already cached. + */ + protected BaseX509ParameterizedTestCase( + Integer paramIndex, + java.util.function.Supplier<X509TestContext> contextSupplier) { + if (cachedTestContexts.containsKey(paramIndex)) { + x509TestContext = cachedTestContexts.get(paramIndex); + } else { + x509TestContext = contextSupplier.get(); + cachedTestContexts.put(paramIndex, x509TestContext); + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProviderTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProviderTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProviderTest.java new file mode 100644 index 0000000..59c27b2 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProviderTest.java @@ -0,0 +1,46 @@ +/** + * 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 org.apache.zookeeper.ZKTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class FileKeyStoreLoaderBuilderProviderTest extends ZKTestCase { + @Test + public void testGetBuilderForJKSFileType() { + FileKeyStoreLoader.Builder<?> builder = + FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType( + KeyStoreFileType.JKS); + Assert.assertTrue(builder instanceof JKSFileLoader.Builder); + } + + @Test + public void testGetBuilderForPEMFileType() { + FileKeyStoreLoader.Builder<?> builder = + FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType( + KeyStoreFileType.PEM); + Assert.assertTrue(builder instanceof PEMFileLoader.Builder); + } + + @Test(expected = NullPointerException.class) + public void testGetBuilderForNullFileType() { + FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(null); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/JKSFileLoaderTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/JKSFileLoaderTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/JKSFileLoaderTest.java new file mode 100644 index 0000000..5e916f3 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/JKSFileLoaderTest.java @@ -0,0 +1,165 @@ +/** + * 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 JKSFileLoaderTest extends BaseX509ParameterizedTestCase { + + @Parameterized.Parameters + public static Collection<Object[]> params() { + return BaseX509ParameterizedTestCase.defaultParams(); + } + + public JKSFileLoaderTest( + 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.JKS) + .getAbsolutePath(); + KeyStore ks = new JKSFileLoader.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.JKS) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword("wrong password") + .build() + .loadKeyStore(); + } + + @Test(expected = IOException.class) + public void testLoadKeyStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setKeyStorePath(path + ".does_not_exist") + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadKeyStoreWithNullFilePath() throws Exception { + new JKSFileLoader.Builder() + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = IOException.class) + public void testLoadKeyStoreWithWrongFileType() throws Exception { + // Trying to load a PEM file with JKS loader should fail + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test + public void testLoadTrustStore() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS) + .getAbsolutePath(); + KeyStore ts = new JKSFileLoader.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.JKS) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword("wrong password") + .build() + .loadTrustStore(); + } + + @Test(expected = IOException.class) + public void testLoadTrustStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setTrustStorePath(path + ".does_not_exist") + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadTrustStoreWithNullFilePath() throws Exception { + new JKSFileLoader.Builder() + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test(expected = IOException.class) + public void testLoadTrustStoreWithWrongFileType() throws Exception { + // Trying to load a PEM file with JKS loader should fail + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM) + .getAbsolutePath(); + new JKSFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..53aa0b0 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java @@ -0,0 +1,90 @@ +/** + * 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 org.apache.zookeeper.ZKTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class KeyStoreFileTypeTest extends ZKTestCase { + @Test + public void testGetPropertyValue() { + Assert.assertEquals("PEM", KeyStoreFileType.PEM.getPropertyValue()); + Assert.assertEquals("JKS", KeyStoreFileType.JKS.getPropertyValue()); + } + + @Test + public void testFromPropertyValue() { + Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("PEM")); + Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("JKS")); + Assert.assertNull(KeyStoreFileType.fromPropertyValue("")); + Assert.assertNull(KeyStoreFileType.fromPropertyValue(null)); + } + + @Test + public void testFromPropertyValueIgnoresCase() { + Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("pem")); + Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("jks")); + Assert.assertNull(KeyStoreFileType.fromPropertyValue("")); + Assert.assertNull(KeyStoreFileType.fromPropertyValue(null)); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromPropertyValueThrowsOnBadPropertyValue() { + KeyStoreFileType.fromPropertyValue("foobar"); + } + + @Test + public void testFromFilename() { + Assert.assertEquals(KeyStoreFileType.JKS, + KeyStoreFileType.fromFilename("mykey.jks")); + Assert.assertEquals(KeyStoreFileType.JKS, + KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.jks")); + Assert.assertEquals(KeyStoreFileType.PEM, + KeyStoreFileType.fromFilename("mykey.pem")); + Assert.assertEquals(KeyStoreFileType.PEM, + KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.pem")); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromFilenameThrowsOnBadFileExtension() { + KeyStoreFileType.fromFilename("prod.key"); + } + + @Test + public void testFromPropertyValueOrFileName() { + // Property value takes precedence if provided + Assert.assertEquals(KeyStoreFileType.JKS, + KeyStoreFileType.fromPropertyValueOrFileName( + "JKS", "prod.key")); + // Falls back to filename detection if no property value + Assert.assertEquals(KeyStoreFileType.JKS, + KeyStoreFileType.fromPropertyValueOrFileName("", "prod.jks")); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromPropertyValueOrFileNameThrowsOnBadPropertyValue() { + KeyStoreFileType.fromPropertyValueOrFileName("foobar", "prod.jks"); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromPropertyValueOrFileNameThrowsOnBadFileExtension() { + KeyStoreFileType.fromPropertyValueOrFileName("", "prod.key"); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/PEMFileLoaderTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/PEMFileLoaderTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/PEMFileLoaderTest.java new file mode 100644 index 0000000..e78c750 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/PEMFileLoaderTest.java @@ -0,0 +1,156 @@ +/** + * 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.security.KeyStoreException; +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 PEMFileLoaderTest extends BaseX509ParameterizedTestCase { + + @Parameterized.Parameters + public static Collection<Object[]> params() { + return BaseX509ParameterizedTestCase.defaultParams(); + } + + public PEMFileLoaderTest( + 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.PEM) + .getAbsolutePath(); + KeyStore ks = new PEMFileLoader.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.PEM) + .getAbsolutePath(); + new PEMFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword("wrong password") + .build() + .loadKeyStore(); + } + + @Test(expected = IOException.class) + public void testLoadKeyStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM) + .getAbsolutePath(); + new PEMFileLoader.Builder() + .setKeyStorePath(path + ".does_not_exist") + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadKeyStoreWithNullFilePath() throws Exception { + new PEMFileLoader.Builder() + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test(expected = KeyStoreException.class) + public void testLoadKeyStoreWithWrongFileType() throws Exception { + // Trying to load a JKS file with PEM loader should fail + String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS) + .getAbsolutePath(); + new PEMFileLoader.Builder() + .setKeyStorePath(path) + .setKeyStorePassword(x509TestContext.getKeyStorePassword()) + .build() + .loadKeyStore(); + } + + @Test + public void testLoadTrustStore() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM) + .getAbsolutePath(); + KeyStore ts = new PEMFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + Assert.assertEquals(1, ts.size()); + } + + @Test(expected = IOException.class) + public void testLoadTrustStoreWithWrongFilePath() throws Exception { + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM) + .getAbsolutePath(); + new PEMFileLoader.Builder() + .setTrustStorePath(path + ".does_not_exist") + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test(expected = NullPointerException.class) + public void testLoadTrustStoreWithNullFilePath() throws Exception { + new PEMFileLoader.Builder() + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + } + + @Test + public void testLoadTrustStoreWithWrongFileType() throws Exception { + // Trying to load a JKS file with PEM loader should fail + String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS) + .getAbsolutePath(); + KeyStore ts = new PEMFileLoader.Builder() + .setTrustStorePath(path) + .setTrustStorePassword(x509TestContext.getTrustStorePassword()) + .build() + .loadTrustStore(); + Assert.assertEquals(0, ts.size()); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509KeyType.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509KeyType.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509KeyType.java new file mode 100644 index 0000000..e2cdc81 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509KeyType.java @@ -0,0 +1,26 @@ +/** + * 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; + +/** + * Represents a type of key pair used for X509 certs in tests. The two options are RSA or EC (elliptic curve). + */ +public enum X509KeyType { + RSA, EC; +}