Skip to content

Commit 08bb0b4

Browse files
committedMar 22, 2025
Minor doc updates and code cleanup
1 parent 0c1a973 commit 08bb0b4

File tree

6 files changed

+626
-619
lines changed

6 files changed

+626
-619
lines changed
 

‎src/javaxt/express/KeyManager.java

Lines changed: 175 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,176 @@
1-
package javaxt.express;
2-
import java.net.InetAddress;
3-
import java.net.Socket;
4-
import java.security.KeyStore;
5-
import java.security.Principal;
6-
import java.security.PrivateKey;
7-
import java.security.cert.X509Certificate;
8-
import javax.net.ssl.*;
9-
10-
//******************************************************************************
11-
//** KeyManager
12-
//******************************************************************************
13-
/**
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.
31-
*
32-
******************************************************************************/
33-
34-
public class KeyManager extends X509ExtendedKeyManager { //implements X509KeyManager
35-
private KeyStore keyStore;
36-
private char[] password;
37-
private java.util.HashMap<InetAddress, String> aliases;
38-
39-
40-
//**************************************************************************
41-
//** Constructor
42-
//**************************************************************************
43-
public KeyManager(KeyStore keystore, char[] password, java.util.HashMap<InetAddress, String> aliases) {
44-
if (aliases==null || aliases.isEmpty()) throw new IllegalArgumentException("Hosts is null or empty.");
45-
this.keyStore = keystore;
46-
this.password = password;
47-
this.aliases = aliases;
48-
}
49-
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-
*/
60-
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
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;
70-
}
71-
}
72-
if (hostname==null) return null;
73-
else hostname = hostname.toLowerCase();
74-
75-
76-
//Get top-level domain name (acme.com)
77-
String[] arr = hostname.split("\\.");
78-
String domainName = arr[arr.length-2] + "." + arr[arr.length-1];
79-
80-
81-
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-
}
96-
}
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;
107-
}
108-
}
109-
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-
*/
119-
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
120-
//System.out.println("chooseServerAlias: " + socket.getLocalAddress());
121-
return aliases.get(socket.getLocalAddress());
122-
}
123-
124-
125-
//**************************************************************************
126-
//** getPrivateKey
127-
//**************************************************************************
128-
/** Returns the private key from the keystore for a given alias.
129-
*/
130-
public PrivateKey getPrivateKey(String alias) {
131-
try {
132-
return (PrivateKey) keyStore.getKey(alias, password);
133-
}
134-
catch (Exception e) {
135-
return null;
136-
}
137-
}
138-
139-
140-
//**************************************************************************
141-
//** getPrivateKey
142-
//**************************************************************************
143-
/** Returns the x509 certificate chain from the keystore for a given alias.
144-
*/
145-
public X509Certificate[] getCertificateChain(String alias) {
146-
try {
147-
java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias);
148-
if (certs == null || certs.length == 0) return null;
149-
X509Certificate[] x509 = new X509Certificate[certs.length];
150-
for (int i = 0; i < certs.length; i++){
151-
x509[i] = (X509Certificate)certs[i];
152-
}
153-
return x509;
154-
}
155-
catch (Exception e) {
156-
return null;
157-
}
158-
}
159-
160-
public String[] getServerAliases(String keyType, Principal[] issuers) {
161-
throw new UnsupportedOperationException("Method getServerAliases() not implemented.");
162-
}
163-
164-
public String[] getClientAliases(String keyType, Principal[] issuers) {
165-
throw new UnsupportedOperationException("Method getClientAliases() not implemented.");
166-
}
167-
168-
public String chooseClientAlias(String keyTypes[], Principal[] issuers, Socket socket) {
169-
throw new UnsupportedOperationException("Method chooseClientAlias() not implemented.");
170-
}
171-
172-
public String chooseEngineClientAlias(String[] strings, Principal[] prncpls, SSLEngine ssle) {
173-
throw new UnsupportedOperationException("Method chooseEngineClientAlias() not implemented.");
174-
}
1+
package javaxt.express;
2+
import java.util.*;
3+
import javax.net.ssl.*;
4+
import java.net.Socket;
5+
import java.net.InetAddress;
6+
import java.security.KeyStore;
7+
import java.security.Principal;
8+
import java.security.PrivateKey;
9+
import java.security.cert.X509Certificate;
10+
11+
//******************************************************************************
12+
//** KeyManager
13+
//******************************************************************************
14+
/**
15+
* Custom implementation of a X509KeyManager. This class is used to support
16+
* keystores with multiple SSL certificates. By default, the standard Java
17+
* X509KeyManager and the SunX509 implementation will pick the first alias
18+
* it finds for which there is a private key and a key type that matches
19+
* the chosen cipher suite (typically RSA).
20+
*
21+
* Instead, this class tries to find an alias in the keystore that best
22+
* matches the requested hostname found in the SSL handshake. This assumes
23+
* that the keystore aliases contain hostnames (e.g. "www.acme.com") or top
24+
* level domain names (e.g. "acme.com").
25+
*
26+
* In addition, this class requires a mapping of aliases/hostnames to IP
27+
* addresses on the host server. This is required for the chooseServerAlias()
28+
* method which is called early in the SSL handshake process (well before
29+
* the hostname is known). When the chooseServerAlias() method is called, all
30+
* we have is a IP address to identify the alias so a hashmap is used to tie
31+
* a domain name to an IP address.
32+
*
33+
******************************************************************************/
34+
35+
public class KeyManager extends X509ExtendedKeyManager { //implements X509KeyManager
36+
private KeyStore keyStore;
37+
private char[] password;
38+
private HashMap<InetAddress, String> aliases;
39+
40+
41+
//**************************************************************************
42+
//** Constructor
43+
//**************************************************************************
44+
public KeyManager(KeyStore keystore, char[] password, HashMap<InetAddress, String> aliases) {
45+
if (aliases==null || aliases.isEmpty()) throw new IllegalArgumentException("Hosts is null or empty.");
46+
this.keyStore = keystore;
47+
this.password = password;
48+
this.aliases = aliases;
49+
}
50+
51+
52+
//**************************************************************************
53+
//** chooseEngineServerAlias
54+
//**************************************************************************
55+
/** Returns an alias in the keystore that best matches the requested
56+
* hostname found in the SSL handshake
57+
* @param keyType Not used
58+
* @param issuers Not used
59+
* @param engine SSLEngine with a handshake session
60+
*/
61+
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
62+
try{
63+
64+
//Get hostname from SSL handshake (www.acme.com)
65+
String hostname = null;
66+
ExtendedSSLSession session = (ExtendedSSLSession) engine.getHandshakeSession();
67+
for (SNIServerName name : session.getRequestedServerNames()) {
68+
if (name.getType() == StandardConstants.SNI_HOST_NAME) {
69+
hostname = ((SNIHostName) name).getAsciiName();
70+
break;
71+
}
72+
}
73+
if (hostname==null) return null;
74+
else hostname = hostname.toLowerCase();
75+
76+
77+
//Get top-level domain name (acme.com)
78+
String[] arr = hostname.split("\\.");
79+
String domainName = arr[arr.length-2] + "." + arr[arr.length-1];
80+
81+
82+
83+
//Special case for keystores with wildcard certs and top-level domain
84+
//certs. When creating aliases for wildcard certs, I use the top-level
85+
//domain name as the alias (e.g. "acme.com" alias for a "*.acme.com"
86+
//wildcard cert). However, in some cases we also want a cert for the
87+
//top-level domain (e.g. "acme.com"). So for top-level domain certs,
88+
//I use an underscore prefix (e.g. "_acme.com" alias for a "acme.com"
89+
//cert). The following code will search for an alias with an
90+
//underscore prefix whenever a top-level domain name is requested.
91+
if (domainName.equals(hostname)){
92+
Enumeration enumeration = keyStore.aliases();
93+
while (enumeration.hasMoreElements()) {
94+
String alias = (String) enumeration.nextElement();
95+
if (alias.equals("_"+domainName)) return alias;
96+
}
97+
}
98+
99+
100+
101+
//Return the alias associated with the IP address of the top-level
102+
//domain name.
103+
return aliases.get(InetAddress.getByName(domainName));
104+
105+
}
106+
catch(Exception e){
107+
return null;
108+
}
109+
}
110+
111+
112+
//**************************************************************************
113+
//** chooseEngineServerAlias
114+
//**************************************************************************
115+
/** Returns an alias that best matches the given HTTP socket.
116+
* @param keyType Not used
117+
* @param issuers Not used
118+
* @param socket HTTP socket
119+
*/
120+
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
121+
//System.out.println("chooseServerAlias: " + socket.getLocalAddress());
122+
return aliases.get(socket.getLocalAddress());
123+
}
124+
125+
126+
//**************************************************************************
127+
//** getPrivateKey
128+
//**************************************************************************
129+
/** Returns the private key from the keystore for a given alias.
130+
*/
131+
public PrivateKey getPrivateKey(String alias) {
132+
try {
133+
return (PrivateKey) keyStore.getKey(alias, password);
134+
}
135+
catch (Exception e) {
136+
return null;
137+
}
138+
}
139+
140+
141+
//**************************************************************************
142+
//** getPrivateKey
143+
//**************************************************************************
144+
/** Returns the x509 certificate chain from the keystore for a given alias.
145+
*/
146+
public X509Certificate[] getCertificateChain(String alias) {
147+
try {
148+
java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias);
149+
if (certs == null || certs.length == 0) return null;
150+
X509Certificate[] x509 = new X509Certificate[certs.length];
151+
for (int i = 0; i < certs.length; i++){
152+
x509[i] = (X509Certificate)certs[i];
153+
}
154+
return x509;
155+
}
156+
catch (Exception e) {
157+
return null;
158+
}
159+
}
160+
161+
public String[] getServerAliases(String keyType, Principal[] issuers) {
162+
throw new UnsupportedOperationException("Method getServerAliases() not implemented.");
163+
}
164+
165+
public String[] getClientAliases(String keyType, Principal[] issuers) {
166+
throw new UnsupportedOperationException("Method getClientAliases() not implemented.");
167+
}
168+
169+
public String chooseClientAlias(String keyTypes[], Principal[] issuers, Socket socket) {
170+
throw new UnsupportedOperationException("Method chooseClientAlias() not implemented.");
171+
}
172+
173+
public String chooseEngineClientAlias(String[] strings, Principal[] prncpls, SSLEngine ssle) {
174+
throw new UnsupportedOperationException("Method chooseEngineClientAlias() not implemented.");
175+
}
175176
}

0 commit comments

Comments
 (0)