24 */ |
24 */ |
25 |
25 |
26 package sun.security.ssl; |
26 package sun.security.ssl; |
27 |
27 |
28 import java.io.IOException; |
28 import java.io.IOException; |
29 import javax.net.ssl.SSLProtocolException; |
|
30 import java.security.MessageDigest; |
29 import java.security.MessageDigest; |
31 import java.security.SecureRandom; |
30 import java.security.SecureRandom; |
32 |
31 import java.util.Arrays; |
33 import sun.security.ssl.HandshakeMessage.ClientHello; |
32 import static sun.security.ssl.ClientHello.ClientHelloMessage; |
34 |
33 |
35 /* |
34 /** |
36 * HelloVerifyRequest cookie manager |
35 * (D)TLS handshake cookie manager |
37 */ |
36 */ |
38 final class HelloCookieManager { |
37 abstract class HelloCookieManager { |
39 // the cookie secret life time |
38 |
40 private static long COOKIE_TIMING_WINDOW = 3600000; // in milliseconds |
39 static class Builder { |
41 private static int COOKIE_MAX_LENGTH_DTLS10 = 32; // 32 bytes |
40 |
42 private static int COOKIE_MAX_LENGTH_DTLS12 = 0xFF; // 2^8 -1 bytes |
41 final SecureRandom secureRandom; |
43 |
42 |
44 private final SecureRandom secureRandom; |
43 private volatile D10HelloCookieManager d10HelloCookieManager; |
45 private final MessageDigest cookieDigest; |
44 private volatile D13HelloCookieManager d13HelloCookieManager; |
46 |
45 private volatile T13HelloCookieManager t13HelloCookieManager; |
47 private int cookieVersion; // allow to wrap |
46 |
48 private long secretLifetime; |
47 Builder(SecureRandom secureRandom) { |
49 private byte[] cookieSecret; |
48 this.secureRandom = secureRandom; |
50 |
49 } |
51 private int prevCookieVersion; |
50 |
52 private byte[] prevCookieSecret; |
51 HelloCookieManager valueOf(ProtocolVersion protocolVersion) { |
53 |
52 if (protocolVersion.isDTLS) { |
54 HelloCookieManager(SecureRandom secureRandom) { |
53 if (protocolVersion.useTLS13PlusSpec()) { |
55 this.secureRandom = secureRandom; |
54 if (d13HelloCookieManager != null) { |
56 this.cookieDigest = JsseJce.getMessageDigest("SHA-256"); |
55 return d13HelloCookieManager; |
57 |
56 } |
58 this.cookieVersion = secureRandom.nextInt(); |
57 |
59 this.secretLifetime = 0; |
58 synchronized (this) { |
60 this.cookieSecret = null; |
59 if (d13HelloCookieManager == null) { |
61 |
60 d13HelloCookieManager = |
62 this.prevCookieVersion = 0; |
61 new D13HelloCookieManager(secureRandom); |
63 this.prevCookieSecret = null; |
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 } |
64 } |
99 } |
65 |
100 |
66 // Used by server side to generate cookies in HelloVerifyRequest message. |
101 abstract byte[] createCookie(ServerHandshakeContext context, |
67 synchronized byte[] getCookie(ClientHello clientHelloMsg) { |
102 ClientHelloMessage clientHello) throws IOException; |
68 if (secretLifetime < System.currentTimeMillis()) { |
103 |
69 if (cookieSecret != null) { |
104 abstract boolean isCookieValid(ServerHandshakeContext context, |
70 prevCookieVersion = cookieVersion; |
105 ClientHelloMessage clientHello, byte[] cookie) throws IOException; |
71 prevCookieSecret = cookieSecret.clone(); |
106 |
72 } else { |
107 // DTLS 1.0/1.2 |
73 cookieSecret = new byte[32]; |
108 private static final |
74 } |
109 class D10HelloCookieManager extends HelloCookieManager { |
75 |
110 |
76 cookieVersion++; |
111 final SecureRandom secureRandom; |
|
112 private int cookieVersion; // allow to wrap, version + sequence |
|
113 private byte[] cookieSecret; |
|
114 private byte[] legacySecret; |
|
115 |
|
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 |
77 secureRandom.nextBytes(cookieSecret); |
123 secureRandom.nextBytes(cookieSecret); |
78 secretLifetime = System.currentTimeMillis() + COOKIE_TIMING_WINDOW; |
124 System.arraycopy(cookieSecret, 0, legacySecret, 0, 32); |
79 } |
125 } |
80 |
126 |
81 clientHelloMsg.updateHelloCookie(cookieDigest); |
127 @Override |
82 byte[] cookie = cookieDigest.digest(cookieSecret); // 32 bytes |
128 byte[] createCookie(ServerHandshakeContext context, |
83 cookie[0] = (byte)((cookieVersion >> 24) & 0xFF); |
129 ClientHelloMessage clientHello) throws IOException { |
84 cookie[1] = (byte)((cookieVersion >> 16) & 0xFF); |
130 int version; |
85 cookie[2] = (byte)((cookieVersion >> 8) & 0xFF); |
131 byte[] secret; |
86 cookie[3] = (byte)(cookieVersion & 0xFF); |
132 |
87 |
133 synchronized (this) { |
88 return cookie; |
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 |
|
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 Arrays.equals(target, cookie); |
|
179 } |
89 } |
180 } |
90 |
181 |
91 // Used by server side to check the cookie in ClientHello message. |
182 private static final |
92 synchronized boolean isValid(ClientHello clientHelloMsg) { |
183 class D13HelloCookieManager extends HelloCookieManager { |
93 byte[] cookie = clientHelloMsg.cookie; |
184 D13HelloCookieManager(SecureRandom secureRandom) { |
94 |
185 } |
95 // no cookie exchange or not a valid cookie length |
186 |
96 if ((cookie == null) || (cookie.length != 32)) { |
187 @Override |
97 return false; |
188 byte[] createCookie(ServerHandshakeContext context, |
98 } |
189 ClientHelloMessage clientHello) throws IOException { |
99 |
190 throw new UnsupportedOperationException("Not supported yet."); |
100 int version = ((cookie[0] & 0xFF) << 24) | |
191 } |
101 ((cookie[1] & 0xFF) << 16) | |
192 |
102 ((cookie[2] & 0xFF) << 8) | |
193 @Override |
103 (cookie[3] & 0xFF); |
194 boolean isCookieValid(ServerHandshakeContext context, |
104 |
195 ClientHelloMessage clientHello, byte[] cookie) throws IOException { |
105 byte[] secret; |
196 throw new UnsupportedOperationException("Not supported yet."); |
106 if (version == cookieVersion) { |
197 } |
107 secret = cookieSecret; |
|
108 } else if (version == prevCookieVersion) { |
|
109 secret = prevCookieSecret; |
|
110 } else { |
|
111 return false; // may be out of the timing window |
|
112 } |
|
113 |
|
114 clientHelloMsg.updateHelloCookie(cookieDigest); |
|
115 byte[] target = cookieDigest.digest(secret); // 32 bytes |
|
116 |
|
117 for (int i = 4; i < 32; i++) { |
|
118 if (cookie[i] != target[i]) { |
|
119 return false; |
|
120 } |
|
121 } |
|
122 |
|
123 return true; |
|
124 } |
198 } |
125 |
199 |
126 // Used by client side to check the cookie in HelloVerifyRequest message. |
200 private static final |
127 static void checkCookie(ProtocolVersion protocolVersion, |
201 class T13HelloCookieManager extends HelloCookieManager { |
128 byte[] cookie) throws IOException { |
202 |
129 if (cookie != null && cookie.length != 0) { |
203 final SecureRandom secureRandom; |
130 int limit = COOKIE_MAX_LENGTH_DTLS12; |
204 private int cookieVersion; // version + sequence |
131 if (protocolVersion.v == ProtocolVersion.DTLS10.v) { |
205 private final byte[] cookieSecret; |
132 limit = COOKIE_MAX_LENGTH_DTLS10; |
206 private final byte[] legacySecret; |
133 } |
207 |
134 |
208 T13HelloCookieManager(SecureRandom secureRandom) { |
135 if (cookie.length > COOKIE_MAX_LENGTH_DTLS10) { |
209 this.secureRandom = secureRandom; |
136 throw new SSLProtocolException( |
210 this.cookieVersion = secureRandom.nextInt(); |
137 "Invalid HelloVerifyRequest.cookie (length = " + |
211 this.cookieSecret = new byte[64]; |
138 cookie.length + " bytes)"); |
212 this.legacySecret = new byte[64]; |
139 } |
213 |
140 } |
214 secureRandom.nextBytes(cookieSecret); |
141 |
215 System.arraycopy(cookieSecret, 0, legacySecret, 0, 64); |
142 // Otherwise, no cookie exchange. |
216 } |
|
217 |
|
218 @Override |
|
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 |
|
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 (!Arrays.equals(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 } |
143 } |
339 } |
144 } |
340 } |