summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/cert/ct_objects_extractor.h12
-rw-r--r--net/cert/ct_objects_extractor_nss.cc359
-rw-r--r--net/cert/ct_objects_extractor_openssl.cc8
-rw-r--r--net/cert/ct_objects_extractor_unittest.cc53
-rw-r--r--net/cert/ct_verifier.h16
-rw-r--r--net/cert/multi_log_ct_verifier.cc14
-rw-r--r--net/cert/multi_log_ct_verifier.h2
-rw-r--r--net/cert/multi_log_ct_verifier_unittest.cc9
-rw-r--r--net/socket/ssl_client_socket.cc12
-rw-r--r--net/socket/ssl_client_socket.h20
-rw-r--r--net/socket/ssl_client_socket_nss.cc111
-rw-r--r--net/socket/ssl_client_socket_unittest.cc75
-rw-r--r--net/test/ct_test_util.cc69
-rw-r--r--net/test/ct_test_util.h12
-rw-r--r--net/test/spawned_test_server/base_test_server.cc17
-rw-r--r--net/test/spawned_test_server/base_test_server.h9
-rwxr-xr-xnet/tools/testserver/testserver.py26
-rw-r--r--third_party/tlslite/README.chromium1
-rw-r--r--third_party/tlslite/patches/status_request.patch208
-rw-r--r--third_party/tlslite/tlslite/TLSConnection.py29
-rw-r--r--third_party/tlslite/tlslite/constants.py5
-rw-r--r--third_party/tlslite/tlslite/messages.py53
22 files changed, 1012 insertions, 108 deletions
diff --git a/net/cert/ct_objects_extractor.h b/net/cert/ct_objects_extractor.h
index de47c852..d8fc5f9 100644
--- a/net/cert/ct_objects_extractor.h
+++ b/net/cert/ct_objects_extractor.h
@@ -45,6 +45,18 @@ NET_EXPORT_PRIVATE bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
NET_EXPORT_PRIVATE bool GetX509LogEntry(X509Certificate::OSCertHandle leaf,
LogEntry* result);
+// Extracts a SignedCertificateTimestampList that has been embedded within
+// an OCSP response as an extension with the OID 1.3.6.1.4.1.11129.2.4.5.
+// If the extension is present, and the response matches the issuer and
+// serial number, returns true, updating |*sct_list| to contain
+// the encoded list, minus the DER encoding necessary for the extension.
+// |*sct_list| can then be further decoded with ct::DecodeSCTList.
+NET_EXPORT_PRIVATE bool ExtractSCTListFromOCSPResponse(
+ X509Certificate::OSCertHandle issuer,
+ const std::string& cert_serial_number,
+ const std::string& ocsp_response,
+ std::string* sct_list);
+
} // namespace ct
} // namespace net
diff --git a/net/cert/ct_objects_extractor_nss.cc b/net/cert/ct_objects_extractor_nss.cc
index 0f353489e..c29b34c 100644
--- a/net/cert/ct_objects_extractor_nss.cc
+++ b/net/cert/ct_objects_extractor_nss.cc
@@ -10,6 +10,7 @@
#include <secoid.h>
#include "base/lazy_instance.h"
+#include "base/sha1.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/sha2.h"
#include "net/cert/asn1_util.h"
@@ -22,6 +23,12 @@ namespace ct {
namespace {
+// NSS black magic to get the address of externally defined template at runtime.
+SEC_ASN1_MKSUB(SEC_IntegerTemplate)
+SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
+SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate)
+SEC_ASN1_MKSUB(CERT_SequenceOfCertExtensionTemplate)
+
// Wrapper class to convert a X509Certificate::OSCertHandle directly
// into a CERTCertificate* usable with other NSS functions. This is used for
// platforms where X509Certificate::OSCertHandle refers to a different type
@@ -62,6 +69,27 @@ const char kEmbeddedSCTDescription[] =
"X.509v3 Certificate Transparency Embedded Signed Certificate Timestamp "
"List";
+// The wire form of the OID 1.3.6.1.4.1.11129.2.4.5 - OCSP SingleExtension for
+// X.509v3 Certificate Transparency Signed Certificate Timestamp List, see
+// Section 3.3 of RFC6962.
+const unsigned char kOCSPExtensionOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
+ 0xD6, 0x79, 0x02, 0x04, 0x05};
+
+const SECItem kOCSPExtensionOidItem = {
+ siBuffer, const_cast<unsigned char*>(kOCSPExtensionOid),
+ sizeof(kOCSPExtensionOid)
+};
+
+// id-ad-ocsp: 1.3.6.1.5.5.7.48.1.1
+const unsigned char kBasicOCSPResponseOid[] = {0x2B, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x01};
+
+const SECItem kBasicOCSPResponseOidItem = {
+ siBuffer, const_cast<unsigned char*>(kBasicOCSPResponseOid),
+ sizeof(kBasicOCSPResponseOid)
+};
+
+
// Initializes the necessary NSS internals for use with Certificate
// Transparency.
class CTInitSingleton {
@@ -108,12 +136,12 @@ base::LazyInstance<CTInitSingleton>::Leaky g_ct_singleton =
LAZY_INSTANCE_INITIALIZER;
// Obtains the data for an X.509v3 certificate extension identified by |oid|
-// and encoded as an OCTET STRING. Returns true if the extension was found,
-// updating |ext_data| to be the extension data after removing the DER
-// encoding of OCTET STRING.
-bool GetOctetStringExtension(CERTCertificate* cert,
- SECOidTag oid,
- std::string* extension_data) {
+// and encoded as an OCTET STRING. Returns true if the extension was found in
+// the certificate, updating |ext_data| to be the extension data after removing
+// the DER encoding of OCTET STRING.
+bool GetCertOctetStringExtension(CERTCertificate* cert,
+ SECOidTag oid,
+ std::string* extension_data) {
SECItem extension;
SECStatus rv = CERT_FindCertExtension(cert, oid, &extension);
if (rv != SECSuccess)
@@ -133,6 +161,48 @@ bool GetOctetStringExtension(CERTCertificate* cert,
return rv == SECSuccess;
}
+// NSS offers CERT_FindCertExtension for certificates, but that only accepts
+// CERTCertificate* inputs, so the method below extracts the SCT extension
+// directly from the CERTCertExtension** of an OCSP response.
+//
+// Obtains the data for an OCSP extension identified by kOCSPExtensionOidItem
+// and encoded as an OCTET STRING. Returns true if the extension was found in
+// |extensions|, updating |extension_data| to be the extension data after
+// removing the DER encoding of OCTET STRING.
+bool GetSCTListFromOCSPExtension(PLArenaPool* arena,
+ const CERTCertExtension* const* extensions,
+ std::string* extension_data) {
+ if (!extensions)
+ return false;
+
+ const CERTCertExtension* match = NULL;
+
+ for (const CERTCertExtension* const* exts = extensions; *exts; ++exts) {
+ const CERTCertExtension* ext = *exts;
+ if (SECITEM_ItemsAreEqual(&kOCSPExtensionOidItem, &ext->id)) {
+ match = ext;
+ break;
+ }
+ }
+
+ if (!match)
+ return false;
+
+ SECItem contents;
+ // SEC_QuickDERDecodeItem sets |contents| to point to |match|, so it is not
+ // necessary to free the contents of |contents|.
+ SECStatus rv = SEC_QuickDERDecodeItem(arena, &contents,
+ SEC_ASN1_GET(SEC_OctetStringTemplate),
+ &match->value);
+ if (rv != SECSuccess)
+ return false;
+
+ base::StringPiece parsed_data(reinterpret_cast<char*>(contents.data),
+ contents.len);
+ parsed_data.CopyToString(extension_data);
+ return true;
+}
+
// Given a |cert|, extract the TBSCertificate from this certificate, also
// removing the X.509 extension with OID 1.3.6.1.4.1.11129.2.4.2 (that is,
// the embedded SCT)
@@ -180,6 +250,179 @@ bool ExtractTBSCertWithoutSCTs(CERTCertificate* cert,
return true;
}
+// The following code is adapted from the NSS OCSP module, in order to expose
+// the internal structure of an OCSP response.
+
+// ResponseBytes ::= SEQUENCE {
+// responseType OBJECT IDENTIFIER,
+// response OCTET STRING }
+struct ResponseBytes {
+ SECItem response_type;
+ SECItem der_response;
+};
+
+const SEC_ASN1Template kResponseBytesTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ResponseBytes) },
+ { SEC_ASN1_OBJECT_ID, offsetof(ResponseBytes, response_type) },
+ { SEC_ASN1_OCTET_STRING, offsetof(ResponseBytes, der_response) },
+ { 0 }
+};
+
+// OCSPResponse ::= SEQUENCE {
+// responseStatus OCSPResponseStatus,
+// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+struct OCSPResponse {
+ SECItem response_status;
+ // This indirection is needed because |response_bytes| is an optional
+ // component and we need a way to determine if it is missing.
+ ResponseBytes* response_bytes;
+};
+
+const SEC_ASN1Template kPointerToResponseBytesTemplate[] = {
+ { SEC_ASN1_POINTER, 0, kResponseBytesTemplate }
+};
+
+const SEC_ASN1Template kOCSPResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OCSPResponse) },
+ { SEC_ASN1_ENUMERATED, offsetof(OCSPResponse, response_status) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(OCSPResponse, response_bytes),
+ kPointerToResponseBytesTemplate },
+ { 0 }
+};
+
+// CertID ::= SEQUENCE {
+// hashAlgorithm AlgorithmIdentifier,
+// issuerNameHash OCTET STRING, -- Hash of Issuer's DN
+// issuerKeyHash OCTET STRING, -- Hash of Issuers public key
+// serialNumber CertificateSerialNumber }
+struct CertID {
+ SECAlgorithmID hash_algorithm;
+ SECItem issuer_name_hash;
+ SECItem issuer_key_hash;
+ SECItem serial_number;
+};
+
+const SEC_ASN1Template kCertIDTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CertID) },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CertID, hash_algorithm),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING, offsetof(CertID, issuer_name_hash) },
+ { SEC_ASN1_OCTET_STRING, offsetof(CertID, issuer_key_hash) },
+ { SEC_ASN1_INTEGER, offsetof(CertID, serial_number) },
+ { 0 }
+};
+
+// SingleResponse ::= SEQUENCE {
+// certID CertID,
+// certStatus CertStatus,
+// thisUpdate GeneralizedTime,
+// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+struct SingleResponse {
+ CertID cert_id;
+ // The following three fields are not used.
+ SECItem der_cert_status;
+ SECItem this_update;
+ SECItem next_update;
+ CERTCertExtension** single_extensions;
+};
+
+const SEC_ASN1Template kSingleResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SingleResponse) },
+ { SEC_ASN1_INLINE, offsetof(SingleResponse, cert_id), kCertIDTemplate },
+ // Really a CHOICE but we make it an ANY because we don't care about the
+ // contents of this field.
+ // TODO(ekasper): use SEC_ASN1_CHOICE.
+ { SEC_ASN1_ANY, offsetof(SingleResponse, der_cert_status) },
+ { SEC_ASN1_GENERALIZED_TIME, offsetof(SingleResponse, this_update) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(SingleResponse, next_update),
+ SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
+ offsetof(SingleResponse, single_extensions),
+ SEC_ASN1_SUB(CERT_SequenceOfCertExtensionTemplate) },
+ { 0 }
+};
+
+// ResponseData ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// responderID ResponderID,
+// producedAt GeneralizedTime,
+// responses SEQUENCE OF SingleResponse,
+// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+struct ResponseData {
+ // The first three fields are not used.
+ SECItem version;
+ SECItem der_responder_id;
+ SECItem produced_at;
+ SingleResponse** single_responses;
+ // Skip extensions.
+};
+
+const SEC_ASN1Template kResponseDataTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ResponseData) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ResponseData, version), SEC_ASN1_SUB(SEC_IntegerTemplate) },
+ // Really a CHOICE but we make it an ANY because we don't care about the
+ // contents of this field.
+ // TODO(ekasper): use SEC_ASN1_CHOICE.
+ { SEC_ASN1_ANY, offsetof(ResponseData, der_responder_id) },
+ { SEC_ASN1_GENERALIZED_TIME, offsetof(ResponseData, produced_at) },
+ { SEC_ASN1_SEQUENCE_OF, offsetof(ResponseData, single_responses),
+ kSingleResponseTemplate },
+ { SEC_ASN1_SKIP_REST },
+ { 0 }
+};
+
+// BasicOCSPResponse ::= SEQUENCE {
+// tbsResponseData ResponseData,
+// signatureAlgorithm AlgorithmIdentifier,
+// signature BIT STRING,
+// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+struct BasicOCSPResponse {
+ ResponseData tbs_response_data;
+ // We do not care about the rest.
+};
+
+const SEC_ASN1Template kBasicOCSPResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(BasicOCSPResponse) },
+ { SEC_ASN1_INLINE, offsetof(BasicOCSPResponse, tbs_response_data),
+ kResponseDataTemplate },
+ { SEC_ASN1_SKIP_REST },
+ { 0 }
+};
+
+bool StringEqualToSECItem(const std::string& value1, const SECItem& value2) {
+ if (value1.size() != value2.len)
+ return false;
+ return memcmp(value1.data(), value2.data, value2.len) == 0;
+}
+
+// TODO(ekasper): also use the issuer name hash in matching.
+bool CertIDMatches(const CertID& cert_id,
+ const std::string& serial_number,
+ const std::string& issuer_key_sha1_hash,
+ const std::string& issuer_key_sha256_hash) {
+ if (!StringEqualToSECItem(serial_number, cert_id.serial_number))
+ return false;
+
+ SECOidTag hash_alg = SECOID_FindOIDTag(&cert_id.hash_algorithm.algorithm);
+ switch (hash_alg) {
+ case SEC_OID_SHA1:
+ return StringEqualToSECItem(issuer_key_sha1_hash,
+ cert_id.issuer_key_hash);
+ case SEC_OID_SHA256:
+ return StringEqualToSECItem(issuer_key_sha256_hash,
+ cert_id.issuer_key_hash);
+ default:
+ return false;
+ }
+}
+
} // namespace
bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
@@ -190,8 +433,9 @@ bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
if (!leaf_cert.cert)
return false;
- return GetOctetStringExtension(
- leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), sct_list);
+ return GetCertOctetStringExtension(leaf_cert.cert.get(),
+ g_ct_singleton.Get().embedded_oid(),
+ sct_list);
}
bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
@@ -269,6 +513,105 @@ bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) {
return true;
}
+bool ExtractSCTListFromOCSPResponse(X509Certificate::OSCertHandle issuer,
+ const std::string& cert_serial_number,
+ const std::string& ocsp_response,
+ std::string* sct_list) {
+ DCHECK(issuer);
+
+ // Any OCSP response is unlikely to be even close to 2^24 bytes; further, CT
+ // only uses stapled OCSP responses which have this limit imposed by the TLS
+ // protocol.
+ if (ocsp_response.size() > 0xffffff)
+ return false;
+
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+
+ OCSPResponse response;
+ memset(&response, 0, sizeof(response));
+
+ SECItem src = { siBuffer,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(
+ ocsp_response.data())),
+ static_cast<unsigned int>(ocsp_response.size()) };
+
+ // |response| will point directly into |src|, so it's not necessary to
+ // free the |response| contents, but they may only be used while |src|
+ // is valid (i.e., in this method).
+ SECStatus rv = SEC_QuickDERDecodeItem(arena.get(), &response,
+ kOCSPResponseTemplate, &src);
+ if (rv != SECSuccess)
+ return false;
+
+ if (!response.response_bytes)
+ return false;
+
+ if (!SECITEM_ItemsAreEqual(&kBasicOCSPResponseOidItem,
+ &response.response_bytes->response_type)) {
+ return false;
+ }
+
+ BasicOCSPResponse basic_response;
+ memset(&basic_response, 0, sizeof(basic_response));
+
+ rv = SEC_QuickDERDecodeItem(arena.get(), &basic_response,
+ kBasicOCSPResponseTemplate,
+ &response.response_bytes->der_response);
+ if (rv != SECSuccess)
+ return false;
+
+ SingleResponse** responses =
+ basic_response.tbs_response_data.single_responses;
+ if (!responses)
+ return false;
+
+ std::string issuer_der;
+ if (!X509Certificate::GetDEREncoded(issuer, &issuer_der))
+ return false;
+
+ base::StringPiece issuer_spki;
+ if (!asn1::ExtractSPKIFromDERCert(issuer_der, &issuer_spki))
+ return false;
+
+ // In OCSP, only the key itself is under hash.
+ base::StringPiece issuer_spk;
+ if (!asn1::ExtractSubjectPublicKeyFromSPKI(issuer_spki, &issuer_spk))
+ return false;
+
+ // ExtractSubjectPublicKey... does not remove the initial octet encoding the
+ // number of unused bits in the ASN.1 BIT STRING so we do it here. For public
+ // keys, the bitstring is in practice always byte-aligned.
+ if (issuer_spk.empty() || issuer_spk[0] != 0)
+ return false;
+ issuer_spk.remove_prefix(1);
+
+ // NSS OCSP lib recognizes SHA1, MD5 and MD2; MD5 and MD2 are dead but
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=663315 will add SHA-256
+ // and SHA-384.
+ // TODO(ekasper): add SHA-384 to crypto/sha2.h and here if it proves
+ // necessary.
+ // TODO(ekasper): only compute the hashes on demand.
+ std::string issuer_key_sha256_hash = crypto::SHA256HashString(issuer_spk);
+ std::string issuer_key_sha1_hash = base::SHA1HashString(
+ issuer_spk.as_string());
+
+ const SingleResponse* match = NULL;
+ for (const SingleResponse* const* resps = responses; *resps; ++resps) {
+ const SingleResponse* resp = *resps;
+ if (CertIDMatches(resp->cert_id, cert_serial_number,
+ issuer_key_sha1_hash, issuer_key_sha256_hash)) {
+ match = resp;
+ break;
+ }
+ }
+
+ if (!match)
+ return false;
+
+ return GetSCTListFromOCSPExtension(arena.get(), match->single_extensions,
+ sct_list);
+}
+
} // namespace ct
} // namespace net
diff --git a/net/cert/ct_objects_extractor_openssl.cc b/net/cert/ct_objects_extractor_openssl.cc
index 3fb5f2c..f6bd8b0 100644
--- a/net/cert/ct_objects_extractor_openssl.cc
+++ b/net/cert/ct_objects_extractor_openssl.cc
@@ -28,6 +28,14 @@ bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) {
return false;
}
+bool ExtractSCTListFromOCSPResponse(X509Certificate::OSCertHandle issuer,
+ const std::string& cert_serial_number,
+ const std::string& ocsp_response,
+ std::string* sct_list) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
} // namespace ct
} // namespace net
diff --git a/net/cert/ct_objects_extractor_unittest.cc b/net/cert/ct_objects_extractor_unittest.cc
index b560966..b229a47 100644
--- a/net/cert/ct_objects_extractor_unittest.cc
+++ b/net/cert/ct_objects_extractor_unittest.cc
@@ -123,6 +123,59 @@ TEST_F(CTObjectsExtractorTest, ComplementarySCTVerifies) {
EXPECT_TRUE(log_->Verify(entry, *sct));
}
+// Test that the extractor can parse OCSP responses.
+TEST_F(CTObjectsExtractorTest, ExtractSCTListFromOCSPResponse) {
+ std::string der_subject_cert(ct::GetDerEncodedFakeOCSPResponseCert());
+ scoped_refptr<X509Certificate> subject_cert =
+ X509Certificate::CreateFromBytes(der_subject_cert.data(),
+ der_subject_cert.length());
+ std::string der_issuer_cert(ct::GetDerEncodedFakeOCSPResponseIssuerCert());
+ scoped_refptr<X509Certificate> issuer_cert =
+ X509Certificate::CreateFromBytes(der_issuer_cert.data(),
+ der_issuer_cert.length());
+
+ std::string fake_sct_list = ct::GetFakeOCSPExtensionValue();
+ ASSERT_FALSE(fake_sct_list.empty());
+ std::string ocsp_response = ct::GetDerEncodedFakeOCSPResponse();
+
+ std::string extracted_sct_list;
+ EXPECT_TRUE(ct::ExtractSCTListFromOCSPResponse(
+ issuer_cert->os_cert_handle(), subject_cert->serial_number(),
+ ocsp_response, &extracted_sct_list));
+ EXPECT_EQ(extracted_sct_list, fake_sct_list);
+}
+
+// Test that the extractor honours serial number.
+TEST_F(CTObjectsExtractorTest, ExtractSCTListFromOCSPResponseMatchesSerial) {
+ std::string der_issuer_cert(ct::GetDerEncodedFakeOCSPResponseIssuerCert());
+ scoped_refptr<X509Certificate> issuer_cert =
+ X509Certificate::CreateFromBytes(der_issuer_cert.data(),
+ der_issuer_cert.length());
+
+ std::string ocsp_response = ct::GetDerEncodedFakeOCSPResponse();
+
+ std::string extracted_sct_list;
+ EXPECT_FALSE(ct::ExtractSCTListFromOCSPResponse(
+ issuer_cert->os_cert_handle(), test_cert_->serial_number(),
+ ocsp_response, &extracted_sct_list));
+}
+
+// Test that the extractor honours issuer ID.
+TEST_F(CTObjectsExtractorTest, ExtractSCTListFromOCSPResponseMatchesIssuer) {
+ std::string der_subject_cert(ct::GetDerEncodedFakeOCSPResponseCert());
+ scoped_refptr<X509Certificate> subject_cert =
+ X509Certificate::CreateFromBytes(der_subject_cert.data(),
+ der_subject_cert.length());
+
+ std::string ocsp_response = ct::GetDerEncodedFakeOCSPResponse();
+
+ std::string extracted_sct_list;
+ // Use test_cert_ for issuer - it is not the correct issuer of |subject_cert|.
+ EXPECT_FALSE(ct::ExtractSCTListFromOCSPResponse(
+ test_cert_->os_cert_handle(), subject_cert->serial_number(),
+ ocsp_response, &extracted_sct_list));
+}
+
} // namespace ct
} // namespace net
diff --git a/net/cert/ct_verifier.h b/net/cert/ct_verifier.h
index 5a68686..290a047 100644
--- a/net/cert/ct_verifier.h
+++ b/net/cert/ct_verifier.h
@@ -21,12 +21,18 @@ class NET_EXPORT CTVerifier {
public:
virtual ~CTVerifier() {}
- // Verifies either embedded SCTs or SCTs obtained via the
- // signed_certificate_timestamp TLS extension or OCSP on the given |cert|
- // |result| will be filled with these SCTs, divided into categories based on
- // the verification result.
+ // Verifies SCTs embedded in the certificate itself, SCTs embedded in a
+ // stapled OCSP response, and SCTs obtained via the
+ // signed_certificate_timestamp TLS extension on the given |cert|.
+ // A certificate is permitted but not required to use multiple sources for
+ // SCTs. It is expected that most certificates will use only one source
+ // (embedding, TLS extension or OCSP stapling). If no stapled OCSP response
+ // is available, |stapled_ocsp_response| should be an empty string. If no SCT
+ // TLS extension was negotiated, |sct_list_from_tls_extension| should be an
+ // empty string. |result| will be filled with the SCTs present, divided into
+ // categories based on the verification result.
virtual int Verify(X509Certificate* cert,
- const std::string& sct_list_from_ocsp,
+ const std::string& stapled_ocsp_response,
const std::string& sct_list_from_tls_extension,
ct::CTVerifyResult* result,
const BoundNetLog& net_log) = 0;
diff --git a/net/cert/multi_log_ct_verifier.cc b/net/cert/multi_log_ct_verifier.cc
index d91195f..05a32da 100644
--- a/net/cert/multi_log_ct_verifier.cc
+++ b/net/cert/multi_log_ct_verifier.cc
@@ -32,7 +32,7 @@ void MultiLogCTVerifier::AddLog(scoped_ptr<CTLogVerifier> log_verifier) {
int MultiLogCTVerifier::Verify(
X509Certificate* cert,
- const std::string& sct_list_from_ocsp,
+ const std::string& stapled_ocsp_response,
const std::string& sct_list_from_tls_extension,
ct::CTVerifyResult* result,
const BoundNetLog& net_log) {
@@ -64,8 +64,16 @@ int MultiLogCTVerifier::Verify(
result);
}
- // Log to Net Log, after extracting embedded SCTs but before
- // possibly failing on X.509 entry creation.
+ std::string sct_list_from_ocsp;
+ if (!stapled_ocsp_response.empty() &&
+ !cert->GetIntermediateCertificates().empty()) {
+ ct::ExtractSCTListFromOCSPResponse(
+ cert->GetIntermediateCertificates().front(), cert->serial_number(),
+ stapled_ocsp_response, &sct_list_from_ocsp);
+ }
+
+ // Log to Net Log, after extracting SCTs but before possibly failing on
+ // X.509 entry creation.
NetLog::ParametersCallback net_log_callback =
base::Bind(&NetLogRawSignedCertificateTimestampCallback,
&embedded_scts, &sct_list_from_ocsp, &sct_list_from_tls_extension);
diff --git a/net/cert/multi_log_ct_verifier.h b/net/cert/multi_log_ct_verifier.h
index 8f105f0..d70ba45 100644
--- a/net/cert/multi_log_ct_verifier.h
+++ b/net/cert/multi_log_ct_verifier.h
@@ -36,7 +36,7 @@ class NET_EXPORT MultiLogCTVerifier : public CTVerifier {
// CTVerifier implementation:
virtual int Verify(X509Certificate* cert,
- const std::string& sct_list_from_ocsp,
+ const std::string& stapled_ocsp_response,
const std::string& sct_list_from_tls_extension,
ct::CTVerifyResult* result,
const BoundNetLog& net_log) OVERRIDE;
diff --git a/net/cert/multi_log_ct_verifier_unittest.cc b/net/cert/multi_log_ct_verifier_unittest.cc
index 30d92b8..b1d1aa8 100644
--- a/net/cert/multi_log_ct_verifier_unittest.cc
+++ b/net/cert/multi_log_ct_verifier_unittest.cc
@@ -83,7 +83,8 @@ class MultiLogCTVerifierTest : public ::testing::Test {
CapturingNetLog net_log;
BoundNetLog bound_net_log =
BoundNetLog::Make(&net_log, NetLog::SOURCE_CONNECT_JOB);
- return (verifier_->Verify(chain, "", "", &result, bound_net_log) == OK) &&
+ return (verifier_->Verify(chain, std::string(), std::string(), &result,
+ bound_net_log) == OK) &&
CheckForSingleVerifiedSCTInResult(result) &&
CheckForSCTOrigin(
result, ct::SignedCertificateTimestamp::SCT_EMBEDDED) &&
@@ -141,7 +142,8 @@ TEST_F(MultiLogCTVerifierTest,
ct::CTVerifyResult result;
EXPECT_EQ(OK,
- verifier_->Verify(chain_, "", sct_list, &result, BoundNetLog()));
+ verifier_->Verify(chain_, std::string(), sct_list, &result,
+ BoundNetLog()));
ASSERT_TRUE(CheckForSingleVerifiedSCTInResult(result));
ASSERT_TRUE(CheckForSCTOrigin(
result, ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION));
@@ -160,7 +162,8 @@ TEST_F(MultiLogCTVerifierTest,
ct::CTVerifyResult result;
EXPECT_NE(OK,
- verifier_->Verify(chain_, sct_list, "", &result, BoundNetLog()));
+ verifier_->Verify(chain_, std::string(), sct_list, &result,
+ BoundNetLog()));
EXPECT_EQ(1U, result.unknown_logs_scts.size());
EXPECT_EQ("", result.unknown_logs_scts[0]->log_description);
}
diff --git a/net/socket/ssl_client_socket.cc b/net/socket/ssl_client_socket.cc
index 588b701..d96a720 100644
--- a/net/socket/ssl_client_socket.cc
+++ b/net/socket/ssl_client_socket.cc
@@ -17,7 +17,8 @@ SSLClientSocket::SSLClientSocket()
was_spdy_negotiated_(false),
protocol_negotiated_(kProtoUnknown),
channel_id_sent_(false),
- signed_cert_timestamps_received_(false) {
+ signed_cert_timestamps_received_(false),
+ stapled_ocsp_response_received_(false) {
}
// static
@@ -145,15 +146,16 @@ void SSLClientSocket::set_channel_id_sent(bool channel_id_sent) {
channel_id_sent_ = channel_id_sent;
}
-bool SSLClientSocket::WereSignedCertTimestampsReceived() const {
- return signed_cert_timestamps_received_;
-}
-
void SSLClientSocket::set_signed_cert_timestamps_received(
bool signed_cert_timestamps_received) {
signed_cert_timestamps_received_ = signed_cert_timestamps_received;
}
+void SSLClientSocket::set_stapled_ocsp_response_received(
+ bool stapled_ocsp_response_received) {
+ stapled_ocsp_response_received_ = stapled_ocsp_response_received;
+}
+
// static
void SSLClientSocket::RecordChannelIDSupport(
ServerBoundCertService* server_bound_cert_service,
diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h
index 40840e4..410062d 100644
--- a/net/socket/ssl_client_socket.h
+++ b/net/socket/ssl_client_socket.h
@@ -7,6 +7,7 @@
#include <string>
+#include "base/gtest_prod_util.h"
#include "net/base/completion_callback.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
@@ -131,18 +132,15 @@ class NET_EXPORT SSLClientSocket : public SSLSocket {
// Public for ssl_client_socket_openssl_unittest.cc.
virtual bool WasChannelIDSent() const;
- // Returns true if the server sent Certificate Transparency SCTs
- // via a TLS extension.
- // Temporary glue for testing while the CT code hasn't landed.
- // TODO(ekasper): expose received SCTs via SSLInfo instead.
- virtual bool WereSignedCertTimestampsReceived() const;
-
protected:
virtual void set_channel_id_sent(bool channel_id_sent);
virtual void set_signed_cert_timestamps_received(
bool signed_cert_timestamps_received);
+ virtual void set_stapled_ocsp_response_received(
+ bool stapled_ocsp_response_received);
+
// Records histograms for channel id support during full handshakes - resumed
// handshakes are ignored.
static void RecordChannelIDSupport(
@@ -157,6 +155,14 @@ class NET_EXPORT SSLClientSocket : public SSLSocket {
ServerBoundCertService* server_bound_cert_service);
private:
+ // For signed_cert_timestamps_received_ and stapled_ocsp_response_received_.
+ FRIEND_TEST_ALL_PREFIXES(SSLClientSocketTest,
+ ConnectSignedCertTimestampsEnabledTLSExtension);
+ FRIEND_TEST_ALL_PREFIXES(SSLClientSocketTest,
+ ConnectSignedCertTimestampsEnabledOCSP);
+ FRIEND_TEST_ALL_PREFIXES(SSLClientSocketTest,
+ ConnectSignedCertTimestampsDisabled);
+
// True if NPN was responded to, independent of selecting SPDY or HTTP.
bool was_npn_negotiated_;
// True if NPN successfully negotiated SPDY.
@@ -167,6 +173,8 @@ class NET_EXPORT SSLClientSocket : public SSLSocket {
bool channel_id_sent_;
// True if SCTs were received via a TLS extension.
bool signed_cert_timestamps_received_;
+ // True if a stapled OCSP response was received.
+ bool stapled_ocsp_response_received_;
};
} // namespace net
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index 0815962..dbf64af 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -93,6 +93,7 @@
#include "net/cert/asn1_util.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/cert_verifier.h"
+#include "net/cert/ct_objects_extractor.h"
#include "net/cert/ct_verifier.h"
#include "net/cert/ct_verify_result.h"
#include "net/cert/scoped_nss_types.h"
@@ -418,6 +419,7 @@ struct HandshakeState {
server_cert_chain.Reset(NULL);
server_cert = NULL;
sct_list_from_tls_extension.clear();
+ stapled_ocsp_response.clear();
resumed_handshake = false;
ssl_connection_status = 0;
}
@@ -449,6 +451,8 @@ struct HandshakeState {
scoped_refptr<X509Certificate> server_cert;
// SignedCertificateTimestampList received via TLS extension (RFC 6962).
std::string sct_list_from_tls_extension;
+ // Stapled OCSP response received.
+ std::string stapled_ocsp_response;
// True if the current handshake was the result of TLS session resumption.
bool resumed_handshake;
@@ -760,10 +764,13 @@ class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
// Updates the NSS and platform specific certificates.
void UpdateServerCert();
- // Update the nss_handshake_state_ with SignedCertificateTimestampLists
- // received in the handshake, via a TLS extension or (to be implemented)
- // OCSP stapling.
+ // Update the nss_handshake_state_ with the SignedCertificateTimestampList
+ // received in the handshake via a TLS extension.
void UpdateSignedCertTimestamps();
+ // Update the OCSP response cache with the stapled response received in the
+ // handshake, and update nss_handshake_state_ with
+ // the SignedCertificateTimestampList received in the stapled OCSP response.
+ void UpdateStapledOCSPResponse();
// Updates the nss_handshake_state_ with the negotiated security parameters.
void UpdateConnectionStatus();
// Record histograms for channel id support during full handshakes - resumed
@@ -1663,6 +1670,7 @@ void SSLClientSocketNSS::Core::HandshakeSucceeded() {
RecordChannelIDSupportOnNSSTaskRunner();
UpdateServerCert();
UpdateSignedCertTimestamps();
+ UpdateStapledOCSPResponse();
UpdateConnectionStatus();
UpdateNextProto();
@@ -1834,43 +1842,6 @@ int SSLClientSocketNSS::Core::DoHandshake() {
false_started_ = true;
HandshakeSucceeded();
}
-
- // TODO(wtc): move this block of code to OwnAuthCertHandler.
- #if defined(SSL_ENABLE_OCSP_STAPLING)
- // TODO(agl): figure out how to plumb an OCSP response into the Mac
- // system library and update IsOCSPStaplingSupported for Mac.
- if (IsOCSPStaplingSupported()) {
- const SECItemArray* ocsp_responses =
- SSL_PeerStapledOCSPResponses(nss_fd_);
- if (ocsp_responses->len) {
- #if defined(OS_WIN)
- if (nss_handshake_state_.server_cert) {
- CRYPT_DATA_BLOB ocsp_response_blob;
- ocsp_response_blob.cbData = ocsp_responses->items[0].len;
- ocsp_response_blob.pbData = ocsp_responses->items[0].data;
- BOOL ok = CertSetCertificateContextProperty(
- nss_handshake_state_.server_cert->os_cert_handle(),
- CERT_OCSP_RESPONSE_PROP_ID,
- CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
- &ocsp_response_blob);
- if (!ok) {
- VLOG(1) << "Failed to set OCSP response property: "
- << GetLastError();
- }
- }
- #elif defined(USE_NSS)
- CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
- GetCacheOCSPResponseFromSideChannelFunction();
-
- cache_ocsp_response(
- CERT_GetDefaultCertDB(),
- nss_handshake_state_.server_cert_chain[0], PR_Now(),
- &ocsp_responses->items[0], NULL);
- #endif
- }
- }
- #endif
- // Done!
} else {
PRErrorCode prerr = PR_GetError();
net_error = HandleNSSError(prerr, true);
@@ -2436,6 +2407,46 @@ void SSLClientSocketNSS::Core::UpdateSignedCertTimestamps() {
signed_cert_timestamps->len);
}
+void SSLClientSocketNSS::Core::UpdateStapledOCSPResponse() {
+ const SECItemArray* ocsp_responses =
+ SSL_PeerStapledOCSPResponses(nss_fd_);
+ if (!ocsp_responses || !ocsp_responses->len)
+ return;
+
+ nss_handshake_state_.stapled_ocsp_response = std::string(
+ reinterpret_cast<char*>(ocsp_responses->items[0].data),
+ ocsp_responses->items[0].len);
+
+ // TODO(agl): figure out how to plumb an OCSP response into the Mac
+ // system library and update IsOCSPStaplingSupported for Mac.
+ if (IsOCSPStaplingSupported()) {
+ #if defined(OS_WIN)
+ if (nss_handshake_state_.server_cert) {
+ CRYPT_DATA_BLOB ocsp_response_blob;
+ ocsp_response_blob.cbData = ocsp_responses->items[0].len;
+ ocsp_response_blob.pbData = ocsp_responses->items[0].data;
+ BOOL ok = CertSetCertificateContextProperty(
+ nss_handshake_state_.server_cert->os_cert_handle(),
+ CERT_OCSP_RESPONSE_PROP_ID,
+ CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
+ &ocsp_response_blob);
+ if (!ok) {
+ VLOG(1) << "Failed to set OCSP response property: "
+ << GetLastError();
+ }
+ }
+ #elif defined(USE_NSS)
+ CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
+ GetCacheOCSPResponseFromSideChannelFunction();
+
+ cache_ocsp_response(
+ CERT_GetDefaultCertDB(),
+ nss_handshake_state_.server_cert_chain[0], PR_Now(),
+ &ocsp_responses->items[0], NULL);
+ #endif
+ } // IsOCSPStaplingSupported()
+}
+
void SSLClientSocketNSS::Core::UpdateConnectionStatus() {
SSLChannelInfo channel_info;
SECStatus ok = SSL_GetChannelInfo(nss_fd_,
@@ -3202,12 +3213,14 @@ int SSLClientSocketNSS::InitializeSSLOptions() {
// Added in NSS 3.15
#ifdef SSL_ENABLE_OCSP_STAPLING
- if (IsOCSPStaplingSupported()) {
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OCSP_STAPLING, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet",
- "SSL_ENABLE_OCSP_STAPLING");
- }
+ // Request OCSP stapling even on platforms that don't support it, in
+ // order to extract Certificate Transparency information.
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OCSP_STAPLING,
+ (IsOCSPStaplingSupported() ||
+ ssl_config_.signed_cert_timestamps_enabled));
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet",
+ "SSL_ENABLE_OCSP_STAPLING");
}
#endif
@@ -3365,6 +3378,8 @@ int SSLClientSocketNSS::DoHandshakeComplete(int result) {
set_channel_id_sent(core_->state().channel_id_sent);
set_signed_cert_timestamps_received(
!core_->state().sct_list_from_tls_extension.empty());
+ set_stapled_ocsp_response_received(
+ !core_->state().stapled_ocsp_response.empty());
LeaveFunction(result);
return result;
@@ -3522,10 +3537,12 @@ void SSLClientSocketNSS::VerifyCT() {
// external communication.
int result = cert_transparency_verifier_->Verify(
server_cert_verify_result_.verified_cert,
- std::string(), // SCT list from OCSP response
+ core_->state().stapled_ocsp_response,
core_->state().sct_list_from_tls_extension,
&ct_verify_result_,
net_log_);
+ // TODO(ekasper): wipe stapled_ocsp_response and sct_list_from_tls_extension
+ // from the state after verification is complete, to conserve memory.
VLOG(1) << "CT Verification complete: result " << result
<< " Invalid scts: " << ct_verify_result_.invalid_scts.size()
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 0e667c6..6d4b118 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -1793,9 +1793,11 @@ TEST_F(SSLClientSocketCertRequestInfoTest, TwoAuthorities) {
request_info->cert_authorities[1]);
}
-TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabled) {
+} // namespace
+
+TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabledTLSExtension) {
SpawnedTestServer::SSLOptions ssl_options;
- ssl_options.signed_cert_timestamps = "test";
+ ssl_options.signed_cert_timestamps_tls_ext = "test";
SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
ssl_options,
@@ -1835,10 +1837,69 @@ TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabled) {
EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
#if !defined(USE_OPENSSL)
- EXPECT_TRUE(sock->WereSignedCertTimestampsReceived());
+ EXPECT_TRUE(sock->signed_cert_timestamps_received_);
#else
// Enabling CT for OpenSSL is currently a noop.
- EXPECT_FALSE(sock->WereSignedCertTimestampsReceived());
+ EXPECT_FALSE(sock->signed_cert_timestamps_received_);
+#endif
+
+ sock->Disconnect();
+ EXPECT_FALSE(sock->IsConnected());
+}
+
+// Test that enabling Signed Certificate Timestamps enables OCSP stapling.
+TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabledOCSP) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.staple_ocsp_response = true;
+ // The test server currently only knows how to generate OCSP responses
+ // for a freshly minted certificate.
+ ssl_options.server_certificate = SpawnedTestServer::SSLOptions::CERT_AUTO;
+
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ SSLConfig ssl_config;
+ // Enabling Signed Cert Timestamps ensures we request OCSP stapling for
+ // Certificate Transparency verification regardless of whether the platform
+ // is able to process the OCSP status itself.
+ ssl_config.signed_cert_timestamps_enabled = true;
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), ssl_config));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+
+#if !defined(USE_OPENSSL)
+ EXPECT_TRUE(sock->stapled_ocsp_response_received_);
+#else
+ // OCSP stapling isn't currently supported in the OpenSSL socket.
+ EXPECT_FALSE(sock->stapled_ocsp_response_received_);
#endif
sock->Disconnect();
@@ -1847,7 +1908,7 @@ TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabled) {
TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsDisabled) {
SpawnedTestServer::SSLOptions ssl_options;
- ssl_options.signed_cert_timestamps = "test";
+ ssl_options.signed_cert_timestamps_tls_ext = "test";
SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
ssl_options,
@@ -1886,12 +1947,10 @@ TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsDisabled) {
log.GetEntries(&entries);
EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
- EXPECT_FALSE(sock->WereSignedCertTimestampsReceived());
+ EXPECT_FALSE(sock->signed_cert_timestamps_received_);
sock->Disconnect();
EXPECT_FALSE(sock->IsConnected());
}
-} // namespace
-
} // namespace net
diff --git a/net/test/ct_test_util.cc b/net/test/ct_test_util.cc
index 924366e..4e95e19 100644
--- a/net/test/ct_test_util.cc
+++ b/net/test/ct_test_util.cc
@@ -101,6 +101,59 @@ const char kTestSCTPrecertSignatureData[] =
"30450220482f6751af35dba65436be1fd6640f3dbf9a41429495924530288fa3e5e23e0602"
"2100e4edc0db3ac572b1e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08";
+// A well-formed OCSP response with fake SCT contents. Does not come from
+// http://code.google.com/p/certificate-transparency, does not pertain to any
+// of the test certs here, and is only used to test extracting the extension
+// contents from the response.
+const char kFakeOCSPResponse[] =
+ "3082016e0a0100a08201673082016306092b060105050730010104820154308201503081ba"
+ "a21604144edfdf5ff9c90ffacfca66e7fbc436bc39ee3fc7180f3230313030313031303630"
+ "3030305a30818e30818b3049300906052b0e03021a050004141833a1e6a4f09577cca0e64c"
+ "e7d145ca4b93700904144edfdf5ff9c90ffacfca66e7fbc436bc39ee3fc7021001aef99bde"
+ "e0bb58c6f2b816bc3ae02f8000180f32303130303130313036303030305aa011180f323033"
+ "30303130313036303030305aa11830163014060a2b06010401d67902040504060404746573"
+ "74300d06092a864886f70d0101050500038181003586ffcf0794e64eb643d52a3d570a1c93"
+ "836395986a2f792dd4e9c70b05161186c55c1658e0607dc9ec0d0924ac37fb99506c870579"
+ "634be1de62ba2fced5f61f3b428f959fcee9bddf6f268c8e14c14fdf3b447786e638a5c8cc"
+ "b610893df17a60e4cff30f4780aeffe0086ef19910f0d9cd7414bc93d1945686f88ad0a3c3"
+ ;
+
+const char kFakeOCSPResponseCert[] =
+ "3082022930820192a003020102021001aef99bdee0bb58c6f2b816bc3ae02f300d06092a86"
+ "4886f70d01010505003015311330110603550403130a54657374696e67204341301e170d31"
+ "30303130313036303030305a170d3332313230313036303030305a30373112301006035504"
+ "0313093132372e302e302e31310b300906035504061302585831143012060355040a130b54"
+ "657374696e67204f726730819d300d06092a864886f70d010101050003818b003081870281"
+ "8100a71998f2930bfe73d031a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da99"
+ "9de4ed65832afcb0807f98787506539d258a0ce3c2c77967653099a9034a9b115a876c39a8"
+ "c4e4ed4acd0c64095946fb39eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1"
+ "afc45519ab9eca47c30185c771c64aa5ecf07d020103a35a3058303a06082b060105050701"
+ "01010100042b3029302706082b06010505073001861b687474703a2f2f3132372e302e302e"
+ "313a35353038312f6f637370301a0603551d200101000410300e300c060a2b06010401d679"
+ "020401300d06092a864886f70d01010505000381810065e04fadd3484197f3412479d917e1"
+ "9d8f7db57b526f2d0e4c046f86cebe643bf568ea0cd6570b228842aa057c6a7c79f209dfcd"
+ "3419a4d93b1ecfb1c0224f33083c7d4da023499fbd00d81d6711ad58ffcf65f1545247fe9d"
+ "83203425fd706b4fc5e797002af3d88151be5901eef56ec30aacdfc404be1bd35865ff1943"
+ "2516";
+
+const char kFakeOCSPResponseIssuerCert[] =
+ "308201d13082013aa003020102020101300d06092a864886f70d0101050500301531133011"
+ "0603550403130a54657374696e67204341301e170d3130303130313036303030305a170d33"
+ "32313230313036303030305a3015311330110603550403130a54657374696e672043413081"
+ "9d300d06092a864886f70d010101050003818b0030818702818100a71998f2930bfe73d031"
+ "a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da999de4ed65832afcb0807f9878"
+ "7506539d258a0ce3c2c77967653099a9034a9b115a876c39a8c4e4ed4acd0c64095946fb39"
+ "eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1afc45519ab9eca47c30185c7"
+ "71c64aa5ecf07d020103a333303130120603551d130101ff040830060101ff020100301b06"
+ "03551d200101000411300f300d060b2b06010401d6790201ce0f300d06092a864886f70d01"
+ "01050500038181003f4936f8d00e83fbdde331f2c64335dcf7dec8b1a2597683edeed61af0"
+ "fa862412fad848938fe7ab77f1f9a43671ff6fdb729386e26f49e7aca0c0ea216e5970d933"
+ "3ea1e11df2ccb357a5fed5220f9c6239e8946b9b7517707631d51ab996833d58a022cff5a6"
+ "2169ac9258ec110efee78da9ab4a641e3b3c9ee5e8bd291460";
+
+
+const char kFakeOCSPExtensionValue[] = "74657374"; // "test"
+
} // namespace
void GetX509CertLogEntry(LogEntry* entry) {
@@ -171,6 +224,22 @@ std::string GetDefaultIssuerKeyHash() {
return HexToBytes(kDefaultIssuerKeyHash);
}
+std::string GetDerEncodedFakeOCSPResponse() {
+return HexToBytes(kFakeOCSPResponse);
+}
+
+std::string GetFakeOCSPExtensionValue() {
+ return HexToBytes(kFakeOCSPExtensionValue);
+}
+
+std::string GetDerEncodedFakeOCSPResponseCert() {
+ return HexToBytes(kFakeOCSPResponseCert);
+}
+
+std::string GetDerEncodedFakeOCSPResponseIssuerCert() {
+ return HexToBytes(kFakeOCSPResponseIssuerCert);
+}
+
} // namespace ct
} // namespace net
diff --git a/net/test/ct_test_util.h b/net/test/ct_test_util.h
index 6a07f32..87afc10 100644
--- a/net/test/ct_test_util.h
+++ b/net/test/ct_test_util.h
@@ -50,6 +50,18 @@ void GetPrecertSCT(scoped_refptr<SignedCertificateTimestamp>* sct);
// Issuer key hash
std::string GetDefaultIssuerKeyHash();
+// Fake OCSP response with an embedded SCT list.
+std::string GetDerEncodedFakeOCSPResponse();
+
+// The SCT list embedded in the response above.
+std::string GetFakeOCSPExtensionValue();
+
+// The cert the OCSP response is for.
+std::string GetDerEncodedFakeOCSPResponseCert();
+
+// The issuer of the previous cert.
+std::string GetDerEncodedFakeOCSPResponseIssuerCert();
+
} // namespace ct
} // namespace net
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
index 3b06a0a..ac37c70 100644
--- a/net/test/spawned_test_server/base_test_server.cc
+++ b/net/test/spawned_test_server/base_test_server.cc
@@ -61,7 +61,8 @@ BaseTestServer::SSLOptions::SSLOptions()
bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
record_resume(false),
tls_intolerant(TLS_INTOLERANT_NONE),
- fallback_scsv_enabled(false) {}
+ fallback_scsv_enabled(false),
+ staple_ocsp_response(false) {}
BaseTestServer::SSLOptions::SSLOptions(
BaseTestServer::SSLOptions::ServerCertificate cert)
@@ -72,7 +73,8 @@ BaseTestServer::SSLOptions::SSLOptions(
bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
record_resume(false),
tls_intolerant(TLS_INTOLERANT_NONE),
- fallback_scsv_enabled(false) {}
+ fallback_scsv_enabled(false),
+ staple_ocsp_response(false) {}
BaseTestServer::SSLOptions::~SSLOptions() {}
@@ -400,11 +402,14 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const {
}
if (ssl_options_.fallback_scsv_enabled)
arguments->Set("fallback-scsv", base::Value::CreateNullValue());
- if (!ssl_options_.signed_cert_timestamps.empty()) {
- std::string b64_scts;
- base::Base64Encode(ssl_options_.signed_cert_timestamps, &b64_scts);
- arguments->SetString("signed-cert-timestamps", b64_scts);
+ if (!ssl_options_.signed_cert_timestamps_tls_ext.empty()) {
+ std::string b64_scts_tls_ext;
+ base::Base64Encode(ssl_options_.signed_cert_timestamps_tls_ext,
+ &b64_scts_tls_ext);
+ arguments->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext);
}
+ if (ssl_options_.staple_ocsp_response)
+ arguments->Set("staple-ocsp-response", base::Value::CreateNullValue());
}
return GenerateAdditionalArguments(arguments);
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index bb82ed0..fb8d6ed 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -155,13 +155,16 @@ class BaseTestServer {
// connections.
bool fallback_scsv_enabled;
- // (Fake) SignedCertificateTimestampList (as a raw binary string) to send in
- // a TLS extension.
// Temporary glue for testing: validation of SCTs is application-controlled
// and can be appropriately mocked out, so sending fake data here does not
// affect handshaking behaviour.
// TODO(ekasper): replace with valid SCT files for test certs.
- std::string signed_cert_timestamps;
+ // (Fake) SignedCertificateTimestampList (as a raw binary string) to send in
+ // a TLS extension.
+ std::string signed_cert_timestamps_tls_ext;
+
+ // Whether to staple the OCSP response.
+ bool staple_ocsp_response;
};
// Pass as the 'host' parameter during construction to server on 127.0.0.1
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 085619f..0a1f59b 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -136,7 +136,7 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
def __init__(self, server_address, request_hander_class, pem_cert_and_key,
ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
record_resume_info, tls_intolerant, signed_cert_timestamps,
- fallback_scsv_enabled):
+ fallback_scsv_enabled, ocsp_response):
self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
# Force using only python implementation - otherwise behavior is different
# depending on whether m2crypto Python module is present (error is thrown
@@ -150,6 +150,7 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
self.tls_intolerant = tls_intolerant
self.signed_cert_timestamps = signed_cert_timestamps
self.fallback_scsv_enabled = fallback_scsv_enabled
+ self.ocsp_response = ocsp_response
for ca_file in ssl_client_cas:
s = open(ca_file).read()
@@ -184,7 +185,8 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
tlsIntolerant=self.tls_intolerant,
signedCertTimestamps=
self.signed_cert_timestamps,
- fallbackSCSV=self.fallback_scsv_enabled)
+ fallbackSCSV=self.fallback_scsv_enabled,
+ ocspResponse = self.ocsp_response)
tlsConnection.ignoreAbruptClose = True
return True
except tlslite.api.TLSAbruptCloseError:
@@ -1946,15 +1948,21 @@ class ServerRunner(testserver_base.TestServerRunner):
raise testserver_base.OptionError(
'specified trusted client CA file not found: ' + ca_cert +
' exiting...')
+
+ stapled_ocsp_response = None
+ if self.__ocsp_server and self.options.staple_ocsp_response:
+ stapled_ocsp_response = self.__ocsp_server.ocsp_response
+
server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
self.options.ssl_client_auth,
self.options.ssl_client_ca,
self.options.ssl_bulk_cipher,
self.options.record_resume,
self.options.tls_intolerant,
- self.options.signed_cert_timestamps.decode(
+ self.options.signed_cert_timestamps_tls_ext.decode(
"base64"),
- self.options.fallback_scsv)
+ self.options.fallback_scsv,
+ stapled_ocsp_response)
print 'HTTPS server started on %s:%d...' % (host, server.server_port)
else:
server = HTTPServer((host, port), TestPageHandler)
@@ -2092,8 +2100,8 @@ class ServerRunner(testserver_base.TestServerRunner):
'aborted. 2 means TLS 1.1 or higher will be '
'aborted. 3 means TLS 1.2 or higher will be '
'aborted.')
- self.option_parser.add_option('--signed-cert-timestamps',
- dest='signed_cert_timestamps',
+ self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
+ dest='signed_cert_timestamps_tls_ext',
default='',
help='Base64 encoded SCT list. If set, '
'server will respond with a '
@@ -2106,6 +2114,12 @@ class ServerRunner(testserver_base.TestServerRunner):
'will be enabled. This causes the server to '
'reject fallback connections from compatible '
'clients (e.g. Chrome).')
+ self.option_parser.add_option('--staple-ocsp-response',
+ dest='staple_ocsp_response',
+ default=False, action='store_true',
+ help='If set, server will staple the OCSP '
+ 'response whenever OCSP is on and the client '
+ 'supports OCSP stapling.')
self.option_parser.add_option('--https-record-resume',
dest='record_resume', const=True,
default=False, action='store_const',
diff --git a/third_party/tlslite/README.chromium b/third_party/tlslite/README.chromium
index ec2fad8..ed0d793 100644
--- a/third_party/tlslite/README.chromium
+++ b/third_party/tlslite/README.chromium
@@ -38,3 +38,4 @@ Local Modifications:
Certificate Timestamps over a TLS extension.
- patches/fallback_scsv.patch: add support for TLS_FALLBACK_SCSV. See
https://tools.ietf.org/html/draft-bmoeller-tls-downgrade-scsv-01
+- patches/status_request.patch: add support for sending stapled OCSP responses.
diff --git a/third_party/tlslite/patches/status_request.patch b/third_party/tlslite/patches/status_request.patch
new file mode 100644
index 0000000..15f01d4
--- /dev/null
+++ b/third_party/tlslite/patches/status_request.patch
@@ -0,0 +1,208 @@
+diff --git a/third_party/tlslite/tlslite/TLSConnection.py b/third_party/tlslite/tlslite/TLSConnection.py
+index e6ce187..94ee5eb 100644
+--- a/third_party/tlslite/tlslite/TLSConnection.py
++++ b/third_party/tlslite/tlslite/TLSConnection.py
+@@ -937,8 +937,8 @@ class TLSConnection(TLSRecordLayer):
+ certChain=None, privateKey=None, reqCert=False,
+ sessionCache=None, settings=None, checker=None,
+ reqCAs=None, tlsIntolerant=0,
+- signedCertTimestamps=None,
+- fallbackSCSV=False):
++ signedCertTimestamps=None, fallbackSCSV=False,
++ ocspResponse=None):
+ """Perform a handshake in the role of server.
+
+ This function performs an SSL or TLS handshake. Depending on
+@@ -1014,6 +1014,16 @@ class TLSConnection(TLSRecordLayer):
+ binary 8-bit string) that will be sent as a TLS extension whenever
+ the client announces support for the extension.
+
++ @type ocspResponse: str
++ @param ocspResponse: An OCSP response (as a binary 8-bit string) that
++ will be sent stapled in the handshake whenever the client announces
++ support for the status_request extension.
++ Note that the response is sent independent of the ClientHello
++ status_request extension contents, and is thus only meant for testing
++ environments. Real OCSP stapling is more complicated as it requires
++ choosing a suitable response based on the ClientHello status_request
++ extension contents.
++
+ @raise socket.error: If a socket error occurs.
+ @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+ without a preceding alert.
+@@ -1024,7 +1034,7 @@ class TLSConnection(TLSRecordLayer):
+ for result in self.handshakeServerAsync(sharedKeyDB, verifierDB,
+ certChain, privateKey, reqCert, sessionCache, settings,
+ checker, reqCAs, tlsIntolerant, signedCertTimestamps,
+- fallbackSCSV):
++ fallbackSCSV, ocspResponse):
+ pass
+
+
+@@ -1033,7 +1043,7 @@ class TLSConnection(TLSRecordLayer):
+ sessionCache=None, settings=None, checker=None,
+ reqCAs=None, tlsIntolerant=0,
+ signedCertTimestamps=None,
+- fallbackSCSV=False):
++ fallbackSCSV=False, ocspResponse=None):
+ """Start a server handshake operation on the TLS connection.
+
+ This function returns a generator which behaves similarly to
+@@ -1053,7 +1063,8 @@ class TLSConnection(TLSRecordLayer):
+ reqCAs=reqCAs,
+ tlsIntolerant=tlsIntolerant,
+ signedCertTimestamps=signedCertTimestamps,
+- fallbackSCSV=fallbackSCSV)
++ fallbackSCSV=fallbackSCSV, ocspResponse=ocspResponse)
++
+ for result in self._handshakeWrapperAsync(handshaker, checker):
+ yield result
+
+@@ -1062,7 +1073,7 @@ class TLSConnection(TLSRecordLayer):
+ certChain, privateKey, reqCert,
+ sessionCache, settings, reqCAs,
+ tlsIntolerant, signedCertTimestamps,
+- fallbackSCSV):
++ fallbackSCSV, ocspResponse):
+
+ self._handshakeStart(client=False)
+
+@@ -1439,10 +1450,14 @@ class TLSConnection(TLSRecordLayer):
+ sessionID, cipherSuite, certificateType)
+ serverHello.channel_id = clientHello.channel_id
+ if clientHello.support_signed_cert_timestamps:
+- serverHello.signed_cert_timestamps = signedCertTimestamps
++ serverHello.signed_cert_timestamps = signedCertTimestamps
++ serverHello.status_request = (clientHello.status_request and
++ ocspResponse)
+ doingChannelID = clientHello.channel_id
+ msgs.append(serverHello)
+ msgs.append(Certificate(certificateType).create(serverCertChain))
++ if serverHello.status_request:
++ msgs.append(CertificateStatus().create(ocspResponse))
+ if reqCert and reqCAs:
+ msgs.append(CertificateRequest().create([], reqCAs))
+ elif reqCert:
+diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py
+index 23e3dcb..d027ef5 100644
+--- a/third_party/tlslite/tlslite/constants.py
++++ b/third_party/tlslite/tlslite/constants.py
+@@ -22,6 +22,7 @@ class HandshakeType:
+ certificate_verify = 15
+ client_key_exchange = 16
+ finished = 20
++ certificate_status = 22
+ encrypted_extensions = 203
+
+ class ContentType:
+@@ -31,7 +32,11 @@ class ContentType:
+ application_data = 23
+ all = (20,21,22,23)
+
++class CertificateStatusType:
++ ocsp = 1
++
+ class ExtensionType:
++ status_request = 5 # OCSP stapling
+ signed_cert_timestamps = 18 # signed_certificate_timestamp in RFC 6962
+ channel_id = 30031
+
+diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py
+index 296f422..497ef60 100644
+--- a/third_party/tlslite/tlslite/messages.py
++++ b/third_party/tlslite/tlslite/messages.py
+@@ -132,6 +132,7 @@ class ClientHello(HandshakeMsg):
+ self.srp_username = None # a string
+ self.channel_id = False
+ self.support_signed_cert_timestamps = False
++ self.status_request = False
+
+ def create(self, version, random, session_id, cipher_suites,
+ certificate_types=None, srp_username=None):
+@@ -182,6 +183,19 @@ class ClientHello(HandshakeMsg):
+ if extLength:
+ raise SyntaxError()
+ self.support_signed_cert_timestamps = True
++ elif extType == ExtensionType.status_request:
++ # Extension contents are currently ignored.
++ # According to RFC 6066, this is not strictly forbidden
++ # (although it is suboptimal):
++ # Servers that receive a client hello containing the
++ # "status_request" extension MAY return a suitable
++ # certificate status response to the client along with
++ # their certificate. If OCSP is requested, they
++ # SHOULD use the information contained in the extension
++ # when selecting an OCSP responder and SHOULD include
++ # request_extensions in the OCSP request.
++ p.getFixBytes(extLength)
++ self.status_request = True
+ else:
+ p.getFixBytes(extLength)
+ soFar += 4 + extLength
+@@ -230,6 +244,7 @@ class ServerHello(HandshakeMsg):
+ self.compression_method = 0
+ self.channel_id = False
+ self.signed_cert_timestamps = None
++ self.status_request = False
+
+ def create(self, version, random, session_id, cipher_suite,
+ certificate_type):
+@@ -282,6 +297,9 @@ class ServerHello(HandshakeMsg):
+ if self.signed_cert_timestamps:
+ extLength += 4 + len(self.signed_cert_timestamps)
+
++ if self.status_request:
++ extLength += 4
++
+ if extLength != 0:
+ w.add(extLength, 2)
+
+@@ -299,6 +317,10 @@ class ServerHello(HandshakeMsg):
+ w.add(ExtensionType.signed_cert_timestamps, 2)
+ w.addVarSeq(stringToBytes(self.signed_cert_timestamps), 1, 2)
+
++ if self.status_request:
++ w.add(ExtensionType.status_request, 2)
++ w.add(0, 2)
++
+ return HandshakeMsg.postWrite(self, w, trial)
+
+ class Certificate(HandshakeMsg):
+@@ -367,6 +389,37 @@ class Certificate(HandshakeMsg):
+ raise AssertionError()
+ return HandshakeMsg.postWrite(self, w, trial)
+
++class CertificateStatus(HandshakeMsg):
++ def __init__(self):
++ self.contentType = ContentType.handshake
++
++ def create(self, ocsp_response):
++ self.ocsp_response = ocsp_response
++ return self
++
++ # Defined for the sake of completeness, even though we currently only
++ # support sending the status message (server-side), not requesting
++ # or receiving it (client-side).
++ def parse(self, p):
++ p.startLengthCheck(3)
++ status_type = p.get(1)
++ # Only one type is specified, so hardwire it.
++ if status_type != CertificateStatusType.ocsp:
++ raise SyntaxError()
++ ocsp_response = p.getVarBytes(3)
++ if not ocsp_response:
++ # Can't be empty
++ raise SyntaxError()
++ self.ocsp_response = ocsp_response
++ return self
++
++ def write(self, trial=False):
++ w = HandshakeMsg.preWrite(self, HandshakeType.certificate_status,
++ trial)
++ w.add(CertificateStatusType.ocsp, 1)
++ w.addVarSeq(stringToBytes(self.ocsp_response), 1, 3)
++ return HandshakeMsg.postWrite(self, w, trial)
++
+ class CertificateRequest(HandshakeMsg):
+ def __init__(self):
+ self.contentType = ContentType.handshake
diff --git a/third_party/tlslite/tlslite/TLSConnection.py b/third_party/tlslite/tlslite/TLSConnection.py
index c5722d6..0c34536 100644
--- a/third_party/tlslite/tlslite/TLSConnection.py
+++ b/third_party/tlslite/tlslite/TLSConnection.py
@@ -937,8 +937,8 @@ class TLSConnection(TLSRecordLayer):
certChain=None, privateKey=None, reqCert=False,
sessionCache=None, settings=None, checker=None,
reqCAs=None, tlsIntolerant=0,
- signedCertTimestamps=None,
- fallbackSCSV=False):
+ signedCertTimestamps=None, fallbackSCSV=False,
+ ocspResponse=None):
"""Perform a handshake in the role of server.
This function performs an SSL or TLS handshake. Depending on
@@ -1027,6 +1027,16 @@ class TLSConnection(TLSRecordLayer):
TLS_FALLBACK_SCSV and thus reject connections using less than the
server's maximum TLS version that include this cipher suite.
+ @type ocspResponse: str
+ @param ocspResponse: An OCSP response (as a binary 8-bit string) that
+ will be sent stapled in the handshake whenever the client announces
+ support for the status_request extension.
+ Note that the response is sent independent of the ClientHello
+ status_request extension contents, and is thus only meant for testing
+ environments. Real OCSP stapling is more complicated as it requires
+ choosing a suitable response based on the ClientHello status_request
+ extension contents.
+
@raise socket.error: If a socket error occurs.
@raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
without a preceding alert.
@@ -1037,7 +1047,7 @@ class TLSConnection(TLSRecordLayer):
for result in self.handshakeServerAsync(sharedKeyDB, verifierDB,
certChain, privateKey, reqCert, sessionCache, settings,
checker, reqCAs, tlsIntolerant, signedCertTimestamps,
- fallbackSCSV):
+ fallbackSCSV, ocspResponse):
pass
@@ -1046,7 +1056,7 @@ class TLSConnection(TLSRecordLayer):
sessionCache=None, settings=None, checker=None,
reqCAs=None, tlsIntolerant=0,
signedCertTimestamps=None,
- fallbackSCSV=False):
+ fallbackSCSV=False, ocspResponse=None):
"""Start a server handshake operation on the TLS connection.
This function returns a generator which behaves similarly to
@@ -1066,7 +1076,8 @@ class TLSConnection(TLSRecordLayer):
reqCAs=reqCAs,
tlsIntolerant=tlsIntolerant,
signedCertTimestamps=signedCertTimestamps,
- fallbackSCSV=fallbackSCSV)
+ fallbackSCSV=fallbackSCSV, ocspResponse=ocspResponse)
+
for result in self._handshakeWrapperAsync(handshaker, checker):
yield result
@@ -1075,7 +1086,7 @@ class TLSConnection(TLSRecordLayer):
certChain, privateKey, reqCert,
sessionCache, settings, reqCAs,
tlsIntolerant, signedCertTimestamps,
- fallbackSCSV):
+ fallbackSCSV, ocspResponse):
self._handshakeStart(client=False)
@@ -1452,10 +1463,14 @@ class TLSConnection(TLSRecordLayer):
sessionID, cipherSuite, certificateType)
serverHello.channel_id = clientHello.channel_id
if clientHello.support_signed_cert_timestamps:
- serverHello.signed_cert_timestamps = signedCertTimestamps
+ serverHello.signed_cert_timestamps = signedCertTimestamps
+ serverHello.status_request = (clientHello.status_request and
+ ocspResponse)
doingChannelID = clientHello.channel_id
msgs.append(serverHello)
msgs.append(Certificate(certificateType).create(serverCertChain))
+ if serverHello.status_request:
+ msgs.append(CertificateStatus().create(ocspResponse))
if reqCert and reqCAs:
msgs.append(CertificateRequest().create([], reqCAs))
elif reqCert:
diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py
index 23e3dcb..d027ef5 100644
--- a/third_party/tlslite/tlslite/constants.py
+++ b/third_party/tlslite/tlslite/constants.py
@@ -22,6 +22,7 @@ class HandshakeType:
certificate_verify = 15
client_key_exchange = 16
finished = 20
+ certificate_status = 22
encrypted_extensions = 203
class ContentType:
@@ -31,7 +32,11 @@ class ContentType:
application_data = 23
all = (20,21,22,23)
+class CertificateStatusType:
+ ocsp = 1
+
class ExtensionType:
+ status_request = 5 # OCSP stapling
signed_cert_timestamps = 18 # signed_certificate_timestamp in RFC 6962
channel_id = 30031
diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py
index 296f422..497ef60 100644
--- a/third_party/tlslite/tlslite/messages.py
+++ b/third_party/tlslite/tlslite/messages.py
@@ -132,6 +132,7 @@ class ClientHello(HandshakeMsg):
self.srp_username = None # a string
self.channel_id = False
self.support_signed_cert_timestamps = False
+ self.status_request = False
def create(self, version, random, session_id, cipher_suites,
certificate_types=None, srp_username=None):
@@ -182,6 +183,19 @@ class ClientHello(HandshakeMsg):
if extLength:
raise SyntaxError()
self.support_signed_cert_timestamps = True
+ elif extType == ExtensionType.status_request:
+ # Extension contents are currently ignored.
+ # According to RFC 6066, this is not strictly forbidden
+ # (although it is suboptimal):
+ # Servers that receive a client hello containing the
+ # "status_request" extension MAY return a suitable
+ # certificate status response to the client along with
+ # their certificate. If OCSP is requested, they
+ # SHOULD use the information contained in the extension
+ # when selecting an OCSP responder and SHOULD include
+ # request_extensions in the OCSP request.
+ p.getFixBytes(extLength)
+ self.status_request = True
else:
p.getFixBytes(extLength)
soFar += 4 + extLength
@@ -230,6 +244,7 @@ class ServerHello(HandshakeMsg):
self.compression_method = 0
self.channel_id = False
self.signed_cert_timestamps = None
+ self.status_request = False
def create(self, version, random, session_id, cipher_suite,
certificate_type):
@@ -282,6 +297,9 @@ class ServerHello(HandshakeMsg):
if self.signed_cert_timestamps:
extLength += 4 + len(self.signed_cert_timestamps)
+ if self.status_request:
+ extLength += 4
+
if extLength != 0:
w.add(extLength, 2)
@@ -299,6 +317,10 @@ class ServerHello(HandshakeMsg):
w.add(ExtensionType.signed_cert_timestamps, 2)
w.addVarSeq(stringToBytes(self.signed_cert_timestamps), 1, 2)
+ if self.status_request:
+ w.add(ExtensionType.status_request, 2)
+ w.add(0, 2)
+
return HandshakeMsg.postWrite(self, w, trial)
class Certificate(HandshakeMsg):
@@ -367,6 +389,37 @@ class Certificate(HandshakeMsg):
raise AssertionError()
return HandshakeMsg.postWrite(self, w, trial)
+class CertificateStatus(HandshakeMsg):
+ def __init__(self):
+ self.contentType = ContentType.handshake
+
+ def create(self, ocsp_response):
+ self.ocsp_response = ocsp_response
+ return self
+
+ # Defined for the sake of completeness, even though we currently only
+ # support sending the status message (server-side), not requesting
+ # or receiving it (client-side).
+ def parse(self, p):
+ p.startLengthCheck(3)
+ status_type = p.get(1)
+ # Only one type is specified, so hardwire it.
+ if status_type != CertificateStatusType.ocsp:
+ raise SyntaxError()
+ ocsp_response = p.getVarBytes(3)
+ if not ocsp_response:
+ # Can't be empty
+ raise SyntaxError()
+ self.ocsp_response = ocsp_response
+ return self
+
+ def write(self, trial=False):
+ w = HandshakeMsg.preWrite(self, HandshakeType.certificate_status,
+ trial)
+ w.add(CertificateStatusType.ocsp, 1)
+ w.addVarSeq(stringToBytes(self.ocsp_response), 1, 3)
+ return HandshakeMsg.postWrite(self, w, trial)
+
class CertificateRequest(HandshakeMsg):
def __init__(self):
self.contentType = ContentType.handshake