resolved: resolve insecure answers with unsupported sig algorithms (#40778)

sd-resolved does not support all the permissible DNSSEC signature
algorithms, and some are intentionally unsupported as a matter of
policy. Answers that can only be validated via unsupported algorithms
should be treated as if they were unsigned, per RFC4035 § 5.2.

Previously, sd-resolved tried to properly record insecure answers for
unsupported algortihms, but did not record this status for each of the
auxilliary DNSSEC transactions, so the primary transaction had no way to
know if there was a plausible DNSKEY with an unsupported signature
algorithm in the chain of trust.

This commit adds the insecure DNSKEYs that use unsupported algorithms to
the list of validated keys for each transaction, so that dependent
transactions can learn that a plausible chain of trust exists, even if
no authenticated one does, and report the insecure answer.

This should improve the situation in #35126.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-03-25 10:38:08 +01:00
committed by GitHub
2 changed files with 57 additions and 12 deletions

View File

@@ -646,8 +646,10 @@ static int dnssec_rrset_verify_sig(
if (!ctx)
return -ENOMEM;
/* If the signature algorithm is supported by systemd-resolved but disabled by host policy,
* also return -EOPNOTSUPP. */
if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0)
return -EIO;
return -EOPNOTSUPP;
if (EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0)
return -EIO;
@@ -912,9 +914,6 @@ int dnssec_verify_rrset_search(
DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
DnssecResult one_result;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
continue;
/* Is this a DNSKEY RR that matches they key of our RRSIG? */
r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
if (r < 0)
@@ -922,6 +921,14 @@ int dnssec_verify_rrset_search(
if (r == 0)
continue;
if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) {
/* An unauthenticated DNSKEY in validated_dnskeys is a key we are not able to
* authenticate, but might still be valid. Record this as an unsupported
* algorithm so we can still at least report an insecure answer. */
found_unsupported_algorithm = true;
continue;
}
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
@@ -1092,8 +1099,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
if (!ctx)
return -ENOMEM;
/* If the digest is supported by systemd-resolved but disabled by host policy, also return -EOPNOTSUPP
*/
if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0)
return -EIO;
return -EOPNOTSUPP;
if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0)
return -EIO;
@@ -1121,6 +1130,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds,
int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
DnsResourceRecord *ds;
DnsAnswerFlags flags;
bool found_unsupported_algorithm = false;
int r;
assert(dnskey);
@@ -1145,14 +1155,21 @@ int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *vali
continue;
r = dnssec_verify_dnskey_by_ds(dnskey, ds, false);
if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP))
continue; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */
if (r == -EKEYREJECTED)
continue; /* The DNSKEY is revoked or otherwise invalid. */
if (r == -EOPNOTSUPP) {
found_unsupported_algorithm = true;
continue;
}
if (r < 0)
return r;
if (r > 0)
return 1;
}
if (found_unsupported_algorithm)
return -EOPNOTSUPP;
return 0;
}
@@ -1201,7 +1218,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
return -ENOMEM;
if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0)
return -EIO;
return -EOPNOTSUPP;
r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
if (r < 0)
@@ -1218,7 +1235,7 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
for (unsigned k = 0; k < nsec3->nsec3.iterations; k++) {
if (EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0)
return -EIO;
return -EOPNOTSUPP;
if (EVP_DigestUpdate(ctx, result, hash_size) <= 0)
return -EIO;
if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0)

View File

@@ -2836,13 +2836,18 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
DNS_ANSWER_FOREACH_ITEM(item, t->answer) {
r = dnssec_verify_dnskey_by_ds_search(item->rr, t->validated_keys);
if (r < 0)
if (r < 0 && r != -EOPNOTSUPP)
return r;
if (r == 0)
continue;
/* If so, the DNSKEY is validated too. */
r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig);
/* If so, the DNSKEY is validated too, but only mark it authenticated if the DS verification
* succeeded with a known algorithm. */
if (r == -EOPNOTSUPP)
r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags, NULL);
else
r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig);
if (r < 0)
return r;
}
@@ -3259,6 +3264,12 @@ static int dns_transaction_copy_validated(DnsTransaction *t) {
if (DNS_TRANSACTION_IS_LIVE(dt->state))
continue;
/* Some of the validated keys may not be authenticated, but are still useful to report
* insecure answers when the domain is signed only by unsupported algorithms. */
r = dns_answer_extend(&t->validated_keys, dt->validated_keys);
if (r < 0)
return r;
if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED))
continue;
@@ -3478,6 +3489,23 @@ static int dnssec_validate_records(
/* https://datatracker.ietf.org/doc/html/rfc6840#section-5.2 */
if (result == DNSSEC_UNSUPPORTED_ALGORITHM) {
if (rr->key->type == DNS_TYPE_DNSKEY) {
/* This is a DNSKEY we cannot authenticate, but it might still be the best
* offer from the resolver. Add it to the validated keys in case it's the
* best we can find, but do not mark it as authenticated.
*/
r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, 0, NULL);
if (r < 0)
return r;
/* Some of the DNSKEYs we just added might already have been revoked,
* remove them again in that case. */
r = dns_transaction_invalidate_revoked_keys(t);
if (r < 0)
return r;
}
r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL);
if (r < 0)
return r;