OpenSSLSocketImpl.java revision a6d7bdeb041f42efd0cb441dea270b07debde421
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
17package org.apache.harmony.xnet.provider.jsse;
18
19import dalvik.system.BlockGuard;
20import dalvik.system.CloseGuard;
21import java.io.FileDescriptor;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25import java.net.InetAddress;
26import java.net.Socket;
27import java.net.SocketException;
28import java.security.PrivateKey;
29import java.security.SecureRandom;
30import java.security.cert.CertificateEncodingException;
31import java.security.cert.CertificateException;
32import java.security.cert.X509Certificate;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.HashSet;
36import java.util.Set;
37import java.util.concurrent.atomic.AtomicInteger;
38import java.util.logging.Logger;
39import javax.net.ssl.HandshakeCompletedEvent;
40import javax.net.ssl.HandshakeCompletedListener;
41import javax.net.ssl.SSLException;
42import javax.net.ssl.SSLHandshakeException;
43import javax.net.ssl.SSLSession;
44import javax.net.ssl.X509TrustManager;
45import javax.security.auth.x500.X500Principal;
46import org.apache.harmony.security.provider.cert.X509CertImpl;
47
48/**
49 * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
50 * <p>
51 * This class only supports SSLv3 and TLSv1. This should be documented elsewhere
52 * later, for example in the package.html or a separate reference document.
53 * <p>
54 * Extensions to SSLSocket include:
55 * <ul>
56 * <li>handshake timeout
57 * <li>compression methods
58 * <li>session tickets
59 * <li>Server Name Indication
60 * </ul>
61 */
62public class OpenSSLSocketImpl
63        extends javax.net.ssl.SSLSocket
64        implements NativeCrypto.SSLHandshakeCallbacks {
65
66    private int sslNativePointer;
67    private InputStream is;
68    private OutputStream os;
69    private final Object handshakeLock = new Object();
70    private final Object readLock = new Object();
71    private final Object writeLock = new Object();
72    private SSLParametersImpl sslParameters;
73    private String[] enabledProtocols;
74    private String[] enabledCipherSuites;
75    private String[] enabledCompressionMethods;
76    private boolean useSessionTickets;
77    private String hostname;
78    private OpenSSLSessionImpl sslSession;
79    private final Socket socket;
80    private boolean autoClose;
81    private boolean handshakeStarted = false;
82    private final CloseGuard guard = CloseGuard.get();
83
84    /**
85     * Not set to true until the update from native that tells us the
86     * full handshake is complete, since SSL_do_handshake can return
87     * before the handshake is completely done due to
88     * handshake_cutthrough support.
89     */
90    private boolean handshakeCompleted = false;
91
92    private ArrayList<HandshakeCompletedListener> listeners;
93
94    /**
95     * Local cache of timeout to avoid getsockopt on every read and
96     * write for non-wrapped sockets. Note that
97     * OpenSSLSocketImplWrapper overrides setSoTimeout and
98     * getSoTimeout to delegate to the wrapped socket.
99     */
100    private int timeoutMilliseconds = 0;
101
102    private int handshakeTimeoutMilliseconds = -1;  // -1 = same as timeout; 0 = infinite
103    private String wrappedHost;
104    private int wrappedPort;
105
106    /**
107     * Class constructor with 1 parameter
108     *
109     * @param sslParameters Parameters for the SSL
110     *            context
111     * @throws IOException if network fails
112     */
113    protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
114        this.socket = this;
115        init(sslParameters);
116    }
117
118    /**
119     * Create an OpenSSLSocketImpl from an OpenSSLServerSocketImpl
120     *
121     * @param sslParameters Parameters for the SSL
122     *            context
123     * @throws IOException if network fails
124     */
125    protected OpenSSLSocketImpl(SSLParametersImpl sslParameters,
126                                String[] enabledProtocols,
127                                String[] enabledCipherSuites,
128                                String[] enabledCompressionMethods) throws IOException {
129        this.socket = this;
130        init(sslParameters, enabledProtocols, enabledCipherSuites, enabledCompressionMethods);
131    }
132
133    /**
134     * Class constructor with 3 parameters
135     *
136     * @throws IOException if network fails
137     * @throws java.net.UnknownHostException host not defined
138     */
139    protected OpenSSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
140            throws IOException {
141        super(host, port);
142        this.socket = this;
143        init(sslParameters);
144    }
145
146    /**
147     * Class constructor with 3 parameters: 1st is InetAddress
148     *
149     * @throws IOException if network fails
150     * @throws java.net.UnknownHostException host not defined
151     */
152    protected OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
153            throws IOException {
154        super(address, port);
155        this.socket = this;
156        init(sslParameters);
157    }
158
159
160    /**
161     * Class constructor with 5 parameters: 1st is host
162     *
163     * @throws IOException if network fails
164     * @throws java.net.UnknownHostException host not defined
165     */
166    protected OpenSSLSocketImpl(String host, int port,
167                                InetAddress clientAddress, int clientPort,
168                                SSLParametersImpl sslParameters)
169            throws IOException {
170        super(host, port, clientAddress, clientPort);
171        this.socket = this;
172        init(sslParameters);
173    }
174
175    /**
176     * Class constructor with 5 parameters: 1st is InetAddress
177     *
178     * @throws IOException if network fails
179     * @throws java.net.UnknownHostException host not defined
180     */
181    protected OpenSSLSocketImpl(InetAddress address, int port,
182                                InetAddress clientAddress, int clientPort,
183                                SSLParametersImpl sslParameters)
184            throws IOException {
185        super(address, port, clientAddress, clientPort);
186        this.socket = this;
187        init(sslParameters);
188    }
189
190    /**
191     * Constructor with 5 parameters: 1st is socket. Enhances an existing socket
192     * with SSL functionality. Invoked via OpenSSLSocketImplWrapper constructor.
193     *
194     * @throws IOException if network fails
195     */
196    protected OpenSSLSocketImpl(Socket socket, String host, int port,
197            boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
198        this.socket = socket;
199        this.wrappedHost = host;
200        this.wrappedPort = port;
201        this.autoClose = autoClose;
202        init(sslParameters);
203
204        // this.timeout is not set intentionally.
205        // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
206        // to wrapped socket
207    }
208
209    /**
210     * Initialize the SSL socket and set the certificates for the
211     * future handshaking.
212     */
213    private void init(SSLParametersImpl sslParameters) throws IOException {
214        init(sslParameters,
215             NativeCrypto.getSupportedProtocols(),
216             NativeCrypto.getDefaultCipherSuites(),
217             NativeCrypto.getDefaultCompressionMethods());
218    }
219
220    /**
221     * Initialize the SSL socket and set the certificates for the
222     * future handshaking.
223     */
224    private void init(SSLParametersImpl sslParameters,
225                      String[] enabledProtocols,
226                      String[] enabledCipherSuites,
227                      String[] enabledCompressionMethods) throws IOException {
228        this.sslParameters = sslParameters;
229        this.enabledProtocols = enabledProtocols;
230        this.enabledCipherSuites = enabledCipherSuites;
231        this.enabledCompressionMethods = enabledCompressionMethods;
232    }
233
234    /**
235     * Gets the suitable session reference from the session cache container.
236     *
237     * @return OpenSSLSessionImpl
238     */
239    private OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext) {
240        if (super.getInetAddress() == null ||
241                super.getInetAddress().getHostAddress() == null ||
242                super.getInetAddress().getHostName() == null) {
243            return null;
244        }
245        OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(
246                super.getInetAddress().getHostName(),
247                super.getPort());
248        if (session == null) {
249            return null;
250        }
251
252        String protocol = session.getProtocol();
253        boolean protocolFound = false;
254        for (String enabledProtocol : enabledProtocols) {
255            if (protocol.equals(enabledProtocol)) {
256                protocolFound = true;
257                break;
258            }
259        }
260        if (!protocolFound) {
261            return null;
262        }
263
264        String cipherSuite = session.getCipherSuite();
265        boolean cipherSuiteFound = false;
266        for (String enabledCipherSuite : enabledCipherSuites) {
267            if (cipherSuite.equals(enabledCipherSuite)) {
268                cipherSuiteFound = true;
269                break;
270            }
271        }
272        if (!cipherSuiteFound) {
273            return null;
274        }
275
276        String compressionMethod = session.getCompressionMethod();
277        boolean compressionMethodFound = false;
278        for (String enabledCompressionMethod : enabledCompressionMethods) {
279            if (compressionMethod.equals(enabledCompressionMethod)) {
280                compressionMethodFound = true;
281                break;
282            }
283        }
284        if (!compressionMethodFound) {
285            return null;
286        }
287
288        return session;
289    }
290
291    /**
292     * Ensures that logger is lazily loaded. The outer class seems to load
293     * before logging is ready.
294     */
295    static class LoggerHolder {
296        static final Logger logger = Logger.getLogger(OpenSSLSocketImpl.class.getName());
297    }
298
299    /**
300     * Starts a TLS/SSL handshake on this connection using some native methods
301     * from the OpenSSL library. It can negotiate new encryption keys, change
302     * cipher suites, or initiate a new session. The certificate chain is
303     * verified if the correspondent property in java.Security is set. All
304     * listeners are notified at the end of the TLS/SSL handshake.
305     *
306     * @throws <code>IOException</code> if network fails
307     */
308    @Override
309    public void startHandshake() throws IOException {
310        startHandshake(true);
311    }
312
313    /**
314     * Checks whether the socket is closed, and throws an exception.
315     *
316     * @throws SocketException
317     *             if the socket is closed.
318     */
319    private void checkOpen() throws SocketException {
320        if (isClosed()) {
321            throw new SocketException("Socket is closed");
322        }
323    }
324
325    /**
326     * Perform the handshake
327     * @param full If true, disable handshake cutthrough for a fully synchronous handshake
328     */
329    public synchronized void startHandshake(boolean full) throws IOException {
330        synchronized (handshakeLock) {
331            checkOpen();
332            if (!handshakeStarted) {
333                handshakeStarted = true;
334            } else {
335                return;
336            }
337        }
338
339        // note that this modifies the global seed, not something specific to the connection
340        final int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES;
341        final SecureRandom secureRandom = sslParameters.getSecureRandomMember();
342        if (secureRandom == null) {
343            NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);
344        } else {
345            NativeCrypto.RAND_seed(secureRandom.generateSeed(seedLengthInBytes));
346        }
347
348        final boolean client = sslParameters.getUseClientMode();
349
350        final int sslCtxNativePointer = (client) ?
351            sslParameters.getClientSessionContext().sslCtxNativePointer :
352            sslParameters.getServerSessionContext().sslCtxNativePointer;
353
354        this.sslNativePointer = 0;
355        boolean exception = true;
356        try {
357            sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
358            guard.open("close");
359
360            // setup server certificates and private keys.
361            // clients will receive a call back to request certificates.
362            if (!client) {
363                Set<String> keyTypes = new HashSet<String>();
364                for (String enabledCipherSuite : enabledCipherSuites) {
365                    if (enabledCipherSuite.equals(NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
366                        continue;
367                    }
368                    String keyType = CipherSuite.getByName(enabledCipherSuite).getServerKeyType();
369                    if (keyType != null) {
370                        keyTypes.add(keyType);
371                    }
372                }
373                for (String keyType : keyTypes) {
374                    try {
375                        setCertificate(sslParameters.getKeyManager().chooseServerAlias(keyType,
376                                                                                       null,
377                                                                                       this));
378                    } catch (CertificateEncodingException e) {
379                        throw new IOException(e);
380                    }
381                }
382            }
383
384            NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
385            NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
386            if (enabledCompressionMethods.length != 0) {
387                NativeCrypto.setEnabledCompressionMethods(sslNativePointer,
388                                                          enabledCompressionMethods);
389            }
390            if (useSessionTickets) {
391                NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);
392            }
393            if (hostname != null) {
394                NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, hostname);
395            }
396
397            boolean enableSessionCreation = sslParameters.getEnableSessionCreation();
398            if (!enableSessionCreation) {
399                NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
400                                                              enableSessionCreation);
401            }
402
403            AbstractSessionContext sessionContext;
404            OpenSSLSessionImpl session;
405            if (client) {
406                // look for client session to reuse
407                ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext();
408                sessionContext = clientSessionContext;
409                session = getCachedClientSession(clientSessionContext);
410                if (session != null) {
411                    NativeCrypto.SSL_set_session(sslNativePointer,
412                                                 session.sslSessionNativePointer);
413                }
414            } else {
415                sessionContext = sslParameters.getServerSessionContext();
416                session = null;
417            }
418
419            // setup peer certificate verification
420            if (client) {
421                // TODO support for anonymous cipher would require us to
422                // conditionally use SSL_VERIFY_NONE
423            } else {
424                // needing client auth takes priority...
425                boolean certRequested = false;
426                if (sslParameters.getNeedClientAuth()) {
427                    NativeCrypto.SSL_set_verify(sslNativePointer,
428                                                NativeCrypto.SSL_VERIFY_PEER
429                                                | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
430                    certRequested = true;
431                // ... over just wanting it...
432                } else if (sslParameters.getWantClientAuth()) {
433                    NativeCrypto.SSL_set_verify(sslNativePointer,
434                                                NativeCrypto.SSL_VERIFY_PEER);
435                    certRequested = true;
436                // ... and it defaults properly so don't call SSL_set_verify in the common case.
437                } else {
438                    certRequested = false;
439                }
440
441                if (certRequested) {
442                    X509TrustManager trustManager = sslParameters.getTrustManager();
443                    X509Certificate[] issuers = trustManager.getAcceptedIssuers();
444                    if (issuers != null && issuers.length != 0) {
445                        byte[][] issuersBytes;
446                        try {
447                            issuersBytes = NativeCrypto.encodeIssuerX509Principals(issuers);
448                        } catch (CertificateEncodingException e) {
449                            throw new IOException("Problem encoding principals", e);
450                        }
451                        NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
452                    }
453                }
454            }
455
456            if (client && full) {
457                // we want to do a full synchronous handshake, so turn off cutthrough
458                NativeCrypto.SSL_clear_mode(sslNativePointer,
459                                            NativeCrypto.SSL_MODE_HANDSHAKE_CUTTHROUGH);
460            }
461
462            // Temporarily use a different timeout for the handshake process
463            int savedTimeoutMilliseconds = getSoTimeout();
464            if (handshakeTimeoutMilliseconds >= 0) {
465                setSoTimeout(handshakeTimeoutMilliseconds);
466            }
467
468            int sslSessionNativePointer;
469            try {
470                sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,
471                        socket.getFileDescriptor$(), this, getSoTimeout(), client);
472            } catch (CertificateException e) {
473                SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
474                wrapper.initCause(e);
475                throw wrapper;
476            }
477            byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
478            sslSession = (OpenSSLSessionImpl) sessionContext.getSession(sessionId);
479            if (sslSession != null) {
480                sslSession.lastAccessedTime = System.currentTimeMillis();
481                LoggerHolder.logger.fine("Reused cached session for "
482                                         + getInetAddress() + ".");
483                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
484            } else {
485                if (!enableSessionCreation) {
486                    // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled
487                    throw new IllegalStateException("SSL Session may not be created");
488                }
489                X509Certificate[] localCertificates
490                        = createCertChain(NativeCrypto.SSL_get_certificate(sslNativePointer));
491                X509Certificate[] peerCertificates
492                        = createCertChain(NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));
493                if (wrappedHost == null) {
494                    sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
495                                                        localCertificates, peerCertificates,
496                                                        super.getInetAddress().getHostName(),
497                                                        super.getPort(), sessionContext);
498                } else  {
499                    sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
500                                                        localCertificates, peerCertificates,
501                                                        wrappedHost, wrappedPort,
502                                                        sessionContext);
503                }
504                // if not, putSession later in handshakeCompleted() callback
505                if (handshakeCompleted) {
506                    sessionContext.putSession(sslSession);
507                }
508                LoggerHolder.logger.fine("Created new session for "
509                                         + getInetAddress().getHostName() + ".");
510            }
511
512            // Restore the original timeout now that the handshake is complete
513            if (handshakeTimeoutMilliseconds >= 0) {
514                setSoTimeout(savedTimeoutMilliseconds);
515            }
516
517            // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
518            if (handshakeCompleted) {
519                notifyHandshakeCompletedListeners();
520            }
521
522            exception = false;
523        } finally {
524            // on exceptional exit, treat the socket as closed
525            if (exception) {
526                close();
527            }
528        }
529    }
530
531    /**
532     * Return a possibly null array of X509Certificates given the
533     * possibly null array of DER encoded bytes.
534     */
535    private static X509Certificate[] createCertChain(byte[][] certificatesBytes) {
536        if (certificatesBytes == null) {
537            return null;
538        }
539        X509Certificate[] certificates = new X509Certificate[certificatesBytes.length];
540        for (int i = 0; i < certificatesBytes.length; i++) {
541            try {
542                certificates[i] = new X509CertImpl(certificatesBytes[i]);
543            } catch (IOException e) {
544                return null;
545            }
546        }
547        return certificates;
548    }
549
550    private void setCertificate(String alias) throws CertificateEncodingException, SSLException {
551        if (alias == null) {
552            return;
553        }
554
555        PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
556        byte[] privateKeyBytes = privateKey.getEncoded();
557        NativeCrypto.SSL_use_PrivateKey(sslNativePointer, privateKeyBytes);
558
559        X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
560        byte[][] certificateBytes = NativeCrypto.encodeCertificates(certificates);
561        NativeCrypto.SSL_use_certificate(sslNativePointer, certificateBytes);
562
563        // checks the last installed private key and certificate,
564        // so need to do this once per loop iteration
565        NativeCrypto.SSL_check_private_key(sslNativePointer);
566    }
567
568    /**
569     * Implementation of NativeCrypto.SSLHandshakeCallbacks
570     * invoked via JNI from client_cert_cb
571     */
572    public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
573            throws CertificateEncodingException, SSLException {
574
575        String[] keyTypes = new String[keyTypeBytes.length];
576        for (int i = 0; i < keyTypeBytes.length; i++) {
577            keyTypes[i] = CipherSuite.getClientKeyType(keyTypeBytes[i]);
578        }
579
580        X500Principal[] issuers;
581        if (asn1DerEncodedPrincipals == null) {
582            issuers = null;
583        } else {
584            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
585            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
586                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
587            }
588        }
589        setCertificate(sslParameters.getKeyManager().chooseClientAlias(keyTypes, issuers, this));
590    }
591
592    /**
593     * Implementation of NativeCrypto.SSLHandshakeCallbacks
594     * invoked via JNI from info_callback
595     */
596    public void handshakeCompleted() {
597        handshakeCompleted = true;
598
599        // If sslSession is null, the handshake was completed during
600        // the call to NativeCrypto.SSL_do_handshake and not during a
601        // later read operation. That means we do not need to fixup
602        // the SSLSession and session cache or notify
603        // HandshakeCompletedListeners, it will be done in
604        // startHandshake.
605        if (sslSession == null) {
606            return;
607        }
608
609        // reset session id from the native pointer and update the
610        // appropriate cache.
611        sslSession.resetId();
612        AbstractSessionContext sessionContext =
613            (sslParameters.getUseClientMode())
614            ? sslParameters.getClientSessionContext()
615                : sslParameters.getServerSessionContext();
616        sessionContext.putSession(sslSession);
617
618        // let listeners know we are finally done
619        notifyHandshakeCompletedListeners();
620    }
621
622    private void notifyHandshakeCompletedListeners() {
623        if (listeners != null && !listeners.isEmpty()) {
624            // notify the listeners
625            HandshakeCompletedEvent event =
626                new HandshakeCompletedEvent(this, sslSession);
627            for (HandshakeCompletedListener listener : listeners) {
628                try {
629                    listener.handshakeCompleted(event);
630                } catch (RuntimeException e) {
631                    // The RI runs the handlers in a separate thread,
632                    // which we do not. But we try to preserve their
633                    // behavior of logging a problem and not killing
634                    // the handshaking thread just because a listener
635                    // has a problem.
636                    Thread thread = Thread.currentThread();
637                    thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
638                }
639            }
640        }
641    }
642
643    /**
644     * Implementation of NativeCrypto.SSLHandshakeCallbacks
645     *
646     * @param bytes An array of ASN.1 DER encoded certficates
647     * @param authMethod auth algorithm name
648     *
649     * @throws CertificateException if the certificate is untrusted
650     */
651    @SuppressWarnings("unused")
652    public void verifyCertificateChain(byte[][] bytes, String authMethod)
653            throws CertificateException {
654        try {
655            if (bytes == null || bytes.length == 0) {
656                throw new SSLException("Peer sent no certificate");
657            }
658            X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length];
659            for (int i = 0; i < bytes.length; i++) {
660                peerCertificateChain[i] = new X509CertImpl(bytes[i]);
661            }
662            boolean client = sslParameters.getUseClientMode();
663            if (client) {
664                sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain,
665                                                                   authMethod);
666            } else {
667                String authType = peerCertificateChain[0].getPublicKey().getAlgorithm();
668                sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain,
669                                                                   authType);
670            }
671
672        } catch (CertificateException e) {
673            throw e;
674        } catch (Exception e) {
675            throw new RuntimeException(e);
676        }
677    }
678
679    /**
680     * Returns an input stream for this SSL socket using native calls to the
681     * OpenSSL library.
682     *
683     * @return: an input stream for reading bytes from this socket.
684     * @throws: <code>IOException</code> if an I/O error occurs when creating
685     *          the input stream, the socket is closed, the socket is not
686     *          connected, or the socket input has been shutdown.
687     */
688    @Override
689    public InputStream getInputStream() throws IOException {
690        checkOpen();
691        synchronized (this) {
692            if (is == null) {
693                is = new SSLInputStream();
694            }
695
696            return is;
697        }
698    }
699
700    /**
701     * Returns an output stream for this SSL socket using native calls to the
702     * OpenSSL library.
703     *
704     * @return an output stream for writing bytes to this socket.
705     * @throws <code>IOException</code> if an I/O error occurs when creating
706     *             the output stream, or no connection to the socket exists.
707     */
708    @Override
709    public OutputStream getOutputStream() throws IOException {
710        checkOpen();
711        synchronized (this) {
712            if (os == null) {
713                os = new SSLOutputStream();
714            }
715
716            return os;
717        }
718    }
719
720    /**
721     * This method is not supported for this SSLSocket implementation
722     * because reading from an SSLSocket may involve writing to the
723     * network.
724     */
725    @Override
726    public void shutdownInput() throws IOException {
727        throw new UnsupportedOperationException();
728    }
729
730    /**
731     * This method is not supported for this SSLSocket implementation
732     * because writing to an SSLSocket may involve reading from the
733     * network.
734     */
735    @Override
736    public void shutdownOutput() throws IOException {
737        throw new UnsupportedOperationException();
738    }
739
740    /**
741     * This inner class provides input data stream functionality
742     * for the OpenSSL native implementation. It is used to
743     * read data received via SSL protocol.
744     */
745    private class SSLInputStream extends InputStream {
746        SSLInputStream() throws IOException {
747            /**
748            /* Note: When startHandshake() throws an exception, no
749             * SSLInputStream object will be created.
750             */
751            OpenSSLSocketImpl.this.startHandshake(false);
752        }
753
754        /**
755         * Reads one byte. If there is no data in the underlying buffer,
756         * this operation can block until the data will be
757         * available.
758         * @return read value.
759         * @throws <code>IOException</code>
760         */
761        @Override
762        public int read() throws IOException {
763            BlockGuard.getThreadPolicy().onNetwork();
764            synchronized (readLock) {
765                checkOpen();
766                return NativeCrypto.SSL_read_byte(sslNativePointer, socket.getFileDescriptor$(),
767                        OpenSSLSocketImpl.this, getSoTimeout());
768            }
769        }
770
771        /**
772         * Method acts as described in spec for superclass.
773         * @see java.io.InputStream#read(byte[],int,int)
774         */
775        @Override
776        public int read(byte[] buf, int offset, int byteCount) throws IOException {
777            BlockGuard.getThreadPolicy().onNetwork();
778            synchronized (readLock) {
779                checkOpen();
780                Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
781                if (byteCount == 0) {
782                    return 0;
783                }
784                return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
785                        OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
786            }
787        }
788    }
789
790    /**
791     * This inner class provides output data stream functionality
792     * for the OpenSSL native implementation. It is used to
793     * write data according to the encryption parameters given in SSL context.
794     */
795    private class SSLOutputStream extends OutputStream {
796        SSLOutputStream() throws IOException {
797            /**
798            /* Note: When startHandshake() throws an exception, no
799             * SSLOutputStream object will be created.
800             */
801            OpenSSLSocketImpl.this.startHandshake(false);
802        }
803
804        /**
805         * Method acts as described in spec for superclass.
806         * @see java.io.OutputStream#write(int)
807         */
808        @Override
809        public void write(int b) throws IOException {
810            BlockGuard.getThreadPolicy().onNetwork();
811            synchronized (writeLock) {
812                checkOpen();
813                NativeCrypto.SSL_write_byte(sslNativePointer, socket.getFileDescriptor$(),
814                        OpenSSLSocketImpl.this, b);
815            }
816        }
817
818        /**
819         * Method acts as described in spec for superclass.
820         * @see java.io.OutputStream#write(byte[],int,int)
821         */
822        @Override
823        public void write(byte[] buf, int offset, int byteCount) throws IOException {
824            BlockGuard.getThreadPolicy().onNetwork();
825            synchronized (writeLock) {
826                checkOpen();
827                Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
828                if (byteCount == 0) {
829                    return;
830                }
831                NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
832                        OpenSSLSocketImpl.this, buf, offset, byteCount);
833            }
834        }
835    }
836
837
838    /**
839     * The SSL session used by this connection is returned. The SSL session
840     * determines which cipher suite should be used by all connections within
841     * that session and which identities have the session's client and server.
842     * This method starts the SSL handshake.
843     * @return the SSLSession.
844     * @throws <code>IOException</code> if the handshake fails
845     */
846    @Override
847    public SSLSession getSession() {
848        if (sslSession == null) {
849            try {
850                startHandshake(true);
851            } catch (IOException e) {
852                // return an invalid session with
853                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
854                return SSLSessionImpl.NULL_SESSION;
855            }
856        }
857        return sslSession;
858    }
859
860    /**
861     * Registers a listener to be notified that a SSL handshake
862     * was successfully completed on this connection.
863     * @throws <code>IllegalArgumentException</code> if listener is null.
864     */
865    @Override
866    public void addHandshakeCompletedListener(
867            HandshakeCompletedListener listener) {
868        if (listener == null) {
869            throw new IllegalArgumentException("Provided listener is null");
870        }
871        if (listeners == null) {
872            listeners = new ArrayList();
873        }
874        listeners.add(listener);
875    }
876
877    /**
878     * The method removes a registered listener.
879     * @throws IllegalArgumentException if listener is null or not registered
880     */
881    @Override
882    public void removeHandshakeCompletedListener(
883            HandshakeCompletedListener listener) {
884        if (listener == null) {
885            throw new IllegalArgumentException("Provided listener is null");
886        }
887        if (listeners == null) {
888            throw new IllegalArgumentException(
889                    "Provided listener is not registered");
890        }
891        if (!listeners.remove(listener)) {
892            throw new IllegalArgumentException(
893                    "Provided listener is not registered");
894        }
895    }
896
897    /**
898     * Returns true if new SSL sessions may be established by this socket.
899     *
900     * @return true if the session may be created; false if a session already
901     *         exists and must be resumed.
902     */
903    @Override
904    public boolean getEnableSessionCreation() {
905        return sslParameters.getEnableSessionCreation();
906    }
907
908    /**
909     * Set a flag for the socket to inhibit or to allow the creation of a new
910     * SSL sessions. If the flag is set to false, and there are no actual
911     * sessions to resume, then there will be no successful handshaking.
912     *
913     * @param flag true if session may be created; false
914     *            if a session already exists and must be resumed.
915     */
916    @Override
917    public void setEnableSessionCreation(boolean flag) {
918        sslParameters.setEnableSessionCreation(flag);
919    }
920
921    /**
922     * The names of the cipher suites which could be used by the SSL connection
923     * are returned.
924     * @return an array of cipher suite names
925     */
926    @Override
927    public String[] getSupportedCipherSuites() {
928        return NativeCrypto.getSupportedCipherSuites();
929    }
930
931    /**
932     * The names of the cipher suites that are in use in the actual the SSL
933     * connection are returned.
934     *
935     * @return an array of cipher suite names
936     */
937    @Override
938    public String[] getEnabledCipherSuites() {
939        return enabledCipherSuites.clone();
940    }
941
942    /**
943     * This method enables the cipher suites listed by
944     * getSupportedCipherSuites().
945     *
946     * @param suites names of all the cipher suites to
947     *            put on use
948     * @throws IllegalArgumentException when one or more of the
949     *             ciphers in array suites are not supported, or when the array
950     *             is null.
951     */
952    @Override
953    public void setEnabledCipherSuites(String[] suites) {
954        enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
955    }
956
957    /**
958     * The names of the protocols' versions that may be used on this SSL
959     * connection.
960     * @return an array of protocols names
961     */
962    @Override
963    public String[] getSupportedProtocols() {
964        return NativeCrypto.getSupportedProtocols();
965    }
966
967    /**
968     * The names of the protocols' versions that are in use on this SSL
969     * connection.
970     *
971     * @return an array of protocols names
972     */
973    @Override
974    public String[] getEnabledProtocols() {
975        return enabledProtocols.clone();
976    }
977
978    /**
979     * This method enables the protocols' versions listed by
980     * getSupportedProtocols().
981     *
982     * @param protocols The names of all the protocols to allow
983     *
984     * @throws IllegalArgumentException when one or more of the names in the
985     *             array are not supported, or when the array is null.
986     */
987    @Override
988    public void setEnabledProtocols(String[] protocols) {
989        enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
990    }
991
992    /**
993     * The names of the compression methods that may be used on this SSL
994     * connection.
995     * @return an array of compression methods
996     */
997    public String[] getSupportedCompressionMethods() {
998        return NativeCrypto.getSupportedCompressionMethods();
999    }
1000
1001    /**
1002     * The names of the compression methods versions that are in use
1003     * on this SSL connection.
1004     *
1005     * @return an array of compression methods
1006     */
1007    public String[] getEnabledCompressionMethods() {
1008        return enabledCompressionMethods.clone();
1009    }
1010
1011    /**
1012     * This method enables the compression method listed by
1013     * getSupportedCompressionMethods().
1014     *
1015     * @param methods The names of all the compression methods to allow
1016     *
1017     * @throws IllegalArgumentException when one or more of the names in the
1018     *             array are not supported, or when the array is null.
1019     */
1020    public void setEnabledCompressionMethods (String[] methods) {
1021        enabledCompressionMethods = NativeCrypto.checkEnabledCompressionMethods(methods);
1022    }
1023
1024    /**
1025     * This method enables session ticket support.
1026     *
1027     * @param useSessionTickets True to enable session tickets
1028     */
1029    public void setUseSessionTickets(boolean useSessionTickets) {
1030        this.useSessionTickets = useSessionTickets;
1031    }
1032
1033    /**
1034     * This method gives true back if the SSL socket is set to client mode.
1035     *
1036     * @return true if the socket should do the handshaking as client.
1037     */
1038    public boolean getUseSessionTickets() {
1039        return useSessionTickets;
1040    }
1041
1042    /**
1043     * This method enables Server Name Indication
1044     *
1045     * @param hostname the desired SNI hostname, or null to disable
1046     */
1047    public void setHostname(String hostname) {
1048        this.hostname = hostname;
1049    }
1050
1051    /**
1052     * This method returns the current SNI hostname
1053     *
1054     * @return a host name if SNI is enabled, or null otherwise
1055     */
1056    public String getHostname() {
1057        return hostname;
1058    }
1059
1060    /**
1061     * This method gives true back if the SSL socket is set to client mode.
1062     *
1063     * @return true if the socket should do the handshaking as client.
1064     */
1065    public boolean getUseClientMode() {
1066        return sslParameters.getUseClientMode();
1067    }
1068
1069    /**
1070     * This method set the actual SSL socket to client mode.
1071     *
1072     * @param mode true if the socket starts in client
1073     *            mode
1074     * @throws IllegalArgumentException if mode changes during
1075     *             handshake.
1076     */
1077    @Override
1078    public void setUseClientMode(boolean mode) {
1079        if (handshakeStarted) {
1080            throw new IllegalArgumentException(
1081            "Could not change the mode after the initial handshake has begun.");
1082        }
1083        sslParameters.setUseClientMode(mode);
1084    }
1085
1086    /**
1087     * Returns true if the SSL socket requests client's authentication. Relevant
1088     * only for server sockets!
1089     *
1090     * @return true if client authentication is desired, false if not.
1091     */
1092    @Override
1093    public boolean getWantClientAuth() {
1094        return sslParameters.getWantClientAuth();
1095    }
1096
1097    /**
1098     * Returns true if the SSL socket needs client's authentication. Relevant
1099     * only for server sockets!
1100     *
1101     * @return true if client authentication is desired, false if not.
1102     */
1103    @Override
1104    public boolean getNeedClientAuth() {
1105        return sslParameters.getNeedClientAuth();
1106    }
1107
1108    /**
1109     * Sets the SSL socket to use client's authentication. Relevant only for
1110     * server sockets!
1111     *
1112     * @param need true if client authentication is
1113     *            desired, false if not.
1114     */
1115    @Override
1116    public void setNeedClientAuth(boolean need) {
1117        sslParameters.setNeedClientAuth(need);
1118    }
1119
1120    /**
1121     * Sets the SSL socket to use client's authentication. Relevant only for
1122     * server sockets! Notice that in contrast to setNeedClientAuth(..) this
1123     * method will continue the negotiation if the client decide not to send
1124     * authentication credentials.
1125     *
1126     * @param want true if client authentication is
1127     *            desired, false if not.
1128     */
1129    @Override
1130    public void setWantClientAuth(boolean want) {
1131        sslParameters.setWantClientAuth(want);
1132    }
1133
1134    /**
1135     * This method is not supported for SSLSocket implementation.
1136     */
1137    @Override
1138    public void sendUrgentData(int data) throws IOException {
1139        throw new SocketException(
1140                "Method sendUrgentData() is not supported.");
1141    }
1142
1143    /**
1144     * This method is not supported for SSLSocket implementation.
1145     */
1146    @Override
1147    public void setOOBInline(boolean on) throws SocketException {
1148        throw new SocketException(
1149                "Methods sendUrgentData, setOOBInline are not supported.");
1150    }
1151
1152    /**
1153     * Set the read timeout on this socket. The SO_TIMEOUT option, is specified
1154     * in milliseconds. The read operation will block indefinitely for a zero
1155     * value.
1156     *
1157     * @param timeout the read timeout value
1158     * @throws SocketException if an error occurs setting the option
1159     */
1160    @Override
1161    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
1162        super.setSoTimeout(timeoutMilliseconds);
1163        this.timeoutMilliseconds = timeoutMilliseconds;
1164    }
1165
1166    @Override
1167    public int getSoTimeout() throws SocketException {
1168        return timeoutMilliseconds;
1169    }
1170
1171    /**
1172     * Set the handshake timeout on this socket.  This timeout is specified in
1173     * milliseconds and will be used only during the handshake process.
1174     *
1175     * @param timeout the handshake timeout value
1176     */
1177    public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException {
1178        this.handshakeTimeoutMilliseconds = timeoutMilliseconds;
1179    }
1180
1181    /**
1182     * Closes the SSL socket. Once closed, a socket is not available for further
1183     * use anymore under any circumstance. A new socket must be created.
1184     *
1185     * @throws <code>IOException</code> if an I/O error happens during the
1186     *             socket's closure.
1187     */
1188    @Override
1189    public void close() throws IOException {
1190        // TODO: Close SSL sockets using a background thread so they close
1191        // gracefully.
1192
1193        synchronized (handshakeLock) {
1194            if (!handshakeStarted) {
1195                // prevent further attemps to start handshake
1196                handshakeStarted = true;
1197
1198                synchronized (this) {
1199                    free();
1200
1201                    if (socket != this) {
1202                        if (autoClose && !socket.isClosed()) socket.close();
1203                    } else {
1204                        if (!super.isClosed()) super.close();
1205                    }
1206                }
1207
1208                return;
1209            }
1210        }
1211
1212        NativeCrypto.SSL_interrupt(sslNativePointer);
1213
1214        synchronized (this) {
1215            synchronized (writeLock) {
1216                synchronized (readLock) {
1217
1218                    // Shut down the SSL connection, per se.
1219                    try {
1220                        if (handshakeStarted) {
1221                            BlockGuard.getThreadPolicy().onNetwork();
1222                            NativeCrypto.SSL_shutdown(sslNativePointer, socket.getFileDescriptor$(),
1223                                    this);
1224                        }
1225                    } catch (IOException ignored) {
1226                        /*
1227                         * Note that although close() can throw
1228                         * IOException, the RI does not throw if there
1229                         * is problem sending a "close notify" which
1230                         * can happen if the underlying socket is closed.
1231                         */
1232                    }
1233
1234                    /*
1235                     * Even if the above call failed, it is still safe to free
1236                     * the native structs, and we need to do so lest we leak
1237                     * memory.
1238                     */
1239                    free();
1240
1241                    if (socket != this) {
1242                        if (autoClose && !socket.isClosed())
1243                            socket.close();
1244                    } else {
1245                        if (!super.isClosed())
1246                            super.close();
1247                    }
1248                }
1249            }
1250        }
1251    }
1252
1253    private void free() {
1254        if (sslNativePointer == 0) {
1255            return;
1256        }
1257        NativeCrypto.SSL_free(sslNativePointer);
1258        sslNativePointer = 0;
1259        guard.close();
1260    }
1261
1262    @Override protected void finalize() throws Throwable {
1263        try {
1264            /*
1265             * Just worry about our own state. Notably we do not try and
1266             * close anything. The SocketImpl, either our own
1267             * PlainSocketImpl, or the Socket we are wrapping, will do
1268             * that. This might mean we do not properly SSL_shutdown, but
1269             * if you want to do that, properly close the socket yourself.
1270             *
1271             * The reason why we don't try to SSL_shutdown, is that there
1272             * can be a race between finalizers where the PlainSocketImpl
1273             * finalizer runs first and closes the socket. However, in the
1274             * meanwhile, the underlying file descriptor could be reused
1275             * for another purpose. If we call SSL_shutdown, the
1276             * underlying socket BIOs still have the old file descriptor
1277             * and will write the close notify to some unsuspecting
1278             * reader.
1279             */
1280            if (guard != null) {
1281                guard.warnIfOpen();
1282            }
1283            free();
1284        } finally {
1285            super.finalize();
1286        }
1287    }
1288}
1289