Home | History | Annotate | Download | only in jsse
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.apache.harmony.xnet.provider.jsse;
     18 
     19 import dalvik.system.BlockGuard;
     20 import dalvik.system.CloseGuard;
     21 
     22 import java.io.FileDescriptor;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.io.OutputStream;
     26 import java.net.InetAddress;
     27 import java.net.Socket;
     28 import java.net.SocketException;
     29 import java.security.PrivateKey;
     30 import java.security.SecureRandom;
     31 import java.security.cert.CertificateEncodingException;
     32 import java.security.cert.CertificateException;
     33 import java.security.cert.X509Certificate;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 import java.util.HashSet;
     37 import java.util.Set;
     38 import javax.net.ssl.HandshakeCompletedEvent;
     39 import javax.net.ssl.HandshakeCompletedListener;
     40 import javax.net.ssl.SSLException;
     41 import javax.net.ssl.SSLHandshakeException;
     42 import javax.net.ssl.SSLProtocolException;
     43 import javax.net.ssl.SSLSession;
     44 import javax.net.ssl.X509TrustManager;
     45 import javax.security.auth.x500.X500Principal;
     46 import libcore.io.Streams;
     47 import org.apache.harmony.security.provider.cert.X509CertImpl;
     48 
     49 /**
     50  * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
     51  * <p>
     52  * This class only supports SSLv3 and TLSv1. This should be documented elsewhere
     53  * later, for example in the package.html or a separate reference document.
     54  * <p>
     55  * Extensions to SSLSocket include:
     56  * <ul>
     57  * <li>handshake timeout
     58  * <li>compression methods
     59  * <li>session tickets
     60  * <li>Server Name Indication
     61  * </ul>
     62  */
     63 public class OpenSSLSocketImpl
     64         extends javax.net.ssl.SSLSocket
     65         implements NativeCrypto.SSLHandshakeCallbacks {
     66 
     67     private int sslNativePointer;
     68     private InputStream is;
     69     private OutputStream os;
     70     private final Object handshakeLock = new Object();
     71     private final Object readLock = new Object();
     72     private final Object writeLock = new Object();
     73     private SSLParametersImpl sslParameters;
     74     private String[] enabledProtocols;
     75     private String[] enabledCipherSuites;
     76     private String[] enabledCompressionMethods;
     77     private boolean useSessionTickets;
     78     private String hostname;
     79     private OpenSSLSessionImpl sslSession;
     80     private final Socket socket;
     81     private boolean autoClose;
     82     private boolean handshakeStarted = false;
     83     private final CloseGuard guard = CloseGuard.get();
     84 
     85     /**
     86      * Not set to true until the update from native that tells us the
     87      * full handshake is complete, since SSL_do_handshake can return
     88      * before the handshake is completely done due to
     89      * handshake_cutthrough support.
     90      */
     91     private boolean handshakeCompleted = false;
     92 
     93     private ArrayList<HandshakeCompletedListener> listeners;
     94 
     95     /**
     96      * Local cache of timeout to avoid getsockopt on every read and
     97      * write for non-wrapped sockets. Note that
     98      * OpenSSLSocketImplWrapper overrides setSoTimeout and
     99      * getSoTimeout to delegate to the wrapped socket.
    100      */
    101     private int timeoutMilliseconds = 0;
    102 
    103     private int handshakeTimeoutMilliseconds = -1;  // -1 = same as timeout; 0 = infinite
    104     private String wrappedHost;
    105     private int wrappedPort;
    106 
    107     protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
    108         this.socket = this;
    109         init(sslParameters);
    110     }
    111 
    112     protected OpenSSLSocketImpl(SSLParametersImpl sslParameters,
    113                                 String[] enabledProtocols,
    114                                 String[] enabledCipherSuites,
    115                                 String[] enabledCompressionMethods) throws IOException {
    116         this.socket = this;
    117         init(sslParameters, enabledProtocols, enabledCipherSuites, enabledCompressionMethods);
    118     }
    119 
    120     protected OpenSSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
    121             throws IOException {
    122         super(host, port);
    123         this.socket = this;
    124         init(sslParameters);
    125     }
    126 
    127     protected OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
    128             throws IOException {
    129         super(address, port);
    130         this.socket = this;
    131         init(sslParameters);
    132     }
    133 
    134 
    135     protected OpenSSLSocketImpl(String host, int port,
    136                                 InetAddress clientAddress, int clientPort,
    137                                 SSLParametersImpl sslParameters) throws IOException {
    138         super(host, port, clientAddress, clientPort);
    139         this.socket = this;
    140         init(sslParameters);
    141     }
    142 
    143     protected OpenSSLSocketImpl(InetAddress address, int port,
    144                                 InetAddress clientAddress, int clientPort,
    145                                 SSLParametersImpl sslParameters) throws IOException {
    146         super(address, port, clientAddress, clientPort);
    147         this.socket = this;
    148         init(sslParameters);
    149     }
    150 
    151     /**
    152      * Create an SSL socket that wraps another socket. Invoked by
    153      * OpenSSLSocketImplWrapper constructor.
    154      */
    155     protected OpenSSLSocketImpl(Socket socket, String host, int port,
    156             boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
    157         this.socket = socket;
    158         this.wrappedHost = host;
    159         this.wrappedPort = port;
    160         this.autoClose = autoClose;
    161         init(sslParameters);
    162 
    163         // this.timeout is not set intentionally.
    164         // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
    165         // to wrapped socket
    166     }
    167 
    168     /**
    169      * Initialize the SSL socket and set the certificates for the
    170      * future handshaking.
    171      */
    172     private void init(SSLParametersImpl sslParameters) throws IOException {
    173         init(sslParameters,
    174              NativeCrypto.getSupportedProtocols(),
    175              NativeCrypto.getDefaultCipherSuites(),
    176              NativeCrypto.getDefaultCompressionMethods());
    177     }
    178 
    179     /**
    180      * Initialize the SSL socket and set the certificates for the
    181      * future handshaking.
    182      */
    183     private void init(SSLParametersImpl sslParameters,
    184                       String[] enabledProtocols,
    185                       String[] enabledCipherSuites,
    186                       String[] enabledCompressionMethods) throws IOException {
    187         this.sslParameters = sslParameters;
    188         this.enabledProtocols = enabledProtocols;
    189         this.enabledCipherSuites = enabledCipherSuites;
    190         this.enabledCompressionMethods = enabledCompressionMethods;
    191     }
    192 
    193     /**
    194      * Gets the suitable session reference from the session cache container.
    195      */
    196     private OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext) {
    197         if (super.getInetAddress() == null ||
    198                 super.getInetAddress().getHostAddress() == null ||
    199                 super.getInetAddress().getHostName() == null) {
    200             return null;
    201         }
    202         OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(
    203                 super.getInetAddress().getHostName(),
    204                 super.getPort());
    205         if (session == null) {
    206             return null;
    207         }
    208 
    209         String protocol = session.getProtocol();
    210         boolean protocolFound = false;
    211         for (String enabledProtocol : enabledProtocols) {
    212             if (protocol.equals(enabledProtocol)) {
    213                 protocolFound = true;
    214                 break;
    215             }
    216         }
    217         if (!protocolFound) {
    218             return null;
    219         }
    220 
    221         String cipherSuite = session.getCipherSuite();
    222         boolean cipherSuiteFound = false;
    223         for (String enabledCipherSuite : enabledCipherSuites) {
    224             if (cipherSuite.equals(enabledCipherSuite)) {
    225                 cipherSuiteFound = true;
    226                 break;
    227             }
    228         }
    229         if (!cipherSuiteFound) {
    230             return null;
    231         }
    232 
    233         String compressionMethod = session.getCompressionMethod();
    234         boolean compressionMethodFound = false;
    235         for (String enabledCompressionMethod : enabledCompressionMethods) {
    236             if (compressionMethod.equals(enabledCompressionMethod)) {
    237                 compressionMethodFound = true;
    238                 break;
    239             }
    240         }
    241         if (!compressionMethodFound) {
    242             return null;
    243         }
    244 
    245         return session;
    246     }
    247 
    248     /**
    249      * Starts a TLS/SSL handshake on this connection using some native methods
    250      * from the OpenSSL library. It can negotiate new encryption keys, change
    251      * cipher suites, or initiate a new session. The certificate chain is
    252      * verified if the correspondent property in java.Security is set. All
    253      * listeners are notified at the end of the TLS/SSL handshake.
    254      */
    255     @Override
    256     public void startHandshake() throws IOException {
    257         startHandshake(true);
    258     }
    259 
    260     private void checkOpen() throws SocketException {
    261         if (isClosed()) {
    262             throw new SocketException("Socket is closed");
    263         }
    264     }
    265 
    266     /**
    267      * Perform the handshake
    268      *
    269      * @param full If true, disable handshake cutthrough for a fully synchronous handshake
    270      */
    271     public synchronized void startHandshake(boolean full) throws IOException {
    272         synchronized (handshakeLock) {
    273             checkOpen();
    274             if (!handshakeStarted) {
    275                 handshakeStarted = true;
    276             } else {
    277                 return;
    278             }
    279         }
    280 
    281         // note that this modifies the global seed, not something specific to the connection
    282         final int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES;
    283         final SecureRandom secureRandom = sslParameters.getSecureRandomMember();
    284         if (secureRandom == null) {
    285             NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);
    286         } else {
    287             NativeCrypto.RAND_seed(secureRandom.generateSeed(seedLengthInBytes));
    288         }
    289 
    290         final boolean client = sslParameters.getUseClientMode();
    291 
    292         final int sslCtxNativePointer = (client) ?
    293             sslParameters.getClientSessionContext().sslCtxNativePointer :
    294             sslParameters.getServerSessionContext().sslCtxNativePointer;
    295 
    296         this.sslNativePointer = 0;
    297         boolean exception = true;
    298         try {
    299             sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
    300             guard.open("close");
    301 
    302             // setup server certificates and private keys.
    303             // clients will receive a call back to request certificates.
    304             if (!client) {
    305                 Set<String> keyTypes = new HashSet<String>();
    306                 for (String enabledCipherSuite : enabledCipherSuites) {
    307                     if (enabledCipherSuite.equals(NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
    308                         continue;
    309                     }
    310                     String keyType = CipherSuite.getByName(enabledCipherSuite).getServerKeyType();
    311                     if (keyType != null) {
    312                         keyTypes.add(keyType);
    313                     }
    314                 }
    315                 for (String keyType : keyTypes) {
    316                     try {
    317                         setCertificate(sslParameters.getKeyManager().chooseServerAlias(keyType,
    318                                                                                        null,
    319                                                                                        this));
    320                     } catch (CertificateEncodingException e) {
    321                         throw new IOException(e);
    322                     }
    323                 }
    324             }
    325 
    326             NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
    327             NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
    328             if (enabledCompressionMethods.length != 0) {
    329                 NativeCrypto.setEnabledCompressionMethods(sslNativePointer,
    330                                                           enabledCompressionMethods);
    331             }
    332             if (useSessionTickets) {
    333                 NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);
    334             }
    335             if (hostname != null) {
    336                 NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, hostname);
    337             }
    338 
    339             boolean enableSessionCreation = sslParameters.getEnableSessionCreation();
    340             if (!enableSessionCreation) {
    341                 NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
    342                                                               enableSessionCreation);
    343             }
    344 
    345             AbstractSessionContext sessionContext;
    346             if (client) {
    347                 // look for client session to reuse
    348                 ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext();
    349                 sessionContext = clientSessionContext;
    350                 OpenSSLSessionImpl session = getCachedClientSession(clientSessionContext);
    351                 if (session != null) {
    352                     NativeCrypto.SSL_set_session(sslNativePointer,
    353                                                  session.sslSessionNativePointer);
    354                 }
    355             } else {
    356                 sessionContext = sslParameters.getServerSessionContext();
    357             }
    358 
    359             // setup peer certificate verification
    360             if (client) {
    361                 // TODO support for anonymous cipher would require us to
    362                 // conditionally use SSL_VERIFY_NONE
    363             } else {
    364                 // needing client auth takes priority...
    365                 boolean certRequested;
    366                 if (sslParameters.getNeedClientAuth()) {
    367                     NativeCrypto.SSL_set_verify(sslNativePointer,
    368                                                 NativeCrypto.SSL_VERIFY_PEER
    369                                                 | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
    370                     certRequested = true;
    371                 // ... over just wanting it...
    372                 } else if (sslParameters.getWantClientAuth()) {
    373                     NativeCrypto.SSL_set_verify(sslNativePointer,
    374                                                 NativeCrypto.SSL_VERIFY_PEER);
    375                     certRequested = true;
    376                 // ... and it defaults properly so don't call SSL_set_verify in the common case.
    377                 } else {
    378                     certRequested = false;
    379                 }
    380 
    381                 if (certRequested) {
    382                     X509TrustManager trustManager = sslParameters.getTrustManager();
    383                     X509Certificate[] issuers = trustManager.getAcceptedIssuers();
    384                     if (issuers != null && issuers.length != 0) {
    385                         byte[][] issuersBytes;
    386                         try {
    387                             issuersBytes = NativeCrypto.encodeIssuerX509Principals(issuers);
    388                         } catch (CertificateEncodingException e) {
    389                             throw new IOException("Problem encoding principals", e);
    390                         }
    391                         NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
    392                     }
    393                 }
    394             }
    395 
    396             if (client && full) {
    397                 // we want to do a full synchronous handshake, so turn off cutthrough
    398                 NativeCrypto.SSL_clear_mode(sslNativePointer,
    399                                             NativeCrypto.SSL_MODE_HANDSHAKE_CUTTHROUGH);
    400             }
    401 
    402             // Temporarily use a different timeout for the handshake process
    403             int savedTimeoutMilliseconds = getSoTimeout();
    404             if (handshakeTimeoutMilliseconds >= 0) {
    405                 setSoTimeout(handshakeTimeoutMilliseconds);
    406             }
    407 
    408             int sslSessionNativePointer;
    409             try {
    410                 sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,
    411                         socket.getFileDescriptor$(), this, getSoTimeout(), client);
    412             } catch (CertificateException e) {
    413                 SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
    414                 wrapper.initCause(e);
    415                 throw wrapper;
    416             }
    417             byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
    418             sslSession = (OpenSSLSessionImpl) sessionContext.getSession(sessionId);
    419             if (sslSession != null) {
    420                 sslSession.lastAccessedTime = System.currentTimeMillis();
    421                 NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
    422             } else {
    423                 if (!enableSessionCreation) {
    424                     // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled
    425                     throw new IllegalStateException("SSL Session may not be created");
    426                 }
    427                 X509Certificate[] localCertificates
    428                         = createCertChain(NativeCrypto.SSL_get_certificate(sslNativePointer));
    429                 X509Certificate[] peerCertificates
    430                         = createCertChain(NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));
    431                 if (wrappedHost == null) {
    432                     sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
    433                                                         localCertificates, peerCertificates,
    434                                                         super.getInetAddress().getHostName(),
    435                                                         super.getPort(), sessionContext);
    436                 } else  {
    437                     sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
    438                                                         localCertificates, peerCertificates,
    439                                                         wrappedHost, wrappedPort,
    440                                                         sessionContext);
    441                 }
    442                 // if not, putSession later in handshakeCompleted() callback
    443                 if (handshakeCompleted) {
    444                     sessionContext.putSession(sslSession);
    445                 }
    446             }
    447 
    448             // Restore the original timeout now that the handshake is complete
    449             if (handshakeTimeoutMilliseconds >= 0) {
    450                 setSoTimeout(savedTimeoutMilliseconds);
    451             }
    452 
    453             // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
    454             if (handshakeCompleted) {
    455                 notifyHandshakeCompletedListeners();
    456             }
    457 
    458             exception = false;
    459         } catch (SSLProtocolException e) {
    460             throw new SSLHandshakeException(e);
    461         } finally {
    462             // on exceptional exit, treat the socket as closed
    463             if (exception) {
    464                 close();
    465             }
    466         }
    467     }
    468 
    469     /**
    470      * Return a possibly null array of X509Certificates given the
    471      * possibly null array of DER encoded bytes.
    472      */
    473     private static X509Certificate[] createCertChain(byte[][] certificatesBytes) {
    474         if (certificatesBytes == null) {
    475             return null;
    476         }
    477         X509Certificate[] certificates = new X509Certificate[certificatesBytes.length];
    478         for (int i = 0; i < certificatesBytes.length; i++) {
    479             try {
    480                 certificates[i] = new X509CertImpl(certificatesBytes[i]);
    481             } catch (IOException e) {
    482                 return null;
    483             }
    484         }
    485         return certificates;
    486     }
    487 
    488     private void setCertificate(String alias) throws CertificateEncodingException, SSLException {
    489         if (alias == null) {
    490             return;
    491         }
    492         PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
    493         if (privateKey == null) {
    494             return;
    495         }
    496         X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
    497         if (certificates == null) {
    498             return;
    499         }
    500 
    501         byte[] privateKeyBytes = privateKey.getEncoded();
    502         byte[][] certificateBytes = NativeCrypto.encodeCertificates(certificates);
    503         NativeCrypto.SSL_use_PrivateKey(sslNativePointer, privateKeyBytes);
    504         NativeCrypto.SSL_use_certificate(sslNativePointer, certificateBytes);
    505 
    506         // checks the last installed private key and certificate,
    507         // so need to do this once per loop iteration
    508         NativeCrypto.SSL_check_private_key(sslNativePointer);
    509     }
    510 
    511     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / client_cert_cb
    512     public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
    513             throws CertificateEncodingException, SSLException {
    514 
    515         String[] keyTypes = new String[keyTypeBytes.length];
    516         for (int i = 0; i < keyTypeBytes.length; i++) {
    517             keyTypes[i] = CipherSuite.getClientKeyType(keyTypeBytes[i]);
    518         }
    519 
    520         X500Principal[] issuers;
    521         if (asn1DerEncodedPrincipals == null) {
    522             issuers = null;
    523         } else {
    524             issuers = new X500Principal[asn1DerEncodedPrincipals.length];
    525             for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
    526                 issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
    527             }
    528         }
    529         setCertificate(sslParameters.getKeyManager().chooseClientAlias(keyTypes, issuers, this));
    530     }
    531 
    532     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback
    533     public void handshakeCompleted() {
    534         handshakeCompleted = true;
    535 
    536         // If sslSession is null, the handshake was completed during
    537         // the call to NativeCrypto.SSL_do_handshake and not during a
    538         // later read operation. That means we do not need to fix up
    539         // the SSLSession and session cache or notify
    540         // HandshakeCompletedListeners, it will be done in
    541         // startHandshake.
    542         if (sslSession == null) {
    543             return;
    544         }
    545 
    546         // reset session id from the native pointer and update the
    547         // appropriate cache.
    548         sslSession.resetId();
    549         AbstractSessionContext sessionContext =
    550             (sslParameters.getUseClientMode())
    551             ? sslParameters.getClientSessionContext()
    552                 : sslParameters.getServerSessionContext();
    553         sessionContext.putSession(sslSession);
    554 
    555         // let listeners know we are finally done
    556         notifyHandshakeCompletedListeners();
    557     }
    558 
    559     private void notifyHandshakeCompletedListeners() {
    560         if (listeners != null && !listeners.isEmpty()) {
    561             // notify the listeners
    562             HandshakeCompletedEvent event =
    563                 new HandshakeCompletedEvent(this, sslSession);
    564             for (HandshakeCompletedListener listener : listeners) {
    565                 try {
    566                     listener.handshakeCompleted(event);
    567                 } catch (RuntimeException e) {
    568                     // The RI runs the handlers in a separate thread,
    569                     // which we do not. But we try to preserve their
    570                     // behavior of logging a problem and not killing
    571                     // the handshaking thread just because a listener
    572                     // has a problem.
    573                     Thread thread = Thread.currentThread();
    574                     thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
    575                 }
    576             }
    577         }
    578     }
    579 
    580     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
    581     @Override public void verifyCertificateChain(byte[][] bytes, String authMethod)
    582             throws CertificateException {
    583         try {
    584             if (bytes == null || bytes.length == 0) {
    585                 throw new SSLException("Peer sent no certificate");
    586             }
    587             X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length];
    588             for (int i = 0; i < bytes.length; i++) {
    589                 peerCertificateChain[i] = new X509CertImpl(bytes[i]);
    590             }
    591             boolean client = sslParameters.getUseClientMode();
    592             if (client) {
    593                 sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain,
    594                                                                    authMethod);
    595             } else {
    596                 String authType = peerCertificateChain[0].getPublicKey().getAlgorithm();
    597                 sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain,
    598                                                                    authType);
    599             }
    600 
    601         } catch (CertificateException e) {
    602             throw e;
    603         } catch (RuntimeException e) {
    604             throw e;
    605         } catch (Exception e) {
    606             throw new RuntimeException(e);
    607         }
    608     }
    609 
    610     @Override public InputStream getInputStream() throws IOException {
    611         checkOpen();
    612         synchronized (this) {
    613             if (is == null) {
    614                 is = new SSLInputStream();
    615             }
    616 
    617             return is;
    618         }
    619     }
    620 
    621     @Override public OutputStream getOutputStream() throws IOException {
    622         checkOpen();
    623         synchronized (this) {
    624             if (os == null) {
    625                 os = new SSLOutputStream();
    626             }
    627 
    628             return os;
    629         }
    630     }
    631 
    632     /**
    633      * This inner class provides input data stream functionality
    634      * for the OpenSSL native implementation. It is used to
    635      * read data received via SSL protocol.
    636      */
    637     private class SSLInputStream extends InputStream {
    638         SSLInputStream() throws IOException {
    639             /**
    640             /* Note: When startHandshake() throws an exception, no
    641              * SSLInputStream object will be created.
    642              */
    643             OpenSSLSocketImpl.this.startHandshake(false);
    644         }
    645 
    646         /**
    647          * Reads one byte. If there is no data in the underlying buffer,
    648          * this operation can block until the data will be
    649          * available.
    650          * @return read value.
    651          * @throws <code>IOException</code>
    652          */
    653         @Override
    654         public int read() throws IOException {
    655             return Streams.readSingleByte(this);
    656         }
    657 
    658         /**
    659          * Method acts as described in spec for superclass.
    660          * @see java.io.InputStream#read(byte[],int,int)
    661          */
    662         @Override
    663         public int read(byte[] buf, int offset, int byteCount) throws IOException {
    664             BlockGuard.getThreadPolicy().onNetwork();
    665             synchronized (readLock) {
    666                 checkOpen();
    667                 Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
    668                 if (byteCount == 0) {
    669                     return 0;
    670                 }
    671                 return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
    672                         OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
    673             }
    674         }
    675     }
    676 
    677     /**
    678      * This inner class provides output data stream functionality
    679      * for the OpenSSL native implementation. It is used to
    680      * write data according to the encryption parameters given in SSL context.
    681      */
    682     private class SSLOutputStream extends OutputStream {
    683         SSLOutputStream() throws IOException {
    684             /**
    685             /* Note: When startHandshake() throws an exception, no
    686              * SSLOutputStream object will be created.
    687              */
    688             OpenSSLSocketImpl.this.startHandshake(false);
    689         }
    690 
    691         /**
    692          * Method acts as described in spec for superclass.
    693          * @see java.io.OutputStream#write(int)
    694          */
    695         @Override
    696         public void write(int oneByte) throws IOException {
    697             Streams.writeSingleByte(this, oneByte);
    698         }
    699 
    700         /**
    701          * Method acts as described in spec for superclass.
    702          * @see java.io.OutputStream#write(byte[],int,int)
    703          */
    704         @Override
    705         public void write(byte[] buf, int offset, int byteCount) throws IOException {
    706             BlockGuard.getThreadPolicy().onNetwork();
    707             synchronized (writeLock) {
    708                 checkOpen();
    709                 Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
    710                 if (byteCount == 0) {
    711                     return;
    712                 }
    713                 NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
    714                         OpenSSLSocketImpl.this, buf, offset, byteCount);
    715             }
    716         }
    717     }
    718 
    719 
    720     @Override public SSLSession getSession() {
    721         if (sslSession == null) {
    722             try {
    723                 startHandshake(true);
    724             } catch (IOException e) {
    725                 // return an invalid session with
    726                 // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
    727                 return SSLSessionImpl.NULL_SESSION;
    728             }
    729         }
    730         return sslSession;
    731     }
    732 
    733     @Override public void addHandshakeCompletedListener(
    734             HandshakeCompletedListener listener) {
    735         if (listener == null) {
    736             throw new IllegalArgumentException("Provided listener is null");
    737         }
    738         if (listeners == null) {
    739             listeners = new ArrayList<HandshakeCompletedListener>();
    740         }
    741         listeners.add(listener);
    742     }
    743 
    744     @Override public void removeHandshakeCompletedListener(
    745             HandshakeCompletedListener listener) {
    746         if (listener == null) {
    747             throw new IllegalArgumentException("Provided listener is null");
    748         }
    749         if (listeners == null) {
    750             throw new IllegalArgumentException(
    751                     "Provided listener is not registered");
    752         }
    753         if (!listeners.remove(listener)) {
    754             throw new IllegalArgumentException(
    755                     "Provided listener is not registered");
    756         }
    757     }
    758 
    759     @Override public boolean getEnableSessionCreation() {
    760         return sslParameters.getEnableSessionCreation();
    761     }
    762 
    763     @Override public void setEnableSessionCreation(boolean flag) {
    764         sslParameters.setEnableSessionCreation(flag);
    765     }
    766 
    767     @Override public String[] getSupportedCipherSuites() {
    768         return NativeCrypto.getSupportedCipherSuites();
    769     }
    770 
    771     @Override public String[] getEnabledCipherSuites() {
    772         return enabledCipherSuites.clone();
    773     }
    774 
    775     @Override public void setEnabledCipherSuites(String[] suites) {
    776         enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
    777     }
    778 
    779     @Override public String[] getSupportedProtocols() {
    780         return NativeCrypto.getSupportedProtocols();
    781     }
    782 
    783     @Override public String[] getEnabledProtocols() {
    784         return enabledProtocols.clone();
    785     }
    786 
    787     @Override public void setEnabledProtocols(String[] protocols) {
    788         enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
    789     }
    790 
    791     /**
    792      * The names of the compression methods that may be used on this SSL
    793      * connection.
    794      * @return an array of compression methods
    795      */
    796     public String[] getSupportedCompressionMethods() {
    797         return NativeCrypto.getSupportedCompressionMethods();
    798     }
    799 
    800     /**
    801      * The names of the compression methods versions that are in use
    802      * on this SSL connection.
    803      *
    804      * @return an array of compression methods
    805      */
    806     public String[] getEnabledCompressionMethods() {
    807         return enabledCompressionMethods.clone();
    808     }
    809 
    810     /**
    811      * Enables compression methods listed by getSupportedCompressionMethods().
    812      *
    813      * @throws IllegalArgumentException when one or more of the names in the
    814      *             array are not supported, or when the array is null.
    815      */
    816     public void setEnabledCompressionMethods(String[] methods) {
    817         enabledCompressionMethods = NativeCrypto.checkEnabledCompressionMethods(methods);
    818     }
    819 
    820     /**
    821      * This method enables session ticket support.
    822      *
    823      * @param useSessionTickets True to enable session tickets
    824      */
    825     public void setUseSessionTickets(boolean useSessionTickets) {
    826         this.useSessionTickets = useSessionTickets;
    827     }
    828 
    829     /**
    830      * This method enables Server Name Indication
    831      *
    832      * @param hostname the desired SNI hostname, or null to disable
    833      */
    834     public void setHostname(String hostname) {
    835         this.hostname = hostname;
    836     }
    837 
    838     @Override public boolean getUseClientMode() {
    839         return sslParameters.getUseClientMode();
    840     }
    841 
    842     @Override public void setUseClientMode(boolean mode) {
    843         if (handshakeStarted) {
    844             throw new IllegalArgumentException(
    845                     "Could not change the mode after the initial handshake has begun.");
    846         }
    847         sslParameters.setUseClientMode(mode);
    848     }
    849 
    850     @Override public boolean getWantClientAuth() {
    851         return sslParameters.getWantClientAuth();
    852     }
    853 
    854     @Override public boolean getNeedClientAuth() {
    855         return sslParameters.getNeedClientAuth();
    856     }
    857 
    858     @Override public void setNeedClientAuth(boolean need) {
    859         sslParameters.setNeedClientAuth(need);
    860     }
    861 
    862     @Override public void setWantClientAuth(boolean want) {
    863         sslParameters.setWantClientAuth(want);
    864     }
    865 
    866     @Override public void sendUrgentData(int data) throws IOException {
    867         throw new SocketException("Method sendUrgentData() is not supported.");
    868     }
    869 
    870     @Override public void setOOBInline(boolean on) throws SocketException {
    871         throw new SocketException("Methods sendUrgentData, setOOBInline are not supported.");
    872     }
    873 
    874     @Override public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
    875         super.setSoTimeout(timeoutMilliseconds);
    876         this.timeoutMilliseconds = timeoutMilliseconds;
    877     }
    878 
    879     @Override public int getSoTimeout() throws SocketException {
    880         return timeoutMilliseconds;
    881     }
    882 
    883     /**
    884      * Set the handshake timeout on this socket.  This timeout is specified in
    885      * milliseconds and will be used only during the handshake process.
    886      */
    887     public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException {
    888         this.handshakeTimeoutMilliseconds = timeoutMilliseconds;
    889     }
    890 
    891     @Override public void close() throws IOException {
    892         // TODO: Close SSL sockets using a background thread so they close gracefully.
    893 
    894         synchronized (handshakeLock) {
    895             if (!handshakeStarted) {
    896                 // prevent further attempts to start handshake
    897                 handshakeStarted = true;
    898 
    899                 synchronized (this) {
    900                     free();
    901 
    902                     if (socket != this) {
    903                         if (autoClose && !socket.isClosed()) socket.close();
    904                     } else {
    905                         if (!super.isClosed()) super.close();
    906                     }
    907                 }
    908 
    909                 return;
    910             }
    911         }
    912 
    913         NativeCrypto.SSL_interrupt(sslNativePointer);
    914 
    915         synchronized (this) {
    916             synchronized (writeLock) {
    917                 synchronized (readLock) {
    918 
    919                     // Shut down the SSL connection, per se.
    920                     try {
    921                         if (handshakeStarted) {
    922                             BlockGuard.getThreadPolicy().onNetwork();
    923                             NativeCrypto.SSL_shutdown(sslNativePointer, socket.getFileDescriptor$(),
    924                                     this);
    925                         }
    926                     } catch (IOException ignored) {
    927                         /*
    928                          * Note that although close() can throw
    929                          * IOException, the RI does not throw if there
    930                          * is problem sending a "close notify" which
    931                          * can happen if the underlying socket is closed.
    932                          */
    933                     } finally {
    934                         /*
    935                          * Even if the above call failed, it is still safe to free
    936                          * the native structs, and we need to do so lest we leak
    937                          * memory.
    938                          */
    939                         free();
    940 
    941                         if (socket != this) {
    942                             if (autoClose && !socket.isClosed()) {
    943                                 socket.close();
    944                             }
    945                         } else {
    946                             if (!super.isClosed()) {
    947                                 super.close();
    948                             }
    949                         }
    950                     }
    951                 }
    952             }
    953         }
    954     }
    955 
    956     private void free() {
    957         if (sslNativePointer == 0) {
    958             return;
    959         }
    960         NativeCrypto.SSL_free(sslNativePointer);
    961         sslNativePointer = 0;
    962         guard.close();
    963     }
    964 
    965     @Override protected void finalize() throws Throwable {
    966         try {
    967             /*
    968              * Just worry about our own state. Notably we do not try and
    969              * close anything. The SocketImpl, either our own
    970              * PlainSocketImpl, or the Socket we are wrapping, will do
    971              * that. This might mean we do not properly SSL_shutdown, but
    972              * if you want to do that, properly close the socket yourself.
    973              *
    974              * The reason why we don't try to SSL_shutdown, is that there
    975              * can be a race between finalizers where the PlainSocketImpl
    976              * finalizer runs first and closes the socket. However, in the
    977              * meanwhile, the underlying file descriptor could be reused
    978              * for another purpose. If we call SSL_shutdown, the
    979              * underlying socket BIOs still have the old file descriptor
    980              * and will write the close notify to some unsuspecting
    981              * reader.
    982              */
    983             if (guard != null) {
    984                 guard.warnIfOpen();
    985             }
    986             free();
    987         } finally {
    988             super.finalize();
    989         }
    990     }
    991 
    992     @Override
    993     public FileDescriptor getFileDescriptor$() {
    994         if (socket == this) {
    995             return super.getFileDescriptor$();
    996         } else {
    997             return socket.getFileDescriptor$();
    998         }
    999     }
   1000 }
   1001