/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import java.util.HashMap;
import javax.net.ssl.SSLProtocolException;
import static sun.security.ssl.CipherSuite.KeyExchange;
import static sun.security.ssl.CipherSuite.KeyExchange.*;
import static sun.security.ssl.HandshakeStateManager.HandshakeState.*;
import static sun.security.ssl.HandshakeMessage.*;
/*
* Handshake state manager.
*
* Messages flow for a full handshake:
*
* - -
* | HelloRequest (No.0, RFC 5246) [*] |
* | <-------------------------------------------- |
* | |
* | ClientHello (No.1, RFC 5246) |
* | --------------------------------------------> |
* | |
* C | ServerHello (No.2, RFC 5246) | S
* L | SupplementalData (No.23, RFC4680) [*] | E
* I | Certificate (No.11, RFC 5246) [*] | R
* E | CertificateStatus (No.22, RFC 6066) [*] | V
* N | ServerKeyExchange (No.12, RFC 5246) [*] | E
* T | CertificateRequest (No.13, RFC 5246) [*] | R
* | ServerHelloDone (No.14, RFC 5246) |
* | <-------------------------------------------- |
* | |
* | SupplementalData (No.23, RFC4680) [*] |
* | Certificate (No.11, RFC 5246) [*] Or |
* | CertificateURL (No.21, RFC6066) [*] |
* | ClientKeyExchange (No.16, RFC 5246) |
* | CertificateVerify (No.15, RFC 5246) [*] |
* | [ChangeCipherSpec] (RFC 5246) |
* | Finished (No.20, RFC 5246) |
* | --------------------------------------------> |
* | |
* | NewSessionTicket (No.4, RFC4507) [*] |
* | [ChangeCipherSpec] (RFC 5246) |
* | Finished (No.20, RFC 5246) |
* | <-------------------------------------------- |
* - -
* [*] Indicates optional or situation-dependent messages that are not
* always sent.
*
* Message flow for an abbreviated handshake:
* - -
* | ClientHello (No.1, RFC 5246) |
* | --------------------------------------------> |
* | |
* C | ServerHello (No.2, RFC 5246) | S
* L | NewSessionTicket (No.4, RFC4507) [*] | E
* I | [ChangeCipherSpec] (RFC 5246) | R
* E | Finished (No.20, RFC 5246) | V
* N | <-------------------------------------------- | E
* T | | R
* | [ChangeCipherSpec] (RFC 5246) |
* | Finished (No.20, RFC 5246) |
* | --------------------------------------------> |
* - -
*
*
* State machine of handshake states:
*
* +--------------+
* START -----> | HelloRequest |
* | +--------------+
* | |
* v v
* +---------------------+ --> +---------------------+
* | ClientHello | | HelloVerifyRequest |
* +---------------------+ <-- +---------------------+
* |
* |
* =========================================================================
* |
* v
* +---------------------+
* | ServerHello | ----------------------------------+------+
* +---------------------+ --> +-------------------------+ | |
* | | Server SupplementalData | | |
* | +-------------------------+ | |
* | | | |
* v v | |
* +---------------------+ | |
* +---- | Server Certificate | | |
* | +---------------------+ | |
* | | | |
* | | +--------------------+ | |
* | +-> | CertificateStatus | | |
* | | +--------------------+ v |
* | | | | +--------------------+ |
* | v v +--> | ServerKeyExchange | |
* | +---------------------+ | +--------------------+ |
* | | CertificateRequest | | | |
* | +---------------------+ <-+---------+ |
* | | | | |
* v v | | |
* +---------------------+ <-------+ | |
* | ServerHelloDone | <-----------------+ |
* +---------------------+ |
* | | |
* | | |
* | | |
* =========================================================================
* | | |
* | v |
* | +-------------------------+ |
* | | Client SupplementalData | --------------+ |
* | +-------------------------+ | |
* | | | |
* | v | |
* | +--------------------+ | |
* +-> | Client Certificate | ALT. | |
* | +--------------------+----------------+ | |
* | | CertificateURL | | |
* | +----------------+ | |
* v | |
* +-------------------+ <------------------------+ |
* | ClientKeyExchange | |
* +-------------------+ |
* | | |
* | v |
* | +-------------------+ |
* | | CertificateVerify | |
* | +-------------------+ |
* | | |
* v v |
* +-------------------------+ |
* | Client ChangeCipherSpec | <---------------+ |
* +-------------------------+ | |
* | | |
* v | |
* +-----------------+ (abbreviated) | |
* | Client Finished | -------------> END | |
* +-----------------+ (Abbreviated handshake) | |
* | | |
* | (full) | |
* | | |
* ================================ | |
* | | |
* | ================================
* | | |
* v | |
* +------------------+ | (abbreviated) |
* | NewSessionTicket | <--------------------------------+
* +------------------+ | |
* | | |
* v | |
* +-------------------------+ | (abbreviated) |
* | Server ChangeCipherSpec | <-------------------------------------+
* +-------------------------+ |
* | |
* v |
* +-----------------+ (abbreviated) |
* | Server Finished | -------------------------+
* +-----------------+
* | (full)
* v
* END (Full handshake)
*
*
* The scenarios of the use of this class:
* 1. Create an instance of HandshakeStateManager during the initializtion
* handshake.
* 2. If receiving a handshake message, call HandshakeStateManager.check()
* to make sure that the message is of the expected handshake type. And
* then call HandshakeStateManager.update() in case handshake states may
* be impacted by this new incoming handshake message.
* 3. On delivering a handshake message, call HandshakeStateManager.update()
* in case handshake states may by thie new outgoing handshake message.
* 4. On receiving and delivering ChangeCipherSpec message, call
* HandshakeStateManager.changeCipherSpec() to check the present sequence
* of this message, and update the states if necessary.
*/
final class HandshakeStateManager {
// upcoming handshake states.
private LinkedList<HandshakeState> upcomingStates;
private LinkedList<HandshakeState> alternatives;
private static final boolean debugIsOn;
private static final HashMap<Byte, String> handshakeTypes;
static {
debugIsOn = (Handshaker.debug != null) &&
Debug.isOn("handshake") && Debug.isOn("verbose");
handshakeTypes = new HashMap<>(8);
handshakeTypes.put(ht_hello_request, "hello_request");
handshakeTypes.put(ht_client_hello, "client_hello");
handshakeTypes.put(ht_server_hello, "server_hello");
handshakeTypes.put(ht_certificate, "certificate");
handshakeTypes.put(ht_server_key_exchange, "server_key_exchange");
handshakeTypes.put(ht_server_hello_done, "server_hello_done");
handshakeTypes.put(ht_certificate_verify, "certificate_verify");
handshakeTypes.put(ht_client_key_exchange, "client_key_exchange");
handshakeTypes.put(ht_finished, "finished");
}
HandshakeStateManager() {
this.upcomingStates = new LinkedList<>();
this.alternatives = new LinkedList<>();
}
//
// enumation of handshake type
//
static enum HandshakeState {
HS_HELLO_REQUEST(
"hello_request",
HandshakeMessage.ht_hello_request),
HS_CLIENT_HELLO(
"client_hello",
HandshakeMessage.ht_client_hello),
HS_SERVER_HELLO(
"server_hello",
HandshakeMessage.ht_server_hello),
HS_SERVER_CERTIFICATE(
"server certificate",
HandshakeMessage.ht_certificate),
HS_SERVER_KEY_EXCHANGE(
"server_key_exchange",
HandshakeMessage.ht_server_key_exchange, true),
HS_CERTIFICATE_REQUEST(
"certificate_request",
HandshakeMessage.ht_certificate_request, true),
HS_SERVER_HELLO_DONE(
"server_hello_done",
HandshakeMessage.ht_server_hello_done),
HS_CLIENT_CERTIFICATE(
"client certificate",
HandshakeMessage.ht_certificate, true),
HS_CLIENT_KEY_EXCHANGE(
"client_key_exchange",
HandshakeMessage.ht_client_key_exchange),
HS_CERTIFICATE_VERIFY(
"certificate_verify",
HandshakeMessage.ht_certificate_verify, true),
HS_CLIENT_CHANGE_CIPHER_SPEC(
"client change_cipher_spec",
HandshakeMessage.ht_not_applicable),
HS_CLIENT_FINISHED(
"client finished",
HandshakeMessage.ht_finished),
HS_SERVER_CHANGE_CIPHER_SPEC(
"server change_cipher_spec",
HandshakeMessage.ht_not_applicable),
HS_SERVER_FINISHED(
"server finished",
HandshakeMessage.ht_finished);
final String description;
final byte handshakeType;
final boolean isOptional;
HandshakeState(String description, byte handshakeType) {
this.description = description;
this.handshakeType = handshakeType;
this.isOptional = false;
}
HandshakeState(String description,
byte handshakeType, boolean isOptional) {
this.description = description;
this.handshakeType = handshakeType;
this.isOptional = isOptional;
}
public String toString() {
return description + "[" + handshakeType + "]" +
(isOptional ? "(optional)" : "");
}
}
boolean isEmpty() {
return upcomingStates.isEmpty();
}
List<Byte> check(byte handshakeType) throws SSLProtocolException {
List<Byte> ignoredOptional = new LinkedList<>();
String exceptionMsg =
"Handshake message sequence violation, " + handshakeType;
if (debugIsOn) {
System.out.println(
"check handshake state: " + toString(handshakeType));
}
if (upcomingStates.isEmpty()) {
// Is it a kickstart message?
if ((handshakeType != HandshakeMessage.ht_hello_request) &&
(handshakeType != HandshakeMessage.ht_client_hello)) {
throw new SSLProtocolException(
"Handshake message sequence violation, " + handshakeType);
}
// It is a kickstart message.
return Collections.emptyList();
}
// Ignore the checking for HelloRequest messages as they
// may be sent by the server at any time.
if (handshakeType == HandshakeMessage.ht_hello_request) {
return Collections.emptyList();
}
for (HandshakeState handshakeState : upcomingStates) {
if (handshakeState.handshakeType == handshakeType) {
// It's the expected next handshake type.
return ignoredOptional;
}
if (handshakeState.isOptional) {
ignoredOptional.add(handshakeState.handshakeType);
continue;
} else {
for (HandshakeState alternative : alternatives) {
if (alternative.handshakeType == handshakeType) {
return ignoredOptional;
}
if (alternative.isOptional) {
continue;
} else {
throw new SSLProtocolException(exceptionMsg);
}
}
}
throw new SSLProtocolException(exceptionMsg);
}
// Not an expected Handshake message.
throw new SSLProtocolException(
"Handshake message sequence violation, " + handshakeType);
}
void update(HandshakeMessage handshakeMessage,
boolean isAbbreviated) throws SSLProtocolException {
byte handshakeType = (byte)handshakeMessage.messageType();
String exceptionMsg =
"Handshake message sequence violation, " + handshakeType;
if (debugIsOn) {
System.out.println(
"update handshake state: " + toString(handshakeType));
}
boolean hasPresentState = false;
switch (handshakeType) {
case HandshakeMessage.ht_hello_request:
//
// State machine:
// PRESENT: START
// TO : ClientHello
//
// No old state to update.
// Add the upcoming states.
if (!upcomingStates.isEmpty()) {
// A ClientHello message should be followed.
upcomingStates.add(HS_CLIENT_HELLO);
} // Otherwise, ignore this HelloRequest message.
break;
case HandshakeMessage.ht_client_hello:
//
// State machine:
// PRESENT: START
// HS_CLIENT_HELLO
// TO : HS_SERVER_HELLO
//
// Check and update the present state.
if (!upcomingStates.isEmpty()) {
// The current state should be HS_CLIENT_HELLO.
HandshakeState handshakeState = upcomingStates.pop();
if (handshakeState != HS_CLIENT_HELLO) {
throw new SSLProtocolException(exceptionMsg);
}
}
// Add the upcoming states.
ClientHello clientHello = (ClientHello)handshakeMessage;
upcomingStates.add(HS_SERVER_HELLO);
break;
case HandshakeMessage.ht_server_hello:
//
// State machine:
// PRESENT: HS_SERVER_HELLO
// TO :
// Full handshake state stacks
// (ServerHello Flight)
// HS_SERVER_SUPPLEMENTAL_DATA [optional]
// --> HS_SERVER_CERTIFICATE [optional]
// --> HS_CERTIFICATE_STATUS [optional]
// --> HS_SERVER_KEY_EXCHANGE [optional]
// --> HS_CERTIFICATE_REQUEST [optional]
// --> HS_SERVER_HELLO_DONE
// (Client ClientKeyExchange Flight)
// --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
// --> HS_CLIENT_CERTIFICATE or
// HS_CERTIFICATE_URL
// --> HS_CLIENT_KEY_EXCHANGE
// --> HS_CERTIFICATE_VERIFY [optional]
// --> HS_CLIENT_CHANGE_CIPHER_SPEC
// --> HS_CLIENT_FINISHED
// (Server Finished Flight)
// --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
//
// Abbreviated handshake state stacks
// (Server Finished Flight)
// HS_NEW_SESSION_TICKET
// --> HS_SERVER_CHANGE_CIPHER_SPEC
// --> HS_SERVER_FINISHED
// (Client Finished Flight)
// --> HS_CLIENT_CHANGE_CIPHER_SPEC
// --> HS_CLIENT_FINISHED
//
// Note that this state may have an alternative option.
// Check and update the present state.
if (!upcomingStates.isEmpty()) {
// The current state should be HS_SERVER_HELLO
HandshakeState handshakeState = upcomingStates.pop();
HandshakeState alternative = null;
if (!alternatives.isEmpty()) {
alternative = alternatives.pop();
}
if ((handshakeState != HS_SERVER_HELLO) &&
(alternative != HS_SERVER_HELLO)) {
throw new SSLProtocolException(exceptionMsg);
}
} else {
// No present state.
throw new SSLProtocolException(exceptionMsg);
}
// Add the upcoming states.
ServerHello serverHello = (ServerHello)handshakeMessage;
HelloExtensions hes = serverHello.extensions;
// Not support SessionTicket extension yet.
//
// boolean hasSessionTicketExt =
// (hes.get(HandshakeMessage.ht_new_session_ticket) != null);
if (isAbbreviated) {
// Not support SessionTicket extension yet.
//
// // Mandatory NewSessionTicket message
// if (hasSessionTicketExt) {
// upcomingStates.add(HS_NEW_SESSION_TICKET);
// }
// Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_SERVER_FINISHED);
// Mandatory client ChangeCipherSpec and Finished messages
upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_CLIENT_FINISHED);
} else {
// Not support SupplementalData extension yet.
//
// boolean hasSupplementalDataExt =
// (hes.get(HandshakeMessage.ht_supplemental_data) != null);
// Not support CertificateURL extension yet.
//
// boolean hasCertificateUrlExt =
// (hes.get(ExtensionType EXT_CLIENT_CERTIFICATE_URL)
// != null);
// Not support SupplementalData extension yet.
//
// // Optional SupplementalData message
// if (hasSupplementalDataExt) {
// upcomingStates.add(HS_SERVER_SUPPLEMENTAL_DATA);
// }
// Need server Certificate message or not?
KeyExchange keyExchange = serverHello.cipherSuite.keyExchange;
if ((keyExchange != K_KRB5) &&
(keyExchange != K_KRB5_EXPORT) &&
(keyExchange != K_DH_ANON) &&
(keyExchange != K_ECDH_ANON)) {
// Mandatory Certificate message
upcomingStates.add(HS_SERVER_CERTIFICATE);
}
// Need ServerKeyExchange message or not?
if ((keyExchange == K_RSA_EXPORT) ||
(keyExchange == K_DHE_RSA) ||
(keyExchange == K_DHE_DSS) ||
(keyExchange == K_DH_ANON) ||
(keyExchange == K_ECDHE_RSA) ||
(keyExchange == K_ECDHE_ECDSA) ||
(keyExchange == K_ECDH_ANON)) {
// Optional ServerKeyExchange message
upcomingStates.add(HS_SERVER_KEY_EXCHANGE);
}
// Optional CertificateRequest message
upcomingStates.add(HS_CERTIFICATE_REQUEST);
// Mandatory ServerHelloDone message
upcomingStates.add(HS_SERVER_HELLO_DONE);
// Not support SupplementalData extension yet.
//
// // Optional SupplementalData message
// if (hasSupplementalDataExt) {
// upcomingStates.add(HS_CLIENT_SUPPLEMENTAL_DATA);
// }
// Optional client Certificate message
upcomingStates.add(HS_CLIENT_CERTIFICATE);
// Not support CertificateURL extension yet.
//
// // Alternative CertificateURL message, optional too.
// //
// // Please put CertificateURL rather than Certificate
// // message in the alternatives list. So that we can
// // simplify the process of this alternative pair later.
// if (hasCertificateUrlExt) {
// alternatives.add(HS_CERTIFICATE_URL);
// }
// Mandatory ClientKeyExchange message
upcomingStates.add(HS_CLIENT_KEY_EXCHANGE);
// Optional CertificateVerify message
upcomingStates.add(HS_CERTIFICATE_VERIFY);
// Mandatory client ChangeCipherSpec and Finished messages
upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_CLIENT_FINISHED);
// Not support SessionTicket extension yet.
//
// // Mandatory NewSessionTicket message
// if (hasSessionTicketExt) {
// upcomingStates.add(HS_NEW_SESSION_TICKET);
// }
// Mandatory server ChangeCipherSpec and Finished messages
upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
upcomingStates.add(HS_SERVER_FINISHED);
}
break;
case HandshakeMessage.ht_certificate:
//
// State machine:
// PRESENT: HS_CERTIFICATE_URL or
// HS_CLIENT_CERTIFICATE
// TO : HS_CLIENT_KEY_EXCHANGE
//
// Or
//
// PRESENT: HS_SERVER_CERTIFICATE
// TO : HS_CERTIFICATE_STATUS [optional]
// HS_SERVER_KEY_EXCHANGE [optional]
// HS_CERTIFICATE_REQUEST [optional]
// HS_SERVER_HELLO_DONE
//
// Note that this state may have an alternative option.
// Check and update the present state.
while (!upcomingStates.isEmpty()) {
HandshakeState handshakeState = upcomingStates.pop();
if (handshakeState.handshakeType == handshakeType) {
hasPresentState = true;
// The current state should be HS_CLIENT_CERTIFICATE or
// HS_SERVER_CERTIFICATE.
//
// Note that we won't put HS_CLIENT_CERTIFICATE into
// the alternative list.
if ((handshakeState != HS_CLIENT_CERTIFICATE) &&
(handshakeState != HS_SERVER_CERTIFICATE)) {
throw new SSLProtocolException(exceptionMsg);
}
// Is it an expected client Certificate message?
boolean isClientMessage = false;
if (!upcomingStates.isEmpty()) {
// If the next expected message is ClientKeyExchange,
// this one should be an expected client Certificate
// message.
HandshakeState nextState = upcomingStates.getFirst();
if (nextState == HS_CLIENT_KEY_EXCHANGE) {
isClientMessage = true;
}
}
if (isClientMessage) {
if (handshakeState != HS_CLIENT_CERTIFICATE) {
throw new SSLProtocolException(exceptionMsg);
}
// Not support CertificateURL extension yet.
/*******************************************
// clear up the alternatives list
if (!alternatives.isEmpty()) {
HandshakeState alternative = alternatives.pop();
if (alternative != HS_CERTIFICATE_URL) {
throw new SSLProtocolException(exceptionMsg);
}
}
********************************************/
} else {
if ((handshakeState != HS_SERVER_CERTIFICATE)) {
throw new SSLProtocolException(exceptionMsg);
}
}
break;
} else if (!handshakeState.isOptional) {
throw new SSLProtocolException(exceptionMsg);
} // Otherwise, looking for next state track.
}
// No present state.
if (!hasPresentState) {
throw new SSLProtocolException(exceptionMsg);
}
// no new upcoming states.
break;
default:
// Check and update the present state.
while (!upcomingStates.isEmpty()) {
HandshakeState handshakeState = upcomingStates.pop();
if (handshakeState.handshakeType == handshakeType) {
hasPresentState = true;
break;
} else if (!handshakeState.isOptional) {
throw new SSLProtocolException(exceptionMsg);
} // Otherwise, looking for next state track.
}
// No present state.
if (!hasPresentState) {
throw new SSLProtocolException(exceptionMsg);
}
// no new upcoming states.
}
if (debugIsOn) {
for (HandshakeState handshakeState : upcomingStates) {
System.out.println(
"upcoming handshake states: " + handshakeState);
}
for (HandshakeState handshakeState : alternatives) {
System.out.println(
"upcoming handshake alternative state: " + handshakeState);
}
}
}
void changeCipherSpec(boolean isInput,
boolean isClient) throws SSLProtocolException {
if (debugIsOn) {
System.out.println(
"update handshake state: change_cipher_spec");
}
String exceptionMsg = "ChangeCipherSpec message sequence violation";
HandshakeState expectedState;
if ((isClient && isInput) || (!isClient && !isInput)) {
expectedState = HS_SERVER_CHANGE_CIPHER_SPEC;
} else {
expectedState = HS_CLIENT_CHANGE_CIPHER_SPEC;
}
boolean hasPresentState = false;
// Check and update the present state.
while (!upcomingStates.isEmpty()) {
HandshakeState handshakeState = upcomingStates.pop();
if (handshakeState == expectedState) {
hasPresentState = true;
break;
} else if (!handshakeState.isOptional) {
throw new SSLProtocolException(exceptionMsg);
} // Otherwise, looking for next state track.
}
// No present state.
if (!hasPresentState) {
throw new SSLProtocolException(exceptionMsg);
}
// no new upcoming states.
if (debugIsOn) {
for (HandshakeState handshakeState : upcomingStates) {
System.out.println(
"upcoming handshake states: " + handshakeState);
}
for (HandshakeState handshakeState : alternatives) {
System.out.println(
"upcoming handshake alternative state: " + handshakeState);
}
}
}
private static String toString(byte handshakeType) {
String s = handshakeTypes.get(handshakeType);
if (s == null) {
s = "unknown";
}
return (s + "[" + handshakeType + "]");
}
}