Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(342)

Unified Diff: net/cert/asn1_util.cc

Issue 92443002: Extract Certificate Transparency SCTs from stapled OCSP responses (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@extract_scts
Patch Set: review comments Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/cert/asn1_util.cc
diff --git a/net/cert/asn1_util.cc b/net/cert/asn1_util.cc
index 6dcff52ee65c90a4eb3ed033b226b0709a2e9b3d..a4d44b1c2b4fbd40352c70100b80c4e096c4fed7 100644
--- a/net/cert/asn1_util.cc
+++ b/net/cert/asn1_util.cc
@@ -326,6 +326,286 @@ bool ExtractCRLURLsFromDERCert(base::StringPiece cert,
return true;
}
+// ExtractSingleResponses extracts the ASN.1 'responses' element
+// (SEQUENCE OF SingleResponse contents, excluding the SEQUENCE tag and length)
+// from the 'tbsResponseData' of a BasicOCSPResponse. On successful return,
+// |single_responses| points into |ocsp_response|.
+static bool ExtractSingleResponses(base::StringPiece ocsp_response,
+ base::StringPiece* single_responses) {
+ // OCSPResponse ::= SEQUENCE {
+ // responseStatus OCSPResponseStatus,
+ // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+ base::StringPiece ocsp_response_sequence;
+ if (!GetElement(&ocsp_response, kSEQUENCE, &ocsp_response_sequence))
+ return false;
+ if (!ocsp_response.empty())
+ return false;
+
+ // responseStatus
+ if (!GetElement(&ocsp_response_sequence, kENUMERATED, NULL))
+ return false;
+
+ // responseBytes
+ base::StringPiece response_bytes_outer;
+ if (!GetElement(&ocsp_response_sequence,
+ kOptional | kConstructed | kContextSpecific | 0,
+ &response_bytes_outer)) {
+ return false;
+ }
+ if (!ocsp_response_sequence.empty())
+ return false;
+
+ // ResponseBytes ::= SEQUENCE {
+ // responseType OBJECT IDENTIFIER,
+ // response OCTET STRING } -- DER encoding of BasicOCSPResponse
+ base::StringPiece response_bytes;
+ if (!GetElement(&response_bytes_outer, kSEQUENCE, &response_bytes))
+ return false;
+ if (!response_bytes_outer.empty())
+ return false;
+
+ // responseType
+ base::StringPiece response_type;
+ if (!GetElement(&response_bytes, kOID, &response_type))
+ return false;
+
+ // id-ad-ocsp: 1.3.6.1.5.5.7.48.1.1
+ static const uint8 kBasicOCSPResponseOID[] = { 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x01 };
+
+ if (response_type.size() != sizeof(kBasicOCSPResponseOID) ||
+ memcmp(response_type.data(), kBasicOCSPResponseOID,
+ response_type.size()) != 0) {
+ return false;
+ }
+
+ base::StringPiece wrapped_response;
+ if (!GetElement(&response_bytes, kOCTETSTRING, &wrapped_response))
+ return false;
+
+ if (!response_bytes.empty())
+ return false;
+
+ // BasicOCSPResponse ::= SEQUENCE {
+ // tbsResponseData ResponseData,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signature BIT STRING,
+ // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ base::StringPiece basic_ocsp_response;
+ if (!GetElement(&wrapped_response, kSEQUENCE, &basic_ocsp_response))
+ return false;
+ if (!wrapped_response.empty())
+ return false;
+
+ // ResponseData ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // responderID ResponderID,
+ // producedAt GeneralizedTime,
+ // responses SEQUENCE OF SingleResponse,
+ // responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+ base::StringPiece tbs_response_data;
+ if (!GetElement(&basic_ocsp_response, kSEQUENCE, &tbs_response_data))
+ return false;
+
+ // version
+ if (!GetElement(&tbs_response_data,
+ kOptional | kConstructed | kContextSpecific | 0, NULL)) {
+ return false;
+ }
+
+ // ResponderID ::= CHOICE {
+ // byName [1] Name,
+ // byKey [2] KeyHash }
+ if (!GetElement(&tbs_response_data,
+ kConstructed | kContextSpecific | 1, NULL) &&
+ !GetElement(&tbs_response_data,
+ kConstructed | kContextSpecific | 2, NULL)) {
+ return false;
+ }
+
+ // producedAt
+ if (!GetElement(&tbs_response_data, kGENERALIZEDTIME, NULL))
+ return false;
+
+ // responses: SEQUENCE OF and SEQUENCE share the same tag.
+ base::StringPiece responses;
+ if (!GetElement(&tbs_response_data, kSEQUENCE, &responses))
+ return false;
wtc 2013/12/03 21:04:25 Nit: you are meticulous in checking there is no ju
+
+ *single_responses = responses;
+ return true;
+}
+
+// ExtractExtensionsWithMatchingCertID extracts the 'singleExtensions' inner
wtc 2013/12/03 21:04:25 Nit: this comment says "inner contents", but the c
+// contents (Extensions) of a SingleResponse matching the given certificate
+// identifiers. Returns False if ASN.1 parsing failed, or if the OCSP response
wtc 2013/12/03 21:04:25 Nit: "identifiers" => "identifier" or "serial numb
+// did not contain a matching SingleResponse. On successful return,
+// |single_extensions| is either empty (no extensions found in the matching
+// response) or points into |single_responses|.
+static bool ExtractExtensionsWithMatchingCertID(
+ base::StringPiece single_responses,
+ base::StringPiece cert_serial_number,
+ base::StringPiece* single_extensions) {
+ while (single_responses.size() > 0) {
wtc 2013/12/03 21:04:25 Nit: use !single_responses.empty(), to be consiste
+ // SingleResponse ::= SEQUENCE {
+ // certID CertID,
+ // certStatus CertStatus,
+ // thisUpdate GeneralizedTime,
+ // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+ // singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+ base::StringPiece single_response;
+ if (!GetElement(&single_responses, kSEQUENCE, &single_response))
+ return false;
+
+ // CertID ::= SEQUENCE {
+ // hashAlgorithm AlgorithmIdentifier,
+ // issuerNameHash OCTET STRING, -- Hash of Issuer's DN
+ // issuerKeyHash OCTET STRING, -- Hash of Issuers public key
+ // serialNumber CertificateSerialNumber }
+ base::StringPiece cert_id;
+ if (!GetElement(&single_response, kSEQUENCE, &cert_id))
+ return false;
+
+ // hashAlgorithm
+ if (!GetElement(&cert_id, kSEQUENCE, NULL))
+ return false;
+ // issuerNameHash
+ if (!GetElement(&cert_id, kOCTETSTRING, NULL))
+ return false;
+ // issuerKeyHash
+ if (!GetElement(&cert_id, kOCTETSTRING, NULL))
+ return false;
+
+ base::StringPiece serial_number;
+ if (!GetElement(&cert_id, kINTEGER, &serial_number))
+ return false;
+
+ if (serial_number != cert_serial_number) {
+ // Serial number mismatch - continue.
+ continue;
+ }
+ // Serial number match: continue to see if the CT extension is present.
+ // CertStatus ::= CHOICE {
+ // good [0] IMPLICIT NULL,
+ // revoked [1] IMPLICIT RevokedInfo,
+ // unknown [2] IMPLICIT UnknownInfo }
+ if (!GetElement(&single_response, kContextSpecific | 0, NULL) &&
+ !GetElement(&single_response,
+ kContextSpecific | kConstructed | 1, NULL) &&
+ !GetElement(&single_response, kContextSpecific | 2, NULL)) {
+ return false;
+ }
+
+ // thisUpdate
+ if (!GetElement(&single_response, kGENERALIZEDTIME, NULL))
+ return false;
+ // nextUpdate
+ if (!GetElement(&single_response,
+ kConstructed | kOptional | kContextSpecific | 0, NULL)) {
+ return false;
+ }
+
+ base::StringPiece single_extensions_outer;
+ if (!GetElement(&single_response,
+ kConstructed | kOptional | kContextSpecific | 1,
+ &single_extensions_outer)) {
+ return false;
+ }
+ if (!single_response.empty())
+ return false;
+
+ *single_extensions = single_extensions_outer;
+ return true;
+ }
+
+ // Match not found.
+ return false;
+}
+
+bool ExtractSCTExtensionFromOCSPResponse(
wtc 2013/12/03 21:04:25 I found that NSS has a function for decoding an OC
+ base::StringPiece ocsp_response,
+ base::StringPiece cert_serial_number,
+ base::StringPiece* sct_list_out) {
+ sct_list_out->clear();
+
+ base::StringPiece single_responses;
+ if (!ExtractSingleResponses(ocsp_response, &single_responses))
+ return false;
+
+ base::StringPiece single_extensions;
+ if (!ExtractExtensionsWithMatchingCertID(single_responses,
+ cert_serial_number,
+ &single_extensions)) {
+ return false;
+ }
+
+ if (single_extensions.empty()) {
+ // Extensions are optional.
+ return true;
+ }
+
+ // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ base::StringPiece single_extensions_inner;
+ if (!GetElement(&single_extensions, kSEQUENCE, &single_extensions_inner))
+ return false;
wtc 2013/12/03 21:04:25 Nit: you didn't check for extra junk at the end of
+
+ if (single_extensions_inner.empty()) {
+ // Extensions, if present, should contain at least one element.
+ return false;
+ }
+
+ // Extension ::= SEQUENCE {
+ // extnID OBJECT IDENTIFIER,
+ // critical BOOLEAN DEFAULT FALSE,
+ // extnValue OCTET STRING
+ // -- contains the DER encoding of an ASN.1 value
+ // -- corresponding to the extension type identified
+ // -- by extnID
+ // }
+ while (!single_extensions_inner.empty()) {
+ base::StringPiece extension;
+ if (!GetElement(&single_extensions_inner, kSEQUENCE, &extension))
+ return false;
+
+ base::StringPiece extn_id;
+ if (!GetElement(&extension, kOID, &extn_id))
+ return false;
+
+ static const uint8 kSignedCertTimestampListOID[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
+ };
+ if (extn_id.size() != sizeof(kSignedCertTimestampListOID) ||
+ memcmp(extn_id.data(), kSignedCertTimestampListOID,
+ extn_id.size() != 0)) {
wtc 2013/12/03 21:04:25 BUG: extn_id.size() != 0)) => extn_id.size()) != 0
ekasper 2013/12/04 19:25:15 This has gone away but... YIKES. Good catch.
+ continue;
+ }
+
+ // critical
+ if (!GetElement(&extension, kBOOLEAN, NULL))
wtc 2013/12/03 21:04:25 IMPORTANT: Should we allow |critical| to be option
ekasper 2013/12/04 19:25:15 No, this was a bug that tests didn't catch because
+ return false;
+
+ // extnValue
+ base::StringPiece extn_value;
+ if (!GetElement(&extension, kOCTETSTRING, &extn_value))
+ return false;
+ if (!extension.empty())
+ return false;
+
+ // The value of this extension is an OCTET STRING:
+ // SignedCertificateTimestampList ::= OCTET STRING
+ base::StringPiece sct_list;
+ if (!GetElement(&extn_value, kOCTETSTRING, &sct_list))
+ return false;
+ *sct_list_out = sct_list;
+ break;
+ }
+
+ // Either we found the right extension or we looped through all of them
+ // without success. The latter is not an error because the presence of an
+ // SCT extension is optional.
+ return true;
+}
+
} // namespace asn1
} // namespace net

Powered by Google App Engine
This is Rietveld 408576698