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
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index dd8f137d..07f9fcc5 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -305,96 +305,65 @@
DNSVR_CONTINUE, // perform CA validation as usual.
};
-// VerifyTXTRecords processes the RRDATA for a number of DNS TXT records and
-// checks them against the given certificate.
-// dnssec: if true then the TXT records are DNSSEC validated. In this case,
-// DNSVR_SUCCESS may be returned.
-// server_cert_nss: the certificate to validate
-// rrdatas: the TXT records for the current domain.
-DNSValidationResult VerifyTXTRecords(
- bool dnssec,
+// VerifyCAARecords processes DNSSEC validated RRDATA for a number of DNS CAA
+// records and checks them against the given chain.
+// 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.
+DNSValidationResult VerifyCAARecords(
CERTCertificate* server_cert_nss,
- const std::vector<base::StringPiece>& rrdatas) {
- bool found_well_formed_record = false;
- bool matched_record = false;
+ const std::vector<base::StringPiece>& rrdatas,
+ uint16 port) {
+ DnsCAARecord::Policy policy;
+ const DnsCAARecord::ParseResult r = DnsCAARecord::Parse(rrdatas, &policy);
+ if (r == DnsCAARecord::SYNTAX_ERROR || r == DnsCAARecord::UNKNOWN_CRITICAL)
+ return DNSVR_FAILURE;
+ if (r == DnsCAARecord::DISCARD)
+ return DNSVR_CONTINUE;
+ DCHECK(r == DnsCAARecord::SUCCESS);
- for (std::vector<base::StringPiece>::const_iterator
- i = rrdatas.begin(); i != rrdatas.end(); ++i) {
- std::map<std::string, std::string> m(
- DNSSECChainVerifier::ParseTLSTXTRecord(*i));
- if (m.empty())
- continue;
+ for (std::vector<DnsCAARecord::Policy::Hash>::const_iterator
+ hash = policy.authorized_hashes.begin();
+ hash != policy.authorized_hashes.end();
+ ++hash) {
+ if (hash->target == DnsCAARecord::Policy::SUBJECT_PUBLIC_KEY_INFO &&
+ (hash->port == 0 || hash->port == port)) {
+ CHECK_LE(hash->data.size(), static_cast<unsigned>(SHA512_LENGTH));
+ uint8 calculated_hash[SHA512_LENGTH]; // SHA512 is the largest.
+ SECStatus rv = HASH_HashBuf(
+ static_cast<HASH_HashType>(hash->algorithm),
+ calculated_hash,
+ server_cert_nss->derPublicKey.data,
+ server_cert_nss->derPublicKey.len);
+ DCHECK(rv == SECSuccess);
+ const std::string actual_digest(reinterpret_cast<char*>(calculated_hash),
+ hash->data.size());
- std::map<std::string, std::string>::const_iterator j;
- j = m.find("v");
- if (j == m.end() || j->second != "tls1")
- continue;
-
- j = m.find("ha");
-
- HASH_HashType hash_algorithm;
- unsigned hash_length;
- if (j == m.end() || j->second == "sha1") {
- hash_algorithm = HASH_AlgSHA1;
- hash_length = SHA1_LENGTH;
- } else if (j->second == "sha256") {
- hash_algorithm = HASH_AlgSHA256;
- hash_length = SHA256_LENGTH;
- } else {
- continue;
- }
-
- j = m.find("h");
- if (j == m.end())
- continue;
-
- std::vector<uint8> given_hash;
- if (!base::HexStringToBytes(j->second, &given_hash))
- continue;
-
- if (given_hash.size() != hash_length)
- continue;
-
- uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest.
- SECStatus rv;
-
- j = m.find("hr");
- if (j == m.end() || j->second == "pubkey") {
- rv = HASH_HashBuf(hash_algorithm, calculated_hash,
- server_cert_nss->derPublicKey.data,
- server_cert_nss->derPublicKey.len);
- } else if (j->second == "cert") {
- rv = HASH_HashBuf(hash_algorithm, calculated_hash,
- server_cert_nss->derCert.data,
- server_cert_nss->derCert.len);
- } else {
- continue;
- }
-
- if (rv != SECSuccess)
- NOTREACHED();
-
- found_well_formed_record = true;
-
- if (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) {
- matched_record = true;
- if (dnssec)
+ // Note that the parser ensures that hash->data.size() is correct for the
+ // given algorithm. An attacker cannot give a zero length hash that
+ // always matches.
+ if (actual_digest == hash->data) {
+ // A DNSSEC secure hash over the public key of the leaf-certificate
+ // is sufficient.
return DNSVR_SUCCESS;
+ }
}
}
- if (found_well_formed_record && !matched_record)
- return DNSVR_FAILURE;
-
- return DNSVR_CONTINUE;
+ // If a CAA record was found, but nothing matched, then 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 TXT record that contains a valid public key fingerprint.
+// value of a CAA record that contains a valid public key fingerprint.
+// |port| contains the TCP port number that we connected to as CAA records can
+// be specific to a given port.
DNSValidationResult CheckDNSSECChain(
const std::string& hostname,
- CERTCertificate* server_cert_nss) {
+ CERTCertificate* server_cert_nss,
+ uint16 port) {
if (!server_cert_nss)
return DNSVR_CONTINUE;
@@ -439,12 +408,13 @@
return DNSVR_CONTINUE;
}
- if (verifier.rrtype() != kDNS_TXT)
+ if (verifier.rrtype() != kDNS_CAA)
return DNSVR_CONTINUE;
- DNSValidationResult r = VerifyTXTRecords(
- true /* DNSSEC verified */, server_cert_nss, verifier.rrdatas());
+ DNSValidationResult r = VerifyCAARecords(
+ server_cert_nss, verifier.rrdatas(), port);
SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE);
+
return r;
}
@@ -483,7 +453,6 @@
eset_mitm_detected_(false),
predicted_cert_chain_correct_(false),
peername_initialized_(false),
- dnssec_provider_(NULL),
next_handshake_state_(STATE_NONE),
nss_fd_(NULL),
nss_bufs_(NULL),
@@ -580,10 +549,6 @@
#endif
}
-void SSLClientSocketNSS::UseDNSSEC(DNSSECProvider* provider) {
- dnssec_provider_ = provider;
-}
-
int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
EnterFunction("");
DCHECK(transport_.get());
@@ -1243,9 +1208,6 @@
case STATE_VERIFY_DNSSEC:
rv = DoVerifyDNSSEC(rv);
break;
- case STATE_VERIFY_DNSSEC_COMPLETE:
- rv = DoVerifyDNSSECComplete(rv);
- break;
case STATE_VERIFY_CERT:
DCHECK(rv == OK);
rv = DoVerifyCert(rv);
@@ -1446,7 +1408,8 @@
if (ssl_config_.dnssec_enabled) {
DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(),
- server_cert_nss_);
+ server_cert_nss_,
+ host_and_port_.port());
if (r == DNSVR_SUCCESS) {
local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
server_cert_verify_result_ = &local_server_cert_verify_result_;
@@ -1455,60 +1418,7 @@
}
}
- if (dnssec_provider_ == NULL) {
- GotoState(STATE_VERIFY_CERT);
- return OK;
- }
-
- GotoState(STATE_VERIFY_DNSSEC_COMPLETE);
- RRResponse* response;
- dnssec_wait_start_time_ = base::Time::Now();
- return dnssec_provider_->GetDNSSECRecords(&response, &handshake_io_callback_);
-}
-
-int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) {
- RRResponse* response;
- int err = dnssec_provider_->GetDNSSECRecords(&response, NULL);
- DCHECK_EQ(err, OK);
-
- const base::TimeDelta elapsed = base::Time::Now() - dnssec_wait_start_time_;
- HISTOGRAM_TIMES("Net.DNSSECWaitTime", elapsed);
-
GotoState(STATE_VERIFY_CERT);
- if (!response || response->rrdatas.empty())
- return OK;
-
- std::vector<base::StringPiece> records;
- records.resize(response->rrdatas.size());
- for (unsigned i = 0; i < response->rrdatas.size(); i++)
- records[i] = base::StringPiece(response->rrdatas[i]);
- DNSValidationResult r =
- VerifyTXTRecords(response->dnssec, server_cert_nss_, records);
-
- if (!ssl_config_.dnssec_enabled) {
- // If DNSSEC is not enabled we don't take any action based on the result,
- // except to record the latency, above.
- return OK;
- }
-
- switch (r) {
- case DNSVR_FAILURE:
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_NOT_IN_DNS;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- return ERR_CERT_NOT_IN_DNS;
- case DNSVR_CONTINUE:
- GotoState(STATE_VERIFY_CERT);
- break;
- case DNSVR_SUCCESS:
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- break;
- default:
- NOTREACHED();
- GotoState(STATE_VERIFY_CERT);
- }
return OK;
}