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