summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-28 15:53:50 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-28 15:53:50 +0000
commit899c3e93acbd41bcd3c5ce5d09eddef8f45ab3ff (patch)
tree38b3e5caabeb273244b348e1bd101181aa6ce576 /net/base
parent40251a21ba6eea816755b1bcb9edf305c782aeda (diff)
downloadchromium_src-899c3e93acbd41bcd3c5ce5d09eddef8f45ab3ff.zip
chromium_src-899c3e93acbd41bcd3c5ce5d09eddef8f45ab3ff.tar.gz
chromium_src-899c3e93acbd41bcd3c5ce5d09eddef8f45ab3ff.tar.bz2
https: add support for DNS exclusion and switch to TXT records.
(This code has no effect unless --enable-dnssec-certs is given.) The existing DNSSEC code will process embeded chains in certificates and validate CERT records there in. The format of the CERT record was just something made up as a proof of concept. This change switches that code to using TXT records which are at least used by some other code. Additionally, when --enable-dnssec-certs is given. TXT record lookups are triggered for each HTTPS connection. If DNSSEC secure, these lookups can validate a HTTPS certificate. Even without DNSSEC, they can by used for exclusion: if TLS fingerprints are given, but the certificate doesn't match any of them, then the certificate is rejected. The next step in this series will be to perform the TXT lookup for some percentage of dev channel users in order to measure the latency impact. For this experiment, all behavioural changes will be disabled. BUG=none TEST=net_unittests http://codereview.chromium.org/3148037/show git-svn-id: svn://svn.chromium.org/chrome/trunk/src@57787 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r--net/base/cert_verify_result.h1
-rw-r--r--net/base/dnssec_chain_verifier.cc90
-rw-r--r--net/base/dnssec_chain_verifier.h8
-rw-r--r--net/base/dnssec_unittest.cc89
-rw-r--r--net/base/net_error_list.h6
5 files changed, 192 insertions, 2 deletions
diff --git a/net/base/cert_verify_result.h b/net/base/cert_verify_result.h
index b3381af..faac17a 100644
--- a/net/base/cert_verify_result.h
+++ b/net/base/cert_verify_result.h
@@ -23,6 +23,7 @@ class CertVerifyResult {
has_md2_ca = false;
}
+ // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h
int cert_status;
// Properties of the certificate chain.
diff --git a/net/base/dnssec_chain_verifier.cc b/net/base/dnssec_chain_verifier.cc
index 96b5b7d..711c17e 100644
--- a/net/base/dnssec_chain_verifier.cc
+++ b/net/base/dnssec_chain_verifier.cc
@@ -8,6 +8,7 @@
#include "base/scoped_ptr.h"
#include "base/sha1.h"
#include "base/sha2.h"
+#include "base/string_util.h"
#include "net/base/dns_util.h"
// We don't have a location for the spec yet, so we'll include it here until it
@@ -597,6 +598,95 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::ReadCERTs(
return OK;
}
+// 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;
+}
+
// CountLabels returns the number of DNS labels in |a|, which must be in DNS,
// length-prefixed form.
static unsigned CountLabels(base::StringPiece a) {
diff --git a/net/base/dnssec_chain_verifier.h b/net/base/dnssec_chain_verifier.h
index 2556564..096dea1 100644
--- a/net/base/dnssec_chain_verifier.h
+++ b/net/base/dnssec_chain_verifier.h
@@ -5,6 +5,7 @@
#ifndef NET_BASE_DNSSEC_CHAIN_VERIFIER_H_
#define NET_BASE_DNSSEC_CHAIN_VERIFIER_H_
+#include <map>
#include <string>
#include <vector>
@@ -54,6 +55,13 @@ class 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);
diff --git a/net/base/dnssec_unittest.cc b/net/base/dnssec_unittest.cc
index 8e6f7d0..4181e09 100644
--- a/net/base/dnssec_unittest.cc
+++ b/net/base/dnssec_unittest.cc
@@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/base/dns_util.h"
#include "net/base/dnssec_chain_verifier.h"
#include "net/base/dnssec_keyset.h"
-#include "net/base/dns_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -400,3 +402,88 @@ TEST(DNSSECChainVerifierTest, DISABLED_Fuzz) {
ASSERT_NE(net::DNSSECChainVerifier::OK, verifier.Verify());
}
}
+
+// 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;
+}
+
+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(net::DNSSECChainVerifier::ParseTLSTXTRecord(wrapped).empty());
+ }
+
+ EXPECT_TRUE(net::DNSSECChainVerifier::ParseTLSTXTRecord(
+ std::string("a=b\0", 4)).empty());
+}
+
+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++;
+ }
+
+ if (m.size() != matched)
+ return false;
+ return true;
+}
+
+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(
+ net::DNSSECChainVerifier::ParseTLSTXTRecord(wrapped));
+ ASSERT_FALSE(m.empty());
+ ASSERT_TRUE(MatchMap(m, &kTXTRecords[i+1]));
+ while (kTXTRecords[i])
+ i++;
+ }
+}
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 5fe0846..d136a7f 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -259,13 +259,17 @@ NET_ERROR(CERT_INVALID, -207)
// signature algorithm.
NET_ERROR(CERT_WEAK_SIGNATURE_ALGORITHM, -208)
+// The domain has CERT records which are tagged as being an exclusive list of
+// valid fingerprints. But the certificate presented was not in this list.
+NET_ERROR(CERT_NOT_IN_DNS, -209)
+
// Add new certificate error codes here.
//
// Update the value of CERT_END whenever you add a new certificate error
// code.
// The value immediately past the last certificate error code.
-NET_ERROR(CERT_END, -209)
+NET_ERROR(CERT_END, -210)
// The URL is invalid.
NET_ERROR(INVALID_URL, -300)