| /* | |
| * Copyright (c) 2018, 2022, 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.io.IOException; | |
| import java.security.MessageDigest; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.security.SecureRandom; | |
| import java.util.Arrays; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| import static sun.security.ssl.ClientHello.ClientHelloMessage; | |
| /** | |
| * (D)TLS handshake cookie manager | |
| */ | |
| abstract class HelloCookieManager { | |
| static class Builder { | |
| final SecureRandom secureRandom; | |
| private volatile D10HelloCookieManager d10HelloCookieManager; | |
| private volatile D13HelloCookieManager d13HelloCookieManager; | |
| private volatile T13HelloCookieManager t13HelloCookieManager; | |
| private final ReentrantLock managerLock = new ReentrantLock(); | |
| Builder(SecureRandom secureRandom) { | |
| this.secureRandom = secureRandom; | |
| } | |
| HelloCookieManager valueOf(ProtocolVersion protocolVersion) { | |
| if (protocolVersion.isDTLS) { | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| if (d13HelloCookieManager != null) { | |
| return d13HelloCookieManager; | |
| } | |
| managerLock.lock(); | |
| try { | |
| if (d13HelloCookieManager == null) { | |
| d13HelloCookieManager = | |
| new D13HelloCookieManager(secureRandom); | |
| } | |
| } finally { | |
| managerLock.unlock(); | |
| } | |
| return d13HelloCookieManager; | |
| } else { | |
| if (d10HelloCookieManager != null) { | |
| return d10HelloCookieManager; | |
| } | |
| managerLock.lock(); | |
| try { | |
| if (d10HelloCookieManager == null) { | |
| d10HelloCookieManager = | |
| new D10HelloCookieManager(secureRandom); | |
| } | |
| } finally { | |
| managerLock.unlock(); | |
| } | |
| return d10HelloCookieManager; | |
| } | |
| } else { | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| if (t13HelloCookieManager != null) { | |
| return t13HelloCookieManager; | |
| } | |
| managerLock.lock(); | |
| try { | |
| if (t13HelloCookieManager == null) { | |
| t13HelloCookieManager = | |
| new T13HelloCookieManager(secureRandom); | |
| } | |
| } finally { | |
| managerLock.unlock(); | |
| } | |
| return t13HelloCookieManager; | |
| } | |
| } | |
| return null; | |
| } | |
| } | |
| abstract byte[] createCookie(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello) throws IOException; | |
| abstract boolean isCookieValid(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello, byte[] cookie) throws IOException; | |
| // DTLS 1.0/1.2 | |
| private static final | |
| class D10HelloCookieManager extends HelloCookieManager { | |
| final SecureRandom secureRandom; | |
| private int cookieVersion; // allow to wrap, version + sequence | |
| private final byte[] cookieSecret; | |
| private final byte[] legacySecret; | |
| private final ReentrantLock d10ManagerLock = new ReentrantLock(); | |
| D10HelloCookieManager(SecureRandom secureRandom) { | |
| this.secureRandom = secureRandom; | |
| this.cookieVersion = secureRandom.nextInt(); | |
| this.cookieSecret = new byte[32]; | |
| this.legacySecret = new byte[32]; | |
| secureRandom.nextBytes(cookieSecret); | |
| System.arraycopy(cookieSecret, 0, legacySecret, 0, 32); | |
| } | |
| @Override | |
| byte[] createCookie(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello) { | |
| int version; | |
| byte[] secret; | |
| d10ManagerLock.lock(); | |
| try { | |
| 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++; | |
| } finally { | |
| d10ManagerLock.unlock(); | |
| } | |
| MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance("SHA-256"); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new RuntimeException( | |
| "MessageDigest algorithm SHA-256 is not available", nsae); | |
| } | |
| 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(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello, byte[] cookie) { | |
| // no cookie exchange or not a valid cookie length | |
| if ((cookie == null) || (cookie.length != 32)) { | |
| return false; | |
| } | |
| byte[] secret; | |
| d10ManagerLock.lock(); | |
| try { | |
| if (((cookieVersion >> 24) & 0xFF) == cookie[0]) { | |
| secret = cookieSecret; | |
| } else { | |
| secret = legacySecret; // including out of window cookies | |
| } | |
| } finally { | |
| d10ManagerLock.unlock(); | |
| } | |
| MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance("SHA-256"); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new RuntimeException( | |
| "MessageDigest algorithm SHA-256 is not available", nsae); | |
| } | |
| byte[] helloBytes = clientHello.getHelloCookieBytes(); | |
| md.update(helloBytes); | |
| byte[] target = md.digest(secret); // 32 bytes | |
| target[0] = cookie[0]; | |
| return MessageDigest.isEqual(target, cookie); | |
| } | |
| } | |
| private static final | |
| class D13HelloCookieManager extends HelloCookieManager { | |
| D13HelloCookieManager(SecureRandom secureRandom) { | |
| } | |
| @Override | |
| byte[] createCookie(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello) { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| @Override | |
| boolean isCookieValid(ServerHandshakeContext context, | |
| ClientHelloMessage clientHello, byte[] cookie) { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| } | |
| private static final | |
| class T13HelloCookieManager extends HelloCookieManager { | |
| final SecureRandom secureRandom; | |
| private int cookieVersion; // version + sequence | |
| private final byte[] cookieSecret; | |
| private final byte[] legacySecret; | |
| private final ReentrantLock t13ManagerLock = new ReentrantLock(); | |
| 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) { | |
| int version; | |
| byte[] secret; | |
| t13ManagerLock.lock(); | |
| try { | |
| 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 | |
| } finally { | |
| t13ManagerLock.unlock(); | |
| } | |
| MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance( | |
| context.negotiatedCipherSuite.hashAlg.name); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new RuntimeException( | |
| "MessageDigest algorithm " + | |
| context.negotiatedCipherSuite.hashAlg.name + | |
| " is not available", nsae); | |
| } | |
| byte[] headerBytes = clientHello.getHeaderBytes(); | |
| md.update(headerBytes); | |
| byte[] headerCookie = md.digest(secret); | |
| // hash of ClientHello handshake message | |
| 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) | |
| // + (hash length): Hash(ClientHello) | |
| 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 { | |
| // 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; | |
| } | |
| 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; | |
| t13ManagerLock.lock(); | |
| try { | |
| if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) { | |
| secret = cookieSecret; | |
| } else { | |
| secret = legacySecret; // including out of window cookies | |
| } | |
| } finally { | |
| t13ManagerLock.unlock(); | |
| } | |
| MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance(cs.hashAlg.name); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new RuntimeException( | |
| "MessageDigest algorithm " + | |
| cs.hashAlg.name + " is not available", nsae); | |
| } | |
| byte[] headerBytes = clientHello.getHeaderBytes(); | |
| md.update(headerBytes); | |
| byte[] headerCookie = md.digest(secret); | |
| if (!MessageDigest.isEqual(headerCookie, prevHeadCookie)) { | |
| return false; | |
| } | |
| // Use the ClientHello hash in the cookie for transcript | |
| // 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); | |
| context.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); | |
| context.handshakeHash.push(hashedClientHello); | |
| return true; | |
| } | |
| } | |
| } |