org.openjsse.sun.security.ssl.CertStatusExtension Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjsse Show documentation
Show all versions of openjsse Show documentation
OpenJSSE delivers a TLS 1.3 JSSE provider for Java SE 8
/*
* Copyright (c) 2015, 2019, 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 org.openjsse.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 org.openjsse.sun.security.ssl.SSLExtension.CH_STATUS_REQUEST;
import static org.openjsse.sun.security.ssl.SSLExtension.CH_STATUS_REQUEST_V2;
import org.openjsse.sun.security.ssl.SSLExtension.ExtensionConsumer;
import static org.openjsse.sun.security.ssl.SSLExtension.SH_STATUS_REQUEST;
import static org.openjsse.sun.security.ssl.SSLExtension.SH_STATUS_REQUEST_V2;
import org.openjsse.sun.security.ssl.SSLExtension.SSLExtensionSpec;
import org.openjsse.sun.security.ssl.SSLHandshake.HandshakeMessage;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import org.openjsse.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 SSLStringizer certStatusReqStringizer =
new CertStatusRequestStringizer();
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 SSLStringizer certStatusReqV2Stringizer =
new CertStatusRequestsStringizer();
static final SSLStringizer certStatusRespStringizer =
new CertStatusRespStringizer();
/**
* 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 ?
"" : 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 ?
"" : statusResponse.toString();
}
}
private static final
class CertStatusRequestStringizer implements SSLStringizer {
@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 CertStatusRespStringizer implements SSLStringizer {
@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 responderIds;
final List extensions;
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");
}
List rids = new ArrayList<>();
List 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 = "";
if (!responderIds.isEmpty()) {
ridStr = responderIds.toString();
}
String extsStr = "";
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 consuming 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) {
throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
}
// Update the context.
shc.handshakeExtensions.put(CH_STATUS_REQUEST, spec);
if (!shc.isResumption &&
!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) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected status_request extension in ServerHello");
}
// Parse the extension.
if (buffer.hasRemaining()) {
throw 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);
// 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);
if (chc.staplingActive) {
chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id,
SSLHandshake.CERTIFICATE_STATUS);
}
// 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 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 "";
} 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 CertStatusRequestsStringizer implements SSLStringizer {
@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 consuming 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) {
throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
}
// Update the context.
shc.handshakeExtensions.put(CH_STATUS_REQUEST_V2, spec);
if (!shc.isResumption) {
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) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected status_request_v2 extension in ServerHello");
}
// Parse the extension.
if (buffer.hasRemaining()) {
throw 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);
// Since we've received a legitimate status_request in the
// ServerHello, stapling is active if it's been enabled. If it
// is active, make sure we add the CertificateStatus message
// consumer.
chc.staplingActive = chc.sslContext.isStaplingEnabled(true);
if (chc.staplingActive) {
chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id,
SSLHandshake.CERTIFICATE_STATUS);
}
// 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) {
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE,
"Failed to parse server certificates", ce);
} catch (IOException ioe) {
throw 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) {
throw chc.conContext.fatal(Alert.DECODE_ERROR, ioe);
}
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 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");
}
}
}
}
}