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