You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by an...@apache.org on 2018/11/07 01:29:53 UTC
[1/2] zookeeper git commit: ZOOKEEPER-3173: Quorum TLS - support PEM
trust/key stores
Repository: zookeeper
Updated Branches:
refs/heads/master e5fc12281 -> 03286f29d
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..5a86bb4
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
@@ -0,0 +1,492 @@
+/**
+ * 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.FileOutputStream;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * This class simplifies the creation of certificates and private keys for SSL/TLS connections.
+ */
+public class X509TestContext {
+ private static final String TRUST_STORE_PREFIX = "zk_test_ca";
+ private static final String KEY_STORE_PREFIX = "zk_test_key";
+
+ private final File tempDir;
+
+ private final X509KeyType trustStoreKeyType;
+ private final KeyPair trustStoreKeyPair;
+ private final long trustStoreCertExpirationMillis;
+ private final X509Certificate trustStoreCertificate;
+ private final String trustStorePassword;
+ private File trustStoreJksFile;
+ private File trustStorePemFile;
+
+ private final X509KeyType keyStoreKeyType;
+ private final KeyPair keyStoreKeyPair;
+ private final long keyStoreCertExpirationMillis;
+ private final X509Certificate keyStoreCertificate;
+ private final String keyStorePassword;
+ private File keyStoreJksFile;
+ private File keyStorePemFile;
+
+ private final Boolean hostnameVerification;
+
+ /**
+ * Constructor is intentionally private, use the Builder class instead.
+ * @param tempDir the directory in which key store and trust store temp files will be written.
+ * @param trustStoreKeyPair the key pair for the trust store.
+ * @param trustStoreCertExpirationMillis the expiration of the trust store cert, in milliseconds from now.
+ * @param trustStorePassword the password to protect a JKS trust store (ignored for PEM trust stores).
+ * @param keyStoreKeyPair the key pair for the key store.
+ * @param keyStoreCertExpirationMillis the expiration of the key store cert, in milliseconds from now.
+ * @param keyStorePassword the password to protect the key store private key.
+ * @throws IOException
+ * @throws GeneralSecurityException
+ * @throws OperatorCreationException
+ */
+ private X509TestContext(File tempDir,
+ KeyPair trustStoreKeyPair,
+ long trustStoreCertExpirationMillis,
+ String trustStorePassword,
+ KeyPair keyStoreKeyPair,
+ long keyStoreCertExpirationMillis,
+ String keyStorePassword,
+ Boolean hostnameVerification) throws IOException, GeneralSecurityException, OperatorCreationException {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ throw new IllegalStateException("BC Security provider was not found");
+ }
+ this.tempDir = requireNonNull(tempDir);
+ if (!tempDir.isDirectory()) {
+ throw new IllegalArgumentException("Not a directory: " + tempDir);
+ }
+ this.trustStoreKeyPair = requireNonNull(trustStoreKeyPair);
+ this.trustStoreKeyType = keyPairToType(trustStoreKeyPair);
+ this.trustStoreCertExpirationMillis = trustStoreCertExpirationMillis;
+ this.trustStorePassword = requireNonNull(trustStorePassword);
+ this.keyStoreKeyPair = requireNonNull(keyStoreKeyPair);
+ this.keyStoreKeyType = keyPairToType(keyStoreKeyPair);
+ this.keyStoreCertExpirationMillis = keyStoreCertExpirationMillis;
+ this.keyStorePassword = requireNonNull(keyStorePassword);
+
+ X500NameBuilder caNameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+ caNameBuilder.addRDN(BCStyle.CN, MethodHandles.lookup().lookupClass().getCanonicalName() + " Root CA");
+ trustStoreCertificate = X509TestHelpers.newSelfSignedCACert(
+ caNameBuilder.build(),
+ trustStoreKeyPair,
+ trustStoreCertExpirationMillis);
+
+ X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+ nameBuilder.addRDN(BCStyle.CN, MethodHandles.lookup().lookupClass().getCanonicalName() + " Zookeeper Test");
+ keyStoreCertificate = X509TestHelpers.newCert(
+ trustStoreCertificate,
+ trustStoreKeyPair,
+ nameBuilder.build(),
+ keyStoreKeyPair.getPublic(),
+ keyStoreCertExpirationMillis);
+ trustStorePemFile = trustStoreJksFile = keyStorePemFile = keyStoreJksFile = null;
+
+ this.hostnameVerification = hostnameVerification;
+ }
+
+ /**
+ * Returns the X509KeyType of the given key pair.
+ * @param keyPair the key pair.
+ * @return <code>X509KeyType.RSA</code> if given an RSA key pair, and <code>X509KeyType.EC</code> otherwise.
+ */
+ private X509KeyType keyPairToType(KeyPair keyPair) {
+ if (keyPair.getPrivate().getAlgorithm().contains("RSA")) {
+ return X509KeyType.RSA;
+ } else {
+ return X509KeyType.EC;
+ }
+ }
+
+ public File getTempDir() {
+ return tempDir;
+ }
+
+ public X509KeyType getTrustStoreKeyType() {
+ return trustStoreKeyType;
+ }
+
+ public KeyPair getTrustStoreKeyPair() {
+ return trustStoreKeyPair;
+ }
+
+ public long getTrustStoreCertExpirationMillis() {
+ return trustStoreCertExpirationMillis;
+ }
+
+ public X509Certificate getTrustStoreCertificate() {
+ return trustStoreCertificate;
+ }
+
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ /**
+ * Returns the path to the trust store file in the given format (JKS or PEM). Note that the file is created lazily,
+ * the first time this method is called. The trust store file is temporary and will be deleted on exit.
+ * @param storeFileType the store file type (JKS or PEM).
+ * @return the path to the trust store file.
+ * @throws IOException if there is an error creating the trust store file.
+ */
+ public File getTrustStoreFile(KeyStoreFileType storeFileType) throws IOException {
+ switch (storeFileType) {
+ case JKS:
+ return getTrustStoreJksFile();
+ case PEM:
+ return getTrustStorePemFile();
+ default:
+ throw new IllegalArgumentException("Invalid trust store type: " + storeFileType + ", must be one of: " +
+ Arrays.toString(KeyStoreFileType.values()));
+ }
+ }
+
+ private File getTrustStoreJksFile() throws IOException {
+ if (trustStoreJksFile == null) {
+ try {
+ File trustStoreJksFile = File.createTempFile(
+ TRUST_STORE_PREFIX, KeyStoreFileType.JKS.getDefaultFileExtension(), tempDir);
+ trustStoreJksFile.deleteOnExit();
+ final FileOutputStream trustStoreOutputStream = new FileOutputStream(trustStoreJksFile);
+ try {
+ byte[] bytes = X509TestHelpers.certToJavaTrustStoreBytes(trustStoreCertificate, trustStorePassword);
+ trustStoreOutputStream.write(bytes);
+ trustStoreOutputStream.flush();
+ } finally {
+ trustStoreOutputStream.close();
+ }
+ this.trustStoreJksFile = trustStoreJksFile;
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ }
+ }
+ return trustStoreJksFile;
+ }
+
+ private File getTrustStorePemFile() throws IOException {
+ if (trustStorePemFile == null) {
+ File trustStorePemFile = File.createTempFile(
+ TRUST_STORE_PREFIX, KeyStoreFileType.PEM.getDefaultFileExtension(), tempDir);
+ trustStorePemFile.deleteOnExit();
+ FileUtils.writeStringToFile(
+ trustStorePemFile,
+ X509TestHelpers.pemEncodeX509Certificate(trustStoreCertificate),
+ StandardCharsets.US_ASCII,
+ false);
+ this.trustStorePemFile = trustStorePemFile;
+ }
+ return trustStorePemFile;
+ }
+
+ public X509KeyType getKeyStoreKeyType() {
+ return keyStoreKeyType;
+ }
+
+ public KeyPair getKeyStoreKeyPair() {
+ return keyStoreKeyPair;
+ }
+
+ public long getKeyStoreCertExpirationMillis() {
+ return keyStoreCertExpirationMillis;
+ }
+
+ public X509Certificate getKeyStoreCertificate() {
+ return keyStoreCertificate;
+ }
+
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public boolean isKeyStoreEncrypted() {
+ return keyStorePassword.length() > 0;
+ }
+
+ /**
+ * Returns the path to the key store file in the given format (JKS or 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).
+ * @return the path to the key store file.
+ * @throws IOException if there is an error creating the key store file.
+ */
+ public File getKeyStoreFile(KeyStoreFileType storeFileType) throws IOException {
+ switch (storeFileType) {
+ case JKS:
+ return getKeyStoreJksFile();
+ case PEM:
+ return getKeyStorePemFile();
+ default:
+ throw new IllegalArgumentException("Invalid key store type: " + storeFileType + ", must be one of: " +
+ Arrays.toString(KeyStoreFileType.values()));
+ }
+ }
+
+ private File getKeyStoreJksFile() throws IOException {
+ if (keyStoreJksFile == null) {
+ try {
+ File keyStoreJksFile = File.createTempFile(
+ KEY_STORE_PREFIX, KeyStoreFileType.JKS.getDefaultFileExtension(), tempDir);
+ keyStoreJksFile.deleteOnExit();
+ final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreJksFile);
+ try {
+ byte[] bytes = X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ keyStoreCertificate, keyStoreKeyPair.getPrivate(), keyStorePassword);
+ keyStoreOutputStream.write(bytes);
+ keyStoreOutputStream.flush();
+ } finally {
+ keyStoreOutputStream.close();
+ }
+ this.keyStoreJksFile = keyStoreJksFile;
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ }
+ }
+ return keyStoreJksFile;
+ }
+
+ private File getKeyStorePemFile() throws IOException {
+ if (keyStorePemFile == null) {
+ try {
+ File keyStorePemFile = File.createTempFile(
+ KEY_STORE_PREFIX, KeyStoreFileType.PEM.getDefaultFileExtension(), tempDir);
+ keyStorePemFile.deleteOnExit();
+ FileUtils.writeStringToFile(
+ keyStorePemFile,
+ X509TestHelpers.pemEncodeCertAndPrivateKey(
+ keyStoreCertificate, keyStoreKeyPair.getPrivate(), keyStorePassword),
+ StandardCharsets.US_ASCII,
+ false);
+ this.keyStorePemFile = keyStorePemFile;
+ } catch (OperatorCreationException e) {
+ throw new IOException(e);
+ }
+ }
+ return keyStorePemFile;
+ }
+
+ /**
+ * 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:
+ * <pre>
+ * X509TestContext testContext = ...; // create the test context
+ * X509Util x509Util = new QuorumX509Util();
+ * testContext.setSystemProperties(x509Util, KeyStoreFileType.JKS, KeyStoreFileType.JKS);
+ * // The returned context will use the key store and trust store created by the test context.
+ * SSLContext ctx = x509Util.getDefaultSSLContext();
+ * </pre>
+ * @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).
+ * @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 {
+ System.setProperty(
+ x509Util.getSslKeystoreLocationProperty(),
+ this.getKeyStoreFile(keyStoreFileType).getAbsolutePath());
+ System.setProperty(x509Util.getSslKeystorePasswdProperty(), this.getKeyStorePassword());
+ System.setProperty(x509Util.getSslKeystoreTypeProperty(), keyStoreFileType.getPropertyValue());
+ System.setProperty(
+ x509Util.getSslTruststoreLocationProperty(),
+ this.getTrustStoreFile(trustStoreFileType).getAbsolutePath());
+ System.setProperty(x509Util.getSslTruststorePasswdProperty(), this.getTrustStorePassword());
+ System.setProperty(x509Util.getSslTruststoreTypeProperty(), trustStoreFileType.getPropertyValue());
+ if (hostnameVerification != null) {
+ System.setProperty(x509Util.getSslHostnameVerificationEnabledProperty(), hostnameVerification.toString());
+ } else {
+ System.clearProperty(x509Util.getSslHostnameVerificationEnabledProperty());
+ }
+ }
+
+ /**
+ * Clears system properties set by
+ * {@link #setSystemProperties(X509Util, KeyStoreFileType, KeyStoreFileType)}.
+ * @param x509Util the X509Util to read property keys from.
+ */
+ public void clearSystemProperties(X509Util x509Util) {
+ System.clearProperty(x509Util.getSslKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslKeystoreTypeProperty());
+ System.clearProperty(x509Util.getSslTruststoreLocationProperty());
+ System.clearProperty(x509Util.getSslTruststorePasswdProperty());
+ System.clearProperty(x509Util.getSslTruststoreTypeProperty());
+ System.clearProperty(x509Util.getSslHostnameVerificationEnabledProperty());
+ }
+
+ /**
+ * Builder class, used for creating new instances of X509TestContext.
+ */
+ public static class Builder {
+ public static final long DEFAULT_CERT_EXPIRATION_MILLIS = 1000L * 60 * 60 * 24; // 1 day
+ private File tempDir;
+ private X509KeyType trustStoreKeyType;
+ private String trustStorePassword;
+ private long trustStoreCertExpirationMillis;
+ private X509KeyType keyStoreKeyType;
+ private String keyStorePassword;
+ private long keyStoreCertExpirationMillis;
+ private Boolean hostnameVerification;
+
+ /**
+ * Creates an empty builder.
+ */
+ public Builder() {
+ trustStoreKeyType = X509KeyType.EC;
+ trustStorePassword = "";
+ trustStoreCertExpirationMillis = DEFAULT_CERT_EXPIRATION_MILLIS;
+ keyStoreKeyType = X509KeyType.EC;
+ keyStorePassword = "";
+ keyStoreCertExpirationMillis = DEFAULT_CERT_EXPIRATION_MILLIS;
+ hostnameVerification = null;
+ }
+
+ /**
+ * Builds a new X509TestContext from this builder.
+ * @return a new X509TestContext
+ * @throws IOException
+ * @throws GeneralSecurityException
+ * @throws OperatorCreationException
+ */
+ public X509TestContext build() throws IOException, GeneralSecurityException, OperatorCreationException {
+ KeyPair trustStoreKeyPair = X509TestHelpers.generateKeyPair(trustStoreKeyType);
+ KeyPair keyStoreKeyPair = X509TestHelpers.generateKeyPair(keyStoreKeyType);
+ return new X509TestContext(
+ tempDir,
+ trustStoreKeyPair,
+ trustStoreCertExpirationMillis,
+ trustStorePassword,
+ keyStoreKeyPair,
+ keyStoreCertExpirationMillis,
+ keyStorePassword,
+ hostnameVerification);
+ }
+
+ /**
+ * Sets the temporary directory. Certificate and private key files will be created in this directory.
+ * @param tempDir the temp directory.
+ * @return this Builder.
+ */
+ public Builder setTempDir(File tempDir) {
+ this.tempDir = tempDir;
+ return this;
+ }
+
+ /**
+ * Sets the trust store key type. The CA key generated for the test context will be of this type.
+ * @param keyType the key type.
+ * @return this Builder.
+ */
+ public Builder setTrustStoreKeyType(X509KeyType keyType) {
+ trustStoreKeyType = keyType;
+ return this;
+ }
+
+ /**
+ * Sets the trust store password. Ignored for PEM trust stores, JKS trust stores will be encrypted with this
+ * password.
+ * @param password the password.
+ * @return this Builder.
+ */
+ public Builder setTrustStorePassword(String password) {
+ trustStorePassword = password;
+ return this;
+ }
+
+ /**
+ * Sets the trust store certificate's expiration, in milliseconds from when <code>build()</code> is called.
+ * @param expirationMillis expiration in milliseconds.
+ * @return this Builder.
+ */
+ public Builder setTrustStoreCertExpirationMillis(long expirationMillis) {
+ trustStoreCertExpirationMillis = expirationMillis;
+ return this;
+ }
+
+ /**
+ * Sets the key store key type. The private key generated for the test context will be of this type.
+ * @param keyType the key type.
+ * @return this Builder.
+ */
+ public Builder setKeyStoreKeyType(X509KeyType keyType) {
+ keyStoreKeyType = keyType;
+ return this;
+ }
+
+ /**
+ * Sets the key store password. The private key (PEM, JKS) and certificate (JKS only) will be encrypted with
+ * this password.
+ * @param password the password.
+ * @return this Builder.
+ */
+ public Builder setKeyStorePassword(String password) {
+ keyStorePassword = password;
+ return this;
+ }
+
+ /**
+ * Sets the key store certificate's expiration, in milliseconds from when <code>build()</code> is called.
+ * @param expirationMillis expiration in milliseconds.
+ * @return this Builder.
+ */
+ public Builder setKeyStoreCertExpirationMillis(long expirationMillis) {
+ keyStoreCertExpirationMillis = expirationMillis;
+ return this;
+ }
+
+ /**
+ * Sets the hostname verification behavior. If null is provided, reverts the behavior to the default, otherwise
+ * explicitly sets hostname verification to true or false.
+ * @param hostnameVerification new value for the hostname verification setting.
+ * @return this Builder.
+ */
+ public Builder setHostnameVerification(Boolean hostnameVerification) {
+ this.hostnameVerification = hostnameVerification;
+ return this;
+ }
+ }
+
+ /**
+ * Returns a new default-constructed Builder.
+ * @return a new Builder.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..59b7634
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
@@ -0,0 +1,402 @@
+/**
+ * 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.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.operator.bc.BcContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcECContentSignerBuilder;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+/**
+ * This class contains helper methods for creating X509 certificates and key pairs, and for serializing them
+ * to JKS or PEM files.
+ */
+public class X509TestHelpers {
+ private static final Logger LOG = LoggerFactory.getLogger(X509TestHelpers.class);
+
+ private static final SecureRandom PRNG = new SecureRandom();
+ private static final int DEFAULT_RSA_KEY_SIZE_BITS = 2048;
+ private static final BigInteger DEFAULT_RSA_PUB_EXPONENT = RSAKeyGenParameterSpec.F4; // 65537
+ private static final String DEFAULT_ELLIPTIC_CURVE_NAME = "secp256r1";
+ // Per RFC 5280 section 4.1.2.2, X509 certificates can use up to 20 bytes == 160 bits for serial numbers.
+ private static final int SERIAL_NUMBER_MAX_BITS = 20 * Byte.SIZE;
+
+ /**
+ * Uses the private key of the given key pair to create a self-signed CA certificate with the public half of the
+ * key pair and the given subject and expiration. The issuer of the new cert will be equal to the subject.
+ * Returns the new certificate.
+ * The returned certificate should be used as the trust store. The private key of the input key pair should be
+ * used to sign certificates that are used by test peers to establish TLS connections to each other.
+ * @param subject the subject of the new certificate being created.
+ * @param keyPair the key pair to use. The public key will be embedded in the new certificate, and the private key
+ * will be used to self-sign the certificate.
+ * @param expirationMillis expiration of the new certificate, in milliseconds from now.
+ * @return a new self-signed CA certificate.
+ * @throws IOException
+ * @throws OperatorCreationException
+ * @throws GeneralSecurityException
+ */
+ public static X509Certificate newSelfSignedCACert(
+ X500Name subject,
+ KeyPair keyPair,
+ long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
+ Date now = new Date();
+ X509v3CertificateBuilder builder = initCertBuilder(
+ subject, // for self-signed certs, issuer == subject
+ now,
+ new Date(now.getTime() + expirationMillis),
+ subject,
+ keyPair.getPublic());
+ builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); // is a CA
+ builder.addExtension(
+ Extension.keyUsage,
+ true,
+ new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
+ return buildAndSignCertificate(keyPair.getPrivate(), builder);
+ }
+
+ /**
+ * Using the private key of the given CA key pair and the Subject of the given CA cert as the Issuer, issues a
+ * new cert with the given subject and public key. The returned certificate, combined with the private key half
+ * of the <code>certPublicKey</code>, should be used as the key store.
+ * @param caCert the certificate of the CA that's doing the signing.
+ * @param caKeyPair the key pair of the CA. The private key will be used to sign. The public key must match the
+ * public key in the <code>caCert</code>.
+ * @param certSubject the subject field of the new cert being issued.
+ * @param certPublicKey the public key of the new cert being issued.
+ * @param expirationMillis the expiration of the cert being issued, in milliseconds from now.
+ * @return a new certificate signed by the CA's private key.
+ * @throws IOException
+ * @throws OperatorCreationException
+ * @throws GeneralSecurityException
+ */
+ public static X509Certificate newCert(
+ X509Certificate caCert,
+ KeyPair caKeyPair,
+ X500Name certSubject,
+ PublicKey certPublicKey,
+ long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
+ if (!caKeyPair.getPublic().equals(caCert.getPublicKey())) {
+ throw new IllegalArgumentException("CA private key does not match the public key in the CA cert");
+ }
+ Date now = new Date();
+ X509v3CertificateBuilder builder = initCertBuilder(
+ new X500Name(caCert.getIssuerDN().getName()),
+ now,
+ new Date(now.getTime() + expirationMillis),
+ certSubject,
+ certPublicKey);
+ builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); // not a CA
+ builder.addExtension(
+ Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement));
+ builder.addExtension(
+ Extension.extendedKeyUsage,
+ true,
+ new ExtendedKeyUsage(new KeyPurposeId[] { KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth }));
+
+ builder.addExtension(
+ Extension.subjectAlternativeName,
+ false,
+ getLocalhostSubjectAltNames());
+ return buildAndSignCertificate(caKeyPair.getPrivate(), builder);
+ }
+
+ /**
+ * Returns subject alternative names for "localhost".
+ * @return the subject alternative names for "localhost".
+ */
+ private static GeneralNames getLocalhostSubjectAltNames() throws UnknownHostException {
+ InetAddress[] localAddresses = InetAddress.getAllByName("localhost");
+ GeneralName[] generalNames = new GeneralName[localAddresses.length + 1];
+ for (int i = 0; i < localAddresses.length; i++) {
+ generalNames[i] = new GeneralName(GeneralName.iPAddress, new DEROctetString(localAddresses[i].getAddress()));
+ }
+ generalNames[generalNames.length - 1] = new GeneralName(GeneralName.dNSName, new DERIA5String("localhost"));
+ return new GeneralNames(generalNames);
+ }
+
+ /**
+ * Helper method for newSelfSignedCACert() and newCert(). Initializes a X509v3CertificateBuilder with
+ * logic that's common to both methods.
+ * @param issuer Issuer field of the new cert.
+ * @param notBefore date before which the new cert is not valid.
+ * @param notAfter date after which the new cert is not valid.
+ * @param subject Subject field of the new cert.
+ * @param subjectPublicKey public key to store in the new cert.
+ * @return a X509v3CertificateBuilder that can be further customized to finish creating the new cert.
+ */
+ private static X509v3CertificateBuilder initCertBuilder(
+ X500Name issuer,
+ Date notBefore,
+ Date notAfter,
+ X500Name subject,
+ PublicKey subjectPublicKey) {
+ return new X509v3CertificateBuilder(
+ issuer,
+ new BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG),
+ notBefore,
+ notAfter,
+ subject,
+ SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
+ }
+
+ /**
+ * Signs the certificate being built by the given builder using the given private key and returns the certificate.
+ * @param privateKey the private key to sign the certificate with.
+ * @param builder the cert builder that contains the certificate data.
+ * @return the signed certificate.
+ * @throws IOException
+ * @throws OperatorCreationException
+ * @throws CertificateException
+ */
+ private static X509Certificate buildAndSignCertificate(
+ PrivateKey privateKey,
+ X509v3CertificateBuilder builder) throws IOException, OperatorCreationException, CertificateException {
+ BcContentSignerBuilder signerBuilder;
+ if (privateKey.getAlgorithm().contains("RSA")) { // a little hacky way to detect key type, but it works
+ AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find(
+ "SHA256WithRSAEncryption");
+ AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(signatureAlgorithm);
+ signerBuilder = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm);
+ } else { // if not RSA, assume EC
+ AlgorithmIdentifier signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find(
+ "SHA256withECDSA");
+ AlgorithmIdentifier digestAlgorithm = new DefaultDigestAlgorithmIdentifierFinder().find(signatureAlgorithm);
+ signerBuilder = new BcECContentSignerBuilder(signatureAlgorithm, digestAlgorithm);
+ }
+ AsymmetricKeyParameter privateKeyParam = PrivateKeyFactory.createKey(privateKey.getEncoded());
+ ContentSigner signer = signerBuilder.build(privateKeyParam);
+ return toX509Cert(builder.build(signer));
+ }
+
+ /**
+ * Generates a new asymmetric key pair of the given type.
+ * @param keyType the type of key pair to generate.
+ * @return the new key pair.
+ * @throws GeneralSecurityException if your java crypto providers are messed up.
+ */
+ public static KeyPair generateKeyPair(X509KeyType keyType) throws GeneralSecurityException {
+ switch (keyType) {
+ case RSA:
+ return generateRSAKeyPair();
+ case EC:
+ return generateECKeyPair();
+ default:
+ throw new IllegalArgumentException("Invalid X509KeyType");
+ }
+ }
+
+ /**
+ * Generates an RSA key pair with a 2048-bit private key and F4 (65537) as the public exponent.
+ * @return the key pair.
+ */
+ public static KeyPair generateRSAKeyPair() throws GeneralSecurityException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ RSAKeyGenParameterSpec keyGenSpec = new RSAKeyGenParameterSpec(
+ DEFAULT_RSA_KEY_SIZE_BITS, DEFAULT_RSA_PUB_EXPONENT);
+ keyGen.initialize(keyGenSpec, PRNG);
+ return keyGen.generateKeyPair();
+ }
+
+ /**
+ * Generates an elliptic curve key pair using the "secp256r1" aka "prime256v1" aka "NIST P-256" curve.
+ * @return the key pair.
+ */
+ public static KeyPair generateECKeyPair() throws GeneralSecurityException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ keyGen.initialize(new ECGenParameterSpec(DEFAULT_ELLIPTIC_CURVE_NAME), PRNG);
+ return keyGen.generateKeyPair();
+ }
+
+ /**
+ * PEM-encodes the given X509 certificate and private key (compatible with OpenSSL), optionally protecting the
+ * private key with a password. Concatenates them both and returns the result as a single string.
+ * This creates the PEM encoding of a key store.
+ * @param cert the X509 certificate to PEM-encode.
+ * @param privateKey the private key to PEM-encode.
+ * @param keyPassword an optional key password. If empty or null, the private key will not be encrypted.
+ * @return a String containing the PEM encodings of the certificate and private key.
+ * @throws IOException if converting the certificate or private key to PEM format fails.
+ * @throws OperatorCreationException if constructing the encryptor from the given password fails.
+ */
+ public static String pemEncodeCertAndPrivateKey(
+ X509Certificate cert,
+ PrivateKey privateKey,
+ String keyPassword) throws IOException, OperatorCreationException {
+ return pemEncodeX509Certificate(cert) +
+ "\n" +
+ pemEncodePrivateKey(privateKey, keyPassword);
+ }
+
+ /**
+ * PEM-encodes the given private key (compatible with OpenSSL), optionally protecting it with a password, and
+ * returns the result as a String.
+ * @param key the private key.
+ * @param password an optional key password. If empty or null, the private key will not be encrypted.
+ * @return a String containing the PEM encoding of the private key.
+ * @throws IOException if converting the key to PEM format fails.
+ * @throws OperatorCreationException if constructing the encryptor from the given password fails.
+ */
+ public static String pemEncodePrivateKey(
+ PrivateKey key,
+ String password) throws IOException, OperatorCreationException {
+ StringWriter stringWriter = new StringWriter();
+ JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
+ OutputEncryptor encryptor = null;
+ if (password != null && password.length() > 0) {
+ encryptor = new JceOpenSSLPKCS8EncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC)
+ .setProvider(BouncyCastleProvider.PROVIDER_NAME)
+ .setRandom(PRNG)
+ .setPasssword(password.toCharArray())
+ .build();
+ }
+ pemWriter.writeObject(new JcaPKCS8Generator(key, encryptor));
+ pemWriter.close();
+ return stringWriter.toString();
+ }
+
+ /**
+ * PEM-encodes the given X509 certificate (compatible with OpenSSL) and returns the result as a String.
+ * @param cert the certificate.
+ * @return a String containing the PEM encoding of the certificate.
+ * @throws IOException if converting the certificate to PEM format fails.
+ */
+ public static String pemEncodeX509Certificate(X509Certificate cert) throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
+ pemWriter.writeObject(cert);
+ pemWriter.close();
+ return stringWriter.toString();
+ }
+
+ /**
+ * Encodes the given X509Certificate as a JKS 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 JKS trust store.
+ * @throws IOException
+ * @throws GeneralSecurityException
+ */
+ public static byte[] certToJavaTrustStoreBytes(
+ X509Certificate cert,
+ String keyPassword) throws IOException, GeneralSecurityException {
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
+ trustStore.load(null, keyPasswordChars);
+ trustStore.setCertificateEntry(cert.getSubjectDN().toString(), cert);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ trustStore.store(outputStream, keyPasswordChars);
+ outputStream.flush();
+ byte[] result = outputStream.toByteArray();
+ outputStream.close();
+ return result;
+ }
+
+ /**
+ * Encodes the given X509Certificate and private key as a JKS 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 JKS key store.
+ * @throws IOException
+ * @throws GeneralSecurityException
+ */
+ public static byte[] certAndPrivateKeyToJavaKeyStoreBytes(
+ X509Certificate cert,
+ PrivateKey privateKey,
+ String keyPassword) throws IOException, GeneralSecurityException {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
+ keyStore.load(null, keyPasswordChars);
+ keyStore.setKeyEntry(
+ "key",
+ privateKey,
+ keyPasswordChars,
+ new Certificate[] { cert });
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ keyStore.store(outputStream, keyPasswordChars);
+ outputStream.flush();
+ byte[] result = outputStream.toByteArray();
+ outputStream.close();
+ return result;
+ }
+
+ /**
+ * Convenience method to convert a bouncycastle X509CertificateHolder to a java X509Certificate.
+ * @param certHolder a bouncycastle X509CertificateHolder.
+ * @return a java X509Certificate
+ * @throws CertificateException if the conversion fails.
+ */
+ public static X509Certificate toX509Cert(X509CertificateHolder certHolder) throws CertificateException {
+ return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certHolder);
+ }
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
index e726caa..6b343c3 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
@@ -17,150 +17,78 @@
*/
package org.apache.zookeeper.common;
+import java.security.Security;
+import java.util.Collection;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
import org.apache.zookeeper.PortAssignment;
-import org.apache.zookeeper.ZKTestCase;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.server.ServerCnxnFactory;
-import org.bouncycastle.asn1.x500.X500NameBuilder;
-import org.bouncycastle.asn1.x500.style.BCStyle;
-import org.bouncycastle.asn1.x509.BasicConstraints;
-import org.bouncycastle.asn1.x509.Extension;
-import org.bouncycastle.asn1.x509.KeyUsage;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-import java.io.FileOutputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Random;
-
-import static org.apache.zookeeper.test.ClientBase.createTmpDir;
-
-public class X509UtilTest extends ZKTestCase {
-
- private static final char[] PASSWORD = "password".toCharArray();
- private X509Certificate rootCertificate;
-
- private String truststorePath;
- private String keystorePath;
- private static KeyPair rootKeyPair;
-
+@RunWith(Parameterized.class)
+public class X509UtilTest extends BaseX509ParameterizedTestCase {
private X509Util x509Util;
- private String[] customCipherSuites = new String[]{"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"};
-
- @BeforeClass
- public static void createKeyPair() throws Exception {
- Security.addProvider(new BouncyCastleProvider());
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
- keyPairGenerator.initialize(4096);
- rootKeyPair = keyPairGenerator.genKeyPair();
+ private static final String[] customCipherSuites = new String[]{
+ "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
+ "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"};
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> params() {
+ return BaseX509ParameterizedTestCase.defaultParams();
}
- @AfterClass
- public static void removeBouncyCastleProvider() throws Exception {
- Security.removeProvider("BC");
+ public X509UtilTest(
+ X509KeyType caKeyType,
+ X509KeyType certKeyType,
+ String keyPassword,
+ 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);
+ }
+ });
}
@Before
public void setUp() throws Exception {
- rootCertificate = createSelfSignedCertifcate(rootKeyPair);
-
- String tmpDir = createTmpDir().getAbsolutePath();
- truststorePath = tmpDir + "/truststore.jks";
- keystorePath = tmpDir + "/keystore.jks";
-
- x509Util = new ClientX509Util();
-
- writeKeystore(rootCertificate, rootKeyPair, keystorePath);
-
+ x509TestContext.setSystemProperties(new ClientX509Util(), KeyStoreFileType.JKS, KeyStoreFileType.JKS);
System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory");
System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty");
- System.setProperty(x509Util.getSslKeystoreLocationProperty(), keystorePath);
- System.setProperty(x509Util.getSslKeystorePasswdProperty(), new String(PASSWORD));
- System.setProperty(x509Util.getSslTruststoreLocationProperty(), truststorePath);
- System.setProperty(x509Util.getSslTruststorePasswdProperty(), new String(PASSWORD));
- System.setProperty(x509Util.getSslHostnameVerificationEnabledProperty(), "false");
-
- writeTrustStore(PASSWORD);
- }
-
- private void writeKeystore(X509Certificate certificate, KeyPair keyPair, String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("JKS");
- keyStore.load(null, PASSWORD);
- keyStore.setKeyEntry("alias", keyPair.getPrivate(), PASSWORD, new Certificate[] { certificate });
- FileOutputStream outputStream = new FileOutputStream(path);
- keyStore.store(outputStream, PASSWORD);
- outputStream.flush();
- outputStream.close();
- }
-
- private void writeTrustStore(char[] password) throws Exception {
- KeyStore trustStore = KeyStore.getInstance("JKS");
- trustStore.load(null, password);
- trustStore.setCertificateEntry(rootCertificate.getSubjectDN().toString(), rootCertificate);
- FileOutputStream outputStream = new FileOutputStream(truststorePath);
- if (password == null) {
- trustStore.store(outputStream, new char[0]);
- } else {
- trustStore.store(outputStream, password);
- }
- outputStream.flush();
- outputStream.close();
- }
-
- private X509Certificate createSelfSignedCertifcate(KeyPair keyPair) throws Exception {
- X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
- nameBuilder.addRDN(BCStyle.CN, "localhost");
- Date notBefore = new Date();
- Calendar cal = Calendar.getInstance();
- cal.setTime(notBefore);
- cal.add(Calendar.YEAR, 1);
- Date notAfter = cal.getTime();
- BigInteger serialNumber = new BigInteger(128, new Random());
-
- X509v3CertificateBuilder certificateBuilder =
- new JcaX509v3CertificateBuilder(nameBuilder.build(), serialNumber, notBefore, notAfter, nameBuilder.build(), keyPair.getPublic())
- .addExtension(Extension.basicConstraints, true, new BasicConstraints(0))
- .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
-
- ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate());
-
- return new JcaX509CertificateConverter().getCertificate(certificateBuilder.build(contentSigner));
+ x509Util = new ClientX509Util();
}
@After
- public void cleanUp() throws Exception {
- System.clearProperty(x509Util.getSslKeystoreLocationProperty());
- System.clearProperty(x509Util.getSslKeystorePasswdProperty());
- System.clearProperty(x509Util.getSslTruststoreLocationProperty());
- System.clearProperty(x509Util.getSslTruststorePasswdProperty());
- System.clearProperty(x509Util.getSslHostnameVerificationEnabledProperty());
+ public void cleanUp() {
+ x509TestContext.clearSystemProperties(x509Util);
System.clearProperty(x509Util.getSslOcspEnabledProperty());
System.clearProperty(x509Util.getSslCrlEnabledProperty());
System.clearProperty(x509Util.getCipherSuitesProperty());
+ System.clearProperty(x509Util.getSslProtocolProperty());
System.clearProperty("com.sun.net.ssl.checkRevocation");
System.clearProperty("com.sun.security.enableCRLDP");
- Security.setProperty("com.sun.security.enableCRLDP", "false");
+ Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
+ Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString());
+ System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
+ System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET);
}
@Test(timeout = 5000)
@@ -178,13 +106,6 @@ public class X509UtilTest extends ZKTestCase {
}
@Test(timeout = 5000)
- public void testCreateSSLContextWithoutTrustStorePassword() throws Exception {
- writeTrustStore(null);
- System.clearProperty(x509Util.getSslTruststorePasswdProperty());
- x509Util.getDefaultSSLContext();
- }
-
- @Test(timeout = 5000, expected = X509Exception.SSLContextException.class)
public void testCreateSSLContextWithoutKeyStoreLocation() throws Exception {
System.clearProperty(x509Util.getSslKeystoreLocationProperty());
x509Util.getDefaultSSLContext();
@@ -192,6 +113,9 @@ public class X509UtilTest extends ZKTestCase {
@Test(timeout = 5000, expected = X509Exception.SSLContextException.class)
public void testCreateSSLContextWithoutKeyStorePassword() throws Exception {
+ if (!x509TestContext.isKeyStoreEncrypted()) {
+ throw new X509Exception.SSLContextException("");
+ }
System.clearProperty(x509Util.getSslKeystorePasswdProperty());
x509Util.getDefaultSSLContext();
}
@@ -256,6 +180,182 @@ public class X509UtilTest extends ZKTestCase {
Assert.assertTrue(sslServerSocket.getNeedClientAuth());
}
+ @Test
+ public void testLoadPEMKeyStore() throws Exception {
+ // Make sure we can instantiate a key manager from the PEM file on disk
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ KeyStoreFileType.PEM.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadPEMKeyStoreNullPassword() throws Exception {
+ if (!x509TestContext.getKeyStorePassword().isEmpty()) {
+ return;
+ }
+ // Make sure that empty password and null password are treated the same
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ null,
+ KeyStoreFileType.PEM.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadPEMKeyStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a key manager from the PEM file on disk
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ null /* null StoreFileType means 'autodetect from file extension' */);
+ }
+
+ @Test(expected = X509Exception.KeyManagerException.class)
+ public void testLoadPEMKeyStoreWithWrongPassword() throws Exception {
+ // Attempting to load with the wrong key password should fail
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ "wrong password", // intentionally use the wrong password
+ KeyStoreFileType.PEM.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadPEMTrustStore() throws Exception {
+ // Make sure we can instantiate a trust manager from the PEM file on disk
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(),
+ KeyStoreFileType.PEM.getPropertyValue(),
+ false,
+ false,
+ true,
+ true);
+ }
+
+ @Test
+ public void testLoadPEMTrustStoreNullPassword() throws Exception {
+ if (!x509TestContext.getTrustStorePassword().isEmpty()) {
+ return;
+ }
+ // Make sure that empty password and null password are treated the same
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ null,
+ KeyStoreFileType.PEM.getPropertyValue(),
+ false,
+ false,
+ true,
+ true);
+
+ }
+
+ @Test
+ public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a trust manager from the PEM file on disk
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(),
+ null, // null StoreFileType means 'autodetect from file extension'
+ false,
+ false,
+ true,
+ true);
+ }
+
+ @Test
+ public void testLoadJKSKeyStore() throws Exception {
+ // Make sure we can instantiate a key manager from the JKS file on disk
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ KeyStoreFileType.JKS.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadJKSKeyStoreNullPassword() throws Exception {
+ if (!x509TestContext.getKeyStorePassword().isEmpty()) {
+ return;
+ }
+ // Make sure that empty password and null password are treated the same
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ null,
+ KeyStoreFileType.JKS.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a key manager from the JKS file on disk
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ null /* null StoreFileType means 'autodetect from file extension' */);
+ }
+
+ @Test(expected = X509Exception.KeyManagerException.class)
+ public void testLoadJKSKeyStoreWithWrongPassword() throws Exception {
+ // Attempting to load with the wrong key password should fail
+ X509KeyManager km = X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ "wrong password",
+ KeyStoreFileType.JKS.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadJKSTrustStore() throws Exception {
+ // Make sure we can instantiate a trust manager from the JKS file on disk
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(),
+ KeyStoreFileType.JKS.getPropertyValue(),
+ true,
+ true,
+ true,
+ true);
+ }
+
+ @Test
+ public void testLoadJKSTrustStoreNullPassword() throws Exception {
+ if (!x509TestContext.getTrustStorePassword().isEmpty()) {
+ return;
+ }
+ // Make sure that empty password and null password are treated the same
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ null,
+ KeyStoreFileType.JKS.getPropertyValue(),
+ false,
+ false,
+ true,
+ true);
+ }
+
+ @Test
+ public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a trust manager from the JKS file on disk
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(),
+ null, // null StoreFileType means 'autodetect from file extension'
+ true,
+ true,
+ true,
+ true);
+ }
+
+ @Test(expected = X509Exception.TrustManagerException.class)
+ public void testLoadJKSTrustStoreWithWrongPassword() throws Exception {
+ // Attempting to load with the wrong key password should fail
+ X509TrustManager tm = X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
+ "wrong password",
+ KeyStoreFileType.JKS.getPropertyValue(),
+ true,
+ true,
+ true,
+ true);
+ }
+
// Warning: this will reset the x509Util
private void setCustomCipherSuites() {
System.setProperty(x509Util.getCipherSuitesProperty(), customCipherSuites[0] + "," + customCipherSuites[1]);
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/03286f29/zookeeper-server/src/test/java/org/apache/zookeeper/util/PemReaderTest.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/util/PemReaderTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/util/PemReaderTest.java
new file mode 100644
index 0000000..d0d3dc7
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/util/PemReaderTest.java
@@ -0,0 +1,137 @@
+/**
+ * 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.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.zookeeper.common.BaseX509ParameterizedTestCase;
+import org.apache.zookeeper.common.KeyStoreFileType;
+import org.apache.zookeeper.common.X509KeyType;
+import org.apache.zookeeper.common.X509TestContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PemReaderTest extends BaseX509ParameterizedTestCase {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> params() {
+ return BaseX509ParameterizedTestCase.defaultParams();
+ }
+
+ public PemReaderTest(
+ X509KeyType caKeyType,
+ X509KeyType certKeyType,
+ String keyPassword,
+ 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 testLoadPrivateKeyFromKeyStore() throws IOException, GeneralSecurityException {
+ Optional<String> optPassword = x509TestContext.getKeyStorePassword().length() > 0
+ ? Optional.of(x509TestContext.getKeyStorePassword())
+ : Optional.empty();
+ PrivateKey privateKey = PemReader.loadPrivateKey(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM), optPassword);
+ Assert.assertEquals(x509TestContext.getKeyStoreKeyPair().getPrivate(), privateKey);
+ }
+
+ // Try to load a password-protected private key without providing a password
+ @Test(expected = GeneralSecurityException.class)
+ public void testLoadEncryptedPrivateKeyFromKeyStoreWithoutPassword() throws GeneralSecurityException, IOException {
+ if (!x509TestContext.isKeyStoreEncrypted()) {
+ throw new GeneralSecurityException(); // this case is not tested so throw the expected exception
+ }
+ PemReader.loadPrivateKey(x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM), Optional.empty());
+ }
+
+ // Try to load a password-protected private key with the wrong password
+ @Test(expected = GeneralSecurityException.class)
+ public void testLoadEncryptedPrivateKeyFromKeyStoreWithWrongPassword() throws GeneralSecurityException, IOException {
+ if (!x509TestContext.isKeyStoreEncrypted()) {
+ throw new GeneralSecurityException(); // this case is not tested so throw the expected exception
+ }
+ PemReader.loadPrivateKey(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM),
+ Optional.of("wrong password"));
+ }
+
+ // Try to load a non-protected private key while providing a password
+ @Test(expected = IOException.class)
+ public void testLoadUnencryptedPrivateKeyFromKeyStoreWithWrongPassword() throws GeneralSecurityException, IOException {
+ if (x509TestContext.isKeyStoreEncrypted()) {
+ throw new IOException();
+ }
+ PemReader.loadPrivateKey(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM),
+ Optional.of("wrong password"));
+ }
+
+ // Expect this to fail, the trust store does not contain a private key
+ @Test(expected = KeyStoreException.class)
+ public void testLoadPrivateKeyFromTrustStore() throws IOException, GeneralSecurityException {
+ PemReader.loadPrivateKey(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM), Optional.empty());
+ }
+
+ // Expect this to fail, the trust store does not contain a private key
+ @Test(expected = KeyStoreException.class)
+ public void testLoadPrivateKeyFromTrustStoreWithPassword() throws IOException, GeneralSecurityException {
+ PemReader.loadPrivateKey(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM), Optional.of("foobar"));
+ }
+
+ @Test
+ public void testLoadCertificateFromKeyStore() throws IOException, GeneralSecurityException {
+ List<X509Certificate> certs = PemReader.readCertificateChain(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM));
+ Assert.assertEquals(1, certs.size());
+ Assert.assertEquals(x509TestContext.getKeyStoreCertificate(), certs.get(0));
+ }
+
+ @Test
+ public void testLoadCertificateFromTrustStore() throws IOException, GeneralSecurityException {
+ List<X509Certificate> certs = PemReader.readCertificateChain(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM));
+ Assert.assertEquals(1, certs.size());
+ Assert.assertEquals(x509TestContext.getTrustStoreCertificate(), certs.get(0));
+ }
+}
[2/2] zookeeper git commit: ZOOKEEPER-3173: Quorum TLS - support PEM
trust/key stores
Posted by an...@apache.org.
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: andor@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;
+}