< prev index next >
src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
--- 1,7 ----
/*
! * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
*** 24,39 ****
*/
package sun.security.ssl;
import java.io.IOException;
! import java.util.Objects;
! /*
* RFC6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
* The "extension data" field of this extension contains a
* "CertificateStatusRequest" structure:
*
* struct {
* CertificateStatusType status_type;
--- 24,102 ----
*/
package sun.security.ssl;
import java.io.IOException;
! import java.io.ByteArrayInputStream;
! import java.nio.ByteBuffer;
! import java.security.cert.Extension;
! import java.security.cert.CertificateFactory;
! import java.security.cert.CertificateException;
! import java.security.cert.X509Certificate;
! import java.text.MessageFormat;
! import java.util.ArrayList;
! import java.util.List;
! import java.util.Locale;
! import javax.net.ssl.SSLProtocolException;
! import sun.security.provider.certpath.OCSPResponse;
! import sun.security.provider.certpath.ResponderId;
! import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST;
! import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST_V2;
! import sun.security.ssl.SSLExtension.ExtensionConsumer;
! import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST;
! import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST_V2;
! import sun.security.ssl.SSLExtension.SSLExtensionSpec;
! import sun.security.ssl.SSLHandshake.HandshakeMessage;
! import sun.security.util.DerInputStream;
! import sun.security.util.DerValue;
! import sun.security.util.HexDumpEncoder;
! /**
! * Pack of "status_request" and "status_request_v2" extensions.
! */
! final class CertStatusExtension {
! static final HandshakeProducer chNetworkProducer =
! new CHCertStatusReqProducer();
! static final ExtensionConsumer chOnLoadConsumer =
! new CHCertStatusReqConsumer();
!
! static final HandshakeProducer shNetworkProducer =
! new SHCertStatusReqProducer();
! static final ExtensionConsumer shOnLoadConsumer =
! new SHCertStatusReqConsumer();
!
! static final HandshakeProducer ctNetworkProducer =
! new CTCertStatusResponseProducer();
! static final ExtensionConsumer ctOnLoadConsumer =
! new CTCertStatusResponseConsumer();
!
! static final SSLStringize certStatusReqStringize =
! new CertStatusRequestStringize();
!
! static final HandshakeProducer chV2NetworkProducer =
! new CHCertStatusReqV2Producer();
! static final ExtensionConsumer chV2OnLoadConsumer =
! new CHCertStatusReqV2Consumer();
!
! static final HandshakeProducer shV2NetworkProducer =
! new SHCertStatusReqV2Producer();
! static final ExtensionConsumer shV2OnLoadConsumer =
! new SHCertStatusReqV2Consumer();
!
! static final SSLStringize certStatusReqV2Stringize =
! new CertStatusRequestsStringize();
!
! static final SSLStringize certStatusRespStringize =
! new CertStatusRespStringize();
!
! /**
! * The "status_request" extension.
! *
* RFC6066 defines the TLS extension,"status_request" (type 0x5),
* which allows the client to request that the server perform OCSP
* on the client's behalf.
+ *
* The "extension data" field of this extension contains a
* "CertificateStatusRequest" structure:
*
* struct {
* CertificateStatusType status_type;
*** 50,205 ****
* } OCSPStatusRequest;
*
* opaque ResponderID<1..2^16-1>;
* opaque Extensions<0..2^16-1>;
*/
! final class CertStatusReqExtension extends HelloExtension {
! private final StatusRequestType statReqType;
! private final StatusRequest request;
! /**
! * Construct the default status request extension object. The default
! * object results in a status_request extension where the extension
! * data segment is zero-length. This is used primarily in ServerHello
! * messages where the server asserts it can do RFC 6066 status stapling.
! */
! CertStatusReqExtension() {
! super(ExtensionType.EXT_STATUS_REQUEST);
! statReqType = null;
! request = null;
}
/**
! * Construct the status request extension object given a request type
! * and {@code StatusRequest} object.
*
! * @param reqType a {@code StatusRequestExtType object correspoding
! * to the underlying {@code StatusRequest} object. A value of
! * {@code null} is not allowed.
! * @param statReq the {@code StatusRequest} object used to provide the
! * encoding for the TLS extension. A value of {@code null} is not
! * allowed.
! *
! * @throws IllegalArgumentException if the provided {@code StatusRequest}
! * does not match the type.
! * @throws NullPointerException if either the {@code reqType} or
! * {@code statReq} arguments are {@code null}.
*/
! CertStatusReqExtension(StatusRequestType reqType, StatusRequest statReq) {
! super(ExtensionType.EXT_STATUS_REQUEST);
! statReqType = Objects.requireNonNull(reqType,
! "Unallowed null value for status_type");
! request = Objects.requireNonNull(statReq,
! "Unallowed null value for request");
! // There is currently only one known status type (OCSP)
! // We can add more clauses to cover other types in the future
! if (statReqType == StatusRequestType.OCSP) {
! if (!(statReq instanceof OCSPStatusRequest)) {
! throw new IllegalArgumentException("StatusRequest not " +
! "of type OCSPStatusRequest");
}
}
}
/**
! * Construct the {@code CertStatusReqExtension} object from data read from
! * a {@code HandshakeInputStream}
*
! * @param s the {@code HandshakeInputStream} providing the encoded data
! * @param len the length of the extension data
*
! * @throws IOException if any decoding errors happen during object
! * construction.
*/
! CertStatusReqExtension(HandshakeInStream s, int len) throws IOException {
! super(ExtensionType.EXT_STATUS_REQUEST);
! if (len > 0) {
! // Obtain the status type (first byte)
! statReqType = StatusRequestType.get(s.getInt8());
! if (statReqType == StatusRequestType.OCSP) {
! request = new OCSPStatusRequest(s);
! } else {
! // This is a status_type we don't understand. Create
! // an UnknownStatusRequest in order to preserve the data
! request = new UnknownStatusRequest(s, len - 1);
}
} else {
! // Treat this as a zero-length extension (i.e. from a ServerHello
! statReqType = null;
! request = null;
}
}
/**
! * Return the length of the encoded extension, including extension type,
! * extension length and status_type fields.
! *
! * @return the length in bytes, including the extension type and
! * length fields.
*/
@Override
! int length() {
! return (statReqType != null ? 5 + request.length() : 4);
}
/**
! * Send the encoded TLS extension through a {@code HandshakeOutputStream}
! *
! * @param s the {@code HandshakeOutputStream} used to send the encoded data
! *
! * @throws IOException tf any errors occur during the encoding process
*/
@Override
! void send(HandshakeOutStream s) throws IOException {
! s.putInt16(type.id);
! s.putInt16(this.length() - 4);
! if (statReqType != null) {
! s.putInt8(statReqType.id);
! request.send(s);
}
}
/**
! * Create a string representation of this {@code CertStatusReqExtension}
*
! * @return the string representation of this {@code CertStatusReqExtension}
*/
@Override
public String toString() {
! StringBuilder sb = new StringBuilder("Extension ").append(type);
! if (statReqType != null) {
! sb.append(": ").append(statReqType).append(", ").append(request);
}
! return sb.toString();
}
/**
! * Return the type field for this {@code CertStatusReqExtension}
! *
! * @return the {@code StatusRequestType} for this extension. {@code null}
! * will be returned if the default constructor is used to create
! * a zero length status_request extension (found in ServerHello
! * messages)
*/
! StatusRequestType getType() {
! return statReqType;
}
/**
! * Get the underlying {@code StatusRequest} for this
! * {@code CertStatusReqExtension}
! *
! * @return the {@code StatusRequest} or {@code null} if the default
! * constructor was used to create this extension.
*/
! StatusRequest getRequest() {
! return request;
}
}
--- 113,1218 ----
* } OCSPStatusRequest;
*
* opaque ResponderID<1..2^16-1>;
* opaque Extensions<0..2^16-1>;
*/
+ static final class CertStatusRequestSpec implements SSLExtensionSpec {
+ static final CertStatusRequestSpec DEFAULT =
+ new CertStatusRequestSpec(OCSPStatusRequest.EMPTY_OCSP);
! final CertStatusRequest statusRequest;
! private CertStatusRequestSpec(CertStatusRequest statusRequest) {
! this.statusRequest = statusRequest;
! }
+ private CertStatusRequestSpec(ByteBuffer buffer) throws IOException {
+ // Is it a empty extension_data?
+ if (buffer.remaining() == 0) {
+ // server response
+ this.statusRequest = null;
+ return;
+ }
! if (buffer.remaining() < 1) {
! throw new SSLProtocolException(
! "Invalid status_request extension: insufficient data");
! }
!
! byte statusType = (byte)Record.getInt8(buffer);
! byte[] encoded = new byte[buffer.remaining()];
! if (encoded.length != 0) {
! buffer.get(encoded);
! }
! if (statusType == CertStatusRequestType.OCSP.id) {
! this.statusRequest = new OCSPStatusRequest(statusType, encoded);
! } else {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.info(
! "Unknown certificate status request " +
! "(status type: " + statusType + ")");
! }
!
! this.statusRequest = new CertStatusRequest(statusType, encoded);
! }
! }
!
! @Override
! public String toString() {
! return statusRequest == null ?
! "<empty>" : statusRequest.toString();
! }
}
/**
! * Defines the CertificateStatus response structure as outlined in
! * RFC 6066. This will contain a status response type, plus a single,
! * non-empty OCSP response in DER-encoded form.
*
! * struct {
! * CertificateStatusType status_type;
! * select (status_type) {
! * case ocsp: OCSPResponse;
! * } response;
! * } CertificateStatus;
*/
! static final class CertStatusResponseSpec implements SSLExtensionSpec {
! final CertStatusResponse statusResponse;
! private CertStatusResponseSpec(CertStatusResponse resp) {
! this.statusResponse = resp;
! }
! private CertStatusResponseSpec(ByteBuffer buffer) throws IOException {
! if (buffer.remaining() < 2) {
! throw new SSLProtocolException(
! "Invalid status_request extension: insufficient data");
}
+
+ // Get the status type (1 byte) and response data (vector)
+ byte type = (byte)Record.getInt8(buffer);
+ byte[] respData = Record.getBytes24(buffer);
+
+ // Create the CertStatusResponse based on the type
+ if (type == CertStatusRequestType.OCSP.id) {
+ this.statusResponse = new OCSPStatusResponse(type, respData);
+ } else {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.info(
+ "Unknown certificate status response " +
+ "(status type: " + type + ")");
+ }
+
+ this.statusResponse = new CertStatusResponse(type, respData);
}
}
+ @Override
+ public String toString() {
+ return statusResponse == null ?
+ "<empty>" : statusResponse.toString();
+ }
+ }
+
+ private static final
+ class CertStatusRequestStringize implements SSLStringize {
+ @Override
+ public String toString(ByteBuffer buffer) {
+ try {
+ return (new CertStatusRequestSpec(buffer)).toString();
+ } catch (IOException ioe) {
+ // For debug logging only, so please swallow exceptions.
+ return ioe.getMessage();
+ }
+ }
+ }
+
+ private static final
+ class CertStatusRespStringize implements SSLStringize {
+ @Override
+ public String toString(ByteBuffer buffer) {
+ try {
+ return (new CertStatusResponseSpec(buffer)).toString();
+ } catch (IOException ioe) {
+ // For debug logging only, so please swallow exceptions.
+ return ioe.getMessage();
+ }
+ }
+ }
+
+ static enum CertStatusRequestType {
+ OCSP ((byte)0x01, "ocsp"), // RFC 6066/6961
+ OCSP_MULTI ((byte)0x02, "ocsp_multi"); // RFC 6961
+
+ final byte id;
+ final String name;
+
+ private CertStatusRequestType(byte id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
/**
! * Returns the enum constant of the specified id (see RFC 6066).
! */
! static CertStatusRequestType valueOf(byte id) {
! for (CertStatusRequestType srt : CertStatusRequestType.values()) {
! if (srt.id == id) {
! return srt;
! }
! }
!
! return null;
! }
!
! static String nameOf(byte id) {
! for (CertStatusRequestType srt : CertStatusRequestType.values()) {
! if (srt.id == id) {
! return srt.name;
! }
! }
!
! return "UNDEFINED-CERT-STATUS-TYPE(" + id + ")";
! }
! }
!
! static class CertStatusRequest {
! final byte statusType;
! final byte[] encodedRequest;
!
! protected CertStatusRequest(byte statusType, byte[] encodedRequest) {
! this.statusType = statusType;
! this.encodedRequest = encodedRequest;
! }
!
! @Override
! public String toString() {
! MessageFormat messageFormat = new MessageFormat(
! "\"certificate status type\": {0}\n" +
! "\"encoded certificate status\": '{'\n" +
! "{1}\n" +
! "'}'",
! Locale.ENGLISH);
!
! HexDumpEncoder hexEncoder = new HexDumpEncoder();
! String encoded = hexEncoder.encodeBuffer(encodedRequest);
!
! Object[] messageFields = {
! CertStatusRequestType.nameOf(statusType),
! Utilities.indent(encoded)
! };
!
! return messageFormat.format(messageFields);
! }
! }
!
! /*
! * RFC6066 defines the TLS extension,"status_request" (type 0x5),
! * which allows the client to request that the server perform OCSP
! * on the client's behalf.
*
! * The RFC defines an OCSPStatusRequest structure:
*
! * struct {
! * ResponderID responder_id_list<0..2^16-1>;
! * Extensions request_extensions;
! * } OCSPStatusRequest;
*/
! static final class OCSPStatusRequest extends CertStatusRequest {
! static final OCSPStatusRequest EMPTY_OCSP;
! static final OCSPStatusRequest EMPTY_OCSP_MULTI;
! final List<ResponderId> responderIds;
! final List<Extension> extensions;
! private final int encodedLen;
! private final int ridListLen;
! private final int extListLen;
!
! static {
! OCSPStatusRequest ocspReq = null;
! OCSPStatusRequest multiReq = null;
!
! try {
! ocspReq = new OCSPStatusRequest(
! CertStatusRequestType.OCSP.id,
! new byte[] {0x00, 0x00, 0x00, 0x00});
! multiReq = new OCSPStatusRequest(
! CertStatusRequestType.OCSP_MULTI.id,
! new byte[] {0x00, 0x00, 0x00, 0x00});
! } catch (IOException ioe) {
! // unlikely
}
+
+ EMPTY_OCSP = ocspReq;
+ EMPTY_OCSP_MULTI = multiReq;
+ }
+
+ private OCSPStatusRequest(byte statusType,
+ byte[] encoded) throws IOException {
+ super(statusType, encoded);
+
+ if (encoded == null || encoded.length < 4) {
+ // 2: length of responder_id_list
+ // +2: length of request_extensions
+ throw new SSLProtocolException(
+ "Invalid OCSP status request: insufficient data");
+ }
+ this.encodedLen = encoded.length;
+
+ List<ResponderId> rids = new ArrayList<>();
+ List<Extension> exts = new ArrayList<>();
+ ByteBuffer m = ByteBuffer.wrap(encoded);
+
+ this.ridListLen = Record.getInt16(m);
+ if (m.remaining() < (ridListLen + 2)) {
+ throw new SSLProtocolException(
+ "Invalid OCSP status request: insufficient data");
+ }
+
+ int ridListBytesRemaining = ridListLen;
+ while (ridListBytesRemaining >= 2) { // 2: length of responder_id
+ byte[] ridBytes = Record.getBytes16(m);
+ try {
+ rids.add(new ResponderId(ridBytes));
+ } catch (IOException ioe) {
+ throw new SSLProtocolException(
+ "Invalid OCSP status request: invalid responder ID");
+ }
+ ridListBytesRemaining -= ridBytes.length + 2;
+ }
+
+ if (ridListBytesRemaining != 0) {
+ throw new SSLProtocolException(
+ "Invalid OCSP status request: incomplete data");
+ }
+
+ byte[] extListBytes = Record.getBytes16(m);
+ this.extListLen = extListBytes.length;
+ if (extListLen > 0) {
+ try {
+ DerInputStream dis = new DerInputStream(extListBytes);
+ DerValue[] extSeqContents =
+ dis.getSequence(extListBytes.length);
+ for (DerValue extDerVal : extSeqContents) {
+ exts.add(new sun.security.x509.Extension(extDerVal));
+ }
+ } catch (IOException ioe) {
+ throw new SSLProtocolException(
+ "Invalid OCSP status request: invalid extension");
+ }
+ }
+
+ this.responderIds = rids;
+ this.extensions = exts;
+ }
+
+ @Override
+ public String toString() {
+ MessageFormat messageFormat = new MessageFormat(
+ "\"certificate status type\": {0}\n" +
+ "\"OCSP status request\": '{'\n" +
+ "{1}\n" +
+ "'}'",
+ Locale.ENGLISH);
+
+ MessageFormat requestFormat = new MessageFormat(
+ "\"responder_id\": {0}\n" +
+ "\"request extensions\": '{'\n" +
+ "{1}\n" +
+ "'}'",
+ Locale.ENGLISH);
+
+ String ridStr = "<empty>";
+ if (!responderIds.isEmpty()) {
+ ridStr = responderIds.toString();
+
+ }
+
+ String extsStr = "<empty>";
+ if (!extensions.isEmpty()) {
+ StringBuilder extBuilder = new StringBuilder(512);
+ boolean isFirst = true;
+ for (Extension ext : this.extensions) {
+ if (isFirst) {
+ isFirst = false;
} else {
! extBuilder.append(",\n");
! }
! extBuilder.append(
! "{\n" + Utilities.indent(ext.toString()) + "}");
! }
!
! extsStr = extBuilder.toString();
! }
!
! Object[] requestFields = {
! ridStr,
! Utilities.indent(extsStr)
! };
! String ocspStatusRequest = requestFormat.format(requestFields);
!
! Object[] messageFields = {
! CertStatusRequestType.nameOf(statusType),
! Utilities.indent(ocspStatusRequest)
! };
!
! return messageFormat.format(messageFields);
! }
! }
!
! static class CertStatusResponse {
! final byte statusType;
! final byte[] encodedResponse;
!
! protected CertStatusResponse(byte statusType, byte[] respDer) {
! this.statusType = statusType;
! this.encodedResponse = respDer;
! }
!
! byte[] toByteArray() throws IOException {
! // Create a byte array large enough to handle the status_type
! // field (1) + OCSP length (3) + OCSP data (variable)
! byte[] outData = new byte[encodedResponse.length + 4];
! ByteBuffer buf = ByteBuffer.wrap(outData);
! Record.putInt8(buf, statusType);
! Record.putBytes24(buf, encodedResponse);
! return buf.array();
! }
!
! @Override
! public String toString() {
! MessageFormat messageFormat = new MessageFormat(
! "\"certificate status response type\": {0}\n" +
! "\"encoded certificate status\": '{'\n" +
! "{1}\n" +
! "'}'",
! Locale.ENGLISH);
!
! HexDumpEncoder hexEncoder = new HexDumpEncoder();
! String encoded = hexEncoder.encodeBuffer(encodedResponse);
!
! Object[] messageFields = {
! CertStatusRequestType.nameOf(statusType),
! Utilities.indent(encoded)
! };
!
! return messageFormat.format(messageFields);
! }
! }
!
! static final class OCSPStatusResponse extends CertStatusResponse {
! final OCSPResponse ocspResponse;
!
! private OCSPStatusResponse(byte statusType,
! byte[] encoded) throws IOException {
! super(statusType, encoded);
!
! // The DER-encoded OCSP response must not be zero length
! if (encoded == null || encoded.length < 1) {
! throw new SSLProtocolException(
! "Invalid OCSP status response: insufficient data");
! }
!
! // Otherwise, make an OCSPResponse object from the data
! ocspResponse = new OCSPResponse(encoded);
! }
!
! @Override
! public String toString() {
! MessageFormat messageFormat = new MessageFormat(
! "\"certificate status response type\": {0}\n" +
! "\"OCSP status response\": '{'\n" +
! "{1}\n" +
! "'}'",
! Locale.ENGLISH);
!
! Object[] messageFields = {
! CertStatusRequestType.nameOf(statusType),
! Utilities.indent(ocspResponse.toString())
! };
!
! return messageFormat.format(messageFields);
}
}
/**
! * Network data producer of a "status_request" extension in the
! * ClientHello handshake message.
*/
+ private static final
+ class CHCertStatusReqProducer implements HandshakeProducer {
+ // Prevent instantiation of this class.
+ private CHCertStatusReqProducer() {
+ // blank
+ }
+
@Override
! public byte[] produce(ConnectionContext context,
! HandshakeMessage message) throws IOException {
! // The producing happens in client side only.
! ClientHandshakeContext chc = (ClientHandshakeContext)context;
!
! if (!chc.sslContext.isStaplingEnabled(true)) {
! return null;
! }
!
! if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST)) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.fine(
! "Ignore unavailable extension: " +
! CH_STATUS_REQUEST.name);
! }
! return null;
! }
!
! // Produce the extension.
! //
! // We are using empty OCSPStatusRequest at present. May extend to
! // support specific responder or extensions later.
! byte[] extData = new byte[] {0x01, 0x00, 0x00, 0x00, 0x00};
!
! // Update the context.
! chc.handshakeExtensions.put(
! CH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT);
!
! return extData;
! }
}
/**
! * Network data consumer of a "status_request" extension in the
! * ClientHello handshake message.
*/
+ private static final
+ class CHCertStatusReqConsumer implements ExtensionConsumer {
+ // Prevent instantiation of this class.
+ private CHCertStatusReqConsumer() {
+ // blank
+ }
+
@Override
! public void consume(ConnectionContext context,
! HandshakeMessage message, ByteBuffer buffer) throws IOException {
!
! // The comsuming happens in server side only.
! ServerHandshakeContext shc = (ServerHandshakeContext)context;
!
! if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST)) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.fine("Ignore unavailable extension: " +
! CH_STATUS_REQUEST.name);
! }
! return; // ignore the extension
! }
!
! // Parse the extension.
! CertStatusRequestSpec spec;
! try {
! spec = new CertStatusRequestSpec(buffer);
! } catch (IOException ioe) {
! shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
! return; // fatal() always throws, make the compiler happy.
! }
! // Update the context.
! shc.handshakeExtensions.put(CH_STATUS_REQUEST, spec);
! if (!shc.negotiatedProtocol.useTLS13PlusSpec()) {
! shc.handshakeProducers.put(SSLHandshake.CERTIFICATE_STATUS.id,
! SSLHandshake.CERTIFICATE_STATUS);
! } // Otherwise, the certificate status presents in server cert.
!
! // No impact on session resumption.
}
}
/**
! * Network data producer of a "status_request" extension in the
! * ServerHello handshake message.
! */
! private static final
! class SHCertStatusReqProducer implements HandshakeProducer {
! // Prevent instantiation of this class.
! private SHCertStatusReqProducer() {
! // blank
! }
!
! @Override
! public byte[] produce(ConnectionContext context,
! HandshakeMessage message) throws IOException {
! // The producing happens in client side only.
! ServerHandshakeContext shc = (ServerHandshakeContext)context;
!
! // The StaplingParameters in the ServerHandshakeContext will
! // contain the info about what kind of stapling (if any) to
! // perform and whether this status_request extension should be
! // produced or the status_request_v2 (found in a different producer)
! // No explicit check is required for isStaplingEnabled here. If
! // it is false then stapleParams will be null. If it is true
! // then stapleParams may or may not be false and the check below
! // is sufficient.
! if ((shc.stapleParams == null) ||
! (shc.stapleParams.statusRespExt !=
! SSLExtension.CH_STATUS_REQUEST)) {
! return null; // Do not produce status_request in ServerHello
! }
!
! // In response to "status_request" extension request only.
! CertStatusRequestSpec spec = (CertStatusRequestSpec)
! shc.handshakeExtensions.get(CH_STATUS_REQUEST);
! if (spec == null) {
! // Ignore, no status_request extension requested.
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "Ignore unavailable extension: " +
! CH_STATUS_REQUEST.name);
! }
!
! return null; // ignore the extension
! }
!
! // Is it a session resuming?
! if (shc.isResumption) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "No status_request response for session resuming");
! }
!
! return null; // ignore the extension
! }
!
! // The "extension_data" in the extended ServerHello handshake
! // message MUST be empty.
! byte[] extData = new byte[0];
!
! // Update the context.
! shc.handshakeExtensions.put(
! SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT);
!
! return extData;
! }
! }
!
! /**
! * Network data consumer of a "status_request" extension in the
! * ServerHello handshake message.
! */
! private static final
! class SHCertStatusReqConsumer implements ExtensionConsumer {
! // Prevent instantiation of this class.
! private SHCertStatusReqConsumer() {
! // blank
! }
!
! @Override
! public void consume(ConnectionContext context,
! HandshakeMessage message, ByteBuffer buffer) throws IOException {
!
! // The producing happens in client side only.
! ClientHandshakeContext chc = (ClientHandshakeContext)context;
!
! // In response to "status_request" extension request only.
! CertStatusRequestSpec requestedCsr = (CertStatusRequestSpec)
! chc.handshakeExtensions.get(CH_STATUS_REQUEST);
! if (requestedCsr == null) {
! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
! "Unexpected status_request extension in ServerHello");
! }
!
! // Parse the extension.
! if (buffer.hasRemaining()) {
! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
! "Invalid status_request extension in ServerHello message: " +
! "the extension data must be empty");
! }
!
! // Update the context.
! chc.handshakeExtensions.put(
! SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT);
! chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id,
! SSLHandshake.CERTIFICATE_STATUS);
!
! // Since we've received a legitimate status_request in the
! // ServerHello, stapling is active if it's been enabled.
! chc.staplingActive = chc.sslContext.isStaplingEnabled(true);
!
! // No impact on session resumption.
! }
! }
!
! /**
! * The "status_request_v2" extension.
! *
! * RFC6961 defines the TLS extension,"status_request_v2" (type 0x5),
! * which allows the client to request that the server perform OCSP
! * on the client's behalf.
! *
! * The RFC defines an CertStatusReqItemV2 structure:
! *
! * struct {
! * CertificateStatusType status_type;
! * uint16 request_length;
! * select (status_type) {
! * case ocsp: OCSPStatusRequest;
! * case ocsp_multi: OCSPStatusRequest;
! * } request;
! * } CertificateStatusRequestItemV2;
! *
! * enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType;
! * struct {
! * ResponderID responder_id_list<0..2^16-1>;
! * Extensions request_extensions;
! * } OCSPStatusRequest;
*
! * opaque ResponderID<1..2^16-1>;
! * opaque Extensions<0..2^16-1>;
! *
! * struct {
! * CertificateStatusRequestItemV2
! * certificate_status_req_list<1..2^16-1>;
! * } CertificateStatusRequestListV2;
*/
+ static final class CertStatusRequestV2Spec implements SSLExtensionSpec {
+ static final CertStatusRequestV2Spec DEFAULT =
+ new CertStatusRequestV2Spec(new CertStatusRequest[] {
+ OCSPStatusRequest.EMPTY_OCSP_MULTI});
+
+ final CertStatusRequest[] certStatusRequests;
+
+ private CertStatusRequestV2Spec(CertStatusRequest[] certStatusRequests) {
+ this.certStatusRequests = certStatusRequests;
+ }
+
+ private CertStatusRequestV2Spec(ByteBuffer message) throws IOException {
+ // Is it a empty extension_data?
+ if (message.remaining() == 0) {
+ // server response
+ this.certStatusRequests = new CertStatusRequest[0];
+ return;
+ }
+
+ if (message.remaining() < 5) { // 2: certificate_status_req_list
+ // +1: status_type
+ // +2: request_length
+ throw new SSLProtocolException(
+ "Invalid status_request_v2 extension: insufficient data");
+ }
+
+ int listLen = Record.getInt16(message);
+ if (listLen <= 0) {
+ throw new SSLProtocolException(
+ "certificate_status_req_list length must be positive " +
+ "(received length: " + listLen + ")");
+ }
+
+ int remaining = listLen;
+ List<CertStatusRequest> statusRequests = new ArrayList<>();
+ while (remaining > 0) {
+ byte statusType = (byte)Record.getInt8(message);
+ int requestLen = Record.getInt16(message);
+
+ if (message.remaining() < requestLen) {
+ throw new SSLProtocolException(
+ "Invalid status_request_v2 extension: " +
+ "insufficient data (request_length=" + requestLen +
+ ", remining=" + message.remaining() + ")");
+ }
+
+ byte[] encoded = new byte[requestLen];
+ if (encoded.length != 0) {
+ message.get(encoded);
+ }
+ remaining -= 3; // 1(status type) + 2(request_length) bytes
+ remaining -= requestLen;
+
+ if (statusType == CertStatusRequestType.OCSP.id ||
+ statusType == CertStatusRequestType.OCSP_MULTI.id) {
+ if (encoded.length < 4) {
+ // 2: length of responder_id_list
+ // +2: length of request_extensions
+ throw new SSLProtocolException(
+ "Invalid status_request_v2 extension: " +
+ "insufficient data");
+ }
+ statusRequests.add(
+ new OCSPStatusRequest(statusType, encoded));
+ } else {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.info(
+ "Unknown certificate status request " +
+ "(status type: " + statusType + ")");
+ }
+ statusRequests.add(
+ new CertStatusRequest(statusType, encoded));
+ }
+ }
+
+ certStatusRequests =
+ statusRequests.toArray(new CertStatusRequest[0]);
+ }
+
@Override
public String toString() {
! if (certStatusRequests == null || certStatusRequests.length == 0) {
! return "<empty>";
! } else {
! MessageFormat messageFormat = new MessageFormat(
! "\"cert status request\": '{'\n{0}\n'}'", Locale.ENGLISH);
!
! StringBuilder builder = new StringBuilder(512);
! boolean isFirst = true;
! for (CertStatusRequest csr : certStatusRequests) {
! if (isFirst) {
! isFirst = false;
! } else {
! builder.append(", ");
! }
! Object[] messageFields = {
! Utilities.indent(csr.toString())
! };
! builder.append(messageFormat.format(messageFields));
! }
!
! return builder.toString();
! }
! }
}
! private static final
! class CertStatusRequestsStringize implements SSLStringize {
! @Override
! public String toString(ByteBuffer buffer) {
! try {
! return (new CertStatusRequestV2Spec(buffer)).toString();
! } catch (IOException ioe) {
! // For debug logging only, so please swallow exceptions.
! return ioe.getMessage();
! }
! }
}
/**
! * Network data producer of a "status_request_v2" extension in the
! * ClientHello handshake message.
! */
! private static final
! class CHCertStatusReqV2Producer implements HandshakeProducer {
! // Prevent instantiation of this class.
! private CHCertStatusReqV2Producer() {
! // blank
! }
!
! @Override
! public byte[] produce(ConnectionContext context,
! HandshakeMessage message) throws IOException {
! // The producing happens in client side only.
! ClientHandshakeContext chc = (ClientHandshakeContext)context;
!
! if (!chc.sslContext.isStaplingEnabled(true)) {
! return null;
! }
!
! if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "Ignore unavailable status_request_v2 extension");
! }
!
! return null;
! }
!
! // Produce the extension.
! //
! // We are using empty OCSPStatusRequest at present. May extend to
! // support specific responder or extensions later.
! byte[] extData = new byte[] {
! 0x00, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00};
!
! // Update the context.
! chc.handshakeExtensions.put(
! CH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT);
!
! return extData;
! }
! }
!
! /**
! * Network data consumer of a "status_request_v2" extension in the
! * ClientHello handshake message.
*/
! private static final
! class CHCertStatusReqV2Consumer implements ExtensionConsumer {
! // Prevent instantiation of this class.
! private CHCertStatusReqV2Consumer() {
! // blank
! }
!
! @Override
! public void consume(ConnectionContext context,
! HandshakeMessage message, ByteBuffer buffer) throws IOException {
!
! // The comsuming happens in server side only.
! ServerHandshakeContext shc = (ServerHandshakeContext)context;
!
! if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "Ignore unavailable status_request_v2 extension");
! }
!
! return; // ignore the extension
! }
!
! // Parse the extension.
! CertStatusRequestV2Spec spec;
! try {
! spec = new CertStatusRequestV2Spec(buffer);
! } catch (IOException ioe) {
! shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
! return; // fatal() always throws, make the compiler happy.
! }
!
! // Update the context.
! shc.handshakeExtensions.put(CH_STATUS_REQUEST_V2, spec);
! shc.handshakeProducers.putIfAbsent(
! SSLHandshake.CERTIFICATE_STATUS.id,
! SSLHandshake.CERTIFICATE_STATUS);
! // No impact on session resumption.
! }
}
/**
! * Network data producer of a "status_request_v2" extension in the
! * ServerHello handshake message.
! */
! private static final
! class SHCertStatusReqV2Producer implements HandshakeProducer {
! // Prevent instantiation of this class.
! private SHCertStatusReqV2Producer() {
! // blank
! }
!
! @Override
! public byte[] produce(ConnectionContext context,
! HandshakeMessage message) throws IOException {
! // The producing happens in client side only.
!
! ServerHandshakeContext shc = (ServerHandshakeContext)context;
! // The StaplingParameters in the ServerHandshakeContext will
! // contain the info about what kind of stapling (if any) to
! // perform and whether this status_request extension should be
! // produced or the status_request_v2 (found in a different producer)
! // No explicit check is required for isStaplingEnabled here. If
! // it is false then stapleParams will be null. If it is true
! // then stapleParams may or may not be false and the check below
! // is sufficient.
! if ((shc.stapleParams == null) ||
! (shc.stapleParams.statusRespExt !=
! SSLExtension.CH_STATUS_REQUEST_V2)) {
! return null; // Do not produce status_request_v2 in SH
! }
!
! // In response to "status_request_v2" extension request only
! CertStatusRequestV2Spec spec = (CertStatusRequestV2Spec)
! shc.handshakeExtensions.get(CH_STATUS_REQUEST_V2);
! if (spec == null) {
! // Ignore, no status_request_v2 extension requested.
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "Ignore unavailable status_request_v2 extension");
! }
!
! return null; // ignore the extension
! }
!
! // Is it a session resuming?
! if (shc.isResumption) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "No status_request_v2 response for session resumption");
! }
! return null; // ignore the extension
! }
!
! // The "extension_data" in the extended ServerHello handshake
! // message MUST be empty.
! byte[] extData = new byte[0];
!
! // Update the context.
! shc.handshakeExtensions.put(
! SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT);
!
! return extData;
! }
! }
!
! /**
! * Network data consumer of a "status_request_v2" extension in the
! * ServerHello handshake message.
*/
! private static final
! class SHCertStatusReqV2Consumer implements ExtensionConsumer {
! // Prevent instantiation of this class.
! private SHCertStatusReqV2Consumer() {
! // blank
! }
!
! @Override
! public void consume(ConnectionContext context,
! HandshakeMessage message, ByteBuffer buffer) throws IOException {
!
! // The consumption happens in client side only.
! ClientHandshakeContext chc = (ClientHandshakeContext)context;
!
! // In response to "status_request" extension request only
! CertStatusRequestV2Spec requestedCsr = (CertStatusRequestV2Spec)
! chc.handshakeExtensions.get(CH_STATUS_REQUEST_V2);
! if (requestedCsr == null) {
! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
! "Unexpected status_request_v2 extension in ServerHello");
! }
!
! // Parse the extension.
! if (buffer.hasRemaining()) {
! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
! "Invalid status_request_v2 extension in ServerHello: " +
! "the extension data must be empty");
! }
!
! // Update the context.
! chc.handshakeExtensions.put(
! SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT);
! chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id,
! SSLHandshake.CERTIFICATE_STATUS);
!
! // Since we've received a legitimate status_request in the
! // ServerHello, stapling is active if it's been enabled.
! chc.staplingActive = chc.sslContext.isStaplingEnabled(true);
!
! // No impact on session resumption.
! }
! }
!
! private static final
! class CTCertStatusResponseProducer implements HandshakeProducer {
! // Prevent instantiation of this class.
! private CTCertStatusResponseProducer() {
! // blank
! }
!
! @Override
! public byte[] produce(ConnectionContext context,
! HandshakeMessage message) throws IOException {
! ServerHandshakeContext shc = (ServerHandshakeContext)context;
! byte[] producedData = null;
!
! // Stapling needs to be active and have valid data to proceed
! if (shc.stapleParams == null) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest(
! "Stapling is disabled for this connection");
! }
! return null;
! }
!
! // There needs to be a non-null CertificateEntry to proceed
! if (shc.currentCertEntry == null) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
! SSLLogger.finest("Found null CertificateEntry in context");
! }
! return null;
! }
!
! // Pull the certificate from the CertificateEntry and find
! // a response from the response map. If one exists we will
! // staple it.
! try {
! CertificateFactory cf = CertificateFactory.getInstance("X.509");
! X509Certificate x509Cert =
! (X509Certificate)cf.generateCertificate(
! new ByteArrayInputStream(
! shc.currentCertEntry.encoded));
! byte[] respBytes = shc.stapleParams.responseMap.get(x509Cert);
! if (respBytes == null) {
! // We're done with this entry. Clear it from the context
! if (SSLLogger.isOn &&
! SSLLogger.isOn("ssl,handshake,verbose")) {
! SSLLogger.finest("No status response found for " +
! x509Cert.getSubjectX500Principal());
! }
! shc.currentCertEntry = null;
! return null;
! }
!
! // Build a proper response buffer from the stapling information
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
! SSLLogger.finest("Found status response for " +
! x509Cert.getSubjectX500Principal() +
! ", response length: " + respBytes.length);
! }
! CertStatusResponse certResp = (shc.stapleParams.statReqType ==
! CertStatusRequestType.OCSP) ?
! new OCSPStatusResponse(shc.stapleParams.statReqType.id,
! respBytes) :
! new CertStatusResponse(shc.stapleParams.statReqType.id,
! respBytes);
! producedData = certResp.toByteArray();
! } catch (CertificateException ce) {
! shc.conContext.fatal(Alert.BAD_CERTIFICATE,
! "Failed to parse server certificates", ce);
! } catch (IOException ioe) {
! shc.conContext.fatal(Alert.BAD_CERT_STATUS_RESPONSE,
! "Failed to parse certificate status response", ioe);
! }
!
! // Clear the pinned CertificateEntry from the context
! shc.currentCertEntry = null;
! return producedData;
! }
! }
!
! private static final
! class CTCertStatusResponseConsumer implements ExtensionConsumer {
! // Prevent instantiation of this class.
! private CTCertStatusResponseConsumer() {
! // blank
! }
!
! @Override
! public void consume(ConnectionContext context,
! HandshakeMessage message, ByteBuffer buffer) throws IOException {
! // The consumption happens in client side only.
! ClientHandshakeContext chc = (ClientHandshakeContext)context;
!
! // Parse the extension.
! CertStatusResponseSpec spec;
! try {
! spec = new CertStatusResponseSpec(buffer);
! } catch (IOException ioe) {
! chc.conContext.fatal(Alert.DECODE_ERROR, ioe);
! return; // fatal() always throws, make the compiler happy.
! }
!
! if (chc.sslContext.isStaplingEnabled(true)) {
! // Activate stapling
! chc.staplingActive = true;
! } else {
! // Do no further processing of stapled responses
! return;
! }
!
! // Get response list from the session. This is unmodifiable
! // so we need to create a new list. Then add this new response
! // to the end and submit it back to the session object.
! if ((chc.handshakeSession != null) && (!chc.isResumption)) {
! List<byte[]> respList = new ArrayList<>(
! chc.handshakeSession.getStatusResponses());
! respList.add(spec.statusResponse.encodedResponse);
! chc.handshakeSession.setStatusResponses(respList);
! } else {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
! SSLLogger.finest(
! "Ignoring stapled data on resumed session");
! }
! }
! }
}
}
< prev index next >