View Javadoc
1   /*
2    * Copyright 2018 peter.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package au.net.zeus.rmi.tls;
17  
18  import java.lang.ref.WeakReference;
19  import java.security.GeneralSecurityException;
20  import java.security.KeyException;
21  import java.security.NoSuchAlgorithmException;
22  import java.security.NoSuchProviderException;
23  import java.security.Principal;
24  import java.security.PrivateKey;
25  import java.security.cert.CertPath;
26  import java.security.cert.CertificateException;
27  import java.security.cert.X509Certificate;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.List;
31  import java.util.logging.Logger;
32  import javax.net.ssl.X509KeyManager;
33  import javax.security.auth.Subject;
34  import javax.security.auth.x500.X500Principal;
35  import javax.security.auth.x500.X500PrivateCredential;
36  import net.jini.security.AuthenticationPermission;
37  import org.apache.river.logging.Levels;
38  
39  /**
40   *
41   * @author peter
42   */
43  abstract class SubjectKeyManager extends FilterX509TrustManager implements X509KeyManager {
44  
45  
46      
47      //Client Subject
48      final WeakReference<Subject> subjectRef;
49      
50      /**
51       * Whether the subject was read-only when it was stored -- used to avoid
52       * checking for changes in the contents of the subject.
53       */
54      final boolean subjectIsReadOnly;
55      
56          /** The server certificate chosen by the first handshake. */
57      protected X509Certificate serverCredential;
58  
59      /** The server principal chosen by the first handshake. */
60      protected X500Principal serverPrincipal;
61  
62      /**
63       * The private credential supplied by chooseClientAlias in the last
64       * handshake or null if none was supplied.
65       */
66      protected X500PrivateCredential clientCredential;
67  
68      /** The client principal chosen by the first handshake. */
69      protected X500Principal clientPrincipal;
70  
71      /**
72       * The exception that occurred within the last call to chooseClientAlias if
73       * no credential could be supplied.
74       */
75      protected Exception clientCredentialException;
76  
77      /**
78       * The latest time for which all client and server credentials remain
79       * valid.
80       */
81      protected long credentialsValidUntil = 0;
82  
83      /** The permission to check for the last cached credential */
84      protected AuthenticationPermission authenticationPermission;
85      
86      SubjectKeyManager(Subject subject)
87  	    throws NoSuchAlgorithmException, NoSuchProviderException
88      {
89  	subjectRef = new WeakReference<Subject>(subject);
90  	subjectIsReadOnly = subject.isReadOnly();
91      }
92      
93      /** Returns the logger to use for logging. */
94      abstract Logger getLogger();
95      
96      synchronized Subject getSubject(){
97  	return subjectRef.get();
98      }
99      
100     /**
101      * Checks if the specified certificate chain can be used for keys of the
102      * specified type and with the specified issuers.  Returns null if the
103      * chain has the wrong key type, throws an exception if the credentials or
104      * subject has problems, and otherwise returns the associated private
105      * credential. <p>
106      *
107      * Checks that:
108      * <ul>
109      * <li> The key algorithm matches that of the certificate's public key
110      * <li> The subject contains the principal for the certificate's issuer DN
111      * <li> That principal is a permitted local principal
112      * <li> The certificate chain terminates with one of the issuers, if issuers
113      *      are specified
114      * <li> The certificate chain is currently valid
115      * <li> If the certificate specifies a KeyUsage extension, that the
116      *      extension permits use in digital signatures
117      * <li> The caller has the proper AuthenticationPermission
118      * </ul> <p>
119      *
120      * Because the following things should only occur because of a
121      * configuration problem, this method does not check for:
122      * <ul>
123      * <li> Gaps in certificate chains
124      * <li> CA extensions
125      * <li> Incorrect private key
126      * </ul>
127      */
128     protected X500PrivateCredential checkChain(CertPath chain,
129 					     String keyType,
130 					     Principal[] issuers)
131       	throws GeneralSecurityException
132     {
133 	X509Certificate head = firstX509Cert(chain);
134 	String certKeyType = head.getPublicKey().getAlgorithm();
135 	if (!certKeyType.equals(keyType)) {
136 	    return null;
137 	}
138 	Subject subject = getSubject();
139 	X500Principal principal =
140 	    getPrincipal(subject, head);
141 	if (principal == null) {
142 	    throw new GeneralSecurityException(
143 		"Principal not found: " + head.getSubjectDN());
144 	} 
145 	X500Principal[] x500Issuers = null;
146 	if (issuers != null) {
147 	    x500Issuers = new X500Principal[issuers.length];
148 	    for (int i = issuers.length; --i >= 0; ) {
149 		x500Issuers[i] = (issuers[i] instanceof X500Principal)
150 		    ? (X500Principal) issuers[i]
151 		    : new X500Principal(issuers[i].getName());
152 	    }
153 	}
154 	checkValidity(chain, x500Issuers);
155 
156 	/*
157 	 * Check that a critical key usage extension, if present, permits use
158 	 * for digital signatures.
159 	 */
160 	boolean[] keyUsage = head.getKeyUsage();
161 	if (keyUsage != null
162 	    && keyUsage.length > 0
163 	    /* Element 0 is for digitalSignature */
164 	    && !keyUsage[0])
165 	{
166 	    throw new CertificateException(
167 		"Certificate not permitted for digital signatures: " + head);
168 	}
169 
170 	/* Also throws SecurityException */
171 	X500PrivateCredential xpc = getPrivateCredential(head);
172 	if (xpc == null) {
173 	    throw new KeyException(
174 		"Private key not found for certificate: " + head);
175 	}
176 
177 	return xpc;
178     }
179     
180     /**
181      * Gets the private credential for the specified X.509 certificate,
182      * checking for AuthenticationPermission to connect with the last server
183      * principal.
184      *
185      * @param cert the certificate for the local principal
186      * @return the associated private credential or null if not found
187      * @throws SecurityException if the access control context does not have
188      *	       the proper AuthenticationPermission
189      */
190     abstract X500PrivateCredential getPrivateCredential(X509Certificate cert);
191     
192 
193    
194 
195     public X509Certificate[] getCertificateChain(String alias) {
196 	CertPath chain = 
197 	    getCertificateChain(getSubject(), alias);
198 	List certs = chain.getCertificates();
199 	return (X509Certificate[]) certs.toArray(
200 	    new X509Certificate[certs.size()]);
201     }
202 
203     public PrivateKey getPrivateKey(String alias) {
204 	CertPath chain =
205 	    getCertificateChain(getSubject(), alias);
206 	if (chain != null) {
207 	    try {
208 		X500PrivateCredential xpc =
209 		    getPrivateCredential(firstX509Cert(chain));
210 		if (xpc != null) {
211 		    return xpc.getPrivateKey();
212 		}
213 	    } catch (SecurityException e) {
214 		Logger logger = getLogger();
215 		if (logger.isLoggable(Levels.HANDLED)) {
216 		    logThrow(logger, Levels.HANDLED,
217 			     SubjectKeyManager.class, "getPrivateKey",
218 			     "get private key for alias {0}\n" +
219 			     "caught exception",
220 			     new Object[] { alias },
221 			     e);
222 		}
223 	    }
224 	}
225 	return null;
226     }
227     
228     /**
229      * Returns a private credential that matches the specified key type and
230      * issuers for which checkChain returns a non-null value, or null if no
231      * matching credentials are found.  Throws a GeneralSecurityException or
232      * SecurityException if a problem occurs with all matching credentials.
233      */
234     X500PrivateCredential chooseCredential(String keyType,
235 					   Principal[] issuers)
236 	throws GeneralSecurityException
237     {
238 	List certPaths = getCertificateChains(getSubject());
239 	if (certPaths == null) {
240 	    return null;
241 	}
242 	List exceptions = null;
243 	for (int i = certPaths.size(); --i >= 0; ) {
244 	    CertPath chain = (CertPath) certPaths.get(i);
245 	    Exception exception;
246 	    try {
247 		X500PrivateCredential pc = checkChain(chain, keyType, issuers);
248 		if (pc == null) {
249 		    continue;
250 		} else {
251 		    return pc;
252 		}
253 	    } catch (GeneralSecurityException e) {
254 		exception = e;
255 	    } catch (SecurityException e) {
256 		exception = e;
257 	    }
258 	    if (exceptions == null) {
259 		exceptions = new ArrayList();
260 	    }
261 	    exceptions.add(exception);
262 	    Logger logger = getLogger();
263 	    if (logger.isLoggable(Levels.HANDLED)) {
264 		logThrow(logger, Levels.HANDLED,
265 			 SubjectKeyManager.class, "chooseCredential",
266 			 "choose credential for key type {0}\n" +
267 			 "and issuers {1}\ncaught exception",
268 			 new Object[] { keyType, toString(issuers) },
269 			 exception);
270 	    }
271 	}
272 	if (exceptions == null) {
273 	    return null;
274 	} else if (exceptions.size() > 1) {
275 	    for (int i = exceptions.size(); --i >= 0; ) {
276 		Exception e = (Exception) exceptions.get(i);
277 		if (!(e instanceof SecurityException)) {
278 		    throw new GeneralSecurityException(exceptions.toString());
279 		}
280 	    }
281 	    throw new SecurityException(exceptions.toString());
282 	} else if (exceptions.get(0) instanceof SecurityException) {
283 	    throw (SecurityException) exceptions.get(0);
284 	} else {
285 	    throw (GeneralSecurityException) exceptions.get(0);
286 	}
287     }
288     
289     /**
290      * Checks if the two private credentials refer to the same principal and
291      * have the equivalent private key.
292      */
293     boolean equalPrivateCredentials(X500PrivateCredential cred1,
294 				    X500PrivateCredential cred2)
295     {
296 	if (cred1 == null || cred2 == null) {
297 	    return false;
298 	}
299 	X509Certificate cert1 = cred1.getCertificate();
300 	X509Certificate cert2 = cred2.getCertificate();
301 	if (cert1 == null
302 	    || cert2 == null
303 	    || !safeEquals(cert1.getSubjectDN(), cert2.getSubjectDN()))
304 	{
305 	    return false;
306 	}
307 	/*
308 	 * I'm assuming I can depend on the equals method for private keys to
309 	 * check if the two objects represent the same key without being
310 	 * identical objects.  Although that behavior isn't documented, at
311 	 * least the sun.security.pkcs.PKCS8Key class does that.
312 	 * -tjb[8.Jan.2001]
313 	 */
314 	PrivateKey key1 = cred1.getPrivateKey();
315 	return key1 != null && key1.equals(cred2.getPrivateKey());
316     }
317 
318     /**
319      * Returns all the aliases that match the specified key type and issuers
320      * for which checkChain succeeds.  Returns null if no matching aliases are
321      * found.
322      */
323     String[] getAliases(String keyType, Principal[] issuers) {
324 	List certPaths = getCertificateChains(getSubject());
325 	if (certPaths == null) {
326 	    return null;
327 	}
328 	Collection result = null;
329 	for (int i = certPaths.size(); --i >= 0;) {
330 	    CertPath chain = (CertPath) certPaths.get(i);
331 	    Exception exception;
332 	    try {
333 		if (checkChain(chain, keyType, issuers) != null) {
334 		    if (result == null) {
335 			result = new ArrayList(certPaths.size());
336 		    }
337 		    result.add(getCertificateName(firstX509Cert(chain)));
338 		}
339 		continue;
340 	    } catch (GeneralSecurityException e) {
341 		exception = e;
342 	    } catch (SecurityException e) {
343 		exception = e;
344 	    }
345 	    	    Logger logger = getLogger();
346 	    	    if (logger.isLoggable(Levels.HANDLED)) {
347 	    		logThrow(logger, Levels.HANDLED,
348 	    			 SubjectKeyManager.class, "getAliases",
349 	    			 "get aliases for key type {0}\nand issuers {1}\n" +
350 	    			 "caught exception",
351 	    			 new Object[] { keyType, toString(issuers) },
352 	    			 exception);
353 	    	    }
354 	}
355 	if (result == null) {
356 	    return null;
357 	} else {
358 	    return (String[]) result.toArray(new String[result.size()]);
359 	}
360     }
361     
362 }