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