src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
parent 47216 71c04702a3d5
child 56651 0c13b82d3274
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java	Fri May 11 15:53:12 2018 -0700
@@ -0,0 +1,1218 @@
+/*
+ * 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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;
+     *          select (status_type) {
+     *              case ocsp: OCSPStatusRequest;
+     *          } request;
+     *      } CertificateStatusRequest;
+     *
+     *      enum { ocsp(1), (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>;
+     */
+    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");
+                }
+            }
+        }
+    }
+}