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.AccessController;
20  import java.security.GeneralSecurityException;
21  import java.security.NoSuchAlgorithmException;
22  import java.security.NoSuchProviderException;
23  import java.security.Principal;
24  import java.security.PrivateKey;
25  import java.security.PrivilegedAction;
26  import java.security.cert.CertPath;
27  import java.security.cert.X509Certificate;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  import java.util.logging.Level;
38  import java.util.logging.Logger;
39  import javax.net.ssl.SSLSession;
40  import javax.net.ssl.SSLSessionContext;
41  import javax.security.auth.Subject;
42  import javax.security.auth.x500.X500Principal;
43  import javax.security.auth.x500.X500PrivateCredential;
44  import net.jini.security.AuthenticationPermission;
45  import org.apache.river.logging.Levels;
46  
47  /**
48   *
49   * @author peter
50   */
51  class ServerSubjectKeyManager extends SubjectKeyManager{
52      
53      /** Server transport logger */
54      private static final Logger logger = SERVER_LOGGER;
55  
56      /** The SSLSessionContext for all connections. */
57      private final SSLSessionContext sslSessionContext;
58  
59      /** The subject's private credentials, if the subject is read-only. */
60      private final X500PrivateCredential[] readOnlyPrivateCredentials;
61  
62      /**
63       * Maps a key type to last private credentials returned for that key type,
64       * or a String describing problems that prevented getting private
65       * credentials.
66       */
67      private final Map credentialCache = new HashMap(2);
68      
69      /** Returns the server logger */
70      Logger getLogger() {
71  	return logger;
72      }
73  
74      /**
75       * Creates an AuthManager that retrieves principals and credentials for
76       * authentication from the specified subject.
77       *
78       * @param subject the subject for retrieving principals and credentials
79       * @throws NoSuchAlgorithmException if the trust manager factory algorithm
80       *	       is not found
81       */
82      ServerSubjectKeyManager(Subject subject,
83  			    SSLSessionContext sslSessionContext)
84  	throws NoSuchAlgorithmException, NoSuchProviderException
85      {
86  	super(subject);
87  	this.sslSessionContext = sslSessionContext;
88  	readOnlyPrivateCredentials =
89  	    !subjectIsReadOnly || subject == null ? null
90  	    : (X500PrivateCredential[]) AccessController.doPrivileged(
91  		new GetAllPrivateCredentialsAction(
92  		    subject));
93      }
94  
95      public String[] getClientAliases(String keyType, Principal[] issuers) {
96  	return null;
97      }
98  
99      public String[] getServerAliases(String keyType, Principal[] issuers) {
100 	String[] result = getAliases(keyType, issuers);
101 	if (logger.isLoggable(Level.FINE)) {
102 	    logger.log(Level.FINE,
103 		       "get server aliases for key type {0}\n" +
104 		       "and issuers {1}\nreturns {2}",
105 		       new Object[] {
106 			   keyType, toString(issuers), toString(result)
107 		       });
108 	}
109 	return result;
110     }
111     
112     public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
113 	return null;
114     }
115     
116     /*
117      * Returns the last server credential selected for this key type, if still
118      * usable.  If not, then invalidate all sessions with the same key type and
119      * attempt to find another key.
120      */
121     @Override
122     public String chooseServerAlias(
123 	String keyType, Principal[] issuers, Socket socket)
124     {
125 	X500PrivateCredential cred = null;
126 	synchronized (credentialCache) {
127 	    Object val = credentialCache.get(keyType);
128 	    if (val instanceof X500PrivateCredential) {
129 		cred = (X500PrivateCredential) val;
130 		try {
131                         checkCredentials(cred, null, "listen");
132 		} catch (SecurityException e) {
133 		    if (logger.isLoggable(Levels.HANDLED)) {
134 			logThrow(logger, Levels.HANDLED,
135 				 ServerSubjectKeyManager.class, "chooseServerAlias",
136 				 "choose server alias for key type {0}\n" +
137 				 "and issuers {1}\ncaught exception",
138 				 new Object[] { keyType, toString(issuers) },
139 				 e);
140 		    }
141 		    /*
142 		     * This credential is no longer present or we don't have
143 		     * permission to use it.  Clear the cache and invalidate
144 		     * sessions with this key type.
145 		     */
146 		    cred = null;
147 		    credentialCache.remove(keyType);
148 		    for (Enumeration en = sslSessionContext.getIds();
149 			 en.hasMoreElements(); )
150 		    {
151 			SSLSession session =
152 			    sslSessionContext.getSession(
153 				(byte[]) en.nextElement());
154 			if (session != null) {
155 			    String suite = session.getCipherSuite();
156 			    if (keyType.equals(getKeyAlgorithm(suite))) {
157 				session.invalidate();
158 			    }
159 			}
160 		    }
161 		}
162 	    }
163 	    if (cred == null) {
164 		/* Try to select a new alias */
165 		Exception exception = null;
166 		try {
167 		    cred = chooseCredential(keyType, issuers);
168 		    if (cred != null) {
169 			credentialCache.put(keyType, cred);
170 		    }
171 		} catch (GeneralSecurityException e) {
172 		    exception = e;
173 		} catch (SecurityException e) {
174 		    exception = e;
175 		}
176 		if (exception != null) {
177 		    credentialCache.put(keyType, exception.getMessage());
178 		    return null;
179 		}
180 	    }
181 	}
182 	String result = (cred == null)
183 	    ? null
184 	    : getCertificateName(cred.getCertificate());
185 	    if (logger.isLoggable(Level.FINE)) {
186 		logger.log(Level.FINE,
187 			   "choose server alias for key type {0}\nissuers {1}\n" +
188 			   "returns {2}",
189 			   new Object[] { keyType, toString(issuers), result });
190 	    }
191 	return result;
192     }
193     
194     /**
195      * Checks that the principals and credentials associated with the specified
196      * private credential are present and valid in the server subject, and that
197      * the caller has permission to access them given the specified client
198      * subject and permission action.  Returns the time until which the
199      * certificates are valid if successful, otherwise throws
200      * SecurityException.  The clientSubject should be read-only if it is not
201      * null.
202      */
203     private long checkCredentials(X500PrivateCredential cred,
204 				  Subject clientSubject,
205 				  String permissionAction)
206     {
207 	Subject subject = getSubject();
208 	if (subject == null) {
209 	    throw new SecurityException("Missing subject");
210 	}
211 	X509Certificate cert = cred.getCertificate();
212 	if (getPrincipal(subject, cert) == null) {
213 	    throw new SecurityException("Missing principal");
214 	}
215 	CertPath chain =
216 	    getCertificateChain(subject, cert);
217 	if (chain == null) {
218 	    throw new SecurityException("Missing public credentials");
219 	}
220 	long validUntil = certificatesValidUntil(chain);
221 	if (clientSubject != null) {
222 	    assert clientSubject.isReadOnly();
223 	    CertPath clientChain = (CertPath)
224 		clientSubject.getPublicCredentials().iterator().next();
225 	    validUntil = Math.min(
226 		validUntil, certificatesValidUntil(clientChain));
227 	}
228 	if (System.currentTimeMillis() > validUntil) {
229 	    throw new SecurityException("Certificates no longer valid");
230 	}
231 	String peer = getPeerPrincipalName(clientSubject);
232 	X500PrivateCredential pc =
233 	    getPrivateCredential(cert, peer, permissionAction);
234 	if (pc == null) {
235 	    throw new SecurityException("Missing private credentials");
236 	} else if (!equalPrivateCredentials(cred, pc)) {
237 	    throw new SecurityException("Wrong private credential");
238 	}
239 	return validUntil;
240     }
241     
242     /**
243      * Checks for AuthenticationPermission to accept for the specified local
244      * and peer principals.  The peer is specified as a String to avoid needing
245      * to use the separate X.509 certificate type that JSSE uses for peer
246      * certificate chains.
247      *
248      * @param cert the certificate for the local principal
249      * @param peer the name of the peer principal or null if not known
250      * @param permissionAction the AuthenticationPermission action
251      * @return the associated private credential or null if not found
252      * @throws SecurityException if the current access control context does not
253      *	       have the proper AuthenticationPermission
254      */
255     private X500PrivateCredential getPrivateCredential(X509Certificate cert,
256 						       String peer,
257 						       String permissionAction)
258     {
259 	Subject subject = getSubject();
260 	if (subject == null) {
261 	    return null;
262 	}
263 
264 	/* We can't check permission with RMI because there is an empty
265 	   unprivileged domain on the stack, to do so would require granting
266 	   this permission to everyone, just to complete a handshake. 
267 	   That would be a generally bad idea.  Instead
268 	   these methods remain private and inaccessible so the details
269 	   don't leak outside the api */
270 //	SecurityManager sm = System.getSecurityManager();
271 //	if (sm != null) {
272 //	    sm.checkPermission(
273 //		getAuthPermission(cert, peer, permissionAction));
274 //	}
275 
276 	if (subjectIsReadOnly) {
277 	    for (int i = readOnlyPrivateCredentials.length; --i >= 0; ) {
278 		X500PrivateCredential xpc = readOnlyPrivateCredentials[i];
279 		if (cert.equals(xpc.getCertificate())) {
280 		    return xpc;
281 		}
282 	    }
283 	    return null;
284 	}
285 	return (X500PrivateCredential) AccessController.doPrivileged(
286 	    new GetPrivateCredentialAction(subject, cert));
287     }
288     
289     /**
290      * Returns the authentication permission for the specified principals and
291      * action.
292      */
293     private AuthenticationPermission getAuthPermission(X509Certificate cert,
294 						       String peer,
295 						       String action)
296     {
297 	Set server = Collections.singleton(cert.getSubjectX500Principal());
298 	Set client = (peer == null)
299 	    ? null : Collections.singleton(new X500Principal(peer));
300 	return new AuthenticationPermission(server, client, action);
301     }
302     
303    
304     
305   /**
306      * Returns the name of the principal for the peer subject, which should be
307      * read-only if it is not null.
308      */
309     private String getPeerPrincipalName(Subject peerSubject) {
310 	if (peerSubject == null) {
311 	    return null;
312 	}
313 	assert peerSubject.isReadOnly();
314 	Principal p =
315 	    (Principal) peerSubject.getPrincipals().iterator().next();
316 	return p.getName();
317     }
318 
319     /**
320      * Gets the private credential for the specified X.509 certificate,
321      * checking for AuthenticationPermission to connect with the last server
322      * principal.
323      *
324      * @param cert the certificate for the local principal
325      * @return the associated private credential or null if not found
326      * @throws SecurityException if the access control context does not have
327      *	       the proper AuthenticationPermission
328      */
329     synchronized X500PrivateCredential getPrivateCredential(X509Certificate cert) {
330 	return getPrivateCredential(cert, (String) null, "listen");
331     }
332     
333     /**
334      * A privileged action that returns all the X.500 private credentials for a
335      * subject as an X500PrivateCredential array.  Assumes that the subject is
336      * non-null.
337      */
338     static class GetAllPrivateCredentialsAction implements PrivilegedAction {
339 	private final Subject subject;
340 
341 	GetAllPrivateCredentialsAction(Subject subject) {
342 	    this.subject = subject;
343 	}
344 
345 	public Object run() {
346 	    Set pcs = subject.getPrivateCredentials();
347 	    List xpcs = new ArrayList(pcs.size());
348 	    synchronized (pcs) {
349 		/*
350 		 * XXX: Include this synchronization to work around BugID
351 		 * 4892913, Subject.getPrivateCredentials not thread-safe
352 		 * against changes to principals.  -tjb[22.Jul.2003]
353 		 *
354 		 * synchronized (subject.getPrincipals()) {
355 		 */
356 		for (Iterator iter = pcs.iterator(); iter.hasNext(); ) {
357 		    Object pc = iter.next();
358 		    if (pc instanceof X500PrivateCredential) {
359 			xpcs.add(pc);
360 		    }
361 		}
362 	    }
363 	    return xpcs.toArray(new X500PrivateCredential[xpcs.size()]);
364 	}
365     }
366 }