普通文本  |  811行  |  23.85 KB

// 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