1 /*
   2  * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.ssl;
  27 
  28 import java.io.IOException;
  29 import java.security.MessageDigest;
  30 import java.security.NoSuchAlgorithmException;
  31 import java.security.SecureRandom;
  32 import java.util.Arrays;
  33 import static sun.security.ssl.ClientHello.ClientHelloMessage;
  34 
  35 /**
  36  *  (D)TLS handshake cookie manager
  37  */
  38 abstract class HelloCookieManager {
  39 
  40     static class Builder {
  41 
  42         final SecureRandom secureRandom;
  43 
  44         private volatile D10HelloCookieManager d10HelloCookieManager;
  45         private volatile D13HelloCookieManager d13HelloCookieManager;
  46         private volatile T13HelloCookieManager t13HelloCookieManager;
  47 
  48         Builder(SecureRandom secureRandom) {
  49             this.secureRandom = secureRandom;
  50         }
  51 
  52         HelloCookieManager valueOf(ProtocolVersion protocolVersion) {
  53             if (protocolVersion.isDTLS) {
  54                 if (protocolVersion.useTLS13PlusSpec()) {
  55                     if (d13HelloCookieManager != null) {
  56                         return d13HelloCookieManager;
  57                     }
  58 
  59                     synchronized (this) {
  60                         if (d13HelloCookieManager == null) {
  61                             d13HelloCookieManager =
  62                                     new D13HelloCookieManager(secureRandom);
  63                         }
  64                     }
  65 
  66                     return d13HelloCookieManager;
  67                 } else {
  68                     if (d10HelloCookieManager != null) {
  69                         return d10HelloCookieManager;
  70                     }
  71 
  72                     synchronized (this) {
  73                         if (d10HelloCookieManager == null) {
  74                             d10HelloCookieManager =
  75                                     new D10HelloCookieManager(secureRandom);
  76                         }
  77                     }
  78 
  79                     return d10HelloCookieManager;
  80                 }
  81             } else {
  82                 if (protocolVersion.useTLS13PlusSpec()) {
  83                     if (t13HelloCookieManager != null) {
  84                         return t13HelloCookieManager;
  85                     }
  86 
  87                     synchronized (this) {
  88                         if (t13HelloCookieManager == null) {
  89                             t13HelloCookieManager =
  90                                     new T13HelloCookieManager(secureRandom);
  91                         }
  92                     }
  93 
  94                     return t13HelloCookieManager;
  95                 }
  96             }
  97 
  98             return null;
  99         }
 100     }
 101 
 102     abstract byte[] createCookie(ServerHandshakeContext context,
 103                 ClientHelloMessage clientHello) throws IOException;
 104 
 105     abstract boolean isCookieValid(ServerHandshakeContext context,
 106             ClientHelloMessage clientHello, byte[] cookie) throws IOException;
 107 
 108     // DTLS 1.0/1.2
 109     private static final
 110             class D10HelloCookieManager extends HelloCookieManager {
 111 
 112         final SecureRandom secureRandom;
 113         private int         cookieVersion;  // allow to wrap, version + sequence
 114         private byte[]      cookieSecret;
 115         private byte[]      legacySecret;
 116 
 117         D10HelloCookieManager(SecureRandom secureRandom) {
 118             this.secureRandom = secureRandom;
 119 
 120             this.cookieVersion = secureRandom.nextInt();
 121             this.cookieSecret = new byte[32];
 122             this.legacySecret = new byte[32];
 123 
 124             secureRandom.nextBytes(cookieSecret);
 125             System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
 126         }
 127 
 128         @Override
 129         byte[] createCookie(ServerHandshakeContext context,
 130                 ClientHelloMessage clientHello) throws IOException {
 131             int version;
 132             byte[] secret;
 133 
 134             synchronized (this) {
 135                 version = cookieVersion;
 136                 secret = cookieSecret;
 137 
 138                 // the cookie secret usage limit is 2^24
 139                 if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
 140                     System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
 141                     secureRandom.nextBytes(cookieSecret);
 142                 }
 143 
 144                 cookieVersion++;
 145             }
 146 
 147             MessageDigest md;
 148             try {
 149                 md = MessageDigest.getInstance("SHA-256");
 150             } catch (NoSuchAlgorithmException nsae) {
 151                 throw new RuntimeException(
 152                     "MessageDigest algorithm SHA-256 is not available", nsae);
 153             }
 154             byte[] helloBytes = clientHello.getHelloCookieBytes();
 155             md.update(helloBytes);
 156             byte[] cookie = md.digest(secret);      // 32 bytes
 157             cookie[0] = (byte)((version >> 24) & 0xFF);
 158 
 159             return cookie;
 160         }
 161 
 162         @Override
 163         boolean isCookieValid(ServerHandshakeContext context,
 164             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 165             // no cookie exchange or not a valid cookie length
 166             if ((cookie == null) || (cookie.length != 32)) {
 167                 return false;
 168             }
 169 
 170             byte[] secret;
 171             synchronized (this) {
 172                 if (((cookieVersion >> 24) & 0xFF) == cookie[0]) {
 173                     secret = cookieSecret;
 174                 } else {
 175                     secret = legacySecret;  // including out of window cookies
 176                 }
 177             }
 178 
 179             MessageDigest md;
 180             try {
 181                 md = MessageDigest.getInstance("SHA-256");
 182             } catch (NoSuchAlgorithmException nsae) {
 183                 throw new RuntimeException(
 184                     "MessageDigest algorithm SHA-256 is not available", nsae);
 185             }
 186             byte[] helloBytes = clientHello.getHelloCookieBytes();
 187             md.update(helloBytes);
 188             byte[] target = md.digest(secret);      // 32 bytes
 189             target[0] = cookie[0];
 190 
 191             return Arrays.equals(target, cookie);
 192         }
 193     }
 194 
 195     private static final
 196             class D13HelloCookieManager extends HelloCookieManager {
 197         D13HelloCookieManager(SecureRandom secureRandom) {
 198         }
 199 
 200         @Override
 201         byte[] createCookie(ServerHandshakeContext context,
 202                 ClientHelloMessage clientHello) throws IOException {
 203             throw new UnsupportedOperationException("Not supported yet.");
 204         }
 205 
 206         @Override
 207         boolean isCookieValid(ServerHandshakeContext context,
 208             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 209             throw new UnsupportedOperationException("Not supported yet.");
 210         }
 211     }
 212 
 213     private static final
 214             class T13HelloCookieManager extends HelloCookieManager {
 215 
 216         final SecureRandom secureRandom;
 217         private int             cookieVersion;      // version + sequence
 218         private final byte[]    cookieSecret;
 219         private final byte[]    legacySecret;
 220 
 221         T13HelloCookieManager(SecureRandom secureRandom) {
 222             this.secureRandom = secureRandom;
 223             this.cookieVersion = secureRandom.nextInt();
 224             this.cookieSecret = new byte[64];
 225             this.legacySecret = new byte[64];
 226 
 227             secureRandom.nextBytes(cookieSecret);
 228             System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
 229         }
 230 
 231         @Override
 232         byte[] createCookie(ServerHandshakeContext context,
 233                 ClientHelloMessage clientHello) throws IOException {
 234             int version;
 235             byte[] secret;
 236 
 237             synchronized (this) {
 238                 version = cookieVersion;
 239                 secret = cookieSecret;
 240 
 241                 // the cookie secret usage limit is 2^24
 242                 if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
 243                     System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
 244                     secureRandom.nextBytes(cookieSecret);
 245                 }
 246 
 247                 cookieVersion++;        // allow wrapped version number
 248             }
 249 
 250             MessageDigest md;
 251             try {
 252                 md = MessageDigest.getInstance(
 253                     context.negotiatedCipherSuite.hashAlg.name);
 254             } catch (NoSuchAlgorithmException nsae) {
 255                 throw new RuntimeException(
 256                         "MessageDigest algorithm " +
 257                         context.negotiatedCipherSuite.hashAlg.name +
 258                         " is not available", nsae);
 259             }
 260             byte[] headerBytes = clientHello.getHeaderBytes();
 261             md.update(headerBytes);
 262             byte[] headerCookie = md.digest(secret);
 263 
 264             // hash of ClientHello handshake message
 265             context.handshakeHash.update();
 266             byte[] clientHelloHash = context.handshakeHash.digest();
 267 
 268             // version and cipher suite
 269             //
 270             // Store the negotiated cipher suite in the cookie as well.
 271             // cookie[0]/[1]: cipher suite
 272             // cookie[2]: cookie version
 273             // + (hash length): Mac(ClientHello header)
 274             // + (hash length): Hash(ClientHello)
 275             byte[] prefix = new byte[] {
 276                     (byte)((context.negotiatedCipherSuite.id >> 8) & 0xFF),
 277                     (byte)(context.negotiatedCipherSuite.id & 0xFF),
 278                     (byte)((version >> 24) & 0xFF)
 279                 };
 280 
 281             byte[] cookie = Arrays.copyOf(prefix,
 282                 prefix.length + headerCookie.length + clientHelloHash.length);
 283             System.arraycopy(headerCookie, 0, cookie,
 284                 prefix.length, headerCookie.length);
 285             System.arraycopy(clientHelloHash, 0, cookie,
 286                 prefix.length + headerCookie.length, clientHelloHash.length);
 287 
 288             return cookie;
 289         }
 290 
 291         @Override
 292         boolean isCookieValid(ServerHandshakeContext context,
 293             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 294             // no cookie exchange or not a valid cookie length
 295             if ((cookie == null) || (cookie.length <= 32)) {    // 32: roughly
 296                 return false;
 297             }
 298 
 299             int csId = ((cookie[0] & 0xFF) << 8) | (cookie[1] & 0xFF);
 300             CipherSuite cs = CipherSuite.valueOf(csId);
 301             if (cs == null || cs.hashAlg == null || cs.hashAlg.hashLength == 0) {
 302                 return false;
 303             }
 304 
 305             int hashLen = cs.hashAlg.hashLength;
 306             if (cookie.length != (3 + hashLen * 2)) {
 307                 return false;
 308             }
 309 
 310             byte[] prevHeadCookie =
 311                     Arrays.copyOfRange(cookie, 3, 3 + hashLen);
 312             byte[] prevClientHelloHash =
 313                     Arrays.copyOfRange(cookie, 3 + hashLen, cookie.length);
 314 
 315             byte[] secret;
 316             synchronized (this) {
 317                 if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) {
 318                     secret = cookieSecret;
 319                 } else {
 320                     secret = legacySecret;  // including out of window cookies
 321                 }
 322             }
 323 
 324             MessageDigest md;
 325             try {
 326                 md = MessageDigest.getInstance(cs.hashAlg.name);
 327             } catch (NoSuchAlgorithmException nsae) {
 328                 throw new RuntimeException(
 329                         "MessageDigest algorithm " +
 330                         cs.hashAlg.name + " is not available", nsae);
 331             }
 332             byte[] headerBytes = clientHello.getHeaderBytes();
 333             md.update(headerBytes);
 334             byte[] headerCookie = md.digest(secret);
 335 
 336             if (!Arrays.equals(headerCookie, prevHeadCookie)) {
 337                 return false;
 338             }
 339 
 340             // Use the ClientHello hash in the cookie for transtript
 341             // hash calculation for stateless HelloRetryRequest.
 342             //
 343             // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
 344             //   Hash(message_hash ||    /* Handshake type */
 345             //     00 00 Hash.length ||  /* Handshake message length (bytes) */
 346             //     Hash(ClientHello1) || /* Hash of ClientHello1 */
 347             //     HelloRetryRequest || ... || Mn)
 348 
 349             // Reproduce HelloRetryRequest handshake message
 350             byte[] hrrMessage =
 351                     ServerHello.hrrReproducer.produce(context, clientHello);
 352             context.handshakeHash.push(hrrMessage);
 353 
 354             // Construct the 1st ClientHello message for transcript hash
 355             byte[] hashedClientHello = new byte[4 + hashLen];
 356             hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;
 357             hashedClientHello[1] = (byte)0x00;
 358             hashedClientHello[2] = (byte)0x00;
 359             hashedClientHello[3] = (byte)(hashLen & 0xFF);
 360             System.arraycopy(prevClientHelloHash, 0,
 361                     hashedClientHello, 4, hashLen);
 362 
 363             context.handshakeHash.push(hashedClientHello);
 364 
 365             return true;
 366         }
 367     }
 368 }