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.net.Socket;
19  import java.security.AccessControlContext;
20  import java.security.AccessController;
21  import java.security.GeneralSecurityException;
22  import java.security.NoSuchAlgorithmException;
23  import java.security.NoSuchProviderException;
24  import java.security.Principal;
25  import java.security.PrivilegedAction;
26  import java.security.cert.CertPath;
27  import java.security.cert.CertificateException;
28  import java.security.cert.X509Certificate;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  import javax.security.auth.Subject;
37  import javax.security.auth.x500.X500PrivateCredential;
38  import net.jini.io.UnsupportedConstraintException;
39  import net.jini.security.AuthenticationPermission;
40  import org.apache.river.logging.Levels;
41  
42  /**
43   *
44   * @author peter
45   */
46  class ClientSubjectKeyManager extends SubjectKeyManager {
47      /** Client logger */
48      private static final Logger logger = CLIENT_LOGGER;
49     
50      
51      /** Returns the client logger */
52      Logger getLogger() {
53  	return logger;
54      }
55      
56      ClientSubjectKeyManager(Subject subject) throws NoSuchAlgorithmException, NoSuchProviderException {
57  	super(subject);
58      }
59  
60      public String[] getClientAliases(String keyType, Principal[] issuers) {
61  	List certPaths = getCertificateChains(getSubject());
62  	if (certPaths == null) {
63  	    return null;
64  	}
65  	Collection result = null;
66  	for (int i = certPaths.size(); --i >= 0;) {
67  	    CertPath chain = (CertPath) certPaths.get(i);
68  	    Exception exception;
69  	    try {
70  		if (checkChain(chain, keyType, issuers) != null) {
71  		    if (result == null) {
72  			result = new ArrayList(certPaths.size());
73  		    }
74  		    result.add(getCertificateName(firstX509Cert(chain)));
75  		}
76  		continue;
77  	    } catch (SecurityException e) {
78  		exception = e;
79  	    } catch (GeneralSecurityException ex) {
80  		exception = ex;
81  	    }
82  	    Logger logger = Logger.getLogger(SubjectKeyManager.class.getName());
83  	    if (logger.isLoggable(Levels.HANDLED)) {
84  	    }
85  	}
86  	if (result == null) {
87  	    return null;
88  	} else {
89  	    return (String[]) result.toArray(new String[result.size()]);
90  	}
91      }
92  
93      public synchronized String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
94  	/*
95  	 * Only choose new client credentials for the first handshake.
96  	 * Otherwise, just use the previous client credentials.
97  	 */
98  	if (clientCredentialException != null) {
99  	    return null;
100 	} else if (clientCredential == null) {
101 	    List exceptions = null;
102 	    for (int i = 0, l = keyTypes.length; i < l; i++) {
103 		Exception exception;
104 		try {
105 		    clientCredential = chooseCredential(keyTypes[i], issuers);
106 		    if (clientCredential != null) {
107 			break;
108 		    }
109 		    continue;
110 		} catch (GeneralSecurityException e) {
111 		    exception = e;
112 		} catch (SecurityException e) {
113 		    exception = e;
114 		}
115 		if (exceptions == null) {
116 		    exceptions = new ArrayList();
117 		}
118 		exceptions.add(exception);
119 	    }
120 	    if (clientCredential == null) {
121 		if (exceptions == null) {
122 		    clientCredentialException = new GeneralSecurityException("Credentials not found");
123 		} else if (exceptions.size() == 1) {
124 		    clientCredentialException = (Exception) exceptions.get(0);
125 		} else {
126 		    for (int i = exceptions.size(); --i >= 0;) {
127 			Exception e = (Exception) exceptions.get(i);
128 			if (!(e instanceof SecurityException)) {
129 			    clientCredentialException = new GeneralSecurityException(exceptions.toString());
130 			    break;
131 			}
132 		    }
133 		    if (clientCredentialException == null) {
134 			clientCredentialException = new SecurityException(exceptions.toString());
135 		    }
136 		}
137 		return null;
138 	    }
139 	}
140 	X509Certificate cert = clientCredential.getCertificate();
141 	clientPrincipal = cert.getSubjectX500Principal();
142 	credentialsValidUntil = Math.min(credentialsValidUntil, certificatesValidUntil(getCertificateChain(getSubject(), cert)));
143 	authenticationPermission = getAuthenticationPermission(cert);
144 	String result = getCertificateName(clientCredential.getCertificate());
145 		if (logger.isLoggable(Level.FINE)) {
146 		    logger.log(
147 			Level.FINE,
148 			"choose client alias for key types {0}\nand issuers {1}\n" +
149 			"returns {2}",
150 			new Object[] { toString(keyTypes), toString(issuers), result });
151 		}
152 	return result;
153     }
154 
155     /* -- X500TrustManager -- */
156     /**
157      * Override this X509TrustManager method in order to cache the server
158      * principal and to continue to choose the same one.
159      */
160     @Override
161     public synchronized void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
162 	super.checkServerTrusted(chain, authType);
163 	if (serverPrincipal == null) {
164 	    serverCredential = chain[0];
165 	    serverPrincipal = serverCredential.getSubjectX500Principal();
166 	    setPermittedRemotePrincipals(Collections.singleton(serverPrincipal));
167 	    credentialsValidUntil = certificatesValidUntil(chain);
168 	} else if (!serverCredential.equals(chain[0])) {
169 	    throw new CertificateException("Server credentials changed");
170 	}
171     }
172 
173     public String[] getServerAliases(String arg0, Principal[] arg1) {
174 	return null;
175     }
176 
177     public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
178 	return null;
179     }
180 
181     /**
182      * Returns the permission needed to connect to the last server principal
183      * with the specified client certificate.
184      */
185     AuthenticationPermission getAuthenticationPermission(X509Certificate cert) {
186 	Set client = Collections.singleton(cert.getSubjectX500Principal());
187 	Set server = (serverPrincipal == null) ? null : Collections.singleton(serverPrincipal);
188 	return new AuthenticationPermission(client, server, "connect");
189     }
190 
191     /**
192      * Gets the private credential for the specified X.509 certificate,
193      * checking for AuthenticationPermission to connect with the last server
194      * principal.
195      *
196      * @param cert the certificate for the local principal
197      * @return the associated private credential or null if not found
198      * @throws SecurityException if the access control context does not have
199      *	       the proper AuthenticationPermission
200      */
201     synchronized X500PrivateCredential getPrivateCredential(X509Certificate cert) {
202 	return getPrivateCredential(cert, getAuthenticationPermission(cert));
203     }
204 
205     /**
206      * Gets the private credential for the specified X.509 certificate,
207      * checking for the specified AuthenticationPermission.
208      *
209      * @param cert the certificate for the local principal
210      * @param ap the permission needed to connect to the peer
211      * @return the associated private credential or null if not found
212      * @throws SecurityException if the access control context does not have
213      *	       the proper AuthenticationPermission
214      */
215     protected X500PrivateCredential getPrivateCredential(X509Certificate cert, AuthenticationPermission ap) {
216 	Subject subject = getSubject();
217 	if (subject == null) {
218 	    return null;
219 	}
220 	SecurityManager sm = System.getSecurityManager();
221 	if (sm != null) {
222 	    sm.checkPermission(ap);
223 	}
224 	return (X500PrivateCredential) AccessController.doPrivileged(new GetPrivateCredentialAction(subject, cert));
225     }
226     
227     
228     /**
229      * Checks if the subject still contains the proper credentials, and the
230      * current access control context has the proper AuthenticationPermission,
231      * to use the current session.  Callers should only call this method if
232      * client authentication is being used.
233      *
234      * @throws SecurityException if the access control context does not have
235      *	       the proper AuthenticationPermission
236      * @throws UnsupportedConstraintException if the subject does not contain
237      *	       the proper credentials
238      */
239     synchronized void checkAuthentication()
240 	throws GeneralSecurityException
241     {
242 	if (clientCredential == null) {
243 	    throw new GeneralSecurityException(
244 		"Client is not authenticated");
245 	} else if (clientCredential.isDestroyed()) {
246 	    throw new GeneralSecurityException(
247 		"Private credentials are destroyed");
248 	} else if (System.currentTimeMillis() > credentialsValidUntil) {
249 	    throw new GeneralSecurityException(
250 		"Certificates are no longer valid");
251 	}
252 	if (subjectIsReadOnly) {
253 	    SecurityManager sm = System.getSecurityManager();
254 	    if (sm != null) {
255 		sm.checkPermission(authenticationPermission);
256 	    }
257 	} else {
258 	    Subject subject = getSubject();
259 	    X509Certificate cert = clientCredential.getCertificate();
260 	    if (getPrincipal(subject, cert) == null) {
261 		throw new GeneralSecurityException("Missing principal");
262 	    }
263 	    CertPath chain =
264 		getCertificateChain(subject, cert);
265 	    if (chain == null) {
266 		throw new GeneralSecurityException(
267 		    "Missing public credentials");
268 	    }
269 	    X500PrivateCredential pc = getPrivateCredential(
270 		cert, authenticationPermission);
271 	    if (pc == null) {
272 		throw new GeneralSecurityException(
273 		    "Missing private credentials");
274 	    } else if (!equalPrivateCredentials(clientCredential, pc)) {
275 		throw new GeneralSecurityException(
276 		    "Wrong private credentials");
277 	    }
278 	}
279     }
280 }