| // Copyright 2014 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_storage.h" |
| |
| #include "base/base64.h" |
| #include "base/debug/trace_event.h" |
| #include "base/format_macros.h" |
| #include "base/json/json_reader.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "crypto/sha2.h" |
| #include "third_party/zlib/zlib.h" |
| |
| namespace net { |
| |
| // Decompress zlib decompressed |in| into |out|. |out_len| is the number of |
| // bytes at |out| and must be exactly equal to the size of the decompressed |
| // data. |
| static bool DecompressZlib(uint8* out, int out_len, base::StringPiece in) { |
| z_stream z; |
| memset(&z, 0, sizeof(z)); |
| |
| z.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(in.data())); |
| z.avail_in = in.size(); |
| z.next_out = reinterpret_cast<Bytef*>(out); |
| z.avail_out = out_len; |
| |
| if (inflateInit(&z) != Z_OK) |
| return false; |
| bool ret = false; |
| int r = inflate(&z, Z_FINISH); |
| if (r != Z_STREAM_END) |
| goto err; |
| if (z.avail_in || z.avail_out) |
| goto err; |
| ret = true; |
| |
| err: |
| inflateEnd(&z); |
| return ret; |
| } |
| |
| // CRLSet format: |
| // |
| // uint16le header_len |
| // byte[header_len] header_bytes |
| // repeated { |
| // byte[32] parent_spki_sha256 |
| // uint32le num_serials |
| // [num_serials] { |
| // uint8 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): if this is a delta update (see below), then this |
| // contains the sequence number of the base CRLSet. |
| // Sequence (int32): the monotonic sequence number of this CRL set. |
| // |
| // A delta CRLSet is similar to a CRLSet: |
| // |
| // struct CompressedChanges { |
| // uint32le uncompressed_size |
| // uint32le compressed_size |
| // byte[compressed_size] zlib_data |
| // } |
| // |
| // uint16le header_len |
| // byte[header_len] header_bytes |
| // CompressedChanges crl_changes |
| // [crl_changes.uncompressed_size] { |
| // switch (crl_changes[i]) { |
| // case 0: |
| // // CRL is the same |
| // case 1: |
| // // New CRL inserted |
| // // See CRL structure from the non-delta format |
| // case 2: |
| // // CRL deleted |
| // case 3: |
| // // CRL changed |
| // CompressedChanges serials_changes |
| // [serials_changes.uncompressed_size] { |
| // switch (serials_changes[i]) { |
| // case 0: |
| // // the serial is the same |
| // case 1: |
| // // serial inserted |
| // uint8 serial_length |
| // byte[serial_length] serial |
| // case 2: |
| // // serial deleted |
| // } |
| // } |
| // } |
| // } |
| // |
| // A delta CRLSet applies to a specific CRL set as given in the |
| // header's "DeltaFrom" value. The delta describes the changes to each CRL |
| // in turn with a zlib compressed array of options: either the CRL is the same, |
| // a new CRL is inserted, the CRL is deleted or the CRL is updated. In the case |
| // of an update, the serials in the CRL are considered in the same fashion |
| // except there is no delta update of a serial number: they are either |
| // inserted, deleted or left the same. |
| |
| // 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. |
| static base::DictionaryValue* ReadHeader(base::StringPiece* data) { |
| if (data->size() < 2) |
| return NULL; |
| uint16 header_len; |
| memcpy(&header_len, data->data(), 2); // assumes little-endian. |
| data->remove_prefix(2); |
| |
| if (data->size() < header_len) |
| return NULL; |
| |
| const base::StringPiece header_bytes(data->data(), header_len); |
| data->remove_prefix(header_len); |
| |
| scoped_ptr<base::Value> header(base::JSONReader::Read( |
| header_bytes, base::JSON_ALLOW_TRAILING_COMMAS)); |
| if (header.get() == NULL) |
| return NULL; |
| |
| if (!header->IsType(base::Value::TYPE_DICTIONARY)) |
| return NULL; |
| return reinterpret_cast<base::DictionaryValue*>(header.release()); |
| } |
| |
| // kCurrentFileVersion is the version of the CRLSet file format that we |
| // currently implement. |
| static const int kCurrentFileVersion = 0; |
| |
| static 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); |
| |
| if (data->size() < sizeof(uint32)) |
| return false; |
| uint32 num_serials; |
| memcpy(&num_serials, data->data(), sizeof(uint32)); // assumes little endian |
| if (num_serials > 32 * 1024 * 1024) // Sanity check. |
| return false; |
| |
| out_serials->reserve(num_serials); |
| data->remove_prefix(sizeof(uint32)); |
| |
| for (uint32 i = 0; i < num_serials; ++i) { |
| if (data->size() < sizeof(uint8)) |
| return false; |
| |
| uint8 serial_length = data->data()[0]; |
| data->remove_prefix(sizeof(uint8)); |
| |
| 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; |
| } |
| |
| // static |
| bool CRLSetStorage::CopyBlockedSPKIsFromHeader( |
| CRLSet* crl_set, |
| base::DictionaryValue* header_dict) { |
| base::ListValue* blocked_spkis_list = NULL; |
| if (!header_dict->GetList("BlockedSPKIs", &blocked_spkis_list)) { |
| // BlockedSPKIs is optional, so it's fine if we don't find it. |
| return true; |
| } |
| |
| crl_set->blocked_spkis_.clear(); |
| crl_set->blocked_spkis_.reserve(blocked_spkis_list->GetSize()); |
| |
| std::string spki_sha256_base64; |
| |
| for (size_t i = 0; i < blocked_spkis_list->GetSize(); ++i) { |
| spki_sha256_base64.clear(); |
| |
| if (!blocked_spkis_list->GetString(i, &spki_sha256_base64)) |
| return false; |
| |
| crl_set->blocked_spkis_.push_back(std::string()); |
| if (!base::Base64Decode(spki_sha256_base64, |
| &crl_set->blocked_spkis_.back())) { |
| crl_set->blocked_spkis_.pop_back(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| // kMaxUncompressedChangesLength is the largest changes array that we'll |
| // accept. This bounds the number of CRLs in the CRLSet as well as the number |
| // of serial numbers in a given CRL. |
| static const unsigned kMaxUncompressedChangesLength = 1024 * 1024; |
| |
| static bool ReadChanges(base::StringPiece* data, |
| std::vector<uint8>* out_changes) { |
| uint32 uncompressed_size, compressed_size; |
| if (data->size() < 2 * sizeof(uint32)) |
| return false; |
| // assumes little endian. |
| memcpy(&uncompressed_size, data->data(), sizeof(uint32)); |
| data->remove_prefix(4); |
| memcpy(&compressed_size, data->data(), sizeof(uint32)); |
| data->remove_prefix(4); |
| |
| if (uncompressed_size > kMaxUncompressedChangesLength) |
| return false; |
| if (data->size() < compressed_size) |
| return false; |
| |
| out_changes->clear(); |
| if (uncompressed_size == 0) |
| return true; |
| |
| out_changes->resize(uncompressed_size); |
| base::StringPiece compressed(data->data(), compressed_size); |
| data->remove_prefix(compressed_size); |
| return DecompressZlib(&(*out_changes)[0], uncompressed_size, compressed); |
| } |
| |
| // These are the range coder symbols used in delta updates. |
| enum { |
| SYMBOL_SAME = 0, |
| SYMBOL_INSERT = 1, |
| SYMBOL_DELETE = 2, |
| SYMBOL_CHANGED = 3, |
| }; |
| |
| static bool ReadDeltaCRL(base::StringPiece* data, |
| const std::vector<std::string>& old_serials, |
| std::vector<std::string>* out_serials) { |
| std::vector<uint8> changes; |
| if (!ReadChanges(data, &changes)) |
| return false; |
| |
| size_t i = 0; |
| for (std::vector<uint8>::const_iterator k = changes.begin(); |
| k != changes.end(); ++k) { |
| if (*k == SYMBOL_SAME) { |
| if (i >= old_serials.size()) |
| return false; |
| out_serials->push_back(old_serials[i]); |
| i++; |
| } else if (*k == SYMBOL_INSERT) { |
| uint8 serial_length; |
| if (data->size() < sizeof(uint8)) |
| return false; |
| memcpy(&serial_length, data->data(), sizeof(uint8)); |
| data->remove_prefix(sizeof(uint8)); |
| |
| if (data->size() < serial_length) |
| return false; |
| const std::string serial(data->data(), serial_length); |
| data->remove_prefix(serial_length); |
| |
| out_serials->push_back(serial); |
| } else if (*k == SYMBOL_DELETE) { |
| if (i >= old_serials.size()) |
| return false; |
| i++; |
| } else { |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| if (i != old_serials.size()) |
| return false; |
| return true; |
| } |
| |
| // static |
| bool CRLSetStorage::Parse(base::StringPiece data, |
| scoped_refptr<CRLSet>* out_crl_set) { |
| TRACE_EVENT0("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 |
| |
| scoped_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>(sequence); |
| crl_set->not_after_ = static_cast<uint64>(not_after); |
| crl_set->crls_.reserve(64); // Value observed experimentally. |
| |
| for (size_t crl_index = 0; !data.empty(); crl_index++) { |
| // Speculatively push back a pair and pass it to ReadCRL() to avoid |
| // unnecessary copies. |
| crl_set->crls_.push_back( |
| std::make_pair(std::string(), std::vector<std::string>())); |
| std::pair<std::string, std::vector<std::string> >* const back_pair = |
| &crl_set->crls_.back(); |
| |
| if (!ReadCRL(&data, &back_pair->first, &back_pair->second)) { |
| // Undo the speculative push_back() performed above. |
| crl_set->crls_.pop_back(); |
| return false; |
| } |
| |
| crl_set->crls_index_by_issuer_[back_pair->first] = crl_index; |
| } |
| |
| if (!CopyBlockedSPKIsFromHeader(crl_set.get(), header_dict.get())) |
| return false; |
| |
| *out_crl_set = crl_set; |
| return true; |
| } |
| |
| // static |
| bool CRLSetStorage::ApplyDelta(const CRLSet* in_crl_set, |
| const base::StringPiece& delta_bytes, |
| scoped_refptr<CRLSet>* out_crl_set) { |
| base::StringPiece data(delta_bytes); |
| scoped_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 != "CRLSetDelta") |
| return false; |
| |
| int version; |
| if (!header_dict->GetInteger("Version", &version) || |
| version != kCurrentFileVersion) { |
| return false; |
| } |
| |
| int sequence, delta_from; |
| if (!header_dict->GetInteger("Sequence", &sequence) || |
| !header_dict->GetInteger("DeltaFrom", &delta_from) || |
| delta_from < 0 || |
| static_cast<uint32>(delta_from) != in_crl_set->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>(sequence); |
| crl_set->not_after_ = static_cast<uint64>(not_after); |
| |
| if (!CopyBlockedSPKIsFromHeader(crl_set.get(), header_dict.get())) |
| return false; |
| |
| std::vector<uint8> crl_changes; |
| |
| if (!ReadChanges(&data, &crl_changes)) |
| return false; |
| |
| size_t i = 0, j = 0; |
| for (std::vector<uint8>::const_iterator k = crl_changes.begin(); |
| k != crl_changes.end(); ++k) { |
| if (*k == SYMBOL_SAME) { |
| if (i >= in_crl_set->crls_.size()) |
| return false; |
| crl_set->crls_.push_back(in_crl_set->crls_[i]); |
| crl_set->crls_index_by_issuer_[in_crl_set->crls_[i].first] = j; |
| i++; |
| j++; |
| } else if (*k == SYMBOL_INSERT) { |
| std::string parent_spki_hash; |
| std::vector<std::string> serials; |
| if (!ReadCRL(&data, &parent_spki_hash, &serials)) |
| return false; |
| crl_set->crls_.push_back(std::make_pair(parent_spki_hash, serials)); |
| crl_set->crls_index_by_issuer_[parent_spki_hash] = j; |
| j++; |
| } else if (*k == SYMBOL_DELETE) { |
| if (i >= in_crl_set->crls_.size()) |
| return false; |
| i++; |
| } else if (*k == SYMBOL_CHANGED) { |
| if (i >= in_crl_set->crls_.size()) |
| return false; |
| std::vector<std::string> serials; |
| if (!ReadDeltaCRL(&data, in_crl_set->crls_[i].second, &serials)) |
| return false; |
| crl_set->crls_.push_back( |
| std::make_pair(in_crl_set->crls_[i].first, serials)); |
| crl_set->crls_index_by_issuer_[in_crl_set->crls_[i].first] = j; |
| i++; |
| j++; |
| } else { |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| if (!data.empty()) |
| return false; |
| if (i != in_crl_set->crls_.size()) |
| return false; |
| |
| *out_crl_set = crl_set; |
| return true; |
| } |
| |
| // static |
| bool CRLSetStorage::GetIsDeltaUpdate(const base::StringPiece& bytes, |
| bool* is_delta) { |
| base::StringPiece data(bytes); |
| scoped_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") { |
| *is_delta = false; |
| } else if (contents == "CRLSetDelta") { |
| *is_delta = true; |
| } else { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| std::string CRLSetStorage::Serialize(const CRLSet* crl_set) { |
| std::string header = base::StringPrintf( |
| "{" |
| "\"Version\":0," |
| "\"ContentType\":\"CRLSet\"," |
| "\"Sequence\":%u," |
| "\"DeltaFrom\":0," |
| "\"NumParents\":%u," |
| "\"BlockedSPKIs\":[", |
| static_cast<unsigned>(crl_set->sequence_), |
| static_cast<unsigned>(crl_set->crls_.size())); |
| |
| for (std::vector<std::string>::const_iterator i = |
| crl_set->blocked_spkis_.begin(); |
| i != crl_set->blocked_spkis_.end(); ++i) { |
| std::string spki_hash_base64; |
| base::Base64Encode(*i, &spki_hash_base64); |
| |
| if (i != crl_set->blocked_spkis_.begin()) |
| header += ","; |
| header += "\"" + spki_hash_base64 + "\""; |
| } |
| header += "]"; |
| if (crl_set->not_after_ != 0) |
| header += base::StringPrintf(",\"NotAfter\":%" PRIu64, crl_set->not_after_); |
| header += "}"; |
| |
| size_t len = 2 /* header len */ + header.size(); |
| |
| for (CRLSet::CRLList::const_iterator i = crl_set->crls_.begin(); |
| i != crl_set->crls_.end(); ++i) { |
| len += i->first.size() + 4 /* num serials */; |
| for (std::vector<std::string>::const_iterator j = i->second.begin(); |
| j != i->second.end(); ++j) { |
| len += 1 /* serial length */ + j->size(); |
| } |
| } |
| |
| std::string ret; |
| uint8_t* out = reinterpret_cast<uint8_t*>( |
| WriteInto(&ret, len + 1 /* to include final NUL */)); |
| size_t off = 0; |
| CHECK(base::IsValueInRangeForNumericType<uint16>(header.size())); |
| out[off++] = static_cast<uint8_t>(header.size()); |
| out[off++] = static_cast<uint8_t>(header.size() >> 8); |
| memcpy(out + off, header.data(), header.size()); |
| off += header.size(); |
| |
| for (CRLSet::CRLList::const_iterator i = crl_set->crls_.begin(); |
| i != crl_set->crls_.end(); ++i) { |
| memcpy(out + off, i->first.data(), i->first.size()); |
| off += i->first.size(); |
| const uint32 num_serials = i->second.size(); |
| memcpy(out + off, &num_serials, sizeof(num_serials)); |
| off += sizeof(num_serials); |
| |
| for (std::vector<std::string>::const_iterator j = i->second.begin(); |
| j != i->second.end(); ++j) { |
| CHECK(base::IsValueInRangeForNumericType<uint8_t>(j->size())); |
| out[off++] = static_cast<uint8_t>(j->size()); |
| memcpy(out + off, j->data(), j->size()); |
| off += j->size(); |
| } |
| } |
| |
| CHECK_EQ(off, len); |
| return ret; |
| } |
| |
| } // namespace net |