< prev index next >
src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java
Print this page
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -24,121 +24,323 @@
*/
package sun.security.ssl;
import java.io.IOException;
-import javax.net.ssl.SSLProtocolException;
import java.security.MessageDigest;
import java.security.SecureRandom;
+import java.util.Arrays;
+import static sun.security.ssl.ClientHello.ClientHelloMessage;
-import sun.security.ssl.HandshakeMessage.ClientHello;
-
-/*
- * HelloVerifyRequest cookie manager
+/**
+ * (D)TLS handshake cookie manager
*/
-final class HelloCookieManager {
- // the cookie secret life time
- private static long COOKIE_TIMING_WINDOW = 3600000; // in milliseconds
- private static int COOKIE_MAX_LENGTH_DTLS10 = 32; // 32 bytes
- private static int COOKIE_MAX_LENGTH_DTLS12 = 0xFF; // 2^8 -1 bytes
-
- private final SecureRandom secureRandom;
- private final MessageDigest cookieDigest;
+class HelloCookieManager {
+ final SecureRandom secureRandom;
- private int cookieVersion; // allow to wrap
- private long secretLifetime;
- private byte[] cookieSecret;
-
- private int prevCookieVersion;
- private byte[] prevCookieSecret;
+ private volatile D10HelloCookieManager d10HelloCookieManager;
+ private volatile D13HelloCookieManager d13HelloCookieManager;
+ private volatile T13HelloCookieManager t13HelloCookieManager;
HelloCookieManager(SecureRandom secureRandom) {
this.secureRandom = secureRandom;
- this.cookieDigest = JsseJce.getMessageDigest("SHA-256");
+ }
- this.cookieVersion = secureRandom.nextInt();
- this.secretLifetime = 0;
- this.cookieSecret = null;
+ HelloCookieManager valueOf(ProtocolVersion protocolVersion) {
+ if (protocolVersion.isDTLS) {
+ if (protocolVersion.useTLS13PlusSpec()) {
+ if (d13HelloCookieManager != null) {
+ return d13HelloCookieManager;
+ }
- this.prevCookieVersion = 0;
- this.prevCookieSecret = null;
+ synchronized (this) {
+ if (d13HelloCookieManager == null) {
+ d13HelloCookieManager =
+ new D13HelloCookieManager(secureRandom);
+ }
}
- // Used by server side to generate cookies in HelloVerifyRequest message.
- synchronized byte[] getCookie(ClientHello clientHelloMsg) {
- if (secretLifetime < System.currentTimeMillis()) {
- if (cookieSecret != null) {
- prevCookieVersion = cookieVersion;
- prevCookieSecret = cookieSecret.clone();
+ return d13HelloCookieManager;
} else {
- cookieSecret = new byte[32];
+ if (d10HelloCookieManager != null) {
+ return d10HelloCookieManager;
}
- cookieVersion++;
+ synchronized (this) {
+ if (d10HelloCookieManager == null) {
+ d10HelloCookieManager =
+ new D10HelloCookieManager(secureRandom);
+ }
+ }
+
+ return d10HelloCookieManager;
+ }
+ } else {
+ if (protocolVersion.useTLS13PlusSpec()) {
+ if (t13HelloCookieManager != null) {
+ return t13HelloCookieManager;
+ }
+
+ synchronized (this) {
+ if (t13HelloCookieManager == null) {
+ t13HelloCookieManager =
+ new T13HelloCookieManager(secureRandom);
+ }
+ }
+
+ return t13HelloCookieManager;
+ }
+ }
+
+ return null;
+ }
+
+ byte[] createCookie(ConnectionContext context,
+ ClientHelloMessage clientHello) throws IOException {
+ throw new UnsupportedOperationException(
+ "Not yet supported handshake cookie manager");
+ }
+
+ boolean isCookieValid(ConnectionContext context,
+ ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+ throw new UnsupportedOperationException(
+ "Not yet supported handshake cookie manager");
+ }
+
+ // DTLS 1.0/1.2
+ private static final
+ class D10HelloCookieManager extends HelloCookieManager {
+ private int cookieVersion; // allow to wrap, version + sequence
+ private byte[] cookieSecret;
+ private byte[] legacySecret;
+
+ D10HelloCookieManager(SecureRandom secureRandom) {
+ super(secureRandom);
+
+ this.cookieVersion = secureRandom.nextInt();
+ this.cookieSecret = new byte[32];
+ this.legacySecret = new byte[32];
+
secureRandom.nextBytes(cookieSecret);
- secretLifetime = System.currentTimeMillis() + COOKIE_TIMING_WINDOW;
+ System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
}
- clientHelloMsg.updateHelloCookie(cookieDigest);
- byte[] cookie = cookieDigest.digest(cookieSecret); // 32 bytes
- cookie[0] = (byte)((cookieVersion >> 24) & 0xFF);
- cookie[1] = (byte)((cookieVersion >> 16) & 0xFF);
- cookie[2] = (byte)((cookieVersion >> 8) & 0xFF);
- cookie[3] = (byte)(cookieVersion & 0xFF);
+ @Override
+ byte[] createCookie(ConnectionContext context,
+ ClientHelloMessage clientHello) throws IOException {
+ int version;
+ byte[] secret;
- return cookie;
+ synchronized (this) {
+ version = cookieVersion;
+ secret = cookieSecret;
+
+ // the cookie secret usage limit is 2^24
+ if ((cookieVersion & 0xFFFFFF) == 0) { // reset the secret
+ System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
+ secureRandom.nextBytes(cookieSecret);
+ }
+
+ cookieVersion++;
}
- // Used by server side to check the cookie in ClientHello message.
- synchronized boolean isValid(ClientHello clientHelloMsg) {
- byte[] cookie = clientHelloMsg.cookie;
+ MessageDigest md = JsseJce.getMessageDigest("SHA-256");
+ byte[] helloBytes = clientHello.getHelloCookieBytes();
+ md.update(helloBytes);
+ byte[] cookie = md.digest(secret); // 32 bytes
+ cookie[0] = (byte)((version >> 24) & 0xFF);
+ return cookie;
+ }
+
+ @Override
+ boolean isCookieValid(ConnectionContext context,
+ ClientHelloMessage clientHello, byte[] cookie) throws IOException {
// no cookie exchange or not a valid cookie length
if ((cookie == null) || (cookie.length != 32)) {
return false;
}
- int version = ((cookie[0] & 0xFF) << 24) |
- ((cookie[1] & 0xFF) << 16) |
- ((cookie[2] & 0xFF) << 8) |
- (cookie[3] & 0xFF);
-
byte[] secret;
- if (version == cookieVersion) {
+ synchronized (this) {
+ if (((cookieVersion >> 24) & 0xFF) == cookie[0]) {
secret = cookieSecret;
- } else if (version == prevCookieVersion) {
- secret = prevCookieSecret;
} else {
- return false; // may be out of the timing window
+ secret = legacySecret; // including out of window cookies
+ }
}
- clientHelloMsg.updateHelloCookie(cookieDigest);
- byte[] target = cookieDigest.digest(secret); // 32 bytes
+ MessageDigest md = JsseJce.getMessageDigest("SHA-256");
+ byte[] helloBytes = clientHello.getHelloCookieBytes();
+ md.update(helloBytes);
+ byte[] target = md.digest(secret); // 32 bytes
+ target[0] = cookie[0];
- for (int i = 4; i < 32; i++) {
- if (cookie[i] != target[i]) {
- return false;
+ return Arrays.equals(target, cookie);
}
}
- return true;
+ private static final
+ class D13HelloCookieManager extends HelloCookieManager {
+ D13HelloCookieManager(SecureRandom secureRandom) {
+ super(secureRandom);
+ }
+
+ @Override
+ byte[] createCookie(ConnectionContext context,
+ ClientHelloMessage clientHello) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ boolean isCookieValid(ConnectionContext context,
+ ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final
+ class T13HelloCookieManager extends HelloCookieManager {
+ private int cookieVersion; // version + sequence
+ private final byte[] cookieSecret;
+ private final byte[] legacySecret;
+
+ T13HelloCookieManager(SecureRandom secureRandom) {
+ super(secureRandom);
+
+ this.cookieVersion = secureRandom.nextInt();
+ this.cookieSecret = new byte[64];
+ this.legacySecret = new byte[64];
+
+ secureRandom.nextBytes(cookieSecret);
+ System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
+ }
+
+ @Override
+ byte[] createCookie(ConnectionContext context,
+ ClientHelloMessage clientHello) throws IOException {
+ int version;
+ byte[] secret;
+
+ synchronized (this) {
+ version = cookieVersion;
+ secret = cookieSecret;
+
+ // the cookie secret usage limit is 2^24
+ if ((cookieVersion & 0xFFFFFF) == 0) { // reset the secret
+ System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
+ secureRandom.nextBytes(cookieSecret);
+ }
+
+ cookieVersion++; // allow wrapped version number
+ }
+
+ // happens in server side only
+ ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+ MessageDigest md = JsseJce.getMessageDigest(
+ shc.negotiatedCipherSuite.hashAlg.name);
+ byte[] headerBytes = clientHello.getHeaderBytes();
+ md.update(headerBytes);
+ byte[] headerCookie = md.digest(secret);
+
+ // hash of ClientHello handshake message
+ shc.handshakeHash.update();
+ byte[] clientHelloHash = shc.handshakeHash.digest();
+
+ // version and cipher suite
+ //
+ // Store the negotiated cipher suite in the cookie as well.
+ // cookie[0]/[1]: cipher suite
+ // cookie[2]: cookie version
+ // + (hash length): Mac(ClientHello header)
+ // + (hash length): Hash(ClientHello)
+ byte[] prefix = new byte[] {
+ (byte)((shc.negotiatedCipherSuite.id >> 8) & 0xFF),
+ (byte)(shc.negotiatedCipherSuite.id & 0xFF),
+ (byte)((version >> 24) & 0xFF)
+ };
+
+ byte[] cookie = Arrays.copyOf(prefix,
+ prefix.length + headerCookie.length + clientHelloHash.length);
+ System.arraycopy(headerCookie, 0, cookie,
+ prefix.length, headerCookie.length);
+ System.arraycopy(clientHelloHash, 0, cookie,
+ prefix.length + headerCookie.length, clientHelloHash.length);
+
+ return cookie;
+ }
+
+ @Override
+ boolean isCookieValid(ConnectionContext context,
+ ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+ // no cookie exchange or not a valid cookie length
+ if ((cookie == null) || (cookie.length <= 32)) { // 32: roughly
+ return false;
+ }
+
+ int csId = ((cookie[0] & 0xFF) << 8) | (cookie[1] & 0xFF);
+ CipherSuite cs = CipherSuite.valueOf(csId);
+ if (cs == null || cs.hashAlg == null || cs.hashAlg.hashLength == 0) {
+ return false;
}
- // Used by client side to check the cookie in HelloVerifyRequest message.
- static void checkCookie(ProtocolVersion protocolVersion,
- byte[] cookie) throws IOException {
- if (cookie != null && cookie.length != 0) {
- int limit = COOKIE_MAX_LENGTH_DTLS12;
- if (protocolVersion.v == ProtocolVersion.DTLS10.v) {
- limit = COOKIE_MAX_LENGTH_DTLS10;
+ int hashLen = cs.hashAlg.hashLength;
+ if (cookie.length != (3 + hashLen * 2)) {
+ return false;
}
- if (cookie.length > COOKIE_MAX_LENGTH_DTLS10) {
- throw new SSLProtocolException(
- "Invalid HelloVerifyRequest.cookie (length = " +
- cookie.length + " bytes)");
+ byte[] prevHeadCookie =
+ Arrays.copyOfRange(cookie, 3, 3 + hashLen);
+ byte[] prevClientHelloHash =
+ Arrays.copyOfRange(cookie, 3 + hashLen, cookie.length);
+
+ byte[] secret;
+ synchronized (this) {
+ if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) {
+ secret = cookieSecret;
+ } else {
+ secret = legacySecret; // including out of window cookies
}
}
- // Otherwise, no cookie exchange.
+ // happens in server side only
+ ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+ MessageDigest md = JsseJce.getMessageDigest(cs.hashAlg.name);
+ byte[] headerBytes = clientHello.getHeaderBytes();
+ md.update(headerBytes);
+ byte[] headerCookie = md.digest(secret);
+
+ if (!Arrays.equals(headerCookie, prevHeadCookie)) {
+ return false;
+ }
+
+ // Use the ClientHello hash in the cookie for transtript
+ // hash calculation for stateless HelloRetryRequest.
+ //
+ // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
+ // Hash(message_hash || /* Handshake type */
+ // 00 00 Hash.length || /* Handshake message length (bytes) */
+ // Hash(ClientHello1) || /* Hash of ClientHello1 */
+ // HelloRetryRequest || ... || Mn)
+
+ // Reproduce HelloRetryRequest handshake message
+ byte[] hrrMessage =
+ ServerHello.hrrReproducer.produce(context, clientHello);
+ shc.handshakeHash.push(hrrMessage);
+
+ // Construct the 1st ClientHello message for transcript hash
+ byte[] hashedClientHello = new byte[4 + hashLen];
+ hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;
+ hashedClientHello[1] = (byte)0x00;
+ hashedClientHello[2] = (byte)0x00;
+ hashedClientHello[3] = (byte)(hashLen & 0xFF);
+ System.arraycopy(prevClientHelloHash, 0,
+ hashedClientHello, 4, hashLen);
+
+ shc.handshakeHash.push(hashedClientHello);
+
+ return true;
+ }
}
}
< prev index next >