summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-12 19:56:52 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-12 19:56:52 +0000
commitaa8461e365375015d624540370202af5a4b36723 (patch)
treee95bcd441e351c3f61d32897ff45f554576cd364
parentdc91bc562d3b4c59f061e3fb884207ebae8c523f (diff)
downloadchromium_src-aa8461e365375015d624540370202af5a4b36723.zip
chromium_src-aa8461e365375015d624540370202af5a4b36723.tar.gz
chromium_src-aa8461e365375015d624540370202af5a4b36723.tar.bz2
net: add DANE support for DNSSEC stapled certificates.
Currently we support a form of CAA record for DNSSEC stapled certificates. Now that RFC 6698 has been published, we want to change it to use that. This CL adds support for DANE records in stapled certificates. After this has reached the stable channel, the old CAA support can be removed. BUG=none TEST=Check that https://spki.dane.imperialviolet.org loads without errors. Review URL: https://chromiumcodereview.appspot.com/11184027 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@167227 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/dns_util.h1
-rw-r--r--net/base/dnssec_chain_verifier.cc80
-rw-r--r--net/base/dnssec_chain_verifier.h28
-rw-r--r--net/socket/ssl_client_socket_nss.cc86
4 files changed, 187 insertions, 8 deletions
diff --git a/net/base/dns_util.h b/net/base/dns_util.h
index db44158..3a76e7a 100644
--- a/net/base/dns_util.h
+++ b/net/base/dns_util.h
@@ -51,6 +51,7 @@ static const uint16 kDNS_CERT = 37;
static const uint16 kDNS_DS = 43;
static const uint16 kDNS_RRSIG = 46;
static const uint16 kDNS_DNSKEY = 48;
+static const uint16 kDNS_TLSA = 52;
static const uint16 kDNS_ANY = 0xff;
static const uint16 kDNS_CAA = 257;
static const uint16 kDNS_TESTING = 0xfffe; // in private use area.
diff --git a/net/base/dnssec_chain_verifier.cc b/net/base/dnssec_chain_verifier.cc
index 5104545..4ea79b3 100644
--- a/net/base/dnssec_chain_verifier.cc
+++ b/net/base/dnssec_chain_verifier.cc
@@ -543,6 +543,20 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::EnterZone(
return OK;
}
+// IsValidTerminalRRType returns true if the given RR type is one that we
+// accept as the terminal record in a DNSSEC chain.
+bool DNSSECChainVerifier::IsValidTerminalRRType(uint16 rrtype) {
+ switch (rrtype) {
+ case kDNS_CAA:
+ case kDNS_CERT:
+ case kDNS_TLSA:
+ case kDNS_TXT:
+ return true;
+ }
+
+ return false;
+}
+
// 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.
@@ -562,7 +576,7 @@ DNSSECChainVerifier::Error DNSSECChainVerifier::LeaveZone(
if (rrtype == kDNS_DS) {
err = ReadDSSet(&rrdatas, *next_name);
- } else if (rrtype == kDNS_CERT || rrtype == kDNS_TXT || rrtype == kDNS_CAA) {
+ } else if (IsValidTerminalRRType(rrtype)) {
err = ReadGenericRRs(&rrdatas);
} else if (rrtype == kDNS_CNAME) {
err = ReadCNAME(&rrdatas);
@@ -582,7 +596,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 || rrtype == kDNS_CAA) {
+ } else if (IsValidTerminalRRType(rrtype)) {
// 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())) {
@@ -744,6 +758,7 @@ struct CAAHashFunctionOID {
// 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 kHashNone = 0;
static const int kHashSHA256 = 4;
static const int kHashSHA384 = 5;
static const int kHashSHA512 = 6;
@@ -916,4 +931,65 @@ DnsCAARecord::ParseResult DnsCAARecord::Parse(
return SUCCESS;
}
+void DnsTLSARecord::Parse(
+ const std::vector<base::StringPiece>& rrdatas,
+ std::vector<DnsTLSARecord::Match>* output) {
+ // see https://tools.ietf.org/html/rfc6698#section-2.1
+ output->clear();
+
+ for (std::vector<base::StringPiece>::const_iterator
+ ii = rrdatas.begin(); ii != rrdatas.end(); ++ii) {
+ base::StringPiece i = *ii;
+
+ if (i.size() < 3)
+ continue;
+
+ const uint8 cert_usage = i[0];
+ const uint8 selector = i[1];
+ const uint8 match_type = i[2];
+ i.remove_prefix(3);
+
+ if (cert_usage != 3) {
+ // Type 3 is a "domain issued certificate" - i.e. a certificate (or
+ // public key) which is granted authority by the TLSA record.
+ continue;
+ }
+
+ Match match;
+
+ switch (selector) {
+ case 0:
+ match.target = Match::CERTIFICATE;
+ break;
+ case 1:
+ match.target = Match::SUBJECT_PUBLIC_KEY_INFO;
+ break;
+ default:
+ continue;
+ }
+
+ switch (match_type) {
+ case 0:
+ match.algorithm = kHashNone;
+ break;
+ case 1:
+ match.algorithm = kHashSHA256;
+ break;
+ case 2:
+ match.algorithm = kHashSHA512;
+ break;
+ default:
+ continue;
+ }
+
+ if (match.algorithm != kHashNone &&
+ i.size() != DigestLength(match.algorithm)) {
+ continue;
+ }
+
+ match.data = i.as_string();
+ output->push_back(match);
+ }
+}
+
} // namespace net
diff --git a/net/base/dnssec_chain_verifier.h b/net/base/dnssec_chain_verifier.h
index 9fa80b8..f5a066b 100644
--- a/net/base/dnssec_chain_verifier.h
+++ b/net/base/dnssec_chain_verifier.h
@@ -78,6 +78,7 @@ class NET_EXPORT_PRIVATE DNSSECChainVerifier {
uint8 algorithm);
Error EnterRoot();
+ static bool IsValidTerminalRRType(uint16 rrtype);
Error EnterZone(const base::StringPiece& zone);
Error LeaveZone(base::StringPiece* next_name);
Error ReadDSSet(std::vector<base::StringPiece>*,
@@ -103,6 +104,7 @@ class NET_EXPORT_PRIVATE DNSSECChainVerifier {
// 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
+// TODO(agl): remove once DANE support has been released.
class NET_EXPORT_PRIVATE DnsCAARecord {
public:
enum ParseResult {
@@ -144,6 +146,32 @@ class NET_EXPORT_PRIVATE DnsCAARecord {
Policy* output);
};
+class NET_EXPORT_PRIVATE DnsTLSARecord {
+ public:
+ // A Match is an authorized certificate or public key from the TLSA records.
+ struct NET_EXPORT_PRIVATE Match {
+ // A HashTarget identifies the object that we are hashing.
+ enum HashTarget {
+ CERTIFICATE,
+ SUBJECT_PUBLIC_KEY_INFO,
+ };
+
+ HashTarget target; // what do we hash?
+ // algorithm is an NSS HASH_HashType (i.e. HASH_AlgSHA1). But note that
+ // it can also be HASH_AlgNULL to indicate that |data| isn't hashed at
+ // all.
+ int algorithm;
+ std::string data; // digest, or raw data if |algorithm == HASH_AlgNULL|.
+ };
+
+ // Parse parses a series of TLSA resource records and sets |output| to the
+ // result. Unknown or invalid records are ignored, as are records with a
+ // usage other than "domain-issued certificate" (type 3). See
+ // https://tools.ietf.org/html/rfc6698#section-2.1.1.
+ static void Parse(const std::vector<base::StringPiece>& rrdatas,
+ std::vector<Match>* output);
+};
+
} // namespace net
#endif // NET_BASE_DNSSEC_CHAIN_VERIFIER_H_
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index 4a946d8..017d714 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -279,6 +279,7 @@ enum DNSValidationResult {
// server_cert_nss: the server's leaf certificate.
// rrdatas: the CAA records for the current domain.
// port: the TCP port number that we connected to.
+// TODO(agl): remove once DANE support has been released.
DNSValidationResult VerifyCAARecords(
CERTCertificate* server_cert_nss,
const std::vector<base::StringPiece>& rrdatas,
@@ -324,6 +325,56 @@ DNSValidationResult VerifyCAARecords(
return DNSVR_FAILURE;
}
+// VerifyTLSARecords processes DNSSEC validated RRDATA for a number of DNS TLSA
+// records and checks them against the given chain.
+// server_cert_nss: the server's leaf certificate.
+// rrdatas: the TLSA records for the current domain.
+DNSValidationResult VerifyTLSARecords(
+ CERTCertificate* server_cert_nss,
+ const std::vector<base::StringPiece>& rrdatas) {
+ std::vector<DnsTLSARecord::Match> matches;
+ DnsTLSARecord::Parse(rrdatas, &matches);
+
+ for (std::vector<DnsTLSARecord::Match>::const_iterator
+ i = matches.begin(); i != matches.end(); ++i) {
+ SECItem matched_data;
+ switch (i->target) {
+ case DnsTLSARecord::Match::CERTIFICATE:
+ matched_data = server_cert_nss->derCert;
+ break;
+ case DnsTLSARecord::Match::SUBJECT_PUBLIC_KEY_INFO:
+ matched_data = server_cert_nss->derPublicKey;
+ break;
+ default:
+ continue;
+ }
+
+ uint8 calculated_hash[HASH_LENGTH_MAX];
+ SECItem processed_data;
+ if (i->algorithm == HASH_AlgNULL) {
+ processed_data = matched_data;
+ } else {
+ SECStatus rv = HASH_HashBuf(
+ static_cast<HASH_HashType>(i->algorithm),
+ calculated_hash,
+ matched_data.data,
+ matched_data.len);
+ if (rv != SECSuccess)
+ continue;
+ processed_data.data = calculated_hash;
+ processed_data.len = i->data.size();
+ }
+
+ if (processed_data.len == i->data.size() &&
+ memcmp(processed_data.data, i->data.data(), i->data.size()) == 0) {
+ return DNSVR_SUCCESS;
+ }
+ }
+
+ // No TLSA records matched so we reject the certificate.
+ return DNSVR_FAILURE;
+}
+
// CheckDNSSECChain tries to validate a DNSSEC chain embedded in
// |server_cert_nss|. It returns true iff a chain is found that proves the
// value of a CAA record that contains a valid public key fingerprint.
@@ -364,27 +415,50 @@ DNSValidationResult CheckDNSSECChain(
if (rv != SECSuccess)
return DNSVR_CONTINUE;
- base::StringPiece chain(
+ std::string chain(
reinterpret_cast<char*>(dnssec_embedded_chain.data),
dnssec_embedded_chain.len);
+ SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE);
std::string dns_hostname;
if (!DNSDomainFromDot(hostname, &dns_hostname))
return DNSVR_CONTINUE;
+
DNSSECChainVerifier verifier(dns_hostname, chain);
DNSSECChainVerifier::Error err = verifier.Verify();
+ bool is_tlsa = false;
+ if (err == DNSSECChainVerifier::BAD_TARGET) {
+ // We might have failed because this is a DANE chain, not a CAA chain.
+ // DANE stores records in a subdomain of the name that includes the port
+ // and protocol, i.e. _443._tcp.www.example.com. We have to construct
+ // such a name for |hostname|.
+ const std::string port_str = base::UintToString(port);
+ char port_label_len = 1 + static_cast<char>(port_str.size());
+ const std::string tlsa_domain =
+ std::string(&port_label_len, 1) + "_" + port_str +
+ "\x04_tcp" +
+ dns_hostname;
+
+ verifier = DNSSECChainVerifier(tlsa_domain, chain);
+ err = verifier.Verify();
+ is_tlsa = true;
+ }
+
if (err != DNSSECChainVerifier::OK) {
LOG(ERROR) << "DNSSEC chain verification failed: " << err;
return DNSVR_CONTINUE;
}
+ if (is_tlsa) {
+ if (verifier.rrtype() != kDNS_TLSA)
+ return DNSVR_CONTINUE;
+
+ return VerifyTLSARecords(server_cert_nss, verifier.rrdatas());
+ }
+
if (verifier.rrtype() != kDNS_CAA)
return DNSVR_CONTINUE;
- DNSValidationResult r = VerifyCAARecords(
- server_cert_nss, verifier.rrdatas(), port);
- SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE);
-
- return r;
+ return VerifyCAARecords(server_cert_nss, verifier.rrdatas(), port);
}
void DestroyCertificates(CERTCertificate** certs, size_t len) {