ClientHandshakeImpl.java revision 6882e31b7ce2d04ebbc91c7a55d7840e8fdce8a5
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.apache.harmony.xnet.provider.jsse; 19 20import java.io.IOException; 21import java.security.AccessController; 22import java.security.Key; 23import java.security.KeyFactory; 24import java.security.KeyPair; 25import java.security.KeyPairGenerator; 26import java.security.NoSuchAlgorithmException; 27import java.security.PrivateKey; 28import java.security.PrivilegedExceptionAction; 29import java.security.PublicKey; 30import java.security.cert.CertificateException; 31import java.security.cert.X509Certificate; 32import java.util.Arrays; 33import javax.crypto.Cipher; 34import javax.crypto.KeyAgreement; 35import javax.crypto.interfaces.DHKey; 36import javax.crypto.interfaces.DHPublicKey; 37import javax.crypto.spec.DHParameterSpec; 38import javax.crypto.spec.DHPublicKeySpec; 39import javax.net.ssl.SSLSession; 40import javax.net.ssl.X509ExtendedKeyManager; 41import javax.net.ssl.X509KeyManager; 42import javax.security.auth.x500.X500Principal; 43 44/** 45 * Client side handshake protocol implementation. 46 * Handshake protocol operates on top of the Record Protocol. 47 * It is responsible for session negotiating. 48 * 49 * The implementation processes inbound server handshake messages, 50 * creates and sends respond messages. Outbound messages are supplied 51 * to Record Protocol. Detected errors are reported to the Alert protocol. 52 * 53 * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The 54 * TLS Handshake Protocol</a> 55 * 56 */ 57public class ClientHandshakeImpl extends HandshakeProtocol { 58 59 /** 60 * Creates Client Handshake Implementation 61 * 62 * @param owner 63 */ 64 ClientHandshakeImpl(Object owner) { 65 super(owner); 66 } 67 68 /** 69 * Starts handshake 70 * 71 */ 72 @Override 73 public void start() { 74 if (session == null) { // initial handshake 75 session = findSessionToResume(); 76 } else { // start session renegotiation 77 if (clientHello != null && this.status != FINISHED) { 78 // current negotiation has not completed 79 return; // ignore 80 } 81 if (!session.isValid()) { 82 session = null; 83 } 84 } 85 if (session != null) { 86 isResuming = true; 87 } else if (parameters.getEnableSessionCreation()){ 88 isResuming = false; 89 session = new SSLSessionImpl(parameters.getSecureRandom()); 90 // BEGIN android-added 91 if (engineOwner != null) { 92 session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort()); 93 } else { 94 session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort()); 95 } 96 // END android-added 97 session.protocol = ProtocolVersion.getLatestVersion(parameters 98 .getEnabledProtocols()); 99 recordProtocol.setVersion(session.protocol.version); 100 } else { 101 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created "); 102 } 103 startSession(); 104 } 105 106 /** 107 * Starts renegotiation on a new session 108 * 109 */ 110 private void renegotiateNewSession() { 111 if (parameters.getEnableSessionCreation()){ 112 isResuming = false; 113 session = new SSLSessionImpl(parameters.getSecureRandom()); 114 // BEGIN android-added 115 if (engineOwner != null) { 116 session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort()); 117 } else { 118 session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort()); 119 } 120 // END android-added 121 session.protocol = ProtocolVersion.getLatestVersion(parameters 122 .getEnabledProtocols()); 123 recordProtocol.setVersion(session.protocol.version); 124 startSession(); 125 } else { 126 status = NOT_HANDSHAKING; 127 sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); 128 } 129 } 130 131 /* 132 * Starts/resumes session 133 */ 134 private void startSession() { 135 CipherSuite[] cipher_suites; 136 if (isResuming) { 137 cipher_suites = new CipherSuite[] { session.cipherSuite }; 138 } else { 139 // BEGIN android-changed 140 cipher_suites = parameters.getEnabledCipherSuitesMember(); 141 // END android-changed 142 } 143 clientHello = new ClientHello(parameters.getSecureRandom(), 144 session.protocol.version, session.id, cipher_suites); 145 session.clientRandom = clientHello.random; 146 send(clientHello); 147 status = NEED_UNWRAP; 148 } 149 150 /** 151 * Processes inbound handshake messages 152 * @param bytes 153 */ 154 @Override 155 public void unwrap(byte[] bytes) { 156 if (this.delegatedTaskErr != null) { 157 Exception e = this.delegatedTaskErr; 158 this.delegatedTaskErr = null; 159 this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e); 160 } 161 int handshakeType; 162 io_stream.append(bytes); 163 while (io_stream.available() > 0) { 164 io_stream.mark(); 165 int length; 166 try { 167 handshakeType = io_stream.read(); 168 length = io_stream.readUint24(); 169 if (io_stream.available() < length) { 170 io_stream.reset(); 171 return; 172 } 173 switch (handshakeType) { 174 case 0: // HELLO_REQUEST 175 // we don't need to take this message into account 176 // during FINISH message verification, so remove it 177 io_stream.removeFromMarkedPosition(); 178 if (clientHello != null 179 && (clientFinished == null || serverFinished == null)) { 180 //currently negotiating - ignore 181 break; 182 } 183 // renegotiate 184 if (session.isValid()) { 185 session = (SSLSessionImpl) session.clone(); 186 isResuming = true; 187 startSession(); 188 } else { 189 // if SSLSession is invalidated (e.g. timeout limit is 190 // exceeded) connection can't resume the session. 191 renegotiateNewSession(); 192 } 193 break; 194 case 2: // SERVER_HELLO 195 if (clientHello == null || serverHello != null) { 196 unexpectedMessage(); 197 return; 198 } 199 serverHello = new ServerHello(io_stream, length); 200 201 //check protocol version 202 ProtocolVersion servProt = ProtocolVersion 203 .getByVersion(serverHello.server_version); 204 String[] enabled = parameters.getEnabledProtocols(); 205 find: { 206 for (int i = 0; i < enabled.length; i++) { 207 if (servProt.equals(ProtocolVersion 208 .getByName(enabled[i]))) { 209 break find; 210 } 211 } 212 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 213 "Bad server hello protocol version"); 214 } 215 216 // check compression method 217 if (serverHello.compression_method != 0) { 218 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 219 "Bad server hello compression method"); 220 } 221 222 //check cipher_suite 223 // BEGIN android-changed 224 CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember(); 225 // END android-changed 226 find: { 227 for (int i = 0; i < enabledSuites.length; i++) { 228 if (serverHello.cipher_suite 229 .equals(enabledSuites[i])) { 230 break find; 231 } 232 } 233 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 234 "Bad server hello cipher suite"); 235 } 236 237 if (isResuming) { 238 if (serverHello.session_id.length == 0) { 239 // server is not willing to establish the new connection 240 // using specified session 241 isResuming = false; 242 } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) { 243 isResuming = false; 244 } else if (!session.protocol.equals(servProt)) { 245 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 246 "Bad server hello protocol version"); 247 } else if (!session.cipherSuite 248 .equals(serverHello.cipher_suite)) { 249 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 250 "Bad server hello cipher suite"); 251 } 252 if (serverHello.server_version[1] == 1) { 253 computerReferenceVerifyDataTLS("server finished"); 254 } else { 255 computerReferenceVerifyDataSSLv3(SSLv3Constants.server); 256 } 257 } 258 session.protocol = servProt; 259 recordProtocol.setVersion(session.protocol.version); 260 session.cipherSuite = serverHello.cipher_suite; 261 session.id = serverHello.session_id.clone(); 262 session.serverRandom = serverHello.random; 263 break; 264 case 11: // CERTIFICATE 265 if (serverHello == null || serverKeyExchange != null 266 || serverCert != null || isResuming) { 267 unexpectedMessage(); 268 return; 269 } 270 serverCert = new CertificateMessage(io_stream, length); 271 break; 272 case 12: // SERVER_KEY_EXCHANGE 273 if (serverHello == null || serverKeyExchange != null 274 || isResuming) { 275 unexpectedMessage(); 276 return; 277 } 278 serverKeyExchange = new ServerKeyExchange(io_stream, 279 length, session.cipherSuite.keyExchange); 280 break; 281 case 13: // CERTIFICATE_REQUEST 282 if (serverCert == null || certificateRequest != null 283 || session.cipherSuite.isAnonymous() || isResuming) { 284 unexpectedMessage(); 285 return; 286 } 287 certificateRequest = new CertificateRequest(io_stream, 288 length); 289 break; 290 case 14: // SERVER_HELLO_DONE 291 if (serverHello == null || serverHelloDone != null 292 || isResuming) { 293 unexpectedMessage(); 294 return; 295 } 296 serverHelloDone = new ServerHelloDone(io_stream, length); 297 if (this.nonBlocking) { 298 delegatedTasks.add(new DelegatedTask(new PrivilegedExceptionAction<Void>() { 299 public Void run() throws Exception { 300 processServerHelloDone(); 301 return null; 302 } 303 }, this, AccessController.getContext())); 304 return; 305 } 306 processServerHelloDone(); 307 break; 308 case 20: // FINISHED 309 if (!changeCipherSpecReceived) { 310 unexpectedMessage(); 311 return; 312 } 313 serverFinished = new Finished(io_stream, length); 314 verifyFinished(serverFinished.getData()); 315 session.lastAccessedTime = System.currentTimeMillis(); 316 // BEGIN android-added 317 session.context = parameters.getClientSessionContext(); 318 // END android-added 319 parameters.getClientSessionContext().putSession(session); 320 if (isResuming) { 321 sendChangeCipherSpec(); 322 } else { 323 session.lastAccessedTime = System.currentTimeMillis(); 324 status = FINISHED; 325 } 326 // XXX there is no cleanup work 327 break; 328 default: 329 unexpectedMessage(); 330 return; 331 } 332 } catch (IOException e) { 333 // io stream dosn't contain complete handshake message 334 io_stream.reset(); 335 return; 336 } 337 } 338 339 } 340 341 /** 342 * Processes SSLv2 Hello message. 343 * SSLv2 client hello message message is an unexpected message 344 * for client side of handshake protocol. 345 * @ see TLS 1.0 spec., E.1. Version 2 client hello 346 * @param bytes 347 */ 348 @Override 349 public void unwrapSSLv2(byte[] bytes) { 350 unexpectedMessage(); 351 } 352 353 /** 354 * Creates and sends Finished message 355 */ 356 @Override 357 protected void makeFinished() { 358 byte[] verify_data; 359 if (serverHello.server_version[1] == 1) { 360 verify_data = new byte[12]; 361 computerVerifyDataTLS("client finished", verify_data); 362 } else { 363 verify_data = new byte[36]; 364 computerVerifyDataSSLv3(SSLv3Constants.client, verify_data); 365 } 366 clientFinished = new Finished(verify_data); 367 send(clientFinished); 368 if (isResuming) { 369 session.lastAccessedTime = System.currentTimeMillis(); 370 status = FINISHED; 371 } else { 372 if (serverHello.server_version[1] == 1) { 373 computerReferenceVerifyDataTLS("server finished"); 374 } else { 375 computerReferenceVerifyDataSSLv3(SSLv3Constants.server); 376 } 377 status = NEED_UNWRAP; 378 } 379 } 380 381 /** 382 * Processes ServerHelloDone: makes verification of the server messages; sends 383 * client messages, computers masterSecret, sends ChangeCipherSpec 384 */ 385 void processServerHelloDone() { 386 PrivateKey clientKey = null; 387 388 if (serverCert != null) { 389 if (session.cipherSuite.isAnonymous()) { 390 unexpectedMessage(); 391 return; 392 } 393 verifyServerCert(); 394 } else { 395 if (!session.cipherSuite.isAnonymous()) { 396 unexpectedMessage(); 397 return; 398 } 399 } 400 401 // Client certificate 402 if (certificateRequest != null) { 403 X509Certificate[] certs = null; 404 // obtain certificates from key manager 405 String alias = null; 406 String[] certTypes = certificateRequest.getTypesAsString(); 407 X500Principal[] issuers = certificateRequest.certificate_authorities; 408 X509KeyManager km = parameters.getKeyManager(); 409 if (km instanceof X509ExtendedKeyManager) { 410 X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km; 411 if (this.socketOwner != null) { 412 alias = ekm.chooseClientAlias(certTypes, issuers, this.socketOwner); 413 } else { 414 alias = ekm.chooseEngineClientAlias(certTypes, issuers, this.engineOwner); 415 } 416 if (alias != null) { 417 certs = ekm.getCertificateChain(alias); 418 } 419 } else { 420 alias = km.chooseClientAlias(certTypes, issuers, this.socketOwner); 421 if (alias != null) { 422 certs = km.getCertificateChain(alias); 423 } 424 } 425 426 session.localCertificates = certs; 427 clientCert = new CertificateMessage(certs); 428 clientKey = km.getPrivateKey(alias); 429 send(clientCert); 430 } 431 // Client key exchange 432 if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA 433 || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 434 // RSA encrypted premaster secret message 435 Cipher c; 436 try { 437 c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 438 if (serverKeyExchange != null) { 439 c.init(Cipher.ENCRYPT_MODE, serverKeyExchange 440 .getRSAPublicKey()); 441 } else { 442 c.init(Cipher.ENCRYPT_MODE, serverCert.certs[0]); 443 } 444 } catch (Exception e) { 445 fatalAlert(AlertProtocol.INTERNAL_ERROR, 446 "Unexpected exception", e); 447 return; 448 } 449 preMasterSecret = new byte[48]; 450 parameters.getSecureRandom().nextBytes(preMasterSecret); 451 System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, 2); 452 try { 453 clientKeyExchange = new ClientKeyExchange(c 454 .doFinal(preMasterSecret), 455 serverHello.server_version[1] == 1); 456 } catch (Exception e) { 457 fatalAlert(AlertProtocol.INTERNAL_ERROR, 458 "Unexpected exception", e); 459 return; 460 } 461 } else { 462 PublicKey serverPublic; 463 KeyAgreement agreement = null; 464 DHParameterSpec spec; 465 try { 466 KeyFactory kf = null; 467 try { 468 kf = KeyFactory.getInstance("DH"); 469 } catch (NoSuchAlgorithmException e) { 470 kf = KeyFactory.getInstance("DiffieHellman"); 471 } 472 473 try { 474 agreement = KeyAgreement.getInstance("DH"); 475 } catch (NoSuchAlgorithmException ee) { 476 agreement = KeyAgreement.getInstance("DiffieHellman"); 477 } 478 479 KeyPairGenerator kpg = null; 480 try { 481 kpg = KeyPairGenerator.getInstance("DH"); 482 } catch (NoSuchAlgorithmException e) { 483 kpg = KeyPairGenerator.getInstance("DiffieHellman"); 484 } 485 if (serverKeyExchange != null) { 486 serverPublic = kf.generatePublic(new DHPublicKeySpec( 487 serverKeyExchange.par3, serverKeyExchange.par1, 488 serverKeyExchange.par2)); 489 spec = new DHParameterSpec(serverKeyExchange.par1, 490 serverKeyExchange.par2); 491 } else { 492 serverPublic = serverCert.certs[0].getPublicKey(); 493 spec = ((DHPublicKey) serverPublic).getParams(); 494 } 495 kpg.initialize(spec); 496 497 KeyPair kp = kpg.generateKeyPair(); 498 Key key = kp.getPublic(); 499 if (clientCert != null 500 && serverCert != null 501 && (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA 502 || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS)) { 503 PublicKey client_pk = clientCert.certs[0].getPublicKey(); 504 PublicKey server_pk = serverCert.certs[0].getPublicKey(); 505 if (client_pk instanceof DHKey 506 && server_pk instanceof DHKey) { 507 if (((DHKey) client_pk).getParams().getG().equals( 508 ((DHKey) server_pk).getParams().getG()) 509 && ((DHKey) client_pk).getParams().getP() 510 .equals(((DHKey) server_pk).getParams().getG())) { 511 // client cert message DH public key parameters 512 // matched those specified by the 513 // server in its certificate, 514 clientKeyExchange = new ClientKeyExchange(); // empty 515 } 516 } 517 } else { 518 clientKeyExchange = new ClientKeyExchange( 519 ((DHPublicKey) key).getY()); 520 } 521 key = kp.getPrivate(); 522 agreement.init(key); 523 agreement.doPhase(serverPublic, true); 524 preMasterSecret = agreement.generateSecret(); 525 } catch (Exception e) { 526 fatalAlert(AlertProtocol.INTERNAL_ERROR, 527 "Unexpected exception", e); 528 return; 529 } 530 } 531 if (clientKeyExchange != null) { 532 send(clientKeyExchange); 533 } 534 535 computerMasterSecret(); 536 537 // send certificate verify for all certificates except those containing 538 // fixed DH parameters 539 if (clientCert != null && !clientKeyExchange.isEmpty()) { 540 // Certificate verify 541 String authType = clientKey.getAlgorithm(); 542 DigitalSignature ds = new DigitalSignature(authType); 543 ds.init(clientKey); 544 545 if ("RSA".equals(authType)) { 546 ds.setMD5(io_stream.getDigestMD5()); 547 ds.setSHA(io_stream.getDigestSHA()); 548 } else if ("DSA".equals(authType)) { 549 ds.setSHA(io_stream.getDigestSHA()); 550 // The Signature should be empty in case of anonymous signature algorithm: 551 // } else if ("DH".equals(authType)) { 552 } 553 certificateVerify = new CertificateVerify(ds.sign()); 554 send(certificateVerify); 555 } 556 557 sendChangeCipherSpec(); 558 } 559 560 /* 561 * Verifies certificate path 562 */ 563 private void verifyServerCert() { 564 String authType = null; 565 switch (session.cipherSuite.keyExchange) { 566 case 1: // KeyExchange_RSA 567 authType = "RSA"; 568 break; 569 case 2: // KeyExchange_RSA_EXPORT 570 if (serverKeyExchange != null ) { 571 // ephemeral RSA key is used 572 authType = "RSA_EXPORT"; 573 } else { 574 authType = "RSA"; 575 } 576 break; 577 case 3: // KeyExchange_DHE_DSS 578 case 4: // KeyExchange_DHE_DSS_EXPORT 579 authType = "DHE_DSS"; 580 break; 581 case 5: // KeyExchange_DHE_RSA 582 case 6: // KeyExchange_DHE_RSA_EXPORT 583 authType = "DHE_RSA"; 584 break; 585 case 7: // KeyExchange_DH_DSS 586 case 11: // KeyExchange_DH_DSS_EXPORT 587 authType = "DH_DSS"; 588 break; 589 case 8: // KeyExchange_DH_RSA 590 case 12: // KeyExchange_DH_RSA_EXPORT 591 authType = "DH_RSA"; 592 break; 593 case 9: // KeyExchange_DH_anon 594 case 10: // KeyExchange_DH_anon_EXPORT 595 return; 596 } 597 try { 598 parameters.getTrustManager().checkServerTrusted(serverCert.certs, 599 authType); 600 } catch (CertificateException e) { 601 fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e); 602 return; 603 } 604 session.peerCertificates = serverCert.certs; 605 } 606 607 /** 608 * Processes ChangeCipherSpec message 609 */ 610 @Override 611 public void receiveChangeCipherSpec() { 612 if (isResuming) { 613 if (serverHello == null) { 614 unexpectedMessage(); 615 } 616 } else if (clientFinished == null) { 617 unexpectedMessage(); 618 } 619 changeCipherSpecReceived = true; 620 } 621 622 // Find session to resume in client session context 623 private SSLSessionImpl findSessionToResume() { 624 String host = null; 625 int port = -1; 626 if (engineOwner != null) { 627 host = engineOwner.getPeerHost(); 628 port = engineOwner.getPeerPort(); 629 } else { 630 host = socketOwner.getInetAddress().getHostName(); 631 port = socketOwner.getPort(); 632 } 633 if (host == null || port == -1) { 634 return null; // starts new session 635 } 636 637 // BEGIN android-changed 638 ClientSessionContext context = parameters.getClientSessionContext(); 639 SSLSessionImpl session 640 = (SSLSessionImpl) context.getSession(host, port); 641 if (session != null) { 642 session = (SSLSessionImpl) session.clone(); 643 } 644 return session; 645 // END android-changed 646 } 647 648} 649