1 /* 2 * Copyright (c) 2015, 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 26 package sun.security.ssl; 27 28 import java.io.IOException; 29 import java.util.Objects; 30 31 /* 32 * RFC6066 defines the TLS extension,"status_request" (type 0x5), 33 * which allows the client to request that the server perform OCSP 34 * on the client's behalf. 35 * The "extension data" field of this extension contains a 36 * "CertificateStatusRequest" structure: 37 * 38 * struct { 39 * CertificateStatusType status_type; 40 * select (status_type) { 41 * case ocsp: OCSPStatusRequest; 42 * } request; 43 * } CertificateStatusRequest; 44 * 45 * enum { ocsp(1), (255) } CertificateStatusType; 46 * 47 * struct { 48 * ResponderID responder_id_list<0..2^16-1>; 49 * Extensions request_extensions; 50 * } OCSPStatusRequest; 51 * 52 * opaque ResponderID<1..2^16-1>; 53 * opaque Extensions<0..2^16-1>; 54 */ 55 56 final class CertStatusReqExtension extends HelloExtension { 57 58 private final StatusRequestType statReqType; 59 private final StatusRequest request; 60 61 62 /** 63 * Construct the default status request extension object. The default 64 * object results in a status_request extension where the extension 65 * data segment is zero-length. This is used primarily in ServerHello 66 * messages where the server asserts it can do RFC 6066 status stapling. 67 */ 68 CertStatusReqExtension() { 69 super(ExtensionType.EXT_STATUS_REQUEST); 70 statReqType = null; 71 request = null; 72 } 73 74 /** 75 * Construct the status request extension object given a request type 76 * and {@code StatusRequest} object. 77 * 78 * @param reqType a {@code StatusRequestExtType object correspoding 79 * to the underlying {@code StatusRequest} object. A value of 80 * {@code null} is not allowed. 81 * @param statReq the {@code StatusRequest} object used to provide the 82 * encoding for the TLS extension. A value of {@code null} is not 83 * allowed. 84 * 85 * @throws IllegalArgumentException if the provided {@code StatusRequest} 86 * does not match the type. 87 * @throws NullPointerException if either the {@code reqType} or 88 * {@code statReq} arguments are {@code null}. 89 */ 90 CertStatusReqExtension(StatusRequestType reqType, StatusRequest statReq) { 91 super(ExtensionType.EXT_STATUS_REQUEST); 92 93 statReqType = Objects.requireNonNull(reqType, 94 "Unallowed null value for status_type"); 95 request = Objects.requireNonNull(statReq, 96 "Unallowed null value for request"); 97 98 // There is currently only one known status type (OCSP) 99 // We can add more clauses to cover other types in the future 100 if (statReqType == StatusRequestType.OCSP) { 101 if (!(statReq instanceof OCSPStatusRequest)) { 102 throw new IllegalArgumentException("StatusRequest not " + 103 "of type OCSPStatusRequest"); 104 } 105 } 106 } 107 108 /** 109 * Construct the {@code CertStatusReqExtension} object from data read from 110 * a {@code HandshakeInputStream} 111 * 112 * @param s the {@code HandshakeInputStream} providing the encoded data 113 * @param len the length of the extension data 114 * 115 * @throws IOException if any decoding errors happen during object 116 * construction. 117 */ 118 CertStatusReqExtension(HandshakeInStream s, int len) throws IOException { 119 super(ExtensionType.EXT_STATUS_REQUEST); 120 121 if (len > 0) { 122 // Obtain the status type (first byte) 123 statReqType = StatusRequestType.get(s.getInt8()); 124 if (statReqType == StatusRequestType.OCSP) { 125 request = new OCSPStatusRequest(s); 126 } else { 127 // This is a status_type we don't understand. Create 128 // an UnknownStatusRequest in order to preserve the data 129 request = new UnknownStatusRequest(s, len - 1); 130 } 131 } else { 132 // Treat this as a zero-length extension (i.e. from a ServerHello 133 statReqType = null; 134 request = null; 135 } 136 } 137 138 /** 139 * Return the length of the encoded extension, including extension type, 140 * extension length and status_type fields. 141 * 142 * @return the length in bytes, including the extension type and 143 * length fields. 144 */ 145 @Override 146 int length() { 147 return (statReqType != null ? 5 + request.length() : 4); 148 } 149 150 /** 151 * Send the encoded TLS extension through a {@code HandshakeOutputStream} 152 * 153 * @param s the {@code HandshakeOutputStream} used to send the encoded data 154 * 155 * @throws IOException tf any errors occur during the encoding process 156 */ 157 @Override 158 void send(HandshakeOutStream s) throws IOException { 159 s.putInt16(type.id); 160 s.putInt16(this.length() - 4); 161 162 if (statReqType != null) { 163 s.putInt8(statReqType.id); 164 request.send(s); 165 } 166 } 167 168 /** 169 * Create a string representation of this {@code CertStatusReqExtension} 170 * 171 * @return the string representation of this {@code CertStatusReqExtension} 172 */ 173 @Override 174 public String toString() { 175 StringBuilder sb = new StringBuilder("Extension ").append(type); 176 if (statReqType != null) { 177 sb.append(": ").append(statReqType).append(", ").append(request); 178 } 179 180 return sb.toString(); 181 } 182 183 /** 184 * Return the type field for this {@code CertStatusReqExtension} 185 * 186 * @return the {@code StatusRequestType} for this extension. {@code null} 187 * will be returned if the default constructor is used to create 188 * a zero length status_request extension (found in ServerHello 189 * messages) 190 */ 191 StatusRequestType getType() { 192 return statReqType; 193 } 194 195 /** 196 * Get the underlying {@code StatusRequest} for this 197 * {@code CertStatusReqExtension} 198 * 199 * @return the {@code StatusRequest} or {@code null} if the default 200 * constructor was used to create this extension. 201 */ 202 StatusRequest getRequest() { 203 return request; 204 } 205 } | 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 26 package sun.security.ssl; 27 28 import java.io.IOException; 29 import java.io.ByteArrayInputStream; 30 import java.nio.ByteBuffer; 31 import java.security.cert.Extension; 32 import java.security.cert.CertificateFactory; 33 import java.security.cert.CertificateException; 34 import java.security.cert.X509Certificate; 35 import java.text.MessageFormat; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Locale; 39 import javax.net.ssl.SSLProtocolException; 40 import sun.security.provider.certpath.OCSPResponse; 41 import sun.security.provider.certpath.ResponderId; 42 import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST; 43 import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST_V2; 44 import sun.security.ssl.SSLExtension.ExtensionConsumer; 45 import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST; 46 import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST_V2; 47 import sun.security.ssl.SSLExtension.SSLExtensionSpec; 48 import sun.security.ssl.SSLHandshake.HandshakeMessage; 49 import sun.security.util.DerInputStream; 50 import sun.security.util.DerValue; 51 import sun.security.util.HexDumpEncoder; 52 53 /** 54 * Pack of "status_request" and "status_request_v2" extensions. 55 */ 56 final class CertStatusExtension { 57 static final HandshakeProducer chNetworkProducer = 58 new CHCertStatusReqProducer(); 59 static final ExtensionConsumer chOnLoadConsumer = 60 new CHCertStatusReqConsumer(); 61 62 static final HandshakeProducer shNetworkProducer = 63 new SHCertStatusReqProducer(); 64 static final ExtensionConsumer shOnLoadConsumer = 65 new SHCertStatusReqConsumer(); 66 67 static final HandshakeProducer ctNetworkProducer = 68 new CTCertStatusResponseProducer(); 69 static final ExtensionConsumer ctOnLoadConsumer = 70 new CTCertStatusResponseConsumer(); 71 72 static final SSLStringize certStatusReqStringize = 73 new CertStatusRequestStringize(); 74 75 static final HandshakeProducer chV2NetworkProducer = 76 new CHCertStatusReqV2Producer(); 77 static final ExtensionConsumer chV2OnLoadConsumer = 78 new CHCertStatusReqV2Consumer(); 79 80 static final HandshakeProducer shV2NetworkProducer = 81 new SHCertStatusReqV2Producer(); 82 static final ExtensionConsumer shV2OnLoadConsumer = 83 new SHCertStatusReqV2Consumer(); 84 85 static final SSLStringize certStatusReqV2Stringize = 86 new CertStatusRequestsStringize(); 87 88 static final SSLStringize certStatusRespStringize = 89 new CertStatusRespStringize(); 90 91 /** 92 * The "status_request" extension. 93 * 94 * RFC6066 defines the TLS extension,"status_request" (type 0x5), 95 * which allows the client to request that the server perform OCSP 96 * on the client's behalf. 97 * 98 * The "extension data" field of this extension contains a 99 * "CertificateStatusRequest" structure: 100 * 101 * struct { 102 * CertificateStatusType status_type; 103 * select (status_type) { 104 * case ocsp: OCSPStatusRequest; 105 * } request; 106 * } CertificateStatusRequest; 107 * 108 * enum { ocsp(1), (255) } CertificateStatusType; 109 * 110 * struct { 111 * ResponderID responder_id_list<0..2^16-1>; 112 * Extensions request_extensions; 113 * } OCSPStatusRequest; 114 * 115 * opaque ResponderID<1..2^16-1>; 116 * opaque Extensions<0..2^16-1>; 117 */ 118 static final class CertStatusRequestSpec implements SSLExtensionSpec { 119 static final CertStatusRequestSpec DEFAULT = 120 new CertStatusRequestSpec(OCSPStatusRequest.EMPTY_OCSP); 121 122 final CertStatusRequest statusRequest; 123 124 private CertStatusRequestSpec(CertStatusRequest statusRequest) { 125 this.statusRequest = statusRequest; 126 } 127 128 private CertStatusRequestSpec(ByteBuffer buffer) throws IOException { 129 // Is it a empty extension_data? 130 if (buffer.remaining() == 0) { 131 // server response 132 this.statusRequest = null; 133 return; 134 } 135 136 if (buffer.remaining() < 1) { 137 throw new SSLProtocolException( 138 "Invalid status_request extension: insufficient data"); 139 } 140 141 byte statusType = (byte)Record.getInt8(buffer); 142 byte[] encoded = new byte[buffer.remaining()]; 143 if (encoded.length != 0) { 144 buffer.get(encoded); 145 } 146 if (statusType == CertStatusRequestType.OCSP.id) { 147 this.statusRequest = new OCSPStatusRequest(statusType, encoded); 148 } else { 149 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 150 SSLLogger.info( 151 "Unknown certificate status request " + 152 "(status type: " + statusType + ")"); 153 } 154 155 this.statusRequest = new CertStatusRequest(statusType, encoded); 156 } 157 } 158 159 @Override 160 public String toString() { 161 return statusRequest == null ? 162 "<empty>" : statusRequest.toString(); 163 } 164 } 165 166 /** 167 * Defines the CertificateStatus response structure as outlined in 168 * RFC 6066. This will contain a status response type, plus a single, 169 * non-empty OCSP response in DER-encoded form. 170 * 171 * struct { 172 * CertificateStatusType status_type; 173 * select (status_type) { 174 * case ocsp: OCSPResponse; 175 * } response; 176 * } CertificateStatus; 177 */ 178 static final class CertStatusResponseSpec implements SSLExtensionSpec { 179 final CertStatusResponse statusResponse; 180 181 private CertStatusResponseSpec(CertStatusResponse resp) { 182 this.statusResponse = resp; 183 } 184 185 private CertStatusResponseSpec(ByteBuffer buffer) throws IOException { 186 if (buffer.remaining() < 2) { 187 throw new SSLProtocolException( 188 "Invalid status_request extension: insufficient data"); 189 } 190 191 // Get the status type (1 byte) and response data (vector) 192 byte type = (byte)Record.getInt8(buffer); 193 byte[] respData = Record.getBytes24(buffer); 194 195 // Create the CertStatusResponse based on the type 196 if (type == CertStatusRequestType.OCSP.id) { 197 this.statusResponse = new OCSPStatusResponse(type, respData); 198 } else { 199 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 200 SSLLogger.info( 201 "Unknown certificate status response " + 202 "(status type: " + type + ")"); 203 } 204 205 this.statusResponse = new CertStatusResponse(type, respData); 206 } 207 } 208 209 @Override 210 public String toString() { 211 return statusResponse == null ? 212 "<empty>" : statusResponse.toString(); 213 } 214 } 215 216 private static final 217 class CertStatusRequestStringize implements SSLStringize { 218 @Override 219 public String toString(ByteBuffer buffer) { 220 try { 221 return (new CertStatusRequestSpec(buffer)).toString(); 222 } catch (IOException ioe) { 223 // For debug logging only, so please swallow exceptions. 224 return ioe.getMessage(); 225 } 226 } 227 } 228 229 private static final 230 class CertStatusRespStringize implements SSLStringize { 231 @Override 232 public String toString(ByteBuffer buffer) { 233 try { 234 return (new CertStatusResponseSpec(buffer)).toString(); 235 } catch (IOException ioe) { 236 // For debug logging only, so please swallow exceptions. 237 return ioe.getMessage(); 238 } 239 } 240 } 241 242 static enum CertStatusRequestType { 243 OCSP ((byte)0x01, "ocsp"), // RFC 6066/6961 244 OCSP_MULTI ((byte)0x02, "ocsp_multi"); // RFC 6961 245 246 final byte id; 247 final String name; 248 249 private CertStatusRequestType(byte id, String name) { 250 this.id = id; 251 this.name = name; 252 } 253 254 /** 255 * Returns the enum constant of the specified id (see RFC 6066). 256 */ 257 static CertStatusRequestType valueOf(byte id) { 258 for (CertStatusRequestType srt : CertStatusRequestType.values()) { 259 if (srt.id == id) { 260 return srt; 261 } 262 } 263 264 return null; 265 } 266 267 static String nameOf(byte id) { 268 for (CertStatusRequestType srt : CertStatusRequestType.values()) { 269 if (srt.id == id) { 270 return srt.name; 271 } 272 } 273 274 return "UNDEFINED-CERT-STATUS-TYPE(" + id + ")"; 275 } 276 } 277 278 static class CertStatusRequest { 279 final byte statusType; 280 final byte[] encodedRequest; 281 282 protected CertStatusRequest(byte statusType, byte[] encodedRequest) { 283 this.statusType = statusType; 284 this.encodedRequest = encodedRequest; 285 } 286 287 @Override 288 public String toString() { 289 MessageFormat messageFormat = new MessageFormat( 290 "\"certificate status type\": {0}\n" + 291 "\"encoded certificate status\": '{'\n" + 292 "{1}\n" + 293 "'}'", 294 Locale.ENGLISH); 295 296 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 297 String encoded = hexEncoder.encodeBuffer(encodedRequest); 298 299 Object[] messageFields = { 300 CertStatusRequestType.nameOf(statusType), 301 Utilities.indent(encoded) 302 }; 303 304 return messageFormat.format(messageFields); 305 } 306 } 307 308 /* 309 * RFC6066 defines the TLS extension,"status_request" (type 0x5), 310 * which allows the client to request that the server perform OCSP 311 * on the client's behalf. 312 * 313 * The RFC defines an OCSPStatusRequest structure: 314 * 315 * struct { 316 * ResponderID responder_id_list<0..2^16-1>; 317 * Extensions request_extensions; 318 * } OCSPStatusRequest; 319 */ 320 static final class OCSPStatusRequest extends CertStatusRequest { 321 static final OCSPStatusRequest EMPTY_OCSP; 322 static final OCSPStatusRequest EMPTY_OCSP_MULTI; 323 324 final List<ResponderId> responderIds; 325 final List<Extension> extensions; 326 private final int encodedLen; 327 private final int ridListLen; 328 private final int extListLen; 329 330 static { 331 OCSPStatusRequest ocspReq = null; 332 OCSPStatusRequest multiReq = null; 333 334 try { 335 ocspReq = new OCSPStatusRequest( 336 CertStatusRequestType.OCSP.id, 337 new byte[] {0x00, 0x00, 0x00, 0x00}); 338 multiReq = new OCSPStatusRequest( 339 CertStatusRequestType.OCSP_MULTI.id, 340 new byte[] {0x00, 0x00, 0x00, 0x00}); 341 } catch (IOException ioe) { 342 // unlikely 343 } 344 345 EMPTY_OCSP = ocspReq; 346 EMPTY_OCSP_MULTI = multiReq; 347 } 348 349 private OCSPStatusRequest(byte statusType, 350 byte[] encoded) throws IOException { 351 super(statusType, encoded); 352 353 if (encoded == null || encoded.length < 4) { 354 // 2: length of responder_id_list 355 // +2: length of request_extensions 356 throw new SSLProtocolException( 357 "Invalid OCSP status request: insufficient data"); 358 } 359 this.encodedLen = encoded.length; 360 361 List<ResponderId> rids = new ArrayList<>(); 362 List<Extension> exts = new ArrayList<>(); 363 ByteBuffer m = ByteBuffer.wrap(encoded); 364 365 this.ridListLen = Record.getInt16(m); 366 if (m.remaining() < (ridListLen + 2)) { 367 throw new SSLProtocolException( 368 "Invalid OCSP status request: insufficient data"); 369 } 370 371 int ridListBytesRemaining = ridListLen; 372 while (ridListBytesRemaining >= 2) { // 2: length of responder_id 373 byte[] ridBytes = Record.getBytes16(m); 374 try { 375 rids.add(new ResponderId(ridBytes)); 376 } catch (IOException ioe) { 377 throw new SSLProtocolException( 378 "Invalid OCSP status request: invalid responder ID"); 379 } 380 ridListBytesRemaining -= ridBytes.length + 2; 381 } 382 383 if (ridListBytesRemaining != 0) { 384 throw new SSLProtocolException( 385 "Invalid OCSP status request: incomplete data"); 386 } 387 388 byte[] extListBytes = Record.getBytes16(m); 389 this.extListLen = extListBytes.length; 390 if (extListLen > 0) { 391 try { 392 DerInputStream dis = new DerInputStream(extListBytes); 393 DerValue[] extSeqContents = 394 dis.getSequence(extListBytes.length); 395 for (DerValue extDerVal : extSeqContents) { 396 exts.add(new sun.security.x509.Extension(extDerVal)); 397 } 398 } catch (IOException ioe) { 399 throw new SSLProtocolException( 400 "Invalid OCSP status request: invalid extension"); 401 } 402 } 403 404 this.responderIds = rids; 405 this.extensions = exts; 406 } 407 408 @Override 409 public String toString() { 410 MessageFormat messageFormat = new MessageFormat( 411 "\"certificate status type\": {0}\n" + 412 "\"OCSP status request\": '{'\n" + 413 "{1}\n" + 414 "'}'", 415 Locale.ENGLISH); 416 417 MessageFormat requestFormat = new MessageFormat( 418 "\"responder_id\": {0}\n" + 419 "\"request extensions\": '{'\n" + 420 "{1}\n" + 421 "'}'", 422 Locale.ENGLISH); 423 424 String ridStr = "<empty>"; 425 if (!responderIds.isEmpty()) { 426 ridStr = responderIds.toString(); 427 428 } 429 430 String extsStr = "<empty>"; 431 if (!extensions.isEmpty()) { 432 StringBuilder extBuilder = new StringBuilder(512); 433 boolean isFirst = true; 434 for (Extension ext : this.extensions) { 435 if (isFirst) { 436 isFirst = false; 437 } else { 438 extBuilder.append(",\n"); 439 } 440 extBuilder.append( 441 "{\n" + Utilities.indent(ext.toString()) + "}"); 442 } 443 444 extsStr = extBuilder.toString(); 445 } 446 447 Object[] requestFields = { 448 ridStr, 449 Utilities.indent(extsStr) 450 }; 451 String ocspStatusRequest = requestFormat.format(requestFields); 452 453 Object[] messageFields = { 454 CertStatusRequestType.nameOf(statusType), 455 Utilities.indent(ocspStatusRequest) 456 }; 457 458 return messageFormat.format(messageFields); 459 } 460 } 461 462 static class CertStatusResponse { 463 final byte statusType; 464 final byte[] encodedResponse; 465 466 protected CertStatusResponse(byte statusType, byte[] respDer) { 467 this.statusType = statusType; 468 this.encodedResponse = respDer; 469 } 470 471 byte[] toByteArray() throws IOException { 472 // Create a byte array large enough to handle the status_type 473 // field (1) + OCSP length (3) + OCSP data (variable) 474 byte[] outData = new byte[encodedResponse.length + 4]; 475 ByteBuffer buf = ByteBuffer.wrap(outData); 476 Record.putInt8(buf, statusType); 477 Record.putBytes24(buf, encodedResponse); 478 return buf.array(); 479 } 480 481 @Override 482 public String toString() { 483 MessageFormat messageFormat = new MessageFormat( 484 "\"certificate status response type\": {0}\n" + 485 "\"encoded certificate status\": '{'\n" + 486 "{1}\n" + 487 "'}'", 488 Locale.ENGLISH); 489 490 HexDumpEncoder hexEncoder = new HexDumpEncoder(); 491 String encoded = hexEncoder.encodeBuffer(encodedResponse); 492 493 Object[] messageFields = { 494 CertStatusRequestType.nameOf(statusType), 495 Utilities.indent(encoded) 496 }; 497 498 return messageFormat.format(messageFields); 499 } 500 } 501 502 static final class OCSPStatusResponse extends CertStatusResponse { 503 final OCSPResponse ocspResponse; 504 505 private OCSPStatusResponse(byte statusType, 506 byte[] encoded) throws IOException { 507 super(statusType, encoded); 508 509 // The DER-encoded OCSP response must not be zero length 510 if (encoded == null || encoded.length < 1) { 511 throw new SSLProtocolException( 512 "Invalid OCSP status response: insufficient data"); 513 } 514 515 // Otherwise, make an OCSPResponse object from the data 516 ocspResponse = new OCSPResponse(encoded); 517 } 518 519 @Override 520 public String toString() { 521 MessageFormat messageFormat = new MessageFormat( 522 "\"certificate status response type\": {0}\n" + 523 "\"OCSP status response\": '{'\n" + 524 "{1}\n" + 525 "'}'", 526 Locale.ENGLISH); 527 528 Object[] messageFields = { 529 CertStatusRequestType.nameOf(statusType), 530 Utilities.indent(ocspResponse.toString()) 531 }; 532 533 return messageFormat.format(messageFields); 534 } 535 } 536 537 /** 538 * Network data producer of a "status_request" extension in the 539 * ClientHello handshake message. 540 */ 541 private static final 542 class CHCertStatusReqProducer implements HandshakeProducer { 543 // Prevent instantiation of this class. 544 private CHCertStatusReqProducer() { 545 // blank 546 } 547 548 @Override 549 public byte[] produce(ConnectionContext context, 550 HandshakeMessage message) throws IOException { 551 // The producing happens in client side only. 552 ClientHandshakeContext chc = (ClientHandshakeContext)context; 553 554 if (!chc.sslContext.isStaplingEnabled(true)) { 555 return null; 556 } 557 558 if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST)) { 559 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 560 SSLLogger.fine( 561 "Ignore unavailable extension: " + 562 CH_STATUS_REQUEST.name); 563 } 564 return null; 565 } 566 567 // Produce the extension. 568 // 569 // We are using empty OCSPStatusRequest at present. May extend to 570 // support specific responder or extensions later. 571 byte[] extData = new byte[] {0x01, 0x00, 0x00, 0x00, 0x00}; 572 573 // Update the context. 574 chc.handshakeExtensions.put( 575 CH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); 576 577 return extData; 578 } 579 } 580 581 /** 582 * Network data consumer of a "status_request" extension in the 583 * ClientHello handshake message. 584 */ 585 private static final 586 class CHCertStatusReqConsumer implements ExtensionConsumer { 587 // Prevent instantiation of this class. 588 private CHCertStatusReqConsumer() { 589 // blank 590 } 591 592 @Override 593 public void consume(ConnectionContext context, 594 HandshakeMessage message, ByteBuffer buffer) throws IOException { 595 596 // The comsuming happens in server side only. 597 ServerHandshakeContext shc = (ServerHandshakeContext)context; 598 599 if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST)) { 600 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 601 SSLLogger.fine("Ignore unavailable extension: " + 602 CH_STATUS_REQUEST.name); 603 } 604 return; // ignore the extension 605 } 606 607 // Parse the extension. 608 CertStatusRequestSpec spec; 609 try { 610 spec = new CertStatusRequestSpec(buffer); 611 } catch (IOException ioe) { 612 shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 613 return; // fatal() always throws, make the compiler happy. 614 } 615 616 // Update the context. 617 shc.handshakeExtensions.put(CH_STATUS_REQUEST, spec); 618 if (!shc.negotiatedProtocol.useTLS13PlusSpec()) { 619 shc.handshakeProducers.put(SSLHandshake.CERTIFICATE_STATUS.id, 620 SSLHandshake.CERTIFICATE_STATUS); 621 } // Otherwise, the certificate status presents in server cert. 622 623 // No impact on session resumption. 624 } 625 } 626 627 /** 628 * Network data producer of a "status_request" extension in the 629 * ServerHello handshake message. 630 */ 631 private static final 632 class SHCertStatusReqProducer implements HandshakeProducer { 633 // Prevent instantiation of this class. 634 private SHCertStatusReqProducer() { 635 // blank 636 } 637 638 @Override 639 public byte[] produce(ConnectionContext context, 640 HandshakeMessage message) throws IOException { 641 // The producing happens in client side only. 642 ServerHandshakeContext shc = (ServerHandshakeContext)context; 643 644 // The StaplingParameters in the ServerHandshakeContext will 645 // contain the info about what kind of stapling (if any) to 646 // perform and whether this status_request extension should be 647 // produced or the status_request_v2 (found in a different producer) 648 // No explicit check is required for isStaplingEnabled here. If 649 // it is false then stapleParams will be null. If it is true 650 // then stapleParams may or may not be false and the check below 651 // is sufficient. 652 if ((shc.stapleParams == null) || 653 (shc.stapleParams.statusRespExt != 654 SSLExtension.CH_STATUS_REQUEST)) { 655 return null; // Do not produce status_request in ServerHello 656 } 657 658 // In response to "status_request" extension request only. 659 CertStatusRequestSpec spec = (CertStatusRequestSpec) 660 shc.handshakeExtensions.get(CH_STATUS_REQUEST); 661 if (spec == null) { 662 // Ignore, no status_request extension requested. 663 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 664 SSLLogger.finest( 665 "Ignore unavailable extension: " + 666 CH_STATUS_REQUEST.name); 667 } 668 669 return null; // ignore the extension 670 } 671 672 // Is it a session resuming? 673 if (shc.isResumption) { 674 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 675 SSLLogger.finest( 676 "No status_request response for session resuming"); 677 } 678 679 return null; // ignore the extension 680 } 681 682 // The "extension_data" in the extended ServerHello handshake 683 // message MUST be empty. 684 byte[] extData = new byte[0]; 685 686 // Update the context. 687 shc.handshakeExtensions.put( 688 SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); 689 690 return extData; 691 } 692 } 693 694 /** 695 * Network data consumer of a "status_request" extension in the 696 * ServerHello handshake message. 697 */ 698 private static final 699 class SHCertStatusReqConsumer implements ExtensionConsumer { 700 // Prevent instantiation of this class. 701 private SHCertStatusReqConsumer() { 702 // blank 703 } 704 705 @Override 706 public void consume(ConnectionContext context, 707 HandshakeMessage message, ByteBuffer buffer) throws IOException { 708 709 // The producing happens in client side only. 710 ClientHandshakeContext chc = (ClientHandshakeContext)context; 711 712 // In response to "status_request" extension request only. 713 CertStatusRequestSpec requestedCsr = (CertStatusRequestSpec) 714 chc.handshakeExtensions.get(CH_STATUS_REQUEST); 715 if (requestedCsr == null) { 716 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 717 "Unexpected status_request extension in ServerHello"); 718 } 719 720 // Parse the extension. 721 if (buffer.hasRemaining()) { 722 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 723 "Invalid status_request extension in ServerHello message: " + 724 "the extension data must be empty"); 725 } 726 727 // Update the context. 728 chc.handshakeExtensions.put( 729 SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); 730 chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id, 731 SSLHandshake.CERTIFICATE_STATUS); 732 733 // Since we've received a legitimate status_request in the 734 // ServerHello, stapling is active if it's been enabled. 735 chc.staplingActive = chc.sslContext.isStaplingEnabled(true); 736 737 // No impact on session resumption. 738 } 739 } 740 741 /** 742 * The "status_request_v2" extension. 743 * 744 * RFC6961 defines the TLS extension,"status_request_v2" (type 0x5), 745 * which allows the client to request that the server perform OCSP 746 * on the client's behalf. 747 * 748 * The RFC defines an CertStatusReqItemV2 structure: 749 * 750 * struct { 751 * CertificateStatusType status_type; 752 * uint16 request_length; 753 * select (status_type) { 754 * case ocsp: OCSPStatusRequest; 755 * case ocsp_multi: OCSPStatusRequest; 756 * } request; 757 * } CertificateStatusRequestItemV2; 758 * 759 * enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType; 760 * struct { 761 * ResponderID responder_id_list<0..2^16-1>; 762 * Extensions request_extensions; 763 * } OCSPStatusRequest; 764 * 765 * opaque ResponderID<1..2^16-1>; 766 * opaque Extensions<0..2^16-1>; 767 * 768 * struct { 769 * CertificateStatusRequestItemV2 770 * certificate_status_req_list<1..2^16-1>; 771 * } CertificateStatusRequestListV2; 772 */ 773 static final class CertStatusRequestV2Spec implements SSLExtensionSpec { 774 static final CertStatusRequestV2Spec DEFAULT = 775 new CertStatusRequestV2Spec(new CertStatusRequest[] { 776 OCSPStatusRequest.EMPTY_OCSP_MULTI}); 777 778 final CertStatusRequest[] certStatusRequests; 779 780 private CertStatusRequestV2Spec(CertStatusRequest[] certStatusRequests) { 781 this.certStatusRequests = certStatusRequests; 782 } 783 784 private CertStatusRequestV2Spec(ByteBuffer message) throws IOException { 785 // Is it a empty extension_data? 786 if (message.remaining() == 0) { 787 // server response 788 this.certStatusRequests = new CertStatusRequest[0]; 789 return; 790 } 791 792 if (message.remaining() < 5) { // 2: certificate_status_req_list 793 // +1: status_type 794 // +2: request_length 795 throw new SSLProtocolException( 796 "Invalid status_request_v2 extension: insufficient data"); 797 } 798 799 int listLen = Record.getInt16(message); 800 if (listLen <= 0) { 801 throw new SSLProtocolException( 802 "certificate_status_req_list length must be positive " + 803 "(received length: " + listLen + ")"); 804 } 805 806 int remaining = listLen; 807 List<CertStatusRequest> statusRequests = new ArrayList<>(); 808 while (remaining > 0) { 809 byte statusType = (byte)Record.getInt8(message); 810 int requestLen = Record.getInt16(message); 811 812 if (message.remaining() < requestLen) { 813 throw new SSLProtocolException( 814 "Invalid status_request_v2 extension: " + 815 "insufficient data (request_length=" + requestLen + 816 ", remining=" + message.remaining() + ")"); 817 } 818 819 byte[] encoded = new byte[requestLen]; 820 if (encoded.length != 0) { 821 message.get(encoded); 822 } 823 remaining -= 3; // 1(status type) + 2(request_length) bytes 824 remaining -= requestLen; 825 826 if (statusType == CertStatusRequestType.OCSP.id || 827 statusType == CertStatusRequestType.OCSP_MULTI.id) { 828 if (encoded.length < 4) { 829 // 2: length of responder_id_list 830 // +2: length of request_extensions 831 throw new SSLProtocolException( 832 "Invalid status_request_v2 extension: " + 833 "insufficient data"); 834 } 835 statusRequests.add( 836 new OCSPStatusRequest(statusType, encoded)); 837 } else { 838 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 839 SSLLogger.info( 840 "Unknown certificate status request " + 841 "(status type: " + statusType + ")"); 842 } 843 statusRequests.add( 844 new CertStatusRequest(statusType, encoded)); 845 } 846 } 847 848 certStatusRequests = 849 statusRequests.toArray(new CertStatusRequest[0]); 850 } 851 852 @Override 853 public String toString() { 854 if (certStatusRequests == null || certStatusRequests.length == 0) { 855 return "<empty>"; 856 } else { 857 MessageFormat messageFormat = new MessageFormat( 858 "\"cert status request\": '{'\n{0}\n'}'", Locale.ENGLISH); 859 860 StringBuilder builder = new StringBuilder(512); 861 boolean isFirst = true; 862 for (CertStatusRequest csr : certStatusRequests) { 863 if (isFirst) { 864 isFirst = false; 865 } else { 866 builder.append(", "); 867 } 868 Object[] messageFields = { 869 Utilities.indent(csr.toString()) 870 }; 871 builder.append(messageFormat.format(messageFields)); 872 } 873 874 return builder.toString(); 875 } 876 } 877 } 878 879 private static final 880 class CertStatusRequestsStringize implements SSLStringize { 881 @Override 882 public String toString(ByteBuffer buffer) { 883 try { 884 return (new CertStatusRequestV2Spec(buffer)).toString(); 885 } catch (IOException ioe) { 886 // For debug logging only, so please swallow exceptions. 887 return ioe.getMessage(); 888 } 889 } 890 } 891 892 /** 893 * Network data producer of a "status_request_v2" extension in the 894 * ClientHello handshake message. 895 */ 896 private static final 897 class CHCertStatusReqV2Producer implements HandshakeProducer { 898 // Prevent instantiation of this class. 899 private CHCertStatusReqV2Producer() { 900 // blank 901 } 902 903 @Override 904 public byte[] produce(ConnectionContext context, 905 HandshakeMessage message) throws IOException { 906 // The producing happens in client side only. 907 ClientHandshakeContext chc = (ClientHandshakeContext)context; 908 909 if (!chc.sslContext.isStaplingEnabled(true)) { 910 return null; 911 } 912 913 if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) { 914 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 915 SSLLogger.finest( 916 "Ignore unavailable status_request_v2 extension"); 917 } 918 919 return null; 920 } 921 922 // Produce the extension. 923 // 924 // We are using empty OCSPStatusRequest at present. May extend to 925 // support specific responder or extensions later. 926 byte[] extData = new byte[] { 927 0x00, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00}; 928 929 // Update the context. 930 chc.handshakeExtensions.put( 931 CH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); 932 933 return extData; 934 } 935 } 936 937 /** 938 * Network data consumer of a "status_request_v2" extension in the 939 * ClientHello handshake message. 940 */ 941 private static final 942 class CHCertStatusReqV2Consumer implements ExtensionConsumer { 943 // Prevent instantiation of this class. 944 private CHCertStatusReqV2Consumer() { 945 // blank 946 } 947 948 @Override 949 public void consume(ConnectionContext context, 950 HandshakeMessage message, ByteBuffer buffer) throws IOException { 951 952 // The comsuming happens in server side only. 953 ServerHandshakeContext shc = (ServerHandshakeContext)context; 954 955 if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) { 956 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 957 SSLLogger.finest( 958 "Ignore unavailable status_request_v2 extension"); 959 } 960 961 return; // ignore the extension 962 } 963 964 // Parse the extension. 965 CertStatusRequestV2Spec spec; 966 try { 967 spec = new CertStatusRequestV2Spec(buffer); 968 } catch (IOException ioe) { 969 shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); 970 return; // fatal() always throws, make the compiler happy. 971 } 972 973 // Update the context. 974 shc.handshakeExtensions.put(CH_STATUS_REQUEST_V2, spec); 975 shc.handshakeProducers.putIfAbsent( 976 SSLHandshake.CERTIFICATE_STATUS.id, 977 SSLHandshake.CERTIFICATE_STATUS); 978 // No impact on session resumption. 979 } 980 } 981 982 /** 983 * Network data producer of a "status_request_v2" extension in the 984 * ServerHello handshake message. 985 */ 986 private static final 987 class SHCertStatusReqV2Producer implements HandshakeProducer { 988 // Prevent instantiation of this class. 989 private SHCertStatusReqV2Producer() { 990 // blank 991 } 992 993 @Override 994 public byte[] produce(ConnectionContext context, 995 HandshakeMessage message) throws IOException { 996 // The producing happens in client side only. 997 998 ServerHandshakeContext shc = (ServerHandshakeContext)context; 999 // The StaplingParameters in the ServerHandshakeContext will 1000 // contain the info about what kind of stapling (if any) to 1001 // perform and whether this status_request extension should be 1002 // produced or the status_request_v2 (found in a different producer) 1003 // No explicit check is required for isStaplingEnabled here. If 1004 // it is false then stapleParams will be null. If it is true 1005 // then stapleParams may or may not be false and the check below 1006 // is sufficient. 1007 if ((shc.stapleParams == null) || 1008 (shc.stapleParams.statusRespExt != 1009 SSLExtension.CH_STATUS_REQUEST_V2)) { 1010 return null; // Do not produce status_request_v2 in SH 1011 } 1012 1013 // In response to "status_request_v2" extension request only 1014 CertStatusRequestV2Spec spec = (CertStatusRequestV2Spec) 1015 shc.handshakeExtensions.get(CH_STATUS_REQUEST_V2); 1016 if (spec == null) { 1017 // Ignore, no status_request_v2 extension requested. 1018 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 1019 SSLLogger.finest( 1020 "Ignore unavailable status_request_v2 extension"); 1021 } 1022 1023 return null; // ignore the extension 1024 } 1025 1026 // Is it a session resuming? 1027 if (shc.isResumption) { 1028 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 1029 SSLLogger.finest( 1030 "No status_request_v2 response for session resumption"); 1031 } 1032 return null; // ignore the extension 1033 } 1034 1035 // The "extension_data" in the extended ServerHello handshake 1036 // message MUST be empty. 1037 byte[] extData = new byte[0]; 1038 1039 // Update the context. 1040 shc.handshakeExtensions.put( 1041 SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); 1042 1043 return extData; 1044 } 1045 } 1046 1047 /** 1048 * Network data consumer of a "status_request_v2" extension in the 1049 * ServerHello handshake message. 1050 */ 1051 private static final 1052 class SHCertStatusReqV2Consumer implements ExtensionConsumer { 1053 // Prevent instantiation of this class. 1054 private SHCertStatusReqV2Consumer() { 1055 // blank 1056 } 1057 1058 @Override 1059 public void consume(ConnectionContext context, 1060 HandshakeMessage message, ByteBuffer buffer) throws IOException { 1061 1062 // The consumption happens in client side only. 1063 ClientHandshakeContext chc = (ClientHandshakeContext)context; 1064 1065 // In response to "status_request" extension request only 1066 CertStatusRequestV2Spec requestedCsr = (CertStatusRequestV2Spec) 1067 chc.handshakeExtensions.get(CH_STATUS_REQUEST_V2); 1068 if (requestedCsr == null) { 1069 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 1070 "Unexpected status_request_v2 extension in ServerHello"); 1071 } 1072 1073 // Parse the extension. 1074 if (buffer.hasRemaining()) { 1075 chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, 1076 "Invalid status_request_v2 extension in ServerHello: " + 1077 "the extension data must be empty"); 1078 } 1079 1080 // Update the context. 1081 chc.handshakeExtensions.put( 1082 SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); 1083 chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id, 1084 SSLHandshake.CERTIFICATE_STATUS); 1085 1086 // Since we've received a legitimate status_request in the 1087 // ServerHello, stapling is active if it's been enabled. 1088 chc.staplingActive = chc.sslContext.isStaplingEnabled(true); 1089 1090 // No impact on session resumption. 1091 } 1092 } 1093 1094 private static final 1095 class CTCertStatusResponseProducer implements HandshakeProducer { 1096 // Prevent instantiation of this class. 1097 private CTCertStatusResponseProducer() { 1098 // blank 1099 } 1100 1101 @Override 1102 public byte[] produce(ConnectionContext context, 1103 HandshakeMessage message) throws IOException { 1104 ServerHandshakeContext shc = (ServerHandshakeContext)context; 1105 byte[] producedData = null; 1106 1107 // Stapling needs to be active and have valid data to proceed 1108 if (shc.stapleParams == null) { 1109 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 1110 SSLLogger.finest( 1111 "Stapling is disabled for this connection"); 1112 } 1113 return null; 1114 } 1115 1116 // There needs to be a non-null CertificateEntry to proceed 1117 if (shc.currentCertEntry == null) { 1118 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { 1119 SSLLogger.finest("Found null CertificateEntry in context"); 1120 } 1121 return null; 1122 } 1123 1124 // Pull the certificate from the CertificateEntry and find 1125 // a response from the response map. If one exists we will 1126 // staple it. 1127 try { 1128 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 1129 X509Certificate x509Cert = 1130 (X509Certificate)cf.generateCertificate( 1131 new ByteArrayInputStream( 1132 shc.currentCertEntry.encoded)); 1133 byte[] respBytes = shc.stapleParams.responseMap.get(x509Cert); 1134 if (respBytes == null) { 1135 // We're done with this entry. Clear it from the context 1136 if (SSLLogger.isOn && 1137 SSLLogger.isOn("ssl,handshake,verbose")) { 1138 SSLLogger.finest("No status response found for " + 1139 x509Cert.getSubjectX500Principal()); 1140 } 1141 shc.currentCertEntry = null; 1142 return null; 1143 } 1144 1145 // Build a proper response buffer from the stapling information 1146 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { 1147 SSLLogger.finest("Found status response for " + 1148 x509Cert.getSubjectX500Principal() + 1149 ", response length: " + respBytes.length); 1150 } 1151 CertStatusResponse certResp = (shc.stapleParams.statReqType == 1152 CertStatusRequestType.OCSP) ? 1153 new OCSPStatusResponse(shc.stapleParams.statReqType.id, 1154 respBytes) : 1155 new CertStatusResponse(shc.stapleParams.statReqType.id, 1156 respBytes); 1157 producedData = certResp.toByteArray(); 1158 } catch (CertificateException ce) { 1159 shc.conContext.fatal(Alert.BAD_CERTIFICATE, 1160 "Failed to parse server certificates", ce); 1161 } catch (IOException ioe) { 1162 shc.conContext.fatal(Alert.BAD_CERT_STATUS_RESPONSE, 1163 "Failed to parse certificate status response", ioe); 1164 } 1165 1166 // Clear the pinned CertificateEntry from the context 1167 shc.currentCertEntry = null; 1168 return producedData; 1169 } 1170 } 1171 1172 private static final 1173 class CTCertStatusResponseConsumer implements ExtensionConsumer { 1174 // Prevent instantiation of this class. 1175 private CTCertStatusResponseConsumer() { 1176 // blank 1177 } 1178 1179 @Override 1180 public void consume(ConnectionContext context, 1181 HandshakeMessage message, ByteBuffer buffer) throws IOException { 1182 // The consumption happens in client side only. 1183 ClientHandshakeContext chc = (ClientHandshakeContext)context; 1184 1185 // Parse the extension. 1186 CertStatusResponseSpec spec; 1187 try { 1188 spec = new CertStatusResponseSpec(buffer); 1189 } catch (IOException ioe) { 1190 chc.conContext.fatal(Alert.DECODE_ERROR, ioe); 1191 return; // fatal() always throws, make the compiler happy. 1192 } 1193 1194 if (chc.sslContext.isStaplingEnabled(true)) { 1195 // Activate stapling 1196 chc.staplingActive = true; 1197 } else { 1198 // Do no further processing of stapled responses 1199 return; 1200 } 1201 1202 // Get response list from the session. This is unmodifiable 1203 // so we need to create a new list. Then add this new response 1204 // to the end and submit it back to the session object. 1205 if ((chc.handshakeSession != null) && (!chc.isResumption)) { 1206 List<byte[]> respList = new ArrayList<>( 1207 chc.handshakeSession.getStatusResponses()); 1208 respList.add(spec.statusResponse.encodedResponse); 1209 chc.handshakeSession.setStatusResponses(respList); 1210 } else { 1211 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { 1212 SSLLogger.finest( 1213 "Ignoring stapled data on resumed session"); 1214 } 1215 } 1216 } 1217 } 1218 } |