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