Skip to content

Commit e36794e

Browse files
committed
Add support for multiple server certificates
Introduce support for multiple certificates in the server SSL configuration. Tomcat configuration support multiple `Certificate` elements in `SSLHostConfig` of different type [1]. This functionality is supported by NSS which will select the most appropriate certificate as result of the negotiation. 1. https://tomcat.apache.org/tomcat-9.0-doc/config/http.html#SSL_Support_-_SSLHostConfig
1 parent 25942f0 commit e36794e

9 files changed

Lines changed: 233 additions & 103 deletions

File tree

base/src/main/java/org/mozilla/jss/ssl/javax/JSSEngine.java

Lines changed: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import java.util.Collection;
55
import java.util.EventListener;
66
import java.util.HashMap;
7+
import java.util.List;
78
import java.util.concurrent.atomic.AtomicBoolean;
89

910
import javax.net.ssl.SSLEngineResult;
1011
import javax.net.ssl.SSLException;
1112
import javax.net.ssl.SSLParameters;
1213
import javax.net.ssl.X509KeyManager;
1314
import javax.net.ssl.X509TrustManager;
15+
import org.apache.commons.lang3.tuple.ImmutablePair;
16+
import org.apache.commons.lang3.tuple.Pair;
1417

1518
import org.mozilla.jss.crypto.Policy;
1619
import org.mozilla.jss.nss.PR;
@@ -76,21 +79,16 @@ public abstract class JSSEngine extends javax.net.ssl.SSLEngine {
7679
/**
7780
* Certificate alias used by the JSSEngine instance.
7881
*/
79-
protected String certAlias;
82+
protected List<String> certAliases;
8083

8184
/**
82-
* Certificate used by this JSSEngine instance.
85+
* Collection of certificates and related keys used by this JSSEngine instance.
8386
*
8487
* Selected and inferred from the KeyManagers passed, when not passed
8588
* explicitly (either during construction or with a call to
8689
* setKeyMaterials(...)).
8790
*/
88-
protected PK11Cert cert;
89-
90-
/**
91-
* Key corresponding to the local certificate.
92-
*/
93-
protected PK11PrivKey key;
91+
protected List<Pair<PK11Cert, PK11PrivKey>> certs;
9492

9593
/**
9694
* A list of all KeyManagers available to this JSSEngine instance.
@@ -197,7 +195,7 @@ public abstract class JSSEngine extends javax.net.ssl.SSLEngine {
197195
* Set of cached server sockets based on the PK11Cert they were
198196
* initialized with.
199197
*/
200-
protected static HashMap<PK11Cert, SSLFDProxy> serverTemplates = new HashMap<>();
198+
protected static HashMap<List<Pair<PK11Cert, PK11PrivKey>>, SSLFDProxy> serverTemplates = new HashMap<>();
201199

202200
/**
203201
* Whether or not the session cache has been initialized already.
@@ -255,9 +253,8 @@ public JSSEngine(String peerHost, int peerPort,
255253
org.mozilla.jss.crypto.X509Certificate localCert,
256254
org.mozilla.jss.crypto.PrivateKey localKey) {
257255
super(peerHost, peerPort);
258-
259-
cert = (PK11Cert) localCert;
260-
key = (PK11PrivKey) localKey;
256+
certs = new ArrayList<>();
257+
certs.add(ImmutablePair.of((PK11Cert) localCert, (PK11PrivKey) localKey));
261258

262259
session = new JSSSession(this, BUFFER_SIZE);
263260
session.setPeerHost(peerHost);
@@ -338,7 +335,7 @@ public JSSParameters getSSLParameters() {
338335
ret.setWantClientAuth(true);
339336
}
340337

341-
ret.setAlias(certAlias);
338+
ret.setAliases(certAliases);
342339
ret.setHostname(hostname);
343340
ret.setListeners(listeners);
344341

@@ -405,8 +402,9 @@ public void setSSLParameters(SSLParameters params) {
405402
// them from the alias specified... We assume that when the SSLEngine
406403
// has a certificate already, we want to use them, even if parsed has
407404
// a null certificate.
408-
if (parsed.getAlias() != null && key_managers != null && key_managers.length > 0 && cert == null && key == null) {
409-
setCertFromAlias(parsed.getAlias());
405+
if (parsed.getAliases() != null && !parsed.getAliases().isEmpty() && key_managers != null && key_managers.length > 0
406+
&& (certs == null || certs.isEmpty())) {
407+
setCertFromAliases(parsed.getAliases());
410408
}
411409

412410
// When we have a value for the peer hostname, we should try and use
@@ -452,54 +450,64 @@ public void setHostname(String name) {
452450
*
453451
*/
454452
public void setCertFromAlias(String alias) throws IllegalArgumentException {
455-
if (alias == null) {
453+
List<String> aliases = new ArrayList<>();
454+
if (alias != null) {
455+
aliases.add(alias);
456+
}
457+
setCertFromAliases(aliases);
458+
}
459+
460+
public void setCertFromAliases(List<String> aliases) throws IllegalArgumentException {
461+
if (aliases == null || aliases.isEmpty()) {
456462
// Per calling, semantics, get rid of any existing cert/key we
457463
// might have.
458-
certAlias = null;
459-
cert = null;
460-
key = null;
464+
certAliases = null;
465+
certs = null;
461466
return;
462467
}
463468

464-
certAlias = alias;
469+
certAliases = aliases;
470+
certs = new ArrayList<>();
465471

466472
if (key_managers == null || key_managers.length == 0) {
467473
String msg = "Missing or null KeyManagers; refusing to search ";
468474
msg += "for cert";
469475
throw new IllegalArgumentException(msg);
470476
}
471477

472-
for (X509KeyManager key_manager : key_managers) {
473-
if (key_manager == null) {
474-
// Skip this key_manager. This case could occur when
475-
// setKeyManagers(...) is passed an array containing the value
476-
// null, but otherwise shouldn't happen.
477-
continue;
478-
}
478+
for (String alias: certAliases) {
479+
for (X509KeyManager key_manager : key_managers) {
480+
if (key_manager == null) {
481+
// Skip this key_manager. This case could occur when
482+
// setKeyManagers(...) is passed an array containing the value
483+
// null, but otherwise shouldn't happen.
484+
continue;
485+
}
479486

480-
if (!(key_manager instanceof JSSKeyManager)) {
481-
// We're explicitly looking for a JSSKeyManager; skip this if
482-
// it doesn't match.
483-
continue;
484-
}
487+
if (!(key_manager instanceof JSSKeyManager)) {
488+
// We're explicitly looking for a JSSKeyManager; skip this if
489+
// it doesn't match.
490+
continue;
491+
}
485492

486-
JSSKeyManager jkm = (JSSKeyManager) key_manager;
493+
JSSKeyManager jkm = (JSSKeyManager) key_manager;
487494

488-
// While the return type of CryptoManager.findCertByNickname is
489-
// technically org.mozilla.jss.crypto.X509Certificate, in practice
490-
// they are always PK11Cert instances. We're going to need an
491-
// instance of PK11Cert anyways, in order to correctly pass it to
492-
// the native layer.
493-
cert = (PK11Cert) jkm.getCertificate(alias);
494-
key = (PK11PrivKey) jkm.getPrivateKey(alias);
495+
// While the return type of CryptoManager.findCertByNickname is
496+
// technically org.mozilla.jss.crypto.X509Certificate, in practice
497+
// they are always PK11Cert instances. We're going to need an
498+
// instance of PK11Cert anyways, in order to correctly pass it to
499+
// the native layer.
500+
PK11Cert cert = (PK11Cert) jkm.getCertificate(alias);
501+
PK11PrivKey key = (PK11PrivKey) jkm.getPrivateKey(alias);
495502

496-
if (cert != null && key != null) {
497-
// Found a cert and key matching our alias; exit.
498-
break;
503+
if (cert != null && key != null) {
504+
// Found a cert and key matching our alias; exit.
505+
certs.add(ImmutablePair.of(cert, key));
506+
break;
507+
}
499508
}
500509
}
501-
502-
if (cert == null && key == null) {
510+
if (certs.isEmpty()) {
503511
String msg = "JSSEngine.setCertFromAlias: Unable to find ";
504512
msg += "certificate and key for specified alias!";
505513
throw new IllegalArgumentException(msg);
@@ -771,8 +779,10 @@ public void setKeyMaterials(PK11Cert our_cert, PK11PrivKey our_key) throws Illeg
771779
throw new IllegalArgumentException("JSSEngine.setKeyMaterials(): Either both cert and key must be null or both must be not-null");
772780
}
773781

774-
cert = our_cert;
775-
key = our_key;
782+
if (certs == null) {
783+
certs = new ArrayList<>();
784+
}
785+
certs.add(ImmutablePair.of(our_cert, our_key));
776786
}
777787

778788
/**
@@ -1093,23 +1103,25 @@ public void setConfiguration(HashMap<Integer, Integer> config) {
10931103
/**
10941104
* Returns the templated server certificate, if one exists.
10951105
*/
1096-
protected static SSLFDProxy getServerTemplate(PK11Cert cert, PK11PrivKey key) {
1097-
if (cert == null || key == null) {
1106+
protected static SSLFDProxy getServerTemplate(List<Pair<PK11Cert, PK11PrivKey>> lstCerts) {
1107+
if (lstCerts == null || lstCerts.isEmpty()) {
10981108
return null;
10991109
}
11001110

1101-
SSLFDProxy fd = serverTemplates.get(cert);
1111+
SSLFDProxy fd = serverTemplates.get(lstCerts);
11021112
if (fd == null) {
11031113
PRFDProxy base = PR.NewTCPSocket();
11041114
fd = SSL.ImportFD(null, base);
1105-
if (SSL.ConfigServerCert(fd, cert, key) != SSL.SECSuccess) {
1106-
String msg = "Unable to configure certificate and key on ";
1107-
msg += "model SSL PRFileDesc proxy: ";
1108-
msg += errorText(PR.GetError());
1109-
throw new RuntimeException(msg);
1115+
for(Pair<PK11Cert, PK11PrivKey> pairKey: lstCerts) {
1116+
if (SSL.ConfigServerCert(fd, pairKey.getLeft(), pairKey.getRight()) != SSL.SECSuccess) {
1117+
String msg = "Unable to configure certificate and key on ";
1118+
msg += "model SSL PRFileDesc proxy: ";
1119+
msg += errorText(PR.GetError());
1120+
throw new RuntimeException(msg);
1121+
}
11101122
}
1111-
1112-
serverTemplates.put(cert, fd);
1123+
1124+
serverTemplates.put(lstCerts, fd);
11131125
}
11141126

11151127
return fd;

base/src/main/java/org/mozilla/jss/ssl/javax/JSSEngineReferenceImpl.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
import java.nio.channels.WritableByteChannel;
1010
import java.security.PublicKey;
1111
import java.security.cert.CertificateException;
12+
import java.util.ArrayList;
13+
import java.util.List;
1214

1315
import javax.net.ssl.SSLEngineResult;
1416
import javax.net.ssl.SSLException;
1517
import javax.net.ssl.SSLHandshakeException;
1618
import javax.net.ssl.SSLPeerUnverifiedException;
1719
import javax.net.ssl.X509ExtendedTrustManager;
1820
import javax.net.ssl.X509TrustManager;
21+
import org.apache.commons.lang3.tuple.Pair;
1922

2023
import org.mozilla.jss.nss.BadCertHandler;
2124
import org.mozilla.jss.nss.Buffer;
@@ -31,6 +34,7 @@
3134
import org.mozilla.jss.nss.SSLPreliminaryChannelInfo;
3235
import org.mozilla.jss.nss.SecurityStatusResult;
3336
import org.mozilla.jss.pkcs11.PK11Cert;
37+
import org.mozilla.jss.pkcs11.PK11PrivKey;
3438
import org.mozilla.jss.provider.javax.crypto.JSSNativeTrustManager;
3539
import org.mozilla.jss.ssl.SSLAlertDescription;
3640
import org.mozilla.jss.ssl.SSLAlertEvent;
@@ -331,7 +335,7 @@ private void createBufferFD() throws SSLException {
331335
// re-creating it from scratch. This saves a significant amount of
332336
// time during construction. The implementation lives in JSSEngine,
333337
// to be shared by all other JSSEngine implementations.
334-
model = getServerTemplate(cert, key);
338+
model = getServerTemplate(certs);
335339
}
336340

337341
// Initialize ssl_fd from the model Buffer-backed PRFileDesc.
@@ -366,13 +370,17 @@ private void createBufferFD() throws SSLException {
366370
private void initClient() throws SSLException {
367371
debug("JSSEngine: initClient()");
368372

369-
if (cert != null && key != null) {
373+
if (certs != null && !certs.isEmpty()) {
370374
// NSS uses a callback to check for the client certificate; we
371375
// assume we have knowledge of it ahead of time and set it
372376
// directly on our SSLFDProxy instance.
373377
//
374378
// In the future, we could use a KeyManager for inquiring at
375379
// selection time which certificate to use.
380+
381+
// For the clients only the first certificate is used.
382+
// Multiple certificate could be configure if it is needed.
383+
PK11Cert cert = certs.iterator().next().getLeft();
376384
debug("JSSEngine.initClient(): Enabling client auth: " + cert);
377385
ssl_fd.SetClientCert(cert);
378386
if (SSL.AttachClientCertCallback(ssl_fd) != SSL.SECSuccess) {
@@ -400,14 +408,18 @@ private void initServer() throws SSLException {
400408

401409
// The only time cert and key are strictly required are when we're
402410
// creating a server SSLEngine.
403-
if (cert == null || key == null) {
411+
if (certs == null) {
404412
throw new IllegalArgumentException("JSSEngine: must be initialized with server certificate and key!");
405413
}
406414

407-
debug("JSSEngine.initServer(): " + cert);
408-
debug("JSSEngine.initServer(): " + key);
415+
debug("JSSEngine.initServer(): " + certs);
409416

410-
session.setLocalCertificates(new PK11Cert[]{ cert } );
417+
List<PK11Cert> lstCerts = new ArrayList<>();
418+
for (Pair<PK11Cert, PK11PrivKey> pairKeys: certs) {
419+
lstCerts.add(pairKeys.getLeft());
420+
}
421+
422+
session.setLocalCertificates(lstCerts.toArray(new PK11Cert[0]));
411423

412424
// Create a small server session cache.
413425
//

base/src/main/java/org/mozilla/jss/ssl/javax/JSSParameters.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.mozilla.jss.ssl.javax;
22

3-
import javax.net.ssl.*;
4-
import java.util.*;
5-
6-
import org.mozilla.jss.ssl.*;
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.EventListener;
6+
import java.util.List;
7+
import javax.net.ssl.SSLParameters;
8+
import org.mozilla.jss.ssl.SSLCipher;
9+
import org.mozilla.jss.ssl.SSLVersion;
10+
import org.mozilla.jss.ssl.SSLVersionRange;
711

812
/**
913
* JSSParameters is an implementation of SSLParameters to interoperate
@@ -24,7 +28,7 @@
2428
public class JSSParameters extends SSLParameters {
2529
private SSLCipher[] suites;
2630
private SSLVersionRange range;
27-
private String alias;
31+
private List<String> aliases;
2832
private String hostname;
2933
private Collection<? extends EventListener> listeners;
3034

@@ -180,20 +184,20 @@ public SSLVersionRange getSSLVersionRange() {
180184
return range;
181185
}
182186

183-
public String getAlias() {
184-
return alias;
187+
public List<String> getAliases() {
188+
return aliases;
185189
}
186190

187-
public void setAlias(String cert_alias) {
188-
alias = cert_alias;
191+
public void setAliases(List<String> certAliases) {
192+
aliases = certAliases;
189193
}
190194

191195
public String getHostname() {
192196
return hostname;
193197
}
194198

195-
public void setHostname(String server_hostname) {
196-
hostname = server_hostname;
199+
public void setHostname(String serverHostname) {
200+
hostname = serverHostname;
197201
}
198202

199203
public Collection<? extends EventListener> getListeners() {

base/src/test/java/org/mozilla/jss/tests/TestSSLEngine.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.security.KeyStore;
55
import java.util.ArrayList;
66
import java.util.Arrays;
7+
import java.util.Collection;
78

89
import javax.net.ssl.KeyManager;
910
import javax.net.ssl.KeyManagerFactory;
@@ -166,7 +167,9 @@ public static JSSParameters createParameters() throws Exception {
166167
public static JSSParameters createParameters(String alias) throws Exception {
167168
JSSParameters params = new JSSParameters();
168169

169-
params.setAlias(alias);
170+
String[] aliases = alias.split(",");
171+
172+
params.setAliases(Arrays.asList(aliases));
170173
params.setHostname("localhost");
171174

172175
return params;

0 commit comments

Comments
 (0)