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 java.util.concurrent.locks.ReentrantLock;
  34 import static sun.security.ssl.ClientHello.ClientHelloMessage;
  35 
  36 /**
  37  *  (D)TLS handshake cookie manager
  38  */
  39 abstract class HelloCookieManager {
  40 
  41     static class Builder {
  42 
  43         final SecureRandom secureRandom;
  44 
  45         private volatile D10HelloCookieManager d10HelloCookieManager;
  46         private volatile D13HelloCookieManager d13HelloCookieManager;
  47         private volatile T13HelloCookieManager t13HelloCookieManager;
  48 
  49         private final ReentrantLock managerLock = new ReentrantLock();
  50 
  51         Builder(SecureRandom secureRandom) {
  52             this.secureRandom = secureRandom;
  53         }
  54 
  55         HelloCookieManager valueOf(ProtocolVersion protocolVersion) {
  56             if (protocolVersion.isDTLS) {
  57                 if (protocolVersion.useTLS13PlusSpec()) {
  58                     if (d13HelloCookieManager != null) {
  59                         return d13HelloCookieManager;
  60                     }
  61 
  62                     managerLock.lock();
  63                     try {
  64                         if (d13HelloCookieManager == null) {
  65                             d13HelloCookieManager =
  66                                     new D13HelloCookieManager(secureRandom);
  67                         }
  68                     } finally {
  69                         managerLock.unlock();
  70                     }
  71 
  72                     return d13HelloCookieManager;
  73                 } else {
  74                     if (d10HelloCookieManager != null) {
  75                         return d10HelloCookieManager;
  76                     }
  77 
  78                     managerLock.lock();
  79                     try {
  80                         if (d10HelloCookieManager == null) {
  81                             d10HelloCookieManager =
  82                                     new D10HelloCookieManager(secureRandom);
  83                         }
  84                     } finally {
  85                         managerLock.unlock();
  86                     }
  87 
  88                     return d10HelloCookieManager;
  89                 }
  90             } else {
  91                 if (protocolVersion.useTLS13PlusSpec()) {
  92                     if (t13HelloCookieManager != null) {
  93                         return t13HelloCookieManager;
  94                     }
  95 
  96                     managerLock.lock();
  97                     try {
  98                         if (t13HelloCookieManager == null) {
  99                             t13HelloCookieManager =
 100                                     new T13HelloCookieManager(secureRandom);
 101                         }
 102                     } finally {
 103                         managerLock.unlock();
 104                     }
 105 
 106                     return t13HelloCookieManager;
 107                 }
 108             }
 109 
 110             return null;
 111         }
 112     }
 113 
 114     abstract byte[] createCookie(ServerHandshakeContext context,
 115                 ClientHelloMessage clientHello) throws IOException;
 116 
 117     abstract boolean isCookieValid(ServerHandshakeContext context,
 118             ClientHelloMessage clientHello, byte[] cookie) throws IOException;
 119 
 120     // DTLS 1.0/1.2
 121     private static final
 122             class D10HelloCookieManager extends HelloCookieManager {
 123 
 124         final SecureRandom secureRandom;
 125         private int         cookieVersion;  // allow to wrap, version + sequence
 126         private byte[]      cookieSecret;
 127         private byte[]      legacySecret;
 128 
 129         private final ReentrantLock d10ManagerLock = new ReentrantLock();
 130 
 131         D10HelloCookieManager(SecureRandom secureRandom) {
 132             this.secureRandom = secureRandom;
 133 
 134             this.cookieVersion = secureRandom.nextInt();
 135             this.cookieSecret = new byte[32];
 136             this.legacySecret = new byte[32];
 137 
 138             secureRandom.nextBytes(cookieSecret);
 139             System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
 140         }
 141 
 142         @Override
 143         byte[] createCookie(ServerHandshakeContext context,
 144                 ClientHelloMessage clientHello) throws IOException {
 145             int version;
 146             byte[] secret;
 147 
 148             d10ManagerLock.lock();
 149             try {
 150                 version = cookieVersion;
 151                 secret = cookieSecret;
 152 
 153                 // the cookie secret usage limit is 2^24
 154                 if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
 155                     System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
 156                     secureRandom.nextBytes(cookieSecret);
 157                 }
 158 
 159                 cookieVersion++;
 160             } finally {
 161                 d10ManagerLock.unlock();
 162             }
 163 
 164             MessageDigest md;
 165             try {
 166                 md = MessageDigest.getInstance("SHA-256");
 167             } catch (NoSuchAlgorithmException nsae) {
 168                 throw new RuntimeException(
 169                     "MessageDigest algorithm SHA-256 is not available", nsae);
 170             }
 171             byte[] helloBytes = clientHello.getHelloCookieBytes();
 172             md.update(helloBytes);
 173             byte[] cookie = md.digest(secret);      // 32 bytes
 174             cookie[0] = (byte)((version >> 24) & 0xFF);
 175 
 176             return cookie;
 177         }
 178 
 179         @Override
 180         boolean isCookieValid(ServerHandshakeContext context,
 181             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 182             // no cookie exchange or not a valid cookie length
 183             if ((cookie == null) || (cookie.length != 32)) {
 184                 return false;
 185             }
 186 
 187             byte[] secret;
 188             d10ManagerLock.lock();
 189             try {
 190                 if (((cookieVersion >> 24) & 0xFF) == cookie[0]) {
 191                     secret = cookieSecret;
 192                 } else {
 193                     secret = legacySecret;  // including out of window cookies
 194                 }
 195             } finally {
 196                 d10ManagerLock.unlock();
 197             }
 198 
 199             MessageDigest md;
 200             try {
 201                 md = MessageDigest.getInstance("SHA-256");
 202             } catch (NoSuchAlgorithmException nsae) {
 203                 throw new RuntimeException(
 204                     "MessageDigest algorithm SHA-256 is not available", nsae);
 205             }
 206             byte[] helloBytes = clientHello.getHelloCookieBytes();
 207             md.update(helloBytes);
 208             byte[] target = md.digest(secret);      // 32 bytes
 209             target[0] = cookie[0];
 210 
 211             return Arrays.equals(target, cookie);
 212         }
 213     }
 214 
 215     private static final
 216             class D13HelloCookieManager extends HelloCookieManager {
 217         D13HelloCookieManager(SecureRandom secureRandom) {
 218         }
 219 
 220         @Override
 221         byte[] createCookie(ServerHandshakeContext context,
 222                 ClientHelloMessage clientHello) throws IOException {
 223             throw new UnsupportedOperationException("Not supported yet.");
 224         }
 225 
 226         @Override
 227         boolean isCookieValid(ServerHandshakeContext context,
 228             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 229             throw new UnsupportedOperationException("Not supported yet.");
 230         }
 231     }
 232 
 233     private static final
 234             class T13HelloCookieManager extends HelloCookieManager {
 235 
 236         final SecureRandom secureRandom;
 237         private int             cookieVersion;      // version + sequence
 238         private final byte[]    cookieSecret;
 239         private final byte[]    legacySecret;
 240 
 241         private final ReentrantLock t13ManagerLock = new ReentrantLock();
 242 
 243         T13HelloCookieManager(SecureRandom secureRandom) {
 244             this.secureRandom = secureRandom;
 245             this.cookieVersion = secureRandom.nextInt();
 246             this.cookieSecret = new byte[64];
 247             this.legacySecret = new byte[64];
 248 
 249             secureRandom.nextBytes(cookieSecret);
 250             System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
 251         }
 252 
 253         @Override
 254         byte[] createCookie(ServerHandshakeContext context,
 255                 ClientHelloMessage clientHello) throws IOException {
 256             int version;
 257             byte[] secret;
 258 
 259             t13ManagerLock.lock();
 260             try {
 261                 version = cookieVersion;
 262                 secret = cookieSecret;
 263 
 264                 // the cookie secret usage limit is 2^24
 265                 if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
 266                     System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
 267                     secureRandom.nextBytes(cookieSecret);
 268                 }
 269 
 270                 cookieVersion++;        // allow wrapped version number
 271             } finally {
 272                 t13ManagerLock.unlock();
 273             }
 274 
 275             MessageDigest md;
 276             try {
 277                 md = MessageDigest.getInstance(
 278                     context.negotiatedCipherSuite.hashAlg.name);
 279             } catch (NoSuchAlgorithmException nsae) {
 280                 throw new RuntimeException(
 281                         "MessageDigest algorithm " +
 282                         context.negotiatedCipherSuite.hashAlg.name +
 283                         " is not available", nsae);
 284             }
 285             byte[] headerBytes = clientHello.getHeaderBytes();
 286             md.update(headerBytes);
 287             byte[] headerCookie = md.digest(secret);
 288 
 289             // hash of ClientHello handshake message
 290             context.handshakeHash.update();
 291             byte[] clientHelloHash = context.handshakeHash.digest();
 292 
 293             // version and cipher suite
 294             //
 295             // Store the negotiated cipher suite in the cookie as well.
 296             // cookie[0]/[1]: cipher suite
 297             // cookie[2]: cookie version
 298             // + (hash length): Mac(ClientHello header)
 299             // + (hash length): Hash(ClientHello)
 300             byte[] prefix = new byte[] {
 301                     (byte)((context.negotiatedCipherSuite.id >> 8) & 0xFF),
 302                     (byte)(context.negotiatedCipherSuite.id & 0xFF),
 303                     (byte)((version >> 24) & 0xFF)
 304                 };
 305 
 306             byte[] cookie = Arrays.copyOf(prefix,
 307                 prefix.length + headerCookie.length + clientHelloHash.length);
 308             System.arraycopy(headerCookie, 0, cookie,
 309                 prefix.length, headerCookie.length);
 310             System.arraycopy(clientHelloHash, 0, cookie,
 311                 prefix.length + headerCookie.length, clientHelloHash.length);
 312 
 313             return cookie;
 314         }
 315 
 316         @Override
 317         boolean isCookieValid(ServerHandshakeContext context,
 318             ClientHelloMessage clientHello, byte[] cookie) throws IOException {
 319             // no cookie exchange or not a valid cookie length
 320             if ((cookie == null) || (cookie.length <= 32)) {    // 32: roughly
 321                 return false;
 322             }
 323 
 324             int csId = ((cookie[0] & 0xFF) << 8) | (cookie[1] & 0xFF);
 325             CipherSuite cs = CipherSuite.valueOf(csId);
 326             if (cs == null || cs.hashAlg == null || cs.hashAlg.hashLength == 0) {
 327                 return false;
 328             }
 329 
 330             int hashLen = cs.hashAlg.hashLength;
 331             if (cookie.length != (3 + hashLen * 2)) {
 332                 return false;
 333             }
 334 
 335             byte[] prevHeadCookie =
 336                     Arrays.copyOfRange(cookie, 3, 3 + hashLen);
 337             byte[] prevClientHelloHash =
 338                     Arrays.copyOfRange(cookie, 3 + hashLen, cookie.length);
 339 
 340             byte[] secret;
 341             t13ManagerLock.lock();
 342             try {
 343                 if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) {
 344                     secret = cookieSecret;
 345                 } else {
 346                     secret = legacySecret;  // including out of window cookies
 347                 }
 348             } finally {
 349                 t13ManagerLock.unlock();
 350             }
 351 
 352             MessageDigest md;
 353             try {
 354                 md = MessageDigest.getInstance(cs.hashAlg.name);
 355             } catch (NoSuchAlgorithmException nsae) {
 356                 throw new RuntimeException(
 357                         "MessageDigest algorithm " +
 358                         cs.hashAlg.name + " is not available", nsae);
 359             }
 360             byte[] headerBytes = clientHello.getHeaderBytes();
 361             md.update(headerBytes);
 362             byte[] headerCookie = md.digest(secret);
 363 
 364             if (!Arrays.equals(headerCookie, prevHeadCookie)) {
 365                 return false;
 366             }
 367 
 368             // Use the ClientHello hash in the cookie for transtript
 369             // hash calculation for stateless HelloRetryRequest.
 370             //
 371             // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
 372             //   Hash(message_hash ||    /* Handshake type */
 373             //     00 00 Hash.length ||  /* Handshake message length (bytes) */
 374             //     Hash(ClientHello1) || /* Hash of ClientHello1 */
 375             //     HelloRetryRequest || ... || Mn)
 376 
 377             // Reproduce HelloRetryRequest handshake message
 378             byte[] hrrMessage =
 379                     ServerHello.hrrReproducer.produce(context, clientHello);
 380             context.handshakeHash.push(hrrMessage);
 381 
 382             // Construct the 1st ClientHello message for transcript hash
 383             byte[] hashedClientHello = new byte[4 + hashLen];
 384             hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;
 385             hashedClientHello[1] = (byte)0x00;
 386             hashedClientHello[2] = (byte)0x00;
 387             hashedClientHello[3] = (byte)(hashLen & 0xFF);
 388             System.arraycopy(prevClientHelloHash, 0,
 389                     hashedClientHello, 4, hashLen);
 390 
 391             context.handshakeHash.push(hashedClientHello);
 392 
 393             return true;
 394         }
 395     }
 396 }