/*
* 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.discovery.internal.EndpointInternals;
import com.sun.jini.discovery.internal.SslEndpointInternalsAccess;
import com.sun.jini.jeri.internal.connection.ConnManagerFactory;
import com.sun.jini.jeri.internal.connection.ServerConnManager;
import com.sun.jini.logging.Levels;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.security.cert.CertPath;
import java.security.cert.CertificateFactory;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMaxPrincipal;
import net.jini.core.constraint.ClientMaxPrincipalType;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.ClientMinPrincipalType;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConnectionAbsoluteTime;
import net.jini.core.constraint.ConnectionRelativeTime;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.DelegationAbsoluteTime;
import net.jini.core.constraint.DelegationRelativeTime;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.Endpoint;
import net.jini.jeri.OutboundRequestIterator;
import net.jini.jeri.ServerEndpoint;
import net.jini.jeri.connection.ConnectionManager;
import net.jini.jeri.connection.OutboundRequestHandle;
import net.jini.security.AuthenticationPermission;
import net.jini.security.Security;
import net.jini.security.proxytrust.TrustEquivalence;
/**
* An implementation of {@link Endpoint} that uses TLS/SSL to support
* invocation constraints for direct communication over TCP sockets. <p>
*
* Instances of this class are intended to be created by the {@link
* BasicJeriExporter} class when it calls {@link
* ServerEndpoint#enumerateListenEndpoints enumerateListenEndpoints} on
* instances of {@link SslServerEndpoint}. <p>
*
* The {@link SslTrustVerifier} trust verifier may be used for establishing
* trust in remote proxies that use instances of this class. <p>
*
* This class supports at least the following constraints, possibly limited by
* the available cipher suites: <p>
*
* <ul>
* <li> {@link ClientAuthentication}
* <li> {@link ClientMaxPrincipal}, when it contains an {@link X500Principal}
* <li> {@link ClientMaxPrincipalType}, when it contains
* <code>X500Principal</code>
* <li> {@link ClientMinPrincipal}, when it contains a single
* <code>X500Principal</code> only
* <li> {@link ClientMinPrincipalType}, when it contains
* <code>X500Principal</code> only
* <li> {@link Confidentiality}
* <li> {@link ConfidentialityStrength}, a provider-specific constraint for
* specifying weak or strong confidentiality
* <li> {@link ConnectionAbsoluteTime}
* <li> {@link ConstraintAlternatives}, if the elements all have the same
* actual class and at least one element is supported
* <li> {@link Delegation#NO}
* <li> {@link Delegation#YES}, trivially, for anonymous clients
* <li> {@link DelegationAbsoluteTime}, trivially, when delegation is not
* supported
* <li> {@link Integrity#YES}
* <li> {@link ServerAuthentication}
* <li> {@link ServerMinPrincipal}, when it contains a single
* <code>X500Principal</code> only
* </ul> <p>
*
* Note that {@link ConnectionRelativeTime} and {@link DelegationRelativeTime}
* constraints may be used at higher levels, but should be converted to the
* associated absolute time constraints for use by this class. <p>
*
* This class authenticates as a single {@link Principal} if the following
* items are present in the current {@link Subject}: <p>
*
* <ul>
* <li> One or more principals of type <code>X500Principal</code>
* <li> For each principal, one or more certificate chains, stored as public
* credentials, and represented by instances of {@link CertPath}, whose
* <code>getType</code> method returns "X.509", and for which calling
* <code>getSubjectDN</code> on the certificate chain's first element
* returns that principal's name
* <li> For each certificate chain, an instance of {@link
* X500PrivateCredential}, stored as a private credential, whose
* <code>getCertificate</code> method returns a value equal to the first
* element of the certificate chain, and whose <code>getPrivateKey</code>
* method returns the associated private key
* </ul> <p>
*
* In addition, this class's {@link #newRequest newRequest} method will only
* authenticate as a given principal if the caller has been granted {@link
* AuthenticationPermission} with that principal as the local principal, the
* principal representing the authenticated identity of the server as the peer
* principal, and the <code>connect</code> action. <p>
*
* This class supports remote connections between authenticated servers and
* authenticated or anonymous clients, and between anonymous servers and
* anonymous clients. Connections between anonymous servers and authenticated
* clients are not supported. Because of the suites available in the TLS/SSL
* protocol, support for {@link Confidentiality#NO} requires the server to
* authenticate with an RSA public key. <p>
*
* This class permits specifying a {@link SocketFactory} for creating the
* {@link Socket} instances that it uses to make remote connections. These
* socket factories should not be instances of {@link SSLSocketFactory} or
* return instances {@link SSLSocket}; it is the responsibility of the
* implementation to establish a TLS/SSL connection over the socket it obtains
* from the socket factory. <p>
*
* A <code>SocketFactory</code> used with instances of this class should be
* serializable, and must implement {@link Object#equals Object.equals} to
* obey the guidelines that are specified for <code>equals</code> methods of
* {@link Endpoint} instances. <p>
*
* This class uses the <a href="../connection/doc-files/mux.html">Jini
* extensible remote invocation (Jini ERI) multiplexing protocol</a> to map
* outgoing requests to socket connections.
*
* @author Sun Microsystems, Inc.
* @see SslServerEndpoint
* @see ConfidentialityStrength
* @see SslTrustVerifier
* @since 2.0
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* This implementation uses the <a
* href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html"
* target="_top">Java(TM) Secure Socket Extension (JSSE)</a>. <p>
*
* This implementation uses the {@link ConnectionManager} class to manage
* connections. <p>
*
* This implementation uses the following {@link Logger} instances in the
* <code>net.jini.jeri.ssl</code> namespace: <p>
*
* <ul>
* <li> <a href="#init_logger">init</a> - problems during initialization
* <li> <a href="#client_logger">client</a> - information about
* client-side connections
* </ul> <p>
*
* <a name="init_logger"></a>
* <table border="1" cellpadding="5" summary="Describes logging to the init
* logger performed by the SslEndpoint class at different logging
* levels">
*
* <caption halign="center" valign="top"><b><code>
* net.jini.jeri.ssl.init</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
*
* <tr> <td> {@link Level#WARNING WARNING} <td> problems with initializing JSSE
* or with registering internal entry points with discovery providers
*
* </table> <p>
*
* <a name="client_logger"></a>
* <table border="1" cellpadding="5" summary="Describes logging to the client
* logger performed by the SslEndpoint class at different logging
* levels">
*
* <caption halign="center" valign="top"><b><code>
* net.jini.jeri.ssl.client</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
*
* <tr> <td> {@link Levels#FAILED FAILED} <td> problems with outbound requests
*
* <tr> <td> {@link Levels#HANDLED HANDLED} <td> exceptions caught involving
* authentication
*
* <tr> <td> {@link Level#FINE FINE} <td> authentication decisions; creating,
* choosing, expiring, or closing connections; or handling outbound requests
*
* <tr> <td> {@link Level#FINEST FINEST} <td> low level operation tracing
*
* </table> <p>
*
* This implementation uses the following security providers: <p>
*
* <ul>
* <li> {@link SSLContext}, with the protocol specified by the
* <code>com.sun.jini.jeri.ssl.sslProtocol</code> system property, or
* <code>"TLS"</code> if that property is not defined, to provide the
* TLS/SSL implementation. The {@link SSLContext#init SSLContext.init}
* method is called with <code>null</code> for the <code>random</code>
* parameter to use the default {@link SecureRandom} implementation.
* <li> {@link CertificateFactory}, with type <code>"X.509"</code>, to generate
* <code>CertPath</code> instances from X.509 certificate chains
* <li> {@link TrustManagerFactory}, with the algorithm specified by the
* <code>com.sun.jini.jeri.ssl.trustManagerFactoryAlgorithm</code> system
* property, or the default algorithm if that property is not defined, to
* implement trust management for the TLS/SSL implementation. The factory
* must return trust managers that implement {@link X509TrustManager}.
* </ul> <p>
*
* See the documentation on <a
* href="http://java.sun.com/j2se/1.4/docs/guide/security/CryptoSpec.html#ProviderInstalling"
* target="_top">installing security providers</a> and <a
* href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html#ProviderCust"
* target="_top">configuring JSSE</a> for information on configuring these
* providers. <p>
*
* The <a
* href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html#Customization"
* target="_top">JSSE documentation</a> also describes the system properties
* for configuring the location, type, and password of the truststore that this
* implementation uses, through JSSE, to make decisions about what certificate
* chains should be trusted. <p>
*
* This implementation recognizes the following system properties: <p>
*
* <ul>
* <li> <code>com.sun.jini.jeri.ssl.maxClientSessionDuration</code> - The
* maximum number of milliseconds a client-side TLS/SSL session should be
* used. The default is 23.5 hours. The value should be smaller than the
* maximum server session duration to allow the client to negotiate a new
* session before the server timeout occurs.
* <li> <code>com.sun.jini.jeri.ssl.sslProtocol</code> - The secure socket
* protocol used when obtaining {@link SSLContext} instances. The default
* is <code>"TLS"</code>.
* <li> <code>com.sun.jini.jeri.ssl.trustManagerFactoryAlgorithm</code> - The
* algorithm used when obtaining {@link TrustManagerFactory}
* instances. The default is the value returned by {@link
* TrustManagerFactory#getDefaultAlgorithm
* TrustManagerFactory.getDefaultAlgorithm}.
* <li> <code>com.sun.jini.jeri.ssl.cipherSuites</code> - The TLS/SSL cipher
* suites that should be used for communication. The default is the list
* of suites supported by the JSSE implementation. The value should
* specify the suite names, separated by commas. The value will be ignored
* if it contains no suites or specifies suites that are not supported by
* the JSSE implementation. Suites appearing earlier in the list will be
* preferred to ones appearing later for suites that support the same
* requirements and preferences.
* </ul>
*/
public final class SslEndpoint
implements Endpoint, Serializable, TrustEquivalence
{
/* -- Fields -- */
private static final long serialVersionUID = 5311538504705775156L;
/**
* @serialField serverHost String
* The name of the server host.
* @serialField port int
* The server port.
* @serialField socketFactory SocketFactory
* The socket factory for creating sockets, or
* <code>null</code> to use default sockets.
*/
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("serverHost", String.class),
new ObjectStreamField("port", int.class),
new ObjectStreamField("socketFactory", SocketFactory.class)
};
/* Register a back door interface for use by discovery providers. */
static {
SslEndpointInternals.registerDiscoveryBackDoor();
}
/** Implementation delegate. */
transient SslEndpointImpl impl;
/* -- Methods -- */
/**
* Returns a TLS/SSL endpoint for the specified server host and port. Uses
* a <code>null</code> socket factory to create default sockets.
*
* @param serverHost the name of the server host
* @param port the server port
* @return an <code>SslEndpoint</code> instance
* @throws IllegalArgumentException if <code>port</code> is less than or
* equal to <code>0</code>, or greater than <code>65535</code>
* @throws NullPointerException if <code>serverHost</code> is
* <code>null</code>
*/
public static SslEndpoint getInstance(String serverHost, int port) {
return new SslEndpoint(serverHost, port, null);
}
/**
* Returns a TLS/SSL endpoint for the specified server host, port, and
* socket factory. A <code>socketFactory</code> of <code>null</code> uses
* default sockets.
*
* @param serverHost the name of the server host
* @param port the server port
* @param socketFactory the socket factory, or <code>null</code>
* @return an <code>SslEndpoint</code> instance
* @throws IllegalArgumentException if <code>port</code> is less than or
* equal to <code>0</code>, or greater than <code>65535</code>
* @throws NullPointerException if <code>serverHost</code> is
* <code>null</code>
*/
public static SslEndpoint getInstance(
String serverHost, int port, SocketFactory socketFactory)
{
return new SslEndpoint(serverHost, port, socketFactory);
}
/** Creates an instance of this class. */
private SslEndpoint(
String serverHost, int port, SocketFactory socketFactory)
{
impl = new SslEndpointImpl(this, serverHost, port, socketFactory);
}
/**
* Returns the server host that this endpoint connects to.
*
* @return the server host that this endpoint connects to
*/
public String getHost() {
return impl.serverHost;
}
/**
* Returns the TCP port that this endpoint connects to.
*
* @return the TCP port that this endpoint connects to
*/
public int getPort() {
return impl.port;
}
/**
* Returns the socket factory that this endpoint uses to create {@link
* Socket} instances, or <code>null</code> if it uses default sockets.
*
* @return the socket factory that this endpoint uses to create sockets, or
* <code>null</code> if it uses default sockets
*/
public SocketFactory getSocketFactory() {
return impl.socketFactory;
}
/** Returns a string representation of this object. */
public String toString() {
return "SslEndpoint" + impl.fieldsToString();
}
/* -- Implement Endpoint -- */
/** Returns a hash code value for this object. */
public int hashCode() {
return impl.hashCode();
}
/**
* Two instances of this class are equal if they have the same values for
* server host and port; and have socket factories that are either both
* <code>null</code>, or have the same actual class and are equal.
*/
public boolean equals(Object object) {
if (this == object) {
return true;
} else {
return object instanceof SslEndpoint &&
impl.equals(((SslEndpoint) object).impl);
}
}
/**
* {@inheritDoc} <p>
*
* <p>The returned <code>OutboundRequestIterator</code>'s {@link
* OutboundRequestIterator#next next} method behaves as follows:
*
* <blockquote>
*
* Initiates an attempt to communicate the request to this remote
* endpoint.
*
* <p>When the implementation of this method needs to create a new
* <code>Socket</code>, it will do so by invoking one of the
* <code>createSocket</code> methods on the
* <code>SocketFactory</code> of this <code>SslEndpoint</code>
* (which produced this iterator) if non-<code>null</code>, or it
* will create a <code>Socket</code> directly otherwise.
*
* <p>When the implementation needs to connect a
* <code>Socket</code>, if the host name to connect to
* resolves to multiple addresses (according to {@link
* InetAddress#getAllByName InetAddress.getAllByName}), it
* attempts to connect to the first resolved address; if that
* attempt fails with an <code>IOException</code> or a
* <code>SecurityException</code>, it then attempts to connect to
* the next address; and this iteration continues as long as there
* is another resolved address and the attempt to connect to the
* previous address fails with an <code>IOException</code> or a
* <code>SecurityException</code>. If the host name resolves to
* just one address, the implementation makes one attempt to
* connect to that address. If the host name does not resolve to
* any addresses (<code>InetAddress.getAllByName</code> would
* throw an <code>UnknownHostException</code>), the implementation
* still makes an attempt to connect the <code>Socket</code> to
* that host name, which could result in an
* <code>UnknownHostException</code>. If the final connection
* attempt fails with an <code>IOException</code> or a
* <code>SecurityException</code>, then if any connection attempt
* failed with an <code>IOException</code>, this method throws an
* <code>IOException</code>, and otherwise (if all connection
* attempts failed with a <code>SecurityException</code>), this
* method throws a <code>SecurityException</code>.
*
*
* <p>If there is a security manager:
*
* <ul>
*
* <li>If a new connection is to be created, the security
* manager's {@link SecurityManager#checkConnect(String,int)
* checkConnect} method is invoked with this
* <code>SslEndpoint</code>'s host and <code>-1</code> for the
* port; if this results in a <code>SecurityException</code>, this
* method throws that exception. <code>checkConnect</code> is
* also invoked for each connection attempt, with the remote IP
* address (or the host name, if it could not be resolved) and
* port to connect to; this could result in a
* <code>SecurityException</code> for that attempt. (Note that
* the implementation may carry out these security checks
* indirectly, such as through invocations of
* <code>InetAddress.getAllByName</code> or <code>Socket</code>'s
* constructors or <code>connect</code> method.)
*
* <li><p>In order to reuse an existing connection for the
* communication, the current security context must have all of
* the permissions that would be necessary if the connection were
* being created. Specifically, it must be possible to invoke
* <code>checkConnect</code> in the current security context with
* this <code>SslEndpoint</code>'s host and <code>-1</code> for
* the port without resulting in a <code>SecurityException</code>,
* and it also must be possible to invoke
* <code>checkConnect</code> with the remote IP address and port
* of the <code>Socket</code> without resulting in a
* <code>SecurityException</code> (if the remote socket address is
* unresolved, its host name is used instead). If no existing
* connection satisfies these requirements, then this method must
* behave as if there are no existing connections.
*
* </ul>
*
* <p>Throws {@link NoSuchElementException} if this iterator does
* not support making another attempt to communicate the request
* (that is, if <code>hasNext</code> would return
* <code>false</code>).
*
* <p>Throws {@link IOException} if an I/O exception occurs while
* performing this operation, such as if a connection attempt
* timed out or was refused or there are unsupported or conflicting
* constraints.
*
* <p>Throws {@link SecurityException} if there is a security
* manager and an invocation of its <code>checkConnect</code>
* method fails, or if the caller does not have the appropriate
* <code>AuthenticationPermission</code>.
*
* </blockquote>
*
* @throws NullPointerException {@inheritDoc}
*/
public OutboundRequestIterator newRequest(
InvocationConstraints constraints)
{
return impl.newRequest(constraints);
}
/* -- Implement TrustEquivalence -- */
/**
* Returns <code>true</code> if the argument is an instance of
* <code>SslEndpoint</code> with the same values for server host and port;
* and either both this instance and the argument have <code>null</code>
* socket factories, or the factories have the same actual class and are
* equal; and returns <code>false</code> otherwise.
*/
public boolean checkTrustEquivalence(Object obj) {
if (this == obj) {
return true;
} else {
return obj instanceof SslEndpoint &&
impl.equals(((SslEndpoint) obj).impl);
}
}
/** Support EndpointInternals, for use by discovery providers */
private static final class SslEndpointInternals extends Utilities
implements EndpointInternals
{
/** Register back door. */
static void registerDiscoveryBackDoor() {
final SslEndpointInternals backDoor = new SslEndpointInternals();
try {
Security.doPrivileged(new PrivilegedAction() {
public Object run() {
SslEndpointInternalsAccess.set(backDoor);
return null;
}
});
} catch (RuntimeException e) {
initLogger.log(Level.WARNING,
"Problem registering with discovery provider",
e);
} catch (Error e) {
initLogger.log(Level.WARNING,
"Problem registering with discovery provider",
e);
}
}
/* -- Implement EndpointInternals -- */
public void disableSocketConnect(Endpoint endpoint) {
coerce(endpoint).disableSocketConnect = true;
}
private static SslEndpointImpl coerce(Endpoint endpoint) {
if (!(endpoint instanceof SslEndpoint)) {
throw new IllegalArgumentException(
"Endpoint must be an SslEndpoint: " + endpoint);
}
return ((SslEndpoint) endpoint).impl;
}
private static SslServerEndpointImpl coerce(ServerEndpoint endpoint) {
if (!(endpoint instanceof SslServerEndpoint)) {
throw new IllegalArgumentException(
"Server endpoint must be an SslServerEndpoint: " +
endpoint);
}
return ((SslServerEndpoint) endpoint).impl;
}
public void setConnManagerFactory(Endpoint endpoint,
ConnManagerFactory factory)
{
SslEndpointImpl impl = coerce(endpoint);
impl.connectionManager = factory.create(impl);
}
public void setServerConnManager(ServerEndpoint endpoint,
ServerConnManager manager)
{
coerce(endpoint).serverConnectionManager = manager;
}
public InvocationConstraints getUnfulfilledConstraints(
OutboundRequestHandle handle)
{
return coerce(handle).getUnfulfilledConstraints();
}
private static CallContext coerce(OutboundRequestHandle handle) {
if (!(handle instanceof CallContext)) {
throw new IllegalArgumentException(
"Handle must be a CallContext: " + handle);
}
return (CallContext) handle;
}
}
/* -- Serialization -- */
/**
* Writes the <code>serverHost</code>, <code>port</code>, and
* <code>socketFactory</code> fields.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectOutputStream.PutField fields = out.putFields();
fields.put("serverHost", impl.serverHost);
fields.put("port", impl.port);
fields.put("socketFactory", impl.socketFactory);
out.writeFields();
}
/**
* Reads the <code>serverHost</code>, <code>port</code>, and
* <code>socketFactory</code> fields, checks that <code>serverHost</code>
* is not <code>null</code> and <code>port</code> is in range, and sets
* transient fields.
*
* @throws InvalidObjectException if <code>serverHost</code> is
* <code>null</code> or <code>port</code> is out of range
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
ObjectInputStream.GetField fields = in.readFields();
String serverHost = (String) fields.get("serverHost", null);
int port = fields.get("port", 0);
SocketFactory socketFactory =
(SocketFactory) fields.get("socketFactory", null);
if (serverHost == null) {
throw new InvalidObjectException("serverHost cannot be null");
} else if (port <= 0 || port > 0xFFFF) {
throw new InvalidObjectException("Invalid port: " + port);
}
impl = new SslEndpointImpl(this, serverHost, port, socketFactory);
}
}