| // Copyright (c) 2011 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/base/dnssec_chain_verifier.h" |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/sha1.h" |
| #include "base/string_util.h" |
| #include "crypto/sha2.h" |
| #include "net/base/dns_util.h" |
| #include "net/base/dnssec_keyset.h" |
| |
| // We don't have a location for the spec yet, so we'll include it here until it |
| // finds a better home. |
| |
| /* |
| When connecting to a host www.example.com, www.example.com may present a certificate which includes a DNSSEC chain embedded in it. The aim of the embedded chain is to prove that the fingerprint of the public key is valid DNSSEC data. This is achieved by proving a CERT record for the target domain. |
| |
| Initially, the target domain is constructed by prepending _ssl. For example, the initial target domain for www.example.com is _ssl.www.example.com. |
| |
| A DNSSEC chain verifier can be in one of two states: entering a zone, or within a zone. Initially, the verifier is entering the root zone. |
| |
| When entering a zone, the verifier reads the following structure: |
| |
| uint8 entryKey |
| uint16 signature length: |
| // See RRSIG RDATA in RFC 4043 for details |
| uint8 algorithm |
| uint8 labels |
| uint32 ttl |
| uint32 expires |
| uint32 begins |
| uint16 keyid |
| []byte signature |
| uint8 numKeys |
| // for each key: |
| uint16 key length: |
| []byte DNSKEY RDATA |
| |
| |entryKey| indexes the array of DNSKEYs and MUST be less than |numKeys|. The indexed DNSKEY MUST be a key that the verifier trusts, either because it's the long-term root key, or because of a previously presented DS signature. |
| |
| If only a trusted key is needed within this zone, then the signature length MAY be zero. In which case, |entryKey| MUST be 0 and |numKeys| MUST be 1. |
| |
| After processing this data, the verifier trusts one or more keys for this zone. |
| |
| When within a zone, the verifier reads the following structure: |
| |
| dnsName name |
| uint16 RRtype |
| |
| |name| is in DNS format (a series of 8-bit, length prefixed strings). No DNS name compression is permitted. |
| |
| |name| must be closer to the current target domain than the current zone. Here, 'closer' is defined as a greater number of matching labels when comparing right to left. |
| |
| |RRtype| may be either DS, CERT or CNAME: |
| |
| DS: this indicates a zone transition to a new zone named |name|. The verifier reads the following structure: |
| uint16 signature length: |
| ... (see above for the signature structure) |
| uint8 num_ds |
| // for each DS: |
| uint8 digest_type |
| uint16 length |
| []byte DS DATA |
| |
| The verifier is now entering the named zone. It reads ahead and extracts the entry key from the zone entry data and synthisises a DS record for the given digest type and verifies the signature. It then enters the next zone. |
| |
| |
| CERT: |name| MUST match the target domain. The verifier reads the following structure: |
| uint16 signature length: |
| ... (see above for the signature structure) |
| []byte CERT RDATA |
| |
| (The format of the CERT RDATA isn't specified here, but the verifier must be able to extract a public key fingerprint in order to validate the original certificate.) |
| |
| This terminates the verification. There MUST NOT be any more data in the chain. |
| |
| |
| CNAME: |name| MUST match the target domain. The verifier reads the following structure: |
| uint16 signature length: |
| ... (see above for the signature structure) |
| []byte CNAME RDATA |
| |
| This replaces the target domain with a new domain. The new domain is the target of the CNAME with _ssl prepended. The verifier is now in the zone that is the greatest common ancestor of the old and new target domains. (For example, when switching from _ssl.www.example.com to _ssl.www.example2.com, the verifier is now in com.) |
| |
| |
| Example for www.google.com: |
| |
| The target domain is www.google.com. |
| |
| The verifier enters ., it already trusts the long-term root key and both root keys are presented in order to extend the trust to the smaller root key. |
| |
| A DS signature is presented for .com. The verifier is now entering .com. |
| |
| All four .com keys are presented. The verifier is now in .com. |
| |
| A DS signature is presented for google.com. The verifier is now entering google.com |
| |
| As google.com contains only a single DNSKEY, it is included without a signature. The verifier is now in google.com. |
| |
| A CNAME is presented for www.google.com pointing to www.l.google.com. The target domain is now www.l.google.com. The verifier is now in google.com. |
| |
| A DS signature is presented for l.google.com. The verifier is now entering l.google.com. |
| |
| As l.google.com contains only a single DNSKEY, it is included without a signature. The verifier is now in l.google.com. |
| |
| A CERT record is presented for www.l.google.com. The verification is complete. |
| */ |
| |
| namespace { |
| |
| // This is the 2048-bit DNS root key: http://www.iana.org/dnssec |
| // 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 |
| const unsigned char kRootKey[] = { |
| 0x01, 0x01, 0x03, 0x08, 0x03, 0x01, 0x00, 0x01, 0xa8, 0x00, 0x20, 0xa9, 0x55, |
| 0x66, 0xba, 0x42, 0xe8, 0x86, 0xbb, 0x80, 0x4c, 0xda, 0x84, 0xe4, 0x7e, 0xf5, |
| 0x6d, 0xbd, 0x7a, 0xec, 0x61, 0x26, 0x15, 0x55, 0x2c, 0xec, 0x90, 0x6d, 0x21, |
| 0x16, 0xd0, 0xef, 0x20, 0x70, 0x28, 0xc5, 0x15, 0x54, 0x14, 0x4d, 0xfe, 0xaf, |
| 0xe7, 0xc7, 0xcb, 0x8f, 0x00, 0x5d, 0xd1, 0x82, 0x34, 0x13, 0x3a, 0xc0, 0x71, |
| 0x0a, 0x81, 0x18, 0x2c, 0xe1, 0xfd, 0x14, 0xad, 0x22, 0x83, 0xbc, 0x83, 0x43, |
| 0x5f, 0x9d, 0xf2, 0xf6, 0x31, 0x32, 0x51, 0x93, 0x1a, 0x17, 0x6d, 0xf0, 0xda, |
| 0x51, 0xe5, 0x4f, 0x42, 0xe6, 0x04, 0x86, 0x0d, 0xfb, 0x35, 0x95, 0x80, 0x25, |
| 0x0f, 0x55, 0x9c, 0xc5, 0x43, 0xc4, 0xff, 0xd5, 0x1c, 0xbe, 0x3d, 0xe8, 0xcf, |
| 0xd0, 0x67, 0x19, 0x23, 0x7f, 0x9f, 0xc4, 0x7e, 0xe7, 0x29, 0xda, 0x06, 0x83, |
| 0x5f, 0xa4, 0x52, 0xe8, 0x25, 0xe9, 0xa1, 0x8e, 0xbc, 0x2e, 0xcb, 0xcf, 0x56, |
| 0x34, 0x74, 0x65, 0x2c, 0x33, 0xcf, 0x56, 0xa9, 0x03, 0x3b, 0xcd, 0xf5, 0xd9, |
| 0x73, 0x12, 0x17, 0x97, 0xec, 0x80, 0x89, 0x04, 0x1b, 0x6e, 0x03, 0xa1, 0xb7, |
| 0x2d, 0x0a, 0x73, 0x5b, 0x98, 0x4e, 0x03, 0x68, 0x73, 0x09, 0x33, 0x23, 0x24, |
| 0xf2, 0x7c, 0x2d, 0xba, 0x85, 0xe9, 0xdb, 0x15, 0xe8, 0x3a, 0x01, 0x43, 0x38, |
| 0x2e, 0x97, 0x4b, 0x06, 0x21, 0xc1, 0x8e, 0x62, 0x5e, 0xce, 0xc9, 0x07, 0x57, |
| 0x7d, 0x9e, 0x7b, 0xad, 0xe9, 0x52, 0x41, 0xa8, 0x1e, 0xbb, 0xe8, 0xa9, 0x01, |
| 0xd4, 0xd3, 0x27, 0x6e, 0x40, 0xb1, 0x14, 0xc0, 0xa2, 0xe6, 0xfc, 0x38, 0xd1, |
| 0x9c, 0x2e, 0x6a, 0xab, 0x02, 0x64, 0x4b, 0x28, 0x13, 0xf5, 0x75, 0xfc, 0x21, |
| 0x60, 0x1e, 0x0d, 0xee, 0x49, 0xcd, 0x9e, 0xe9, 0x6a, 0x43, 0x10, 0x3e, 0x52, |
| 0x4d, 0x62, 0x87, 0x3d, |
| }; |
| |
| // kRootKeyID is the key id for kRootKey |
| const uint16 kRootKeyID = 19036; |
| |
| // CountLabels returns the number of DNS labels in |a|, which must be in DNS, |
| // length-prefixed form. |
| unsigned CountLabels(base::StringPiece a) { |
| for (unsigned c = 0;; c++) { |
| if (!a.size()) |
| return c; |
| uint8 label_len = a.data()[0]; |
| a.remove_prefix(1); |
| DCHECK_GE(a.size(), label_len); |
| a.remove_prefix(label_len); |
| } |
| } |
| |
| // RemoveLeadingLabel removes the first label from |a|, which must be in DNS, |
| // length-prefixed form. |
| void RemoveLeadingLabel(base::StringPiece* a) { |
| if (!a->size()) |
| return; |
| uint8 label_len = a->data()[0]; |
| a->remove_prefix(1); |
| a->remove_prefix(label_len); |
| } |
| |
| } // namespace |
| |
| namespace net { |
| |
| struct DNSSECChainVerifier::Zone { |
| base::StringPiece name; |
| // The number of consecutive labels which |name| shares with |target_|, |
| // counting right-to-left from the root. |
| unsigned matching_labels; |
| DNSSECKeySet trusted_keys; |
| Zone* prev; |
| }; |
| |
| DNSSECChainVerifier::DNSSECChainVerifier(const std::string& target, |
| const base::StringPiece& chain) |
| : current_zone_(NULL), |
| target_(target), |
| chain_(chain), |
| ignore_timestamps_(false), |
| valid_(false), |
| already_entered_zone_(false), |
| rrtype_(0) { |
| } |
| |
| DNSSECChainVerifier::~DNSSECChainVerifier() { |
| for (std::vector<void*>::iterator |
| i = scratch_pool_.begin(); i != scratch_pool_.end(); i++) { |
| free(*i); |
| } |
| |
| Zone* next; |
| for (Zone* cur = current_zone_; cur; cur = next) { |
| next = cur->prev; |
| delete cur; |
| } |
| } |
| |
| void DNSSECChainVerifier::IgnoreTimestamps() { |
| ignore_timestamps_ = true; |
| } |
| |
| DNSSECChainVerifier::Error DNSSECChainVerifier::Verify() { |
| Error err; |
| |
| err = EnterRoot(); |
| if (err != OK) |
| return err; |
| |
| for (;;) { |
| base::StringPiece next_name; |
| err = LeaveZone(&next_name); |
| if (err != OK) |
| return err; |
| if (valid_) { |
| if (!chain_.empty()) |
| return BAD_DATA; // no trailing data allowed. |
| break; |
| } |
| |
| if (already_entered_zone_) { |
| already_entered_zone_ = false; |
| } else { |
| err = EnterZone(next_name); |
| if (err != OK) |
| return err; |
| } |
| } |
| |
| return OK; |
| } |
| |
| uint16 DNSSECChainVerifier::rrtype() const { |
| DCHECK(valid_); |
| return rrtype_; |
| } |
| |
| const std::vector<base::StringPiece>& DNSSECChainVerifier::rrdatas() const { |
| DCHECK(valid_); |
| return rrdatas_; |
| } |
| |
| // static |
| std::map<std::string, std::string> |
| DNSSECChainVerifier::ParseTLSTXTRecord(base::StringPiece rrdata) { |
| std::map<std::string, std::string> ret; |
| |
| if (rrdata.empty()) |
| return ret; |
| |
| std::string txt; |
| txt.reserve(rrdata.size()); |
| |
| // TXT records are a series of 8-bit length prefixed substrings that we |
| // concatenate into |txt| |
| while (!rrdata.empty()) { |
| unsigned len = rrdata[0]; |
| if (len == 0 || len + 1 > rrdata.size()) |
| return ret; |
| txt.append(rrdata.data() + 1, len); |
| rrdata.remove_prefix(len + 1); |
| } |
| |
| // We append a space to |txt| to make the parsing code, below, cleaner. |
| txt.append(" "); |
| |
| // RECORD = KV (' '+ KV)* |
| // KV = KEY '=' VALUE |
| // KEY = [a-zA-Z0-9]+ |
| // VALUE = [^ \0]* |
| |
| enum State { |
| STATE_KEY, |
| STATE_VALUE, |
| STATE_SPACE, |
| }; |
| |
| State state = STATE_KEY; |
| |
| std::map<std::string, std::string> m; |
| |
| unsigned start = 0; |
| std::string key; |
| |
| for (unsigned i = 0; i < txt.size(); i++) { |
| char c = txt[i]; |
| if (c == 0) |
| return ret; // NUL values are never allowed. |
| |
| switch (state) { |
| case STATE_KEY: |
| if (c == '=') { |
| if (i == start) |
| return ret; // zero length keys are not allowed. |
| key = txt.substr(start, i - start); |
| start = i + 1; |
| state = STATE_VALUE; |
| continue; |
| } |
| if (!IsAsciiAlpha(c) && !IsAsciiDigit(c)) |
| return ret; // invalid key value |
| break; |
| case STATE_VALUE: |
| if (c == ' ') { |
| if (m.find(key) == m.end()) |
| m.insert(make_pair(key, txt.substr(start, i - start))); |
| state = STATE_SPACE; |
| continue; |
| } |
| break; |
| case STATE_SPACE: |
| if (c != ' ') { |
| start = i; |
| i--; |
| state = STATE_KEY; |
| continue; |
| } |
| break; |
| default: |
| NOTREACHED(); |
| return ret; |
| } |
| } |
| |
| if (state != STATE_SPACE) |
| return ret; |
| |
| ret.swap(m); |
| return ret; |
| } |
| |
| // MatchingLabels returns the number of labels which |a| and |b| share, |
| // counting right-to-left from the root. |a| and |b| must be DNS, |
| // length-prefixed names. All names match at the root label, so this always |
| // returns a value >= 1. |
| |
| // static |
| unsigned DNSSECChainVerifier::MatchingLabels(base::StringPiece a, |
| base::StringPiece b) { |
| unsigned c = 0; |
| unsigned a_labels = CountLabels(a); |
| unsigned b_labels = CountLabels(b); |
| |
| while (a_labels > b_labels) { |
| RemoveLeadingLabel(&a); |
| a_labels--; |
| } |
| while (b_labels > a_labels) { |
| RemoveLeadingLabel(&b); |
| b_labels--; |
| } |
| |
| for (;;) { |
| if (!a.size()) { |
| if (!b.size()) |
| return c; |
| return 0; |
| } |
| if (!b.size()) |
| return 0; |
| uint8 a_length = a.data()[0]; |
| a.remove_prefix(1); |
| uint8 b_length = b.data()[0]; |
| b.remove_prefix(1); |
| DCHECK_GE(a.size(), a_length); |
| DCHECK_GE(b.size(), b_length); |
| |
| if (a_length == b_length && memcmp(a.data(), b.data(), a_length) == 0) { |
| c++; |
| } else { |
| c = 0; |
| } |
| |
| a.remove_prefix(a_length); |
| b.remove_prefix(b_length); |
| } |
| } |
| |
| // U8 reads, and removes, a single byte from |chain_| |
| bool DNSSECChainVerifier::U8(uint8* v) { |
| if (chain_.size() < 1) |
| return false; |
| *v = chain_[0]; |
| chain_.remove_prefix(1); |
| return true; |
| } |
| |
| // U16 reads, and removes, a big-endian uint16 from |chain_| |
| bool DNSSECChainVerifier::U16(uint16* v) { |
| if (chain_.size() < 2) |
| return false; |
| const uint8* data = reinterpret_cast<const uint8*>(chain_.data()); |
| *v = static_cast<uint16>(data[0]) << 8 | |
| static_cast<uint16>(data[1]); |
| chain_.remove_prefix(2); |
| return true; |
| } |
| |
| // VariableLength16 reads, and removes, a big-endian, uint16, length-prefixed |
| // chunk from |chain_| |
| bool DNSSECChainVerifier::VariableLength16(base::StringPiece* v) { |
| uint16 length; |
| if (!U16(&length)) |
| return false; |
| if (chain_.size() < length) |
| return false; |
| *v = chain_.substr(0, length); |
| chain_.remove_prefix(length); |
| return true; |
| } |
| |
| // ReadName reads, and removes, an 8-bit length prefixed DNS name from |chain_| |
| bool DNSSECChainVerifier::ReadName(base::StringPiece* v) { |
| base::StringPiece saved = chain_; |
| unsigned length = 0; |
| static const uint8 kMaxDNSLabelLen = 63; |
| |
| for (;;) { |
| if (chain_.size() < 1) |
| return false; |
| uint8 label_len = chain_.data()[0]; |
| chain_.remove_prefix(1); |
| if (label_len > kMaxDNSLabelLen) |
| return false; |
| length += 1 + label_len; |
| |
| if (label_len == 0) |
| break; |
| |
| if (chain_.size() < label_len) |
| return false; |
| chain_.remove_prefix(label_len); |
| } |
| |
| *v = base::StringPiece(saved.data(), length); |
| return true; |
| } |
| |
| // ReadAheadEntryKey returns the entry key when |chain_| is positioned at the |
| // start of a zone. |
| bool DNSSECChainVerifier::ReadAheadEntryKey(base::StringPiece* v) { |
| base::StringPiece saved = chain_; |
| |
| uint8 entry_key; |
| base::StringPiece sig; |
| if (!U8(&entry_key) || |
| !VariableLength16(&sig)) { |
| return false; |
| } |
| |
| if (!ReadAheadKey(v, entry_key)) |
| return false; |
| chain_ = saved; |
| return true; |
| } |
| |
| // ReadAheadKey returns the entry key when |chain_| is positioned at the start |
| // of a list of keys. |
| bool DNSSECChainVerifier::ReadAheadKey(base::StringPiece* v, uint8 entry_key) { |
| base::StringPiece saved = chain_; |
| |
| uint8 num_keys; |
| if (!U8(&num_keys)) |
| return false; |
| |
| for (unsigned i = 0; i < num_keys; i++) { |
| if (!VariableLength16(v)) |
| return false; |
| if (i == entry_key) { |
| chain_ = saved; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DNSSECChainVerifier::ReadDNSKEYs(std::vector<base::StringPiece>* out, |
| bool is_root) { |
| uint8 num_keys; |
| if (!U8(&num_keys)) |
| return false; |
| |
| for (unsigned i = 0; i < num_keys; i++) { |
| base::StringPiece key; |
| if (!VariableLength16(&key)) |
| return false; |
| if (key.empty()) { |
| if (!is_root) |
| return false; |
| key = base::StringPiece(reinterpret_cast<const char*>(kRootKey), |
| sizeof(kRootKey)); |
| } |
| |
| out->push_back(key); |
| } |
| |
| return true; |
| } |
| |
| // DigestKey calculates a DS digest as specified in |
| // http://tools.ietf.org/html/rfc4034#section-5.1.4 |
| // name: the DNS form name of the key |
| // dnskey: the DNSKEY's RRDATA |
| // digest_type: see http://tools.ietf.org/html/rfc4034#appendix-A.2 |
| // keyid: the key's id |
| // algorithm: see http://tools.ietf.org/html/rfc4034#appendix-A.1 |
| bool DNSSECChainVerifier::DigestKey(base::StringPiece* out, |
| const base::StringPiece& name, |
| const base::StringPiece& dnskey, |
| uint8 digest_type, |
| uint16 keyid, |
| uint8 algorithm) { |
| std::string temp; |
| uint8 temp2[crypto::SHA256_LENGTH]; |
| const uint8* digest; |
| unsigned digest_len; |
| |
| std::string input = name.as_string() + dnskey.as_string(); |
| |
| if (digest_type == kDNSSEC_SHA1) { |
| temp = base::SHA1HashString(input); |
| digest = reinterpret_cast<const uint8*>(temp.data()); |
| digest_len = base::SHA1_LENGTH; |
| } else if (digest_type == kDNSSEC_SHA256) { |
| crypto::SHA256HashString(input, temp2, sizeof(temp2)); |
| digest = temp2; |
| digest_len = sizeof(temp2); |
| } else { |
| return false; |
| } |
| |
| uint8* output = static_cast<uint8*>(malloc(4 + digest_len)); |
| scratch_pool_.push_back(output); |
| output[0] = static_cast<uint8>(keyid >> 8); |
| output[1] = static_cast<uint8>(keyid); |
| output[2] = algorithm; |
| output[3] = digest_type; |
| memcpy(output + 4, digest, digest_len); |
| *out = base::StringPiece(reinterpret_cast<char*>(output), 4 + digest_len); |
| return true; |
| } |
| |
| // EnterRoot enters the root zone at the beginning of the chain. This is |
| // special because no DS record lead us here: we have to validate that the |
| // entry key is the DNS root key that we already know and trust. Additionally, |
| // for the root zone only, the keyid of the entry key is prepended to the data. |
| DNSSECChainVerifier::Error DNSSECChainVerifier::EnterRoot() { |
| uint16 root_keyid; |
| |
| if (!U16(&root_keyid)) |
| return BAD_DATA; |
| |
| if (root_keyid != kRootKeyID) |
| return UNKNOWN_ROOT_KEY; |
| |
| base::StringPiece root_key; |
| if (!ReadAheadEntryKey(&root_key)) |
| return BAD_DATA; |
| |
| // If the root key is given then it must match the expected root key exactly. |
| if (root_key.size()) { |
| if (root_key.size() != sizeof(kRootKey) || |
| memcmp(root_key.data(), kRootKey, sizeof(kRootKey))) { |
| return UNKNOWN_ROOT_KEY; |
| } |
| } |
| |
| base::StringPiece root("", 1); |
| return EnterZone(root); |
| } |
| |
| // EnterZone enters a new DNS zone. On entry it's assumed that the entry key |
| // has been validated. |
| DNSSECChainVerifier::Error DNSSECChainVerifier::EnterZone( |
| const base::StringPiece& zone) { |
| Zone* prev = current_zone_; |
| current_zone_ = new Zone; |
| current_zone_->prev = prev; |
| current_zone_->name = zone; |
| current_zone_->matching_labels = MatchingLabels(target_, zone); |
| if (ignore_timestamps_) |
| current_zone_->trusted_keys.IgnoreTimestamps(); |
| |
| uint8 entry_key; |
| base::StringPiece sig; |
| if (!U8(&entry_key) || |
| !VariableLength16(&sig)) { |
| return BAD_DATA; |
| } |
| |
| base::StringPiece key; |
| if (!ReadAheadKey(&key, entry_key)) |
| return BAD_DATA; |
| |
| if (zone.size() == 1 && key.empty()) { |
| // If a key is omitted in the root zone then it's the root key. |
| key = base::StringPiece(reinterpret_cast<const char*>(kRootKey), |
| sizeof(kRootKey)); |
| } |
| if (!current_zone_->trusted_keys.AddKey(key)) |
| return BAD_DATA; |
| |
| std::vector<base::StringPiece> dnskeys; |
| if (!ReadDNSKEYs(&dnskeys, zone.size() == 1)) |
| return BAD_DATA; |
| |
| if (sig.empty()) { |
| // An omitted signature on the keys means that only the entry key is used. |
| if (dnskeys.size() > 1 || entry_key != 0) |
| return BAD_DATA; |
| return OK; |
| } |
| |
| if (!current_zone_->trusted_keys.CheckSignature( |
| zone, zone, sig, kDNS_DNSKEY, dnskeys)) { |
| return BAD_SIGNATURE; |
| } |
| |
| // Add all the keys as trusted. |
| for (unsigned i = 0; i < dnskeys.size(); i++) { |
| if (i == entry_key) |
| continue; |
| current_zone_->trusted_keys.AddKey(dnskeys[i]); |
| } |
| |
| return OK; |
| } |
| |
| // LeaveZone transitions out of the current zone, either by following DS |
| // records to validate the entry key of the next zone, or because the final |
| // resource records are given. |
| DNSSECChainVerifier::Error DNSSECChainVerifier::LeaveZone( |
| base::StringPiece* next_name) { |
| base::StringPiece sig; |
| uint16 rrtype; |
| Error err; |
| |
| if (!ReadName(next_name) || |
| !U16(&rrtype) || |
| !VariableLength16(&sig)) { |
| return BAD_DATA; |
| } |
| |
| std::vector<base::StringPiece> rrdatas; |
| |
| if (rrtype == kDNS_DS) { |
| err = ReadDSSet(&rrdatas, *next_name); |
| } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT) { |
| err = ReadGenericRRs(&rrdatas); |
| } else if (rrtype == kDNS_CNAME) { |
| err = ReadCNAME(&rrdatas); |
| } else { |
| return UNKNOWN_TERMINAL_RRTYPE; |
| } |
| if (err != OK) |
| return err; |
| |
| if (!current_zone_->trusted_keys.CheckSignature( |
| *next_name, current_zone_->name, sig, rrtype, rrdatas)) { |
| return BAD_SIGNATURE; |
| } |
| |
| if (rrtype == kDNS_DS) { |
| // If we are transitioning to another zone then the next zone must be |
| // 'closer' to the target than the current zone. |
| if (MatchingLabels(target_, *next_name) <= current_zone_->matching_labels) |
| return OFF_COURSE; |
| } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT) { |
| // If this is the final entry in the chain then the name must match target_ |
| if (next_name->size() != target_.size() || |
| memcmp(next_name->data(), target_.data(), target_.size())) { |
| return BAD_TARGET; |
| } |
| rrdatas_ = rrdatas; |
| valid_ = true; |
| rrtype_ = rrtype; |
| } else if (rrtype == kDNS_CNAME) { |
| // A CNAME must match the current target. Then we update the current target |
| // and unwind the chain to the closest common ancestor. |
| if (next_name->size() != target_.size() || |
| memcmp(next_name->data(), target_.data(), target_.size())) { |
| return BAD_TARGET; |
| } |
| DCHECK_EQ(1u, rrdatas.size()); |
| target_ = rrdatas[0].as_string(); |
| // We unwind the zones until the current zone is a (non-strict) subset of |
| // the new target. |
| while (MatchingLabels(target_, current_zone_->name) < |
| CountLabels(current_zone_->name)) { |
| Zone* prev = current_zone_->prev; |
| delete current_zone_; |
| current_zone_ = prev; |
| if (!current_zone_) { |
| NOTREACHED(); |
| return BAD_DATA; |
| } |
| } |
| already_entered_zone_ = true; |
| } else { |
| NOTREACHED(); |
| return UNKNOWN_TERMINAL_RRTYPE; |
| } |
| |
| return OK; |
| } |
| |
| // ReadDSSet reads a set of DS records from the chain. DS records which are |
| // omitted are calculated from the entry key of the next zone. |
| DNSSECChainVerifier::Error DNSSECChainVerifier::ReadDSSet( |
| std::vector<base::StringPiece>* rrdatas, |
| const base::StringPiece& next_name) { |
| uint8 num_ds; |
| if (!U8(&num_ds)) |
| return BAD_DATA; |
| scoped_array<uint8> digest_types(new uint8[num_ds]); |
| // lookahead[i] is true iff the i'th DS record was empty and needs to be |
| // computed by hashing the next entry key. |
| scoped_array<bool> lookahead(new bool[num_ds]); |
| rrdatas->resize(num_ds); |
| |
| for (unsigned i = 0; i < num_ds; i++) { |
| uint8 digest_type; |
| base::StringPiece digest; |
| if (!U8(&digest_type) || |
| !VariableLength16(&digest)) { |
| return BAD_DATA; |
| } |
| |
| digest_types[i] = digest_type; |
| lookahead[i] = digest.empty(); |
| if (!digest.empty()) |
| (*rrdatas)[i] = digest; |
| } |
| |
| base::StringPiece next_entry_key; |
| if (!ReadAheadEntryKey(&next_entry_key)) |
| return BAD_DATA; |
| if (next_entry_key.size() < 4) |
| return BAD_DATA; |
| uint16 keyid = DNSSECKeySet::DNSKEYToKeyID(next_entry_key); |
| uint8 algorithm = next_entry_key[3]; |
| |
| bool good = false; |
| for (unsigned i = 0; i < num_ds; i++) { |
| base::StringPiece digest; |
| bool have_digest = false; |
| if (DigestKey(&digest, next_name, next_entry_key, digest_types[i], |
| keyid, algorithm)) { |
| have_digest = true; |
| } |
| |
| if (lookahead[i]) { |
| // If we needed to fill in one of the DS entries, but we can't calculate |
| // that type of digest, then we can't continue. |
| if (!have_digest) |
| return UNKNOWN_DIGEST; |
| (*rrdatas)[i] = digest; |
| good = true; |
| } else { |
| const base::StringPiece& given_digest = (*rrdatas)[i]; |
| if (have_digest && |
| given_digest.size() == digest.size() && |
| memcmp(given_digest.data(), digest.data(), digest.size()) == 0) { |
| good = true; |
| } |
| } |
| } |
| |
| if (!good) { |
| // We didn't calculate or match any of the digests. |
| return NO_DS_LINK; |
| } |
| |
| return OK; |
| } |
| |
| DNSSECChainVerifier::Error DNSSECChainVerifier::ReadGenericRRs( |
| std::vector<base::StringPiece>* rrdatas) { |
| uint8 num_rrs; |
| if (!U8(&num_rrs)) |
| return BAD_DATA; |
| rrdatas->resize(num_rrs); |
| |
| for (unsigned i = 0; i < num_rrs; i++) { |
| base::StringPiece rrdata; |
| if (!VariableLength16(&rrdata)) |
| return BAD_DATA; |
| (*rrdatas)[i] = rrdata; |
| } |
| |
| return OK; |
| } |
| |
| DNSSECChainVerifier::Error DNSSECChainVerifier::ReadCNAME( |
| std::vector<base::StringPiece>* rrdatas) { |
| base::StringPiece name; |
| if (!ReadName(&name)) |
| return BAD_DATA; |
| |
| rrdatas->resize(1); |
| (*rrdatas)[0] = name; |
| return OK; |
| } |
| |
| } // namespace net |