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