Skip to content

Commit f0277f2

Browse files
committed
ZOOKEEPER-5070: Support Single EKU certificates
Added client keystore, server truststore config
1 parent 53a78e3 commit f0277f2

10 files changed

Lines changed: 1320 additions & 12 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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.net.Socket;
22+
import java.security.Principal;
23+
import java.security.PrivateKey;
24+
import java.security.cert.X509Certificate;
25+
import javax.net.ssl.SSLEngine;
26+
import javax.net.ssl.X509ExtendedKeyManager;
27+
import javax.net.ssl.X509KeyManager;
28+
29+
/**
30+
* An {@link X509ExtendedKeyManager} that delegates client-mode and server-mode
31+
* key selection to separate underlying key managers. This allows a ZooKeeper
32+
* node to use different keystores (and therefore different certificates) for
33+
* its client role (outgoing connections) and server role (incoming connections),
34+
* enabling the use of certificates with a single Extended Key Usage (EKU).
35+
*/
36+
public class ClientServerX509KeyManager extends X509ExtendedKeyManager {
37+
38+
private static final String CLIENT_PREFIX = "client:";
39+
private static final String SERVER_PREFIX = "server:";
40+
41+
private final X509KeyManager clientKeyManager;
42+
private final X509KeyManager serverKeyManager;
43+
44+
public ClientServerX509KeyManager(X509KeyManager clientKeyManager, X509KeyManager serverKeyManager) {
45+
this.clientKeyManager = clientKeyManager;
46+
this.serverKeyManager = serverKeyManager;
47+
}
48+
49+
@Override
50+
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
51+
String alias = clientKeyManager.chooseClientAlias(keyType, issuers, socket);
52+
return alias != null ? CLIENT_PREFIX + alias : null;
53+
}
54+
55+
@Override
56+
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
57+
String alias = serverKeyManager.chooseServerAlias(keyType, issuers, socket);
58+
return alias != null ? SERVER_PREFIX + alias : null;
59+
}
60+
61+
@Override
62+
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
63+
if (clientKeyManager instanceof X509ExtendedKeyManager) {
64+
String alias = ((X509ExtendedKeyManager) clientKeyManager)
65+
.chooseEngineClientAlias(keyType, issuers, engine);
66+
return alias != null ? CLIENT_PREFIX + alias : null;
67+
}
68+
return chooseClientAlias(keyType, issuers, null);
69+
}
70+
71+
@Override
72+
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
73+
if (serverKeyManager instanceof X509ExtendedKeyManager) {
74+
String alias = ((X509ExtendedKeyManager) serverKeyManager)
75+
.chooseEngineServerAlias(keyType, issuers, engine);
76+
return alias != null ? SERVER_PREFIX + alias : null;
77+
}
78+
return chooseServerAlias(keyType, issuers, null);
79+
}
80+
81+
@Override
82+
public X509Certificate[] getCertificateChain(String alias) {
83+
if (alias == null) {
84+
return null;
85+
}
86+
if (alias.startsWith(CLIENT_PREFIX)) {
87+
return clientKeyManager.getCertificateChain(alias.substring(CLIENT_PREFIX.length()));
88+
}
89+
if (alias.startsWith(SERVER_PREFIX)) {
90+
return serverKeyManager.getCertificateChain(alias.substring(SERVER_PREFIX.length()));
91+
}
92+
return serverKeyManager.getCertificateChain(alias);
93+
}
94+
95+
@Override
96+
public PrivateKey getPrivateKey(String alias) {
97+
if (alias == null) {
98+
return null;
99+
}
100+
if (alias.startsWith(CLIENT_PREFIX)) {
101+
return clientKeyManager.getPrivateKey(alias.substring(CLIENT_PREFIX.length()));
102+
}
103+
if (alias.startsWith(SERVER_PREFIX)) {
104+
return serverKeyManager.getPrivateKey(alias.substring(SERVER_PREFIX.length()));
105+
}
106+
return serverKeyManager.getPrivateKey(alias);
107+
}
108+
109+
@Override
110+
public String[] getClientAliases(String keyType, Principal[] issuers) {
111+
String[] aliases = clientKeyManager.getClientAliases(keyType, issuers);
112+
return prefixAliases(aliases, CLIENT_PREFIX);
113+
}
114+
115+
@Override
116+
public String[] getServerAliases(String keyType, Principal[] issuers) {
117+
String[] aliases = serverKeyManager.getServerAliases(keyType, issuers);
118+
return prefixAliases(aliases, SERVER_PREFIX);
119+
}
120+
121+
private static String[] prefixAliases(String[] aliases, String prefix) {
122+
if (aliases == null) {
123+
return null;
124+
}
125+
String[] prefixed = new String[aliases.length];
126+
for (int i = 0; i < aliases.length; i++) {
127+
prefixed[i] = prefix + aliases[i];
128+
}
129+
return prefixed;
130+
}
131+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.net.Socket;
22+
import java.security.cert.CertificateException;
23+
import java.security.cert.X509Certificate;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import javax.net.ssl.SSLEngine;
28+
import javax.net.ssl.X509ExtendedTrustManager;
29+
30+
/**
31+
* An {@link X509ExtendedTrustManager} that delegates to separate trust managers for
32+
* client and server certificate validation. This allows configuring different truststores
33+
* for validating client certificates (when acting as a server) vs. server certificates
34+
* (when acting as a client).
35+
*
36+
* <ul>
37+
* <li>{@code checkServerTrusted} — delegates to the <b>client trust manager</b>
38+
* (validates server certs when this node connects as a client)</li>
39+
* <li>{@code checkClientTrusted} — delegates to the <b>server trust manager</b>
40+
* (validates client certs when this node accepts connections as a server)</li>
41+
* </ul>
42+
*/
43+
public class ClientServerX509TrustManager extends X509ExtendedTrustManager {
44+
45+
private final X509ExtendedTrustManager clientTrustManager;
46+
private final X509ExtendedTrustManager serverTrustManager;
47+
48+
/**
49+
* @param clientTrustManager used to validate server certificates (when acting as TLS client)
50+
* @param serverTrustManager used to validate client certificates (when acting as TLS server)
51+
*/
52+
public ClientServerX509TrustManager(X509ExtendedTrustManager clientTrustManager,
53+
X509ExtendedTrustManager serverTrustManager) {
54+
this.clientTrustManager = clientTrustManager;
55+
this.serverTrustManager = serverTrustManager;
56+
}
57+
58+
@Override
59+
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
60+
serverTrustManager.checkClientTrusted(chain, authType);
61+
}
62+
63+
@Override
64+
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
65+
serverTrustManager.checkClientTrusted(chain, authType, socket);
66+
}
67+
68+
@Override
69+
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
70+
serverTrustManager.checkClientTrusted(chain, authType, engine);
71+
}
72+
73+
@Override
74+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
75+
clientTrustManager.checkServerTrusted(chain, authType);
76+
}
77+
78+
@Override
79+
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
80+
clientTrustManager.checkServerTrusted(chain, authType, socket);
81+
}
82+
83+
@Override
84+
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
85+
clientTrustManager.checkServerTrusted(chain, authType, engine);
86+
}
87+
88+
@Override
89+
public X509Certificate[] getAcceptedIssuers() {
90+
X509Certificate[] clientIssuers = clientTrustManager.getAcceptedIssuers();
91+
X509Certificate[] serverIssuers = serverTrustManager.getAcceptedIssuers();
92+
List<X509Certificate> combined = new ArrayList<>(clientIssuers.length + serverIssuers.length);
93+
combined.addAll(Arrays.asList(clientIssuers));
94+
combined.addAll(Arrays.asList(serverIssuers));
95+
return combined.toArray(new X509Certificate[0]);
96+
}
97+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public SslContext createNettySslContextForClient(ZKConfig config)
6565
throws X509Exception.KeyManagerException, X509Exception.TrustManagerException, SSLException {
6666
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
6767

68-
KeyManager km = buildKeyManager(config);
68+
KeyManager km = buildClientKeyManager(config);
6969
if (km != null) {
7070
sslContextBuilder.keyManager(km);
7171
}
@@ -102,7 +102,7 @@ public SslContext createNettySslContextForServer(ZKConfig config)
102102
throw new X509Exception.SSLContextException(
103103
"Keystore is required for SSL server: " + getSslKeystoreLocationProperty());
104104
}
105-
return createNettySslContextForServer(config, km, buildTrustManager(config));
105+
return createNettySslContextForServer(config, km, buildServerTrustManager(config));
106106
}
107107

108108
public SslContext createNettySslContextForServer(ZKConfig config, KeyManager keyManager, TrustManager trustManager) throws SSLException {

0 commit comments

Comments
 (0)