/* * 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 com.sun.jini.action.GetLongAction; import com.sun.jini.jeri.internal.connection.BasicServerConnManager; import com.sun.jini.jeri.internal.connection.ServerConnManager; import com.sun.jini.jeri.internal.runtime.Util; import com.sun.jini.logging.Levels; import com.sun.jini.thread.Executor; import com.sun.jini.thread.GetThreadPoolAction; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.channels.SocketChannel; import java.security.AccessControlContext; import java.security.AccessController; import java.security.GeneralSecurityException; import java.security.Permission; import java.security.Principal; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; import net.jini.core.constraint.InvocationConstraints; import net.jini.io.UnsupportedConstraintException; import net.jini.jeri.Endpoint; import net.jini.jeri.RequestDispatcher; import net.jini.jeri.ServerEndpoint.ListenContext; import net.jini.jeri.ServerEndpoint.ListenCookie; import net.jini.jeri.ServerEndpoint.ListenEndpoint; import net.jini.jeri.ServerEndpoint.ListenHandle; import net.jini.jeri.ServerEndpoint; import net.jini.jeri.connection.InboundRequestHandle; import net.jini.jeri.connection.ServerConnection; import net.jini.security.AuthenticationPermission; import net.jini.security.Security; import net.jini.security.SecurityContext; /** * Provides the implementation of SslServerEndpoint so that the implementation * can be inherited by HttpsServerEndpoint without revealing the inheritance in * the public API. * * @author Sun Microsystems, Inc. */ class SslServerEndpointImpl extends Utilities { /* -- Fields -- */ /** Server logger */ static final Logger logger = serverLogger; /** * The maximum time a session should be used before expiring -- non-final * to facilitate testing. Use 24 hours to allow the client, which uses * 23.5 hours, to renegotiate a new session before the server timeout. */ static long maxServerSessionDuration = ((Long) Security.doPrivileged( new GetLongAction("com.sun.jini.jeri.ssl.maxServerSessionDuration", 24 * 60 * 60 * 1000))).longValue(); /** * Executes a Runnable in a system thread -- used for listener accept * threads. */ static final Executor systemExecutor = (Executor) Security.doPrivileged(new GetThreadPoolAction(false)); /** The default server connection manager. */ private static final ServerConnManager defaultServerConnectionManager = new BasicServerConnManager(); /** The associated server endpoint. */ final ServerEndpoint serverEndpoint; /** The server subject, or null if the server is anonymous. */ final Subject serverSubject; /** * The principals to use for authentication, or null if the server is * anonymous. */ final Set serverPrincipals; /** * The host name that clients should use to connect to this server, or null * if enumerateListenEndpoints should compute the default. */ final String serverHost; /** The server port */ final int port; /** The socket factory for use in the associated Endpoint. */ final SocketFactory socketFactory; /** The server socket factory. */ final ServerSocketFactory serverSocketFactory; /** * The permissions needed to authenticate when listening on this endpoint, * or null if the server is anonymous. */ Permission[] listenPermissions; /** The listen endpoint. */ private final ListenEndpoint listenEndpoint; /** The factory for creating JSSE sockets -- set by sslInit */ private SSLSocketFactory sslSocketFactory; /** * The authentication manager for the SSLContext for this endpoint -- set * by sslInit. */ private ServerAuthManager authManager; /** The server connection manager. */ ServerConnManager serverConnectionManager = defaultServerConnectionManager; /* -- Methods -- */ /** Creates an instance of this class. */ SslServerEndpointImpl(ServerEndpoint serverEndpoint, Subject serverSubject, X500Principal[] serverPrincipals, String serverHost, int port, SocketFactory socketFactory, ServerSocketFactory serverSocketFactory) { this.serverEndpoint = serverEndpoint; boolean useCurrentSubject = serverSubject == null; if (useCurrentSubject) { final AccessControlContext acc = AccessController.getContext(); serverSubject = (Subject) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return Subject.getSubject(acc); } }); } this.serverPrincipals = (serverPrincipals == null) ? computePrincipals(serverSubject) : checkPrincipals(serverPrincipals); /* Set listenPermissions before calling hasListenPermissions */ if (this.serverPrincipals == null) { listenPermissions = null; } else { listenPermissions = new AuthenticationPermission[this.serverPrincipals.size()]; int i = 0; for (Iterator iter = this.serverPrincipals.iterator(); iter.hasNext(); i++) { Principal p = (Principal) iter.next(); listenPermissions[i] = new AuthenticationPermission( Collections.singleton(p), null, "listen"); } } if (this.serverPrincipals == null || /* Don't use current subject without any permission */ (useCurrentSubject && serverPrincipals != null && !hasListenPermissions())) { this.serverSubject = null; this.listenPermissions = null; } else { this.serverSubject = serverSubject; } this.serverHost = serverHost; if (port < 0 || port > 0xFFFF) { throw new IllegalArgumentException("Invalid port: " + port); } this.port = port; this.socketFactory = socketFactory; this.serverSocketFactory = serverSocketFactory; listenEndpoint = createListenEndpoint(); } /** Computes the principals in the subject available for authentication */ private static Set computePrincipals(Subject subject) { if (subject == null) { return null; } /* Get principals from the subject */ X500PrivateCredential[] credentials = (X500PrivateCredential[]) AccessController.doPrivileged( new SubjectCredentials.GetAllPrivateCredentialsAction( subject)); Set result = SubjectCredentials.getPrincipals( subject, ANY_KEY_ALGORITHM, credentials); /* Remove ones we don't have authentication listen permission for. */ SecurityManager sm = System.getSecurityManager(); if (sm != null) { for (Iterator iter = result.iterator(); iter.hasNext(); ) { Principal p = (Principal) iter.next(); try { sm.checkPermission( new AuthenticationPermission( Collections.singleton(p), null, "listen")); } catch (SecurityException e) { logger.log(Levels.HANDLED, "compute principals for server endpoint " + "caught exception", e); iter.remove(); } } } return result.isEmpty() ? null : result; } /** * Returns true if the caller has AuthenticationPermission for listen on * this endpoint. */ private boolean hasListenPermissions() { try { checkListenPermissions(false); return true; } catch (SecurityException e) { logger.log(Levels.HANDLED, "check listen permissions for server endpoint " + "caught exception", e); return false; } } /** * Checks that principals is not empty and contains no nulls, and returns * it as a set. Returns null if no principals are specified. */ private static Set checkPrincipals(X500Principal[] principals) { if (principals.length == 0) { return null; } Set result = new HashSet(principals.length); for (int i = principals.length; --i >= 0; ) { X500Principal p = principals[i]; if (p == null) { throw new NullPointerException( "Server principal cannot be null"); } result.add(p); } return result; } /** * Initializes the sslSocketFactory and authManager fields. Wait to do * this until needed, because creating the SSLContext requires initializing * the secure random number generator, which can be time consuming. */ private void sslInit() { assert Thread.holdsLock(this); SSLContextInfo info = getServerSSLContextInfo( serverSubject, serverPrincipals); sslSocketFactory = info.sslContext.getSocketFactory(); authManager = (ServerAuthManager) info.authManager; } /** Returns the SSLSocketFactory, calling sslInit if needed. */ final SSLSocketFactory getSSLSocketFactory() { synchronized (this) { if (sslSocketFactory == null) { sslInit(); } } return sslSocketFactory; } /** Returns the ServerAuthManager, calling sslInit if needed. */ final ServerAuthManager getAuthManager() { synchronized (this) { if (authManager == null) { sslInit(); } } return authManager; } /** Returns a hash code value for this object. */ public int hashCode() { return getClass().hashCode() ^ System.identityHashCode(serverSubject) ^ (serverPrincipals == null ? 0 : serverPrincipals.hashCode()) ^ (serverHost == null ? 0 : serverHost.hashCode()) ^ port ^ (socketFactory != null ? socketFactory.hashCode() : 0) ^ (serverSocketFactory != null ? serverSocketFactory.hashCode() : 0); } /** * Two instances of this class are equal if they have the same actual * class; have server subjects that compare equal using ==; have server * principals that are either both null or are equal when compared as the * elements of a Set; have the same server host and port; have socket * factories that are either both null, or have the same actual class and * are equal; and have server socket factories that are either both null, * or have the same actual class and are equal. */ public boolean equals(Object object) { if (object == null || object.getClass() != getClass()) { return false; } SslServerEndpointImpl other = (SslServerEndpointImpl) object; return serverSubject == other.serverSubject && safeEquals(serverPrincipals, other.serverPrincipals) && safeEquals(serverHost, other.serverHost) && port == other.port && Util.sameClassAndEquals(socketFactory, other.socketFactory) && Util.sameClassAndEquals(serverSocketFactory, other.serverSocketFactory); } /** Returns a string representation of this object. */ public String toString() { return getClassName(this) + fieldsToString(); } /** Returns a string representation of the fields of this object. */ final String fieldsToString() { return "[" + (serverPrincipals == null ? "" : serverPrincipals.toString() + ", ") + (serverHost == null ? "" : serverHost + ":") + port + (serverSocketFactory != null ? ", " + serverSocketFactory : "") + (socketFactory != null ? ", " + socketFactory : "") + "]"; } /* -- Implement ServerCapabilities -- */ final InvocationConstraints checkConstraints( InvocationConstraints constraints) throws UnsupportedConstraintException { try { checkListenPermissions(false); } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { logThrow(logger, Levels.FAILED, SslServerEndpoint.class, "checkConstraints", "check constraints for {0}\nwith {1}\nthrows", new Object[] { this, constraints }, e); } throw e; } Set clientPrincipals = getClientPrincipals(constraints); if (clientPrincipals == null) { clientPrincipals = Collections.singleton(UNKNOWN_PRINCIPAL); } Map serverKeyTypes = new HashMap(); List certPaths = SubjectCredentials.getCertificateChains(serverSubject); if (certPaths != null) { for (int i = certPaths.size(); --i >= 0; ) { CertPath chain = (CertPath) certPaths.get(i); X509Certificate cert = SubjectCredentials.firstX509Cert(chain); X500Principal principal = SubjectCredentials.getPrincipal( serverSubject, cert); if (principal != null) { Collection keyTypes = (Collection) serverKeyTypes.get(principal); if (keyTypes == null) { keyTypes = new ArrayList(1); serverKeyTypes.put(principal, keyTypes); } keyTypes.add(cert.getPublicKey().getAlgorithm()); } } } String[] suites = getSupportedCipherSuites(); for (int suiteIndex = suites.length; --suiteIndex >= 0; ) { String suite = suites[suiteIndex]; String suiteKeyType = getKeyAlgorithm(suite); Iterator sIter = (serverPrincipals == null ? Collections.EMPTY_SET : serverPrincipals).iterator(); X500Principal server; do { if (sIter.hasNext()) { server = (X500Principal) sIter.next(); assert server != null; Collection keyTypes = (Collection) serverKeyTypes.get(server); if (keyTypes == null || !keyTypes.contains(suiteKeyType)) { continue; } } else { server = null; } Iterator cIter = clientPrincipals.iterator(); Principal client; do { if (cIter.hasNext()) { client = (Principal) cIter.next(); assert client != null; } else { client = null; } InvocationConstraints unfulfilledConstraints = getUnfulfilledConstraints( suite, client, server, constraints); if (unfulfilledConstraints != null) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "check constraints for {0}\n" + "with {1}\nreturns {2}", new Object[] { serverEndpoint, constraints, unfulfilledConstraints }); } return unfulfilledConstraints; } } while (client != null); } while (server != null); } UnsupportedConstraintException uce = new UnsupportedConstraintException( "Constraints are not supported: " + constraints); if (logger.isLoggable(Levels.FAILED)) { logThrow( logger, Levels.FAILED, SslServerEndpoint.class, "checkConstraints", "check constraints for {0}\nwith {1}\nthrows", new Object[] { serverEndpoint, constraints }, uce); } throw uce; } /** * Returns null if the constraints are not supported, else any integrity * constraints required or preferred by the arguments. */ static InvocationConstraints getUnfulfilledConstraints( String cipherSuite, Principal client, Principal server, InvocationConstraints constraints) { boolean supported = false; for (int i = 2; --i >= 0; ) { boolean integrity = i == 0; ConnectionContext context = ConnectionContext.getInstance( cipherSuite, client, server, integrity, false /* clientSide */, constraints); if (context != null) { if (context.getIntegrityRequired()) { return INTEGRITY_REQUIRED; } else if (context.getIntegrityPreferred()) { return INTEGRITY_PREFERRED; } else { supported = true; } } } return supported ? InvocationConstraints.EMPTY : null; } /* -- Implement ServerEndpoint -- */ final Endpoint enumerateListenEndpoints(ListenContext listenContext) throws IOException { Exception exception = null; try { String resolvedHost = this.serverHost; if (resolvedHost == null) { InetAddress localAddr; try { localAddr = (InetAddress) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws UnknownHostException { return InetAddress.getLocalHost(); } }); } catch (PrivilegedActionException e) { UnknownHostException uhe = (UnknownHostException) e.getCause(); if (logger.isLoggable(Levels.FAILED)) { logThrow(logger, Levels.FAILED, this.getClass(), "enumerateListenEndpoints", "InetAddress.getLocalHost() throws", null, uhe); } // Remove host information if caller does not have // privileges to see it. try { InetAddress.getLocalHost(); } catch (UnknownHostException te) { throw te; } throw new UnknownHostException("Host name cleared due to " + "insufficient caller " + "permissions"); } SecurityManager sm = System.getSecurityManager(); if (sm != null) { try { sm.checkConnect(localAddr.getHostName(), -1); } catch (SecurityException e) { exception = e; /* Throw new exception to not reveal host name */ throw new SecurityException( "Access to resolve local host denied"); } } resolvedHost = localAddr.getHostAddress(); } Endpoint result = createEndpoint( resolvedHost, checkCookie(listenContext.addListenEndpoint(listenEndpoint))); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "enumerate listen endpoints for {0}\nreturns {1}", new Object[] { this, result }); } return result; } finally { if (exception != null && logger.isLoggable(Levels.FAILED)) { logThrow( logger, Levels.FAILED, SslServerEndpointImpl.class, "enumerateListenEndpoints", "enumerate listen endpoints for {0}\nthrows", new Object[] { this }, exception); } } } /** Creates a listen endpoint for this server endpoint. */ ListenEndpoint createListenEndpoint() { return new SslListenEndpoint(); } /** * Creates an endpoint for this server endpoint corresponding to the * specified server host and listen cookie. */ Endpoint createEndpoint(String serverHost, SslListenCookie cookie) { return SslEndpoint.getInstance( serverHost, cookie.getPort(), socketFactory); } /** * Checks that the argument is a valid listen cookie for this server * endpoint. */ private SslListenCookie checkCookie(ListenCookie cookie) { if (!(cookie instanceof SslListenCookie)) { throw new IllegalArgumentException( "Cookie must be of type SslListenCookie: " + cookie); } SslListenCookie sslListenCookie = ((SslListenCookie) cookie); ListenEndpoint cookieListenEndpoint = sslListenCookie.getListenEndpoint(); if (!listenEndpoint.equals(cookieListenEndpoint)) { throw new IllegalArgumentException( "Cookie has wrong listen endpoint: found " + cookieListenEndpoint + ", expected " + listenEndpoint); } return sslListenCookie; } /** * Check for permission to listen on this endpoint, but only checking * socket permissions if checkSocket is true. */ final void checkListenPermissions(boolean checkSocket) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { if (checkSocket) { sm.checkListen(port); } if (listenPermissions != null) { for (int i = listenPermissions.length; --i >= 0; ) { sm.checkPermission(listenPermissions[i]); } } } } /** Implements ListenEndpoint */ class SslListenEndpoint extends Utilities implements ListenEndpoint { /* inherit javadoc */ public void checkPermissions() { checkListenPermissions(true); } /* inherit javadoc */ public ListenHandle listen(RequestDispatcher requestDispatcher) throws IOException { if (requestDispatcher == null) { throw new NullPointerException( "Request dispatcher cannot be null"); } checkCredentials(); ServerSocket serverSocket = serverSocketFactory != null ? serverSocketFactory.createServerSocket(port) : new ServerSocket(port); return createListenHandle(requestDispatcher, serverSocket); } /** * Check that the subject has credentials for the principals specified * when the server endpoint was created. */ private void checkCredentials() throws UnsupportedConstraintException { if (serverSubject == null) { return; } checkListenPermissions(false); Set principals = serverSubject.getPrincipals(); /* Keep track of progress; remove entry when check is done */ Map progress = new HashMap(serverPrincipals.size()); for (Iterator i = serverPrincipals.iterator(); i.hasNext(); ) { X500Principal p = (X500Principal) i.next(); if (!principals.contains(p)) { throw new UnsupportedConstraintException( "Missing principal: " + p); } progress.put(p, X500Principal.class); } X500PrivateCredential[] privateCredentials = (X500PrivateCredential[]) AccessController.doPrivileged( new SubjectCredentials.GetAllPrivateCredentialsAction( serverSubject)); List certPaths = SubjectCredentials.getCertificateChains(serverSubject); if (certPaths != null) { for (int i = certPaths.size(); --i >= 0; ) { CertPath chain = (CertPath) certPaths.get(i); X509Certificate firstCert = firstX509Cert(chain); X500Principal p = firstCert.getSubjectX500Principal(); if (progress.containsKey(p)) { try { checkValidity(chain, null); } catch (CertificateException e) { progress.put(p, e); continue; } progress.put(p, CertPath.class); for (int j = privateCredentials.length; --j >= 0; ) { X509Certificate cert = privateCredentials[j].getCertificate(); if (firstCert.equals(cert)) { progress.remove(p); break; } } } } } if (!progress.isEmpty()) { X500Principal p = (X500Principal) progress.keySet().iterator().next(); Object result = progress.get(p); if (result == X500Principal.class) { throw new UnsupportedConstraintException( "Missing public credentials: " + p); } else if (result == CertPath.class) { throw new UnsupportedConstraintException( "Missing private credentials: " + p); } else { throw new UnsupportedConstraintException( "Problem with certificates: " + p + "\n" + result, (CertificateException) result); } } } /** * Creates a listen handle for the specified dispatcher and server * socket. */ ListenHandle createListenHandle(RequestDispatcher requestDispatcher, ServerSocket serverSocket) throws IOException { return new SslListenHandle(requestDispatcher, serverSocket); } /** Returns a hash code value for this object. */ public int hashCode() { return getClass().hashCode() ^ System.identityHashCode(serverSubject) ^ (serverPrincipals == null ? 0 : serverPrincipals.hashCode()) ^ port ^ (serverSocketFactory != null ? serverSocketFactory.hashCode() : 0); } /** * Two instances of this class are equal if they have the same actual * class; have server subjects that compare equal using * <code>==</code>; have server principals that are either both * <code>null</code> or compare equal using <code>equals</code>; have * the same port; and have server socket factories that are both null, * or have the same actual class and are equal. Note that the server * host and socket factory are ignored. */ public boolean equals(Object object) { if (this == object) { return true; } else if (object == null || getClass() != object.getClass()) { return false; } SslServerEndpointImpl other = ((SslListenEndpoint) object).getImpl(); return serverSubject == other.serverSubject && safeEquals(serverPrincipals, other.serverPrincipals) && port == other.port && Util.sameClassAndEquals(serverSocketFactory, other.serverSocketFactory); } /** * Returns the SslServerEndpointImpl associated with this listen * endpoint. */ private SslServerEndpointImpl getImpl() { return SslServerEndpointImpl.this; } } /** Implements ListenHandle */ class SslListenHandle extends Utilities implements ListenHandle { /** The request handler */ private final RequestDispatcher requestDispatcher; /** The server socket used to accept connections */ final ServerSocket serverSocket; /** The security context at the time of the listen. */ private final SecurityContext securityContext; /** Whether the listen handle has been closed. */ private boolean closed = false; /** Set of connections created by this listen handle */ private final Set connections = new HashSet(); /** Used to throttle accept failures */ private long acceptFailureTime = 0; private int acceptFailureCount; /** Creates a listen handle */ SslListenHandle(RequestDispatcher requestDispatcher, ServerSocket serverSocket) throws IOException { this.requestDispatcher = requestDispatcher; this.serverSocket = serverSocket; securityContext = Security.getContext(); systemExecutor.execute( new Runnable() { public void run() { acceptLoop(); } }, toString()); logger.log(Level.FINE, "created {0}", this); } /** Handles new socket connections. */ final void acceptLoop() { while (true) { Socket socket = null; Throwable exception; SslServerConnection connection = null; try { socket = serverSocket.accept(); /* Send data without delay */ try { socket.setTcpNoDelay(true); } catch (SocketException e) { } /* * Send periodic pings so we can tell if the connection * dies. */ try { socket.setKeepAlive(true); } catch (SocketException e) { } connection = serverConnection(socket); synchronized (this) { if (closed) { try { connection.closeInternal( false /* removeFromListener */); } catch (IOException e) { } break; } connections.add(connection); } final SslServerConnection finalConnection = connection; AccessController.doPrivileged( securityContext.wrap( new PrivilegedAction() { public Object run() { handleConnection( finalConnection, requestDispatcher); return null; } }), securityContext.getAccessControlContext()); continue; } catch (Exception e) { exception = e; } catch (Error e) { exception = e; } /* Problem occurred */ boolean closedSync; synchronized (this) { closedSync = closed; } if (!closedSync && logger.isLoggable(Level.INFO)) { String msg = "handling connection {0} throws"; Object arg = connection; if (connection == null) { msg = "accepting connection on {0} throws"; arg = this; } logThrow(logger, Level.INFO, SslListenHandle.class, "acceptLoop", msg, new Object[] { arg }, exception); } if (socket != null) { try { socket.close(); } catch (IOException e) { } } if (closedSync) { break; } boolean knownFailure = (exception instanceof Exception || exception instanceof OutOfMemoryError || exception instanceof NoClassDefFoundError); if (!(knownFailure && continueAfterAcceptFailure(exception))) { try { serverSocket.close(); } catch (IOException e) { } if (!knownFailure) { throw (Error) exception; } else { return; } } } } /** * Throttles the accept loop after ServerSocket.accept throws * an exception, and decides whether to continue at all. The * current code is borrowed from the JRMP implementation; it * always continues, but it delays the loop after bursts of * failed accepts. */ private boolean continueAfterAcceptFailure(Throwable t) { /* * If we get a burst of NFAIL failures in NMSEC milliseconds, * then wait for ten seconds. This is to ensure that individual * failures don't cause hiccups, but sustained failures don't * hog the CPU in futile accept-fail-retry looping. */ final int NFAIL = 10; final int NMSEC = 5000; long now = System.currentTimeMillis(); if (acceptFailureTime == 0L || (now - acceptFailureTime) > NMSEC) { // failure time is very old, or this is first failure acceptFailureTime = now; acceptFailureCount = 0; } else { // failure window was started recently acceptFailureCount++; if (acceptFailureCount >= NFAIL) { try { Thread.sleep(10000); } catch (InterruptedException ignore) { } // no need to reset counter/timer } } return true; } /** Returns a string representation of this object. */ public String toString() { return getClassName(this) + "[" + serverHost + ":" + getPort() + "]"; } /** Returns a connection for the specified socket. */ SslServerConnection serverConnection(Socket socket) throws IOException { return new SslServerConnection(this, socket); } /** Handles a newly accepted server connection. */ void handleConnection(SslServerConnection connection, RequestDispatcher requestDispatcher) { serverConnectionManager.handleConnection( connection, requestDispatcher); } /** Returns the port on which this handle is listening. */ private int getPort() { return serverSocket.getLocalPort(); } /* inherit javadoc */ public synchronized void close() { if (!closed) { logger.log(Level.FINE, "closing {0}", this); closed = true; try { serverSocket.close(); } catch (IOException e) { } for (Iterator i = connections.iterator(); i.hasNext(); ) { SslServerConnection connection = (SslServerConnection) i.next(); try { /* * Call closeInternal because close would call back to * remove the connection and invalidate the iterator. */ connection.closeInternal( false /* removeFromListener */); } catch (IOException e) { } i.remove(); } } } /** * Called when a connection is closed without a call to close on this * listener. */ synchronized void noteConnectionClosed( SslServerConnection connection) { connections.remove(connection); } /* inherit javadoc */ public ListenCookie getCookie() { return new SslListenCookie(getPort()); } } /** Implements ListenCookie */ final class SslListenCookie implements ListenCookie { private final int port; SslListenCookie(int port) { this.port = port; } /** Returns the port on which the associated handle is listening. */ final int getPort() { return port; } /** * Returns the listen endpoint associated with this listen cookie. */ final ListenEndpoint getListenEndpoint() { return listenEndpoint; } } /** Implements ServerConnection */ class SslServerConnection extends Utilities implements ServerConnection { /** The listen handle that accepted this connection */ private final SslListenHandle listenHandle; /** The JSSE socket used for communication */ final SSLSocket sslSocket; /** The inbound request handle for this connection. */ private final InboundRequestHandle requestHandle = new InboundRequestHandle() { }; /** * The session for this connection's socket, or null if not retrieved * yet. Check that the current session matches to prevent new * handshakes. */ private SSLSession session; /** * The client subject -- depends on session being set. This instance * is read-only. */ private Subject clientSubject; /** The client principal -- depends on session being set. */ private X500Principal clientPrincipal; /** The server principal -- depends on session being set. */ private X500Principal serverPrincipal; /** * The authentication permission required for this connection, or null * if the server is anonymous -- depends on session being set. */ private AuthenticationPermission authPermission; /** The cipher suite -- depends on session being set. */ private String cipherSuite; /** True if the connection has been closed. */ boolean closed; /** Creates a server connection */ SslServerConnection(SslListenHandle listenHandle, Socket socket) throws IOException { this.listenHandle = listenHandle; sslSocket = (SSLSocket) getSSLSocketFactory().createSocket( socket, socket.getInetAddress().getHostName(), socket.getPort(), true /* autoClose */); sslSocket.setEnabledCipherSuites(getSupportedCipherSuites()); /* Need to put in server mode before requesting client auth. */ sslSocket.setUseClientMode(false); sslSocket.setWantClientAuth(true); logger.log(Level.FINE, "created {0}", this); } /* inherit javadoc */ public String toString() { String sessionString; synchronized (this) { sessionString = session == null ? "" : session + ", "; } return getClassName(this) + "[" + sessionString + serverHost + ":" + sslSocket.getLocalPort() + "<=" + sslSocket.getInetAddress().getHostName() + ":" + sslSocket.getPort() + "]"; } /* -- Implement ServerConnection -- */ /* inherit javadoc */ public InputStream getInputStream() throws IOException { return sslSocket.getInputStream(); } /* inherit javadoc */ public OutputStream getOutputStream() throws IOException { return sslSocket.getOutputStream(); } /* inherit javadoc */ public SocketChannel getChannel() { return null; } /* inherit javadoc */ public InboundRequestHandle processRequestData(InputStream in, OutputStream out) { if (in == null || out == null) { throw new NullPointerException("Arguments cannot be null"); } SecurityException exception; try { long now = System.currentTimeMillis(); decacheSession(); long create = session.getCreationTime(); long expiration = create + maxServerSessionDuration; /* Check for rollover */ if (expiration < create) { expiration = Long.MAX_VALUE; } if (expiration < now) { /* * The session has expired. Invalidate the session so that * it is not reused, and throw an exception. */ session.invalidate(); throw new SecurityException("Session has expired"); } if (serverPrincipal != null) { getAuthManager().checkCredentials(session, clientSubject); } return requestHandle; } catch (SecurityException e) { exception = e; } catch (GeneralSecurityException e) { exception = new SecurityException(e.getMessage()); exception.initCause(e); } try { out.close(); } catch (IOException e2) { } if (logger.isLoggable(Levels.FAILED)) { logThrow(logger, Levels.FAILED, SslServerConnection.class, "processRequestData", "process request data for session {0}\nclient {1}\n" + "throws", new Object[] { session, subjectString(clientSubject) }, exception); } throw exception; } /** * Make sure the cached session is up to date, and set session-related * fields if needed. */ private void decacheSession() { synchronized (this) { SSLSession socketSession = sslSocket.getSession(); if (session == socketSession) { return; } else if (session != null) { /* * We disable session creation as soon as we notice the * first session, but it is possible that a second * handshake could have started by then, so check that we * have the same session. -tjb[31.Jan.2003] */ throw new SecurityException( "New handshake occurred on socket"); } session = socketSession; sslSocket.setEnableSessionCreation(false); cipherSuite = session.getCipherSuite(); if ("NULL".equals(getKeyExchangeAlgorithm(cipherSuite))) { throw new SecurityException("Handshake failed"); } clientSubject = getClientSubject(sslSocket); clientPrincipal = clientSubject != null ? ((X500Principal) clientSubject.getPrincipals().iterator().next()) : null; X509Certificate serverCert = getAuthManager().getServerCertificate(session); serverPrincipal = serverCert != null ? serverCert.getSubjectX500Principal() : null; if (serverPrincipal != null) { authPermission = new AuthenticationPermission( Collections.singleton(serverPrincipal), (clientPrincipal != null ? Collections.singleton(clientPrincipal) : null), "accept"); } } } /** * Returns the read-only <code>Subject</code> associated with the * client host connected to the other end of the connection on the * specified <code>SSLSocket</code>. Returns null if the client is * anonymous. */ private Subject getClientSubject(SSLSocket socket) { SSLSession session = socket.getSession(); try { Certificate[] certificateChain = session.getPeerCertificates(); if (certificateChain != null && certificateChain.length > 0 && certificateChain[0] instanceof X509Certificate) { X509Certificate cert = (X509Certificate) certificateChain[0]; return new Subject( true, Collections.singleton(cert.getSubjectX500Principal()), Collections.singleton( getCertFactory().generateCertPath( Arrays.asList(certificateChain))), Collections.EMPTY_SET); } } catch (SSLPeerUnverifiedException e) { } catch (CertificateException e) { logger.log(Levels.HANDLED, "get client subject caught exception", e); } return null; } /* inherit javadoc */ public void checkPermissions(InboundRequestHandle requestHandle) { check(requestHandle); SecurityManager sm = System.getSecurityManager(); if (sm != null) { try { sm.checkAccept(sslSocket.getInetAddress().getHostAddress(), sslSocket.getPort()); if (authPermission != null) { sm.checkPermission(authPermission); } } catch (SecurityException e) { if (logger.isLoggable(Levels.FAILED)) { logThrow(logger, Levels.FAILED, SslServerConnection.class, "checkPermissions", "check permissions for {0} throws", new Object[] { this }, e); } throw e; } } } /** * Checks that the argument is the request handle for this connection. */ private void check(InboundRequestHandle requestHandle) { if (requestHandle == null) { throw new NullPointerException("Request handle cannot be null"); } else if (requestHandle != this.requestHandle) { throw new IllegalArgumentException( "Wrong request handle: found " + requestHandle + ", expected " + this.requestHandle); } } /* inherit javadoc */ public InvocationConstraints checkConstraints( InboundRequestHandle requestHandle, InvocationConstraints constraints) throws UnsupportedConstraintException { check(requestHandle); if (constraints == null) { throw new NullPointerException("Constraints cannot be null"); } InvocationConstraints result = getUnfulfilledConstraints( cipherSuite, clientPrincipal, serverPrincipal, constraints); if (result == null) { UnsupportedConstraintException uce = new UnsupportedConstraintException( "Constraints are not supported: " + constraints); if (logger.isLoggable(Levels.FAILED)) { logThrow(logger, Levels.FAILED, SslServerConnection.class, "checkConstraints", "check constraints for {0}\nwith {1}\n" + "throws", new Object[] { SslServerConnection.this, constraints }, uce); } throw uce; } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "check constraints for {0}\nwith {1}\nreturns {2}", new Object[] { SslServerConnection.this, constraints, result }); } return result; } /* inherit javadoc */ public void populateContext(InboundRequestHandle requestHandle, Collection context) { check(requestHandle); Util.populateContext(context, sslSocket.getInetAddress()); Util.populateContext(context, clientSubject); } /* inherit javadoc */ public void close() throws IOException { closeInternal(true); } /** * Like close, but does not call noteConnectionClosed unless * removeFromListener is true. */ void closeInternal(boolean removeFromListener) throws IOException { synchronized (this) { if (closed) { return; } logger.log(Level.FINE, "closing {0}", this); closed = true; sslSocket.close(); } if (removeFromListener) { listenHandle.noteConnectionClosed(this); } } } }