26 |
26 |
27 import java.io.IOException; |
27 import java.io.IOException; |
28 import java.math.BigInteger; |
28 import java.math.BigInteger; |
29 import java.nio.ByteBuffer; |
29 import java.nio.ByteBuffer; |
30 import java.security.GeneralSecurityException; |
30 import java.security.GeneralSecurityException; |
31 import java.security.ProviderException; |
|
32 import java.security.SecureRandom; |
31 import java.security.SecureRandom; |
33 import java.text.MessageFormat; |
32 import java.text.MessageFormat; |
34 import java.util.Locale; |
33 import java.util.Locale; |
35 import javax.crypto.SecretKey; |
34 import javax.crypto.SecretKey; |
36 import javax.net.ssl.SSLHandshakeException; |
35 import javax.net.ssl.SSLHandshakeException; |
37 import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec; |
36 import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec; |
38 |
37 import sun.security.ssl.SessionTicketExtension.SessionTicketSpec; |
39 import sun.security.ssl.SSLHandshake.HandshakeMessage; |
38 import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
39 import sun.security.util.HexDumpEncoder; |
|
40 |
|
41 import static sun.security.ssl.SSLHandshake.NEW_SESSION_TICKET; |
40 |
42 |
41 /** |
43 /** |
42 * Pack of the NewSessionTicket handshake message. |
44 * Pack of the NewSessionTicket handshake message. |
43 */ |
45 */ |
44 final class NewSessionTicket { |
46 final class NewSessionTicket { |
45 private static final int MAX_TICKET_LIFETIME = 604800; // seconds, 7 days |
47 static final int MAX_TICKET_LIFETIME = 604800; // seconds, 7 days |
46 |
|
47 static final SSLConsumer handshakeConsumer = |
48 static final SSLConsumer handshakeConsumer = |
48 new NewSessionTicketConsumer(); |
49 new T13NewSessionTicketConsumer(); |
|
50 static final SSLConsumer handshake12Consumer = |
|
51 new T12NewSessionTicketConsumer(); |
49 static final SSLProducer kickstartProducer = |
52 static final SSLProducer kickstartProducer = |
50 new NewSessionTicketKickstartProducer(); |
53 new NewSessionTicketKickstartProducer(); |
51 static final HandshakeProducer handshakeProducer = |
54 static final HandshakeProducer handshake12Producer = |
52 new NewSessionTicketProducer(); |
55 new T12NewSessionTicketProducer(); |
53 |
56 |
54 /** |
57 /** |
55 * The NewSessionTicketMessage handshake message. |
58 * The NewSessionTicketMessage handshake messages. |
56 */ |
59 */ |
57 static final class NewSessionTicketMessage extends HandshakeMessage { |
60 abstract static class NewSessionTicketMessage extends HandshakeMessage { |
58 final int ticketLifetime; |
61 int ticketLifetime; |
59 final int ticketAgeAdd; |
62 byte[] ticket = new byte[0]; |
60 final byte[] ticketNonce; |
63 |
61 final byte[] ticket; |
64 NewSessionTicketMessage(HandshakeContext context) { |
62 final SSLExtensions extensions; |
65 super(context); |
63 |
66 } |
64 NewSessionTicketMessage(HandshakeContext context, |
67 |
|
68 @Override |
|
69 public SSLHandshake handshakeType() { |
|
70 return NEW_SESSION_TICKET; |
|
71 } |
|
72 |
|
73 // For TLS 1.3 only |
|
74 int getTicketAgeAdd() throws IOException { |
|
75 throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
76 "TicketAgeAdd not part of RFC 5077."); |
|
77 } |
|
78 |
|
79 // For TLS 1.3 only |
|
80 byte[] getTicketNonce() throws IOException { |
|
81 throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
82 "TicketNonce not part of RFC 5077."); |
|
83 } |
|
84 |
|
85 boolean isValid() { |
|
86 return (ticket.length > 0); |
|
87 } |
|
88 } |
|
89 /** |
|
90 * NewSessionTicket for TLS 1.2 and below (RFC 5077) |
|
91 */ |
|
92 static final class T12NewSessionTicketMessage extends NewSessionTicketMessage { |
|
93 |
|
94 T12NewSessionTicketMessage(HandshakeContext context, |
|
95 int ticketLifetime, byte[] ticket) { |
|
96 super(context); |
|
97 |
|
98 this.ticketLifetime = ticketLifetime; |
|
99 this.ticket = ticket; |
|
100 } |
|
101 |
|
102 T12NewSessionTicketMessage(HandshakeContext context, |
|
103 ByteBuffer m) throws IOException { |
|
104 |
|
105 // RFC5077 struct { |
|
106 // uint32 ticket_lifetime; |
|
107 // opaque ticket<0..2^16-1>; |
|
108 // } NewSessionTicket; |
|
109 |
|
110 super(context); |
|
111 if (m.remaining() < 6) { |
|
112 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
113 "Invalid NewSessionTicket message: insufficient data"); |
|
114 } |
|
115 |
|
116 this.ticketLifetime = Record.getInt32(m); |
|
117 this.ticket = Record.getBytes16(m); |
|
118 } |
|
119 |
|
120 @Override |
|
121 public SSLHandshake handshakeType() { |
|
122 return NEW_SESSION_TICKET; |
|
123 } |
|
124 |
|
125 @Override |
|
126 public int messageLength() { |
|
127 return 4 + // ticketLifetime |
|
128 2 + ticket.length; // len of ticket + ticket |
|
129 } |
|
130 |
|
131 @Override |
|
132 public void send(HandshakeOutStream hos) throws IOException { |
|
133 hos.putInt32(ticketLifetime); |
|
134 hos.putBytes16(ticket); |
|
135 } |
|
136 |
|
137 @Override |
|
138 public String toString() { |
|
139 MessageFormat messageFormat = new MessageFormat( |
|
140 "\"NewSessionTicket\": '{'\n" + |
|
141 " \"ticket_lifetime\" : \"{0}\",\n" + |
|
142 " \"ticket\" : '{'\n" + |
|
143 "{1}\n" + |
|
144 " '}'" + |
|
145 "'}'", |
|
146 Locale.ENGLISH); |
|
147 |
|
148 HexDumpEncoder hexEncoder = new HexDumpEncoder(); |
|
149 Object[] messageFields = { |
|
150 ticketLifetime, |
|
151 Utilities.indent(hexEncoder.encode(ticket), " "), |
|
152 }; |
|
153 return messageFormat.format(messageFields); |
|
154 } |
|
155 } |
|
156 |
|
157 /** |
|
158 * NewSessionTicket defined by the TLS 1.3 |
|
159 */ |
|
160 static final class T13NewSessionTicketMessage extends NewSessionTicketMessage { |
|
161 int ticketAgeAdd; |
|
162 byte[] ticketNonce; |
|
163 SSLExtensions extensions; |
|
164 |
|
165 T13NewSessionTicketMessage(HandshakeContext context, |
65 int ticketLifetime, SecureRandom generator, |
166 int ticketLifetime, SecureRandom generator, |
66 byte[] ticketNonce, byte[] ticket) { |
167 byte[] ticketNonce, byte[] ticket) { |
67 super(context); |
168 super(context); |
68 |
169 |
69 this.ticketLifetime = ticketLifetime; |
170 this.ticketLifetime = ticketLifetime; |
71 this.ticketNonce = ticketNonce; |
172 this.ticketNonce = ticketNonce; |
72 this.ticket = ticket; |
173 this.ticket = ticket; |
73 this.extensions = new SSLExtensions(this); |
174 this.extensions = new SSLExtensions(this); |
74 } |
175 } |
75 |
176 |
76 NewSessionTicketMessage(HandshakeContext context, |
177 T13NewSessionTicketMessage(HandshakeContext context, |
77 ByteBuffer m) throws IOException { |
178 ByteBuffer m) throws IOException { |
78 super(context); |
179 super(context); |
79 |
180 |
80 // struct { |
181 // struct { |
81 // uint32 ticket_lifetime; |
182 // uint32 ticket_lifetime; |
82 // uint32 ticket_age_add; |
183 // uint32 ticket_age_add; |
83 // opaque ticket_nonce<0..255>; |
184 // opaque ticket_nonce<0..255>; |
84 // opaque ticket<1..2^16-1>; |
185 // opaque ticket<1..2^16-1>; |
85 // Extension extensions<0..2^16-2>; |
186 // Extension extensions<0..2^16-2>; |
86 // } NewSessionTicket; |
187 // } NewSessionTicket; |
|
188 |
87 if (m.remaining() < 14) { |
189 if (m.remaining() < 14) { |
88 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
190 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
89 "Invalid NewSessionTicket message: no sufficient data"); |
191 "Invalid NewSessionTicket message: insufficient data"); |
90 } |
192 } |
91 |
193 |
92 this.ticketLifetime = Record.getInt32(m); |
194 this.ticketLifetime = Record.getInt32(m); |
93 this.ticketAgeAdd = Record.getInt32(m); |
195 this.ticketAgeAdd = Record.getInt32(m); |
94 this.ticketNonce = Record.getBytes8(m); |
196 this.ticketNonce = Record.getBytes8(m); |
95 |
197 |
96 if (m.remaining() < 5) { |
198 if (m.remaining() < 5) { |
97 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
199 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
98 "Invalid NewSessionTicket message: no sufficient data"); |
200 "Invalid NewSessionTicket message: insufficient ticket" + |
|
201 " data"); |
99 } |
202 } |
100 |
203 |
101 this.ticket = Record.getBytes16(m); |
204 this.ticket = Record.getBytes16(m); |
102 if (ticket.length == 0) { |
205 if (ticket.length == 0) { |
103 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
206 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
207 SSLLogger.fine( |
104 "No ticket in the NewSessionTicket handshake message"); |
208 "No ticket in the NewSessionTicket handshake message"); |
|
209 } |
105 } |
210 } |
106 |
211 |
107 if (m.remaining() < 2) { |
212 if (m.remaining() < 2) { |
108 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
213 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
109 "Invalid NewSessionTicket message: no sufficient data"); |
214 "Invalid NewSessionTicket message: extra data"); |
110 } |
215 } |
111 |
216 |
112 SSLExtension[] supportedExtensions = |
217 SSLExtension[] supportedExtensions = |
113 context.sslConfig.getEnabledExtensions( |
218 context.sslConfig.getEnabledExtensions( |
114 SSLHandshake.NEW_SESSION_TICKET); |
219 NEW_SESSION_TICKET); |
115 this.extensions = new SSLExtensions(this, m, supportedExtensions); |
220 this.extensions = new SSLExtensions(this, m, supportedExtensions); |
116 } |
221 } |
117 |
222 |
118 @Override |
223 @Override |
119 public SSLHandshake handshakeType() { |
224 public SSLHandshake handshakeType() { |
120 return SSLHandshake.NEW_SESSION_TICKET; |
225 return NEW_SESSION_TICKET; |
|
226 } |
|
227 |
|
228 int getTicketAgeAdd() { |
|
229 return ticketAgeAdd; |
|
230 } |
|
231 |
|
232 byte[] getTicketNonce() { |
|
233 return ticketNonce; |
121 } |
234 } |
122 |
235 |
123 @Override |
236 @Override |
124 public int messageLength() { |
237 public int messageLength() { |
|
238 |
125 int extLen = extensions.length(); |
239 int extLen = extensions.length(); |
126 if (extLen == 0) { |
240 if (extLen == 0) { |
127 extLen = 2; // empty extensions |
241 extLen = 2; // empty extensions |
128 } |
242 } |
129 |
243 |
130 return 8 + ticketNonce.length + 1 + |
244 return 4 +// ticketLifetime |
131 ticket.length + 2 + extLen; |
245 4 + // ticketAgeAdd |
|
246 1 + ticketNonce.length + // len of nonce + nonce |
|
247 2 + ticket.length + // len of ticket + ticket |
|
248 extLen; |
132 } |
249 } |
133 |
250 |
134 @Override |
251 @Override |
135 public void send(HandshakeOutStream hos) throws IOException { |
252 public void send(HandshakeOutStream hos) throws IOException { |
136 hos.putInt32(ticketLifetime); |
253 hos.putInt32(ticketLifetime); |
193 // blank |
313 // blank |
194 } |
314 } |
195 |
315 |
196 @Override |
316 @Override |
197 public byte[] produce(ConnectionContext context) throws IOException { |
317 public byte[] produce(ConnectionContext context) throws IOException { |
|
318 HandshakeContext hc = (HandshakeContext)context; |
|
319 |
198 // The producing happens in server side only. |
320 // The producing happens in server side only. |
|
321 if (hc instanceof ServerHandshakeContext) { |
|
322 // Is this session resumable? |
|
323 if (!hc.handshakeSession.isRejoinable()) { |
|
324 return null; |
|
325 } |
|
326 |
|
327 // What's the requested PSK key exchange modes? |
|
328 // |
|
329 // Note that currently, the NewSessionTicket post-handshake is |
|
330 // produced and delivered only in the current handshake context |
|
331 // if required. |
|
332 PskKeyExchangeModesSpec pkemSpec = |
|
333 (PskKeyExchangeModesSpec) hc.handshakeExtensions.get( |
|
334 SSLExtension.PSK_KEY_EXCHANGE_MODES); |
|
335 if (pkemSpec == null || !pkemSpec.contains( |
|
336 PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) { |
|
337 // Client doesn't support PSK with (EC)DHE key establishment. |
|
338 return null; |
|
339 } |
|
340 } else { // PostHandshakeContext |
|
341 |
|
342 // Check if we have sent a PSK already, then we know it is using a |
|
343 // allowable PSK exchange key mode |
|
344 if (!hc.handshakeSession.isPSKable()) { |
|
345 return null; |
|
346 } |
|
347 } |
|
348 |
|
349 // get a new session ID |
|
350 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
351 hc.sslContext.engineGetServerSessionContext(); |
|
352 SessionId newId = new SessionId(true, |
|
353 hc.sslContext.getSecureRandom()); |
|
354 |
|
355 SecretKey resumptionMasterSecret = |
|
356 hc.handshakeSession.getResumptionMasterSecret(); |
|
357 if (resumptionMasterSecret == null) { |
|
358 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
359 SSLLogger.fine( |
|
360 "Session has no resumption secret. No ticket sent."); |
|
361 } |
|
362 return null; |
|
363 } |
|
364 |
|
365 // construct the PSK and handshake message |
|
366 BigInteger nonce = hc.handshakeSession.incrTicketNonceCounter(); |
|
367 byte[] nonceArr = nonce.toByteArray(); |
|
368 SecretKey psk = derivePreSharedKey( |
|
369 hc.negotiatedCipherSuite.hashAlg, |
|
370 resumptionMasterSecret, nonceArr); |
|
371 |
|
372 int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); |
|
373 if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { |
|
374 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
375 SSLLogger.fine( |
|
376 "Session timeout is too long. No ticket sent."); |
|
377 } |
|
378 return null; |
|
379 } |
|
380 |
|
381 NewSessionTicketMessage nstm = null; |
|
382 |
|
383 SSLSessionImpl sessionCopy = |
|
384 new SSLSessionImpl(hc.handshakeSession, newId); |
|
385 sessionCopy.setPreSharedKey(psk); |
|
386 sessionCopy.setPskIdentity(newId.getId()); |
|
387 |
|
388 // If a stateless ticket is allowed, attempt to make one |
|
389 if (hc.handshakeSession.isStatelessable(hc)) { |
|
390 nstm = new T13NewSessionTicketMessage(hc, |
|
391 sessionTimeoutSeconds, |
|
392 hc.sslContext.getSecureRandom(), |
|
393 nonceArr, |
|
394 new SessionTicketSpec().encrypt(hc, sessionCopy)); |
|
395 // If ticket construction failed, switch to session cache |
|
396 if (!nstm.isValid()) { |
|
397 hc.statelessResumption = false; |
|
398 } else { |
|
399 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
400 SSLLogger.fine( |
|
401 "Produced NewSessionTicket stateless " + |
|
402 "handshake message", nstm); |
|
403 } |
|
404 } |
|
405 } |
|
406 // If a session cache ticket is being used, make one |
|
407 if (!hc.handshakeSession.isStatelessable(hc)) { |
|
408 nstm = new T13NewSessionTicketMessage(hc, sessionTimeoutSeconds, |
|
409 hc.sslContext.getSecureRandom(), nonceArr, |
|
410 newId.getId()); |
|
411 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
412 SSLLogger.fine( |
|
413 "Produced NewSessionTicket handshake message", |
|
414 nstm); |
|
415 } |
|
416 |
|
417 // create and cache the new session |
|
418 // The new session must be a child of the existing session so |
|
419 // they will be invalidated together, etc. |
|
420 hc.handshakeSession.addChild(sessionCopy); |
|
421 sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd()); |
|
422 sessionCache.put(sessionCopy); |
|
423 } |
|
424 |
|
425 // Output the handshake message. |
|
426 if (nstm != null) { |
|
427 // should never be null |
|
428 nstm.write(hc.handshakeOutput); |
|
429 hc.handshakeOutput.flush(); |
|
430 } |
|
431 |
|
432 if (hc instanceof PostHandshakeContext) { |
|
433 ((PostHandshakeContext) hc).finish(); |
|
434 } |
|
435 |
|
436 // The message has been delivered. |
|
437 return null; |
|
438 } |
|
439 } |
|
440 |
|
441 /** |
|
442 * The "NewSessionTicket" handshake message producer for RFC 5077 |
|
443 */ |
|
444 private static final class T12NewSessionTicketProducer |
|
445 implements HandshakeProducer { |
|
446 |
|
447 // Prevent instantiation of this class. |
|
448 private T12NewSessionTicketProducer() { |
|
449 // blank |
|
450 } |
|
451 |
|
452 @Override |
|
453 public byte[] produce(ConnectionContext context, |
|
454 HandshakeMessage message) throws IOException { |
|
455 |
199 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
456 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
200 |
457 |
201 // Is this session resumable? |
458 // Is this session resumable? |
202 if (!shc.handshakeSession.isRejoinable()) { |
459 if (!shc.handshakeSession.isRejoinable()) { |
203 return null; |
460 return null; |
204 } |
461 } |
205 |
462 |
206 // What's the requested PSK key exchange modes? |
|
207 // |
|
208 // Note that currently, the NewSessionTicket post-handshake is |
|
209 // produced and delivered only in the current handshake context |
|
210 // if required. |
|
211 PskKeyExchangeModesSpec pkemSpec = |
|
212 (PskKeyExchangeModesSpec)shc.handshakeExtensions.get( |
|
213 SSLExtension.PSK_KEY_EXCHANGE_MODES); |
|
214 if (pkemSpec == null || !pkemSpec.contains( |
|
215 PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) { |
|
216 // Client doesn't support PSK with (EC)DHE key establishment. |
|
217 return null; |
|
218 } |
|
219 |
|
220 // get a new session ID |
463 // get a new session ID |
|
464 SessionId newId = shc.handshakeSession.getSessionId(); |
|
465 |
221 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
466 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
222 shc.sslContext.engineGetServerSessionContext(); |
467 shc.sslContext.engineGetServerSessionContext(); |
223 SessionId newId = new SessionId(true, |
|
224 shc.sslContext.getSecureRandom()); |
|
225 |
|
226 SecretKey resumptionMasterSecret = |
|
227 shc.handshakeSession.getResumptionMasterSecret(); |
|
228 if (resumptionMasterSecret == null) { |
|
229 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
230 SSLLogger.fine( |
|
231 "Session has no resumption secret. No ticket sent."); |
|
232 } |
|
233 return null; |
|
234 } |
|
235 |
|
236 // construct the PSK and handshake message |
|
237 BigInteger nonce = shc.handshakeSession.incrTicketNonceCounter(); |
|
238 byte[] nonceArr = nonce.toByteArray(); |
|
239 SecretKey psk = derivePreSharedKey( |
|
240 shc.negotiatedCipherSuite.hashAlg, |
|
241 resumptionMasterSecret, nonceArr); |
|
242 |
|
243 int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); |
468 int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); |
244 if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { |
469 if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { |
245 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
470 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
246 SSLLogger.fine( |
471 SSLLogger.fine( |
247 "Session timeout is too long. No ticket sent."); |
472 "Session timeout is too long. No ticket sent."); |
248 } |
473 } |
249 return null; |
474 return null; |
250 } |
475 } |
251 NewSessionTicketMessage nstm = new NewSessionTicketMessage(shc, |
476 |
252 sessionTimeoutSeconds, shc.sslContext.getSecureRandom(), |
477 SSLSessionImpl sessionCopy = |
253 nonceArr, newId.getId()); |
478 new SSLSessionImpl(shc.handshakeSession, newId); |
|
479 sessionCopy.setPskIdentity(newId.getId()); |
|
480 |
|
481 NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(shc, |
|
482 sessionTimeoutSeconds, |
|
483 new SessionTicketSpec().encrypt(shc, sessionCopy)); |
254 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
484 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
255 SSLLogger.fine( |
485 SSLLogger.fine( |
256 "Produced NewSessionTicket handshake message", nstm); |
486 "Produced NewSessionTicket stateless handshake message", nstm); |
257 } |
487 } |
258 |
|
259 // create and cache the new session |
|
260 // The new session must be a child of the existing session so |
|
261 // they will be invalidated together, etc. |
|
262 SSLSessionImpl sessionCopy = |
|
263 new SSLSessionImpl(shc.handshakeSession, newId); |
|
264 shc.handshakeSession.addChild(sessionCopy); |
|
265 sessionCopy.setPreSharedKey(psk); |
|
266 sessionCopy.setPskIdentity(newId.getId()); |
|
267 sessionCopy.setTicketAgeAdd(nstm.ticketAgeAdd); |
|
268 sessionCache.put(sessionCopy); |
|
269 |
488 |
270 // Output the handshake message. |
489 // Output the handshake message. |
271 nstm.write(shc.handshakeOutput); |
490 nstm.write(shc.handshakeOutput); |
272 shc.handshakeOutput.flush(); |
491 shc.handshakeOutput.flush(); |
273 |
492 |
274 // The message has been delivered. |
493 // The message has been delivered. |
275 return null; |
494 return null; |
276 } |
495 } |
277 } |
496 } |
278 |
497 |
279 /** |
498 private static final |
280 * The "NewSessionTicket" handshake message producer. |
499 class T13NewSessionTicketConsumer implements SSLConsumer { |
281 */ |
|
282 private static final class NewSessionTicketProducer |
|
283 implements HandshakeProducer { |
|
284 |
|
285 // Prevent instantiation of this class. |
500 // Prevent instantiation of this class. |
286 private NewSessionTicketProducer() { |
501 private T13NewSessionTicketConsumer() { |
287 // blank |
502 // blank |
288 } |
503 } |
289 |
504 |
290 @Override |
505 @Override |
291 public byte[] produce(ConnectionContext context, |
|
292 HandshakeMessage message) throws IOException { |
|
293 |
|
294 // NSTM may be sent in response to handshake messages. |
|
295 // For example: key update |
|
296 |
|
297 throw new ProviderException( |
|
298 "NewSessionTicket handshake producer not implemented"); |
|
299 } |
|
300 } |
|
301 |
|
302 private static final |
|
303 class NewSessionTicketConsumer implements SSLConsumer { |
|
304 // Prevent instantiation of this class. |
|
305 private NewSessionTicketConsumer() { |
|
306 // blank |
|
307 } |
|
308 |
|
309 @Override |
|
310 public void consume(ConnectionContext context, |
506 public void consume(ConnectionContext context, |
311 ByteBuffer message) throws IOException { |
507 ByteBuffer message) throws IOException { |
312 |
508 |
313 // Note: Although the resumption master secret depends on the |
509 // Note: Although the resumption master secret depends on the |
314 // client's second flight, servers which do not request client |
510 // client's second flight, servers which do not request client |
315 // authentication MAY compute the remainder of the transcript |
511 // authentication MAY compute the remainder of the transcript |
316 // independently and then send a NewSessionTicket immediately |
512 // independently and then send a NewSessionTicket immediately |
317 // upon sending its Finished rather than waiting for the client |
513 // upon sending its Finished rather than waiting for the client |
318 // Finished. |
514 // Finished. |
319 // |
515 // |
320 // The consuming happens in client side only. As the server |
516 // The consuming happens in client side only and is received after |
321 // may send the NewSessionTicket before handshake complete, the |
517 // the server's Finished message with PostHandshakeContext. |
322 // context may be a PostHandshakeContext or HandshakeContext |
518 |
323 // instance. |
|
324 HandshakeContext hc = (HandshakeContext)context; |
519 HandshakeContext hc = (HandshakeContext)context; |
325 NewSessionTicketMessage nstm = |
520 NewSessionTicketMessage nstm = |
326 new NewSessionTicketMessage(hc, message); |
521 new T13NewSessionTicketMessage(hc, message); |
327 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
522 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
328 SSLLogger.fine( |
523 SSLLogger.fine( |
329 "Consuming NewSessionTicket message", nstm); |
524 "Consuming NewSessionTicket message", nstm); |
330 } |
525 } |
331 |
526 |
|
527 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
528 hc.sslContext.engineGetClientSessionContext(); |
|
529 |
332 // discard tickets with timeout 0 |
530 // discard tickets with timeout 0 |
333 if (nstm.ticketLifetime <= 0 || |
531 if (nstm.ticketLifetime <= 0 || |
334 nstm.ticketLifetime > MAX_TICKET_LIFETIME) { |
532 nstm.ticketLifetime > MAX_TICKET_LIFETIME) { |
335 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
533 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
336 SSLLogger.fine( |
534 SSLLogger.fine( |
337 "Discarding NewSessionTicket with lifetime " |
535 "Discarding NewSessionTicket with lifetime " |
338 + nstm.ticketLifetime, nstm); |
536 + nstm.ticketLifetime, nstm); |
339 } |
537 } |
|
538 sessionCache.remove(hc.handshakeSession.getSessionId()); |
340 return; |
539 return; |
341 } |
540 } |
342 |
541 |
343 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
344 hc.sslContext.engineGetClientSessionContext(); |
|
345 |
|
346 if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { |
542 if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { |
347 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
543 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
348 SSLLogger.fine( |
544 SSLLogger.fine( |
349 "Session cache lifetime is too long. Discarding ticket."); |
545 "Session cache lifetime is too long. Discarding ticket."); |
350 } |
546 } |
351 return; |
547 return; |
352 } |
548 } |
353 |
549 |
354 SSLSessionImpl sessionToSave = hc.conContext.conSession; |
550 SSLSessionImpl sessionToSave = hc.conContext.conSession; |
355 |
551 SecretKey psk = null; |
356 SecretKey resumptionMasterSecret = |
552 if (hc.negotiatedProtocol.useTLS13PlusSpec()) { |
357 sessionToSave.getResumptionMasterSecret(); |
553 SecretKey resumptionMasterSecret = |
358 if (resumptionMasterSecret == null) { |
554 sessionToSave.getResumptionMasterSecret(); |
359 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
555 if (resumptionMasterSecret == null) { |
360 SSLLogger.fine( |
556 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
361 "Session has no resumption master secret. Ignoring ticket."); |
557 SSLLogger.fine( |
362 } |
558 "Session has no resumption master secret." + |
363 return; |
559 " Ignoring ticket."); |
364 } |
560 } |
365 |
561 return; |
366 // derive the PSK |
562 } |
367 SecretKey psk = derivePreSharedKey( |
563 |
368 sessionToSave.getSuite().hashAlg, resumptionMasterSecret, |
564 // derive the PSK |
369 nstm.ticketNonce); |
565 psk = derivePreSharedKey( |
|
566 sessionToSave.getSuite().hashAlg, |
|
567 resumptionMasterSecret, nstm.getTicketNonce()); |
|
568 } |
370 |
569 |
371 // create and cache the new session |
570 // create and cache the new session |
372 // The new session must be a child of the existing session so |
571 // The new session must be a child of the existing session so |
373 // they will be invalidated together, etc. |
572 // they will be invalidated together, etc. |
374 SessionId newId = |
573 SessionId newId = |
375 new SessionId(true, hc.sslContext.getSecureRandom()); |
574 new SessionId(true, hc.sslContext.getSecureRandom()); |
376 SSLSessionImpl sessionCopy = new SSLSessionImpl(sessionToSave, |
575 SSLSessionImpl sessionCopy = new SSLSessionImpl(sessionToSave, |
377 newId); |
576 newId); |
378 sessionToSave.addChild(sessionCopy); |
577 sessionToSave.addChild(sessionCopy); |
379 sessionCopy.setPreSharedKey(psk); |
578 sessionCopy.setPreSharedKey(psk); |
380 sessionCopy.setTicketAgeAdd(nstm.ticketAgeAdd); |
579 sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd()); |
381 sessionCopy.setPskIdentity(nstm.ticket); |
580 sessionCopy.setPskIdentity(nstm.ticket); |
382 sessionCache.put(sessionCopy); |
581 sessionCache.put(sessionCopy); |
383 |
582 |
384 // clean handshake context |
583 // clean handshake context |
385 hc.conContext.finishPostHandshake(); |
584 if (hc.negotiatedProtocol.useTLS13PlusSpec()) { |
|
585 hc.conContext.finishPostHandshake(); |
|
586 } |
|
587 } |
|
588 } |
|
589 |
|
590 private static final |
|
591 class T12NewSessionTicketConsumer implements SSLConsumer { |
|
592 // Prevent instantiation of this class. |
|
593 private T12NewSessionTicketConsumer() { |
|
594 // blank |
|
595 } |
|
596 |
|
597 @Override |
|
598 public void consume(ConnectionContext context, |
|
599 ByteBuffer message) throws IOException { |
|
600 |
|
601 HandshakeContext hc = (HandshakeContext)context; |
|
602 hc.handshakeConsumers.remove(NEW_SESSION_TICKET.id); |
|
603 |
|
604 NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(hc, |
|
605 message); |
|
606 if (nstm.ticket.length == 0) { |
|
607 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
608 SSLLogger.fine("NewSessionTicket ticket was empty"); |
|
609 } |
|
610 return; |
|
611 } |
|
612 |
|
613 // discard tickets with timeout 0 |
|
614 if (nstm.ticketLifetime <= 0 || |
|
615 nstm.ticketLifetime > MAX_TICKET_LIFETIME) { |
|
616 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
617 SSLLogger.fine( |
|
618 "Discarding NewSessionTicket with lifetime " |
|
619 + nstm.ticketLifetime, nstm); |
|
620 } |
|
621 return; |
|
622 } |
|
623 |
|
624 SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) |
|
625 hc.sslContext.engineGetClientSessionContext(); |
|
626 |
|
627 if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { |
|
628 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
629 SSLLogger.fine( |
|
630 "Session cache lifetime is too long. Discarding ticket."); |
|
631 } |
|
632 return; |
|
633 } |
|
634 |
|
635 hc.handshakeSession.setPskIdentity(nstm.ticket); |
|
636 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
637 SSLLogger.fine("Consuming NewSessionTicket\n" + |
|
638 nstm.toString()); |
|
639 } |
386 } |
640 } |
387 } |
641 } |
388 } |
642 } |
389 |
643 |