< prev index next >

src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java

Print this page




  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.math.BigInteger;
  29 import java.nio.ByteBuffer;
  30 import java.security.GeneralSecurityException;

  31 import java.security.SecureRandom;
  32 import java.text.MessageFormat;
  33 import java.util.Locale;
  34 import javax.crypto.SecretKey;
  35 import javax.net.ssl.SSLHandshakeException;
  36 import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec;
  37 import sun.security.ssl.SessionTicketExtension.SessionTicketSpec;
  38 import sun.security.ssl.SSLHandshake.HandshakeMessage;
  39 import sun.security.util.HexDumpEncoder;
  40 
  41 import static sun.security.ssl.SSLHandshake.NEW_SESSION_TICKET;
  42 
  43 /**
  44  * Pack of the NewSessionTicket handshake message.
  45  */
  46 final class NewSessionTicket {
  47     static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
  48 
  49     static final SSLConsumer handshakeConsumer =
  50         new T13NewSessionTicketConsumer();
  51     static final SSLConsumer handshake12Consumer =
  52         new T12NewSessionTicketConsumer();
  53     static final SSLProducer kickstartProducer =
  54         new NewSessionTicketKickstartProducer();
  55     static final HandshakeProducer handshake12Producer =
  56         new T12NewSessionTicketProducer();
  57 
  58     /**
  59      * The NewSessionTicketMessage handshake messages.
  60      */
  61     abstract static class NewSessionTicketMessage extends HandshakeMessage {
  62         int ticketLifetime;
  63         byte[] ticket;
  64 
  65         NewSessionTicketMessage(HandshakeContext context) {
  66             super(context);
  67         }
  68 
  69         @Override
  70         public SSLHandshake handshakeType() {
  71             return NEW_SESSION_TICKET;
  72         }
  73 
  74         // For TLS 1.3 only
  75         int getTicketAgeAdd() throws IOException {
  76             throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
  77                     "TicketAgeAdd not part of RFC 5077.");
  78         }
  79 
  80         // For TLS 1.3 only
  81         byte[] getTicketNonce() throws IOException {
  82             throw handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
  83                     "TicketNonce not part of RFC 5077.");
  84         }
  85 
  86     }
  87     /**
  88      * NewSessionTicket for TLS 1.2 and below (RFC 5077)
  89      */
  90     static final class T12NewSessionTicketMessage extends NewSessionTicketMessage {
  91 
  92         T12NewSessionTicketMessage(HandshakeContext context,
  93                 int ticketLifetime, byte[] ticket) {
  94             super(context);
  95 
  96             this.ticketLifetime = ticketLifetime;
  97             this.ticket = ticket;
  98         }
  99 
 100         T12NewSessionTicketMessage(HandshakeContext context,
 101                 ByteBuffer m) throws IOException {
 102 
 103             // RFC5077 struct {
 104             //     uint32 ticket_lifetime;
 105             //     opaque ticket<1..2^16-1>;
 106             // } NewSessionTicket;
 107 
 108             super(context);
 109             if (m.remaining() < 14) {
 110                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 111                         "Invalid NewSessionTicket message: no sufficient data");
 112             }
 113 
 114             this.ticketLifetime = Record.getInt32(m);
 115             this.ticket = Record.getBytes16(m);
 116         }
 117 
 118         @Override
 119         public SSLHandshake handshakeType() {
 120             return NEW_SESSION_TICKET;
 121         }
 122 
 123         @Override
 124         public int messageLength() {
 125             return 4 + // ticketLifetime
 126                     2 + ticket.length;  // len of ticket + ticket
 127         }
 128 
 129         @Override
 130         public void send(HandshakeOutStream hos) throws IOException {
 131             hos.putInt32(ticketLifetime);
 132             hos.putBytes16(ticket);
 133         }
 134 
 135         @Override
 136         public String toString() {
 137             MessageFormat messageFormat = new MessageFormat(
 138                     "\"NewSessionTicket\": '{'\n" +
 139                             "  \"ticket_lifetime\"      : \"{0}\",\n" +
 140                             "  \"ticket\"               : '{'\n" +
 141                             "{1}\n" +
 142                             "  '}'" +
 143                             "'}'",
 144                 Locale.ENGLISH);
 145 
 146             HexDumpEncoder hexEncoder = new HexDumpEncoder();
 147             Object[] messageFields = {
 148                     ticketLifetime,
 149                     Utilities.indent(hexEncoder.encode(ticket), "    "),
 150             };
 151             return messageFormat.format(messageFields);
 152         }
 153     }
 154 
 155     /**
 156      * NewSessionTicket defined by the TLS 1.3
 157      */
 158     static final class T13NewSessionTicketMessage extends NewSessionTicketMessage {
 159         int ticketAgeAdd;
 160         byte[] ticketNonce;
 161         SSLExtensions extensions;
 162 
 163         T13NewSessionTicketMessage(HandshakeContext context,
 164                 int ticketLifetime, SecureRandom generator,
 165                 byte[] ticketNonce, byte[] ticket) {
 166             super(context);
 167 
 168             this.ticketLifetime = ticketLifetime;
 169             this.ticketAgeAdd = generator.nextInt();
 170             this.ticketNonce = ticketNonce;
 171             this.ticket = ticket;
 172             this.extensions = new SSLExtensions(this);
 173         }
 174 
 175         T13NewSessionTicketMessage(HandshakeContext context,
 176                 ByteBuffer m) throws IOException {
 177             super(context);
 178 
 179             // struct {
 180             //     uint32 ticket_lifetime;
 181             //     uint32 ticket_age_add;
 182             //     opaque ticket_nonce<0..255>;
 183             //     opaque ticket<1..2^16-1>;
 184             //     Extension extensions<0..2^16-2>;
 185             // } NewSessionTicket;
 186 
 187             if (m.remaining() < 14) {
 188                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 189                     "Invalid NewSessionTicket message: no sufficient data");
 190             }
 191 
 192             this.ticketLifetime = Record.getInt32(m);
 193             this.ticketAgeAdd = Record.getInt32(m);
 194             this.ticketNonce = Record.getBytes8(m);
 195 
 196             if (m.remaining() < 5) {
 197                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 198                     "Invalid NewSessionTicket message: no sufficient data");
 199             }
 200 
 201             this.ticket = Record.getBytes16(m);
 202             if (ticket.length == 0) {
 203                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 204                     "No ticket in the NewSessionTicket handshake message");
 205             }
 206 
 207             if (m.remaining() < 2) {
 208                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 209                     "Invalid NewSessionTicket message: no sufficient data");
 210             }
 211 
 212             SSLExtension[] supportedExtensions =
 213                     context.sslConfig.getEnabledExtensions(
 214                             NEW_SESSION_TICKET);
 215             this.extensions = new SSLExtensions(this, m, supportedExtensions);
 216         }
 217 
 218         @Override
 219         public SSLHandshake handshakeType() {
 220             return NEW_SESSION_TICKET;
 221         }
 222 
 223         int getTicketAgeAdd() {
 224             return ticketAgeAdd;
 225         }
 226 
 227         byte[] getTicketNonce() {
 228             return ticketNonce;
 229         }
 230 
 231         @Override
 232         public int messageLength() {
 233 
 234             int extLen = extensions.length();
 235             if (extLen == 0) {
 236                 extLen = 2;     // empty extensions
 237             }
 238 
 239             return 4 +// ticketLifetime
 240                     4 + // ticketAgeAdd
 241                     1 + ticketNonce.length + // len of nonce + nonce
 242                     2 + ticket.length + // len of ticket + ticket
 243                     extLen;
 244         }
 245 
 246         @Override
 247         public void send(HandshakeOutStream hos) throws IOException {
 248             hos.putInt32(ticketLifetime);
 249             hos.putInt32(ticketAgeAdd);
 250             hos.putBytes8(ticketNonce);
 251             hos.putBytes16(ticket);
 252 
 253             // Is it an empty extensions?
 254             if (extensions.length() == 0) {
 255                 hos.putInt16(0);
 256             } else {
 257                 extensions.send(hos);
 258             }
 259         }
 260 
 261         @Override
 262         public String toString() {
 263             MessageFormat messageFormat = new MessageFormat(
 264                 "\"NewSessionTicket\": '{'\n" +
 265                 "  \"ticket_lifetime\"      : \"{0}\",\n" +
 266                 "  \"ticket_age_add\"       : \"{1}\",\n" +
 267                 "  \"ticket_nonce\"         : \"{2}\",\n" +
 268                 "  \"ticket\"               : '{'\n" +
 269                 "{3}\n" +
 270                 "  '}'" +
 271                 "  \"extensions\"           : [\n" +
 272                 "{4}\n" +
 273                 "  ]\n" +
 274                 "'}'",
 275                 Locale.ENGLISH);
 276 
 277             HexDumpEncoder hexEncoder = new HexDumpEncoder();
 278             Object[] messageFields = {
 279                 ticketLifetime,
 280                 "<omitted>",    //ticketAgeAdd should not be logged
 281                 Utilities.toHexString(ticketNonce),
 282                 Utilities.indent(hexEncoder.encode(ticket), "    "),
 283                 Utilities.indent(extensions.toString(), "    ")
 284             };
 285 
 286             return messageFormat.format(messageFields);
 287         }
 288     }
 289 
 290     private static SecretKey derivePreSharedKey(CipherSuite.HashAlg hashAlg,
 291             SecretKey resumptionMasterSecret, byte[] nonce) throws IOException {
 292         try {
 293             HKDF hkdf = new HKDF(hashAlg.name);
 294             byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo(
 295                     "tls13 resumption".getBytes(), nonce, hashAlg.hashLength);
 296             return hkdf.expand(resumptionMasterSecret, hkdfInfo,
 297                     hashAlg.hashLength, "TlsPreSharedKey");
 298         } catch  (GeneralSecurityException gse) {
 299             throw (SSLHandshakeException) new SSLHandshakeException(
 300                     "Could not derive PSK").initCause(gse);
 301         }
 302     }


 346                         "Session has no resumption secret. No ticket sent.");
 347                 }
 348                 return null;
 349             }
 350 
 351             // construct the PSK and handshake message
 352             BigInteger nonce = shc.handshakeSession.incrTicketNonceCounter();
 353             byte[] nonceArr = nonce.toByteArray();
 354             SecretKey psk = derivePreSharedKey(
 355                     shc.negotiatedCipherSuite.hashAlg,
 356                     resumptionMasterSecret, nonceArr);
 357 
 358             int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
 359             if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
 360                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 361                     SSLLogger.fine(
 362                         "Session timeout is too long. No ticket sent.");
 363                 }
 364                 return null;
 365             }







 366 
 367             NewSessionTicketMessage nstm;
 368 

 369             SSLSessionImpl sessionCopy =
 370                     new SSLSessionImpl(shc.handshakeSession, newId);

 371             sessionCopy.setPreSharedKey(psk);
 372             sessionCopy.setPskIdentity(newId.getId());


 373 
 374             if (shc.statelessResumption) {
 375                 try {
 376                     nstm = new T13NewSessionTicketMessage(shc,
 377                             sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
 378                             nonceArr, new SessionTicketSpec().encrypt(shc, sessionCopy));
 379                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 380                         SSLLogger.fine(
 381                                 "Produced NewSessionTicket stateless " +
 382                                         "handshake message", nstm);
 383                     }
 384                 } catch (Exception e) {
 385                     // Error with NST ticket, abort NST
 386                     shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
 387                     return null;
 388                 }
 389             } else {
 390                 nstm = new T13NewSessionTicketMessage(shc, sessionTimeoutSeconds,
 391                         shc.sslContext.getSecureRandom(), nonceArr,
 392                         newId.getId());
 393                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 394                     SSLLogger.fine(
 395                             "Produced NewSessionTicket handshake message",
 396                             nstm);
 397                 }
 398 
 399                 // create and cache the new session
 400                 // The new session must be a child of the existing session so
 401                 // they will be invalidated together, etc.
 402                 shc.handshakeSession.addChild(sessionCopy);
 403                 sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
 404                 sessionCache.put(sessionCopy);
 405             }
 406             // Output the handshake message.
 407             nstm.write(shc.handshakeOutput);
 408             shc.handshakeOutput.flush();
 409 
 410             // The message has been delivered.
 411             return null;
 412         }
 413     }
 414 
 415     /**
 416      * The "NewSessionTicket" handshake message producer for RFC 5077
 417      */
 418     private static final class T12NewSessionTicketProducer
 419             implements HandshakeProducer {
 420 
 421         // Prevent instantiation of this class.
 422         private T12NewSessionTicketProducer() {
 423             // blank
 424         }
 425 
 426         @Override
 427         public byte[] produce(ConnectionContext context,
 428                 HandshakeMessage message) throws IOException {
 429 
 430             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 431 
 432             // Is this session resumable?
 433             if (!shc.handshakeSession.isRejoinable()) {
 434                 return null;
 435             }
 436 
 437             // get a new session ID
 438             SessionId newId = shc.handshakeSession.getSessionId();
 439 
 440             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
 441                     shc.sslContext.engineGetServerSessionContext();
 442             int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
 443             if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
 444                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 445                     SSLLogger.fine(
 446                         "Session timeout is too long. No ticket sent.");
 447                 }
 448                 return null;
 449             }
 450 
 451             NewSessionTicketMessage nstm;
 452 
 453             SSLSessionImpl sessionCopy =
 454                     new SSLSessionImpl(shc.handshakeSession, newId);
 455             sessionCopy.setPskIdentity(newId.getId());
 456 
 457             try {
 458                 nstm = new T12NewSessionTicketMessage(shc, sessionTimeoutSeconds,
 459                         new SessionTicketSpec().encrypt(shc, sessionCopy));
 460                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 461                     SSLLogger.fine(
 462                             "Produced NewSessionTicket stateless handshake message", nstm);
 463                 }
 464             } catch (Exception e) {
 465                 // Abort on error with NST ticket
 466                 shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
 467                 return null;
 468             }
 469 
 470             // Output the handshake message.
 471             nstm.write(shc.handshakeOutput);
 472             shc.handshakeOutput.flush();
 473 
 474             // The message has been delivered.
 475             return null;
 476         }
 477     }
 478 
 479     private static final
 480     class T13NewSessionTicketConsumer implements SSLConsumer {
 481         // Prevent instantiation of this class.
 482         private T13NewSessionTicketConsumer() {
 483             // blank
 484         }
 485 
 486         @Override
 487         public void consume(ConnectionContext context,
 488                 ByteBuffer message) throws IOException {
 489 
 490             // Note: Although the resumption master secret depends on the
 491             // client's second flight, servers which do not request client
 492             // authentication MAY compute the remainder of the transcript
 493             // independently and then send a NewSessionTicket immediately
 494             // upon sending its Finished rather than waiting for the client
 495             // Finished.
 496             //
 497             // The consuming happens in client side only and is received after
 498             // the server's Finished message with PostHandshakeContext.
 499 

 500             HandshakeContext hc = (HandshakeContext)context;
 501             NewSessionTicketMessage nstm =
 502                     new T13NewSessionTicketMessage(hc, message);
 503             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 504                 SSLLogger.fine(
 505                 "Consuming NewSessionTicket message", nstm);
 506             }
 507 
 508             // discard tickets with timeout 0
 509             if (nstm.ticketLifetime <= 0 ||
 510                 nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
 511                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 512                     SSLLogger.fine(
 513                     "Discarding NewSessionTicket with lifetime "
 514                         + nstm.ticketLifetime, nstm);
 515                 }
 516                 return;
 517             }
 518 
 519             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
 520                 hc.sslContext.engineGetClientSessionContext();
 521 
 522             if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
 523                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 524                     SSLLogger.fine(
 525                     "Session cache lifetime is too long. Discarding ticket.");
 526                 }
 527                 return;
 528             }
 529 
 530             SSLSessionImpl sessionToSave = hc.conContext.conSession;
 531             SecretKey psk = null;
 532             if (hc.negotiatedProtocol.useTLS13PlusSpec()) {
 533                 SecretKey resumptionMasterSecret =
 534                         sessionToSave.getResumptionMasterSecret();
 535                 if (resumptionMasterSecret == null) {
 536                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 537                         SSLLogger.fine(
 538                                 "Session has no resumption master secret." +
 539                                         " Ignoring ticket.");
 540                     }
 541                     return;
 542                 }
 543 
 544                 // derive the PSK
 545                 psk = derivePreSharedKey(
 546                         sessionToSave.getSuite().hashAlg,
 547                         resumptionMasterSecret, nstm.getTicketNonce());




 548             }
 549 





 550             // create and cache the new session
 551             // The new session must be a child of the existing session so
 552             // they will be invalidated together, etc.
 553             SessionId newId =
 554                     new SessionId(true, hc.sslContext.getSecureRandom());
 555             SSLSessionImpl sessionCopy = new SSLSessionImpl(sessionToSave,
 556                     newId);
 557             sessionToSave.addChild(sessionCopy);
 558             sessionCopy.setPreSharedKey(psk);
 559             sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
 560             sessionCopy.setPskIdentity(nstm.ticket);
 561             sessionCache.put(sessionCopy);
 562 
 563             // clean handshake context
 564             if (hc.negotiatedProtocol.useTLS13PlusSpec()) {
 565                 hc.conContext.finishPostHandshake();
 566             }
 567         }
 568     }
 569 
 570     private static final
 571     class T12NewSessionTicketConsumer implements SSLConsumer {
 572         // Prevent instantiation of this class.
 573         private T12NewSessionTicketConsumer() {
 574             // blank
 575         }
 576 
 577         @Override
 578         public void consume(ConnectionContext context,
 579                 ByteBuffer message) throws IOException {
 580 
 581             HandshakeContext hc = (HandshakeContext)context;
 582             hc.handshakeConsumers.remove(NEW_SESSION_TICKET.id);
 583 
 584             NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(hc,
 585                     message);
 586             if (nstm.ticket.length == 0) {
 587                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 588                     SSLLogger.fine("NewSessionTicket ticket was empty");
 589                 }
 590                 return;
 591             }
 592 
 593             // discard tickets with timeout 0
 594             if (nstm.ticketLifetime <= 0 ||
 595                 nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
 596                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 597                     SSLLogger.fine(
 598                     "Discarding NewSessionTicket with lifetime "
 599                         + nstm.ticketLifetime, nstm);
 600                 }
 601                 return;
 602             }
 603 
 604             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
 605                     hc.sslContext.engineGetClientSessionContext();
 606 
 607             if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
 608                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 609                     SSLLogger.fine(
 610                     "Session cache lifetime is too long. Discarding ticket.");
 611                 }
 612                 return;
 613             }
 614 
 615             hc.handshakeSession.setPskIdentity(nstm.ticket);
 616             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 617                 SSLLogger.fine("Consuming NewSessionTicket\n" +
 618                         nstm.toString());
 619             }
 620         }
 621     }
 622 }
 623 


  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.math.BigInteger;
  29 import java.nio.ByteBuffer;
  30 import java.security.GeneralSecurityException;
  31 import java.security.ProviderException;
  32 import java.security.SecureRandom;
  33 import java.text.MessageFormat;
  34 import java.util.Locale;
  35 import javax.crypto.SecretKey;
  36 import javax.net.ssl.SSLHandshakeException;
  37 import sun.security.ssl.PskKeyExchangeModesExtension.PskKeyExchangeModesSpec;



  38 
  39 import sun.security.ssl.SSLHandshake.HandshakeMessage;
  40 
  41 /**
  42  * Pack of the NewSessionTicket handshake message.
  43  */
  44 final class NewSessionTicket {
  45     private static final int MAX_TICKET_LIFETIME = 604800;  // seconds, 7 days
  46 
  47     static final SSLConsumer handshakeConsumer =
  48         new NewSessionTicketConsumer();


  49     static final SSLProducer kickstartProducer =
  50         new NewSessionTicketKickstartProducer();
  51     static final HandshakeProducer handshakeProducer =
  52         new NewSessionTicketProducer();






















  53 







  54     /**
  55      * The NewSessionTicketMessage handshake message.
  56      */
  57     static final class NewSessionTicketMessage extends HandshakeMessage {
  58         final int ticketLifetime;
  59         final int ticketAgeAdd;
  60         final byte[] ticketNonce;
  61         final byte[] ticket;
  62         final SSLExtensions extensions;





















  63 
  64         NewSessionTicketMessage(HandshakeContext context,













































  65                 int ticketLifetime, SecureRandom generator,
  66                 byte[] ticketNonce, byte[] ticket) {
  67             super(context);
  68 
  69             this.ticketLifetime = ticketLifetime;
  70             this.ticketAgeAdd = generator.nextInt();
  71             this.ticketNonce = ticketNonce;
  72             this.ticket = ticket;
  73             this.extensions = new SSLExtensions(this);
  74         }
  75 
  76         NewSessionTicketMessage(HandshakeContext context,
  77                 ByteBuffer m) throws IOException {
  78             super(context);
  79 
  80             // struct {
  81             //     uint32 ticket_lifetime;
  82             //     uint32 ticket_age_add;
  83             //     opaque ticket_nonce<0..255>;
  84             //     opaque ticket<1..2^16-1>;
  85             //     Extension extensions<0..2^16-2>;
  86             // } NewSessionTicket;

  87             if (m.remaining() < 14) {
  88                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
  89                     "Invalid NewSessionTicket message: no sufficient data");
  90             }
  91 
  92             this.ticketLifetime = Record.getInt32(m);
  93             this.ticketAgeAdd = Record.getInt32(m);
  94             this.ticketNonce = Record.getBytes8(m);
  95 
  96             if (m.remaining() < 5) {
  97                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
  98                     "Invalid NewSessionTicket message: no sufficient data");
  99             }
 100 
 101             this.ticket = Record.getBytes16(m);
 102             if (ticket.length == 0) {
 103                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 104                     "No ticket in the NewSessionTicket handshake message");
 105             }
 106 
 107             if (m.remaining() < 2) {
 108                 throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 109                     "Invalid NewSessionTicket message: no sufficient data");
 110             }
 111 
 112             SSLExtension[] supportedExtensions =
 113                     context.sslConfig.getEnabledExtensions(
 114                             SSLHandshake.NEW_SESSION_TICKET);
 115             this.extensions = new SSLExtensions(this, m, supportedExtensions);
 116         }
 117 
 118         @Override
 119         public SSLHandshake handshakeType() {
 120             return SSLHandshake.NEW_SESSION_TICKET;








 121         }
 122 
 123         @Override
 124         public int messageLength() {

 125             int extLen = extensions.length();
 126             if (extLen == 0) {
 127                 extLen = 2;     // empty extensions
 128             }
 129 
 130             return 8 + ticketNonce.length + 1 +
 131                        ticket.length + 2 + extLen;



 132         }
 133 
 134         @Override
 135         public void send(HandshakeOutStream hos) throws IOException {
 136             hos.putInt32(ticketLifetime);
 137             hos.putInt32(ticketAgeAdd);
 138             hos.putBytes8(ticketNonce);
 139             hos.putBytes16(ticket);
 140 
 141             // Is it an empty extensions?
 142             if (extensions.length() == 0) {
 143                 hos.putInt16(0);
 144             } else {
 145                 extensions.send(hos);
 146             }
 147         }
 148 
 149         @Override
 150         public String toString() {
 151             MessageFormat messageFormat = new MessageFormat(
 152                 "\"NewSessionTicket\": '{'\n" +
 153                 "  \"ticket_lifetime\"      : \"{0}\",\n" +
 154                 "  \"ticket_age_add\"       : \"{1}\",\n" +
 155                 "  \"ticket_nonce\"         : \"{2}\",\n" +
 156                 "  \"ticket\"               : \"{3}\",\n" +


 157                 "  \"extensions\"           : [\n" +
 158                 "{4}\n" +
 159                 "  ]\n" +
 160                 "'}'",
 161                 Locale.ENGLISH);
 162 

 163             Object[] messageFields = {
 164                 ticketLifetime,
 165                 "<omitted>",    //ticketAgeAdd should not be logged
 166                 Utilities.toHexString(ticketNonce),
 167                 Utilities.toHexString(ticket),
 168                 Utilities.indent(extensions.toString(), "    ")
 169             };
 170 
 171             return messageFormat.format(messageFields);
 172         }
 173     }
 174 
 175     private static SecretKey derivePreSharedKey(CipherSuite.HashAlg hashAlg,
 176             SecretKey resumptionMasterSecret, byte[] nonce) throws IOException {
 177         try {
 178             HKDF hkdf = new HKDF(hashAlg.name);
 179             byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo(
 180                     "tls13 resumption".getBytes(), nonce, hashAlg.hashLength);
 181             return hkdf.expand(resumptionMasterSecret, hkdfInfo,
 182                     hashAlg.hashLength, "TlsPreSharedKey");
 183         } catch  (GeneralSecurityException gse) {
 184             throw (SSLHandshakeException) new SSLHandshakeException(
 185                     "Could not derive PSK").initCause(gse);
 186         }
 187     }


 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();
 244             if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
 245                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 246                     SSLLogger.fine(
 247                         "Session timeout is too long. No ticket sent.");
 248                 }
 249                 return null;
 250             }
 251             NewSessionTicketMessage nstm = new NewSessionTicketMessage(shc,
 252                 sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
 253                 nonceArr, newId.getId());
 254             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 255                 SSLLogger.fine(
 256                         "Produced NewSessionTicket handshake message", nstm);
 257             }
 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 
































 270             // Output the handshake message.
 271             nstm.write(shc.handshakeOutput);
 272             shc.handshakeOutput.flush();
 273 
 274             // The message has been delivered.
 275             return null;
 276         }
 277     }
 278 
 279     /**
 280      * The "NewSessionTicket" handshake message producer.
 281      */
 282     private static final class NewSessionTicketProducer
 283             implements HandshakeProducer {
 284 
 285         // Prevent instantiation of this class.
 286         private NewSessionTicketProducer() {
 287             // blank
 288         }
 289 
 290         @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,
 311                             ByteBuffer message) throws IOException {
 312 
 313             // Note: Although the resumption master secret depends on the
 314             // client's second flight, servers which do not request client
 315             // authentication MAY compute the remainder of the transcript
 316             // independently and then send a NewSessionTicket immediately
 317             // upon sending its Finished rather than waiting for the client
 318             // Finished.
 319             //
 320             // The consuming happens in client side only.  As the server
 321             // may send the NewSessionTicket before handshake complete, the
 322             // context may be a PostHandshakeContext or HandshakeContext
 323             // instance.
 324             HandshakeContext hc = (HandshakeContext)context;
 325             NewSessionTicketMessage nstm =
 326                     new NewSessionTicketMessage(hc, message);
 327             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 328                 SSLLogger.fine(
 329                 "Consuming NewSessionTicket message", nstm);
 330             }
 331 
 332             // discard tickets with timeout 0
 333             if (nstm.ticketLifetime <= 0 ||
 334                 nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
 335                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 336                     SSLLogger.fine(
 337                     "Discarding NewSessionTicket with lifetime "
 338                         + nstm.ticketLifetime, nstm);
 339                 }
 340                 return;
 341             }
 342 
 343             SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
 344                 hc.sslContext.engineGetClientSessionContext();
 345 
 346             if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
 347                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 348                     SSLLogger.fine(
 349                     "Session cache lifetime is too long. Discarding ticket.");
 350                 }
 351                 return;
 352             }
 353 
 354             SSLSessionImpl sessionToSave = hc.conContext.conSession;












 355 
 356             SecretKey resumptionMasterSecret =
 357                 sessionToSave.getResumptionMasterSecret();
 358             if (resumptionMasterSecret == null) {
 359                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 360                     SSLLogger.fine(
 361                     "Session has no resumption master secret. Ignoring ticket.");
 362                 }
 363                 return;
 364             }
 365 
 366             // derive the PSK
 367             SecretKey psk = derivePreSharedKey(
 368                 sessionToSave.getSuite().hashAlg, resumptionMasterSecret,
 369                 nstm.ticketNonce);
 370 
 371             // create and cache the new session
 372             // The new session must be a child of the existing session so
 373             // they will be invalidated together, etc.
 374             SessionId newId =
 375                 new SessionId(true, hc.sslContext.getSecureRandom());
 376             SSLSessionImpl sessionCopy = new SSLSessionImpl(sessionToSave,
 377                     newId);
 378             sessionToSave.addChild(sessionCopy);
 379             sessionCopy.setPreSharedKey(psk);
 380             sessionCopy.setTicketAgeAdd(nstm.ticketAgeAdd);
 381             sessionCopy.setPskIdentity(nstm.ticket);
 382             sessionCache.put(sessionCopy);
 383 
 384             // clean handshake context
 385             hc.conContext.finishPostHandshake();























































 386         }
 387     }
 388 }
 389 
< prev index next >