Chromium Code Reviews| Index: net/cert/ocsp_parser.cc |
| diff --git a/net/cert/ocsp_parser.cc b/net/cert/ocsp_parser.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..06f8a329f2487225073068e9c9798ae4127a06b0 |
| --- /dev/null |
| +++ b/net/cert/ocsp_parser.cc |
| @@ -0,0 +1,327 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
|
eroman
2016/02/03 22:54:44
can change to 2016
svaldez
2016/02/04 19:03:24
Done.
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include <algorithm> |
| + |
| +#include "base/base64.h" |
| +#include "base/sha1.h" |
| +#include "net/cert/internal/extended_key_usage.h" |
| +#include "net/cert/internal/signature_policy.h" |
| +#include "net/cert/internal/verify_name_match.h" |
| +#include "net/cert/internal/verify_signed_data.h" |
| +#include "net/cert/ocsp_parser.h" |
| + |
| +namespace net { |
| + |
| +namespace cert { |
| + |
| +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 ParseOCSPSingleResponse(der::Input raw_tlv, OCSPSingleResponse* out) { |
|
eroman
2016/02/03 22:54:45
Please provide comments somewhere that reflect the
svaldez
2016/02/04 19:03:24
Should this be both here and in the header file, o
|
| + der::Parser response_parser(raw_tlv); |
| + der::Parser parser; |
| + if (!response_parser.ReadSequence(&parser)) |
|
eroman
2016/02/03 22:54:45
It doesn't look like unconsumed data is being chec
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + |
| + der::Input cert_id; |
| + if (!parser.ReadTag(der::kSequence, &cert_id)) |
|
eroman
2016/02/03 22:54:45
The status parsing is a good candidate to move to
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + out->cert_id = cert_id.AsString(); |
| + der::Tag status_tag; |
| + der::Input status; |
| + if (!parser.ReadTagAndValue(&status_tag, &status)) |
| + return false; |
| + if (status_tag == der::ContextSpecificPrimitive(0)) { |
| + out->cert_status = OCSPSingleResponse::CERT_GOOD; |
| + } else if (status_tag == der::ContextSpecificPrimitive(1)) { |
| + out->cert_status = OCSPSingleResponse::CERT_REVOKED; |
| + der::Parser revoked_info_parser(status); |
|
eroman
2016/02/03 22:54:45
Same issue here, what if there is unconsumed data
svaldez
2016/02/04 19:03:24
Done.
|
| + der::Input revocation_time; |
| + der::Input revocation_reason; |
| + if (!revoked_info_parser.ReadGeneralizedTime(&(out->revocation_time))) |
| + return false; |
| + if (!revoked_info_parser.ReadTag(der::kEnumerated, &revocation_reason)) |
| + return false; |
| + uint8_t revocation_reason_value; |
| + if (!der::ParseUint8(revocation_reason, &revocation_reason_value)) |
| + return false; |
| + out->revocation_reason = static_cast<OCSPSingleResponse::RevocationReason>( |
|
eroman
2016/02/03 22:54:45
This needs to ensure that the number is in range,
svaldez
2016/02/04 19:03:24
Done.
|
| + revocation_reason_value); |
| + } else if (status_tag == der::ContextSpecificPrimitive(2)) { |
| + out->cert_status = OCSPSingleResponse::CERT_UNKNOWN; |
|
eroman
2016/02/03 22:54:44
In the case of !CERT_REVOKED would be a good idea
svaldez
2016/02/04 19:03:24
Done.
|
| + } else { |
| + return false; |
| + } |
| + |
| + der::Input this_update; |
| + if (!parser.ReadGeneralizedTime(&(out->this_update))) |
| + return false; |
| + der::Input next_update_input; |
| + bool next_update_present; |
| + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
| + &next_update_input, &next_update_present)) { |
| + return false; |
| + } |
| + |
| + if (next_update_present) { |
| + der::Parser next_update_parser(next_update_input); |
|
eroman
2016/02/03 22:54:45
Same issue here, this should fail if there was unc
svaldez
2016/02/04 19:03:24
Done.
|
| + der::Input next_update; |
| + if (!next_update_parser.ReadGeneralizedTime(&(out->next_update))) |
|
eroman
2016/02/03 22:54:45
How does a caller distinguish when it was present?
svaldez
2016/02/04 19:03:24
Done.
Adding has_... for all optional fields.
|
| + return false; |
| + } |
| + bool extensions_present; |
| + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), |
| + &(out->extensions), &extensions_present)) { |
|
eroman
2016/02/03 22:54:45
Same question here: how do callers of the function
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool ParseOCSPResponseData(der::Input raw_tlv, OCSPResponseData* out) { |
|
eroman
2016/02/03 22:54:45
Document the structure being parsed, referencing t
svaldez
2016/02/04 19:03:24
Acknowledged.
|
| + der::Parser parser; |
| + if (!der::Parser(raw_tlv).ReadSequence(&parser)) |
|
eroman
2016/02/03 22:54:44
Fail on unconsumed data?
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + |
| + der::Input version_input; |
| + bool version_present; |
| + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
| + &version_input, &version_present)) { |
| + return false; |
| + } |
| + |
| + if (version_present) { |
| + der::Parser version_parser(version_input); |
| + der::Input version; |
| + if (!version_parser.ReadTag(der::kInteger, &version)) |
| + return false; |
| + if (!der::ParseUint8(version, &(out->version))) |
|
eroman
2016/02/03 22:54:44
Because the version is DEFAULT v1, a strict DER pa
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + } else { |
| + out->version = 0; |
| + } |
| + |
| + der::Tag id_tag; |
| + der::Input responder_id_input; |
| + OCSPResponseData::ResponderID responder_id; |
| + if (!parser.ReadTagAndValue(&id_tag, &responder_id_input)) |
| + return false; |
| + if (id_tag == der::ContextSpecificConstructed(1)) { |
| + responder_id.type = OCSPResponseData::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 (responder_key.Length() != base::kSHA1Length) |
|
eroman
2016/02/03 22:54:44
given the memcpy below, I would say compare the le
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + SHA1HashValue key_hash; |
| + memcpy(key_hash.data, responder_key.AsString().data(), base::kSHA1Length); |
|
eroman
2016/02/03 22:54:44
Unnecessary to call AsString() that will construct
svaldez
2016/02/04 19:03:24
Done.
|
| + responder_id.type = OCSPResponseData::KEY_HASH; |
| + responder_id.key_hash = HashValue(key_hash); |
| + } else { |
| + return false; |
| + } |
| + out->responder_id = responder_id; |
| + |
| + der::Input produced_at; |
| + if (!parser.ReadGeneralizedTime(&(out->produced_at))) |
| + return false; |
| + |
| + der::Parser responses_parser; |
| + if (!parser.ReadSequence(&responses_parser)) |
| + return false; |
| + while (responses_parser.HasMore()) { |
| + der::Input single_response; |
| + if (!responses_parser.ReadRawTLV(&single_response)) |
| + return false; |
| + out->responses.push_back(single_response); |
|
eroman
2016/02/03 22:54:44
Should out->responses not be cleared before starti
svaldez
2016/02/04 19:03:24
Done.
|
| + } |
| + |
| + bool extensions_present; |
| + if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1), |
| + &(out->extensions), &extensions_present)) { |
|
eroman
2016/02/03 22:54:45
Same question here about disambiguating present an
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +bool ParseOCSPResponse(der::Input ocsp_response, OCSPResponse* out) { |
|
eroman
2016/02/03 22:54:45
document the structure being parsed and reference
svaldez
2016/02/04 19:03:24
Acknowledged.
|
| + der::Parser parser(ocsp_response); |
|
eroman
2016/02/03 22:54:45
What about unconsumed data?
svaldez
2016/02/04 19:03:24
Done.
|
| + der::Input response_status; |
| + der::Parser ocsp_response_parser; |
| + if (!parser.ReadSequence(&ocsp_response_parser)) |
| + 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; |
| + out->status = |
| + static_cast<OCSPResponse::ResponseStatus>(response_status_value); |
| + if (out->status != OCSPResponse::SUCCESSFUL) |
|
eroman
2016/02/03 22:54:44
Why is this a failure?
svaldez
2016/02/04 19:03:24
It isn't. "return true" since we don't have to par
|
| + return true; |
| + |
| + 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), |
| + &response_bytes_input_parser)) { |
|
eroman
2016/02/03 22:54:44
What about unconsumed data?
svaldez
2016/02/04 19:03:24
Done.
|
| + return false; |
| + } |
| + if (!response_bytes_input_parser.ReadSequence(&response_bytes_parser)) |
| + return false; |
| + if (!response_bytes_parser.ReadTag(der::kOid, &response_type_oid)) |
| + return false; |
| + if (!response_type_oid.Equals(BasicOCSPResponseOid())) |
| + return false; |
| + if (!response_bytes_parser.ReadTag(der::kOctetString, &response_string)) |
| + return false; |
| + |
| + der::Parser response_parser(response_string); |
|
eroman
2016/02/03 22:54:45
What about unconsumed data?
svaldez
2016/02/04 19:03:24
Done.
|
| + der::Parser basic_response_parser; |
| + der::Parser response_data_parser; |
| + der::Input sigalg_tlv; |
|
eroman
2016/02/03 22:54:44
This function is big. It needs some documentation
|
| + if (!response_parser.ReadSequence(&basic_response_parser)) |
| + return false; |
| + if (!basic_response_parser.ReadRawTLV(&(out->data))) |
| + return false; |
| + if (!basic_response_parser.ReadRawTLV(&sigalg_tlv)) |
| + return false; |
| + out->signature_algorithm = SignatureAlgorithm::CreateFromDer(sigalg_tlv); |
| + |
| + if (!basic_response_parser.ReadBitString(&(out->signature))) |
| + return false; |
| + der::Input certs_input; |
| + bool certs_present; |
| + if (!basic_response_parser.ReadOptionalTag(der::ContextSpecificConstructed(0), |
| + &certs_input, &certs_present)) { |
| + return false; |
| + } |
| + |
| + if (!certs_present) |
| + return true; |
| + der::Parser certs_input_parser(certs_input); |
|
eroman
2016/02/03 22:54:44
Unconsumed data?
eroman
2016/02/03 22:54:45
Same comment as previously. (It is hard to follow
svaldez
2016/02/04 19:03:24
Done.
|
| + der::Parser certs_parser; |
| + if (!certs_input_parser.ReadSequence(&certs_parser)) |
| + return false; |
| + while (certs_parser.HasMore()) { |
| + ParsedCertificate parsed_cert; |
| + der::Input cert_tlv; |
| + if (!certs_parser.ReadRawTLV(&cert_tlv)) |
| + return false; |
| + if (!ParseCertificate(cert_tlv, &parsed_cert)) |
|
eroman
2016/02/03 22:54:44
I am not convinced that parsing the certificates s
|
| + return false; |
| + out->certs.push_back(parsed_cert); |
| + } |
| + return true; |
| +} |
| + |
| +namespace { |
| + |
| +bool CheckResponder(OCSPResponseData::ResponderID id, |
| + ParsedTbsCertificate cert) { |
| + if (id.type == OCSPResponseData::NAME) { |
| + return VerifyNameMatch(id.name, cert.issuer_tlv); |
| + } 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); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +bool VerifyOCSPResponse(OCSPResponse* response, ParsedCertificate* cert) { |
|
eroman
2016/02/03 22:54:44
I didn't review this function yet, but I am skepti
svaldez
2016/02/04 19:03:24
Unfortunately the certificate "chain" used for OCS
|
| + SimpleSignaturePolicy signature_policy(1024); |
| + |
| + OCSPResponseData response_data; |
| + if (!ParseOCSPResponseData(response->data, &response_data)) |
| + return false; |
| + |
| + ParsedTbsCertificate issuer; |
| + ParsedTbsCertificate responder; |
| + if (!cert || !ParseTbsCertificate(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 : response->certs) { |
| + ParsedTbsCertificate tbs_cert; |
| + 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.get()), |
| + 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.get()), |
| + response->data, response->signature, |
| + responder.spki_tlv, &signature_policy); |
| +} |
| + |
| +} // namespace cert |
| + |
| +} // namespace net |