Skip to content

Commit d55a4dd

Browse files
committed
ZOOKEEPER-4468: Backport BCFKS key/trust store format support to branch 3.5
Backporting ZOOKEEPER-3950 to branch-3.5. This is a cherry-pick from #1482, also included checkstyle fix from #1516. This PR is basically the same as #1480 on the master branch, only the unit tests needed to be changed back from junit5 to junit4. The BCFKS key store format is widely used in the industry, as it provides an open source alternative if someone has to use FIPS compliant key stores due to some regulatory constraints. Currently in the ZooKeeper java client, only PEM, JKS and PEM12 is supported. I extend the list of supported key store formats with BCFKS. I also tested this patch on a real FIPS compliant cluster, having the appropriate java security configs, security providers and also running a RedHat-based Linux distro (Centos 7.8) with FIPS mode enabled. I tested both the client and the quorum SSL too. If someone wants to test this patch, and the keystore/truststore file names are not ending with ".bckfs", then (beside the usual SSL configs) make sure to also set the following parameters in the zoo.cfg: ``` ssl.keyStore.type=bcfks ssl.trustStore.type=bcfks ssl.quorum.keyStore.type=bcfks ssl.quorum.trustStore.type=bcfks ``` and also provide the following parameters for the command line java client: ``` -Dzookeeper.ssl.keyStore.type=bcfks -Dzookeeper.ssl.trustStore.type=bcfks ``` This patch doesn't contain any modification for the c-client (that can be handled with a separate Jira, but I don't plan to work on that part right now). Author: Mate Szalay-Beko <[email protected]> Reviewers: Norbert Kalmar <[email protected]>, Andor Molnar <[email protected]> Closes #1815 from symat/ZOOKEEPER-4468
1 parent 6f6757b commit d55a4dd

File tree

12 files changed

+337
-49
lines changed

12 files changed

+337
-49
lines changed

zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -985,9 +985,11 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
985985
* *ssl.keyStore.type* and *ssl.quorum.keyStore.type* :
986986
(Java system properties: **zookeeper.ssl.keyStore.type** and **zookeeper.ssl.quorum.keyStore.type**)
987987
**New in 3.5.5:**
988-
Specifies the file format of client and quorum keystores. Values: JKS, PEM, PKCS12 or null (detect by filename).
989-
Default: null
990-
988+
Specifies the file format of client and quorum keystores. Values: JKS, PEM, PKCS12 or null (detect by filename).
989+
Default: null.
990+
**New in 3.5.10, 3.6.3, 3.7.0:**
991+
The format BCFKS was added.
992+
991993
* *ssl.trustStore.location* and *ssl.trustStore.password* and *ssl.quorum.trustStore.location* and *ssl.quorum.trustStore.password* :
992994
(Java system properties: **zookeeper.ssl.trustStore.location** and **zookeeper.ssl.trustStore.password** and **zookeeper.ssl.quorum.trustStore.location** and **zookeeper.ssl.quorum.trustStore.password**)
993995
**New in 3.5.5:**
@@ -998,8 +1000,10 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
9981000
* *ssl.trustStore.type* and *ssl.quorum.trustStore.type* :
9991001
(Java system properties: **zookeeper.ssl.trustStore.type** and **zookeeper.ssl.quorum.trustStore.type**)
10001002
**New in 3.5.5:**
1001-
Specifies the file format of client and quorum trustStores. Values: JKS, PEM, PKCS12 or null (detect by filename).
1002-
Default: null
1003+
Specifies the file format of client and quorum trustStores. Values: JKS, PEM, PKCS12 or null (detect by filename).
1004+
Default: null.
1005+
**New in 3.5.10, 3.6.3, 3.7.0:**
1006+
The format BCFKS was added.
10031007

10041008
* *ssl.protocol* and *ssl.quorum.protocol* :
10051009
(Java system properties: **zookeeper.ssl.protocol** and **zookeeper.ssl.quorum.protocol**)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.zookeeper.common;
20+
21+
22+
/**
23+
* Implementation of {@link FileKeyStoreLoader} that loads from BCKFS files.
24+
*/
25+
class BCFKSFileLoader extends StandardTypeFileKeyStoreLoader {
26+
private BCFKSFileLoader(String keyStorePath,
27+
String trustStorePath,
28+
String keyStorePassword,
29+
String trustStorePassword) {
30+
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.BCFKS);
31+
}
32+
33+
static class Builder extends FileKeyStoreLoader.Builder<BCFKSFileLoader> {
34+
@Override
35+
BCFKSFileLoader build() {
36+
return new BCFKSFileLoader(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
37+
}
38+
}
39+
}

zookeeper-server/src/main/java/org/apache/zookeeper/common/FileKeyStoreLoaderBuilderProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class FileKeyStoreLoaderBuilderProvider {
3838
return new PEMFileLoader.Builder();
3939
case PKCS12:
4040
return new PKCS12FileLoader.Builder();
41+
case BCFKS:
42+
return new BCFKSFileLoader.Builder();
4143
default:
4244
throw new AssertionError(
4345
"Unexpected StoreFileType: " + type.name());

zookeeper-server/src/main/java/org/apache/zookeeper/common/JKSFileLoader.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
package org.apache.zookeeper.common;
2020

21-
import java.security.KeyStore;
22-
import java.security.KeyStoreException;
2321

2422
/**
2523
* Implementation of {@link FileKeyStoreLoader} that loads from JKS files.
@@ -29,12 +27,7 @@ private JKSFileLoader(String keyStorePath,
2927
String trustStorePath,
3028
String keyStorePassword,
3129
String trustStorePassword) {
32-
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
33-
}
34-
35-
@Override
36-
protected KeyStore keyStoreInstance() throws KeyStoreException {
37-
return KeyStore.getInstance("JKS");
30+
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.JKS);
3831
}
3932

4033
static class Builder extends FileKeyStoreLoader.Builder<JKSFileLoader> {

zookeeper-server/src/main/java/org/apache/zookeeper/common/KeyStoreFileType.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020

2121
/**
2222
* This enum represents the file type of a KeyStore or TrustStore.
23-
* Currently, JKS (Java keystore), PEM, and PKCS12 types are supported.
23+
* Currently, JKS (Java keystore), PEM, PKCS12, and BCFKS types are supported.
2424
*/
2525
public enum KeyStoreFileType {
26-
JKS(".jks"), PEM(".pem"), PKCS12(".p12");
26+
JKS(".jks"),
27+
PEM(".pem"),
28+
PKCS12(".p12"),
29+
BCFKS(".bcfks");
2730

2831
private final String defaultFileExtension;
2932

@@ -53,7 +56,7 @@ public String getDefaultFileExtension() {
5356
* @return the KeyStoreFileType, or <code>null</code> if
5457
* <code>propertyValue</code> is <code>null</code> or empty.
5558
* @throws IllegalArgumentException if <code>propertyValue</code> is not
56-
* one of "JKS", "PEM", "PKCS12", or empty/null.
59+
* one of "JKS", "PEM", "BCFKS", "PKCS12", or empty/null.
5760
*/
5861
public static KeyStoreFileType fromPropertyValue(String propertyValue) {
5962
if (propertyValue == null || propertyValue.length() == 0) {
@@ -67,11 +70,12 @@ public static KeyStoreFileType fromPropertyValue(String propertyValue) {
6770
* If the file name ends with ".jks", returns <code>StoreFileType.JKS</code>.
6871
* If the file name ends with ".pem", returns <code>StoreFileType.PEM</code>.
6972
* If the file name ends with ".p12", returns <code>StoreFileType.PKCS12</code>.
73+
* If the file name ends with ".bckfs", returns <code>StoreFileType.BCKFS</code>.
7074
* Otherwise, throws an IllegalArgumentException.
7175
* @param filename the filename of the key store or trust store file.
7276
* @return a KeyStoreFileType.
7377
* @throws IllegalArgumentException if the filename does not end with
74-
* ".jks", ".pem", or "p12".
78+
* ".jks", ".pem", "p12" or "bcfks".
7579
*/
7680
public static KeyStoreFileType fromFilename(String filename) {
7781
int i = filename.lastIndexOf('.');
@@ -99,7 +103,7 @@ public static KeyStoreFileType fromFilename(String filename) {
99103
* <code>propertyValue</code> is null or empty.
100104
* @return a KeyStoreFileType.
101105
* @throws IllegalArgumentException if <code>propertyValue</code> is not
102-
* one of "JKS", "PEM", "PKCS12", or empty/null.
106+
* one of "JKS", "PEM", "PKCS12", "BCFKS", or empty/null.
103107
* @throws IllegalArgumentException if <code>propertyValue</code>is empty
104108
* or null and the type could not be determined from the file name.
105109
*/

zookeeper-server/src/main/java/org/apache/zookeeper/common/PKCS12FileLoader.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
package org.apache.zookeeper.common;
2020

21-
import java.security.KeyStore;
22-
import java.security.KeyStoreException;
2321

2422
/**
2523
* Implementation of {@link FileKeyStoreLoader} that loads from PKCS12 files.
@@ -29,12 +27,7 @@ private PKCS12FileLoader(String keyStorePath,
2927
String trustStorePath,
3028
String keyStorePassword,
3129
String trustStorePassword) {
32-
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
33-
}
34-
35-
@Override
36-
protected KeyStore keyStoreInstance() throws KeyStoreException {
37-
return KeyStore.getInstance("PKCS12");
30+
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword, SupportedStandardKeyFormat.PKCS12);
3831
}
3932

4033
static class Builder extends FileKeyStoreLoader.Builder<PKCS12FileLoader> {

zookeeper-server/src/main/java/org/apache/zookeeper/common/StandardTypeFileKeyStoreLoader.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@
3434
abstract class StandardTypeFileKeyStoreLoader extends FileKeyStoreLoader {
3535
private static final char[] EMPTY_CHAR_ARRAY = new char[0];
3636

37-
StandardTypeFileKeyStoreLoader(String keyStorePath,
38-
String trustStorePath,
39-
String keyStorePassword,
40-
String trustStorePassword) {
37+
protected final SupportedStandardKeyFormat format;
38+
39+
protected enum SupportedStandardKeyFormat {
40+
JKS, PKCS12, BCFKS
41+
}
42+
43+
44+
StandardTypeFileKeyStoreLoader(String keyStorePath, String trustStorePath, String keyStorePassword,
45+
String trustStorePassword, SupportedStandardKeyFormat format) {
4146
super(keyStorePath, trustStorePath, keyStorePassword, trustStorePassword);
47+
this.format = format;
4248
}
4349

4450
@Override
@@ -59,7 +65,9 @@ public KeyStore loadTrustStore() throws IOException, GeneralSecurityException {
5965
}
6066
}
6167

62-
protected abstract KeyStore keyStoreInstance() throws KeyStoreException;
68+
private KeyStore keyStoreInstance() throws KeyStoreException {
69+
return KeyStore.getInstance(format.name());
70+
}
6371

6472
private static char[] passwordStringToCharArray(String password) {
6573
return password == null ? EMPTY_CHAR_ARRAY : password.toCharArray();

zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,9 @@ public SSLContextAndOptions createSSLContextAndOptions(ZKConfig config) throws S
380380
* @param keyStoreLocation the location of the key store file.
381381
* @param keyStorePassword optional password to decrypt the key store. If
382382
* empty, assumes the key store is not encrypted.
383-
* @param keyStoreTypeProp must be JKS, PEM, or null. If null, attempts to
384-
* autodetect the key store type from the file
385-
* extension (.jks / .pem).
383+
* @param keyStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If null,
384+
* attempts to autodetect the key store type from
385+
* the file extension (e.g. .jks / .pem).
386386
* @return the key manager.
387387
* @throws KeyManagerException if something goes wrong.
388388
*/
@@ -425,9 +425,9 @@ public static X509KeyManager createKeyManager(
425425
* @param trustStorePassword optional password to decrypt the trust store
426426
* (only applies to JKS trust stores). If empty,
427427
* assumes the trust store is not encrypted.
428-
* @param trustStoreTypeProp must be JKS, PEM, or null. If null, attempts
429-
* to autodetect the trust store type from the
430-
* file extension (.jks / .pem).
428+
* @param trustStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If
429+
* null, attempts to autodetect the trust store
430+
* type from the file extension (e.g. .jks / .pem).
431431
* @param crlEnabled enable CRL (certificate revocation list) checks.
432432
* @param ocspEnabled enable OCSP (online certificate status protocol)
433433
* checks.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.zookeeper.common;
20+
21+
import java.io.IOException;
22+
import java.security.KeyStore;
23+
import java.util.Collection;
24+
import org.junit.Assert;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.junit.runners.Parameterized;
28+
29+
30+
@RunWith(Parameterized.class)
31+
public class BCFKSFileLoaderTest extends BaseX509ParameterizedTestCase {
32+
33+
34+
@Parameterized.Parameters
35+
public static Collection<Object[]> params() {
36+
return BaseX509ParameterizedTestCase.defaultParams();
37+
}
38+
39+
public BCFKSFileLoaderTest(
40+
final X509KeyType caKeyType,
41+
final X509KeyType certKeyType,
42+
final String keyPassword,
43+
final Integer paramIndex) {
44+
super(paramIndex, () -> {
45+
try {
46+
return X509TestContext.newBuilder()
47+
.setTempDir(tempDir)
48+
.setKeyStorePassword(keyPassword)
49+
.setKeyStoreKeyType(certKeyType)
50+
.setTrustStorePassword(keyPassword)
51+
.setTrustStoreKeyType(caKeyType)
52+
.build();
53+
} catch (Exception e) {
54+
throw new RuntimeException(e);
55+
}
56+
});
57+
}
58+
59+
@Test
60+
public void testLoadKeyStore() throws Exception {
61+
String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
62+
KeyStore ks = new BCFKSFileLoader.Builder()
63+
.setKeyStorePath(path)
64+
.setKeyStorePassword(x509TestContext.getKeyStorePassword())
65+
.build()
66+
.loadKeyStore();
67+
Assert.assertEquals(1, ks.size());
68+
}
69+
70+
@Test(expected = Exception.class)
71+
public void testLoadKeyStoreWithWrongPassword() throws Exception {
72+
String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
73+
new BCFKSFileLoader.Builder()
74+
.setKeyStorePath(path)
75+
.setKeyStorePassword("wrong password")
76+
.build()
77+
.loadKeyStore();
78+
}
79+
80+
@Test(expected = IOException.class)
81+
public void testLoadKeyStoreWithWrongFilePath() throws Exception {
82+
String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
83+
new BCFKSFileLoader.Builder()
84+
.setKeyStorePath(path + ".does_not_exist")
85+
.setKeyStorePassword(x509TestContext.getKeyStorePassword())
86+
.build()
87+
.loadKeyStore();
88+
}
89+
90+
@Test(expected = NullPointerException.class)
91+
public void testLoadKeyStoreWithNullFilePath() throws Exception {
92+
new BCFKSFileLoader.Builder()
93+
.setKeyStorePassword(x509TestContext.getKeyStorePassword())
94+
.build()
95+
.loadKeyStore();
96+
}
97+
98+
@Test(expected = IOException.class)
99+
public void testLoadKeyStoreWithWrongFileType() throws Exception {
100+
// Trying to load a PEM file with BCFKS loader should fail
101+
String path = x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
102+
new BCFKSFileLoader.Builder()
103+
.setKeyStorePath(path)
104+
.setKeyStorePassword(x509TestContext.getKeyStorePassword())
105+
.build()
106+
.loadKeyStore();
107+
}
108+
109+
@Test
110+
public void testLoadTrustStore() throws Exception {
111+
String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
112+
KeyStore ts = new BCFKSFileLoader.Builder()
113+
.setTrustStorePath(path)
114+
.setTrustStorePassword(x509TestContext.getTrustStorePassword())
115+
.build()
116+
.loadTrustStore();
117+
Assert.assertEquals(1, ts.size());
118+
}
119+
120+
@Test(expected = Exception.class)
121+
public void testLoadTrustStoreWithWrongPassword() throws Exception {
122+
String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
123+
new BCFKSFileLoader.Builder()
124+
.setTrustStorePath(path)
125+
.setTrustStorePassword("wrong password")
126+
.build()
127+
.loadTrustStore();
128+
}
129+
130+
@Test(expected = IOException.class)
131+
public void testLoadTrustStoreWithWrongFilePath() throws Exception {
132+
String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.BCFKS).getAbsolutePath();
133+
new BCFKSFileLoader.Builder()
134+
.setTrustStorePath(path + ".does_not_exist")
135+
.setTrustStorePassword(x509TestContext.getTrustStorePassword())
136+
.build()
137+
.loadTrustStore();
138+
}
139+
140+
@Test(expected = NullPointerException.class)
141+
public void testLoadTrustStoreWithNullFilePath() throws Exception {
142+
new BCFKSFileLoader.Builder()
143+
.setTrustStorePassword(x509TestContext.getTrustStorePassword())
144+
.build()
145+
.loadTrustStore();
146+
}
147+
148+
@Test(expected = IOException.class)
149+
public void testLoadTrustStoreWithWrongFileType() throws Exception {
150+
// Trying to load a PEM file with BCFKS loader should fail
151+
String path = x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath();
152+
new BCFKSFileLoader.Builder()
153+
.setTrustStorePath(path)
154+
.setTrustStorePassword(x509TestContext.getTrustStorePassword())
155+
.build()
156+
.loadTrustStore();
157+
}
158+
159+
160+
}

0 commit comments

Comments
 (0)