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 18 package org.apache.harmony.xnet.provider.jsse; 19 20 import java.io.IOException; 21 import java.math.BigInteger; 22 import java.security.AccessController; 23 import java.security.KeyFactory; 24 import java.security.KeyPair; 25 import java.security.KeyPairGenerator; 26 import java.security.NoSuchAlgorithmException; 27 import java.security.PrivateKey; 28 import java.security.PrivilegedExceptionAction; 29 import java.security.PublicKey; 30 import java.security.cert.CertificateException; 31 import java.security.cert.X509Certificate; 32 import java.security.interfaces.RSAPublicKey; 33 import java.util.Arrays; 34 import javax.crypto.Cipher; 35 import javax.crypto.KeyAgreement; 36 import javax.crypto.interfaces.DHPublicKey; 37 import javax.crypto.spec.DHParameterSpec; 38 import javax.crypto.spec.DHPublicKeySpec; 39 import javax.net.ssl.X509ExtendedKeyManager; 40 import javax.net.ssl.X509KeyManager; 41 import javax.net.ssl.X509TrustManager; 42 43 /** 44 * Server side handshake protocol implementation. 45 * Handshake protocol operates on top of the Record Protocol. 46 * It responsible for negotiating a session. 47 * 48 * The implementation processes inbound client handshake messages, 49 * creates and sends respond messages. Outbound messages are supplied 50 * to Record Protocol. Detected errors are reported to the Alert protocol. 51 * 52 * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4. 53 * Handshake protocol.</a> 54 * 55 */ 56 public class ServerHandshakeImpl extends HandshakeProtocol { 57 58 // private key used in key exchange 59 private PrivateKey privKey; 60 61 /** 62 * Creates Server Handshake Implementation 63 * 64 * @param owner 65 */ 66 public ServerHandshakeImpl(Object owner) { 67 super(owner); 68 status = NEED_UNWRAP; 69 } 70 71 /** 72 * Start session negotiation 73 */ 74 @Override 75 public void start() { 76 if (session == null) { // initial handshake 77 status = NEED_UNWRAP; 78 return; // wait client hello 79 } 80 if (clientHello != null && this.status != FINISHED) { 81 // current negotiation has not completed 82 return; // ignore 83 } 84 85 // renegotiation 86 sendHelloRequest(); 87 status = NEED_UNWRAP; 88 } 89 90 /** 91 * Proceses inbound handshake messages 92 * @param bytes 93 */ 94 @Override 95 public void unwrap(byte[] bytes) { 96 97 io_stream.append(bytes); 98 while (io_stream.available() > 0) { 99 int handshakeType; 100 int length; 101 io_stream.mark(); 102 try { 103 handshakeType = io_stream.read(); 104 length = io_stream.readUint24(); 105 if (io_stream.available() < length) { 106 io_stream.reset(); 107 return; 108 } 109 110 switch (handshakeType) { 111 case 1: // CLIENT_HELLO 112 if (clientHello != null && this.status != FINISHED) { 113 // Client hello has been received during handshake 114 unexpectedMessage(); 115 return; 116 } 117 // if protocol planed to send Hello Request message 118 // - cancel this demand. 119 needSendHelloRequest = false; 120 clientHello = new ClientHello(io_stream, length); 121 if (nonBlocking) { 122 delegatedTasks.add(new DelegatedTask(new Runnable() { 123 public void run() { 124 processClientHello(); 125 } 126 }, this)); 127 return; 128 } 129 processClientHello(); 130 break; 131 132 case 11: // CLIENT CERTIFICATE 133 if (isResuming || certificateRequest == null 134 || serverHelloDone == null || clientCert != null) { 135 unexpectedMessage(); 136 return; 137 } 138 clientCert = new CertificateMessage(io_stream, length); 139 if (clientCert.certs.length == 0) { 140 if (parameters.getNeedClientAuth()) { 141 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 142 "HANDSHAKE FAILURE: no client certificate received"); 143 } 144 } else { 145 String authType = clientCert.getAuthType(); 146 try { 147 parameters.getTrustManager().checkClientTrusted( 148 clientCert.certs, authType); 149 } catch (CertificateException e) { 150 fatalAlert(AlertProtocol.BAD_CERTIFICATE, 151 "Untrusted Client Certificate ", e); 152 } 153 session.peerCertificates = clientCert.certs; 154 } 155 break; 156 157 case 15: // CERTIFICATE_VERIFY 158 if (isResuming 159 || clientKeyExchange == null 160 || clientCert == null 161 || clientKeyExchange.isEmpty() //client certificate 162 // contains fixed DH 163 // parameters 164 || certificateVerify != null 165 || changeCipherSpecReceived) { 166 unexpectedMessage(); 167 return; 168 } 169 certificateVerify = new CertificateVerify(io_stream, length); 170 171 String authType = clientCert.getAuthType(); 172 DigitalSignature ds = new DigitalSignature(authType); 173 ds.init(clientCert.certs[0]); 174 byte[] md5_hash = null; 175 byte[] sha_hash = null; 176 177 if ("RSA".equals(authType)) { 178 md5_hash = io_stream.getDigestMD5withoutLast(); 179 sha_hash = io_stream.getDigestSHAwithoutLast(); 180 } else if ("DSA".equals(authType)) { 181 sha_hash = io_stream.getDigestSHAwithoutLast(); 182 // The Signature should be empty in case of anonymous signature algorithm: 183 // } else if ("DH".equals(authType)) { 184 } 185 ds.setMD5(md5_hash); 186 ds.setSHA(sha_hash); 187 if (!ds.verifySignature(certificateVerify.signedHash)) { 188 fatalAlert(AlertProtocol.DECRYPT_ERROR, 189 "DECRYPT ERROR: CERTIFICATE_VERIFY incorrect signature"); 190 } 191 break; 192 case 16: // CLIENT_KEY_EXCHANGE 193 if (isResuming 194 || serverHelloDone == null 195 || clientKeyExchange != null 196 || (clientCert == null && parameters.getNeedClientAuth())) { 197 unexpectedMessage(); 198 return; 199 } 200 if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA 201 || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 202 clientKeyExchange = new ClientKeyExchange(io_stream, 203 length, serverHello.server_version[1] == 1, 204 true); 205 Cipher c = null; 206 try { 207 c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 208 c.init(Cipher.DECRYPT_MODE, privKey); 209 preMasterSecret = c.doFinal(clientKeyExchange.exchange_keys); 210 // check preMasterSecret: 211 if (preMasterSecret.length != 48 212 || preMasterSecret[0] != clientHello.client_version[0] 213 || preMasterSecret[1] != clientHello.client_version[1]) { 214 // incorrect preMasterSecret 215 // prevent an attack (see TLS 1.0 spec., 7.4.7.1.) 216 preMasterSecret = new byte[48]; 217 parameters.getSecureRandom().nextBytes( 218 preMasterSecret); 219 } 220 } catch (Exception e) { 221 fatalAlert(AlertProtocol.INTERNAL_ERROR, 222 "INTERNAL ERROR", e); 223 } 224 } else { // diffie hellman key exchange 225 clientKeyExchange = new ClientKeyExchange(io_stream, 226 length, serverHello.server_version[1] == 1, 227 false); 228 if (clientKeyExchange.isEmpty()) { 229 // TODO check that client cert. DH params 230 // matched server cert. DH params 231 232 // client cert. contains fixed DH parameters 233 preMasterSecret = ((DHPublicKey) clientCert.certs[0].getPublicKey()).getY().toByteArray(); 234 } else { 235 try { 236 KeyFactory kf = KeyFactory.getInstance("DH"); 237 KeyAgreement agreement = KeyAgreement.getInstance("DH"); 238 PublicKey clientPublic = kf.generatePublic(new DHPublicKeySpec( 239 new BigInteger( 240 1, 241 clientKeyExchange.exchange_keys), 242 serverKeyExchange.par1, 243 serverKeyExchange.par2)); 244 agreement.init(privKey); 245 agreement.doPhase(clientPublic, true); 246 preMasterSecret = agreement.generateSecret(); 247 } catch (Exception e) { 248 fatalAlert(AlertProtocol.INTERNAL_ERROR, 249 "INTERNAL ERROR", e); 250 return; 251 } 252 } 253 } 254 255 computerMasterSecret(); 256 break; 257 258 case 20: // FINISHED 259 if (!isResuming && !changeCipherSpecReceived) { 260 unexpectedMessage(); 261 return; 262 } 263 264 clientFinished = new Finished(io_stream, length); 265 verifyFinished(clientFinished.getData()); 266 session.context = parameters.getServerSessionContext(); 267 parameters.getServerSessionContext().putSession(session); 268 if (!isResuming) { 269 sendChangeCipherSpec(); 270 } else { 271 session.lastAccessedTime = System.currentTimeMillis(); 272 status = FINISHED; 273 } 274 break; 275 default: 276 unexpectedMessage(); 277 return; 278 } 279 } catch (IOException e) { 280 // io stream dosn't contain complete handshake message 281 io_stream.reset(); 282 return; 283 } 284 } 285 } 286 /** 287 * Processes SSLv2 Hello message 288 * @ see TLS 1.0 spec., E.1. Version 2 client hello 289 * @param bytes 290 */ 291 @Override 292 public void unwrapSSLv2(byte[] bytes) { 293 io_stream.append(bytes); 294 io_stream.mark(); 295 try { 296 clientHello = new ClientHello(io_stream); 297 } catch (IOException e) { 298 io_stream.reset(); 299 return; 300 } 301 if (nonBlocking) { 302 delegatedTasks.add(new DelegatedTask(new Runnable() { 303 public void run() { 304 processClientHello(); 305 } 306 }, this)); 307 return; 308 } 309 processClientHello(); 310 } 311 312 /** 313 * 314 * Processes Client Hello message. 315 * Server responds to client hello message with server hello 316 * and (if necessary) server certificate, server key exchange, 317 * certificate request, and server hello done messages. 318 */ 319 void processClientHello() { 320 CipherSuite cipher_suite; 321 322 // check that clientHello contains CompressionMethod.null 323 checkCompression: { 324 for (int i = 0; i < clientHello.compression_methods.length; i++) { 325 if (clientHello.compression_methods[i] == 0) { 326 break checkCompression; 327 } 328 } 329 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 330 "HANDSHAKE FAILURE. Incorrect client hello message"); 331 } 332 333 if (!ProtocolVersion.isSupported(clientHello.client_version)) { 334 fatalAlert(AlertProtocol.PROTOCOL_VERSION, 335 "PROTOCOL VERSION. Unsupported client version " 336 + clientHello.client_version[0] 337 + clientHello.client_version[1]); 338 } 339 340 isResuming = false; 341 FIND: if (clientHello.session_id.length != 0) { 342 // client wishes to reuse session 343 344 SSLSessionImpl sessionToResume; 345 boolean reuseCurrent = false; 346 347 // reuse current session 348 if (session != null 349 && Arrays.equals(session.id, clientHello.session_id)) { 350 if (session.isValid()) { 351 isResuming = true; 352 break FIND; 353 } 354 reuseCurrent = true; 355 } 356 357 // find session in cash 358 sessionToResume = findSessionToResume(clientHello.session_id); 359 if (sessionToResume == null || !sessionToResume.isValid()) { 360 if (!parameters.getEnableSessionCreation()) { 361 if (reuseCurrent) { 362 // we can continue current session 363 sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); 364 status = NOT_HANDSHAKING; 365 clearMessages(); 366 return; 367 } 368 // throw AlertException 369 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created"); 370 } 371 session = null; 372 } else { 373 session = (SSLSessionImpl)sessionToResume.clone(); 374 isResuming = true; 375 } 376 } 377 378 if (isResuming) { 379 cipher_suite = session.cipherSuite; 380 // clientHello.cipher_suites must include at least cipher_suite from the session 381 checkCipherSuite: { 382 for (int i = 0; i < clientHello.cipher_suites.length; i++) { 383 if (cipher_suite.equals(clientHello.cipher_suites[i])) { 384 break checkCipherSuite; 385 } 386 } 387 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 388 "HANDSHAKE FAILURE. Incorrect client hello message"); 389 } 390 } else { 391 cipher_suite = selectSuite(clientHello.cipher_suites); 392 if (cipher_suite == null) { 393 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "HANDSHAKE FAILURE. NO COMMON SUITE"); 394 } 395 if (!parameters.getEnableSessionCreation()) { 396 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, 397 "SSL Session may not be created"); 398 } 399 session = new SSLSessionImpl(cipher_suite, parameters.getSecureRandom()); 400 if (engineOwner != null) { 401 session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort()); 402 } else { 403 session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort()); 404 } 405 } 406 407 recordProtocol.setVersion(clientHello.client_version); 408 session.protocol = ProtocolVersion.getByVersion(clientHello.client_version); 409 session.clientRandom = clientHello.random; 410 411 // create server hello message 412 serverHello = new ServerHello(parameters.getSecureRandom(), 413 clientHello.client_version, 414 session.getId(), cipher_suite, (byte) 0); //CompressionMethod.null 415 session.serverRandom = serverHello.random; 416 send(serverHello); 417 if (isResuming) { 418 sendChangeCipherSpec(); 419 return; 420 } 421 422 // create and send server certificate message if needed 423 if (!cipher_suite.isAnonymous()) { // need to send server certificate 424 X509Certificate[] certs = null; 425 String certType = cipher_suite.getServerKeyType(); 426 if (certType == null) { 427 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "NO CERT TYPE FOR " + cipher_suite.getName()); 428 } 429 // obtain certificates from key manager 430 String alias = null; 431 X509KeyManager km = parameters.getKeyManager(); 432 if (km instanceof X509ExtendedKeyManager) { 433 X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km; 434 if (this.socketOwner != null) { 435 alias = ekm.chooseServerAlias(certType, null, 436 this.socketOwner); 437 } else { 438 alias = ekm.chooseEngineServerAlias(certType, null, 439 this.engineOwner); 440 } 441 if (alias != null) { 442 certs = ekm.getCertificateChain(alias); 443 } 444 } else { 445 alias = km.chooseServerAlias(certType, null, this.socketOwner); 446 if (alias != null) { 447 certs = km.getCertificateChain(alias); 448 } 449 } 450 451 if (certs == null) { 452 fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "NO SERVER CERTIFICATE FOUND"); 453 return; 454 } 455 session.localCertificates = certs; 456 serverCert = new CertificateMessage(certs); 457 privKey = km.getPrivateKey(alias); 458 send(serverCert); 459 } 460 461 // create and send server key exchange message if needed 462 RSAPublicKey rsakey = null; 463 DHPublicKeySpec dhkeySpec = null; 464 byte[] hash = null; 465 BigInteger p = null; 466 BigInteger g = null; 467 468 KeyPairGenerator kpg = null; 469 470 try { 471 if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 472 PublicKey pk = serverCert.certs[0].getPublicKey(); 473 if (getRSAKeyLength(pk) > 512) { 474 // key is longer than 512 bits 475 kpg = KeyPairGenerator.getInstance("RSA"); 476 kpg.initialize(512); 477 } 478 } else if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS 479 || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS_EXPORT 480 || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA 481 || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA_EXPORT 482 || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon 483 || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon_EXPORT) { 484 kpg = KeyPairGenerator.getInstance("DH"); 485 p = new BigInteger(1, DHParameters.getPrime()); 486 g = new BigInteger("2"); 487 DHParameterSpec spec = new DHParameterSpec(p, g); 488 kpg.initialize(spec); 489 } 490 } catch (Exception e) { 491 fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); 492 } 493 494 if (kpg != null) { 495 // need to send server key exchange message 496 DigitalSignature ds = new DigitalSignature(cipher_suite.authType); 497 KeyPair kp = null; 498 try { 499 kp = kpg.genKeyPair(); 500 if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 501 rsakey = (RSAPublicKey) kp.getPublic(); 502 } else { 503 DHPublicKey dhkey = (DHPublicKey) kp.getPublic(); 504 KeyFactory kf = KeyFactory.getInstance("DH"); 505 dhkeySpec = kf.getKeySpec(dhkey, DHPublicKeySpec.class); 506 } 507 if (!cipher_suite.isAnonymous()) { // calculate signed_params 508 509 // init by private key which correspond to 510 // server certificate 511 ds.init(privKey); 512 513 // use emphemeral key for key exchange 514 privKey = kp.getPrivate(); 515 ds.update(clientHello.getRandom()); 516 ds.update(serverHello.getRandom()); 517 518 byte[] tmp; 519 byte[] tmpLength = new byte[2]; 520 //FIXME 1_byte==0x00 521 if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 522 tmp = ServerKeyExchange.toUnsignedByteArray(rsakey.getModulus()); 523 tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); 524 tmpLength[1] = (byte) (tmp.length & 0xFF); 525 ds.update(tmpLength); 526 ds.update(tmp); 527 tmp = ServerKeyExchange.toUnsignedByteArray(rsakey.getPublicExponent()); 528 tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); 529 tmpLength[1] = (byte) (tmp.length & 0xFF); 530 ds.update(tmpLength); 531 ds.update(tmp); 532 } else { 533 tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getP()); 534 tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); 535 tmpLength[1] = (byte) (tmp.length & 0xFF); 536 ds.update(tmpLength); 537 ds.update(tmp); 538 tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getG()); 539 tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); 540 tmpLength[1] = (byte) (tmp.length & 0xFF); 541 ds.update(tmpLength); 542 ds.update(tmp); 543 tmp = ServerKeyExchange.toUnsignedByteArray(dhkeySpec.getY()); 544 tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); 545 tmpLength[1] = (byte) (tmp.length & 0xFF); 546 ds.update(tmpLength); 547 ds.update(tmp); 548 } 549 hash = ds.sign(); 550 } else { 551 privKey = kp.getPrivate(); // use emphemeral key for key exchange 552 } 553 } catch (Exception e) { 554 fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); 555 } 556 557 if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) { 558 serverKeyExchange = new ServerKeyExchange(rsakey.getModulus(), 559 rsakey.getPublicExponent(), null, hash); 560 } else { 561 serverKeyExchange = new ServerKeyExchange(p, 562 g, dhkeySpec.getY(), hash); 563 } 564 send(serverKeyExchange); 565 } 566 567 // CERTIFICATE_REQUEST 568 certRequest: if (parameters.getWantClientAuth() 569 || parameters.getNeedClientAuth()) { 570 X509Certificate[] accepted; 571 try { 572 X509TrustManager tm = parameters.getTrustManager(); 573 accepted = tm.getAcceptedIssuers(); 574 } catch (ClassCastException e) { 575 // don't send certificateRequest 576 break certRequest; 577 } 578 byte[] requestedClientCertTypes = { CipherSuite.TLS_CT_RSA_SIGN, 579 CipherSuite.TLS_CT_DSS_SIGN }; 580 certificateRequest = new CertificateRequest( 581 requestedClientCertTypes, accepted); 582 send(certificateRequest); 583 } 584 585 // SERVER_HELLO_DONE 586 serverHelloDone = new ServerHelloDone(); 587 send(serverHelloDone); 588 status = NEED_UNWRAP; 589 } 590 591 /** 592 * Creates and sends finished message 593 */ 594 @Override 595 protected void makeFinished() { 596 byte[] verify_data; 597 boolean isTLS = (serverHello.server_version[1] == 1); // TLS 1.0 protocol 598 if (isTLS) { 599 verify_data = new byte[12]; 600 computerVerifyDataTLS("server finished", verify_data); 601 } else { // SSL 3.0 protocol (http://wp.netscape.com/eng/ssl3) 602 verify_data = new byte[36]; 603 computerVerifyDataSSLv3(SSLv3Constants.server, verify_data); 604 } 605 serverFinished = new Finished(verify_data); 606 send(serverFinished); 607 if (isResuming) { 608 if (isTLS) { 609 computerReferenceVerifyDataTLS("client finished"); 610 } else { 611 computerReferenceVerifyDataSSLv3(SSLv3Constants.client); 612 } 613 status = NEED_UNWRAP; 614 } else { 615 session.lastAccessedTime = System.currentTimeMillis(); 616 status = FINISHED; 617 } 618 } 619 620 // find sesssion in the session hash 621 private SSLSessionImpl findSessionToResume(byte[] session_id) { 622 return (SSLSessionImpl)parameters.getServerSessionContext().getSession(session_id); 623 } 624 625 // find appropriate cipher_suite in the client suites 626 private CipherSuite selectSuite(CipherSuite[] clientSuites) { 627 for (CipherSuite clientSuite : clientSuites) { 628 if (!clientSuite.supported) { 629 continue; 630 } 631 for (CipherSuite enabledCipherSuite : parameters.getEnabledCipherSuitesMember()) { 632 if (clientSuite.equals(enabledCipherSuite)) { 633 return clientSuite; 634 } 635 } 636 } 637 return null; 638 } 639 640 /** 641 * Processes inbound ChangeCipherSpec message 642 */ 643 @Override 644 public void receiveChangeCipherSpec() { 645 if (isResuming) { 646 if (serverFinished == null) { 647 unexpectedMessage(); 648 } else { 649 changeCipherSpecReceived = true; 650 } 651 } else { 652 if ((parameters.getNeedClientAuth() && clientCert == null) 653 || clientKeyExchange == null 654 || (clientCert != null 655 && !clientKeyExchange.isEmpty() 656 && certificateVerify == null)) { 657 unexpectedMessage(); 658 } else { 659 changeCipherSpecReceived = true; 660 } 661 if (serverHello.server_version[1] == 1) { 662 computerReferenceVerifyDataTLS("client finished"); 663 } else { 664 computerReferenceVerifyDataSSLv3(SSLv3Constants.client); 665 } 666 } 667 } 668 669 } 670