|  | // 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/format_macros.h" | 
|  | #include "base/json/json_reader.h" | 
|  | #include "base/numerics/safe_conversions.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/trace_event/trace_event.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 |