Path: blob/master/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java
41159 views
/*1* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.ssl;2627import java.io.IOException;28import java.security.MessageDigest;29import java.security.NoSuchAlgorithmException;30import java.security.SecureRandom;31import java.util.Arrays;32import java.util.concurrent.locks.ReentrantLock;33import static sun.security.ssl.ClientHello.ClientHelloMessage;3435/**36* (D)TLS handshake cookie manager37*/38abstract class HelloCookieManager {3940static class Builder {4142final SecureRandom secureRandom;4344private volatile D10HelloCookieManager d10HelloCookieManager;45private volatile D13HelloCookieManager d13HelloCookieManager;46private volatile T13HelloCookieManager t13HelloCookieManager;4748private final ReentrantLock managerLock = new ReentrantLock();4950Builder(SecureRandom secureRandom) {51this.secureRandom = secureRandom;52}5354HelloCookieManager valueOf(ProtocolVersion protocolVersion) {55if (protocolVersion.isDTLS) {56if (protocolVersion.useTLS13PlusSpec()) {57if (d13HelloCookieManager != null) {58return d13HelloCookieManager;59}6061managerLock.lock();62try {63if (d13HelloCookieManager == null) {64d13HelloCookieManager =65new D13HelloCookieManager(secureRandom);66}67} finally {68managerLock.unlock();69}7071return d13HelloCookieManager;72} else {73if (d10HelloCookieManager != null) {74return d10HelloCookieManager;75}7677managerLock.lock();78try {79if (d10HelloCookieManager == null) {80d10HelloCookieManager =81new D10HelloCookieManager(secureRandom);82}83} finally {84managerLock.unlock();85}8687return d10HelloCookieManager;88}89} else {90if (protocolVersion.useTLS13PlusSpec()) {91if (t13HelloCookieManager != null) {92return t13HelloCookieManager;93}9495managerLock.lock();96try {97if (t13HelloCookieManager == null) {98t13HelloCookieManager =99new T13HelloCookieManager(secureRandom);100}101} finally {102managerLock.unlock();103}104105return t13HelloCookieManager;106}107}108109return null;110}111}112113abstract byte[] createCookie(ServerHandshakeContext context,114ClientHelloMessage clientHello) throws IOException;115116abstract boolean isCookieValid(ServerHandshakeContext context,117ClientHelloMessage clientHello, byte[] cookie) throws IOException;118119// DTLS 1.0/1.2120private static final121class D10HelloCookieManager extends HelloCookieManager {122123final SecureRandom secureRandom;124private int cookieVersion; // allow to wrap, version + sequence125private final byte[] cookieSecret;126private final byte[] legacySecret;127128private final ReentrantLock d10ManagerLock = new ReentrantLock();129130D10HelloCookieManager(SecureRandom secureRandom) {131this.secureRandom = secureRandom;132133this.cookieVersion = secureRandom.nextInt();134this.cookieSecret = new byte[32];135this.legacySecret = new byte[32];136137secureRandom.nextBytes(cookieSecret);138System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);139}140141@Override142byte[] createCookie(ServerHandshakeContext context,143ClientHelloMessage clientHello) throws IOException {144int version;145byte[] secret;146147d10ManagerLock.lock();148try {149version = cookieVersion;150secret = cookieSecret;151152// the cookie secret usage limit is 2^24153if ((cookieVersion & 0xFFFFFF) == 0) { // reset the secret154System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);155secureRandom.nextBytes(cookieSecret);156}157158cookieVersion++;159} finally {160d10ManagerLock.unlock();161}162163MessageDigest md;164try {165md = MessageDigest.getInstance("SHA-256");166} catch (NoSuchAlgorithmException nsae) {167throw new RuntimeException(168"MessageDigest algorithm SHA-256 is not available", nsae);169}170byte[] helloBytes = clientHello.getHelloCookieBytes();171md.update(helloBytes);172byte[] cookie = md.digest(secret); // 32 bytes173cookie[0] = (byte)((version >> 24) & 0xFF);174175return cookie;176}177178@Override179boolean isCookieValid(ServerHandshakeContext context,180ClientHelloMessage clientHello, byte[] cookie) throws IOException {181// no cookie exchange or not a valid cookie length182if ((cookie == null) || (cookie.length != 32)) {183return false;184}185186byte[] secret;187d10ManagerLock.lock();188try {189if (((cookieVersion >> 24) & 0xFF) == cookie[0]) {190secret = cookieSecret;191} else {192secret = legacySecret; // including out of window cookies193}194} finally {195d10ManagerLock.unlock();196}197198MessageDigest md;199try {200md = MessageDigest.getInstance("SHA-256");201} catch (NoSuchAlgorithmException nsae) {202throw new RuntimeException(203"MessageDigest algorithm SHA-256 is not available", nsae);204}205byte[] helloBytes = clientHello.getHelloCookieBytes();206md.update(helloBytes);207byte[] target = md.digest(secret); // 32 bytes208target[0] = cookie[0];209210return Arrays.equals(target, cookie);211}212}213214private static final215class D13HelloCookieManager extends HelloCookieManager {216D13HelloCookieManager(SecureRandom secureRandom) {217}218219@Override220byte[] createCookie(ServerHandshakeContext context,221ClientHelloMessage clientHello) throws IOException {222throw new UnsupportedOperationException("Not supported yet.");223}224225@Override226boolean isCookieValid(ServerHandshakeContext context,227ClientHelloMessage clientHello, byte[] cookie) throws IOException {228throw new UnsupportedOperationException("Not supported yet.");229}230}231232private static final233class T13HelloCookieManager extends HelloCookieManager {234235final SecureRandom secureRandom;236private int cookieVersion; // version + sequence237private final byte[] cookieSecret;238private final byte[] legacySecret;239240private final ReentrantLock t13ManagerLock = new ReentrantLock();241242T13HelloCookieManager(SecureRandom secureRandom) {243this.secureRandom = secureRandom;244this.cookieVersion = secureRandom.nextInt();245this.cookieSecret = new byte[64];246this.legacySecret = new byte[64];247248secureRandom.nextBytes(cookieSecret);249System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);250}251252@Override253byte[] createCookie(ServerHandshakeContext context,254ClientHelloMessage clientHello) throws IOException {255int version;256byte[] secret;257258t13ManagerLock.lock();259try {260version = cookieVersion;261secret = cookieSecret;262263// the cookie secret usage limit is 2^24264if ((cookieVersion & 0xFFFFFF) == 0) { // reset the secret265System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);266secureRandom.nextBytes(cookieSecret);267}268269cookieVersion++; // allow wrapped version number270} finally {271t13ManagerLock.unlock();272}273274MessageDigest md;275try {276md = MessageDigest.getInstance(277context.negotiatedCipherSuite.hashAlg.name);278} catch (NoSuchAlgorithmException nsae) {279throw new RuntimeException(280"MessageDigest algorithm " +281context.negotiatedCipherSuite.hashAlg.name +282" is not available", nsae);283}284byte[] headerBytes = clientHello.getHeaderBytes();285md.update(headerBytes);286byte[] headerCookie = md.digest(secret);287288// hash of ClientHello handshake message289context.handshakeHash.update();290byte[] clientHelloHash = context.handshakeHash.digest();291292// version and cipher suite293//294// Store the negotiated cipher suite in the cookie as well.295// cookie[0]/[1]: cipher suite296// cookie[2]: cookie version297// + (hash length): Mac(ClientHello header)298// + (hash length): Hash(ClientHello)299byte[] prefix = new byte[] {300(byte)((context.negotiatedCipherSuite.id >> 8) & 0xFF),301(byte)(context.negotiatedCipherSuite.id & 0xFF),302(byte)((version >> 24) & 0xFF)303};304305byte[] cookie = Arrays.copyOf(prefix,306prefix.length + headerCookie.length + clientHelloHash.length);307System.arraycopy(headerCookie, 0, cookie,308prefix.length, headerCookie.length);309System.arraycopy(clientHelloHash, 0, cookie,310prefix.length + headerCookie.length, clientHelloHash.length);311312return cookie;313}314315@Override316boolean isCookieValid(ServerHandshakeContext context,317ClientHelloMessage clientHello, byte[] cookie) throws IOException {318// no cookie exchange or not a valid cookie length319if ((cookie == null) || (cookie.length <= 32)) { // 32: roughly320return false;321}322323int csId = ((cookie[0] & 0xFF) << 8) | (cookie[1] & 0xFF);324CipherSuite cs = CipherSuite.valueOf(csId);325if (cs == null || cs.hashAlg == null || cs.hashAlg.hashLength == 0) {326return false;327}328329int hashLen = cs.hashAlg.hashLength;330if (cookie.length != (3 + hashLen * 2)) {331return false;332}333334byte[] prevHeadCookie =335Arrays.copyOfRange(cookie, 3, 3 + hashLen);336byte[] prevClientHelloHash =337Arrays.copyOfRange(cookie, 3 + hashLen, cookie.length);338339byte[] secret;340t13ManagerLock.lock();341try {342if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) {343secret = cookieSecret;344} else {345secret = legacySecret; // including out of window cookies346}347} finally {348t13ManagerLock.unlock();349}350351MessageDigest md;352try {353md = MessageDigest.getInstance(cs.hashAlg.name);354} catch (NoSuchAlgorithmException nsae) {355throw new RuntimeException(356"MessageDigest algorithm " +357cs.hashAlg.name + " is not available", nsae);358}359byte[] headerBytes = clientHello.getHeaderBytes();360md.update(headerBytes);361byte[] headerCookie = md.digest(secret);362363if (!Arrays.equals(headerCookie, prevHeadCookie)) {364return false;365}366367// Use the ClientHello hash in the cookie for transtript368// hash calculation for stateless HelloRetryRequest.369//370// Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =371// Hash(message_hash || /* Handshake type */372// 00 00 Hash.length || /* Handshake message length (bytes) */373// Hash(ClientHello1) || /* Hash of ClientHello1 */374// HelloRetryRequest || ... || Mn)375376// Reproduce HelloRetryRequest handshake message377byte[] hrrMessage =378ServerHello.hrrReproducer.produce(context, clientHello);379context.handshakeHash.push(hrrMessage);380381// Construct the 1st ClientHello message for transcript hash382byte[] hashedClientHello = new byte[4 + hashLen];383hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;384hashedClientHello[1] = (byte)0x00;385hashedClientHello[2] = (byte)0x00;386hashedClientHello[3] = (byte)(hashLen & 0xFF);387System.arraycopy(prevClientHelloHash, 0,388hashedClientHello, 4, hashLen);389390context.handshakeHash.push(hashedClientHello);391392return true;393}394}395}396397398