J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | |
| 27 | package sun.security.ssl; |
| 28 | |
| 29 | import java.io.*; |
| 30 | import java.math.BigInteger; |
| 31 | import java.security.*; |
| 32 | import java.util.*; |
| 33 | |
| 34 | import java.security.interfaces.ECPublicKey; |
| 35 | import java.security.spec.ECParameterSpec; |
| 36 | |
| 37 | import java.security.cert.X509Certificate; |
| 38 | import java.security.cert.CertificateException; |
| 39 | |
| 40 | import javax.crypto.SecretKey; |
| 41 | import javax.crypto.spec.SecretKeySpec; |
| 42 | |
| 43 | import javax.net.ssl.*; |
| 44 | |
| 45 | import javax.security.auth.Subject; |
| 46 | import javax.security.auth.kerberos.KerberosPrincipal; |
| 47 | import sun.security.jgss.krb5.Krb5Util; |
| 48 | import sun.security.jgss.GSSUtil; |
| 49 | |
| 50 | import com.sun.net.ssl.internal.ssl.X509ExtendedTrustManager; |
| 51 | |
| 52 | import sun.security.ssl.HandshakeMessage.*; |
| 53 | import sun.security.ssl.CipherSuite.*; |
| 54 | import static sun.security.ssl.CipherSuite.*; |
| 55 | import static sun.security.ssl.CipherSuite.KeyExchange.*; |
| 56 | |
| 57 | /** |
| 58 | * ClientHandshaker does the protocol handshaking from the point |
| 59 | * of view of a client. It is driven asychronously by handshake messages |
| 60 | * as delivered by the parent Handshaker class, and also uses |
| 61 | * common functionality (e.g. key generation) that is provided there. |
| 62 | * |
| 63 | * @author David Brownell |
| 64 | */ |
| 65 | final class ClientHandshaker extends Handshaker { |
| 66 | |
| 67 | // the server's public key from its certificate. |
| 68 | private PublicKey serverKey; |
| 69 | |
| 70 | // the server's ephemeral public key from the server key exchange message |
| 71 | // for ECDHE/ECDH_anon and RSA_EXPORT. |
| 72 | private PublicKey ephemeralServerKey; |
| 73 | |
| 74 | // server's ephemeral public value for DHE/DH_anon key exchanges |
| 75 | private BigInteger serverDH; |
| 76 | |
| 77 | private DHCrypt dh; |
| 78 | |
| 79 | private ECDHCrypt ecdh; |
| 80 | |
| 81 | private CertificateRequest certRequest; |
| 82 | |
| 83 | private boolean serverKeyExchangeReceived; |
| 84 | |
| 85 | /* |
| 86 | * The RSA PreMasterSecret needs to know the version of |
| 87 | * ClientHello that was used on this handshake. This represents |
| 88 | * the "max version" this client is supporting. In the |
| 89 | * case of an initial handshake, it's the max version enabled, |
| 90 | * but in the case of a resumption attempt, it's the version |
| 91 | * of the session we're trying to resume. |
| 92 | */ |
| 93 | private ProtocolVersion maxProtocolVersion; |
| 94 | |
| 95 | /* |
| 96 | * Constructors |
| 97 | */ |
| 98 | ClientHandshaker(SSLSocketImpl socket, SSLContextImpl context, |
| 99 | ProtocolList enabledProtocols) { |
| 100 | super(socket, context, enabledProtocols, true, true); |
| 101 | } |
| 102 | |
| 103 | ClientHandshaker(SSLEngineImpl engine, SSLContextImpl context, |
| 104 | ProtocolList enabledProtocols) { |
| 105 | super(engine, context, enabledProtocols, true, true); |
| 106 | } |
| 107 | |
| 108 | /* |
| 109 | * This routine handles all the client side handshake messages, one at |
| 110 | * a time. Given the message type (and in some cases the pending cipher |
| 111 | * spec) it parses the type-specific message. Then it calls a function |
| 112 | * that handles that specific message. |
| 113 | * |
| 114 | * It updates the state machine (need to verify it) as each message |
| 115 | * is processed, and writes responses as needed using the connection |
| 116 | * in the constructor. |
| 117 | */ |
| 118 | void processMessage(byte type, int messageLen) throws IOException { |
| 119 | if (state > type |
| 120 | && (type != HandshakeMessage.ht_hello_request |
| 121 | && state != HandshakeMessage.ht_client_hello)) { |
| 122 | throw new SSLProtocolException( |
| 123 | "Handshake message sequence violation, " + type); |
| 124 | } |
| 125 | |
| 126 | switch (type) { |
| 127 | case HandshakeMessage.ht_hello_request: |
| 128 | this.serverHelloRequest(new HelloRequest(input)); |
| 129 | break; |
| 130 | |
| 131 | case HandshakeMessage.ht_server_hello: |
| 132 | this.serverHello(new ServerHello(input, messageLen)); |
| 133 | break; |
| 134 | |
| 135 | case HandshakeMessage.ht_certificate: |
| 136 | if (keyExchange == K_DH_ANON || keyExchange == K_ECDH_ANON |
| 137 | || keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) { |
| 138 | fatalSE(Alerts.alert_unexpected_message, |
| 139 | "unexpected server cert chain"); |
| 140 | // NOTREACHED |
| 141 | } |
| 142 | this.serverCertificate(new CertificateMsg(input)); |
| 143 | serverKey = |
| 144 | session.getPeerCertificates()[0].getPublicKey(); |
| 145 | break; |
| 146 | |
| 147 | case HandshakeMessage.ht_server_key_exchange: |
| 148 | serverKeyExchangeReceived = true; |
| 149 | switch (keyExchange) { |
| 150 | case K_RSA: |
| 151 | case K_RSA_EXPORT: |
| 152 | try { |
| 153 | this.serverKeyExchange(new RSA_ServerKeyExchange(input)); |
| 154 | } catch (GeneralSecurityException e) { |
| 155 | throwSSLException("Server key", e); |
| 156 | } |
| 157 | break; |
| 158 | case K_DH_ANON: |
| 159 | this.serverKeyExchange(new DH_ServerKeyExchange(input)); |
| 160 | break; |
| 161 | case K_DHE_DSS: |
| 162 | case K_DHE_RSA: |
| 163 | try { |
| 164 | this.serverKeyExchange(new DH_ServerKeyExchange( |
| 165 | input, serverKey, |
| 166 | clnt_random.random_bytes, svr_random.random_bytes, |
| 167 | messageLen)); |
| 168 | } catch (GeneralSecurityException e) { |
| 169 | throwSSLException("Server key", e); |
| 170 | } |
| 171 | break; |
| 172 | case K_ECDHE_ECDSA: |
| 173 | case K_ECDHE_RSA: |
| 174 | case K_ECDH_ANON: |
| 175 | try { |
| 176 | this.serverKeyExchange(new ECDH_ServerKeyExchange |
| 177 | (input, serverKey, clnt_random.random_bytes, |
| 178 | svr_random.random_bytes)); |
| 179 | } catch (GeneralSecurityException e) { |
| 180 | throwSSLException("Server key", e); |
| 181 | } |
| 182 | break; |
| 183 | case K_ECDH_ECDSA: |
| 184 | case K_ECDH_RSA: |
| 185 | throw new SSLProtocolException("Protocol violation: server sent" |
| 186 | + " a server key exchange message for key exchange " + keyExchange); |
| 187 | case K_KRB5: |
| 188 | case K_KRB5_EXPORT: |
| 189 | throw new SSLProtocolException( |
| 190 | "unexpected receipt of server key exchange algorithm"); |
| 191 | default: |
| 192 | throw new SSLProtocolException( |
| 193 | "unsupported key exchange algorithm = " |
| 194 | + keyExchange); |
| 195 | } |
| 196 | break; |
| 197 | |
| 198 | case HandshakeMessage.ht_certificate_request: |
| 199 | // save for later, it's handled by serverHelloDone |
| 200 | if ((keyExchange == K_DH_ANON) || (keyExchange == K_ECDH_ANON)) { |
| 201 | throw new SSLHandshakeException( |
| 202 | "Client authentication requested for "+ |
| 203 | "anonymous cipher suite."); |
| 204 | } else if (keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) { |
| 205 | throw new SSLHandshakeException( |
| 206 | "Client certificate requested for "+ |
| 207 | "kerberos cipher suite."); |
| 208 | } |
| 209 | certRequest = new CertificateRequest(input); |
| 210 | if (debug != null && Debug.isOn("handshake")) { |
| 211 | certRequest.print(System.out); |
| 212 | } |
| 213 | break; |
| 214 | |
| 215 | case HandshakeMessage.ht_server_hello_done: |
| 216 | this.serverHelloDone(new ServerHelloDone(input)); |
| 217 | break; |
| 218 | |
| 219 | case HandshakeMessage.ht_finished: |
| 220 | this.serverFinished(new Finished(protocolVersion, input)); |
| 221 | break; |
| 222 | |
| 223 | default: |
| 224 | throw new SSLProtocolException( |
| 225 | "Illegal client handshake msg, " + type); |
| 226 | } |
| 227 | |
| 228 | // |
| 229 | // Move state machine forward if the message handling |
| 230 | // code didn't already do so |
| 231 | // |
| 232 | if (state < type) { |
| 233 | state = type; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | /* |
| 238 | * Used by the server to kickstart negotiations -- this requests a |
| 239 | * "client hello" to renegotiate current cipher specs (e.g. maybe lots |
| 240 | * of data has been encrypted with the same keys, or the server needs |
| 241 | * the client to present a certificate). |
| 242 | */ |
| 243 | private void serverHelloRequest(HelloRequest mesg) throws IOException { |
| 244 | if (debug != null && Debug.isOn("handshake")) { |
| 245 | mesg.print(System.out); |
| 246 | } |
| 247 | |
| 248 | // |
| 249 | // Could be (e.g. at connection setup) that we already |
| 250 | // sent the "client hello" but the server's not seen it. |
| 251 | // |
| 252 | if (state < HandshakeMessage.ht_client_hello) { |
| 253 | kickstart(); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | |
| 258 | /* |
| 259 | * Server chooses session parameters given options created by the |
| 260 | * client -- basically, cipher options, session id, and someday a |
| 261 | * set of compression options. |
| 262 | * |
| 263 | * There are two branches of the state machine, decided by the |
| 264 | * details of this message. One is the "fast" handshake, where we |
| 265 | * can resume the pre-existing session we asked resume. The other |
| 266 | * is a more expensive "full" handshake, with key exchange and |
| 267 | * probably authentication getting done. |
| 268 | */ |
| 269 | private void serverHello(ServerHello mesg) throws IOException { |
| 270 | serverKeyExchangeReceived = false; |
| 271 | if (debug != null && Debug.isOn("handshake")) { |
| 272 | mesg.print(System.out); |
| 273 | } |
| 274 | |
| 275 | // check if the server selected protocol version is OK for us |
| 276 | ProtocolVersion mesgVersion = mesg.protocolVersion; |
| 277 | if (enabledProtocols.contains(mesgVersion) == false) { |
| 278 | throw new SSLHandshakeException |
| 279 | ("Server chose unsupported or disabled protocol: " + mesgVersion); |
| 280 | } |
| 281 | |
| 282 | // Set protocolVersion and propagate to SSLSocket and the |
| 283 | // Handshake streams |
| 284 | setVersion(mesgVersion); |
| 285 | |
| 286 | // |
| 287 | // Save server nonce, we always use it to compute connection |
| 288 | // keys and it's also used to create the master secret if we're |
| 289 | // creating a new session (i.e. in the full handshake). |
| 290 | // |
| 291 | svr_random = mesg.svr_random; |
| 292 | |
| 293 | if (isEnabled(mesg.cipherSuite) == false) { |
| 294 | fatalSE(Alerts.alert_illegal_parameter, |
| 295 | "Server selected disabled ciphersuite " + cipherSuite); |
| 296 | } |
| 297 | setCipherSuite(mesg.cipherSuite); |
| 298 | |
| 299 | if (mesg.compression_method != 0) { |
| 300 | fatalSE(Alerts.alert_illegal_parameter, |
| 301 | "compression type not supported, " |
| 302 | + mesg.compression_method); |
| 303 | // NOTREACHED |
| 304 | } |
| 305 | |
| 306 | // so far so good, let's look at the session |
| 307 | if (session != null) { |
| 308 | // we tried to resume, let's see what the server decided |
| 309 | if (session.getSessionId().equals(mesg.sessionId)) { |
| 310 | // server resumed the session, let's make sure everything |
| 311 | // checks out |
| 312 | |
| 313 | // Verify that the session ciphers are unchanged. |
| 314 | CipherSuite sessionSuite = session.getSuite(); |
| 315 | if (cipherSuite != sessionSuite) { |
| 316 | throw new SSLProtocolException |
| 317 | ("Server returned wrong cipher suite for session"); |
| 318 | } |
| 319 | |
| 320 | // verify protocol version match |
| 321 | ProtocolVersion sessionVersion = session.getProtocolVersion(); |
| 322 | if (protocolVersion != sessionVersion) { |
| 323 | throw new SSLProtocolException |
| 324 | ("Server resumed session with wrong protocol version"); |
| 325 | } |
| 326 | |
| 327 | // validate subject identity |
| 328 | if (sessionSuite.keyExchange == K_KRB5 || |
| 329 | sessionSuite.keyExchange == K_KRB5_EXPORT) { |
| 330 | Principal localPrincipal = session.getLocalPrincipal(); |
| 331 | |
| 332 | Subject subject = null; |
| 333 | try { |
| 334 | subject = AccessController.doPrivileged( |
| 335 | new PrivilegedExceptionAction<Subject>() { |
| 336 | public Subject run() throws Exception { |
| 337 | return Krb5Util.getSubject( |
| 338 | GSSUtil.CALLER_SSL_CLIENT, |
| 339 | getAccSE()); |
| 340 | }}); |
| 341 | } catch (PrivilegedActionException e) { |
| 342 | subject = null; |
| 343 | if (debug != null && Debug.isOn("session")) { |
| 344 | System.out.println("Attempt to obtain" + |
| 345 | " subject failed!"); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | if (subject != null) { |
| 350 | Set<KerberosPrincipal> principals = |
| 351 | subject.getPrincipals(KerberosPrincipal.class); |
| 352 | if (!principals.contains(localPrincipal)) { |
| 353 | throw new SSLProtocolException("Server resumed" + |
| 354 | " session with wrong subject identity"); |
| 355 | } else { |
| 356 | if (debug != null && Debug.isOn("session")) |
| 357 | System.out.println("Subject identity is same"); |
| 358 | } |
| 359 | } else { |
| 360 | if (debug != null && Debug.isOn("session")) |
| 361 | System.out.println("Kerberos credentials are not" + |
| 362 | " present in the current Subject; check if " + |
| 363 | " javax.security.auth.useSubjectAsCreds" + |
| 364 | " system property has been set to false"); |
| 365 | throw new SSLProtocolException |
| 366 | ("Server resumed session with no subject"); |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | // looks fine; resume it, and update the state machine. |
| 371 | resumingSession = true; |
| 372 | state = HandshakeMessage.ht_finished - 1; |
| 373 | calculateConnectionKeys(session.getMasterSecret()); |
| 374 | if (debug != null && Debug.isOn("session")) { |
| 375 | System.out.println("%% Server resumed " + session); |
| 376 | } |
| 377 | return; |
| 378 | } else { |
| 379 | // we wanted to resume, but the server refused |
| 380 | session = null; |
| 381 | if (!enableNewSession) { |
| 382 | throw new SSLException |
| 383 | ("New session creation is disabled"); |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | // check extensions |
| 389 | for (HelloExtension ext : mesg.extensions.list()) { |
| 390 | ExtensionType type = ext.type; |
| 391 | if ((type != ExtensionType.EXT_ELLIPTIC_CURVES) |
| 392 | && (type != ExtensionType.EXT_EC_POINT_FORMATS)) { |
| 393 | fatalSE(Alerts.alert_unsupported_extension, |
| 394 | "Server sent an unsupported extension: " + type); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | // Create a new session, we need to do the full handshake |
| 399 | session = new SSLSessionImpl(protocolVersion, cipherSuite, |
| 400 | mesg.sessionId, getHostSE(), getPortSE()); |
| 401 | if (debug != null && Debug.isOn("handshake")) { |
| 402 | System.out.println("** " + cipherSuite); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | /* |
| 407 | * Server's own key was either a signing-only key, or was too |
| 408 | * large for export rules ... this message holds an ephemeral |
| 409 | * RSA key to use for key exchange. |
| 410 | */ |
| 411 | private void serverKeyExchange(RSA_ServerKeyExchange mesg) |
| 412 | throws IOException, GeneralSecurityException { |
| 413 | if (debug != null && Debug.isOn("handshake")) { |
| 414 | mesg.print(System.out); |
| 415 | } |
| 416 | if (!mesg.verify(serverKey, clnt_random, svr_random)) { |
| 417 | fatalSE(Alerts.alert_handshake_failure, |
| 418 | "server key exchange invalid"); |
| 419 | // NOTREACHED |
| 420 | } |
| 421 | ephemeralServerKey = mesg.getPublicKey(); |
| 422 | } |
| 423 | |
| 424 | |
| 425 | /* |
| 426 | * Diffie-Hellman key exchange. We save the server public key and |
| 427 | * our own D-H algorithm object so we can defer key calculations |
| 428 | * until after we've sent the client key exchange message (which |
| 429 | * gives client and server some useful parallelism). |
| 430 | */ |
| 431 | private void serverKeyExchange(DH_ServerKeyExchange mesg) |
| 432 | throws IOException { |
| 433 | if (debug != null && Debug.isOn("handshake")) { |
| 434 | mesg.print(System.out); |
| 435 | } |
| 436 | dh = new DHCrypt(mesg.getModulus(), mesg.getBase(), sslContext.getSecureRandom()); |
| 437 | serverDH = mesg.getServerPublicKey(); |
| 438 | } |
| 439 | |
| 440 | private void serverKeyExchange(ECDH_ServerKeyExchange mesg) throws IOException { |
| 441 | if (debug != null && Debug.isOn("handshake")) { |
| 442 | mesg.print(System.out); |
| 443 | } |
| 444 | ECPublicKey key = mesg.getPublicKey(); |
| 445 | ecdh = new ECDHCrypt(key.getParams(), sslContext.getSecureRandom()); |
| 446 | ephemeralServerKey = key; |
| 447 | } |
| 448 | |
| 449 | /* |
| 450 | * The server's "Hello Done" message is the client's sign that |
| 451 | * it's time to do all the hard work. |
| 452 | */ |
| 453 | private void serverHelloDone(ServerHelloDone mesg) throws IOException { |
| 454 | if (debug != null && Debug.isOn("handshake")) { |
| 455 | mesg.print(System.out); |
| 456 | } |
| 457 | /* |
| 458 | * Always make sure the input has been digested before we |
| 459 | * start emitting data, to ensure the hashes are correctly |
| 460 | * computed for the Finished and CertificateVerify messages |
| 461 | * which we send (here). |
| 462 | */ |
| 463 | input.digestNow(); |
| 464 | |
| 465 | /* |
| 466 | * FIRST ... if requested, send an appropriate Certificate chain |
| 467 | * to authenticate the client, and remember the associated private |
| 468 | * key to sign the CertificateVerify message. |
| 469 | */ |
| 470 | PrivateKey signingKey = null; |
| 471 | |
| 472 | if (certRequest != null) { |
| 473 | X509ExtendedKeyManager km = sslContext.getX509KeyManager(); |
| 474 | |
| 475 | ArrayList<String> keytypesTmp = new ArrayList<String>(4); |
| 476 | |
| 477 | for (int i = 0; i < certRequest.types.length; i++) { |
| 478 | String typeName; |
| 479 | |
| 480 | switch (certRequest.types[i]) { |
| 481 | case CertificateRequest.cct_rsa_sign: |
| 482 | typeName = "RSA"; |
| 483 | break; |
| 484 | |
| 485 | case CertificateRequest.cct_dss_sign: |
| 486 | typeName = "DSA"; |
| 487 | break; |
| 488 | |
| 489 | case CertificateRequest.cct_ecdsa_sign: |
| 490 | // ignore if we do not have EC crypto available |
| 491 | typeName = JsseJce.isEcAvailable() ? "EC" : null; |
| 492 | break; |
| 493 | |
| 494 | // Fixed DH/ECDH client authentication not supported |
| 495 | case CertificateRequest.cct_rsa_fixed_dh: |
| 496 | case CertificateRequest.cct_dss_fixed_dh: |
| 497 | case CertificateRequest.cct_rsa_fixed_ecdh: |
| 498 | case CertificateRequest.cct_ecdsa_fixed_ecdh: |
| 499 | // Any other values (currently not used in TLS) |
| 500 | case CertificateRequest.cct_rsa_ephemeral_dh: |
| 501 | case CertificateRequest.cct_dss_ephemeral_dh: |
| 502 | default: |
| 503 | typeName = null; |
| 504 | break; |
| 505 | } |
| 506 | |
| 507 | if ((typeName != null) && (!keytypesTmp.contains(typeName))) { |
| 508 | keytypesTmp.add(typeName); |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | String alias = null; |
| 513 | int keytypesTmpSize = keytypesTmp.size(); |
| 514 | if (keytypesTmpSize != 0) { |
| 515 | String keytypes[] = |
| 516 | keytypesTmp.toArray(new String[keytypesTmpSize]); |
| 517 | |
| 518 | if (conn != null) { |
| 519 | alias = km.chooseClientAlias(keytypes, |
| 520 | certRequest.getAuthorities(), conn); |
| 521 | } else { |
| 522 | alias = km.chooseEngineClientAlias(keytypes, |
| 523 | certRequest.getAuthorities(), engine); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | CertificateMsg m1 = null; |
| 528 | if (alias != null) { |
| 529 | X509Certificate[] certs = km.getCertificateChain(alias); |
| 530 | if ((certs != null) && (certs.length != 0)) { |
| 531 | PublicKey publicKey = certs[0].getPublicKey(); |
| 532 | // for EC, make sure we use a supported named curve |
| 533 | if (publicKey instanceof ECPublicKey) { |
| 534 | ECParameterSpec params = ((ECPublicKey)publicKey).getParams(); |
| 535 | int index = SupportedEllipticCurvesExtension.getCurveIndex(params); |
| 536 | if (!SupportedEllipticCurvesExtension.isSupported(index)) { |
| 537 | publicKey = null; |
| 538 | } |
| 539 | } |
| 540 | if (publicKey != null) { |
| 541 | m1 = new CertificateMsg(certs); |
| 542 | signingKey = km.getPrivateKey(alias); |
| 543 | session.setLocalPrivateKey(signingKey); |
| 544 | session.setLocalCertificates(certs); |
| 545 | } |
| 546 | } |
| 547 | } |
| 548 | if (m1 == null) { |
| 549 | // |
| 550 | // No appropriate cert was found ... report this to the |
| 551 | // server. For SSLv3, send the no_certificate alert; |
| 552 | // TLS uses an empty cert chain instead. |
| 553 | // |
| 554 | if (protocolVersion.v >= ProtocolVersion.TLS10.v) { |
| 555 | m1 = new CertificateMsg(new X509Certificate [0]); |
| 556 | } else { |
| 557 | warningSE(Alerts.alert_no_certificate); |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | // |
| 562 | // At last ... send any client certificate chain. |
| 563 | // |
| 564 | if (m1 != null) { |
| 565 | if (debug != null && Debug.isOn("handshake")) { |
| 566 | m1.print(System.out); |
| 567 | } |
| 568 | m1.write(output); |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | /* |
| 573 | * SECOND ... send the client key exchange message. The |
| 574 | * procedure used is a function of the cipher suite selected; |
| 575 | * one is always needed. |
| 576 | */ |
| 577 | HandshakeMessage m2; |
| 578 | |
| 579 | switch (keyExchange) { |
| 580 | |
| 581 | case K_RSA: |
| 582 | case K_RSA_EXPORT: |
| 583 | /* |
| 584 | * For RSA key exchange, we randomly generate a new |
| 585 | * pre-master secret and encrypt it with the server's |
| 586 | * public key. Then we save that pre-master secret |
| 587 | * so that we can calculate the keying data later; |
| 588 | * it's a performance speedup not to do that until |
| 589 | * the client's waiting for the server response, but |
| 590 | * more of a speedup for the D-H case. |
| 591 | */ |
| 592 | PublicKey key = (keyExchange == K_RSA) ? serverKey : ephemeralServerKey; |
| 593 | m2 = new RSAClientKeyExchange(protocolVersion, maxProtocolVersion, |
| 594 | sslContext.getSecureRandom(), key); |
| 595 | break; |
| 596 | case K_DH_RSA: |
| 597 | case K_DH_DSS: |
| 598 | /* |
| 599 | * For DH Key exchange, we only need to make sure the server |
| 600 | * knows our public key, so we calculate the same pre-master |
| 601 | * secret. |
| 602 | * |
| 603 | * For certs that had DH keys in them, we send an empty |
| 604 | * handshake message (no key) ... we flag this case by |
| 605 | * passing a null "dhPublic" value. |
| 606 | * |
| 607 | * Otherwise we send ephemeral DH keys, unsigned. |
| 608 | */ |
| 609 | // if (useDH_RSA || useDH_DSS) |
| 610 | m2 = new DHClientKeyExchange(); |
| 611 | break; |
| 612 | case K_DHE_RSA: |
| 613 | case K_DHE_DSS: |
| 614 | case K_DH_ANON: |
| 615 | if (dh == null) { |
| 616 | throw new SSLProtocolException |
| 617 | ("Server did not send a DH Server Key Exchange message"); |
| 618 | } |
| 619 | m2 = new DHClientKeyExchange(dh.getPublicKey()); |
| 620 | break; |
| 621 | case K_ECDHE_RSA: |
| 622 | case K_ECDHE_ECDSA: |
| 623 | case K_ECDH_ANON: |
| 624 | if (ecdh == null) { |
| 625 | throw new SSLProtocolException |
| 626 | ("Server did not send a ECDH Server Key Exchange message"); |
| 627 | } |
| 628 | m2 = new ECDHClientKeyExchange(ecdh.getPublicKey()); |
| 629 | break; |
| 630 | case K_ECDH_RSA: |
| 631 | case K_ECDH_ECDSA: |
| 632 | if (serverKey == null) { |
| 633 | throw new SSLProtocolException |
| 634 | ("Server did not send certificate message"); |
| 635 | } |
| 636 | if (serverKey instanceof ECPublicKey == false) { |
| 637 | throw new SSLProtocolException |
| 638 | ("Server certificate does not include an EC key"); |
| 639 | } |
| 640 | ECParameterSpec params = ((ECPublicKey)serverKey).getParams(); |
| 641 | ecdh = new ECDHCrypt(params, sslContext.getSecureRandom()); |
| 642 | m2 = new ECDHClientKeyExchange(ecdh.getPublicKey()); |
| 643 | break; |
| 644 | case K_KRB5: |
| 645 | case K_KRB5_EXPORT: |
| 646 | String hostname = getHostSE(); |
| 647 | if (hostname == null) { |
| 648 | throw new IOException("Hostname is required" + |
| 649 | " to use Kerberos cipher suites"); |
| 650 | } |
| 651 | KerberosClientKeyExchange kerberosMsg = new KerberosClientKeyExchange |
| 652 | (hostname, isLoopbackSE(), getAccSE(), protocolVersion, |
| 653 | sslContext.getSecureRandom()); |
| 654 | // Record the principals involved in exchange |
| 655 | session.setPeerPrincipal(kerberosMsg.getPeerPrincipal()); |
| 656 | session.setLocalPrincipal(kerberosMsg.getLocalPrincipal()); |
| 657 | m2 = kerberosMsg; |
| 658 | break; |
| 659 | default: |
| 660 | // somethings very wrong |
| 661 | throw new RuntimeException |
| 662 | ("Unsupported key exchange: " + keyExchange); |
| 663 | } |
| 664 | if (debug != null && Debug.isOn("handshake")) { |
| 665 | m2.print(System.out); |
| 666 | } |
| 667 | m2.write(output); |
| 668 | |
| 669 | |
| 670 | /* |
| 671 | * THIRD, send a "change_cipher_spec" record followed by the |
| 672 | * "Finished" message. We flush the messages we've queued up, to |
| 673 | * get concurrency between client and server. The concurrency is |
| 674 | * useful as we calculate the master secret, which is needed both |
| 675 | * to compute the "Finished" message, and to compute the keys used |
| 676 | * to protect all records following the change_cipher_spec. |
| 677 | */ |
| 678 | |
| 679 | output.doHashes(); |
| 680 | output.flush(); |
| 681 | |
| 682 | /* |
| 683 | * We deferred calculating the master secret and this connection's |
| 684 | * keying data; we do it now. Deferring this calculation is good |
| 685 | * from a performance point of view, since it lets us do it during |
| 686 | * some time that network delays and the server's own calculations |
| 687 | * would otherwise cause to be "dead" in the critical path. |
| 688 | */ |
| 689 | SecretKey preMasterSecret; |
| 690 | switch (keyExchange) { |
| 691 | case K_RSA: |
| 692 | case K_RSA_EXPORT: |
| 693 | preMasterSecret = ((RSAClientKeyExchange)m2).preMaster; |
| 694 | break; |
| 695 | case K_KRB5: |
| 696 | case K_KRB5_EXPORT: |
| 697 | byte[] secretBytes = |
| 698 | ((KerberosClientKeyExchange)m2).getPreMasterSecret().getUnencrypted(); |
| 699 | preMasterSecret = new SecretKeySpec(secretBytes, "TlsPremasterSecret"); |
| 700 | break; |
| 701 | case K_DHE_RSA: |
| 702 | case K_DHE_DSS: |
| 703 | case K_DH_ANON: |
| 704 | preMasterSecret = dh.getAgreedSecret(serverDH); |
| 705 | break; |
| 706 | case K_ECDHE_RSA: |
| 707 | case K_ECDHE_ECDSA: |
| 708 | case K_ECDH_ANON: |
| 709 | preMasterSecret = ecdh.getAgreedSecret(ephemeralServerKey); |
| 710 | break; |
| 711 | case K_ECDH_RSA: |
| 712 | case K_ECDH_ECDSA: |
| 713 | preMasterSecret = ecdh.getAgreedSecret(serverKey); |
| 714 | break; |
| 715 | default: |
| 716 | throw new IOException("Internal error: unknown key exchange " + keyExchange); |
| 717 | } |
| 718 | |
| 719 | calculateKeys(preMasterSecret, null); |
| 720 | |
| 721 | /* |
| 722 | * FOURTH, if we sent a Certificate, we need to send a signed |
| 723 | * CertificateVerify (unless the key in the client's certificate |
| 724 | * was a Diffie-Hellman key).). |
| 725 | * |
| 726 | * This uses a hash of the previous handshake messages ... either |
| 727 | * a nonfinal one (if the particular implementation supports it) |
| 728 | * or else using the third element in the arrays of hashes being |
| 729 | * computed. |
| 730 | */ |
| 731 | if (signingKey != null) { |
| 732 | CertificateVerify m3; |
| 733 | try { |
| 734 | m3 = new CertificateVerify(protocolVersion, handshakeHash, |
| 735 | signingKey, session.getMasterSecret(), |
| 736 | sslContext.getSecureRandom()); |
| 737 | } catch (GeneralSecurityException e) { |
| 738 | fatalSE(Alerts.alert_handshake_failure, |
| 739 | "Error signing certificate verify", e); |
| 740 | // NOTREACHED, make compiler happy |
| 741 | m3 = null; |
| 742 | } |
| 743 | if (debug != null && Debug.isOn("handshake")) { |
| 744 | m3.print(System.out); |
| 745 | } |
| 746 | m3.write(output); |
| 747 | output.doHashes(); |
| 748 | } |
| 749 | |
| 750 | /* |
| 751 | * OK, that's that! |
| 752 | */ |
| 753 | sendChangeCipherAndFinish(false); |
| 754 | } |
| 755 | |
| 756 | |
| 757 | /* |
| 758 | * "Finished" is the last handshake message sent. If we got this |
| 759 | * far, the MAC has been validated post-decryption. We validate |
| 760 | * the two hashes here as an additional sanity check, protecting |
| 761 | * the handshake against various active attacks. |
| 762 | */ |
| 763 | private void serverFinished(Finished mesg) throws IOException { |
| 764 | if (debug != null && Debug.isOn("handshake")) { |
| 765 | mesg.print(System.out); |
| 766 | } |
| 767 | |
| 768 | boolean verified = mesg.verify(protocolVersion, handshakeHash, |
| 769 | Finished.SERVER, session.getMasterSecret()); |
| 770 | |
| 771 | if (!verified) { |
| 772 | fatalSE(Alerts.alert_illegal_parameter, |
| 773 | "server 'finished' message doesn't verify"); |
| 774 | // NOTREACHED |
| 775 | } |
| 776 | |
| 777 | /* |
| 778 | * OK, it verified. If we're doing the fast handshake, add that |
| 779 | * "Finished" message to the hash of handshake messages, then send |
| 780 | * our own change_cipher_spec and Finished message for the server |
| 781 | * to verify in turn. These are the last handshake messages. |
| 782 | * |
| 783 | * In any case, update the session cache. We're done handshaking, |
| 784 | * so there are no threats any more associated with partially |
| 785 | * completed handshakes. |
| 786 | */ |
| 787 | if (resumingSession) { |
| 788 | input.digestNow(); |
| 789 | sendChangeCipherAndFinish(true); |
| 790 | } |
| 791 | session.setLastAccessedTime(System.currentTimeMillis()); |
| 792 | |
| 793 | if (!resumingSession) { |
| 794 | if (session.isRejoinable()) { |
| 795 | ((SSLSessionContextImpl) sslContext |
| 796 | .engineGetClientSessionContext()) |
| 797 | .put(session); |
| 798 | if (debug != null && Debug.isOn("session")) { |
| 799 | System.out.println("%% Cached client session: " + session); |
| 800 | } |
| 801 | } else if (debug != null && Debug.isOn("session")) { |
| 802 | System.out.println( |
| 803 | "%% Didn't cache non-resumable client session: " |
| 804 | + session); |
| 805 | } |
| 806 | } |
| 807 | } |
| 808 | |
| 809 | |
| 810 | /* |
| 811 | * Send my change-cipher-spec and Finished message ... done as the |
| 812 | * last handshake act in either the short or long sequences. In |
| 813 | * the short one, we've already seen the server's Finished; in the |
| 814 | * long one, we wait for it now. |
| 815 | */ |
| 816 | private void sendChangeCipherAndFinish(boolean finishedTag) |
| 817 | throws IOException { |
| 818 | Finished mesg = new Finished(protocolVersion, handshakeHash, |
| 819 | Finished.CLIENT, session.getMasterSecret()); |
| 820 | |
| 821 | /* |
| 822 | * Send the change_cipher_spec message, then the Finished message |
| 823 | * which we just calculated (and protected using the keys we just |
| 824 | * calculated). Server responds with its Finished message, except |
| 825 | * in the "fast handshake" (resume session) case. |
| 826 | */ |
| 827 | sendChangeCipherSpec(mesg, finishedTag); |
| 828 | |
| 829 | /* |
| 830 | * Update state machine so server MUST send 'finished' next. |
| 831 | * (In "long" handshake case; in short case, we're responding |
| 832 | * to its message.) |
| 833 | */ |
| 834 | state = HandshakeMessage.ht_finished - 1; |
| 835 | } |
| 836 | |
| 837 | |
| 838 | /* |
| 839 | * Returns a ClientHello message to kickstart renegotiations |
| 840 | */ |
| 841 | HandshakeMessage getKickstartMessage() throws SSLException { |
| 842 | ClientHello mesg = new ClientHello(sslContext.getSecureRandom(), |
| 843 | protocolVersion); |
| 844 | maxProtocolVersion = protocolVersion; |
| 845 | |
| 846 | clnt_random = mesg.clnt_random; |
| 847 | |
| 848 | // |
| 849 | // Try to resume an existing session. This might be mandatory, |
| 850 | // given certain API options. |
| 851 | // |
| 852 | session = ((SSLSessionContextImpl)sslContext |
| 853 | .engineGetClientSessionContext()) |
| 854 | .get(getHostSE(), getPortSE()); |
| 855 | if (debug != null && Debug.isOn("session")) { |
| 856 | if (session != null) { |
| 857 | System.out.println("%% Client cached " |
| 858 | + session |
| 859 | + (session.isRejoinable() ? "" : " (not rejoinable)")); |
| 860 | } else { |
| 861 | System.out.println("%% No cached client session"); |
| 862 | } |
| 863 | } |
| 864 | if ((session != null) && (session.isRejoinable() == false)) { |
| 865 | session = null; |
| 866 | } |
| 867 | |
| 868 | if (session != null) { |
| 869 | CipherSuite sessionSuite = session.getSuite(); |
| 870 | ProtocolVersion sessionVersion = session.getProtocolVersion(); |
| 871 | if (isEnabled(sessionSuite) == false) { |
| 872 | if (debug != null && Debug.isOn("session")) { |
| 873 | System.out.println("%% can't resume, cipher disabled"); |
| 874 | } |
| 875 | session = null; |
| 876 | } |
| 877 | |
| 878 | if ((session != null) && |
| 879 | (enabledProtocols.contains(sessionVersion) == false)) { |
| 880 | if (debug != null && Debug.isOn("session")) { |
| 881 | System.out.println("%% can't resume, protocol disabled"); |
| 882 | } |
| 883 | session = null; |
| 884 | } |
| 885 | |
| 886 | if (session != null) { |
| 887 | if (debug != null) { |
| 888 | if (Debug.isOn("handshake") || Debug.isOn("session")) { |
| 889 | System.out.println("%% Try resuming " + session |
| 890 | + " from port " + getLocalPortSE()); |
| 891 | } |
| 892 | } |
| 893 | mesg.sessionId = session.getSessionId(); |
| 894 | |
| 895 | mesg.protocolVersion = sessionVersion; |
| 896 | maxProtocolVersion = sessionVersion; |
| 897 | |
| 898 | // Update SSL version number in underlying SSL socket and |
| 899 | // handshake output stream, so that the output records (at the |
| 900 | // record layer) have the correct version |
| 901 | setVersion(sessionVersion); |
| 902 | } |
| 903 | |
| 904 | // |
| 905 | // don't say much beyond the obvious if we _must_ resume. |
| 906 | // |
| 907 | if (!enableNewSession) { |
| 908 | if (session == null) { |
| 909 | throw new SSLException( |
| 910 | "Can't reuse existing SSL client session"); |
| 911 | } |
| 912 | mesg.setCipherSuites(new CipherSuiteList(sessionSuite)); |
| 913 | return mesg; |
| 914 | } |
| 915 | } |
| 916 | if (session == null) { |
| 917 | if (enableNewSession) { |
| 918 | mesg.sessionId = SSLSessionImpl.nullSession.getSessionId(); |
| 919 | } else { |
| 920 | throw new SSLException("No existing session to resume."); |
| 921 | } |
| 922 | } |
| 923 | |
| 924 | // |
| 925 | // All we have left to do is fill out the cipher suites. |
| 926 | // (If this changes, change the 'return' above!) |
| 927 | // |
| 928 | mesg.setCipherSuites(enabledCipherSuites); |
| 929 | |
| 930 | return mesg; |
| 931 | } |
| 932 | |
| 933 | /* |
| 934 | * Fault detected during handshake. |
| 935 | */ |
| 936 | void handshakeAlert(byte description) throws SSLProtocolException { |
| 937 | String message = Alerts.alertDescription(description); |
| 938 | |
| 939 | if (debug != null && Debug.isOn("handshake")) { |
| 940 | System.out.println("SSL - handshake alert: " + message); |
| 941 | } |
| 942 | throw new SSLProtocolException("handshake alert: " + message); |
| 943 | } |
| 944 | |
| 945 | /* |
| 946 | * Unless we are using an anonymous ciphersuite, the server always |
| 947 | * sends a certificate message (for the CipherSuites we currently |
| 948 | * support). The trust manager verifies the chain for us. |
| 949 | */ |
| 950 | private void serverCertificate(CertificateMsg mesg) throws IOException { |
| 951 | if (debug != null && Debug.isOn("handshake")) { |
| 952 | mesg.print(System.out); |
| 953 | } |
| 954 | X509Certificate[] peerCerts = mesg.getCertificateChain(); |
| 955 | if (peerCerts.length == 0) { |
| 956 | fatalSE(Alerts.alert_bad_certificate, |
| 957 | "empty certificate chain"); |
| 958 | } |
| 959 | // ask the trust manager to verify the chain |
| 960 | X509TrustManager tm = sslContext.getX509TrustManager(); |
| 961 | try { |
| 962 | // find out the key exchange algorithm used |
| 963 | // use "RSA" for non-ephemeral "RSA_EXPORT" |
| 964 | String keyExchangeString; |
| 965 | if (keyExchange == K_RSA_EXPORT && !serverKeyExchangeReceived) { |
| 966 | keyExchangeString = K_RSA.name; |
| 967 | } else { |
| 968 | keyExchangeString = keyExchange.name; |
| 969 | } |
| 970 | |
| 971 | String identificator = getHostnameVerificationSE(); |
| 972 | if (tm instanceof X509ExtendedTrustManager) { |
| 973 | ((X509ExtendedTrustManager)tm).checkServerTrusted( |
| 974 | (peerCerts != null ? |
| 975 | peerCerts.clone() : |
| 976 | null), |
| 977 | keyExchangeString, |
| 978 | getHostSE(), |
| 979 | identificator); |
| 980 | } else { |
| 981 | if (identificator != null) { |
| 982 | throw new RuntimeException( |
| 983 | "trust manager does not support peer identification"); |
| 984 | } |
| 985 | |
| 986 | tm.checkServerTrusted( |
| 987 | (peerCerts != null ? |
| 988 | peerCerts.clone() : |
| 989 | peerCerts), |
| 990 | keyExchangeString); |
| 991 | } |
| 992 | } catch (CertificateException e) { |
| 993 | // This will throw an exception, so include the original error. |
| 994 | fatalSE(Alerts.alert_certificate_unknown, e); |
| 995 | } |
| 996 | session.setPeerCertificates(peerCerts); |
| 997 | } |
| 998 | } |