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 |