diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 7e8f4d888b..8b3e59a1ea 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1280,13 +1280,62 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { dns_transaction_process_dnssec(t); } -int dns_transaction_validate_dnssec(DnsTransaction *t) { - _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + if (r != 0) + return r; + + return 0; +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { DnsResourceRecord *rr; int ifindex, r; assert(t); + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the lis of validated + * keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex); + if (r < 0) + return r; + } + + return 0; +} + +int dns_transaction_validate_dnssec(DnsTransaction *t) { + _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + bool dnskeys_finalized = false; + DnsResourceRecord *rr; + int r; + + assert(t); + /* We have now collected all DS and DNSKEY RRs in * t->validated_keys, let's see which RRs we can now * authenticate with that. */ @@ -1312,22 +1361,12 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { } /* First see if there are DNSKEYs we already known a validated DS for. */ - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - - r = dnssec_verify_dnskey_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; - - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex); - if (r < 0) - return r; - } + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; for (;;) { - bool changed = false, missing_key_for_transaction = false; + bool changed = false; DNS_ANSWER_FOREACH(rr, t->answer) { DnssecResult result; @@ -1346,9 +1385,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result)); } - switch (result) { - - case DNSSEC_VALIDATED: + if (result == DNSSEC_VALIDATED) { /* Add the validated RRset to the new list of validated RRsets */ r = dns_answer_copy_by_key(&validated, t->answer, rr->key); @@ -1372,71 +1409,56 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; + /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; - case DNSSEC_INVALID: - case DNSSEC_NO_SIGNATURE: - case DNSSEC_SIGNATURE_EXPIRED: + } else if (dnskeys_finalized) { + /* If we haven't read all DNSKEYs yet + * a negative result of the validation + * is irrelevant, as there might be + * more DNSKEYs coming. */ - /* Is this the RRset that we were looking for? If so, this is fatal for the whole transaction */ - r = dns_resource_key_match_rr(t->key, rr, NULL); + r = dns_transaction_is_primary_response(t, rr); if (r < 0) return r; if (r > 0) { + /* This is a primary response + * to our question, and it + * failed validation. That's + * fatal. */ t->dnssec_result = result; return 0; } - /* Is this a CNAME for a record we were looking for? If so, it's also fatal for the whole transaction */ - r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); - if (r < 0) - return r; - if (r > 0) { - t->dnssec_result = result; - return 0; - } - - /* This is just something auxiliary. Just remove the RRset and continue. */ + /* This is just some auxiliary + * data. Just remove the RRset and + * continue. */ r = dns_answer_remove_by_key(&t->answer, rr->key); if (r < 0) return r; + /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; - - case DNSSEC_MISSING_KEY: - /* They key is missing? Let's continue - * with the next iteration, maybe - * we'll find it in an DNSKEY RRset - * later on. */ - - r = dns_resource_key_equal(rr->key, t->key); - if (r < 0) - return r; - if (r > 0) - missing_key_for_transaction = true; - - break; - - default: - assert_not_reached("Unexpected DNSSEC result"); } - - if (changed) - break; } if (changed) continue; - /* This didn't work either, there's no point in - * continuing. */ - if (missing_key_for_transaction) { - t->dnssec_result = DNSSEC_MISSING_KEY; - return 0; + if (!dnskeys_finalized) { + /* OK, now we know we have added all DNSKEYs + * we possibly could to our validated + * list. Now run the whole thing once more, + * and strip everything we still cannot + * validate. + */ + dnskeys_finalized = true; + continue; } + /* We're done */ break; }