src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java
Print this page
@@ -139,15 +139,24 @@
* server certificate change in renegotiation is disabled.
*/
private final static boolean allowUnsafeServerCertChange =
Debug.getBooleanProperty("jdk.tls.allowUnsafeServerCertChange", false);
+ // To switch off the max_fragment_length extension.
+ private final static boolean enableMFLExtension =
+ Debug.getBooleanProperty("jsse.enableMFLExtension", false);
+
private List<SNIServerName> requestedServerNames =
Collections.<SNIServerName>emptyList();
+ // maximum fragment length
+ private int requestedMFLength = -1; // -1: no fragment length limit
+
private boolean serverNamesAccepted = false;
+ private ClientHello initialClientHelloMsg = null; // DTLS only
+
/*
* the reserved server certificate chain in previous handshaking
*
* The server certificate chain is only reserved if the previous
* handshake is a session-resumption abbreviated initial handshake.
@@ -170,15 +179,16 @@
ClientHandshaker(SSLEngineImpl engine, SSLContextImpl context,
ProtocolList enabledProtocols,
ProtocolVersion activeProtocolVersion,
boolean isInitialHandshake, boolean secureRenegotiation,
- byte[] clientVerifyData, byte[] serverVerifyData) {
+ byte[] clientVerifyData, byte[] serverVerifyData,
+ boolean isDTLS) {
super(engine, context, enabledProtocols, true, true,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
- clientVerifyData, serverVerifyData);
+ clientVerifyData, serverVerifyData, isDTLS);
}
/*
* This routine handles all the client side handshake messages, one at
* a time. Given the message type (and in some cases the pending cipher
@@ -189,33 +199,51 @@
* is processed, and writes responses as needed using the connection
* in the constructor.
*/
@Override
void processMessage(byte type, int messageLen) throws IOException {
- if (state >= type
- && (type != HandshakeMessage.ht_hello_request)) {
- throw new SSLProtocolException(
- "Handshake message sequence violation, " + type);
- }
+ // check the handshake state
+ handshakeState.check(type);
switch (type) {
case HandshakeMessage.ht_hello_request:
- this.serverHelloRequest(new HelloRequest(input));
+ HelloRequest helloRequest = new HelloRequest(input);
+ handshakeState.update(helloRequest, resumingSession);
+ this.serverHelloRequest(helloRequest);
break;
+ case HandshakeMessage.ht_hello_verify_request:
+ if (!isDTLS) {
+ throw new SSLProtocolException(
+ "hello_verify_request is not a SSL/TLS handshake message");
+ }
+
+ HelloVerifyRequest helloVerifyRequest =
+ new HelloVerifyRequest(input, messageLen);
+ handshakeState.update(helloVerifyRequest, resumingSession);
+ this.helloVerifyRequest(helloVerifyRequest);
+ break;
+
case HandshakeMessage.ht_server_hello:
- this.serverHello(new ServerHello(input, messageLen));
+ ServerHello serverHello = new ServerHello(input, messageLen);
+ this.serverHello(serverHello);
+
+ // This handshake state update needs the resumingSession value
+ // set by serverHello().
+ handshakeState.update(serverHello, resumingSession);
break;
case HandshakeMessage.ht_certificate:
if (keyExchange == K_DH_ANON || keyExchange == K_ECDH_ANON
|| keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) {
fatalSE(Alerts.alert_unexpected_message,
"unexpected server cert chain");
// NOTREACHED
}
- this.serverCertificate(new CertificateMsg(input));
+ CertificateMsg certificateMsg = new CertificateMsg(input);
+ handshakeState.update(certificateMsg, resumingSession);
+ this.serverCertificate(certificateMsg);
serverKey =
session.getPeerCertificates()[0].getPublicKey();
break;
case HandshakeMessage.ht_server_key_exchange:
@@ -247,45 +275,56 @@
" when the public key in the server certificate" +
" is less than or equal to 512 bits in length");
}
try {
- this.serverKeyExchange(new RSA_ServerKeyExchange(input));
+ RSA_ServerKeyExchange rsaSrvKeyExchange =
+ new RSA_ServerKeyExchange(input);
+ handshakeState.update(rsaSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(rsaSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_DH_ANON:
try {
- this.serverKeyExchange(new DH_ServerKeyExchange(
- input, protocolVersion));
+ DH_ServerKeyExchange dhSrvKeyExchange =
+ new DH_ServerKeyExchange(input, protocolVersion);
+ handshakeState.update(dhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(dhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_DHE_DSS:
case K_DHE_RSA:
try {
- this.serverKeyExchange(new DH_ServerKeyExchange(
+ DH_ServerKeyExchange dhSrvKeyExchange =
+ new DH_ServerKeyExchange(
input, serverKey,
clnt_random.random_bytes, svr_random.random_bytes,
messageLen,
- localSupportedSignAlgs, protocolVersion));
+ localSupportedSignAlgs, protocolVersion);
+ handshakeState.update(dhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(dhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_ECDHE_ECDSA:
case K_ECDHE_RSA:
case K_ECDH_ANON:
try {
- this.serverKeyExchange(new ECDH_ServerKeyExchange
+ ECDH_ServerKeyExchange ecdhSrvKeyExchange =
+ new ECDH_ServerKeyExchange
(input, serverKey, clnt_random.random_bytes,
svr_random.random_bytes,
- localSupportedSignAlgs, protocolVersion));
+ localSupportedSignAlgs, protocolVersion);
+ handshakeState.update(ecdhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(ecdhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_RSA:
case K_DH_RSA:
case K_DH_DSS:
@@ -318,12 +357,13 @@
}
certRequest = new CertificateRequest(input, protocolVersion);
if (debug != null && Debug.isOn("handshake")) {
certRequest.print(System.out);
}
+ handshakeState.update(certRequest, resumingSession);
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
Collection<SignatureAndHashAlgorithm> peerSignAlgs =
certRequest.getSignAlgorithms();
if (peerSignAlgs == null || peerSignAlgs.isEmpty()) {
throw new SSLHandshakeException(
"No peer supported signature algorithms");
@@ -343,38 +383,29 @@
}
break;
case HandshakeMessage.ht_server_hello_done:
- this.serverHelloDone(new ServerHelloDone(input));
+ ServerHelloDone serverHelloDone = new ServerHelloDone(input);
+ handshakeState.update(serverHelloDone, resumingSession);
+ this.serverHelloDone(serverHelloDone);
+
break;
case HandshakeMessage.ht_finished:
- // A ChangeCipherSpec record must have been received prior to
- // reception of the Finished message (RFC 5246, 7.4.9).
- if (!receivedChangeCipherSpec()) {
- fatalSE(Alerts.alert_handshake_failure,
- "Received Finished message before ChangeCipherSpec");
- }
+ Finished serverFinished =
+ new Finished(protocolVersion, input, cipherSuite);
+ handshakeState.update(serverFinished, resumingSession);
+ this.serverFinished(serverFinished);
- this.serverFinished(
- new Finished(protocolVersion, input, cipherSuite));
break;
default:
throw new SSLProtocolException(
"Illegal client handshake msg, " + type);
}
-
- //
- // Move state machine forward if the message handling
- // code didn't already do so
- //
- if (state < type) {
- state = type;
}
- }
/*
* Used by the server to kickstart negotiations -- this requests a
* "client hello" to renegotiate current cipher specs (e.g. maybe lots
* of data has been encrypted with the same keys, or the server needs
@@ -387,14 +418,14 @@
//
// Could be (e.g. at connection setup) that we already
// sent the "client hello" but the server's not seen it.
//
- if (state < HandshakeMessage.ht_client_hello) {
+ if (!clientHelloDelivered) {
if (!secureRenegotiation && !allowUnsafeRenegotiation) {
// renegotiation is not allowed.
- if (activeProtocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (activeProtocolVersion.useTLS10PlusSpec()) {
// response with a no_renegotiation warning,
warningSE(Alerts.alert_no_renegotiation);
// invalidate the handshake so that the caller can
// dispose this object.
@@ -426,11 +457,34 @@
kickstart();
}
}
}
+ private void helloVerifyRequest(
+ HelloVerifyRequest mesg) throws IOException {
+ if (debug != null && Debug.isOn("handshake")) {
+ mesg.print(System.out);
+ }
+
+ //
+ // Note that HelloVerifyRequest.server_version is used solely to
+ // indicate packet formatting, and not as part of version negotiation.
+ // Need not to check version values match for HelloVerifyRequest
+ // message.
+ //
+ initialClientHelloMsg.cookie = mesg.cookie.clone();
+
+ if (debug != null && Debug.isOn("handshake")) {
+ initialClientHelloMsg.print(System.out);
+ }
+
+ // deliver the ClientHello message with cookie
+ initialClientHelloMsg.write(output);
+ handshakeState.update(initialClientHelloMsg, resumingSession);
+ }
+
/*
* Server chooses session parameters given options created by the
* client -- basically, cipher options, session id, and someday a
* set of compression options.
*
@@ -439,10 +493,13 @@
* can resume the pre-existing session we asked resume. The other
* is a more expensive "full" handshake, with key exchange and
* probably authentication getting done.
*/
private void serverHello(ServerHello mesg) throws IOException {
+ // Dispose the reserved ClientHello message (if exists).
+ initialClientHelloMsg = null;
+
serverKeyExchangeReceived = false;
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
}
@@ -534,11 +591,11 @@
fatalSE(Alerts.alert_illegal_parameter,
"Server selected improper ciphersuite " + mesg.cipherSuite);
}
setCipherSuite(mesg.cipherSuite);
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
handshakeHash.setFinishedAlg(cipherSuite.prfAlg.getPRFHashAlg());
}
if (mesg.compression_method != 0) {
fatalSE(Alerts.alert_illegal_parameter,
@@ -609,13 +666,12 @@
throw new SSLProtocolException
("Server resumed session with no subject");
}
}
- // looks fine; resume it, and update the state machine.
+ // looks fine; resume it.
resumingSession = true;
- state = HandshakeMessage.ht_finished - 1;
calculateConnectionKeys(session.getMasterSecret());
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Server resumed " + session);
}
} else {
@@ -625,10 +681,28 @@
throw new SSLException("New session creation is disabled");
}
}
}
+ // check the "max_fragment_length" extension
+ MaxFragmentLengthExtension maxFragLenExt = (MaxFragmentLengthExtension)
+ mesg.extensions.get(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+ if (maxFragLenExt != null) {
+ if ((requestedMFLength == -1) ||
+ maxFragLenExt.getMaxFragLen() != requestedMFLength) {
+ // If the client did not request this extension, or the
+ // response value is different from the length it requested,
+ // abort the handshake with a fatal illegal_parameter alert.
+ fatalSE(Alerts.alert_illegal_parameter,
+ "Failed to negotiate the max_fragment_length");
+ }
+ } else if (!resumingSession) {
+ // no "max_fragment_length" extension
+ requestedMFLength = -1;
+ } // Otherwise, using the value negotiated during the original
+ // session initiation
+
if (resumingSession && session != null) {
setHandshakeSessionSE(session);
// Reserve the handshake state if this is a session-resumption
// abbreviated initial handshake.
if (isInitialHandshake) {
@@ -655,10 +729,12 @@
// Create a new session, we need to do the full handshake
session = new SSLSessionImpl(protocolVersion, cipherSuite,
getLocalSupportedSignAlgs(),
mesg.sessionId, getHostSE(), getPortSE());
session.setRequestedServerNames(requestedServerNames);
+ session.setNegotiatedMaxFragSize(requestedMFLength);
+ session.setMaximumPacketSize(maximumPacketSize);
setHandshakeSessionSE(session);
if (debug != null && Debug.isOn("handshake")) {
System.out.println("** " + cipherSuite);
}
}
@@ -679,11 +755,10 @@
// NOTREACHED
}
ephemeralServerKey = mesg.getPublicKey();
}
-
/*
* Diffie-Hellman key exchange. We save the server public key and
* our own D-H algorithm object so we can defer key calculations
* until after we've sent the client key exchange message (which
* gives client and server some useful parallelism).
@@ -714,17 +789,10 @@
*/
private void serverHelloDone(ServerHelloDone mesg) throws IOException {
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
}
- /*
- * Always make sure the input has been digested before we
- * start emitting data, to ensure the hashes are correctly
- * computed for the Finished and CertificateVerify messages
- * which we send (here).
- */
- input.digestNow();
/*
* FIRST ... if requested, send an appropriate Certificate chain
* to authenticate the client, and remember the associated private
* key to sign the CertificateVerify message.
@@ -815,11 +883,11 @@
//
// No appropriate cert was found ... report this to the
// server. For SSLv3, send the no_certificate alert;
// TLS uses an empty cert chain instead.
//
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
m1 = new CertificateMsg(new X509Certificate [0]);
} else {
warningSE(Alerts.alert_no_certificate);
}
if (debug != null && Debug.isOn("handshake")) {
@@ -835,10 +903,11 @@
if (m1 != null) {
if (debug != null && Debug.isOn("handshake")) {
m1.print(System.out);
}
m1.write(output);
+ handshakeState.update(m1, resumingSession);
}
}
/*
* SECOND ... send the client key exchange message. The
@@ -998,22 +1067,20 @@
}
if (debug != null && Debug.isOn("handshake")) {
m2.print(System.out);
}
m2.write(output);
+ handshakeState.update(m2, resumingSession);
-
/*
* THIRD, send a "change_cipher_spec" record followed by the
* "Finished" message. We flush the messages we've queued up, to
* get concurrency between client and server. The concurrency is
* useful as we calculate the master secret, which is needed both
* to compute the "Finished" message, and to compute the keys used
* to protect all records following the change_cipher_spec.
*/
-
- output.doHashes();
output.flush();
/*
* We deferred calculating the master secret and this connection's
* keying data; we do it now. Deferring this calculation is good
@@ -1067,11 +1134,11 @@
*/
if (signingKey != null) {
CertificateVerify m3;
try {
SignatureAndHashAlgorithm preferableSignatureAlgorithm = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
peerSupportedSignAlgs, signingKey.getAlgorithm(),
signingKey);
@@ -1101,17 +1168,21 @@
}
if (debug != null && Debug.isOn("handshake")) {
m3.print(System.out);
}
m3.write(output);
- output.doHashes();
+ handshakeState.update(m3, resumingSession);
+ output.flush();
}
/*
* OK, that's that!
*/
sendChangeCipherAndFinish(false);
+
+ // expecting the final ChangeCipherSpec and Finished messages
+ expectingFinishFlightSE();
}
/*
* "Finished" is the last handshake message sent. If we got this
@@ -1156,12 +1227,13 @@
* In any case, update the session cache. We're done handshaking,
* so there are no threats any more associated with partially
* completed handshakes.
*/
if (resumingSession) {
- input.digestNow();
sendChangeCipherAndFinish(true);
+ } else {
+ handshakeFinished = true;
}
session.setLastAccessedTime(System.currentTimeMillis());
if (!resumingSession) {
if (session.isRejoinable()) {
@@ -1186,10 +1258,14 @@
* the short one, we've already seen the server's Finished; in the
* long one, we wait for it now.
*/
private void sendChangeCipherAndFinish(boolean finishedTag)
throws IOException {
+
+ // Reload if this message has been reserved.
+ handshakeHash.reload();
+
Finished mesg = new Finished(protocolVersion, handshakeHash,
Finished.CLIENT, session.getMasterSecret(), cipherSuite);
/*
* Send the change_cipher_spec message, then the Finished message
@@ -1203,17 +1279,10 @@
* save client verify data for secure renegotiation
*/
if (secureRenegotiation) {
clientVerifyData = mesg.getVerifyData();
}
-
- /*
- * Update state machine so server MUST send 'finished' next.
- * (In "long" handshake case; in short case, we're responding
- * to its message.)
- */
- state = HandshakeMessage.ht_finished - 1;
}
/*
* Returns a ClientHello message to kickstart renegotiations
@@ -1359,14 +1428,14 @@
// }
// create the ClientHello message
ClientHello clientHelloMessage = new ClientHello(
sslContext.getSecureRandom(), maxProtocolVersion,
- sessionId, cipherSuites);
+ sessionId, cipherSuites, isDTLS);
// add signature_algorithm extension
- if (maxProtocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (maxProtocolVersion.useTLS12PlusSpec()) {
// we will always send the signature_algorithm extension
Collection<SignatureAndHashAlgorithm> localSignAlgs =
getLocalSupportedSignAlgs();
if (localSignAlgs.isEmpty()) {
throw new SSLHandshakeException(
@@ -1387,10 +1456,41 @@
if (!requestedServerNames.isEmpty()) {
clientHelloMessage.addSNIExtension(requestedServerNames);
}
}
+ // add max_fragment_length extension
+ if (enableMFLExtension) {
+ if (session != null) {
+ // The same extension should be sent for resumption.
+ requestedMFLength = session.getNegotiatedMaxFragSize();
+ } else if (maximumPacketSize != 0) {
+ // Maybe we can calculate the fragment size more accurate
+ // by condering the enabled cipher suites in the future.
+ requestedMFLength = maximumPacketSize;
+ if (isDTLS) {
+ requestedMFLength -= DTLSRecord.maxPlaintextPlusSize;
+ } else {
+ requestedMFLength -= SSLRecord.maxPlaintextPlusSize;
+ }
+ } else {
+ // Need no max_fragment_length extension.
+ requestedMFLength = -1;
+ }
+
+ if ((requestedMFLength > 0) &&
+ MaxFragmentLengthExtension.needFragLenNego(requestedMFLength)) {
+
+ requestedMFLength =
+ MaxFragmentLengthExtension.getValidMaxFragLen(
+ requestedMFLength);
+ clientHelloMessage.addMFLExtension(requestedMFLength);
+ } else {
+ requestedMFLength = -1;
+ }
+ }
+
// reset the client random cookie
clnt_random = clientHelloMessage.clnt_random;
/*
* need to set the renegotiation_info extension for:
@@ -1401,10 +1501,15 @@
if (secureRenegotiation ||
!cipherSuites.contains(CipherSuite.C_SCSV)) {
clientHelloMessage.addRenegotiationInfoExtension(clientVerifyData);
}
+ if (isDTLS) {
+ // Cookie exchange need to reserve the initial ClientHello message.
+ initialClientHelloMsg = clientHelloMessage;
+ }
+
return clientHelloMessage;
}
/*
* Fault detected during handshake.