diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-02 21:06:54 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-02 21:06:54 +0000 |
commit | bad796d05629858e027778dc1280455926f68f2c (patch) | |
tree | 0b901c0b5515310df7b91119910528c1301c12de /net/base | |
parent | c06c697b737656f912730bdd7d3572b179f7fd5e (diff) | |
download | chromium_src-bad796d05629858e027778dc1280455926f68f2c.zip chromium_src-bad796d05629858e027778dc1280455926f68f2c.tar.gz chromium_src-bad796d05629858e027778dc1280455926f68f2c.tar.bz2 |
net: switch from TXT DNS records to CAA.
The format of the keys-in-DNS record has started to solidify into CAA.
This change starts to switch over to using CAA records. None of this
code is enabled by default in Chrome.
BUG=none
TEST=net_unittests
Review URL: http://codereview.chromium.org/6281012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@87677 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/dns_util.h | 2 | ||||
-rw-r--r-- | net/base/dnssec_chain_verifier.cc | 285 | ||||
-rw-r--r-- | net/base/dnssec_chain_verifier.h | 49 | ||||
-rw-r--r-- | net/base/dnssec_unittest.cc | 345 |
4 files changed, 509 insertions, 172 deletions
diff --git a/net/base/dns_util.h b/net/base/dns_util.h index d70dfd7..6c940c1 100644 --- a/net/base/dns_util.h +++ b/net/base/dns_util.h @@ -43,7 +43,7 @@ static const uint16 kDNS_DS = 43; static const uint16 kDNS_RRSIG = 46; static const uint16 kDNS_DNSKEY = 48; static const uint16 kDNS_ANY = 0xff; -static const uint16 kDNS_CAA = 13172; // temporary, not IANA +static const uint16 kDNS_CAA = 257; static const uint16 kDNS_TESTING = 0xfffe; // in private use area. // http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml diff --git a/net/base/dnssec_chain_verifier.cc b/net/base/dnssec_chain_verifier.cc index e3eeee7..3929ae9 100644 --- a/net/base/dnssec_chain_verifier.cc +++ b/net/base/dnssec_chain_verifier.cc @@ -9,6 +9,7 @@ #include "base/sha1.h" #include "base/string_util.h" #include "crypto/sha2.h" +#include "net/base/asn1_util.h" #include "net/base/dns_util.h" #include "net/base/dnssec_keyset.h" @@ -244,95 +245,6 @@ const std::vector<base::StringPiece>& DNSSECChainVerifier::rrdatas() const { 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 @@ -650,7 +562,7 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::LeaveZone( if (rrtype == kDNS_DS) { err = ReadDSSet(&rrdatas, *next_name); - } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT) { + } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT || rrtype == kDNS_CAA) { err = ReadGenericRRs(&rrdatas); } else if (rrtype == kDNS_CNAME) { err = ReadCNAME(&rrdatas); @@ -670,7 +582,7 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::LeaveZone( // '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) { + } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT || rrtype == kDNS_CAA) { // 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())) { @@ -807,4 +719,195 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::ReadCNAME( return OK; } +struct CAAHashTargetOID { + uint8 length; + uint8 bytes[13]; + DnsCAARecord::Policy::HashTarget value; +}; + +// kCAAHashTargets maps from the DER encoding of a hash target OID to our +// internal enumeration values. +static CAAHashTargetOID kCAAHashTargets[] = { + { 5, "\x06\x03\x55\x04\x24", DnsCAARecord::Policy::USER_CERTIFICATE }, + { 5, "\x06\x03\x55\x04\x25", DnsCAARecord::Policy::CA_CERTIFICATE }, + { 12 , "\x06\x0a\x2b\x06\x01\x04\x01\xd6\x79\x02\x03\x01", + DnsCAARecord::Policy::SUBJECT_PUBLIC_KEY_INFO}, + { 0 }, +}; + +struct CAAHashFunctionOID { + uint8 length; + uint8 bytes[12]; + int value; +}; + +// The values here are taken from NSS's freebl/hasht.h. Sadly, we cannot +// include it because it pulls in #defines that conflict with Chromium headers. +// However, these values are part of NSS's public API and so are stable. +static const int kHashSHA256 = 4; +static const int kHashSHA384 = 5; +static const int kHashSHA512 = 6; + +// kCAAHashFunctions maps from the DER encoding of hash function OIDs to NSS's +// hash function enumeration. +static CAAHashFunctionOID kCAAHashFunctions[] = { + { 11, "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", kHashSHA256 }, + { 11, "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02", kHashSHA384 }, + { 11, "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03", kHashSHA512 }, + // WARNING: if you update this, be sure to update DigestLength too. + { 0 }, +}; + +// DigestLength returns the expected length of the digest for a given hash +// function. +static unsigned DigestLength(int algorithm) { + switch (algorithm) { + case kHashSHA256: + return 32; + case kHashSHA384: + return 48; + case kHashSHA512: + return 64; + default: + CHECK_NE(algorithm, algorithm); + return 0; + } +} + +// ParseEnumeratedOID parses an OID from |i| and sets |*result| to one of the +// values from |values|, depending on the OID found. |i| is then advanced over +// the OID. +// +// If no OID is found then DISCARD is returned, unless |critical| is true, in +// which case |UNKNOWN_CRITICAL| is returned. +template<typename A, typename Enum> +static DnsCAARecord::ParseResult ParseEnumeratedOID(base::StringPiece* i, + bool critical, + const A* values, + Enum* result) { + base::StringPiece element; + if (!asn1::ParseElement(i, asn1::kOID, &element, NULL)) + return DnsCAARecord::SYNTAX_ERROR; + unsigned j; + for (j = 0; values[j].length; j++) { + if (element.size() == values[j].length && + memcmp(element.data(), values[j].bytes, element.size()) == 0) { + *result = values[j].value; + return DnsCAARecord::SUCCESS; + } + } + + if (critical) + return DnsCAARecord::UNKNOWN_CRITICAL; + return DnsCAARecord::DISCARD; +} + +// static +DnsCAARecord::ParseResult DnsCAARecord::Parse( + const std::vector<base::StringPiece>& rrdatas, + DnsCAARecord::Policy* output) { + // see http://tools.ietf.org/html/draft-hallambaker-donotissue-04 + output->authorized_hashes.clear(); + + for (std::vector<base::StringPiece>::const_iterator + ii = rrdatas.begin(); ii != rrdatas.end(); ++ii) { + base::StringPiece i = *ii; + + if (i.size() < 2) + return SYNTAX_ERROR; + const bool critical = (i[0] & 128) != 0; + const bool must_be_zero = (i[0] & 4) != 0; + const bool relying_use = (i[0] & 2) != 0; + const unsigned tag_length = i[1]; + i.remove_prefix(2); + + if (must_be_zero) + return UNKNOWN_CRITICAL; + + if (!relying_use) + continue; + + if (tag_length == 0 || tag_length > i.size()) + return SYNTAX_ERROR; + + const std::string tag(i.data(), tag_length); + i.remove_prefix(tag_length); + + if (tag_length == 4 && LowerCaseEqualsASCII(tag, "auth")) { + Policy::Hash hash; + + // AuthData ::= SEQUENCE { + // odi ObjectDigestIdentifier + // port INTEGER + // } + // ObjectDigestIdentifier ::= SEQUENCE { + // type OBJECT IDENTIFIER, + // digestAlgorithm OBJECT IDENTIFIER, + // digest OCTET STRING + // } + base::StringPiece seq; + if (!asn1::GetElement(&i, asn1::kSEQUENCE, &seq)) + return SYNTAX_ERROR; + + base::StringPiece odi; + if (!asn1::GetElement(&seq, asn1::kSEQUENCE, &odi)) + return SYNTAX_ERROR; + + // type + ParseResult r = ParseEnumeratedOID(&odi, critical, kCAAHashTargets, + &hash.target); + if (r == DISCARD) + continue; + if (r != SUCCESS) + return r; + + // hash function + r = ParseEnumeratedOID(&odi, critical, kCAAHashFunctions, + &hash.algorithm); + if (r == DISCARD) + continue; + if (r != SUCCESS) + return r; + + base::StringPiece hash_bytes; + if (!asn1::GetElement(&odi, asn1::kOCTETSTRING, &hash_bytes)) + return SYNTAX_ERROR; + + if (hash_bytes.size() != DigestLength(hash.algorithm)) + return SYNTAX_ERROR; + hash.data = hash_bytes.as_string(); + + base::StringPiece port_bytes; + if (!asn1::GetElement(&seq, asn1::kINTEGER, &port_bytes)) + return SYNTAX_ERROR; + + // The port is 1, 2 or 3 bytes. The three byte form is needed because + // values >= 0x8000 would be treated as signed unless padded with a + // leading zero byte. + if (port_bytes.size() == 0 || port_bytes.size() > 3) + return SYNTAX_ERROR; + hash.port = reinterpret_cast<const uint8*>(port_bytes.data())[0]; + if (port_bytes.size() > 1) { + hash.port <<= 8; + hash.port |= reinterpret_cast<const uint8*>(port_bytes.data())[1]; + } + if (port_bytes.size() > 2) { + hash.port <<= 8; + hash.port |= reinterpret_cast<const uint8*>(port_bytes.data())[2]; + } + if (hash.port > 65535) + return SYNTAX_ERROR; + + output->authorized_hashes.push_back(hash); + } else if (critical) { + return UNKNOWN_CRITICAL; + } + } + + if (output->authorized_hashes.empty()) + return DISCARD; + + return SUCCESS; +} + } // namespace net diff --git a/net/base/dnssec_chain_verifier.h b/net/base/dnssec_chain_verifier.h index b3f577b..f0918c2 100644 --- a/net/base/dnssec_chain_verifier.h +++ b/net/base/dnssec_chain_verifier.h @@ -55,13 +55,6 @@ class NET_TEST DNSSECChainVerifier { // this after Verify has returned OK. const std::vector<base::StringPiece>& rrdatas() const; - // ParseTLSTXTRecord parses a TXT record which should contain TLS fingerprint - // information. - // rrdata: the raw TXT RRDATA from DNS - // returns: an empty map on failure, or the result of the parse. - static std::map<std::string, std::string> - ParseTLSTXTRecord(base::StringPiece rrdata); - // Exposed for testing only. static unsigned MatchingLabels(base::StringPiece a, base::StringPiece b); @@ -106,6 +99,48 @@ class NET_TEST DNSSECChainVerifier { std::vector<void*> scratch_pool_; }; +// DnsCAARecord encapsulates code and types for dealing with Certificate +// Authority Authorization records. These are DNS records which can express +// limitations regarding acceptable certificates for a domain. See +// http://tools.ietf.org/html/draft-hallambaker-donotissue-04 +class DnsCAARecord { + public: + enum ParseResult { + SUCCESS, // parse successful. + DISCARD, // no policies applying to this client were found. + SYNTAX_ERROR, // the record was syntactically invalid. + UNKNOWN_CRITICAL, // a critical record was not understood. + }; + + // A CAAPolicy is the result of parsing a set of CAA records. It describes a + // number of properies of certificates in a chain, any of which is sufficient + // to validate the chain. + struct Policy { + public: + // A HashTarget identifies the object that we are hashing. + enum HashTarget { + USER_CERTIFICATE, + CA_CERTIFICATE, + SUBJECT_PUBLIC_KEY_INFO, + }; + + // A Hash is a digest of some property of a certificate. + struct Hash { + HashTarget target; // what do we hash? + int algorithm; // NSS value, i.e. HASH_AlgSHA1. + std::string data; // digest, i.e. 20 bytes for SHA1. + unsigned port; // port number or 0 for any. + }; + + std::vector<Hash> authorized_hashes; + }; + + // Parse parses a series of DNS resource records and sets |output| to the + // result. + static ParseResult Parse(const std::vector<base::StringPiece>& rrdatas, + Policy* output); +}; + } // namespace net #endif // NET_BASE_DNSSEC_CHAIN_VERIFIER_H_ diff --git a/net/base/dnssec_unittest.cc b/net/base/dnssec_unittest.cc index e2c666b..ae004bf 100644 --- a/net/base/dnssec_unittest.cc +++ b/net/base/dnssec_unittest.cc @@ -4,6 +4,7 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "net/base/asn1_util.h" #include "net/base/dns_util.h" #include "net/base/dnssec_chain_verifier.h" #include "net/base/dnssec_keyset.h" @@ -600,89 +601,287 @@ TEST(DNSSECChainVerifierTest, DISABLED_Fuzz) { } } -// StringToTXTRecord takes a NUL terminated string and returns a valid TXT -// RRDATA by prefixing an 8-bit length. -static std::string StringToTXTRecord(const char* in) { - const unsigned len = strlen(in); - CHECK_LT(len, 256u); - std::string wrapped; - char l = len; - wrapped.append(&l, 1); - wrapped.append(in, len); - return wrapped; +static std::string MakeCAA(bool critical, + bool relying_use, + const char* key, + const std::string& value) { + std::string r; + + char a = 0; + if (critical) + a |= 128; + if (relying_use) { + a |= 2; + } else { + // For issuer use only. + a |= 1; + } + r.push_back(a); + r.push_back(static_cast<char>(strlen(key))); + r += key; + r += value; + + return r; } -TEST(DNSSECChainVerifierTest, BadTXT) { - static const char *const kBadTXTRecords[] = { - "", - " ", - " a=b", - "a=b \t", - "abc!=1", - }; - - for (unsigned i = 0; i < arraysize(kBadTXTRecords); i++) { - std::string wrapped(StringToTXTRecord(kBadTXTRecords[i])); - EXPECT_TRUE(DNSSECChainVerifier::ParseTLSTXTRecord(wrapped).empty()); +static std::string MakeAuth(const char* target_oid, + const char* hash_func_oid, + unsigned num_hash_bytes, + unsigned port) { + // AuthData ::= SEQUENCE { + // odi ObjectDigestIdentifier + // port INTEGER + // } + // ObjectDigestIdentifier ::= SEQUENCE { + // type OBJECT IDENTIFIER, + // digestAlgorithm OBJECT IDENTIFIER, + // digest OCTET STRING + // } + unsigned odi_len = strlen(target_oid) + + strlen(hash_func_oid) + + 2 /* OCTET STRING and length bytes */ + + num_hash_bytes; + unsigned len = odi_len + + 2 /* SEQUENCE and length bytes */ + + 2 /* INTEGER and length bytes */ + + 1 /* at least one byte of port number */; + if (port >= 256) + len++; // port number needs at least two bytes. + if (port >= 0x8000) + len++; // port number needs three bytes. + CHECK_LT(len, 128u); + + std::string r; + r.push_back(static_cast<char>(asn1::kSEQUENCE)); + r.push_back(static_cast<char>(len)); + r.push_back(static_cast<char>(asn1::kSEQUENCE)); + r.push_back(static_cast<char>(odi_len)); + r += target_oid; + r += hash_func_oid; + r.push_back(static_cast<char>(asn1::kOCTETSTRING)); + r.push_back(static_cast<char>(num_hash_bytes)); + r += std::string(num_hash_bytes, 'a'); + r.push_back(static_cast<char>(asn1::kINTEGER)); + if (port < 256) { + r.push_back(static_cast<char>(1)); + r.push_back(static_cast<char>(port)); + } else if (port < 0x8000) { + r.push_back(static_cast<char>(2)); + r.push_back(static_cast<char>(port >> 8)); + r.push_back(static_cast<char>(port)); + } else { + r.push_back(static_cast<char>(3)); + // A leading zero is needed to stop the MSB from indicating that the number + // is signed. + r.push_back(static_cast<char>(0)); + r.push_back(static_cast<char>(port >> 8)); + r.push_back(static_cast<char>(port)); } + return r; +} - EXPECT_TRUE(DNSSECChainVerifier::ParseTLSTXTRecord( - std::string("a=b\0", 4)).empty()); +TEST(CAAParserTest, Empty) { + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + + ASSERT_EQ(net::DnsCAARecord::DISCARD, + net::DnsCAARecord::Parse(rrdatas, &policy)); } -static bool MatchMap(const std::map<std::string, std::string>& m, - const char* const* match) { - unsigned matched = 0; - - for (unsigned i = 0; match[i]; i += 2) { - const char* key = match[i]; - const char* value = match[i+1]; - std::map<std::string, std::string>::const_iterator j; - j = m.find(key); - if (j == m.end()) - return false; - if (j->second != value) - return false; - matched++; - } +TEST(CAAParserTest, UnknownCriticalCAEntry) { + // Check that unknown critical keys that aren't for relying parties are + // ignored. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA(true /* critical */, false /* CAs only */, + "foo", "")); + rrdatas.push_back(caa); - if (m.size() != matched) - return false; - return true; + ASSERT_EQ(net::DnsCAARecord::DISCARD, + net::DnsCAARecord::Parse(rrdatas, &policy)); } -TEST(DNSSECChainVerifierTest, GoodTXT) { - // This array consists of a NULL terminated series of records. A record - // consists of a TXT string followed by a NULL terminated series of key, - // value pairs. - static const char *const kTXTRecords[] = { - "a=", - "a", "", NULL, - - "a=b", - "a", "b", NULL, - - "a=b c=", - "a", "b", "c", "", NULL, - - "a=b a=c", - "a", "b", NULL, - - "v=tls1 ha=sha1 h=<hexhash> sts=1", - "v", "tls1", "ha", "sha1", "h", "<hexhash>", "sts", "1", NULL, - - NULL, - }; - - for (unsigned i = 0; kTXTRecords[i]; i++) { - std::string wrapped(StringToTXTRecord(kTXTRecords[i])); - std::map<std::string, std::string> m( - DNSSECChainVerifier::ParseTLSTXTRecord(wrapped)); - ASSERT_FALSE(m.empty()); - ASSERT_TRUE(MatchMap(m, &kTXTRecords[i+1])); - while (kTXTRecords[i]) - i++; +TEST(CAAParserTest, UnknownCriticalEntry) { + // Check that unknown critical keys for relying parties cause an error. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA(true /* critical */, true /* for us */, + "foo", "")); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::UNKNOWN_CRITICAL, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, UnknownEntry) { + // Check that unknown, non-critical keys are ignored + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA(false /* not critical */, true /* for us */, + "foo", "")); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::DISCARD, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +static void CAAFuzz(const std::string& in) { + net::DnsCAARecord::Policy policy; + + for (unsigned i = 0; i < in.size() * 8; i++) { + const unsigned byte = i >> 3; + const unsigned bit = i & 7; + + std::string copy(in); + copy[byte] ^= static_cast<char>(1) << bit; + + std::vector<base::StringPiece> rrdatas; + rrdatas.push_back(copy); + + net::DnsCAARecord::Parse(rrdatas, &policy); } } +TEST(CAAParserTest, PolicyFuzz) { + // Fuzz the policy input for valgrind's. + const std::string policy_value("\x06\x03\x55\x04\x24"); + const std::string in = + MakeCAA(false /* not critical */, true /* for us */, "policy", + policy_value); + + CAAFuzz(in); +} + +TEST(CAAParserTest, Auth) { + // Check that a hash element is parsed correctly. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::SUCCESS, + net::DnsCAARecord::Parse(rrdatas, &policy)); + ASSERT_EQ(1u, policy.authorized_hashes.size()); + ASSERT_EQ(4 /* SHA256 */, policy.authorized_hashes[0].algorithm); + ASSERT_EQ(net::DnsCAARecord::Policy::USER_CERTIFICATE, + policy.authorized_hashes[0].target); + ASSERT_EQ(32u, policy.authorized_hashes[0].data.size()); + ASSERT_EQ(443u, policy.authorized_hashes[0].port); +} + +TEST(CAAParserTest, AuthFuzz) { + // Fuzz the auth value for valgrind. + const std::string in = MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 443)); + + CAAFuzz(in); +} + +TEST(CAAParserTest, UnknownHashFunction) { + // Check that an unknown hash is ignored. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x70", 32, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::DISCARD, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, CriticalUnknownHashFunction) { + // Check that a critical unknown hash is an error. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + true /* critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x70", 32, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::UNKNOWN_CRITICAL, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, UnknownHashTarget) { + // Check that an unknown hash is ignored. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x55\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::DISCARD, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, CriticalUnknownHashTarget) { + // Check that a critical unknown hash target is an error. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + true /* critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x55\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::UNKNOWN_CRITICAL, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, IncorrectDigestLength) { + // Check that a digest with the wrong length is an error. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 31, 443))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::SYNTAX_ERROR, + net::DnsCAARecord::Parse(rrdatas, &policy)); +} + +TEST(CAAParserTest, ZeroPort) { + // Check that a hash element with a zero port is parsed correctly. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 0))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::SUCCESS, + net::DnsCAARecord::Parse(rrdatas, &policy)); + ASSERT_EQ(1u, policy.authorized_hashes.size()); + ASSERT_EQ(0u, policy.authorized_hashes[0].port); +} + +TEST(CAAParserTest, LargePort) { + // Check that a hash element with a large port is parsed correctly. + std::vector<base::StringPiece> rrdatas; + net::DnsCAARecord::Policy policy; + const std::string caa(MakeCAA( + false /* not critical */, true /* for us */, "auth", + MakeAuth("\x06\x03\x55\x04\x24", + "\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01", 32, 40000))); + rrdatas.push_back(caa); + + ASSERT_EQ(net::DnsCAARecord::SUCCESS, + net::DnsCAARecord::Parse(rrdatas, &policy)); + ASSERT_EQ(1u, policy.authorized_hashes.size()); + ASSERT_EQ(40000u, policy.authorized_hashes[0].port); +} + } // namespace net |