|
1 /* |
|
2 * Copyright (c) 2015, 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 package sun.security.ssl; |
|
26 |
|
27 import java.io.IOException; |
|
28 import java.nio.ByteBuffer; |
|
29 import java.security.*; |
|
30 import java.text.MessageFormat; |
|
31 import java.util.List; |
|
32 import java.util.ArrayList; |
|
33 import java.util.Locale; |
|
34 import java.util.Arrays; |
|
35 import java.util.Optional; |
|
36 import javax.crypto.Mac; |
|
37 import javax.crypto.SecretKey; |
|
38 import sun.security.ssl.ClientHello.ClientHelloMessage; |
|
39 import sun.security.ssl.SSLExtension.ExtensionConsumer; |
|
40 import sun.security.ssl.SSLExtension.SSLExtensionSpec; |
|
41 import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
42 import static sun.security.ssl.SSLExtension.*; |
|
43 |
|
44 /** |
|
45 * Pack of the "pre_shared_key" extension. |
|
46 */ |
|
47 final class PreSharedKeyExtension { |
|
48 static final HandshakeProducer chNetworkProducer = |
|
49 new CHPreSharedKeyProducer(); |
|
50 static final ExtensionConsumer chOnLoadConsumer = |
|
51 new CHPreSharedKeyConsumer(); |
|
52 static final HandshakeAbsence chOnLoadAbsence = |
|
53 new CHPreSharedKeyAbsence(); |
|
54 static final HandshakeConsumer chOnTradeConsumer = |
|
55 new CHPreSharedKeyUpdate(); |
|
56 static final SSLStringizer chStringizer = |
|
57 new CHPreSharedKeyStringizer(); |
|
58 |
|
59 static final HandshakeProducer shNetworkProducer = |
|
60 new SHPreSharedKeyProducer(); |
|
61 static final ExtensionConsumer shOnLoadConsumer = |
|
62 new SHPreSharedKeyConsumer(); |
|
63 static final HandshakeAbsence shOnLoadAbsence = |
|
64 new SHPreSharedKeyAbsence(); |
|
65 static final SSLStringizer shStringizer = |
|
66 new SHPreSharedKeyStringizer(); |
|
67 |
|
68 private static final class PskIdentity { |
|
69 final byte[] identity; |
|
70 final int obfuscatedAge; |
|
71 |
|
72 PskIdentity(byte[] identity, int obfuscatedAge) { |
|
73 this.identity = identity; |
|
74 this.obfuscatedAge = obfuscatedAge; |
|
75 } |
|
76 |
|
77 int getEncodedLength() { |
|
78 return 2 + identity.length + 4; |
|
79 } |
|
80 |
|
81 void writeEncoded(ByteBuffer m) throws IOException { |
|
82 Record.putBytes16(m, identity); |
|
83 Record.putInt32(m, obfuscatedAge); |
|
84 } |
|
85 |
|
86 @Override |
|
87 public String toString() { |
|
88 return "{" + Utilities.toHexString(identity) + "," + |
|
89 obfuscatedAge + "}"; |
|
90 } |
|
91 } |
|
92 |
|
93 private static final |
|
94 class CHPreSharedKeySpec implements SSLExtensionSpec { |
|
95 final List<PskIdentity> identities; |
|
96 final List<byte[]> binders; |
|
97 |
|
98 CHPreSharedKeySpec(List<PskIdentity> identities, List<byte[]> binders) { |
|
99 this.identities = identities; |
|
100 this.binders = binders; |
|
101 } |
|
102 |
|
103 CHPreSharedKeySpec(HandshakeContext context, |
|
104 ByteBuffer m) throws IOException { |
|
105 // struct { |
|
106 // PskIdentity identities<7..2^16-1>; |
|
107 // PskBinderEntry binders<33..2^16-1>; |
|
108 // } OfferedPsks; |
|
109 if (m.remaining() < 44) { |
|
110 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
111 "Invalid pre_shared_key extension: " + |
|
112 "insufficient data (length=" + m.remaining() + ")"); |
|
113 } |
|
114 |
|
115 int idEncodedLength = Record.getInt16(m); |
|
116 if (idEncodedLength < 7) { |
|
117 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
118 "Invalid pre_shared_key extension: " + |
|
119 "insufficient identities (length=" + idEncodedLength + ")"); |
|
120 } |
|
121 |
|
122 identities = new ArrayList<>(); |
|
123 int idReadLength = 0; |
|
124 while (idReadLength < idEncodedLength) { |
|
125 byte[] id = Record.getBytes16(m); |
|
126 if (id.length < 1) { |
|
127 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
128 "Invalid pre_shared_key extension: " + |
|
129 "insufficient identity (length=" + id.length + ")"); |
|
130 } |
|
131 int obfuscatedTicketAge = Record.getInt32(m); |
|
132 |
|
133 PskIdentity pskId = new PskIdentity(id, obfuscatedTicketAge); |
|
134 identities.add(pskId); |
|
135 idReadLength += pskId.getEncodedLength(); |
|
136 } |
|
137 |
|
138 if (m.remaining() < 35) { |
|
139 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
140 "Invalid pre_shared_key extension: " + |
|
141 "insufficient binders data (length=" + |
|
142 m.remaining() + ")"); |
|
143 } |
|
144 |
|
145 int bindersEncodedLen = Record.getInt16(m); |
|
146 if (bindersEncodedLen < 33) { |
|
147 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
148 "Invalid pre_shared_key extension: " + |
|
149 "insufficient binders (length=" + |
|
150 bindersEncodedLen + ")"); |
|
151 } |
|
152 |
|
153 binders = new ArrayList<>(); |
|
154 int bindersReadLength = 0; |
|
155 while (bindersReadLength < bindersEncodedLen) { |
|
156 byte[] binder = Record.getBytes8(m); |
|
157 if (binder.length < 32) { |
|
158 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
159 "Invalid pre_shared_key extension: " + |
|
160 "insufficient binder entry (length=" + |
|
161 binder.length + ")"); |
|
162 } |
|
163 binders.add(binder); |
|
164 bindersReadLength += 1 + binder.length; |
|
165 } |
|
166 } |
|
167 |
|
168 int getIdsEncodedLength() { |
|
169 int idEncodedLength = 0; |
|
170 for(PskIdentity curId : identities) { |
|
171 idEncodedLength += curId.getEncodedLength(); |
|
172 } |
|
173 |
|
174 return idEncodedLength; |
|
175 } |
|
176 |
|
177 int getBindersEncodedLength() { |
|
178 int binderEncodedLength = 0; |
|
179 for (byte[] curBinder : binders) { |
|
180 binderEncodedLength += 1 + curBinder.length; |
|
181 } |
|
182 |
|
183 return binderEncodedLength; |
|
184 } |
|
185 |
|
186 byte[] getEncoded() throws IOException { |
|
187 int idsEncodedLength = getIdsEncodedLength(); |
|
188 int bindersEncodedLength = getBindersEncodedLength(); |
|
189 int encodedLength = 4 + idsEncodedLength + bindersEncodedLength; |
|
190 byte[] buffer = new byte[encodedLength]; |
|
191 ByteBuffer m = ByteBuffer.wrap(buffer); |
|
192 Record.putInt16(m, idsEncodedLength); |
|
193 for(PskIdentity curId : identities) { |
|
194 curId.writeEncoded(m); |
|
195 } |
|
196 Record.putInt16(m, bindersEncodedLength); |
|
197 for (byte[] curBinder : binders) { |
|
198 Record.putBytes8(m, curBinder); |
|
199 } |
|
200 |
|
201 return buffer; |
|
202 } |
|
203 |
|
204 @Override |
|
205 public String toString() { |
|
206 MessageFormat messageFormat = new MessageFormat( |
|
207 "\"PreSharedKey\": '{'\n" + |
|
208 " \"identities\" : \"{0}\",\n" + |
|
209 " \"binders\" : \"{1}\",\n" + |
|
210 "'}'", |
|
211 Locale.ENGLISH); |
|
212 |
|
213 Object[] messageFields = { |
|
214 Utilities.indent(identitiesString()), |
|
215 Utilities.indent(bindersString()) |
|
216 }; |
|
217 |
|
218 return messageFormat.format(messageFields); |
|
219 } |
|
220 |
|
221 String identitiesString() { |
|
222 StringBuilder result = new StringBuilder(); |
|
223 for(PskIdentity curId : identities) { |
|
224 result.append(curId.toString() + "\n"); |
|
225 } |
|
226 |
|
227 return result.toString(); |
|
228 } |
|
229 |
|
230 String bindersString() { |
|
231 StringBuilder result = new StringBuilder(); |
|
232 for(byte[] curBinder : binders) { |
|
233 result.append("{" + Utilities.toHexString(curBinder) + "}\n"); |
|
234 } |
|
235 |
|
236 return result.toString(); |
|
237 } |
|
238 } |
|
239 |
|
240 private static final |
|
241 class CHPreSharedKeyStringizer implements SSLStringizer { |
|
242 @Override |
|
243 public String toString(ByteBuffer buffer) { |
|
244 try { |
|
245 // As the HandshakeContext parameter of CHPreSharedKeySpec |
|
246 // constructor is used for fatal alert only, we can use |
|
247 // null HandshakeContext here as we don't care about exception. |
|
248 // |
|
249 // Please take care of this code if the CHPreSharedKeySpec |
|
250 // constructor is updated in the future. |
|
251 return (new CHPreSharedKeySpec(null, buffer)).toString(); |
|
252 } catch (Exception ex) { |
|
253 // For debug logging only, so please swallow exceptions. |
|
254 return ex.getMessage(); |
|
255 } |
|
256 } |
|
257 } |
|
258 |
|
259 private static final |
|
260 class SHPreSharedKeySpec implements SSLExtensionSpec { |
|
261 final int selectedIdentity; |
|
262 |
|
263 SHPreSharedKeySpec(int selectedIdentity) { |
|
264 this.selectedIdentity = selectedIdentity; |
|
265 } |
|
266 |
|
267 SHPreSharedKeySpec(HandshakeContext context, |
|
268 ByteBuffer m) throws IOException { |
|
269 if (m.remaining() < 2) { |
|
270 context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
271 "Invalid pre_shared_key extension: " + |
|
272 "insufficient selected_identity (length=" + |
|
273 m.remaining() + ")"); |
|
274 } |
|
275 this.selectedIdentity = Record.getInt16(m); |
|
276 } |
|
277 |
|
278 byte[] getEncoded() throws IOException { |
|
279 return new byte[] { |
|
280 (byte)((selectedIdentity >> 8) & 0xFF), |
|
281 (byte)(selectedIdentity & 0xFF) |
|
282 }; |
|
283 } |
|
284 |
|
285 @Override |
|
286 public String toString() { |
|
287 MessageFormat messageFormat = new MessageFormat( |
|
288 "\"PreSharedKey\": '{'\n" + |
|
289 " \"selected_identity\" : \"{0}\",\n" + |
|
290 "'}'", |
|
291 Locale.ENGLISH); |
|
292 |
|
293 Object[] messageFields = { |
|
294 Utilities.byte16HexString(selectedIdentity) |
|
295 }; |
|
296 |
|
297 return messageFormat.format(messageFields); |
|
298 } |
|
299 } |
|
300 |
|
301 private static final |
|
302 class SHPreSharedKeyStringizer implements SSLStringizer { |
|
303 @Override |
|
304 public String toString(ByteBuffer buffer) { |
|
305 try { |
|
306 // As the HandshakeContext parameter of SHPreSharedKeySpec |
|
307 // constructor is used for fatal alert only, we can use |
|
308 // null HandshakeContext here as we don't care about exception. |
|
309 // |
|
310 // Please take care of this code if the SHPreSharedKeySpec |
|
311 // constructor is updated in the future. |
|
312 return (new SHPreSharedKeySpec(null, buffer)).toString(); |
|
313 } catch (Exception ex) { |
|
314 // For debug logging only, so please swallow exceptions. |
|
315 return ex.getMessage(); |
|
316 } |
|
317 } |
|
318 } |
|
319 |
|
320 private static final |
|
321 class CHPreSharedKeyConsumer implements ExtensionConsumer { |
|
322 // Prevent instantiation of this class. |
|
323 private CHPreSharedKeyConsumer() { |
|
324 // blank |
|
325 } |
|
326 |
|
327 @Override |
|
328 public void consume(ConnectionContext context, |
|
329 HandshakeMessage message, |
|
330 ByteBuffer buffer) throws IOException { |
|
331 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
332 // Is it a supported and enabled extension? |
|
333 if (!shc.sslConfig.isAvailable(SSLExtension.CH_PRE_SHARED_KEY)) { |
|
334 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
335 SSLLogger.fine( |
|
336 "Ignore unavailable pre_shared_key extension"); |
|
337 } |
|
338 return; // ignore the extension |
|
339 } |
|
340 |
|
341 // Parse the extension. |
|
342 CHPreSharedKeySpec pskSpec = null; |
|
343 try { |
|
344 pskSpec = new CHPreSharedKeySpec(shc, buffer); |
|
345 } catch (IOException ioe) { |
|
346 shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
347 return; // fatal() always throws, make the compiler happy. |
|
348 } |
|
349 |
|
350 // The "psk_key_exchange_modes" extension should have been loaded. |
|
351 if (!shc.handshakeExtensions.containsKey( |
|
352 SSLExtension.PSK_KEY_EXCHANGE_MODES)) { |
|
353 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
354 "Client sent PSK but not PSK modes, or the PSK " + |
|
355 "extension is not the last extension"); |
|
356 } |
|
357 |
|
358 // error if id and binder lists are not the same length |
|
359 if (pskSpec.identities.size() != pskSpec.binders.size()) { |
|
360 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
361 "PSK extension has incorrect number of binders"); |
|
362 } |
|
363 |
|
364 if (shc.isResumption) { // resumingSession may not be set |
|
365 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
366 shc.sslContext.engineGetServerSessionContext(); |
|
367 int idIndex = 0; |
|
368 for (PskIdentity requestedId : pskSpec.identities) { |
|
369 SSLSessionImpl s = sessionCache.get(requestedId.identity); |
|
370 if (s != null && s.isRejoinable() && |
|
371 s.getPreSharedKey().isPresent()) { |
|
372 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
373 SSLLogger.fine("Resuming session: ", s); |
|
374 } |
|
375 |
|
376 // binder will be checked later |
|
377 shc.resumingSession = s; |
|
378 shc.handshakeExtensions.put(SH_PRE_SHARED_KEY, |
|
379 new SHPreSharedKeySpec(idIndex)); // for the index |
|
380 break; |
|
381 } |
|
382 |
|
383 ++idIndex; |
|
384 } |
|
385 |
|
386 if (idIndex == pskSpec.identities.size()) { |
|
387 // no resumable session |
|
388 shc.isResumption = false; |
|
389 shc.resumingSession = null; |
|
390 } |
|
391 } |
|
392 |
|
393 // update the context |
|
394 shc.handshakeExtensions.put( |
|
395 SSLExtension.CH_PRE_SHARED_KEY, pskSpec); |
|
396 } |
|
397 } |
|
398 |
|
399 private static final |
|
400 class CHPreSharedKeyUpdate implements HandshakeConsumer { |
|
401 // Prevent instantiation of this class. |
|
402 private CHPreSharedKeyUpdate() { |
|
403 // blank |
|
404 } |
|
405 |
|
406 @Override |
|
407 public void consume(ConnectionContext context, |
|
408 HandshakeMessage message) throws IOException { |
|
409 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
410 if (!shc.isResumption || shc.resumingSession == null) { |
|
411 // not resuming---nothing to do |
|
412 return; |
|
413 } |
|
414 |
|
415 CHPreSharedKeySpec chPsk = (CHPreSharedKeySpec) |
|
416 shc.handshakeExtensions.get(SSLExtension.CH_PRE_SHARED_KEY); |
|
417 SHPreSharedKeySpec shPsk = (SHPreSharedKeySpec) |
|
418 shc.handshakeExtensions.get(SSLExtension.SH_PRE_SHARED_KEY); |
|
419 if (chPsk == null || shPsk == null) { |
|
420 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
421 "Required extensions are unavailable"); |
|
422 } |
|
423 |
|
424 byte[] binder = chPsk.binders.get(shPsk.selectedIdentity); |
|
425 |
|
426 // set up PSK binder hash |
|
427 HandshakeHash pskBinderHash = shc.handshakeHash.copy(); |
|
428 byte[] lastMessage = pskBinderHash.removeLastReceived(); |
|
429 ByteBuffer messageBuf = ByteBuffer.wrap(lastMessage); |
|
430 // skip the type and length |
|
431 messageBuf.position(4); |
|
432 // read to find the beginning of the binders |
|
433 ClientHelloMessage.readPartial(shc.conContext, messageBuf); |
|
434 int length = messageBuf.position(); |
|
435 messageBuf.position(0); |
|
436 pskBinderHash.receive(messageBuf, length); |
|
437 |
|
438 checkBinder(shc, shc.resumingSession, pskBinderHash, binder); |
|
439 } |
|
440 } |
|
441 |
|
442 private static void checkBinder(ServerHandshakeContext shc, |
|
443 SSLSessionImpl session, |
|
444 HandshakeHash pskBinderHash, byte[] binder) throws IOException { |
|
445 Optional<SecretKey> pskOpt = session.getPreSharedKey(); |
|
446 if (!pskOpt.isPresent()) { |
|
447 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
448 "Session has no PSK"); |
|
449 } |
|
450 SecretKey psk = pskOpt.get(); |
|
451 |
|
452 SecretKey binderKey = deriveBinderKey(psk, session); |
|
453 byte[] computedBinder = |
|
454 computeBinder(binderKey, session, pskBinderHash); |
|
455 if (!Arrays.equals(binder, computedBinder)) { |
|
456 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
457 "Incorect PSK binder value"); |
|
458 } |
|
459 } |
|
460 |
|
461 // Class that produces partial messages used to compute binder hash |
|
462 static final class PartialClientHelloMessage extends HandshakeMessage { |
|
463 |
|
464 private final ClientHello.ClientHelloMessage msg; |
|
465 private final CHPreSharedKeySpec psk; |
|
466 |
|
467 PartialClientHelloMessage(HandshakeContext ctx, |
|
468 ClientHello.ClientHelloMessage msg, |
|
469 CHPreSharedKeySpec psk) { |
|
470 super(ctx); |
|
471 |
|
472 this.msg = msg; |
|
473 this.psk = psk; |
|
474 } |
|
475 |
|
476 @Override |
|
477 SSLHandshake handshakeType() { |
|
478 return msg.handshakeType(); |
|
479 } |
|
480 |
|
481 private int pskTotalLength() { |
|
482 return psk.getIdsEncodedLength() + |
|
483 psk.getBindersEncodedLength() + 8; |
|
484 } |
|
485 |
|
486 @Override |
|
487 int messageLength() { |
|
488 |
|
489 if (msg.extensions.get(SSLExtension.CH_PRE_SHARED_KEY) != null) { |
|
490 return msg.messageLength(); |
|
491 } else { |
|
492 return msg.messageLength() + pskTotalLength(); |
|
493 } |
|
494 } |
|
495 |
|
496 @Override |
|
497 void send(HandshakeOutStream hos) throws IOException { |
|
498 msg.sendCore(hos); |
|
499 |
|
500 // complete extensions |
|
501 int extsLen = msg.extensions.length(); |
|
502 if (msg.extensions.get(SSLExtension.CH_PRE_SHARED_KEY) == null) { |
|
503 extsLen += pskTotalLength(); |
|
504 } |
|
505 hos.putInt16(extsLen - 2); |
|
506 // write the complete extensions |
|
507 for (SSLExtension ext : SSLExtension.values()) { |
|
508 byte[] extData = msg.extensions.get(ext); |
|
509 if (extData == null) { |
|
510 continue; |
|
511 } |
|
512 // the PSK could be there from an earlier round |
|
513 if (ext == SSLExtension.CH_PRE_SHARED_KEY) { |
|
514 continue; |
|
515 } |
|
516 int extID = ext.id; |
|
517 hos.putInt16(extID); |
|
518 hos.putBytes16(extData); |
|
519 } |
|
520 |
|
521 // partial PSK extension |
|
522 int extID = SSLExtension.CH_PRE_SHARED_KEY.id; |
|
523 hos.putInt16(extID); |
|
524 byte[] encodedPsk = psk.getEncoded(); |
|
525 hos.putInt16(encodedPsk.length); |
|
526 hos.write(encodedPsk, 0, psk.getIdsEncodedLength() + 2); |
|
527 } |
|
528 } |
|
529 |
|
530 private static final |
|
531 class CHPreSharedKeyProducer implements HandshakeProducer { |
|
532 // Prevent instantiation of this class. |
|
533 private CHPreSharedKeyProducer() { |
|
534 // blank |
|
535 } |
|
536 |
|
537 @Override |
|
538 public byte[] produce(ConnectionContext context, |
|
539 HandshakeMessage message) throws IOException { |
|
540 |
|
541 // The producing happens in client side only. |
|
542 ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
543 if (!chc.isResumption || chc.resumingSession == null) { |
|
544 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
545 SSLLogger.fine("No session to resume."); |
|
546 } |
|
547 return null; |
|
548 } |
|
549 |
|
550 Optional<SecretKey> pskOpt = chc.resumingSession.getPreSharedKey(); |
|
551 if (!pskOpt.isPresent()) { |
|
552 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
553 SSLLogger.fine("Existing session has no PSK."); |
|
554 } |
|
555 return null; |
|
556 } |
|
557 SecretKey psk = pskOpt.get(); |
|
558 Optional<byte[]> pskIdOpt = chc.resumingSession.getPskIdentity(); |
|
559 if (!pskIdOpt.isPresent()) { |
|
560 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
561 SSLLogger.fine( |
|
562 "PSK has no identity, or identity was already used"); |
|
563 } |
|
564 return null; |
|
565 } |
|
566 byte[] pskId = pskIdOpt.get(); |
|
567 |
|
568 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
569 SSLLogger.fine( |
|
570 "Found resumable session. Preparing PSK message."); |
|
571 } |
|
572 |
|
573 List<PskIdentity> identities = new ArrayList<>(); |
|
574 int ageMillis = (int)(System.currentTimeMillis() - |
|
575 chc.resumingSession.getTicketCreationTime()); |
|
576 int obfuscatedAge = |
|
577 ageMillis + chc.resumingSession.getTicketAgeAdd(); |
|
578 identities.add(new PskIdentity(pskId, obfuscatedAge)); |
|
579 |
|
580 SecretKey binderKey = deriveBinderKey(psk, chc.resumingSession); |
|
581 ClientHelloMessage clientHello = (ClientHelloMessage)message; |
|
582 CHPreSharedKeySpec pskPrototype = createPskPrototype( |
|
583 chc.resumingSession.getSuite().hashAlg.hashLength, identities); |
|
584 HandshakeHash pskBinderHash = chc.handshakeHash.copy(); |
|
585 |
|
586 byte[] binder = computeBinder(binderKey, pskBinderHash, |
|
587 chc.resumingSession, chc, clientHello, pskPrototype); |
|
588 |
|
589 List<byte[]> binders = new ArrayList<>(); |
|
590 binders.add(binder); |
|
591 |
|
592 CHPreSharedKeySpec pskMessage = |
|
593 new CHPreSharedKeySpec(identities, binders); |
|
594 chc.handshakeExtensions.put(CH_PRE_SHARED_KEY, pskMessage); |
|
595 return pskMessage.getEncoded(); |
|
596 } |
|
597 |
|
598 private CHPreSharedKeySpec createPskPrototype( |
|
599 int hashLength, List<PskIdentity> identities) { |
|
600 List<byte[]> binders = new ArrayList<>(); |
|
601 byte[] binderProto = new byte[hashLength]; |
|
602 for (PskIdentity curId : identities) { |
|
603 binders.add(binderProto); |
|
604 } |
|
605 |
|
606 return new CHPreSharedKeySpec(identities, binders); |
|
607 } |
|
608 } |
|
609 |
|
610 private static byte[] computeBinder(SecretKey binderKey, |
|
611 SSLSessionImpl session, |
|
612 HandshakeHash pskBinderHash) throws IOException { |
|
613 |
|
614 pskBinderHash.determine( |
|
615 session.getProtocolVersion(), session.getSuite()); |
|
616 pskBinderHash.update(); |
|
617 byte[] digest = pskBinderHash.digest(); |
|
618 |
|
619 return computeBinder(binderKey, session, digest); |
|
620 } |
|
621 |
|
622 private static byte[] computeBinder(SecretKey binderKey, |
|
623 HandshakeHash hash, SSLSessionImpl session, |
|
624 HandshakeContext ctx, ClientHello.ClientHelloMessage hello, |
|
625 CHPreSharedKeySpec pskPrototype) throws IOException { |
|
626 |
|
627 PartialClientHelloMessage partialMsg = |
|
628 new PartialClientHelloMessage(ctx, hello, pskPrototype); |
|
629 |
|
630 SSLEngineOutputRecord record = new SSLEngineOutputRecord(hash); |
|
631 HandshakeOutStream hos = new HandshakeOutStream(record); |
|
632 partialMsg.write(hos); |
|
633 |
|
634 hash.determine(session.getProtocolVersion(), session.getSuite()); |
|
635 hash.update(); |
|
636 byte[] digest = hash.digest(); |
|
637 |
|
638 return computeBinder(binderKey, session, digest); |
|
639 } |
|
640 |
|
641 private static byte[] computeBinder(SecretKey binderKey, |
|
642 SSLSessionImpl session, byte[] digest) throws IOException { |
|
643 try { |
|
644 CipherSuite.HashAlg hashAlg = session.getSuite().hashAlg; |
|
645 HKDF hkdf = new HKDF(hashAlg.name); |
|
646 byte[] label = ("tls13 finished").getBytes(); |
|
647 byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( |
|
648 label, new byte[0], hashAlg.hashLength); |
|
649 SecretKey finishedKey = hkdf.expand( |
|
650 binderKey, hkdfInfo, hashAlg.hashLength, "TlsBinderKey"); |
|
651 |
|
652 String hmacAlg = |
|
653 "Hmac" + hashAlg.name.replace("-", ""); |
|
654 try { |
|
655 Mac hmac = JsseJce.getMac(hmacAlg); |
|
656 hmac.init(finishedKey); |
|
657 return hmac.doFinal(digest); |
|
658 } catch (NoSuchAlgorithmException | InvalidKeyException ex) { |
|
659 throw new IOException(ex); |
|
660 } |
|
661 } catch(GeneralSecurityException ex) { |
|
662 throw new IOException(ex); |
|
663 } |
|
664 } |
|
665 |
|
666 private static SecretKey deriveBinderKey(SecretKey psk, |
|
667 SSLSessionImpl session) throws IOException { |
|
668 try { |
|
669 CipherSuite.HashAlg hashAlg = session.getSuite().hashAlg; |
|
670 HKDF hkdf = new HKDF(hashAlg.name); |
|
671 byte[] zeros = new byte[hashAlg.hashLength]; |
|
672 SecretKey earlySecret = hkdf.extract(zeros, psk, "TlsEarlySecret"); |
|
673 |
|
674 byte[] label = ("tls13 res binder").getBytes(); |
|
675 MessageDigest md = MessageDigest.getInstance(hashAlg.toString());; |
|
676 byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( |
|
677 label, md.digest(new byte[0]), hashAlg.hashLength); |
|
678 return hkdf.expand(earlySecret, |
|
679 hkdfInfo, hashAlg.hashLength, "TlsBinderKey"); |
|
680 } catch (GeneralSecurityException ex) { |
|
681 throw new IOException(ex); |
|
682 } |
|
683 } |
|
684 |
|
685 private static final |
|
686 class CHPreSharedKeyAbsence implements HandshakeAbsence { |
|
687 @Override |
|
688 public void absent(ConnectionContext context, |
|
689 HandshakeMessage message) throws IOException { |
|
690 |
|
691 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
692 SSLLogger.fine( |
|
693 "Handling pre_shared_key absence."); |
|
694 } |
|
695 |
|
696 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
697 |
|
698 // Resumption is only determined by PSK, when enabled |
|
699 shc.resumingSession = null; |
|
700 shc.isResumption = false; |
|
701 } |
|
702 } |
|
703 |
|
704 private static final |
|
705 class SHPreSharedKeyConsumer implements ExtensionConsumer { |
|
706 // Prevent instantiation of this class. |
|
707 private SHPreSharedKeyConsumer() { |
|
708 // blank |
|
709 } |
|
710 |
|
711 @Override |
|
712 public void consume(ConnectionContext context, |
|
713 HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
714 // The consuming happens in client side only. |
|
715 ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
716 |
|
717 // Is it a response of the specific request? |
|
718 if (!chc.handshakeExtensions.containsKey( |
|
719 SSLExtension.CH_PRE_SHARED_KEY)) { |
|
720 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
721 "Server sent unexpected pre_shared_key extension"); |
|
722 } |
|
723 |
|
724 SHPreSharedKeySpec shPsk = new SHPreSharedKeySpec(chc, buffer); |
|
725 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
726 SSLLogger.fine( |
|
727 "Received pre_shared_key extension: ", shPsk); |
|
728 } |
|
729 |
|
730 // The PSK identity should not be reused, even if it is |
|
731 // not selected. |
|
732 chc.resumingSession.consumePskIdentity(); |
|
733 |
|
734 if (shPsk.selectedIdentity != 0) { |
|
735 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
736 "Selected identity index is not in correct range."); |
|
737 } |
|
738 |
|
739 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
740 SSLLogger.fine( |
|
741 "Resuming session: ", chc.resumingSession); |
|
742 } |
|
743 |
|
744 // remove the session from the cache |
|
745 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
746 chc.sslContext.engineGetClientSessionContext(); |
|
747 sessionCache.remove(chc.resumingSession.getSessionId()); |
|
748 } |
|
749 } |
|
750 |
|
751 private static final |
|
752 class SHPreSharedKeyAbsence implements HandshakeAbsence { |
|
753 @Override |
|
754 public void absent(ConnectionContext context, |
|
755 HandshakeMessage message) throws IOException { |
|
756 ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
757 |
|
758 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
759 SSLLogger.fine("Handling pre_shared_key absence."); |
|
760 } |
|
761 |
|
762 if (chc.handshakeExtensions.containsKey( |
|
763 SSLExtension.CH_PRE_SHARED_KEY)) { |
|
764 // The PSK identity should not be reused, even if it is |
|
765 // not selected. |
|
766 chc.resumingSession.consumePskIdentity(); |
|
767 } |
|
768 |
|
769 // The server refused to resume, or the client did not |
|
770 // request 1.3 resumption. |
|
771 chc.resumingSession = null; |
|
772 chc.isResumption = false; |
|
773 } |
|
774 } |
|
775 |
|
776 private static final |
|
777 class SHPreSharedKeyProducer implements HandshakeProducer { |
|
778 // Prevent instantiation of this class. |
|
779 private SHPreSharedKeyProducer() { |
|
780 // blank |
|
781 } |
|
782 |
|
783 @Override |
|
784 public byte[] produce(ConnectionContext context, |
|
785 HandshakeMessage message) throws IOException { |
|
786 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
787 SHPreSharedKeySpec psk = (SHPreSharedKeySpec) |
|
788 shc.handshakeExtensions.get(SH_PRE_SHARED_KEY); |
|
789 if (psk == null) { |
|
790 return null; |
|
791 } |
|
792 |
|
793 return psk.getEncoded(); |
|
794 } |
|
795 } |
|
796 } |