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