/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jini.jeri.ssl; import java.net.Socket; import java.security.AccessController; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; import net.jini.io.UnsupportedConstraintException; import net.jini.security.AuthenticationPermission; /** * An AuthManager for clients. Uses the fact that client connections only * share SSLContexts after a single client and server principal have been * chosen. * * @author Sun Microsystems, Inc. */ class ClientAuthManager extends AuthManager { /* -- Fields -- */ /** Client logger */ private static final Logger logger = clientLogger; /** The server certificate chosen by the first handshake. */ private X509Certificate serverCredential; /** The server principal chosen by the first handshake. */ private X500Principal serverPrincipal; /** * The private credential supplied by chooseClientAlias in the last * handshake or null if none was supplied. */ private X500PrivateCredential clientCredential; /** The client principal chosen by the first handshake. */ private X500Principal clientPrincipal; /** * The exception that occurred within the last call to chooseClientAlias if * no credential could be supplied. */ private Exception clientCredentialException; /** * The latest time for which all client and server credentials remain * valid. */ private long credentialsValidUntil = 0; /** The permission to check for the last cached credential */ private AuthenticationPermission authenticationPermission; /* -- Constructors -- */ /** * Creates an AuthManager that retrieves principals and credentials for * authentication from the specified subject. If permittedLocalPrincipals * is non-null, then the principals used for authentication are restricted * to the elements of that set. If permittedRemotePrincipals is non-null, * then the server principals accepted are restricted to the elements of * that set. * * @param subject the subject for retrieving principals and credentials * @param permittedLocalPrincipals if non-null, then only principals in * this set may be used for authentication * @param permittedRemotePrincipals if non-null, then only principals in * this set will be trusted when authenticating the peer * @throws NoSuchAlgorithmException if the trust manager factory algorithm * is not found */ ClientAuthManager(Subject subject, Set permittedLocalPrincipals, Set permittedRemotePrincipals) throws NoSuchAlgorithmException { super(subject, permittedLocalPrincipals, permittedRemotePrincipals); } /* -- Methods -- */ /** * Returns true if the last handshake authenticated the client, else * false. */ synchronized boolean getClientAuthenticated() { return clientCredential != null; } /** * Returns the last SecurityException or GeneralSecurityException that * occurred when attempting to choose client credentials, or null if no * exception occurred. */ synchronized Exception getClientCredentialException() { return clientCredentialException; } /** * Checks if the subject still contains the proper credentials, and the * current access control context has the proper AuthenticationPermission, * to use the current session. Callers should only call this method if * client authentication is being used. * * @param constraints the requested constraints * @param serverSubject the server subject for the connection * @throws SecurityException if the access control context does not have * the proper AuthenticationPermission * @throws UnsupportedConstraintException if the subject does not contain * the proper credentials */ synchronized void checkAuthentication() throws UnsupportedConstraintException { if (clientCredential == null) { throw new UnsupportedConstraintException( "Client is not authenticated"); } else if (clientCredential.isDestroyed()) { throw new UnsupportedConstraintException( "Private credentials are destroyed"); } else if (System.currentTimeMillis() > credentialsValidUntil) { throw new UnsupportedConstraintException( "Certificates are no longer valid"); } if (subjectIsReadOnly) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(authenticationPermission); } } else { Subject subject = getSubject(); X509Certificate cert = clientCredential.getCertificate(); if (SubjectCredentials.getPrincipal(subject, cert) == null) { throw new UnsupportedConstraintException("Missing principal"); } CertPath chain = SubjectCredentials.getCertificateChain(subject, cert); if (chain == null) { throw new UnsupportedConstraintException( "Missing public credentials"); } X500PrivateCredential pc = getPrivateCredential( cert, authenticationPermission); if (pc == null) { throw new UnsupportedConstraintException( "Missing private credentials"); } else if (!equalPrivateCredentials(clientCredential, pc)) { throw new UnsupportedConstraintException( "Wrong private credentials"); } } } /** * Gets the private credential for the specified X.509 certificate, * checking for AuthenticationPermission to connect with the last server * principal. * * @param cert the certificate for the local principal * @return the associated private credential or null if not found * @throws SecurityException if the access control context does not have * the proper AuthenticationPermission */ synchronized X500PrivateCredential getPrivateCredential( X509Certificate cert) { return getPrivateCredential(cert, getAuthenticationPermission(cert)); } /** * Gets the private credential for the specified X.509 certificate, * checking for the specified AuthenticationPermission. * * @param cert the certificate for the local principal * @param ap the permission needed to connect to the peer * @return the associated private credential or null if not found * @throws SecurityException if the access control context does not have * the proper AuthenticationPermission */ private X500PrivateCredential getPrivateCredential( X509Certificate cert, AuthenticationPermission ap) { Subject subject = getSubject(); if (subject == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(ap); } return (X500PrivateCredential) AccessController.doPrivileged( new SubjectCredentials.GetPrivateCredentialAction( subject, cert)); } /** Returns the client logger */ Logger getLogger() { return logger; } /** * Returns the permission needed to connect to the last server principal * with the specified client certificate. */ private AuthenticationPermission getAuthenticationPermission( X509Certificate cert) { Set client = Collections.singleton(cert.getSubjectX500Principal()); Set server = (serverPrincipal == null) ? null : Collections.singleton(serverPrincipal); return new AuthenticationPermission(client, server, "connect"); } /** Returns the server principal chosen. */ synchronized X500Principal getServerPrincipal() { return serverPrincipal; } /** Returns the client principal chosen. */ synchronized X500Principal getClientPrincipal() { return clientPrincipal; } /* -- X500TrustManager -- */ /** * Override this X509TrustManager method in order to cache the server * principal and to continue to choose the same one. */ public synchronized void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { super.checkServerTrusted(chain, authType); if (serverPrincipal == null) { serverCredential = chain[0]; serverPrincipal = serverCredential.getSubjectX500Principal(); setPermittedRemotePrincipals( Collections.singleton(serverPrincipal)); credentialsValidUntil = certificatesValidUntil(chain); } else if (!serverCredential.equals(chain[0])) { throw new CertificateException("Server credentials changed"); } } /* -- Implement X509KeyManager -- */ public String[] getClientAliases(String keyType, Principal[] issuers) { String[] result = getAliases(keyType, issuers); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "get client aliases for key type {0}\n" + "and issuers {1}\nreturns {2}", new Object[] { keyType, toString(issuers), toString(result) }); } return result; } public String[] getServerAliases(String keyType, Principal[] issuers) { return null; } public synchronized String chooseClientAlias( String[] keyTypes, Principal[] issuers, Socket socket) { /* * Only choose new client credentials for the first handshake. * Otherwise, just use the previous client credentials. */ if (clientCredentialException != null) { return null; } else if (clientCredential == null) { List exceptions = null; for (int i = 0; i < keyTypes.length; i++) { Exception exception; try { clientCredential = chooseCredential(keyTypes[i], issuers); if (clientCredential != null) { break; } continue; } catch (GeneralSecurityException e) { exception = e; } catch (SecurityException e) { exception = e; } if (exceptions == null) { exceptions = new ArrayList(); } exceptions.add(exception); } if (clientCredential == null) { if (exceptions == null) { clientCredentialException = new GeneralSecurityException("Credentials not found"); } else if (exceptions.size() == 1) { clientCredentialException = (Exception) exceptions.get(0); } else { for (int i = exceptions.size(); --i >= 0; ) { Exception e = (Exception) exceptions.get(i); if (!(e instanceof SecurityException)) { clientCredentialException = new GeneralSecurityException( exceptions.toString()); break; } } if (clientCredentialException == null) { clientCredentialException = new SecurityException(exceptions.toString()); } } return null; } } X509Certificate cert = clientCredential.getCertificate(); clientPrincipal = cert.getSubjectX500Principal(); credentialsValidUntil = Math.min(credentialsValidUntil, certificatesValidUntil( SubjectCredentials.getCertificateChain( getSubject(), cert))); authenticationPermission = getAuthenticationPermission(cert); String result = SubjectCredentials.getCertificateName( clientCredential.getCertificate()); if (logger.isLoggable(Level.FINE)) { logger.log( Level.FINE, "choose client alias for key types {0}\nand issuers {1}\n" + "returns {2}", new Object[] { toString(keyTypes), toString(issuers), result }); } return result; } public String chooseServerAlias( String keyType, Principal[] issuers, Socket socket) { return null; } }