You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by bb...@apache.org on 2022/09/06 19:43:47 UTC
[hbase] branch master updated: HBASE-27346 Autodetect key/truststore file type from file extension (#4757)
This is an automated email from the ASF dual-hosted git repository.
bbeaudreault pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/master by this push:
new d2cc840edf4 HBASE-27346 Autodetect key/truststore file type from file extension (#4757)
d2cc840edf4 is described below
commit d2cc840edf4888d090e6777a621fd976e7abccd7
Author: Andor Molnár <an...@cloudera.com>
AuthorDate: Tue Sep 6 21:43:41 2022 +0200
HBASE-27346 Autodetect key/truststore file type from file extension (#4757)
Signed-off-by: Duo Zhang <zh...@apache.org>
Signed-off-by: Bryan Beaudreault <bb...@apache.org>
---
.../hbase/io/crypto/tls/BCFKSFileLoader.java | 42 ++++
.../hbase/io/crypto/tls/FileKeyStoreLoader.java | 80 ++++++++
.../tls/FileKeyStoreLoaderBuilderProvider.java | 54 +++++
.../hadoop/hbase/io/crypto/tls/JKSFileLoader.java | 41 ++++
.../hadoop/hbase/io/crypto/tls/KeyStoreLoader.java | 53 +++++
.../hadoop/hbase/io/crypto/tls/PEMFileLoader.java | 56 ++++++
.../hbase/io/crypto/tls/PKCS12FileLoader.java | 42 ++++
.../hadoop/hbase/io/crypto/tls/PemReader.java | 218 +++++++++++++++++++++
.../crypto/tls/StandardTypeFileKeyStoreLoader.java | 79 ++++++++
.../hadoop/hbase/io/crypto/tls/X509Util.java | 29 +--
.../crypto/tls/AbstractTestX509Parameterized.java | 128 ++++++++++++
.../hbase/io/crypto/tls/TestBCFKSFileLoader.java | 118 +++++++++++
.../tls/TestFileKeyStoreLoaderBuilderProvider.java | 66 +++++++
.../hbase/io/crypto/tls/TestJKSFileLoader.java | 117 +++++++++++
.../hbase/io/crypto/tls/TestKeyStoreFileType.java | 120 ++++++++++++
.../hbase/io/crypto/tls/TestPEMFileLoader.java | 112 +++++++++++
.../hbase/io/crypto/tls/TestPKCS12FileLoader.java | 117 +++++++++++
.../hadoop/hbase/io/crypto/tls/TestX509Util.java | 196 +++++++++---------
.../hbase/io/crypto/tls/X509TestContext.java | 52 ++++-
.../io/crypto/tls/X509TestContextProvider.java | 17 ++
.../hbase/io/crypto/tls/X509TestHelpers.java | 36 +++-
21 files changed, 1647 insertions(+), 126 deletions(-)
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/BCFKSFileLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/BCFKSFileLoader.java
new file mode 100644
index 00000000000..cefa4135c90
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/BCFKSFileLoader.java
@@ -0,0 +1,42 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+/**
+ * Implementation of {@link FileKeyStoreLoader} that loads from BCKFS files.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/BCFKSFileLoader.java">Base
+ * revision</a>
+ */
+final class BCFKSFileLoader extends StandardTypeFileKeyStoreLoader {
+ private BCFKSFileLoader(String keyStorePath, String trustStorePath, char[] keyStorePassword,
+ char[] trustStorePassword) {
+ super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword,
+ SupportedStandardKeyFormat.BCFKS);
+ }
+
+ static class Builder extends FileKeyStoreLoader.Builder<BCFKSFileLoader> {
+ @Override
+ BCFKSFileLoader build() {
+ return new BCFKSFileLoader(keyStorePath, trustStorePath, keyStorePassword,
+ trustStorePassword);
+ }
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoader.java
new file mode 100644
index 00000000000..3a1740b4faf
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoader.java
@@ -0,0 +1,80 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.util.Objects;
+
+/**
+ * Base class for instances of {@link KeyStoreLoader} which load the key/trust stores from files on
+ * a filesystem.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoader.java">Base
+ * revision</a>
+ */
+abstract class FileKeyStoreLoader implements KeyStoreLoader {
+ final String keyStorePath;
+ final String trustStorePath;
+ final char[] keyStorePassword;
+ final char[] trustStorePassword;
+
+ FileKeyStoreLoader(String keyStorePath, String trustStorePath, char[] keyStorePassword,
+ char[] 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;
+ char[] keyStorePassword;
+ char[] 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(char[] keyStorePassword) {
+ this.keyStorePassword = Objects.requireNonNull(keyStorePassword);
+ return this;
+ }
+
+ Builder<T> setTrustStorePassword(char[] trustStorePassword) {
+ this.trustStorePassword = Objects.requireNonNull(trustStorePassword);
+ return this;
+ }
+
+ abstract T build();
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoaderBuilderProvider.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoaderBuilderProvider.java
new file mode 100644
index 00000000000..432c8a06d11
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/FileKeyStoreLoaderBuilderProvider.java
@@ -0,0 +1,54 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.util.Objects;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java">Base
+ * revision</a>
+ */
+final 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();
+ case PKCS12:
+ return new PKCS12FileLoader.Builder();
+ case BCFKS:
+ return new BCFKSFileLoader.Builder();
+ default:
+ throw new AssertionError("Unexpected StoreFileType: " + type.name());
+ }
+ }
+
+ private FileKeyStoreLoaderBuilderProvider() {
+ // disabled
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/JKSFileLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/JKSFileLoader.java
new file mode 100644
index 00000000000..36e9643cbfe
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/JKSFileLoader.java
@@ -0,0 +1,41 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+/**
+ * Implementation of {@link FileKeyStoreLoader} that loads from JKS files.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java">Base
+ * revision</a>
+ */
+final class JKSFileLoader extends StandardTypeFileKeyStoreLoader {
+ private JKSFileLoader(String keyStorePath, String trustStorePath, char[] keyStorePassword,
+ char[] trustStorePassword) {
+ super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword,
+ SupportedStandardKeyFormat.JKS);
+ }
+
+ static class Builder extends FileKeyStoreLoader.Builder<JKSFileLoader> {
+ @Override
+ JKSFileLoader build() {
+ return new JKSFileLoader(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
+ }
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/KeyStoreLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/KeyStoreLoader.java
new file mode 100644
index 00000000000..928b3b9d046
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/KeyStoreLoader.java
@@ -0,0 +1,53 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+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.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreLoader.java">Base
+ * revision</a>
+ */
+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;
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PEMFileLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PEMFileLoader.java
new file mode 100644
index 00000000000..06d264b67da
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PEMFileLoader.java
@@ -0,0 +1,56 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+/**
+ * Implementation of {@link FileKeyStoreLoader} that loads from PEM files.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/PEMFileLoader.java">Base
+ * revision</a>
+ */
+final class PEMFileLoader extends FileKeyStoreLoader {
+ private PEMFileLoader(String keyStorePath, String trustStorePath, char[] keyStorePassword,
+ char[] trustStorePassword) {
+ super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
+ }
+
+ @Override
+ public KeyStore loadKeyStore() throws IOException, GeneralSecurityException {
+ File file = new File(keyStorePath);
+ return PemReader.loadKeyStore(file, file, keyStorePassword);
+ }
+
+ @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);
+ }
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PKCS12FileLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PKCS12FileLoader.java
new file mode 100644
index 00000000000..ab5a532787e
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PKCS12FileLoader.java
@@ -0,0 +1,42 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+/**
+ * Implementation of {@link FileKeyStoreLoader} that loads from PKCS12 files.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java">Base
+ * revision</a>
+ */
+final class PKCS12FileLoader extends StandardTypeFileKeyStoreLoader {
+ private PKCS12FileLoader(String keyStorePath, String trustStorePath, char[] keyStorePassword,
+ char[] trustStorePassword) {
+ super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword,
+ SupportedStandardKeyFormat.PKCS12);
+ }
+
+ static class Builder extends FileKeyStoreLoader.Builder<PKCS12FileLoader> {
+ @Override
+ PKCS12FileLoader build() {
+ return new PKCS12FileLoader(keyStorePath, trustStorePath, keyStorePassword,
+ trustStorePassword);
+ }
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PemReader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PemReader.java
new file mode 100644
index 00000000000..b4f7aa5565a
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/PemReader.java
@@ -0,0 +1,218 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+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;
+
+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.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;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java">Base
+ * revision</a>
+ */
+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,
+ char[] 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, 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, char[] keyPassword)
+ throws IOException, GeneralSecurityException {
+ String privateKey = new String(Files.readAllBytes(privateKeyFile.toPath()), US_ASCII);
+ return loadPrivateKey(privateKey, keyPassword);
+ }
+
+ public static PrivateKey loadPrivateKey(String privateKey, char[] 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 != null && keyPassword.length > 0) {
+ EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey);
+ SecretKeyFactory keyFactory =
+ SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
+ SecretKey secretKey = keyFactory.generateSecret(new PBEKeySpec(keyPassword));
+
+ 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) {
+ // ignore
+ }
+
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePrivate(encodedKeySpec);
+ } catch (InvalidKeySpecException ignore) {
+ // 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) {
+ // ignore
+ }
+
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return keyFactory.generatePublic(encodedKeySpec);
+ } catch (InvalidKeySpecException ignore) {
+ // ignore
+ }
+
+ KeyFactory keyFactory = KeyFactory.getInstance("DSA");
+ return keyFactory.generatePublic(encodedKeySpec);
+ }
+
+ private static byte[] base64Decode(String base64) {
+ return getMimeDecoder().decode(base64.getBytes(US_ASCII));
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/StandardTypeFileKeyStoreLoader.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/StandardTypeFileKeyStoreLoader.java
new file mode 100644
index 00000000000..67aebdd6c7b
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/StandardTypeFileKeyStoreLoader.java
@@ -0,0 +1,79 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+
+/**
+ * Base class for instances of {@link KeyStoreLoader} which load the key/trust stores from files on
+ * a filesystem using standard {@link KeyStore} types like JKS or PKCS12.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java">Base
+ * revision</a>
+ */
+abstract class StandardTypeFileKeyStoreLoader extends FileKeyStoreLoader {
+ private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+
+ protected final SupportedStandardKeyFormat format;
+
+ protected enum SupportedStandardKeyFormat {
+ JKS,
+ PKCS12,
+ BCFKS
+ }
+
+ StandardTypeFileKeyStoreLoader(String keyStorePath, String trustStorePath,
+ char[] keyStorePassword, char[] trustStorePassword, SupportedStandardKeyFormat format) {
+ super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
+ this.format = format;
+ }
+
+ @Override
+ public KeyStore loadKeyStore() throws IOException, GeneralSecurityException {
+ try (InputStream inputStream = Files.newInputStream(new File(keyStorePath).toPath())) {
+ KeyStore ks = keyStoreInstance();
+ ks.load(inputStream, passwordStringToCharArray(keyStorePassword));
+ return ks;
+ }
+ }
+
+ @Override
+ public KeyStore loadTrustStore() throws IOException, GeneralSecurityException {
+ try (InputStream inputStream = Files.newInputStream(new File(trustStorePath).toPath())) {
+ KeyStore ts = keyStoreInstance();
+ ts.load(inputStream, passwordStringToCharArray(trustStorePassword));
+ return ts;
+ }
+ }
+
+ private KeyStore keyStoreInstance() throws KeyStoreException {
+ return KeyStore.getInstance(format.name());
+ }
+
+ private static char[] passwordStringToCharArray(char[] password) {
+ return password == null ? EMPTY_CHAR_ARRAY : password;
+ }
+}
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
index 76b7fad4c59..471ad41d06f 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
@@ -17,10 +17,7 @@
*/
package org.apache.hadoop.hbase.io.crypto.tls;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Security;
@@ -227,19 +224,16 @@ public final class X509Util {
static X509KeyManager createKeyManager(String keyStoreLocation, char[] keyStorePassword,
String keyStoreType) throws KeyManagerException {
- if (keyStoreType == null) {
- keyStoreType = "jks";
- }
-
if (keyStorePassword == null) {
keyStorePassword = EMPTY_CHAR_ARRAY;
}
try {
- KeyStore ks = KeyStore.getInstance(keyStoreType);
- try (InputStream inputStream = Files.newInputStream(new File(keyStoreLocation).toPath())) {
- ks.load(inputStream, keyStorePassword);
- }
+ KeyStoreFileType storeFileType =
+ KeyStoreFileType.fromPropertyValueOrFileName(keyStoreType, keyStoreLocation);
+ KeyStore ks = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType)
+ .setKeyStorePath(keyStoreLocation).setKeyStorePassword(keyStorePassword).build()
+ .loadKeyStore();
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, keyStorePassword);
@@ -272,19 +266,16 @@ public final class X509Util {
static X509TrustManager createTrustManager(String trustStoreLocation, char[] trustStorePassword,
String trustStoreType, boolean crlEnabled, boolean ocspEnabled) throws TrustManagerException {
- if (trustStoreType == null) {
- trustStoreType = "jks";
- }
-
if (trustStorePassword == null) {
trustStorePassword = EMPTY_CHAR_ARRAY;
}
try {
- KeyStore ts = KeyStore.getInstance(trustStoreType);
- try (InputStream inputStream = Files.newInputStream(new File(trustStoreLocation).toPath())) {
- ts.load(inputStream, trustStorePassword);
- }
+ KeyStoreFileType storeFileType =
+ KeyStoreFileType.fromPropertyValueOrFileName(trustStoreType, trustStoreLocation);
+ KeyStore ts = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType)
+ .setTrustStorePath(trustStoreLocation).setTrustStorePassword(trustStorePassword).build()
+ .loadTrustStore();
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
if (crlEnabled || ocspEnabled) {
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/AbstractTestX509Parameterized.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/AbstractTestX509Parameterized.java
new file mode 100644
index 00000000000..821a6854135
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/AbstractTestX509Parameterized.java
@@ -0,0 +1,128 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runners.Parameterized;
+
+/**
+ * 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).
+ * <p/>
+ * This base class takes care of setting up / cleaning up the test environment, and caching the
+ * X509TestContext objects used by the tests.
+ * <p/>
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/BaseX509ParameterizedTestCase.java">Base
+ * revision</a>
+ */
+public abstract class AbstractTestX509Parameterized {
+
+ private static final HBaseCommonTestingUtil UTIL = new HBaseCommonTestingUtil();
+ private static X509TestContextProvider PROVIDER;
+
+ @Parameterized.Parameter()
+ public X509KeyType caKeyType;
+
+ @Parameterized.Parameter(value = 1)
+ public X509KeyType certKeyType;
+
+ @Parameterized.Parameter(value = 2)
+ public char[] keyPassword;
+
+ @Parameterized.Parameter(value = 3)
+ public Integer paramIndex;
+
+ /**
+ * Default parameters suitable for most subclasses. See example usage in {@link TestX509Util}.
+ * @return an array of parameter combinations to test with.
+ */
+ @Parameterized.Parameters(
+ name = "{index}: caKeyType={0}, certKeyType={1}, keyPassword={2}, paramIndex={3}")
+ public static Collection<Object[]> defaultParams() {
+ List<Object[]> result = new ArrayList<>();
+ int paramIndex = 0;
+ for (X509KeyType caKeyType : X509KeyType.values()) {
+ for (X509KeyType certKeyType : X509KeyType.values()) {
+ for (char[] keyPassword : new char[][] { "".toCharArray(), "pa$$w0rd".toCharArray() }) {
+ 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 Configuration conf;
+
+ protected X509TestContext x509TestContext;
+
+ @BeforeClass
+ public static void setUpBaseClass() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ File dir = new File(UTIL.getDataTestDir(TestX509Util.class.getSimpleName()).toString())
+ .getCanonicalFile();
+ FileUtils.forceMkdir(dir);
+ PROVIDER = new X509TestContextProvider(UTIL.getConfiguration(), dir);
+ }
+
+ @AfterClass
+ public static void cleanUpBaseClass() {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ UTIL.cleanupTestDir();
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ x509TestContext = PROVIDER.get(caKeyType, certKeyType, keyPassword);
+ x509TestContext.setConfigurations(KeyStoreFileType.JKS, KeyStoreFileType.JKS);
+ conf = new Configuration(UTIL.getConfiguration());
+ }
+
+ @After
+ public void cleanUp() {
+ x509TestContext.clearConfigurations();
+ x509TestContext.getConf().unset(X509Util.TLS_CONFIG_OCSP);
+ x509TestContext.getConf().unset(X509Util.TLS_CONFIG_CLR);
+ x509TestContext.getConf().unset(X509Util.TLS_CONFIG_PROTOCOL);
+ System.clearProperty("com.sun.net.ssl.checkRevocation");
+ System.clearProperty("com.sun.security.enableCRLDP");
+ Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
+ Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString());
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestBCFKSFileLoader.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestBCFKSFileLoader.java
new file mode 100644
index 00000000000..060c60a7a0c
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestBCFKSFileLoader.java
@@ -0,0 +1,118 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/BCFKSFileLoaderTest.java">Base
+ * revision</a>
+ */
+@RunWith(Parameterized.class)
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestBCFKSFileLoader extends AbstractTestX509Parameterized {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestBCFKSFileLoader.class);
+
+ @Test
+ public void testLoadKeyStore() throws Exception {
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ KeyStore ks = new BCFKSFileLoader.Builder().setKeyStorePath(path)
+ .setKeyStorePassword(x509TestContext.getKeyStorePassword()).build().loadKeyStore();
+ Assert.assertEquals(1, ks.size());
+ }
+
+ @Test(expected = Exception.class)
+ public void testLoadKeyStoreWithWrongPassword() throws Exception {
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setKeyStorePath(path)
+ .setKeyStorePassword("wrong password".toCharArray()).build().loadKeyStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadKeyStoreWithWrongFilePath() throws Exception {
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setKeyStorePath(path + ".does_not_exist")
+ .setKeyStorePassword(x509TestContext.getKeyStorePassword()).build().loadKeyStore();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLoadKeyStoreWithNullFilePath() throws Exception {
+ new BCFKSFileLoader.Builder().setKeyStorePassword(x509TestContext.getKeyStorePassword()).build()
+ .loadKeyStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadKeyStoreWithWrongFileType() throws Exception {
+ // Trying to load a PEM file with BCFKS loader should fail
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setKeyStorePath(path)
+ .setKeyStorePassword(x509TestContext.getKeyStorePassword()).build().loadKeyStore();
+ }
+
+ @Test
+ public void testLoadTrustStore() throws Exception {
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ KeyStore ts = new BCFKSFileLoader.Builder().setTrustStorePath(path)
+ .setTrustStorePassword(x509TestContext.getTrustStorePassword()).build().loadTrustStore();
+ Assert.assertEquals(1, ts.size());
+ }
+
+ @Test(expected = Exception.class)
+ public void testLoadTrustStoreWithWrongPassword() throws Exception {
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setTrustStorePath(path)
+ .setTrustStorePassword("wrong password".toCharArray()).build().loadTrustStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadTrustStoreWithWrongFilePath() throws Exception {
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setTrustStorePath(path + ".does_not_exist")
+ .setTrustStorePassword(x509TestContext.getTrustStorePassword()).build().loadTrustStore();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLoadTrustStoreWithNullFilePath() throws Exception {
+ new BCFKSFileLoader.Builder().setTrustStorePassword(x509TestContext.getTrustStorePassword())
+ .build().loadTrustStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadTrustStoreWithWrongFileType() throws Exception {
+ // Trying to load a PEM file with BCFKS loader should fail
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
+ new BCFKSFileLoader.Builder().setTrustStorePath(path)
+ .setTrustStorePassword(x509TestContext.getTrustStorePassword()).build().loadTrustStore();
+ }
+
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestFileKeyStoreLoaderBuilderProvider.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestFileKeyStoreLoaderBuilderProvider.java
new file mode 100644
index 00000000000..a8010348334
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestFileKeyStoreLoaderBuilderProvider.java
@@ -0,0 +1,66 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProviderTest.java">Base
+ * revision</a>
+ */
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestFileKeyStoreLoaderBuilderProvider {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestFileKeyStoreLoaderBuilderProvider.class);
+
+ @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
+ public void testGetBuilderForPKCS12FileType() {
+ FileKeyStoreLoader.Builder<?> builder =
+ FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(KeyStoreFileType.PKCS12);
+ Assert.assertTrue(builder instanceof PKCS12FileLoader.Builder);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testGetBuilderForNullFileType() {
+ FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(null);
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestJKSFileLoader.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestJKSFileLoader.java
new file mode 100644
index 00000000000..6640e3b22f9
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestJKSFileLoader.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/JKSFileLoaderTest.java">Base
+ * revision</a>
+ */
+@RunWith(Parameterized.class)
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestJKSFileLoader extends AbstractTestX509Parameterized {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestJKSFileLoader.class);
+
+ @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".toCharArray()).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".toCharArray()).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();
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestKeyStoreFileType.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestKeyStoreFileType.java
new file mode 100644
index 00000000000..d3f457fb4d3
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestKeyStoreFileType.java
@@ -0,0 +1,120 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/KeyStoreFileTypeTest.java">Base
+ * revision</a>
+ */
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestKeyStoreFileType {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestKeyStoreFileType.class);
+
+ @Test
+ public void testGetPropertyValue() {
+ Assert.assertEquals("PEM", KeyStoreFileType.PEM.getPropertyValue());
+ Assert.assertEquals("JKS", KeyStoreFileType.JKS.getPropertyValue());
+ Assert.assertEquals("PKCS12", KeyStoreFileType.PKCS12.getPropertyValue());
+ Assert.assertEquals("BCFKS", KeyStoreFileType.BCFKS.getPropertyValue());
+ }
+
+ @Test
+ public void testFromPropertyValue() {
+ Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("PEM"));
+ Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("JKS"));
+ Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("PKCS12"));
+ Assert.assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("BCFKS"));
+ Assert.assertNull(KeyStoreFileType.fromPropertyValue(""));
+ Assert.assertNull(KeyStoreFileType.fromPropertyValue(null));
+ }
+
+ @Test
+ public void testFromPropertyValueIgnoresCase() {
+ Assert.assertEquals(KeyStoreFileType.PEM, KeyStoreFileType.fromPropertyValue("pem"));
+ Assert.assertEquals(KeyStoreFileType.JKS, KeyStoreFileType.fromPropertyValue("jks"));
+ Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromPropertyValue("pkcs12"));
+ Assert.assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromPropertyValue("bcfks"));
+ Assert.assertNull(KeyStoreFileType.fromPropertyValue(""));
+ Assert.assertNull(KeyStoreFileType.fromPropertyValue(null));
+ }
+
+ @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"));
+ Assert.assertEquals(KeyStoreFileType.PKCS12, KeyStoreFileType.fromFilename("mykey.p12"));
+ Assert.assertEquals(KeyStoreFileType.PKCS12,
+ KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.p12"));
+ Assert.assertEquals(KeyStoreFileType.BCFKS, KeyStoreFileType.fromFilename("mykey.bcfks"));
+ Assert.assertEquals(KeyStoreFileType.BCFKS,
+ KeyStoreFileType.fromFilename("/path/to/key/dir/mykey.bcfks"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ 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"));
+ Assert.assertEquals(KeyStoreFileType.PEM,
+ KeyStoreFileType.fromPropertyValueOrFileName("PEM", "prod.key"));
+ Assert.assertEquals(KeyStoreFileType.PKCS12,
+ KeyStoreFileType.fromPropertyValueOrFileName("PKCS12", "prod.key"));
+ Assert.assertEquals(KeyStoreFileType.BCFKS,
+ KeyStoreFileType.fromPropertyValueOrFileName("BCFKS", "prod.key"));
+ // Falls back to filename detection if no property value
+ Assert.assertEquals(KeyStoreFileType.JKS,
+ KeyStoreFileType.fromPropertyValueOrFileName("", "prod.jks"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFromPropertyValueOrFileNameThrowsOnBadPropertyValue() {
+ KeyStoreFileType.fromPropertyValueOrFileName("foobar", "prod.jks");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFromPropertyValueOrFileNameThrowsOnBadFileExtension() {
+ KeyStoreFileType.fromPropertyValueOrFileName("", "prod.key");
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPEMFileLoader.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPEMFileLoader.java
new file mode 100644
index 00000000000..0c9924f0907
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPEMFileLoader.java
@@ -0,0 +1,112 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/PEMFileLoaderTest.java">Base
+ * revision</a>
+ */
+@RunWith(Parameterized.class)
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestPEMFileLoader extends AbstractTestX509Parameterized {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestPEMFileLoader.class);
+
+ @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".toCharArray()).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());
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPKCS12FileLoader.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPKCS12FileLoader.java
new file mode 100644
index 00000000000..a0ff83833e2
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestPKCS12FileLoader.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hadoop.hbase.io.crypto.tls;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This file has been copied from the Apache ZooKeeper project.
+ * @see <a href=
+ * "https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/test/java/org/apache/zookeeper/common/PKCS12FileLoaderTest.java">Base
+ * revision</a>
+ */
+@RunWith(Parameterized.class)
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestPKCS12FileLoader extends AbstractTestX509Parameterized {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestPKCS12FileLoader.class);
+
+ @Test
+ public void testLoadKeyStore() throws Exception {
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath();
+ KeyStore ks = new PKCS12FileLoader.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.PKCS12).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setKeyStorePath(path)
+ .setKeyStorePassword("wrong password".toCharArray()).build().loadKeyStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadKeyStoreWithWrongFilePath() throws Exception {
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setKeyStorePath(path + ".does_not_exist")
+ .setKeyStorePassword(x509TestContext.getKeyStorePassword()).build().loadKeyStore();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLoadKeyStoreWithNullFilePath() throws Exception {
+ new PKCS12FileLoader.Builder().setKeyStorePassword(x509TestContext.getKeyStorePassword())
+ .build().loadKeyStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadKeyStoreWithWrongFileType() throws Exception {
+ // Trying to load a PEM file with PKCS12 loader should fail
+ String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setKeyStorePath(path)
+ .setKeyStorePassword(x509TestContext.getKeyStorePassword()).build().loadKeyStore();
+ }
+
+ @Test
+ public void testLoadTrustStore() throws Exception {
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath();
+ KeyStore ts = new PKCS12FileLoader.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.PKCS12).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setTrustStorePath(path)
+ .setTrustStorePassword("wrong password".toCharArray()).build().loadTrustStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadTrustStoreWithWrongFilePath() throws Exception {
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setTrustStorePath(path + ".does_not_exist")
+ .setTrustStorePassword(x509TestContext.getTrustStorePassword()).build().loadTrustStore();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testLoadTrustStoreWithNullFilePath() throws Exception {
+ new PKCS12FileLoader.Builder().setTrustStorePassword(x509TestContext.getTrustStorePassword())
+ .build().loadTrustStore();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadTrustStoreWithWrongFileType() throws Exception {
+ // Trying to load a PEM file with PKCS12 loader should fail
+ String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
+ new PKCS12FileLoader.Builder().setTrustStorePath(path)
+ .setTrustStorePassword(x509TestContext.getTrustStorePassword()).build().loadTrustStore();
+ }
+}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestX509Util.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestX509Util.java
index 61134390e8a..a847db98a04 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestX509Util.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/TestX509Util.java
@@ -28,28 +28,15 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeThat;
import static org.mockito.Mockito.mock;
-import java.io.File;
-import java.io.IOException;
import java.security.Security;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.List;
-import org.apache.commons.io.FileUtils;
-import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
import org.apache.hadoop.hbase.exceptions.KeyManagerException;
import org.apache.hadoop.hbase.exceptions.SSLContextException;
import org.apache.hadoop.hbase.exceptions.TrustManagerException;
-import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -66,83 +53,15 @@ import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
* revision</a>
*/
@RunWith(Parameterized.class)
-@Category({ MiscTests.class, SmallTests.class })
-public class TestX509Util {
+@Category({ SecurityTests.class, SmallTests.class })
+public class TestX509Util extends AbstractTestX509Parameterized {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestX509Util.class);
- private static final HBaseCommonTestingUtil UTIL = new HBaseCommonTestingUtil();
private static final char[] EMPTY_CHAR_ARRAY = new char[0];
- private static X509TestContextProvider PROVIDER;
-
- @Parameterized.Parameter()
- public X509KeyType caKeyType;
-
- @Parameterized.Parameter(value = 1)
- public X509KeyType certKeyType;
-
- @Parameterized.Parameter(value = 2)
- public char[] keyPassword;
-
- @Parameterized.Parameter(value = 3)
- public Integer paramIndex;
-
- private X509TestContext x509TestContext;
-
- private Configuration conf;
-
- @Parameterized.Parameters(
- name = "{index}: caKeyType={0}, certKeyType={1}, keyPassword={2}, paramIndex={3}")
- public static Collection<Object[]> data() {
- List<Object[]> params = new ArrayList<>();
- int paramIndex = 0;
- for (X509KeyType caKeyType : X509KeyType.values()) {
- for (X509KeyType certKeyType : X509KeyType.values()) {
- for (char[] keyPassword : new char[][] { "".toCharArray(), "pa$$w0rd".toCharArray() }) {
- params.add(new Object[] { caKeyType, certKeyType, keyPassword, paramIndex++ });
- }
- }
- }
- return params;
- }
-
- @BeforeClass
- public static void setUpBeforeClass() throws IOException {
- Security.addProvider(new BouncyCastleProvider());
- File dir = new File(UTIL.getDataTestDir(TestX509Util.class.getSimpleName()).toString())
- .getCanonicalFile();
- FileUtils.forceMkdir(dir);
- PROVIDER = new X509TestContextProvider(UTIL.getConfiguration(), dir);
- }
-
- @AfterClass
- public static void tearDownAfterClass() {
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
- UTIL.cleanupTestDir();
- }
-
- @Before
- public void setUp() throws IOException {
- x509TestContext = PROVIDER.get(caKeyType, certKeyType, keyPassword);
- x509TestContext.setConfigurations(KeyStoreFileType.JKS, KeyStoreFileType.JKS);
- conf = new Configuration(UTIL.getConfiguration());
- }
-
- @After
- public void cleanUp() {
- x509TestContext.clearConfigurations();
- x509TestContext.getConf().unset(X509Util.TLS_CONFIG_OCSP);
- x509TestContext.getConf().unset(X509Util.TLS_CONFIG_CLR);
- x509TestContext.getConf().unset(X509Util.TLS_CONFIG_PROTOCOL);
- System.clearProperty("com.sun.net.ssl.checkRevocation");
- System.clearProperty("com.sun.security.enableCRLDP");
- Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
- Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString());
- }
-
@Test
public void testCreateSSLContextWithoutCustomProtocol() throws Exception {
SslContext sslContext = X509Util.createSslContextForClient(conf);
@@ -204,6 +123,69 @@ public class TestX509Util {
assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
}
+ @Test
+ public void testLoadPEMKeyStore() throws Exception {
+ // Make sure we can instantiate a key manager from the PEM file on disk
+ X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(), KeyStoreFileType.PEM.getPropertyValue());
+ }
+
+ @Test
+ public void testLoadPEMKeyStoreNullPassword() throws Exception {
+ assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
+ // Make sure that empty password and null password are treated the same
+ 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
+ X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ null /* null StoreFileType means 'autodetect from file extension' */);
+ }
+
+ @Test(expected = KeyManagerException.class)
+ public void testLoadPEMKeyStoreWithWrongPassword() throws Exception {
+ // Attempting to load with the wrong key password should fail
+ X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ "wrong password".toCharArray(), // 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
+ X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(), false,
+ false);
+ }
+
+ @Test
+ public void testLoadPEMTrustStoreNullPassword() throws Exception {
+ assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
+ // Make sure that empty password and null password are treated the same
+ X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(), null,
+ KeyStoreFileType.PEM.getPropertyValue(), false, false);
+ }
+
+ @Test
+ public void testLoadPEMTrustStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a trust manager from the PEM file on disk
+ X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
+ // file extension'
+ false, false);
+ }
+
@Test
public void testLoadJKSKeyStore() throws Exception {
// Make sure we can instantiate a key manager from the JKS file on disk
@@ -222,7 +204,7 @@ public class TestX509Util {
}
@Test
- public void testLoadJKSKeyStoreFileTypeDefaultToJks() throws Exception {
+ public void testLoadJKSKeyStoreAutodetectStoreFileType() throws Exception {
// Make sure we can instantiate a key manager from the JKS file on disk
X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
@@ -258,16 +240,17 @@ public class TestX509Util {
}
@Test
- public void testLoadJKSTrustStoreFileTypeDefaultToJks() throws Exception {
+ public void testLoadJKSTrustStoreAutodetectStoreFileType() throws Exception {
// Make sure we can instantiate a trust manager from the JKS file on disk
X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
- // null StoreFileType means 'autodetect from file extension'
- x509TestContext.getTrustStorePassword(), null, true, true);
+ x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
+ // file extension'
+ true, true);
}
@Test
- public void testLoadJKSTrustStoreWithWrongPassword() throws Exception {
+ public void testLoadJKSTrustStoreWithWrongPassword() {
assertThrows(TrustManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509Util.createTrustManager(
@@ -294,7 +277,16 @@ public class TestX509Util {
}
@Test
- public void testLoadPKCS12KeyStoreWithWrongPassword() throws Exception {
+ public void testLoadPKCS12KeyStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a key manager from the PKCS12 file on disk
+ X509Util.createKeyManager(
+ x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
+ x509TestContext.getKeyStorePassword(),
+ null /* null StoreFileType means 'autodetect from file extension' */);
+ }
+
+ @Test
+ public void testLoadPKCS12KeyStoreWithWrongPassword() {
assertThrows(KeyManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509Util.createKeyManager(
@@ -322,7 +314,17 @@ public class TestX509Util {
}
@Test
- public void testLoadPKCS12TrustStoreWithWrongPassword() throws Exception {
+ public void testLoadPKCS12TrustStoreAutodetectStoreFileType() throws Exception {
+ // Make sure we can instantiate a trust manager from the PKCS12 file on disk
+ X509Util.createTrustManager(
+ x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
+ x509TestContext.getTrustStorePassword(), null, // null StoreFileType means 'autodetect from
+ // file extension'
+ true, true);
+ }
+
+ @Test
+ public void testLoadPKCS12TrustStoreWithWrongPassword() {
assertThrows(TrustManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509Util.createTrustManager(
@@ -332,42 +334,42 @@ public class TestX509Util {
}
@Test
- public void testGetDefaultCipherSuitesJava8() throws Exception {
+ public void testGetDefaultCipherSuitesJava8() {
String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("1.8");
// Java 8 default should have the CBC suites first
assertThat(cipherSuites[0], containsString("CBC"));
}
@Test
- public void testGetDefaultCipherSuitesJava9() throws Exception {
+ public void testGetDefaultCipherSuitesJava9() {
String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("9");
// Java 9+ default should have the GCM suites first
assertThat(cipherSuites[0], containsString("GCM"));
}
@Test
- public void testGetDefaultCipherSuitesJava10() throws Exception {
+ public void testGetDefaultCipherSuitesJava10() {
String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("10");
// Java 9+ default should have the GCM suites first
assertThat(cipherSuites[0], containsString("GCM"));
}
@Test
- public void testGetDefaultCipherSuitesJava11() throws Exception {
+ public void testGetDefaultCipherSuitesJava11() {
String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("11");
// Java 9+ default should have the GCM suites first
assertThat(cipherSuites[0], containsString("GCM"));
}
@Test
- public void testGetDefaultCipherSuitesUnknownVersion() throws Exception {
+ public void testGetDefaultCipherSuitesUnknownVersion() {
String[] cipherSuites = X509Util.getDefaultCipherSuitesForJavaVersion("notaversion");
// If version can't be parsed, use the more conservative Java 8 default
assertThat(cipherSuites[0], containsString("CBC"));
}
@Test
- public void testGetDefaultCipherSuitesNullVersion() throws Exception {
+ public void testGetDefaultCipherSuitesNullVersion() {
assertThrows(NullPointerException.class, () -> {
X509Util.getDefaultCipherSuitesForJavaVersion(null);
});
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContext.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContext.java
index b2085078860..27cb5bde3ac 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContext.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContext.java
@@ -31,7 +31,6 @@ import java.security.cert.X509Certificate;
import java.util.Arrays;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.yetus.audience.InterfaceAudience;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
@@ -60,6 +59,7 @@ public final class X509TestContext {
private File trustStoreJksFile;
private File trustStorePemFile;
private File trustStorePkcs12File;
+ private File trustStoreBcfksFile;
private final KeyPair keyStoreKeyPair;
private final X509Certificate keyStoreCertificate;
@@ -67,6 +67,7 @@ public final class X509TestContext {
private File keyStoreJksFile;
private File keyStorePemFile;
private File keyStorePkcs12File;
+ private File keyStoreBcfksFile;
/**
* Constructor is intentionally private, use the Builder class instead.
@@ -137,6 +138,8 @@ public final class X509TestContext {
return getTrustStorePemFile();
case PKCS12:
return getTrustStorePkcs12File();
+ case BCFKS:
+ return getTrustStoreBcfksFile();
default:
throw new IllegalArgumentException("Invalid trust store type: " + storeFileType
+ ", must be one of: " + Arrays.toString(KeyStoreFileType.values()));
@@ -194,6 +197,25 @@ public final class X509TestContext {
return trustStorePkcs12File;
}
+ private File getTrustStoreBcfksFile() throws IOException {
+ if (trustStoreBcfksFile == null) {
+ File trustStoreBcfksFile = File.createTempFile(TRUST_STORE_PREFIX,
+ KeyStoreFileType.BCFKS.getDefaultFileExtension(), tempDir);
+ trustStoreBcfksFile.deleteOnExit();
+ try (
+ final FileOutputStream trustStoreOutputStream = new FileOutputStream(trustStoreBcfksFile)) {
+ byte[] bytes =
+ X509TestHelpers.certToBCFKSTrustStoreBytes(trustStoreCertificate, trustStorePassword);
+ trustStoreOutputStream.write(bytes);
+ trustStoreOutputStream.flush();
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ }
+ this.trustStoreBcfksFile = trustStoreBcfksFile;
+ }
+ return trustStoreBcfksFile;
+ }
+
public X509Certificate getKeyStoreCertificate() {
return keyStoreCertificate;
}
@@ -226,6 +248,8 @@ public final class X509TestContext {
return getKeyStorePemFile();
case PKCS12:
return getKeyStorePkcs12File();
+ case BCFKS:
+ return getKeyStoreBcfksFile();
default:
throw new IllegalArgumentException("Invalid key store type: " + storeFileType
+ ", must be one of: " + Arrays.toString(KeyStoreFileType.values()));
@@ -286,6 +310,24 @@ public final class X509TestContext {
return keyStorePkcs12File;
}
+ private File getKeyStoreBcfksFile() throws IOException {
+ if (keyStoreBcfksFile == null) {
+ File keyStoreBcfksFile = File.createTempFile(KEY_STORE_PREFIX,
+ KeyStoreFileType.BCFKS.getDefaultFileExtension(), tempDir);
+ keyStoreBcfksFile.deleteOnExit();
+ try (final FileOutputStream keyStoreOutputStream = new FileOutputStream(keyStoreBcfksFile)) {
+ byte[] bytes = X509TestHelpers.certAndPrivateKeyToBCFKSBytes(keyStoreCertificate,
+ keyStoreKeyPair.getPrivate(), keyStorePassword);
+ keyStoreOutputStream.write(bytes);
+ keyStoreOutputStream.flush();
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ }
+ this.keyStoreBcfksFile = keyStoreBcfksFile;
+ }
+ return keyStoreBcfksFile;
+ }
+
/**
* Sets the SSL system properties such that the given X509Util object can be used to create SSL
* Contexts that will use the trust store and key store files created by this test context.
@@ -413,14 +455,6 @@ public final class X509TestContext {
}
}
- /**
- * Returns a new default-constructed Builder.
- * @return a new Builder.
- */
- public static Builder newBuilder() {
- return newBuilder(HBaseConfiguration.create());
- }
-
/**
* Returns a new default-constructed Builder.
* @return a new Builder.
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContextProvider.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContextProvider.java
index 3024755a2e3..d65cdbe689d 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContextProvider.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestContextProvider.java
@@ -18,7 +18,10 @@
package org.apache.hadoop.hbase.io.crypto.tls;
import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
import java.util.Objects;
import org.apache.hadoop.conf.Configuration;
@@ -83,4 +86,18 @@ public class X509TestContextProvider {
public X509TestContext get(X509KeyType caKeyType, X509KeyType certKeyType, char[] keyPassword) {
return ctxs.getUnchecked(new CacheKey(caKeyType, certKeyType, keyPassword));
}
+
+ static Collection<Object[]> defaultParams() {
+ List<Object[]> params = new ArrayList<>();
+ int paramIndex = 0;
+ for (X509KeyType caKeyType : X509KeyType.values()) {
+ for (X509KeyType certKeyType : X509KeyType.values()) {
+ for (char[] keyPassword : new char[][] { "".toCharArray(), "pa$$w0rd".toCharArray() }) {
+ params.add(new Object[] { caKeyType, certKeyType, keyPassword, paramIndex++ });
+ }
+ }
+ }
+ return params;
+ }
+
}
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestHelpers.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestHelpers.java
index 1697dca8669..968e616ef56 100644
--- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestHelpers.java
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/tls/X509TestHelpers.java
@@ -281,7 +281,7 @@ final class X509TestHelpers {
StringWriter stringWriter = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter);
OutputEncryptor encryptor = null;
- if (password != null) {
+ if (password != null && password.length > 0) {
encryptor =
new JceOpenSSLPKCS8EncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC)
.setProvider(BouncyCastleProvider.PROVIDER_NAME).setRandom(PRNG).setPasssword(password)
@@ -341,6 +341,23 @@ final class X509TestHelpers {
return certToTrustStoreBytes(cert, keyPassword, trustStore);
}
+ /**
+ * Encodes the given X509Certificate as a BCFKS TrustStore, optionally protecting the cert with a
+ * password (though it's unclear why one would do this since certificates only contain public
+ * information and do not need to be kept secret). Returns the byte array encoding of the trust
+ * store, which may be written to a file and loaded to instantiate the trust store at a later
+ * point or in another process.
+ * @param cert the certificate to serialize.
+ * @param keyPassword an optional password to encrypt the trust store. If empty or null, the cert
+ * will not be encrypted.
+ * @return the serialized bytes of the BCFKS trust store. nn
+ */
+ public static byte[] certToBCFKSTrustStoreBytes(X509Certificate cert, char[] keyPassword)
+ throws IOException, GeneralSecurityException {
+ KeyStore trustStore = KeyStore.getInstance("BCFKS");
+ return certToTrustStoreBytes(cert, keyPassword, trustStore);
+ }
+
private static byte[] certToTrustStoreBytes(X509Certificate cert, char[] keyPassword,
KeyStore trustStore) throws IOException, GeneralSecurityException {
trustStore.load(null, keyPassword);
@@ -387,6 +404,23 @@ final class X509TestHelpers {
return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore);
}
+ /**
+ * Encodes the given X509Certificate and private key as a BCFKS KeyStore, optionally protecting
+ * the private key (and possibly the cert?) with a password. Returns the byte array encoding of
+ * the key store, which may be written to a file and loaded to instantiate the key store at a
+ * later point or in another process.
+ * @param cert the X509 certificate to serialize.
+ * @param privateKey the private key to serialize.
+ * @param keyPassword an optional key password. If empty or null, the private key will not be
+ * encrypted.
+ * @return the serialized bytes of the BCFKS key store. nn
+ */
+ public static byte[] certAndPrivateKeyToBCFKSBytes(X509Certificate cert, PrivateKey privateKey,
+ char[] keyPassword) throws IOException, GeneralSecurityException {
+ KeyStore keyStore = KeyStore.getInstance("BCFKS");
+ return certAndPrivateKeyToBytes(cert, privateKey, keyPassword, keyStore);
+ }
+
private static byte[] certAndPrivateKeyToBytes(X509Certificate cert, PrivateKey privateKey,
char[] keyPassword, KeyStore keyStore) throws IOException, GeneralSecurityException {
keyStore.load(null, keyPassword);