summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-02 21:06:54 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-02 21:06:54 +0000
commitbad796d05629858e027778dc1280455926f68f2c (patch)
tree0b901c0b5515310df7b91119910528c1301c12de /net/base
parentc06c697b737656f912730bdd7d3572b179f7fd5e (diff)
downloadchromium_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.h2
-rw-r--r--net/base/dnssec_chain_verifier.cc285
-rw-r--r--net/base/dnssec_chain_verifier.h49
-rw-r--r--net/base/dnssec_unittest.cc345
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