Skip to content

Commit 6bcb90f

Browse files
committed
Updated the KeyManager to support keystores with both wildcard certs and top-level domains certs using underscore prefix
1 parent a6afc09 commit 6bcb90f

File tree

1 file changed

+96
-43
lines changed

1 file changed

+96
-43
lines changed

src/javaxt/express/KeyManager.java

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,75 +11,122 @@
1111
//** KeyManager
1212
//******************************************************************************
1313
/**
14-
* Custom implementation of a X509KeyManager. This class is required to
15-
* support keystores with multiple SSL certificates. By default, the standard
16-
* Java X509KeyManager and the SunX509 implementation will pick the first
17-
* aliases it finds for which there is a private key and a key of the right
18-
* type for the chosen cipher suite (typically RSA). Instead, this class
19-
* relies on a map of hostnames and their corresponding IP addresses. When a
20-
* new SSL request is made, it checks the incoming IP address and finds the
21-
* corresponding hostname. Then, it tries to find an alias in the keystore
22-
* that corresponds to the hostname.
14+
* Custom implementation of a X509KeyManager. This class is used to support
15+
* keystores with multiple SSL certificates. By default, the standard Java
16+
* X509KeyManager and the SunX509 implementation will pick the first alias
17+
* it finds for which there is a private key and a key type that matches
18+
* the chosen cipher suite (typically RSA).
19+
*
20+
* Instead, this class tries to find an alias in the keystore that best
21+
* matches the requested hostname found in the SSL handshake. This assumes
22+
* that the keystore aliases contain hostnames (e.g. "www.acme.com") or top
23+
* level domain names (e.g. "acme.com").
24+
*
25+
* In addition, this class requires a mapping of aliases/hostnames to IP
26+
* addresses on the host server. This is required for the chooseServerAlias()
27+
* method which is called early in the SSL handshake process (well before
28+
* the hostname is known). When the chooseServerAlias() method is called, all
29+
* we have is a IP address to identify the alias so a hashmap is used to tie
30+
* a domain name to an IP address.
2331
*
2432
******************************************************************************/
2533

2634
public class KeyManager extends X509ExtendedKeyManager { //implements X509KeyManager
2735
private KeyStore keyStore;
2836
private char[] password;
29-
private String alias;
3037
private java.util.HashMap<InetAddress, String> aliases;
3138

32-
public KeyManager(KeyStore keystore, char[] password, String alias) {
33-
if (alias==null) throw new IllegalArgumentException("Alias is null.");
34-
this.keyStore = keystore;
35-
this.password = password;
36-
this.alias = alias;
37-
}
3839

40+
//**************************************************************************
41+
//** Constructor
42+
//**************************************************************************
3943
public KeyManager(KeyStore keystore, char[] password, java.util.HashMap<InetAddress, String> aliases) {
4044
if (aliases==null || aliases.isEmpty()) throw new IllegalArgumentException("Hosts is null or empty.");
4145
this.keyStore = keystore;
4246
this.password = password;
4347
this.aliases = aliases;
4448
}
4549

50+
51+
//**************************************************************************
52+
//** chooseEngineServerAlias
53+
//**************************************************************************
54+
/** Returns an alias in the keystore that best matches the requested
55+
* hostname found in the SSL handshake
56+
* @param keyType Not used
57+
* @param issuers Not used
58+
* @param engine SSLEngine with a handshake session
59+
*/
4660
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
47-
if (alias!=null) return alias;
48-
else{
49-
try{
50-
51-
//Get hostname from SSL handshake
52-
ExtendedSSLSession session = (ExtendedSSLSession) engine.getHandshakeSession();
53-
String hostname = null;
54-
for (SNIServerName name : session.getRequestedServerNames()) {
55-
if (name.getType() == StandardConstants.SNI_HOST_NAME) {
56-
hostname = ((SNIHostName) name).getAsciiName();
57-
break;
58-
}
61+
try{
62+
63+
//Get hostname from SSL handshake (www.acme.com)
64+
String hostname = null;
65+
ExtendedSSLSession session = (ExtendedSSLSession) engine.getHandshakeSession();
66+
for (SNIServerName name : session.getRequestedServerNames()) {
67+
if (name.getType() == StandardConstants.SNI_HOST_NAME) {
68+
hostname = ((SNIHostName) name).getAsciiName();
69+
break;
5970
}
71+
}
72+
if (hostname==null) return null;
73+
else hostname = hostname.toLowerCase();
6074

6175

62-
String[] arr = hostname.split("\\.");
63-
hostname = arr[arr.length-2] + "." + arr[arr.length-1];
76+
//Get top-level domain name (acme.com)
77+
String[] arr = hostname.split("\\.");
78+
String domainName = arr[arr.length-2] + "." + arr[arr.length-1];
6479

65-
//System.out.println("hostname: " + hostname);
66-
//System.out.println(InetAddress.getByName(hostname));
67-
//System.out.println(aliases.get(InetAddress.getByName(hostname)));
6880

6981

70-
return aliases.get(InetAddress.getByName(hostname));
71-
}
72-
catch(Exception e){
73-
return null;
82+
//Special case for keystores with wildcard certs and top-level domain
83+
//certs. When creating aliases for wildcard certs, I use the top-level
84+
//domain name as the alias (e.g. "acme.com" alias for a "*.acme.com"
85+
//wildcard cert). However, in some cases we also want a cert for the
86+
//top-level domain (e.g. "acme.com"). So for top-level domain certs,
87+
//I use an underscore prefix (e.g. "_acme.com" alias for a "acme.com"
88+
//cert). The following code will search for an alias with an
89+
//underscore prefix whenever a top-level domain name is requested.
90+
if (domainName.equals(hostname)){
91+
java.util.Enumeration enumeration = keyStore.aliases();
92+
while (enumeration.hasMoreElements()) {
93+
String alias = (String) enumeration.nextElement();
94+
if (alias.equals("_"+domainName)) return alias;
95+
}
7496
}
97+
98+
99+
100+
//Return the alias associated with the IP address of the top-level
101+
//domain name.
102+
return aliases.get(InetAddress.getByName(domainName));
103+
104+
}
105+
catch(Exception e){
106+
return null;
75107
}
76108
}
77109

110+
111+
//**************************************************************************
112+
//** chooseEngineServerAlias
113+
//**************************************************************************
114+
/** Returns an alias that best matches the given HTTP socket.
115+
* @param keyType Not used
116+
* @param issuers Not used
117+
* @param socket HTTP socket
118+
*/
78119
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
79-
if (alias!=null) return alias;
80-
else return aliases.get(socket.getLocalAddress());
120+
//System.out.println("chooseServerAlias: " + socket.getLocalAddress());
121+
return aliases.get(socket.getLocalAddress());
81122
}
82123

124+
125+
//**************************************************************************
126+
//** getPrivateKey
127+
//**************************************************************************
128+
/** Returns the private key from the keystore for a given alias.
129+
*/
83130
public PrivateKey getPrivateKey(String alias) {
84131
try {
85132
return (PrivateKey) keyStore.getKey(alias, password);
@@ -89,6 +136,12 @@ public PrivateKey getPrivateKey(String alias) {
89136
}
90137
}
91138

139+
140+
//**************************************************************************
141+
//** getPrivateKey
142+
//**************************************************************************
143+
/** Returns the x509 certificate chain from the keystore for a given alias.
144+
*/
92145
public X509Certificate[] getCertificateChain(String alias) {
93146
try {
94147
java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias);
@@ -105,18 +158,18 @@ public X509Certificate[] getCertificateChain(String alias) {
105158
}
106159

107160
public String[] getServerAliases(String keyType, Principal[] issuers) {
108-
throw new UnsupportedOperationException("Method getServerAliases() not yet implemented.");
161+
throw new UnsupportedOperationException("Method getServerAliases() not implemented.");
109162
}
110163

111164
public String[] getClientAliases(String keyType, Principal[] issuers) {
112-
throw new UnsupportedOperationException("Method getClientAliases() not yet implemented.");
165+
throw new UnsupportedOperationException("Method getClientAliases() not implemented.");
113166
}
114167

115168
public String chooseClientAlias(String keyTypes[], Principal[] issuers, Socket socket) {
116-
throw new UnsupportedOperationException("Method chooseClientAlias() not yet implemented.");
169+
throw new UnsupportedOperationException("Method chooseClientAlias() not implemented.");
117170
}
118171

119172
public String chooseEngineClientAlias(String[] strings, Principal[] prncpls, SSLEngine ssle) {
120-
throw new UnsupportedOperationException("Method chooseEngineClientAlias() not yet implemented.");
173+
throw new UnsupportedOperationException("Method chooseEngineClientAlias() not implemented.");
121174
}
122175
}

0 commit comments

Comments
 (0)