|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
|
|
*/ |
|
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(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 { |
|
|
|
if (buffer.remaining() == 0) { |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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"); |
|
} |
|
|
|
|
|
byte type = (byte)Record.getInt8(buffer); |
|
byte[] respData = Record.getBytes24(buffer); |
|
|
|
|
|
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 CertStatusRequestStringizer implements SSLStringizer { |
|
@Override |
|
public String toString(ByteBuffer buffer) { |
|
try { |
|
return (new CertStatusRequestSpec(buffer)).toString(); |
|
} catch (IOException ioe) { |
|
|
|
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) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
static enum CertStatusRequestType { |
|
OCSP ((byte)0x01, "ocsp"), |
|
OCSP_MULTI ((byte)0x02, "ocsp_multi"); |
|
|
|
final byte id; |
|
final String name; |
|
|
|
private CertStatusRequestType(byte id, String name) { |
|
this.id = id; |
|
this.name = name; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 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 |
|
|
|
throw new SSLProtocolException( |
|
"Invalid OCSP status request: insufficient data"); |
|
} |
|
|
|
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) { |
|
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 |
|
|
|
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); |
|
|
|
|
|
if (encoded == null || encoded.length < 1) { |
|
throw new SSLProtocolException( |
|
"Invalid OCSP status response: insufficient 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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHCertStatusReqProducer implements HandshakeProducer { |
|
|
|
private CHCertStatusReqProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
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 |
|
|
|
byte[] extData = new byte[] {0x01, 0x00, 0x00, 0x00, 0x00}; |
|
|
|
|
|
chc.handshakeExtensions.put( |
|
CH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHCertStatusReqConsumer implements ExtensionConsumer { |
|
|
|
private CHCertStatusReqConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
CertStatusRequestSpec spec; |
|
try { |
|
spec = new CertStatusRequestSpec(buffer); |
|
} catch (IOException ioe) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
|
|
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. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHCertStatusReqProducer implements HandshakeProducer { |
|
|
|
private SHCertStatusReqProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
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 |
|
|
|
if ((shc.stapleParams == null) || |
|
(shc.stapleParams.statusRespExt != |
|
SSLExtension.CH_STATUS_REQUEST)) { |
|
return null; |
|
} |
|
|
|
|
|
CertStatusRequestSpec spec = (CertStatusRequestSpec) |
|
shc.handshakeExtensions.get(CH_STATUS_REQUEST); |
|
if (spec == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"Ignore unavailable extension: " + |
|
CH_STATUS_REQUEST.name); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
if (shc.isResumption) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"No status_request response for session resuming"); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
// The "extension_data" in the extended ServerHello handshake |
|
|
|
byte[] extData = new byte[0]; |
|
|
|
|
|
shc.handshakeExtensions.put( |
|
SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHCertStatusReqConsumer implements ExtensionConsumer { |
|
|
|
private SHCertStatusReqConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
CertStatusRequestSpec requestedCsr = (CertStatusRequestSpec) |
|
chc.handshakeExtensions.get(CH_STATUS_REQUEST); |
|
if (requestedCsr == null) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected status_request extension in ServerHello"); |
|
} |
|
|
|
|
|
if (buffer.hasRemaining()) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Invalid status_request extension in ServerHello message: " + |
|
"the extension data must be empty"); |
|
} |
|
|
|
|
|
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 |
|
|
|
chc.staplingActive = chc.sslContext.isStaplingEnabled(true); |
|
|
|
// No impact on session resumption. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 { |
|
|
|
if (message.remaining() == 0) { |
|
|
|
this.certStatusRequests = new CertStatusRequest[0]; |
|
return; |
|
} |
|
|
|
if (message.remaining() < 5) { // 2: certificate_status_req_list |
|
// +1: status_type |
|
|
|
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; |
|
remaining -= requestLen; |
|
|
|
if (statusType == CertStatusRequestType.OCSP.id || |
|
statusType == CertStatusRequestType.OCSP_MULTI.id) { |
|
if (encoded.length < 4) { |
|
// 2: length of responder_id_list |
|
|
|
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 CertStatusRequestsStringizer implements SSLStringizer { |
|
@Override |
|
public String toString(ByteBuffer buffer) { |
|
try { |
|
return (new CertStatusRequestV2Spec(buffer)).toString(); |
|
} catch (IOException ioe) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHCertStatusReqV2Producer implements HandshakeProducer { |
|
|
|
private CHCertStatusReqV2Producer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
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 |
|
|
|
byte[] extData = new byte[] { |
|
0x00, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00}; |
|
|
|
|
|
chc.handshakeExtensions.put( |
|
CH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHCertStatusReqV2Consumer implements ExtensionConsumer { |
|
|
|
private CHCertStatusReqV2Consumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
CertStatusRequestV2Spec spec; |
|
try { |
|
spec = new CertStatusRequestV2Spec(buffer); |
|
} catch (IOException ioe) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
|
|
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. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHCertStatusReqV2Producer implements HandshakeProducer { |
|
|
|
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 |
|
|
|
if ((shc.stapleParams == null) || |
|
(shc.stapleParams.statusRespExt != |
|
SSLExtension.CH_STATUS_REQUEST_V2)) { |
|
return null; |
|
} |
|
|
|
|
|
CertStatusRequestV2Spec spec = (CertStatusRequestV2Spec) |
|
shc.handshakeExtensions.get(CH_STATUS_REQUEST_V2); |
|
if (spec == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"Ignore unavailable status_request_v2 extension"); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
if (shc.isResumption) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"No status_request_v2 response for session resumption"); |
|
} |
|
return null; |
|
} |
|
|
|
// The "extension_data" in the extended ServerHello handshake |
|
|
|
byte[] extData = new byte[0]; |
|
|
|
|
|
shc.handshakeExtensions.put( |
|
SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHCertStatusReqV2Consumer implements ExtensionConsumer { |
|
|
|
private SHCertStatusReqV2Consumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
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"); |
|
} |
|
|
|
|
|
if (buffer.hasRemaining()) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Invalid status_request_v2 extension in ServerHello: " + |
|
"the extension data must be empty"); |
|
} |
|
|
|
|
|
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 |
|
|
|
chc.staplingActive = chc.sslContext.isStaplingEnabled(true); |
|
|
|
// No impact on session resumption. |
|
} |
|
} |
|
|
|
private static final |
|
class CTCertStatusResponseProducer implements HandshakeProducer { |
|
|
|
private CTCertStatusResponseProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
byte[] producedData = null; |
|
|
|
|
|
if (shc.stapleParams == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"Stapling is disabled for this connection"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
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 |
|
|
|
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) { |
|
|
|
if (SSLLogger.isOn && |
|
SSLLogger.isOn("ssl,handshake,verbose")) { |
|
SSLLogger.finest("No status response found for " + |
|
x509Cert.getSubjectX500Principal()); |
|
} |
|
shc.currentCertEntry = null; |
|
return null; |
|
} |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
shc.currentCertEntry = null; |
|
return producedData; |
|
} |
|
} |
|
|
|
private static final |
|
class CTCertStatusResponseConsumer implements ExtensionConsumer { |
|
|
|
private CTCertStatusResponseConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
CertStatusResponseSpec spec; |
|
try { |
|
spec = new CertStatusResponseSpec(buffer); |
|
} catch (IOException ioe) { |
|
chc.conContext.fatal(Alert.DECODE_ERROR, ioe); |
|
return; |
|
} |
|
|
|
if (chc.sslContext.isStaplingEnabled(true)) { |
|
|
|
chc.staplingActive = true; |
|
} else { |
|
|
|
return; |
|
} |
|
|
|
// Get response list from the session. This is unmodifiable |
|
// so we need to create a new list. Then add this new response |
|
|
|
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"); |
|
} |
|
} |
|
} |
|
} |
|
} |