diff options
author | ekasper@google.com <ekasper@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 00:25:51 +0000 |
---|---|---|
committer | ekasper@google.com <ekasper@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 00:25:51 +0000 |
commit | c9feb5fdfc7df5f0e30710b58b95a887ac27125d (patch) | |
tree | 807e19c1ccd5a312086a0fc5e67746d0c10947ad | |
parent | a399a758b43c578203f26d592fdca2e47b9c6165 (diff) | |
download | chromium_src-c9feb5fdfc7df5f0e30710b58b95a887ac27125d.zip chromium_src-c9feb5fdfc7df5f0e30710b58b95a887ac27125d.tar.gz chromium_src-c9feb5fdfc7df5f0e30710b58b95a887ac27125d.tar.bz2 |
Extract Certificate Transparency SCTs from stapled OCSP responses
BUG=309578
Review URL: https://codereview.chromium.org/102613006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241083 0039d316-1c4b-4281-b951-d872f2087c98
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 |