FileDocCategorySizeDatePackage
OpenSSLSocketImpl.javaAPI DocAndroid 1.5 API37351Wed May 06 22:41:06 BST 2009org.apache.harmony.xnet.provider.jsse

OpenSSLSocketImpl

public class OpenSSLSocketImpl extends SSLSocket
Implementation of the class OpenSSLSocketImpl based on OpenSSL. The JNI native interface for some methods of this this class are defined in the file: org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp This class only supports SSLv3 and TLSv1. This should be documented elsewhere later, for example in the package.html or a separate reference document.

Fields Summary
private int
ssl_ctx
private int
ssl
private InputStream
is
private OutputStream
os
private final Object
handshakeLock
private Object
readLock
private Object
writeLock
private SSLParameters
sslParameters
private OpenSSLSessionImpl
sslSession
private Socket
socket
private boolean
autoClose
private boolean
handshakeStarted
private ArrayList
listeners
private long
ssl_op_no
private int
timeout
private InetSocketAddress
address
private static final String[]
supportedProtocols
private static int
instanceCount
private static long
SSL_OP_NO_SSLv3
SSL mode of operation with or without back compatibility. See the OpenSSL ssl.h header file for more information.
private static long
SSL_OP_NO_TLSv1
Constructors Summary
protected OpenSSLSocketImpl(String host, int port, InetAddress clientAddress, int clientPort, SSLParameters sslParameters)
Class constructor with 5 parameters: 1st is host

throws
IOException if network fails
throws
java.net.UnknownHostException host not defined

        super(host, port, clientAddress, clientPort);
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(InetAddress address, int port, InetAddress clientAddress, int clientPort, SSLParameters sslParameters)
Class constructor with 5 parameters: 1st is InetAddress

throws
IOException if network fails
throws
java.net.UnknownHostException host not defined

        super(address, port, clientAddress, clientPort);
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(Socket socket, String host, int port, boolean autoClose, SSLParameters sslParameters)
Constructor with 5 parameters: 1st is socket. Enhances an existing socket with SSL functionality.

throws
IOException if network fails

        super();
        this.socket = socket;
        this.timeout = socket.getSoTimeout();
        this.address = new InetSocketAddress(host, port);
        this.autoClose = autoClose;
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(SSLParameters sslParameters, long ssl_op_no)
Class constructor with 2 parameters

param
sslParameters Parameters for the SSL context
param
ssl_op_no Parameter to set the enabled protocols
throws
IOException if network fails

        super();
        this.sslParameters = sslParameters;
        this.ssl_op_no = ssl_op_no;
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(SSLParameters sslParameters)
Class constructor with 1 parameter

param
sslParameters Parameters for the SSL context
throws
IOException if network fails

        super();
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(String host, int port, SSLParameters sslParameters)
Class constructor with 3 parameters

throws
IOException if network fails
throws
java.net.UnknownHostException host not defined

        super(host, port);
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
protected OpenSSLSocketImpl(InetAddress address, int port, SSLParameters sslParameters)
Class constructor with 3 parameters: 1st is InetAddress

throws
IOException if network fails
throws
java.net.UnknownHostException host not defined

        super(address, port);
        this.sslParameters = sslParameters;
        init();
        updateInstanceCount(1);
    
Methods Summary
protected voidaccept(int m_ctx, boolean client_mode)
Performs the first part of a SSL/TLS handshaking process with a given 'host' connection and initializes the SSLSession.

        // Must be set because no handshaking is necessary
        // in this situation
        handshakeStarted = true;

        nativeaccept(this, m_ctx, client_mode);

        ServerSessionContext sessionContext
                = sslParameters.getServerSessionContext();
        sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
                sslParameters, super.getInetAddress().getHostName(),
                super.getPort(), sessionContext);
        sslSession.lastAccessedTime = System.currentTimeMillis();
        sessionContext.putSession(sslSession);
    
public voidaddHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener listener)
Registers a listener to be notified that a SSL handshake was successfully completed on this connection.

throws
IllegalArgumentException if listener is null.

        if (listener == null) {
            throw new IllegalArgumentException("Provided listener is null");
        }
        if (listeners == null) {
            listeners = new ArrayList();
        }
        listeners.add(listener);
    
public voidclose()
Closes the SSL socket. Once closed, a socket is not available for further use anymore under any circumstance. A new socket must be created.

throws
IOException if an I/O error happens during the socket's closure.

        // TODO: Close SSL sockets using a background thread so they close
        // gracefully.

        synchronized (handshakeLock) {
            if (!handshakeStarted) {
                handshakeStarted = true;
                
                synchronized (this) {
                    nativefree();

                    if (socket != null) {
                        if (autoClose && !socket.isClosed()) socket.close();
                    } else {
                        if (!super.isClosed()) super.close();
                    }
                }
                
                return;
            }
        }

        nativeinterrupt();

        synchronized (this) {
            synchronized (writeLock) {
                synchronized (readLock) {

                    IOException pendingException = null;

                    // Shut down the SSL connection, per se.
                    try {
                        if (handshakeStarted) {
                            nativeclose();
                        }
                    } catch (IOException ex) {
                        /*
                         * Note the exception at this point, but try to continue
                         * to clean the rest of this all up before rethrowing.
                         */
                        pendingException = ex;
                    }

                    /*
                     * Even if the above call failed, it is still safe to free
                     * the native structs, and we need to do so lest we leak
                     * memory.
                     */
                    nativefree();

                    if (socket != null) {
                        if (autoClose && !socket.isClosed())
                            socket.close();
                    } else {
                        if (!super.isClosed())
                            super.close();
                    }

                    if (pendingException != null) {
                        throw pendingException;
                    }
                }
            }
        }
    
protected voidfinalize()

        updateInstanceCount(-1);

        if (ssl == 0) {
            /*
             * It's already been closed, so there's no need to do anything
             * more at this point.
             */
            return;
        }

        // Note the underlying socket up-front, for possible later use.
        Socket underlyingSocket = socket;

        // Fire up a thread to (hopefully) do all the real work.
        Finalizer f = new Finalizer();
        f.setDaemon(true);
        f.start();

        /*
         * Give the finalizer thread one second to run. If it fails to
         * terminate in that time, interrupt it (which may help if it
         * is blocked on an interruptible I/O operation), make a note
         * in the log, and go ahead and close the underlying socket if
         * possible.
         */
        try {
            f.join(1000);
        } catch (InterruptedException ex) {
            // Reassert interrupted status.
            Thread.currentThread().interrupt();
        }

        if (f.isAlive()) {
            f.interrupt();
            Logger.global.log(Level.SEVERE,
                    "Slow finalization of SSL socket (" + this + ", for " +
                    underlyingSocket + ")");
            if ((underlyingSocket != null) && !underlyingSocket.isClosed()) {
                underlyingSocket.close();
            }
        }
    
private booleanfindSuite(java.lang.String suite)

        String[] supportedCipherSuites = nativegetsupportedciphersuites();
        for(int i = 0; i < supportedCipherSuites.length; i++)
            if (supportedCipherSuites[i].equals(suite)) return true;
        throw new IllegalArgumentException("Protocol " + suite +
        " is not supported.");
    
private OpenSSLSessionImplgetCachedClientSession()
Gets the suitable session reference from the session cache container.

return
OpenSSLSessionImpl

        if (super.getInetAddress() == null ||
                super.getInetAddress().getHostAddress() == null ||
                super.getInetAddress().getHostName() == null) {
            return null;
        }
        ClientSessionContext sessionContext
                = sslParameters.getClientSessionContext();
        return (OpenSSLSessionImpl) sessionContext.getSession(
                super.getInetAddress().getHostName(),
                super.getPort());
    
public booleangetEnableSessionCreation()
Returns true if new SSL sessions may be established by this socket.

return
true if the session may be created; false if a session already exists and must be resumed.

        return sslParameters.getEnableSessionCreation();
    
public java.lang.String[]getEnabledCipherSuites()
The names of the cipher suites that are in use in the actual the SSL connection are returned.

return
an array of cipher suite names

        return nativegetenabledciphersuites();
    
public java.lang.String[]getEnabledProtocols()
The names of the protocols' versions that are in use on this SSL connection.

return
an array of protocols names


                              
    
       
        ArrayList<String> array = new ArrayList<String>();

        if ((ssl_op_no & SSL_OP_NO_SSLv3) == 0x00000000L) {
            array.add(supportedProtocols[1]);
        }
        if ((ssl_op_no & SSL_OP_NO_TLSv1) == 0x00000000L) {
            array.add(supportedProtocols[2]);
        }
        return array.toArray(new String[array.size()]);
    
public java.io.InputStreamgetInputStream()
Returns an input stream for this SSL socket using native calls to the OpenSSL library.

return:
an input stream for reading bytes from this socket.
throws:
IOException if an I/O error occurs when creating the input stream, the socket is closed, the socket is not connected, or the socket input has been shutdown.

        synchronized(this) {
            if (is == null) {
                is = new SSLInputStream();
            }

            return is;
        }
    
public static intgetInstanceCount()


        
        synchronized (OpenSSLSocketImpl.class) {
            return instanceCount;
        }
    
public booleangetNeedClientAuth()
Returns true if the SSL socket needs client's authentication. Relevant only for server sockets!

return
true if client authentication is desired, false if not.

        return sslParameters.getNeedClientAuth();
    
public java.io.OutputStreamgetOutputStream()
Returns an output stream for this SSL socket using native calls to the OpenSSL library.

return
an output stream for writing bytes to this socket.
throws
IOException if an I/O error occurs when creating the output stream, or no connection to the socket exists.

        synchronized(this) {
            if (os == null) {
                os = new SSLOutputStream();
            }

            return os;
        }
    
public javax.net.ssl.SSLSessiongetSession()
The SSL session used by this connection is returned. The SSL session determines which cipher suite should be used by all connections within that session and which identities have the session's client and server. This method starts the SSL handshake.

return
the SSLSession.
throws
IOException if the handshake fails

        try {
            startHandshake();
        } catch (IOException e) {
            Logger.getLogger(getClass().getName()).log(Level.WARNING,
                    "Error negotiating SSL connection.", e);

            // return an invalid session with
            // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
            return SSLSessionImpl.NULL_SESSION;
        }
        return sslSession;
    
public java.lang.String[]getSupportedCipherSuites()
The names of the cipher suites which could be used by the SSL connection are returned.

return
an array of cipher suite names

        return nativegetsupportedciphersuites();
    
public java.lang.String[]getSupportedProtocols()
The names of the protocols' versions that may be used on this SSL connection.

return
an array of protocols names

        return supportedProtocols.clone();
    
public booleangetUseClientMode()
This method gives true back if the SSL socket is set to client mode.

return
true if the socket should do the handshaking as client.

        return sslParameters.getUseClientMode();
    
public booleangetWantClientAuth()
Returns true if the SSL socket requests client's authentication. Relevant only for server sockets!

return
true if client authentication is desired, false if not.

        return sslParameters.getWantClientAuth();
    
private voidinit()
Initialize the SSL socket and set the certificates for the future handshaking.

        nativeinitstatic();
    
        String alias = sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null);
        if (alias != null) {
            PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
            X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);

            ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream();
            PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS));
            privateKeyPEMWriter.writeObject(privateKey);
            privateKeyPEMWriter.close();

            ByteArrayOutputStream certificateOS = new ByteArrayOutputStream();
            PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS));

            for (int i = 0; i < certificates.length; i++) {
                certificateWriter.writeObject(certificates[i]);
            }
            certificateWriter.close();

            nativeinit(privateKeyOS.toString(), certificateOS.toString(),
                    sslParameters.getSecureRandomMember() != null ?
                    sslParameters.getSecureRandomMember().generateSeed(1024) : null);
        } else {
            nativeinit(null, null,
                    sslParameters.getSecureRandomMember() != null ?
                    sslParameters.getSecureRandomMember().generateSeed(1024) : null);
        }
    
native synchronized voidnativeaccept(java.net.Socket socketObject, int m_ctx, boolean client_mode)

private native java.lang.Stringnativecipherauthenticationmethod()

private native voidnativeclose()

private native booleannativeconnect(int ctx, java.net.Socket sock, boolean client_mode, int sslsession)
Adds OpenSSL functionality to the existing socket and starts the SSL handshaking.

private native voidnativefree()

private native java.lang.String[]nativegetenabledciphersuites()

private native intnativegetsslsession(int ssl)

static native java.lang.String[]nativegetsupportedciphersuites()
Gets all available ciphers from the current OpenSSL library. Needed by OpenSSLSocketFactory too.

private native voidnativeinit(java.lang.String privatekey, java.lang.String certificate, byte[] seed)

private static native voidnativeinitstatic()
Initialize OpenSSL library.

private native voidnativeinterrupt()

private native intnativeread(int timeout)
Reads with the native SSL_read function from the encrypted data stream

return
-1 if error or the end of the stream is reached.

private native intnativeread(byte[] b, int off, int len, int timeout)

private native voidnativesetenabledciphersuites(java.lang.String controlString)
Calls the SSL_CTX_set_cipher_list(...) OpenSSL function with the passed char array.

private native voidnativesetenabledprotocols(long l)

private static native intnativeverifysignature(byte[] message, byte[] signature, java.lang.String algorithm, byte[] modulus, byte[] exponent)

private native voidnativewrite(int b)
Writes with the native SSL_write function to the encrypted data stream.

private native voidnativewrite(byte[] b, int off, int len)

public voidremoveHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener listener)
The method removes a registered listener.

throws
IllegalArgumentException if listener is null or not registered

        if (listener == null) {
            throw new IllegalArgumentException("Provided listener is null");
        }
        if (listeners == null) {
            throw new IllegalArgumentException(
                    "Provided listener is not registered");
        }
        if (!listeners.remove(listener)) {
            throw new IllegalArgumentException(
                    "Provided listener is not registered");
        }
    
public voidsendUrgentData(int data)
This method is not supported for SSLSocket implementation.

        throw new SocketException(
                "Method sendUrgentData() is not supported.");
    
public voidsetEnableSessionCreation(boolean flag)
Set a flag for the socket to inhibit or to allow the creation of a new SSL sessions. If the flag is set to false, and there are no actual sessions to resume, then there will be no successful handshaking.

param
flag true if session may be created; false if a session already exists and must be resumed.

        sslParameters.setEnableSessionCreation(flag);
    
public voidsetEnabledCipherSuites(java.lang.String[] suites)
This method enables the cipher suites listed by getSupportedCipherSuites().

param
suites names of all the cipher suites to put on use
throws
IllegalArgumentException when one or more of the ciphers in array suites are not supported, or when the array is null.

        if (suites == null) {
            throw new IllegalArgumentException("Provided parameter is null");
        }
        String controlString = "";
        for(int i = 0; i < suites.length; i++) {
            findSuite(suites[i]);
            if (i == 0) controlString = suites[i];
            else controlString += ":" + suites[i];
        }
        nativesetenabledciphersuites(controlString);
    
public synchronized voidsetEnabledProtocols(java.lang.String[] protocols)
This method enables the protocols' versions listed by getSupportedProtocols().

param
protocols The names of all the protocols to put on use
throws
IllegalArgumentException when one or more of the names in the array are not supported, or when the array is null.


        if (protocols == null) {
            throw new IllegalArgumentException("Provided parameter is null");
        }

        ssl_op_no  = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;

        for(int i = 0; i < protocols.length; i++) {
            if (protocols[i].equals("SSLv3"))
                ssl_op_no ^= SSL_OP_NO_SSLv3;
            else if (protocols[i].equals("TLSv1"))
                ssl_op_no ^= SSL_OP_NO_TLSv1;
            else throw new IllegalArgumentException("Protocol " + protocols[i] +
            " is not supported.");
        }

        nativesetenabledprotocols(ssl_op_no);
    
public voidsetNeedClientAuth(boolean need)
Sets the SSL socket to use client's authentication. Relevant only for server sockets!

param
need true if client authentication is desired, false if not.

        sslParameters.setNeedClientAuth(need);
    
public voidsetOOBInline(boolean on)
This method is not supported for SSLSocket implementation.

        throw new SocketException(
                "Methods sendUrgentData, setOOBInline are not supported.");
    
public synchronized voidsetSoTimeout(int timeout)
Set the read timeout on this socket. The SO_TIMEOUT option, is specified in milliseconds. The read operation will block indefinitely for a zero value.

param
timeout the read timeout value
throws
SocketException if an error occurs setting the option

        super.setSoTimeout(timeout);
        this.timeout = timeout;
    
public synchronized voidsetUseClientMode(boolean mode)
This method set the actual SSL socket to client mode.

param
mode true if the socket starts in client mode
throws
IllegalArgumentException if mode changes during handshake.

        if (handshakeStarted) {
            throw new IllegalArgumentException(
            "Could not change the mode after the initial handshake has begun.");
        }
        sslParameters.setUseClientMode(mode);
    
public voidsetWantClientAuth(boolean want)
Sets the SSL socket to use client's authentication. Relevant only for server sockets! Notice that in contrast to setNeedClientAuth(..) this method will continue the negotiation if the client decide not to send authentication credentials.

param
want true if client authentication is desired, false if not.

        sslParameters.setWantClientAuth(want);
    
public voidshutdownInput()
This method is not supported for this SSLSocket implementation.

        throw new UnsupportedOperationException(
        "Method shutdownInput() is not supported.");
    
public voidshutdownOutput()
This method is not supported for this SSLSocket implementation.

        throw new UnsupportedOperationException(
        "Method shutdownOutput() is not supported.");
    
public synchronized voidstartHandshake()
Starts a TLS/SSL handshake on this connection using some native methods from the OpenSSL library. It can negotiate new encryption keys, change cipher suites, or initiate a new session. The certificate chain is verified if the correspondent property in java.Security is set. All listensers are notified at the end of the TLS/SSL handshake.

throws
IOException if network fails

    

                                                                   
          
        synchronized (handshakeLock) {
            if (!handshakeStarted) {
                handshakeStarted = true;
            } else {
                return;
            }
        }

        OpenSSLSessionImpl session = getCachedClientSession();

        // Check if it's allowed to create a new session (default is true)
        if (session == null && !sslParameters.getEnableSessionCreation()) {
            throw new SSLHandshakeException("SSL Session may not be created");
        } else {
            Socket socket = this.socket != null ? this.socket : this;
            int sessionId = session != null ? session.session : 0;
            boolean reusedSession;
            synchronized (OpenSSLSocketImpl.class) {
                reusedSession = nativeconnect(ssl_ctx, socket,
                        sslParameters.getUseClientMode(), sessionId);
            }
            if (reusedSession) {
                // nativeconnect shouldn't return true if the session is not
                // done
                session.lastAccessedTime = System.currentTimeMillis();
                sslSession = session;

                LoggerHolder.logger.fine("Reused cached session for "
                        + getInetAddress().getHostName() + ".");
            } else {
                if (session != null) {
                    LoggerHolder.logger.fine("Reuse of cached session for "
                            + getInetAddress().getHostName() + " failed.");
                } else {
                    LoggerHolder.logger.fine("Created new session for "
                            + getInetAddress().getHostName() + ".");
                }

                ClientSessionContext sessionContext
                        = sslParameters.getClientSessionContext();
                synchronized (OpenSSLSocketImpl.class) {
                    sessionId = nativegetsslsession(ssl);
                }
                if (address == null) {
                    sslSession = new OpenSSLSessionImpl(
                            sessionId, sslParameters,
                            super.getInetAddress().getHostName(),
                            super.getPort(), sessionContext);
                } else  {
                    sslSession = new OpenSSLSessionImpl(
                            sessionId, sslParameters,
                            address.getHostName(), address.getPort(),
                            sessionContext);
                }

                try {
                    X509Certificate[] peerCertificates = (X509Certificate[])
                            sslSession.getPeerCertificates();

                    if (peerCertificates == null
                            || peerCertificates.length == 0) {
                        throw new SSLException("Server sends no certificate");
                    }

                    String authMethod;
                    synchronized (OpenSSLSocketImpl.class) {
                        authMethod = nativecipherauthenticationmethod();
                    }
                    sslParameters.getTrustManager().checkServerTrusted(
                            peerCertificates,
                            authMethod);
                    sessionContext.putSession(sslSession);
                } catch (CertificateException e) {
                    throw new SSLException("Not trusted server certificate", e);
                }
            }
        }

        if (listeners != null) {
            // notify the listeners
            HandshakeCompletedEvent event =
                new HandshakeCompletedEvent(this, sslSession);
            int size = listeners.size();
            for (int i = 0; i < size; i++) {
                listeners.get(i).handshakeCompleted(event);
            }
        }
    
private static voidupdateInstanceCount(int amount)

        synchronized (OpenSSLSocketImpl.class) {
            instanceCount += amount;
        }
    
public static booleanverifySignature(byte[] message, byte[] signature, java.lang.String algorithm, java.security.interfaces.RSAPublicKey key)
Verifies an RSA signature. Conceptually, this method doesn't really belong here, but due to its native code being closely tied to OpenSSL (just like the rest of this class), we put it here for the time being. This also solves potential problems with native library initialization.

param
message The message to verify
param
signature The signature to verify
param
algorithm The hash/sign algorithm to use, i.e. "RSA-SHA1"
param
key The RSA public key to use
return
true if the verification succeeds, false otherwise

        byte[] modulus = key.getModulus().toByteArray();
        byte[] exponent = key.getPublicExponent().toByteArray();

        return nativeverifysignature(message, signature, algorithm, modulus, exponent) == 1;
    
private intverify_callback(byte[][] bytes)
Callback methode for the OpenSSL native certificate verification process.

param
bytes Byte array containing the cert's information.
return
0 if the certificate verification fails or 1 if OK

        try {
            X509Certificate[] peerCertificateChain
                    = new X509Certificate[bytes.length];
            for(int i = 0; i < bytes.length; i++) {
                peerCertificateChain[i] =
                    new X509CertImpl(javax.security.cert.X509Certificate.getInstance(bytes[i]).getEncoded());
            }

            try {
                // TODO "null" String
                sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain, "null");
            } catch (CertificateException e) {
                throw new AlertException(AlertProtocol.BAD_CERTIFICATE,
                        new SSLException("Not trusted server certificate", e));
            }
        } catch (javax.security.cert.CertificateException e) {
            return 0;
        } catch (IOException e) {
            return 0;
        }
        return 1;