Skip to content

Commit

Permalink
net: allow CRLSets to block specific SPKIs.
Browse files Browse the repository at this point in the history
This change allows CRLSets to include a list of blocked SPKI fingerprints,
which may save us doing emergency binary pushes in the future.

It also corrects a bug where the NSS code was passing in the full SPKI rather
than the SHA256 hash.

BUG=none
TEST=net_unittests


Review URL: http://codereview.chromium.org/9149010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@117069 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
agl@chromium.org committed Jan 10, 2012
1 parent 0ca5bb5 commit da7582b
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 51 deletions.
6 changes: 3 additions & 3 deletions crypto/sha2.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// 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.

Expand All @@ -10,13 +10,13 @@

namespace crypto {

void SHA256HashString(const std::string& str, void* output, size_t len) {
void SHA256HashString(const base::StringPiece& str, void* output, size_t len) {
scoped_ptr<SecureHash> ctx(SecureHash::Create(SecureHash::SHA256));
ctx->Update(str.data(), str.length());
ctx->Finish(output, len);
}

std::string SHA256HashString(const std::string& str) {
std::string SHA256HashString(const base::StringPiece& str) {
std::string output(kSHA256Length, 0);
SHA256HashString(str, string_as_array(&output), output.size());
return output;
Expand Down
7 changes: 4 additions & 3 deletions crypto/sha2.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// 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.

Expand All @@ -8,6 +8,7 @@

#include <string>

#include "base/string_piece.h"
#include "crypto/crypto_export.h"

namespace crypto {
Expand All @@ -21,12 +22,12 @@ static const size_t kSHA256Length = 32; // Length in bytes of a SHA-256 hash.
// Computes the SHA-256 hash of the input string 'str' and stores the first
// 'len' bytes of the hash in the output buffer 'output'. If 'len' > 32,
// only 32 bytes (the full hash) are stored in the 'output' buffer.
CRYPTO_EXPORT void SHA256HashString(const std::string& str,
CRYPTO_EXPORT void SHA256HashString(const base::StringPiece& str,
void* output, size_t len);

// Convenience version of the above that returns the result in a 32-byte
// string.
CRYPTO_EXPORT std::string SHA256HashString(const std::string& str);
CRYPTO_EXPORT std::string SHA256HashString(const base::StringPiece& str);

} // namespace crypto

Expand Down
76 changes: 63 additions & 13 deletions net/base/crl_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ CRLSet::~CRLSet() {
// 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 DictionaryValue* ReadHeader(base::StringPiece* data) {
static base::DictionaryValue* ReadHeader(base::StringPiece* data) {
if (data->size() < 2)
return NULL;
uint16 header_len;
Expand All @@ -143,7 +143,7 @@ static DictionaryValue* ReadHeader(base::StringPiece* data) {

if (!header->IsType(Value::TYPE_DICTIONARY))
return NULL;
return reinterpret_cast<DictionaryValue*>(header.release());
return reinterpret_cast<base::DictionaryValue*>(header.release());
}

// kCurrentFileVersion is the version of the CRLSet file format that we
Expand All @@ -163,7 +163,7 @@ static bool ReadCRL(base::StringPiece* data, std::string* out_parent_spki_hash,
memcpy(&num_serials, data->data(), sizeof(uint32)); // assumes little endian
data->remove_prefix(sizeof(uint32));

for (uint32 i = 0; i < num_serials; i++) {
for (uint32 i = 0; i < num_serials; ++i) {
uint8 serial_length;
if (data->size() < sizeof(uint8))
return false;
Expand All @@ -180,6 +180,27 @@ static bool ReadCRL(base::StringPiece* data, std::string* out_parent_spki_hash,
return true;
}

bool CRLSet::CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict) {
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;
}

blocked_spkis_.clear();

for (size_t i = 0; i < blocked_spkis_list->GetSize(); ++i) {
std::string spki_sha256_base64, spki_sha256;
if (!blocked_spkis_list->GetString(i, &spki_sha256_base64))
return false;
if (!base::Base64Decode(spki_sha256_base64, &spki_sha256))
return false;
blocked_spkis_.push_back(spki_sha256);
}

return true;
}

// static
bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) {
// Other parts of Chrome assume that we're little endian, so we don't lose
Expand All @@ -192,7 +213,7 @@ bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) {
#error assumes little endian
#endif

scoped_ptr<DictionaryValue> header_dict(ReadHeader(&data));
scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
if (!header_dict.get())
return false;

Expand Down Expand Up @@ -225,6 +246,9 @@ bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) {
crl_set->crls_index_by_issuer_[parent_spki_sha256] = crl_index;
}

if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get()))
return false;

*out_crl_set = crl_set;
return true;
}
Expand Down Expand Up @@ -314,7 +338,7 @@ bool ReadDeltaCRL(base::StringPiece* data,
bool CRLSet::ApplyDelta(const base::StringPiece& in_data,
scoped_refptr<CRLSet>* out_crl_set) {
base::StringPiece data(in_data);
scoped_ptr<DictionaryValue> header_dict(ReadHeader(&data));
scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
if (!header_dict.get())
return false;

Expand All @@ -341,6 +365,9 @@ bool CRLSet::ApplyDelta(const base::StringPiece& in_data,
scoped_refptr<CRLSet> crl_set(new CRLSet);
crl_set->sequence_ = static_cast<uint32>(sequence);

if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get()))
return false;

std::vector<uint8> crl_changes;

if (!ReadChanges(&data, &crl_changes))
Expand Down Expand Up @@ -397,7 +424,7 @@ bool CRLSet::ApplyDelta(const base::StringPiece& in_data,
bool CRLSet::GetIsDeltaUpdate(const base::StringPiece& in_data,
bool *is_delta) {
base::StringPiece data(in_data);
scoped_ptr<DictionaryValue> header_dict(ReadHeader(&data));
scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
if (!header_dict.get())
return false;

Expand All @@ -423,17 +450,28 @@ std::string CRLSet::Serialize() const {
"\"ContentType\":\"CRLSet\","
"\"Sequence\":%u,"
"\"DeltaFrom\":0,"
"\"NumParents\":%u"
"}",
"\"NumParents\":%u,"
"\"BlockedSPKIs\":[",
static_cast<unsigned>(sequence_),
static_cast<unsigned>(crls_.size()));

for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin();
i != blocked_spkis_.end(); ++i) {
std::string spki_hash_base64;
base::Base64Encode(*i, &spki_hash_base64);

if (i != blocked_spkis_.begin())
header += ",";
header += "\"" + spki_hash_base64 + "\"";
}
header += "]}";

size_t len = 2 /* header len */ + header.size();

for (CRLList::const_iterator i = crls_.begin(); i != 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++) {
j != i->second.end(); ++j) {
len += 1 /* serial length */ + j->size();
}
}
Expand All @@ -454,7 +492,7 @@ std::string CRLSet::Serialize() const {
off += sizeof(num_serials);

for (std::vector<std::string>::const_iterator j = i->second.begin();
j != i->second.end(); j++) {
j != i->second.end(); ++j) {
out[off++] = j->size();
memcpy(out + off, j->data(), j->size());
off += j->size();
Expand All @@ -465,9 +503,21 @@ std::string CRLSet::Serialize() const {
return ret;
}

CRLSet::Result CRLSet::CheckCertificate(
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::CheckSerial(
const base::StringPiece& serial_number,
const base::StringPiece& parent_spki) const {
const base::StringPiece& issuer_spki_hash) const {
base::StringPiece serial(serial_number);

if (!serial.empty() && (serial[0] & 0x80) != 0) {
Expand All @@ -481,7 +531,7 @@ CRLSet::Result CRLSet::CheckCertificate(
serial.remove_prefix(1);

std::map<std::string, size_t>::const_iterator i =
crls_index_by_issuer_.find(parent_spki.as_string());
crls_index_by_issuer_.find(issuer_spki_hash.as_string());
if (i == crls_index_by_issuer_.end())
return UNKNOWN;
const std::vector<std::string>& serials = crls_[i->second].second;
Expand Down
19 changes: 16 additions & 3 deletions net/base/crl_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include "base/time.h"
#include "net/base/net_export.h"

namespace base {
class DictionaryValue;
}

namespace net {

// A CRLSet is a structure that lists the serial numbers of revoked
Expand All @@ -37,12 +41,16 @@ class NET_EXPORT CRLSet : public base::RefCountedThreadSafe<CRLSet> {
static bool Parse(base::StringPiece data,
scoped_refptr<CRLSet>* out_crl_set);

// CheckCertificate returns the information contained in the set for a given
// CheckSPKI checks whether the given SPKI has been listed as blocked.
// spki_hash: the SHA256 of the SubjectPublicKeyInfo of the certificate.
Result CheckSPKI(const base::StringPiece& spki_hash) const;

// CheckSerial returns the information contained in the set for a given
// certificate:
// serial_number: the serial number of the certificate
// issuer_spki_hash: the SHA256 of the SubjectPublicKeyInfo of the CRL
// signer
Result CheckCertificate(
Result CheckSerial(
const base::StringPiece& serial_number,
const base::StringPiece& issuer_spki_hash) const;

Expand Down Expand Up @@ -78,7 +86,9 @@ class NET_EXPORT CRLSet : public base::RefCountedThreadSafe<CRLSet> {
private:
CRLSet();

static CRLSet* CRLSetFromHeader(base::StringPiece header);
// CopyBlockedSPKIsFromHeader sets |blocked_spkis_| to the list of values
// from "BlockedSPKIs" in |header_dict|.
bool CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict);

uint32 sequence_;
CRLList crls_;
Expand All @@ -87,6 +97,9 @@ class NET_EXPORT CRLSet : public base::RefCountedThreadSafe<CRLSet> {
// and |crls_index_by_issuer_| because, when applying a delta update, we need
// to identify a CRL by index.
std::map<std::string, size_t> crls_index_by_issuer_;
// blocked_spkis_ contains the SHA256 hashes of SPKIs which are to be blocked
// no matter where in a certificate chain they might appear.
std::vector<std::string> blocked_spkis_;
};

} // namespace net
Expand Down
71 changes: 53 additions & 18 deletions net/base/crl_set_unittest.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// 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.

Expand All @@ -8,26 +8,28 @@
// These data blocks were generated using a lot of code that is still in
// development. For now, if you need to update them, you have to contact agl.
static const uint8 kGIACRLSet[] = {
0x4e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
0x31, 0x7d, 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae, 0x57, 0xfe, 0x02, 0x0c, 0xb2,
0xb7, 0x4f, 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7, 0x08, 0xaf, 0xc9, 0xe7, 0x32,
0xac, 0xe2, 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05, 0x0d, 0x00, 0x00, 0x00, 0x0a,
0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0e,
0x37, 0x06, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb1, 0x0a, 0x16, 0x25, 0x42, 0x54,
0x00, 0x03, 0x00, 0x00, 0x14, 0x51, 0x0a, 0x16, 0x69, 0xd1, 0xd7, 0x00, 0x03,
0x00, 0x00, 0x14, 0x52, 0x0a, 0x16, 0x70, 0x8c, 0x22, 0x00, 0x03, 0x00, 0x00,
0x14, 0x53, 0x0a, 0x16, 0x71, 0x31, 0x2c, 0x00, 0x03, 0x00, 0x00, 0x14, 0x54,
0x0a, 0x16, 0x7d, 0x75, 0x9d, 0x00, 0x03, 0x00, 0x00, 0x14, 0x55, 0x0a, 0x1f,
0xee, 0xf9, 0x49, 0x00, 0x03, 0x00, 0x00, 0x23, 0xae, 0x0a, 0x1f, 0xfc, 0xd1,
0x89, 0x00, 0x03, 0x00, 0x00, 0x23, 0xaf, 0x0a, 0x61, 0xdd, 0xc7, 0x48, 0x00,
0x03, 0x00, 0x00, 0x18, 0x0e, 0x0a, 0x61, 0xe6, 0x12, 0x64, 0x00, 0x03, 0x00,
0x00, 0x18, 0x0f, 0x0a, 0x61, 0xe9, 0x46, 0x56, 0x00, 0x03, 0x00, 0x00, 0x18,
0x10, 0x0a, 0x64, 0x63, 0x49, 0xd2, 0x00, 0x03, 0x00, 0x00, 0x1d, 0x77,
0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae,
0x57, 0xfe, 0x02, 0x0c, 0xb2, 0xb7, 0x4f, 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7,
0x08, 0xaf, 0xc9, 0xe7, 0x32, 0xac, 0xe2, 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05,
0x0d, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00,
0x23, 0xb0, 0x0a, 0x10, 0x0e, 0x37, 0x06, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb1,
0x0a, 0x16, 0x25, 0x42, 0x54, 0x00, 0x03, 0x00, 0x00, 0x14, 0x51, 0x0a, 0x16,
0x69, 0xd1, 0xd7, 0x00, 0x03, 0x00, 0x00, 0x14, 0x52, 0x0a, 0x16, 0x70, 0x8c,
0x22, 0x00, 0x03, 0x00, 0x00, 0x14, 0x53, 0x0a, 0x16, 0x71, 0x31, 0x2c, 0x00,
0x03, 0x00, 0x00, 0x14, 0x54, 0x0a, 0x16, 0x7d, 0x75, 0x9d, 0x00, 0x03, 0x00,
0x00, 0x14, 0x55, 0x0a, 0x1f, 0xee, 0xf9, 0x49, 0x00, 0x03, 0x00, 0x00, 0x23,
0xae, 0x0a, 0x1f, 0xfc, 0xd1, 0x89, 0x00, 0x03, 0x00, 0x00, 0x23, 0xaf, 0x0a,
0x61, 0xdd, 0xc7, 0x48, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0e, 0x0a, 0x61, 0xe6,
0x12, 0x64, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0f, 0x0a, 0x61, 0xe9, 0x46, 0x56,
0x00, 0x03, 0x00, 0x00, 0x18, 0x10, 0x0a, 0x64, 0x63, 0x49, 0xd2, 0x00, 0x03,
0x00, 0x00, 0x1d, 0x77,
};

static const uint8 kNoopDeltaCRL[] = {
Expand Down Expand Up @@ -145,6 +147,21 @@ static const uint8 kUpdateSerialsDelta[] = {
0xb0,
};

static const uint8 kBlockedSPKICRLSet[] = {
0x8e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
0x49, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x34, 0x37, 0x44, 0x45, 0x51, 0x70, 0x6a,
0x38, 0x48, 0x42, 0x53, 0x61, 0x2b, 0x2f, 0x54, 0x49, 0x6d, 0x57, 0x2b, 0x35,
0x4a, 0x43, 0x65, 0x75, 0x51, 0x65, 0x52, 0x6b, 0x6d, 0x35, 0x4e, 0x4d, 0x70,
0x4a, 0x57, 0x5a, 0x47, 0x33, 0x68, 0x53, 0x75, 0x46, 0x55, 0x3d, 0x22, 0x5d,
0x7d,
};

// kGIASPKISHA256 is the SHA256 digest the Google Internet Authority's
// SubjectPublicKeyInfo.
static const uint8 kGIASPKISHA256[32] = {
Expand Down Expand Up @@ -173,10 +190,10 @@ TEST(CRLSetTest, Parse) {
const std::string gia_spki_hash(
reinterpret_cast<const char*>(kGIASPKISHA256),
sizeof(kGIASPKISHA256));
EXPECT_EQ(net::CRLSet::REVOKED, set->CheckCertificate(
EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSerial(
std::string("\x16\x7D\x75\x9D\x00\x03\x00\x00\x14\x55", 10),
gia_spki_hash));
EXPECT_EQ(net::CRLSet::GOOD, set->CheckCertificate(
EXPECT_EQ(net::CRLSet::GOOD, set->CheckSerial(
std::string("\x47\x54\x3E\x79\x00\x03\x00\x00\x14\xF5", 10),
gia_spki_hash));
}
Expand Down Expand Up @@ -263,3 +280,21 @@ TEST(CRLSetTest, UpdateSerialsDelta) {
const std::vector<std::string>& serials = crls[0].second;
EXPECT_EQ(45u, serials.size());
}

TEST(CRLSetTest, BlockedSPKIs) {
base::StringPiece s(reinterpret_cast<const char*>(kBlockedSPKICRLSet),
sizeof(kBlockedSPKICRLSet));
scoped_refptr<net::CRLSet> set;
EXPECT_TRUE(net::CRLSet::Parse(s, &set));
ASSERT_TRUE(set.get() != NULL);

const uint8 spki_hash[] = {
227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36,
39, 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85,
0,
};

EXPECT_EQ(net::CRLSet::GOOD, set->CheckSPKI(""));
EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSPKI(
reinterpret_cast<const char*>(spki_hash)));
}
Loading

0 comments on commit da7582b

Please sign in to comment.