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