< 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 >