mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-03 02:36:09 +03:00
397 lines
11 KiB
C++
397 lines
11 KiB
C++
// Copyright (c) 2012 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 "net/cert/crl_set.h"
|
|
|
|
#include "base/base64.h"
|
|
#include "base/json/json_reader.h"
|
|
#include "base/time/time.h"
|
|
#include "base/trace_event/trace_event.h"
|
|
#include "base/values.h"
|
|
#include "crypto/sha2.h"
|
|
#include "net/base/trace_constants.h"
|
|
#include "third_party/boringssl/src/include/openssl/bytestring.h"
|
|
#include "third_party/boringssl/src/include/openssl/mem.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
// CRLSet format:
|
|
//
|
|
// uint16le header_len
|
|
// byte[header_len] header_bytes
|
|
// repeated {
|
|
// byte[32] parent_spki_sha256
|
|
// uint32le num_serials
|
|
// [num_serials] {
|
|
// uint8_t serial_length;
|
|
// byte[serial_length] serial;
|
|
// }
|
|
//
|
|
// header_bytes consists of a JSON dictionary with the following keys:
|
|
// Version (int): currently 0
|
|
// ContentType (string): "CRLSet" or "CRLSetDelta" (magic value)
|
|
// DeltaFrom (int32_t): if this is a delta update (see below), then this
|
|
// contains the sequence number of the base CRLSet.
|
|
// Sequence (int32_t): the monotonic sequence number of this CRL set.
|
|
//
|
|
// ReadHeader reads the header (including length prefix) from |data| and
|
|
// updates |data| to remove the header on return. Caller takes ownership of the
|
|
// returned pointer.
|
|
base::DictionaryValue* ReadHeader(base::StringPiece* data) {
|
|
uint16_t header_len;
|
|
if (data->size() < sizeof(header_len))
|
|
return nullptr;
|
|
// Assumes little-endian.
|
|
memcpy(&header_len, data->data(), sizeof(header_len));
|
|
data->remove_prefix(sizeof(header_len));
|
|
|
|
if (data->size() < header_len)
|
|
return nullptr;
|
|
|
|
const base::StringPiece header_bytes(data->data(), header_len);
|
|
data->remove_prefix(header_len);
|
|
|
|
std::unique_ptr<base::Value> header =
|
|
base::JSONReader::Read(header_bytes, base::JSON_ALLOW_TRAILING_COMMAS);
|
|
if (header.get() == nullptr)
|
|
return nullptr;
|
|
|
|
if (!header->is_dict())
|
|
return nullptr;
|
|
return static_cast<base::DictionaryValue*>(header.release());
|
|
}
|
|
|
|
// kCurrentFileVersion is the version of the CRLSet file format that we
|
|
// currently implement.
|
|
static const int kCurrentFileVersion = 0;
|
|
|
|
bool ReadCRL(base::StringPiece* data,
|
|
std::string* out_parent_spki_hash,
|
|
std::vector<std::string>* out_serials) {
|
|
if (data->size() < crypto::kSHA256Length)
|
|
return false;
|
|
out_parent_spki_hash->assign(data->data(), crypto::kSHA256Length);
|
|
data->remove_prefix(crypto::kSHA256Length);
|
|
|
|
uint32_t num_serials;
|
|
if (data->size() < sizeof(num_serials))
|
|
return false;
|
|
// Assumes little endian.
|
|
memcpy(&num_serials, data->data(), sizeof(num_serials));
|
|
data->remove_prefix(sizeof(num_serials));
|
|
|
|
if (num_serials > 32 * 1024 * 1024) // Sanity check.
|
|
return false;
|
|
|
|
out_serials->reserve(num_serials);
|
|
|
|
for (uint32_t i = 0; i < num_serials; ++i) {
|
|
if (data->size() < sizeof(uint8_t))
|
|
return false;
|
|
|
|
uint8_t serial_length = data->data()[0];
|
|
data->remove_prefix(sizeof(uint8_t));
|
|
|
|
if (data->size() < serial_length)
|
|
return false;
|
|
|
|
out_serials->push_back(std::string());
|
|
out_serials->back().assign(data->data(), serial_length);
|
|
data->remove_prefix(serial_length);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// CopyHashListFromHeader parses a list of base64-encoded, SHA-256 hashes from
|
|
// the given |key| in |header_dict| and sets |*out| to the decoded values. It's
|
|
// not an error if |key| is not found in |header_dict|.
|
|
bool CopyHashListFromHeader(base::DictionaryValue* header_dict,
|
|
const char* key,
|
|
std::vector<std::string>* out) {
|
|
base::ListValue* list = nullptr;
|
|
if (!header_dict->GetList(key, &list)) {
|
|
// Hash lists are optional so it's not an error if not present.
|
|
return true;
|
|
}
|
|
|
|
out->clear();
|
|
out->reserve(list->GetSize());
|
|
|
|
std::string sha256_base64;
|
|
|
|
for (size_t i = 0; i < list->GetSize(); ++i) {
|
|
sha256_base64.clear();
|
|
|
|
if (!list->GetString(i, &sha256_base64))
|
|
return false;
|
|
|
|
out->push_back(std::string());
|
|
if (!base::Base64Decode(sha256_base64, &out->back())) {
|
|
out->pop_back();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// CopyHashToHashesMapFromHeader parse a map from base64-encoded, SHA-256
|
|
// hashes to lists of the same, from the given |key| in |header_dict|. It
|
|
// copies the map data into |out| (after base64-decoding).
|
|
bool CopyHashToHashesMapFromHeader(
|
|
base::DictionaryValue* header_dict,
|
|
const char* key,
|
|
std::unordered_map<std::string, std::vector<std::string>>* out) {
|
|
out->clear();
|
|
|
|
base::Value* const dict =
|
|
header_dict->FindKeyOfType(key, base::Value::Type::DICTIONARY);
|
|
if (dict == nullptr) {
|
|
// Maps are optional so it's not an error if not present.
|
|
return true;
|
|
}
|
|
|
|
for (const auto& i : dict->DictItems()) {
|
|
if (!i.second.is_list()) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::string> allowed_spkis;
|
|
for (const auto& j : i.second.GetList()) {
|
|
allowed_spkis.push_back(std::string());
|
|
if (!j.is_string() ||
|
|
!base::Base64Decode(j.GetString(), &allowed_spkis.back())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string subject_hash;
|
|
if (!base::Base64Decode(i.first, &subject_hash)) {
|
|
return false;
|
|
}
|
|
|
|
(*out)[subject_hash] = allowed_spkis;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CRLSet::CRLSet()
|
|
: sequence_(0),
|
|
not_after_(0) {
|
|
}
|
|
|
|
CRLSet::~CRLSet() = default;
|
|
|
|
// static
|
|
bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) {
|
|
TRACE_EVENT0(kNetTracingCategory, "CRLSet::Parse");
|
|
// Other parts of Chrome assume that we're little endian, so we don't lose
|
|
// anything by doing this.
|
|
#if defined(__BYTE_ORDER)
|
|
// Linux check
|
|
static_assert(__BYTE_ORDER == __LITTLE_ENDIAN, "assumes little endian");
|
|
#elif defined(__BIG_ENDIAN__)
|
|
// Mac check
|
|
#error assumes little endian
|
|
#endif
|
|
|
|
std::unique_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
|
|
if (!header_dict.get())
|
|
return false;
|
|
|
|
std::string contents;
|
|
if (!header_dict->GetString("ContentType", &contents))
|
|
return false;
|
|
if (contents != "CRLSet")
|
|
return false;
|
|
|
|
int version;
|
|
if (!header_dict->GetInteger("Version", &version) ||
|
|
version != kCurrentFileVersion) {
|
|
return false;
|
|
}
|
|
|
|
int sequence;
|
|
if (!header_dict->GetInteger("Sequence", &sequence))
|
|
return false;
|
|
|
|
double not_after;
|
|
if (!header_dict->GetDouble("NotAfter", ¬_after)) {
|
|
// NotAfter is optional for now.
|
|
not_after = 0;
|
|
}
|
|
if (not_after < 0)
|
|
return false;
|
|
|
|
scoped_refptr<CRLSet> crl_set(new CRLSet());
|
|
crl_set->sequence_ = static_cast<uint32_t>(sequence);
|
|
crl_set->not_after_ = static_cast<uint64_t>(not_after);
|
|
crl_set->crls_.reserve(64); // Value observed experimentally.
|
|
|
|
for (size_t crl_index = 0; !data.empty(); crl_index++) {
|
|
std::string spki_hash;
|
|
std::vector<std::string> blocked_serials;
|
|
|
|
if (!ReadCRL(&data, &spki_hash, &blocked_serials)) {
|
|
return false;
|
|
}
|
|
crl_set->crls_[std::move(spki_hash)] = std::move(blocked_serials);
|
|
}
|
|
|
|
if (!CopyHashListFromHeader(header_dict.get(), "BlockedSPKIs",
|
|
&crl_set->blocked_spkis_) ||
|
|
!CopyHashToHashesMapFromHeader(header_dict.get(), "LimitedSubjects",
|
|
&crl_set->limited_subjects_)) {
|
|
return false;
|
|
}
|
|
|
|
*out_crl_set = std::move(crl_set);
|
|
return true;
|
|
}
|
|
|
|
CRLSet::Result CRLSet::CheckSPKI(const base::StringPiece& spki_hash) const {
|
|
for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin();
|
|
i != blocked_spkis_.end(); ++i) {
|
|
if (spki_hash.size() == i->size() &&
|
|
memcmp(spki_hash.data(), i->data(), i->size()) == 0) {
|
|
return REVOKED;
|
|
}
|
|
}
|
|
|
|
return GOOD;
|
|
}
|
|
|
|
CRLSet::Result CRLSet::CheckSubject(const base::StringPiece& encoded_subject,
|
|
const base::StringPiece& spki_hash) const {
|
|
const std::string digest(crypto::SHA256HashString(encoded_subject));
|
|
const auto i = limited_subjects_.find(digest);
|
|
if (i == limited_subjects_.end()) {
|
|
return GOOD;
|
|
}
|
|
|
|
for (const auto& j : i->second) {
|
|
if (spki_hash == j) {
|
|
return GOOD;
|
|
}
|
|
}
|
|
|
|
return REVOKED;
|
|
}
|
|
|
|
CRLSet::Result CRLSet::CheckSerial(
|
|
const base::StringPiece& serial_number,
|
|
const base::StringPiece& issuer_spki_hash) const {
|
|
base::StringPiece serial(serial_number);
|
|
|
|
if (!serial.empty() && (serial[0] & 0x80) != 0) {
|
|
// This serial number is negative but the process which generates CRL sets
|
|
// will reject any certificates with negative serial numbers as invalid.
|
|
return UNKNOWN;
|
|
}
|
|
|
|
// Remove any leading zero bytes.
|
|
while (serial.size() > 1 && serial[0] == 0x00)
|
|
serial.remove_prefix(1);
|
|
|
|
auto it = crls_.find(issuer_spki_hash.as_string());
|
|
if (it == crls_.end())
|
|
return UNKNOWN;
|
|
|
|
for (const auto& issuer_serial : it->second) {
|
|
if (issuer_serial == serial)
|
|
return REVOKED;
|
|
}
|
|
|
|
return GOOD;
|
|
}
|
|
|
|
bool CRLSet::IsExpired() const {
|
|
if (not_after_ == 0)
|
|
return false;
|
|
|
|
uint64_t now = base::Time::Now().ToTimeT();
|
|
return now > not_after_;
|
|
}
|
|
|
|
uint32_t CRLSet::sequence() const {
|
|
return sequence_;
|
|
}
|
|
|
|
const CRLSet::CRLList& CRLSet::CrlsForTesting() const {
|
|
return crls_;
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<CRLSet> CRLSet::EmptyCRLSetForTesting() {
|
|
return ForTesting(false, nullptr, "", "", {});
|
|
}
|
|
|
|
scoped_refptr<CRLSet> CRLSet::ExpiredCRLSetForTesting() {
|
|
return ForTesting(true, nullptr, "", "", {});
|
|
}
|
|
|
|
// static
|
|
scoped_refptr<CRLSet> CRLSet::ForTesting(
|
|
bool is_expired,
|
|
const SHA256HashValue* issuer_spki,
|
|
const std::string& serial_number,
|
|
const std::string common_name,
|
|
const std::vector<std::string> acceptable_spki_hashes_for_cn) {
|
|
std::string subject_hash;
|
|
if (!common_name.empty()) {
|
|
CBB cbb, top_level, set, inner_seq, oid, cn;
|
|
uint8_t* x501_data;
|
|
size_t x501_len;
|
|
static const uint8_t kCommonNameOID[] = {0x55, 0x04, 0x03}; // 2.5.4.3
|
|
|
|
CBB_zero(&cbb);
|
|
|
|
if (!CBB_init(&cbb, 32) ||
|
|
!CBB_add_asn1(&cbb, &top_level, CBS_ASN1_SEQUENCE) ||
|
|
!CBB_add_asn1(&top_level, &set, CBS_ASN1_SET) ||
|
|
!CBB_add_asn1(&set, &inner_seq, CBS_ASN1_SEQUENCE) ||
|
|
!CBB_add_asn1(&inner_seq, &oid, CBS_ASN1_OBJECT) ||
|
|
!CBB_add_bytes(&oid, kCommonNameOID, sizeof(kCommonNameOID)) ||
|
|
!CBB_add_asn1(&inner_seq, &cn, CBS_ASN1_PRINTABLESTRING) ||
|
|
!CBB_add_bytes(&cn,
|
|
reinterpret_cast<const uint8_t*>(common_name.data()),
|
|
common_name.size()) ||
|
|
!CBB_finish(&cbb, &x501_data, &x501_len)) {
|
|
CBB_cleanup(&cbb);
|
|
return nullptr;
|
|
}
|
|
|
|
subject_hash.assign(crypto::SHA256HashString(
|
|
base::StringPiece(reinterpret_cast<char*>(x501_data), x501_len)));
|
|
OPENSSL_free(x501_data);
|
|
}
|
|
|
|
scoped_refptr<CRLSet> crl_set(new CRLSet);
|
|
if (is_expired)
|
|
crl_set->not_after_ = 1;
|
|
|
|
if (issuer_spki) {
|
|
const std::string spki(reinterpret_cast<const char*>(issuer_spki->data),
|
|
sizeof(issuer_spki->data));
|
|
std::vector<std::string> serials;
|
|
if (!serial_number.empty())
|
|
serials.push_back(serial_number);
|
|
|
|
crl_set->crls_.emplace(std::move(spki), std::move(serials));
|
|
}
|
|
|
|
if (!subject_hash.empty())
|
|
crl_set->limited_subjects_[subject_hash] = acceptable_spki_hashes_for_cn;
|
|
|
|
return crl_set;
|
|
}
|
|
|
|
} // namespace net
|