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