Index: net/cert/internal/parse_ocsp.cc |
diff --git a/net/cert/internal/parse_ocsp.cc b/net/cert/internal/parse_ocsp.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2647fea3ff37dfac7507ad4b44e9ca3b914474fb |
--- /dev/null |
+++ b/net/cert/internal/parse_ocsp.cc |
@@ -0,0 +1,523 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <algorithm> |
+ |
+#include "base/sha1.h" |
+#include "crypto/sha2.h" |
+#include "net/cert/internal/extended_key_usage.h" |
+#include "net/cert/internal/parse_ocsp.h" |
+#include "net/cert/internal/signature_policy.h" |
+#include "net/cert/internal/verify_name_match.h" |
+#include "net/cert/internal/verify_signed_data.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+// Continues reading from |parser| to extract an OCSP CertStatus (RFC 6960) |
+// and stores the result in the OCSPSingleResponse |out|. Returns whether |
+// the parsing was successful. |
+bool ParseOCSPStatus(der::Parser* parser, OCSPCertStatus* out) { |
eroman
2016/02/13 00:56:50
I would find it useful when reading this to duplic
svaldez
2016/02/16 17:25:11
Acknowledged.
|
+ der::Tag status_tag; |
+ der::Input status; |
+ |
+ if (!parser->ReadTagAndValue(&status_tag, &status)) |
+ return false; |
+ |
+ if (status_tag == der::ContextSpecificPrimitive(0)) { |
+ out->status = OCSPCertStatus::Status::GOOD; |
+ } else if (status_tag == der::ContextSpecificConstructed(1)) { |
+ out->status = OCSPCertStatus::Status::REVOKED; |
+ der::Parser revoked_info_parser(status); |
+ der::Input revocation_time; |
+ der::Input revocation_reason_input; |
+ if (!revoked_info_parser.ReadGeneralizedTime(&(out->revocation_time))) |
+ return false; |
+ if (!revoked_info_parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
+ &revocation_reason_input, |
+ &(out->has_reason))) { |
eroman
2016/02/13 00:56:49
How about initializing has_reason to false at the
svaldez
2016/02/16 17:25:11
Done.
|
+ return false; |
+ } |
+ if (revoked_info_parser.HasMore()) |
+ return false; |
+ if (!out->has_reason) |
+ return true; |
eroman
2016/02/13 00:56:49
From a future-proofing perspective, earlier return
svaldez
2016/02/16 17:25:11
Done.
|
+ der::Parser reason_parser(revocation_reason_input); |
+ der::Input revocation_reason; |
+ if (!reason_parser.ReadTag(der::kEnumerated, &revocation_reason)) |
eroman
2016/02/13 00:56:49
Why is this der::kEnumerated?
I assumed the defin
svaldez
2016/02/16 17:25:10
CRLReason is defined as an ENUMERATED in RFC5912 (
|
+ return false; |
+ uint8_t revocation_reason_value; |
+ if (!der::ParseUint8(revocation_reason, &revocation_reason_value)) |
+ return false; |
+ if (revocation_reason_value >= |
+ (uint8_t)OCSPCertStatus::RevocationReason::REVOCATION_REASON_MAX) { |
eroman
2016/02/13 00:56:49
static_cast<>
svaldez
2016/02/16 17:25:10
Done.
|
+ return false; |
+ } |
+ out->revocation_reason = |
+ static_cast<OCSPCertStatus::RevocationReason>(revocation_reason_value); |
eroman
2016/02/13 00:56:49
I don't see that reason_parser.HasMore() is checke
svaldez
2016/02/16 17:25:11
Done.
|
+ } else if (status_tag == der::ContextSpecificPrimitive(2)) { |
+ out->status = OCSPCertStatus::Status::UNKNOWN; |
eroman
2016/02/13 00:56:49
For sanity it would be a good idea to reset all th
svaldez
2016/02/16 17:25:10
Done.
|
+ } else { |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+// Continues reading from |parser| to extract the ResponderID (RFC 6960) and |
+// stores the result in |out|. Returns whether the parsing was successful. |
+bool ParseOCSPResponder(der::Parser* parser, |
+ OCSPResponseData::ResponderID* out) { |
+ der::Tag id_tag; |
+ der::Input responder_id_input; |
+ if (!parser->ReadTagAndValue(&id_tag, &responder_id_input)) |
+ return false; |
+ |
+ OCSPResponseData::ResponderID responder_id; |
eroman
2016/02/13 00:56:50
Why this temporary rather than writing directly to
svaldez
2016/02/16 17:25:11
Done.
|
+ if (id_tag == der::ContextSpecificConstructed(1)) { |
+ responder_id.type = OCSPResponseData::ResponderType::NAME; |
+ responder_id.name = responder_id_input; |
+ } else if (id_tag == der::ContextSpecificConstructed(2)) { |
+ der::Parser key_parser(responder_id_input); |
+ der::Input responder_key; |
+ if (!key_parser.ReadTag(der::kOctetString, &responder_key)) |
+ return false; |
+ if (key_parser.HasMore()) |
+ return false; |
+ |
+ SHA1HashValue key_hash; |
+ if (responder_key.Length() != sizeof(key_hash.data)) |
+ return false; |
+ memcpy(key_hash.data, responder_key.UnsafeData(), base::kSHA1Length); |
eroman
2016/02/13 00:56:49
can you use sizeof(key_hash.data) instead of base:
svaldez
2016/02/16 17:25:11
Done.
|
+ responder_id.type = OCSPResponseData::ResponderType::KEY_HASH; |
+ responder_id.key_hash = HashValue(key_hash); |
+ } else { |
+ return false; |
+ } |
+ *out = responder_id; |
+ return true; |
+} |
+ |
+// Continues reading from |parser| to extract a BasicOCSPResponse (RFC 6960) |
+// and stores the result in OCSPResponse |out|. Returns whether the parsing |
+// was successful. |
+bool ParseBasicOCSPResponse(der::Parser* parser, OCSPResponse* out) { |
+ der::Input sigalg_tlv; |
+ if (!parser->ReadRawTLV(&(out->data))) |
+ return false; |
+ if (!parser->ReadRawTLV(&sigalg_tlv)) |
+ return false; |
+ out->signature_algorithm = SignatureAlgorithm::CreateFromDer(sigalg_tlv); |
+ if (!out->signature_algorithm) |
+ return false; |
+ |
+ if (!parser->ReadBitString(&(out->signature))) |
+ return false; |
+ der::Input certs_input; |
+ bool certs_present; |
+ if (!parser->ReadOptionalTag(der::ContextSpecificConstructed(0), &certs_input, |
+ &certs_present)) { |
+ return false; |
+ } |
+ |
+ if (parser->HasMore()) |
eroman
2016/02/13 00:56:50
I think this would be more appropriate in the call
svaldez
2016/02/16 17:25:10
Done.
|
+ return false; |
+ |
+ if (!certs_present) |
+ return true; |
eroman
2016/02/13 00:56:49
Same comment as earlier - "return true" short cuic
svaldez
2016/02/16 17:25:10
Done.
|
+ der::Parser certs_input_parser(certs_input); |
+ der::Parser certs_parser; |
+ if (!certs_input_parser.ReadSequence(&certs_parser)) |
+ return false; |
+ if (certs_input_parser.HasMore()) |
+ return false; |
+ while (certs_parser.HasMore()) { |
+ der::Input cert_tlv; |
+ if (!certs_parser.ReadRawTLV(&cert_tlv)) |
+ return false; |
+ out->certs.push_back(cert_tlv); |
eroman
2016/02/13 00:56:50
Where is the array first initialized?
In other wo
svaldez
2016/02/16 17:25:10
Done.
|
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+OCSPCertID::OCSPCertID() {} |
+OCSPCertID::~OCSPCertID() {} |
+ |
+OCSPSingleResponse::OCSPSingleResponse() {} |
+OCSPSingleResponse::~OCSPSingleResponse() {} |
+ |
+OCSPResponseData::OCSPResponseData() {} |
+OCSPResponseData::~OCSPResponseData() {} |
+ |
+OCSPResponse::OCSPResponse() {} |
+OCSPResponse::~OCSPResponse() {} |
+ |
+der::Input BasicOCSPResponseOid() { |
+ // From RFC 6960: |
+ // |
+ // id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp } |
+ // id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 } |
+ // |
+ // In dotted notation: 1.3.6.1.5.5.7.48.1.1 |
+ static const uint8_t oid[] = {0x2b, 0x06, 0x01, 0x05, 0x05, |
+ 0x07, 0x30, 0x01, 0x01}; |
+ return der::Input(oid); |
+} |
+ |
+bool ParseOCSPCertID(der::Input raw_tlv, OCSPCertID* out) { |
+ der::Parser outer_parser(raw_tlv); |
+ der::Parser parser; |
+ if (!outer_parser.ReadSequence(&parser)) |
+ return false; |
+ der::Input sigalg_tlv; |
+ if (!parser.ReadRawTLV(&sigalg_tlv)) |
+ return false; |
+ if (!ParseHashAlgorithm(sigalg_tlv, &(out->hash_algorithm))) |
+ return false; |
+ if (!parser.ReadTag(der::kOctetString, &(out->issuer_name_hash))) |
+ return false; |
+ if (!parser.ReadTag(der::kOctetString, &(out->issuer_key_hash))) |
+ return false; |
+ if (!parser.ReadTag(der::kInteger, &(out->serial_number))) |
eroman
2016/02/13 00:56:50
This is more lenient then the certificate serial n
svaldez
2016/02/16 17:25:11
Done.
|
+ return false; |
+ return !parser.HasMore(); |
+} |
+ |
+bool ParseOCSPSingleResponse(der::Input raw_tlv, OCSPSingleResponse* out) { |
+ der::Parser response_parser(raw_tlv); |
+ der::Parser parser; |
+ if (!response_parser.ReadSequence(&parser)) |
+ return false; |
+ if (response_parser.HasMore()) |
+ return false; |
+ if (!parser.ReadRawTLV(&(out->cert_id_tlv))) |
+ return false; |
+ if (!ParseOCSPStatus(&parser, &(out->cert_status))) |
+ return false; |
+ der::Input this_update; |
+ if (!parser.ReadGeneralizedTime(&(out->this_update))) |
+ return false; |
+ der::Input next_update_input; |
+ if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
+ &next_update_input, &(out->has_next_update))) { |
+ return false; |
+ } |
+ |
+ if (out->has_next_update) { |
+ der::Parser next_update_parser(next_update_input); |
+ der::Input next_update; |
+ if (!next_update_parser.ReadGeneralizedTime(&(out->next_update))) |
+ return false; |
+ if (next_update_parser.HasMore()) |
+ return false; |
+ } |
+ |
+ if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), |
+ &(out->extensions), &(out->has_extensions))) { |
+ return false; |
+ } |
+ |
+ return !parser.HasMore(); |
+} |
+ |
+bool ParseOCSPResponseData(der::Input raw_tlv, OCSPResponseData* out) { |
+ der::Parser outer_parser(raw_tlv); |
+ der::Parser parser; |
+ if (!outer_parser.ReadSequence(&parser)) |
+ return false; |
+ if (outer_parser.HasMore()) |
+ return false; |
+ |
+ der::Input version_input; |
+ bool version_present; |
+ if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
+ &version_input, &version_present)) { |
+ return false; |
+ } |
+ |
+ // We ignore the restriction that the DEFAULT Version v1 shouldn't be |
eroman
2016/02/13 00:56:49
Per previous comment, try to avoid "We" when possi
svaldez
2016/02/16 17:25:10
Done.
|
+ // specified due to many implementations including it regardless of the DER |
+ // spec. |
+ if (version_present) { |
+ der::Parser version_parser(version_input); |
+ der::Input version; |
+ if (!version_parser.ReadTag(der::kInteger, &version)) |
+ return false; |
+ if (version_parser.HasMore()) |
+ return false; |
+ if (!der::ParseUint8(version, &(out->version))) |
+ return false; |
+ } else { |
+ out->version = 0; |
+ } |
+ |
+ if (!ParseOCSPResponder(&parser, &(out->responder_id))) |
+ return false; |
+ |
+ der::Input produced_at; |
+ if (!parser.ReadGeneralizedTime(&(out->produced_at))) |
+ return false; |
+ |
+ der::Parser responses_parser; |
+ if (!parser.ReadSequence(&responses_parser)) |
+ return false; |
+ out->responses.clear(); |
+ while (responses_parser.HasMore()) { |
+ der::Input single_response; |
+ if (!responses_parser.ReadRawTLV(&single_response)) |
+ return false; |
+ out->responses.push_back(single_response); |
+ } |
+ |
+ if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), |
+ &(out->extensions), &(out->has_extensions))) { |
+ return false; |
+ } |
+ |
+ return !parser.HasMore(); |
+} |
+ |
+bool ParseOCSPResponse(der::Input ocsp_response, OCSPResponse* out) { |
eroman
2016/02/13 00:56:49
Would be useful to see the ASN.1 structure duplica
svaldez
2016/02/16 17:25:11
Done.
|
+ der::Parser parser(ocsp_response); |
+ der::Input response_status; |
+ der::Parser ocsp_response_parser; |
+ if (!parser.ReadSequence(&ocsp_response_parser)) |
+ return false; |
+ if (parser.HasMore()) |
+ return false; |
+ if (!ocsp_response_parser.ReadTag(der::kEnumerated, &response_status)) |
+ return false; |
+ uint8_t response_status_value; |
+ if (!der::ParseUint8(response_status, &response_status_value)) |
+ return false; |
+ if (response_status_value >= |
+ (uint8_t)OCSPResponse::ResponseStatus::RESPONSE_STATUS_MAX) { |
eroman
2016/02/13 00:56:49
static_cast
svaldez
2016/02/16 17:25:10
Done.
|
+ return false; |
+ } |
+ out->status = |
+ static_cast<OCSPResponse::ResponseStatus>(response_status_value); |
+ if (out->status != OCSPResponse::ResponseStatus::SUCCESSFUL) |
+ return true; |
eroman
2016/02/13 00:56:50
Same comment as earlier regarding "return true;" s
svaldez
2016/02/16 17:25:11
Done.
|
+ |
+ der::Input response_type_oid; |
+ der::Input response_string; |
+ der::Parser response_bytes_input_parser; |
+ der::Parser response_bytes_parser; |
+ if (!ocsp_response_parser.ReadConstructed(der::ContextSpecificConstructed(0), |
eroman
2016/02/13 00:56:50
Is this allowed to be omitted in the case of succe
svaldez
2016/02/16 17:25:10
responseBytes must be set if the status is SUCCESS
|
+ &response_bytes_input_parser)) { |
+ return false; |
+ } |
+ if (ocsp_response_parser.HasMore()) |
+ return false; |
+ if (!response_bytes_input_parser.ReadSequence(&response_bytes_parser)) |
eroman
2016/02/13 00:56:49
I would find it easier to read if each sequence is
svaldez
2016/02/16 17:25:10
Moved the sequence parsing into BasicResponse pars
|
+ return false; |
+ if (response_bytes_input_parser.HasMore()) |
+ return false; |
+ if (!response_bytes_parser.ReadTag(der::kOid, &response_type_oid)) |
+ return false; |
+ if (response_type_oid != BasicOCSPResponseOid()) |
eroman
2016/02/13 00:56:50
(Note: Not familiar enough if this is the only OID
svaldez
2016/02/16 17:25:10
Its the only defined OID currently.
|
+ return false; |
+ if (!response_bytes_parser.ReadTag(der::kOctetString, &response_string)) |
eroman
2016/02/13 00:56:50
Please provide documentation here that the octet s
svaldez
2016/02/16 17:25:11
Done.
|
+ return false; |
+ if (response_bytes_parser.HasMore()) |
+ return false; |
+ der::Parser response_parser(response_string); |
+ der::Parser basic_response_parser; |
+ if (!response_parser.ReadSequence(&basic_response_parser)) |
+ return false; |
+ if (response_parser.HasMore()) |
+ return false; |
+ return ParseBasicOCSPResponse(&basic_response_parser, out); |
+} |
+ |
+namespace { |
+ |
+bool CheckResponder(OCSPResponseData::ResponderID id, |
eroman
2016/02/13 00:56:50
The non-parsing functions feel like they belong in
svaldez
2016/02/16 17:25:11
I'll split out the non-parsing bits after finishin
|
+ ParsedTbsCertificate cert) { |
+ if (id.type == OCSPResponseData::ResponderType::NAME) { |
+ der::Input name_rdn; |
+ der::Input cert_rdn; |
+ if (!der::Parser(id.name).ReadTag(der::kSequence, &name_rdn) || |
+ !der::Parser(cert.subject_tlv).ReadTag(der::kSequence, &cert_rdn)) |
+ return false; |
+ return VerifyNameMatch(name_rdn, cert_rdn); |
+ } else { |
+ der::Parser parser(cert.spki_tlv); |
+ der::Parser spki_parser; |
+ der::BitString key_bits; |
+ if (!parser.ReadSequence(&spki_parser)) |
+ return false; |
+ if (!spki_parser.SkipTag(der::kSequence)) |
+ return false; |
+ if (!spki_parser.ReadBitString(&key_bits)) |
+ return false; |
+ |
+ der::Input key = key_bits.bytes(); |
+ HashValue key_hash(HASH_VALUE_SHA1); |
+ base::SHA1HashBytes(key.UnsafeData(), key.Length(), key_hash.data()); |
+ return key_hash.Equals(id.key_hash); |
+ } |
+} |
+ |
+bool CheckCertID(der::Input id_tlv, |
+ ParsedTbsCertificate issuer, |
+ der::Input serial_number) { |
+ OCSPCertID id; |
+ if (!ParseOCSPCertID(id_tlv, &id)) |
+ return false; |
+ |
+ HashValueTag type; |
+ switch (id.hash_algorithm) { |
+ case DigestAlgorithm::Sha1: |
+ type = HASH_VALUE_SHA1; |
+ break; |
+ case DigestAlgorithm::Sha256: |
+ type = HASH_VALUE_SHA256; |
+ break; |
+ default: |
+ NOTIMPLEMENTED(); |
+ return false; |
+ } |
+ |
+ HashValue id_name_hash(type); |
+ if (id.issuer_name_hash.Length() != id_name_hash.size()) |
+ return false; |
+ memcpy(id_name_hash.data(), id.issuer_name_hash.UnsafeData(), |
+ id_name_hash.size()); |
+ HashValue issuer_name_hash(type); |
+ der::Input name = issuer.subject_tlv; |
+ if (type == HASH_VALUE_SHA1) |
+ base::SHA1HashBytes(name.UnsafeData(), name.Length(), |
+ issuer_name_hash.data()); |
+ else { |
+ std::string hash = crypto::SHA256HashString(name.AsString()); |
+ memcpy(issuer_name_hash.data(), hash.data(), issuer_name_hash.size()); |
+ } |
+ if (!id_name_hash.Equals(issuer_name_hash)) |
+ return false; |
+ |
+ HashValue id_key_hash(type); |
+ if (id.issuer_key_hash.Length() != id_key_hash.size()) |
+ return false; |
+ memcpy(id_key_hash.data(), id.issuer_key_hash.UnsafeData(), |
+ id_key_hash.size()); |
+ HashValue issuer_key_hash(type); |
+ der::Parser parser(issuer.spki_tlv); |
+ der::Parser spki_parser; |
+ der::BitString key_bits; |
+ if (!parser.ReadSequence(&spki_parser)) |
+ return false; |
+ if (!spki_parser.SkipTag(der::kSequence)) |
+ return false; |
+ if (!spki_parser.ReadBitString(&key_bits)) |
+ return false; |
+ der::Input key = key_bits.bytes(); |
+ if (type == HASH_VALUE_SHA1) |
+ base::SHA1HashBytes(key.UnsafeData(), key.Length(), issuer_key_hash.data()); |
+ else { |
+ std::string hash = crypto::SHA256HashString(key.AsString()); |
+ memcpy(issuer_key_hash.data(), hash.data(), issuer_key_hash.size()); |
+ } |
+ |
+ if (!id_key_hash.Equals(issuer_key_hash)) |
+ return false; |
+ |
+ return id.serial_number == serial_number; |
+} |
+ |
+} // namespace |
+ |
+bool VerifyOCSPResponse(const OCSPResponse* response, |
+ const ParsedCertificate* issuer_cert) { |
+ SimpleSignaturePolicy signature_policy(1024); |
+ |
+ OCSPResponseData response_data; |
+ if (!ParseOCSPResponseData(response->data, &response_data)) |
+ return false; |
+ |
+ ParsedTbsCertificate issuer; |
+ ParsedTbsCertificate responder; |
+ if (!issuer_cert || |
+ !ParseTbsCertificate(issuer_cert->tbs_certificate_tlv, &issuer)) |
+ return false; |
+ |
+ if (CheckResponder(response_data.responder_id, issuer)) { |
+ responder = issuer; |
+ } else { |
+ bool found = false; |
+ for (const auto& responder_cert_tlv : response->certs) { |
+ ParsedCertificate responder_cert; |
+ ParsedTbsCertificate tbs_cert; |
+ if (!ParseCertificate(responder_cert_tlv, &responder_cert)) |
+ return false; |
+ if (!ParseTbsCertificate(responder_cert.tbs_certificate_tlv, &tbs_cert)) |
+ return false; |
+ |
+ if (CheckResponder(response_data.responder_id, tbs_cert)) { |
+ found = true; |
+ responder = tbs_cert; |
+ |
+ scoped_ptr<SignatureAlgorithm> signature_algorithm = |
+ SignatureAlgorithm::CreateFromDer( |
+ responder_cert.signature_algorithm_tlv); |
+ der::Input issuer_spki = issuer.spki_tlv; |
+ if (!VerifySignedData(*signature_algorithm, |
+ responder_cert.tbs_certificate_tlv, |
+ responder_cert.signature_value, issuer_spki, |
+ &signature_policy)) { |
+ return false; |
+ } |
+ |
+ std::map<der::Input, ParsedExtension> extensions; |
+ std::vector<der::Input> eku; |
+ if (!ParseExtensions(responder.extensions_tlv, &extensions)) |
+ return false; |
+ if (!ParseEKUExtension(extensions[ExtKeyUsageOid()].value, &eku)) |
+ return false; |
+ if (std::find(eku.begin(), eku.end(), OCSPSigning()) == eku.end()) |
+ return false; |
+ break; |
+ } |
+ } |
+ if (!found) |
+ return false; |
+ } |
+ return VerifySignedData(*(response->signature_algorithm), response->data, |
+ response->signature, responder.spki_tlv, |
+ &signature_policy); |
+} |
+ |
+bool GetOCSPCertStatus(const OCSPResponseData* response_data, |
+ const ParsedCertificate* issuer, |
+ const ParsedCertificate* cert, |
+ OCSPCertStatus* out) { |
+ out->status = OCSPCertStatus::Status::UNKNOWN; |
+ |
+ ParsedTbsCertificate tbs_cert; |
+ if (!ParseTbsCertificate(cert->tbs_certificate_tlv, &tbs_cert)) |
+ return false; |
+ ParsedTbsCertificate issuer_tbs_cert; |
+ if (!ParseTbsCertificate(issuer->tbs_certificate_tlv, &issuer_tbs_cert)) |
+ return false; |
+ |
+ for (const auto& response : response_data->responses) { |
+ OCSPSingleResponse single_response; |
+ if (!ParseOCSPSingleResponse(response, &single_response)) |
+ return false; |
+ if (CheckCertID(single_response.cert_id_tlv, issuer_tbs_cert, |
+ tbs_cert.serial_number)) { |
+ *out = single_response.cert_status; |
+ if (single_response.cert_status.status != OCSPCertStatus::Status::GOOD) |
+ return true; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace net |