Index: net/cert/asn1_util.cc |
diff --git a/net/cert/asn1_util.cc b/net/cert/asn1_util.cc |
index 6dcff52ee65c90a4eb3ed033b226b0709a2e9b3d..323bd9eecf779bc3b327b74a0ffee776e8b8df21 100644 |
--- a/net/cert/asn1_util.cc |
+++ b/net/cert/asn1_util.cc |
@@ -326,6 +326,236 @@ bool ExtractCRLURLsFromDERCert(base::StringPiece cert, |
return true; |
} |
+bool ExtractSCTExtensionFromOCSPResponse( |
+ base::StringPiece ocsp_response, |
+ const base::StringPiece& cert_serial_number, |
+ base::StringPiece* sct_list_out) { |
+ sct_list_out->clear(); |
+ |
+ // 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; |
wtc
2013/12/03 01:18:06
Nit: add curly braces {} because the conditional e
ekasper
2013/12/03 13:50:51
Done.
|
+ 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}; |
wtc
2013/12/03 01:18:06
Nit: add a space after '{' and before '}'.
ekasper
2013/12/03 13:50:51
Done.
|
+ |
+ 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; |
+ |
+ while (responses.size() > 0) { |
wtc
2013/12/03 01:18:06
Nit: this function is very long. If we want to bre
ekasper
2013/12/03 13:50:51
I've broken it up so that the seeking to the right
|
+ // SingleResponse ::= SEQUENCE { |
+ // certID CertID, |
+ // certStatus CertStatus, |
+ // thisUpdate GeneralizedTime, |
+ // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, |
+ // singleExtensions [1] EXPLICIT Extensions OPTIONAL } |
+ base::StringPiece single_response; |
+ if (!GetElement(&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; |
+ |
+ if (single_extensions_outer.empty()) { |
+ // No extensions; and since we should only have one singleResponse per |
+ // certificate, we stop here. |
+ return true; |
+ } |
+ |
+ // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension |
+ base::StringPiece single_extensions; |
+ if (!GetElement(&single_extensions_outer, kSEQUENCE, &single_extensions)) |
+ 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.empty()) { |
+ base::StringPiece extension; |
+ if (!GetElement(&single_extensions, 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)) |
+ continue; |
+ |
+ // critical |
+ if (!GetElement(&extension, kBOOLEAN, NULL)) |
+ 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; |
+ return true; |
+ } |
wtc
2013/12/03 01:18:06
We should also return true at the end of this (inn
ekasper
2013/12/03 13:50:51
Good catch, done as you suggested, break; followed
|
+ } |
+ |
+ // No extension found. |
+ return true; |
wtc
2013/12/03 01:18:06
With the change I proposed above, the only reason
ekasper
2013/12/03 13:50:51
Yes, a false is probably better in this case. This
|
+} |
+ |
} // namespace asn1 |
} // namespace net |