|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.IOException; |
|
import java.security.MessageDigest; |
|
import java.security.SecureRandom; |
|
import java.util.Arrays; |
|
import static sun.security.ssl.ClientHello.ClientHelloMessage; |
|
|
|
|
|
|
|
*/ |
|
abstract class HelloCookieManager { |
|
|
|
static class Builder { |
|
|
|
final SecureRandom secureRandom; |
|
|
|
private volatile T13HelloCookieManager t13HelloCookieManager; |
|
|
|
Builder(SecureRandom secureRandom) { |
|
this.secureRandom = secureRandom; |
|
} |
|
|
|
HelloCookieManager valueOf(ProtocolVersion protocolVersion) { |
|
if (protocolVersion.useTLS13PlusSpec()) { |
|
if (t13HelloCookieManager != null) { |
|
return t13HelloCookieManager; |
|
} |
|
|
|
synchronized (this) { |
|
if (t13HelloCookieManager == null) { |
|
t13HelloCookieManager = |
|
new T13HelloCookieManager(secureRandom); |
|
} |
|
} |
|
|
|
return t13HelloCookieManager; |
|
} |
|
|
|
return null; |
|
} |
|
} |
|
|
|
abstract byte[] createCookie(ServerHandshakeContext context, |
|
ClientHelloMessage clientHello) throws IOException; |
|
|
|
abstract boolean isCookieValid(ServerHandshakeContext context, |
|
ClientHelloMessage clientHello, byte[] cookie) throws IOException; |
|
|
|
private static final |
|
class T13HelloCookieManager extends HelloCookieManager { |
|
|
|
final SecureRandom secureRandom; |
|
private int cookieVersion; |
|
private final byte[] cookieSecret; |
|
private final byte[] legacySecret; |
|
|
|
T13HelloCookieManager(SecureRandom secureRandom) { |
|
this.secureRandom = 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(ServerHandshakeContext context, |
|
ClientHelloMessage clientHello) throws IOException { |
|
int version; |
|
byte[] secret; |
|
|
|
synchronized (this) { |
|
version = cookieVersion; |
|
secret = cookieSecret; |
|
|
|
|
|
if ((cookieVersion & 0xFFFFFF) == 0) { |
|
System.arraycopy(cookieSecret, 0, legacySecret, 0, 64); |
|
secureRandom.nextBytes(cookieSecret); |
|
} |
|
|
|
cookieVersion++; |
|
} |
|
|
|
MessageDigest md = JsseJce.getMessageDigest( |
|
context.negotiatedCipherSuite.hashAlg.name); |
|
byte[] headerBytes = clientHello.getHeaderBytes(); |
|
md.update(headerBytes); |
|
byte[] headerCookie = md.digest(secret); |
|
|
|
|
|
context.handshakeHash.update(); |
|
byte[] clientHelloHash = context.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) |
|
|
|
byte[] prefix = new byte[] { |
|
(byte)((context.negotiatedCipherSuite.id >> 8) & 0xFF), |
|
(byte)(context.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(ServerHandshakeContext context, |
|
ClientHelloMessage clientHello, byte[] cookie) throws IOException { |
|
|
|
if ((cookie == null) || (cookie.length <= 32)) { |
|
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; |
|
} |
|
|
|
int hashLen = cs.hashAlg.hashLength; |
|
if (cookie.length != (3 + hashLen * 2)) { |
|
return false; |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
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) |
|
|
|
|
|
byte[] hrrMessage = |
|
ServerHello.hrrReproducer.produce(context, clientHello); |
|
context.handshakeHash.push(hrrMessage); |
|
|
|
|
|
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); |
|
|
|
context.handshakeHash.push(hashedClientHello); |
|
|
|
return true; |
|
} |
|
} |
|
} |