summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/resolve/resolved-dns-dnssec.c2
-rw-r--r--src/resolve/resolved-dns-dnssec.h11
-rw-r--r--src/resolve/resolved-dns-server.c20
-rw-r--r--src/resolve/resolved-dns-server.h12
-rw-r--r--src/resolve/resolved-dns-transaction.c75
5 files changed, 106 insertions, 14 deletions
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 14fa58c544..a856f0717e 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -1197,6 +1197,7 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
+ [DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
@@ -1211,5 +1212,6 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_UNSIGNED] = "unsigned",
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+ [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index 9ad20c8c69..d7aecbce13 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -32,7 +32,14 @@ enum DnssecMode {
/* No DNSSEC validation is done */
DNSSEC_NO,
- /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
+ /* Validate locally, if the server knows DO, but if not,
+ * don't. Don't trust the AD bit. If the server doesn't do
+ * DNSSEC properly, downgrade to non-DNSSEC operation. Of
+ * course, we then are vulnerable to a downgrade attack, but
+ * that's life and what is configured. */
+ DNSSEC_DOWNGRADE_OK,
+
+ /* Insist on DNSSEC server support, and rather fail than downgrading. */
DNSSEC_YES,
_DNSSEC_MODE_MAX,
@@ -54,6 +61,8 @@ enum DnssecResult {
DNSSEC_UNSIGNED,
DNSSEC_FAILED_AUXILIARY,
DNSSEC_NSEC_MISMATCH,
+ DNSSEC_INCOMPATIBLE_SERVER,
+
_DNSSEC_RESULT_MAX,
_DNSSEC_RESULT_INVALID = -1
};
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index d565f99c09..b0db5bbb16 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -228,9 +228,11 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, us
assert(s);
if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) {
- /* even if we successfully receive a reply to a request announcing
- support for large packets, that does not mean we can necessarily
- receive large packets. */
+ /* Even if we successfully receive a reply to a
+ request announcing support for large packets, that
+ does not mean we can necessarily receive large
+ packets. */
+
if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
@@ -278,6 +280,17 @@ void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) {
s->n_failed_attempts = (unsigned) -1;
}
+void dns_server_packet_rrsig_missing(DnsServer *s) {
+ _cleanup_free_ char *ip = NULL;
+ assert(s);
+ assert(s->manager);
+
+ in_addr_to_string(s->family, &s->address, &ip);
+ log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", strna(ip));
+
+ s->rrsig_missing = true;
+}
+
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
@@ -307,6 +320,7 @@ DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
s->n_failed_attempts = 0;
s->verified_usec = 0;
+ s->rrsig_missing = false;
in_addr_to_string(s->family, &s->address, &ip);
log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip));
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index b07fc3af3d..3011904bfd 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -61,8 +61,6 @@ struct DnsServer {
int family;
union in_addr_union address;
- bool marked:1;
-
usec_t resend_timeout;
usec_t max_rtt;
@@ -73,6 +71,15 @@ struct DnsServer {
usec_t verified_usec;
usec_t features_grace_period_usec;
+ /* Indicates whether responses are augmented with RRSIG by
+ * server or not. Note that this is orthogonal to the feature
+ * level stuff, as it's only information describing responses,
+ * and has no effect on how the questions are asked. */
+ bool rrsig_missing:1;
+
+ /* Used when GC'ing old DNS servers when configuration changes. */
+ bool marked:1;
+
/* If linked is set, then this server appears in the servers linked list */
bool linked:1;
LIST_FIELDS(DnsServer, servers);
@@ -95,6 +102,7 @@ void dns_server_move_back_and_unmark(DnsServer *s);
void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size);
void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec);
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
+void dns_server_packet_rrsig_missing(DnsServer *s);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 2875330812..5933e0e462 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -478,10 +478,19 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) {
return;
}
+ if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER &&
+ t->scope->dnssec_mode == DNSSEC_YES) {
+ /* We are not in automatic downgrade mode, and the
+ * server is bad, refuse operation. */
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
if (!IN_SET(t->answer_dnssec_result,
- _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
- DNSSEC_VALIDATED, /* Answer is signed and validated successfully */
- DNSSEC_UNSIGNED)) { /* Answer is right-fully unsigned */
+ _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
+ DNSSEC_VALIDATED, /* Answer is signed and validated successfully */
+ DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */
+ DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */
dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
return;
}
@@ -1001,7 +1010,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (t->sent)
return 0;
- r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO);
if (r < 0)
return r;
@@ -1336,9 +1345,14 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
* - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
*/
- if (t->scope->dnssec_mode != DNSSEC_YES)
+ if (t->scope->dnssec_mode == DNSSEC_NO)
return 0;
+ if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO)
+ return 0; /* Server doesn't do DNSSEC, there's no point in requesting any RRs then. */
+ if (t->server && t->server->rrsig_missing)
+ return 0; /* Server handles DNSSEC requests, but isn't augmenting responses with RRSIGs. No point in trying DNSSEC then. */
+
DNS_ANSWER_FOREACH(rr, t->answer) {
if (dns_type_is_pseudo(rr->key->type))
@@ -1682,7 +1696,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
/* Checks if the RR we are looking for must be signed with an
* RRSIG. This is used for positive responses. */
- if (t->scope->dnssec_mode != DNSSEC_YES)
+ if (t->scope->dnssec_mode == DNSSEC_NO)
return false;
if (dns_type_is_pseudo(rr->key->type))
@@ -1819,7 +1833,7 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
/* Checks if we need to insist on NSEC/NSEC3 RRs for proving
* this negative reply */
- if (t->scope->dnssec_mode != DNSSEC_YES)
+ if (t->scope->dnssec_mode == DNSSEC_NO)
return false;
if (dns_type_is_pseudo(t->key->type))
@@ -1932,6 +1946,17 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
return found ? false : -ENXIO;
}
+static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) {
+ assert(t);
+ assert(rr);
+
+ /* We know that the root domain is signed, hence if it appears
+ * not to be signed, there's a problem with the DNS server */
+
+ return rr->key->class == DNS_CLASS_IN &&
+ dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key));
+}
+
int dns_transaction_validate_dnssec(DnsTransaction *t) {
_cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
bool dnskeys_finalized = false;
@@ -1945,7 +1970,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
* t->validated_keys, let's see which RRs we can now
* authenticate with that. */
- if (t->scope->dnssec_mode != DNSSEC_YES)
+ if (t->scope->dnssec_mode == DNSSEC_NO)
return 0;
/* Already validated */
@@ -1963,6 +1988,13 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (t->answer_source != DNS_TRANSACTION_NETWORK)
return 0;
+ if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO ||
+ (t->server && t->server->rrsig_missing)) {
+ /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
+ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+ return 0;
+ }
+
log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t));
/* First see if there are DNSKEYs we already known a validated DS for. */
@@ -2037,6 +2069,33 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
changed = true;
break;
}
+
+ r = dns_transaction_known_signed(t, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* This is an RR we know has to be signed. If it isn't this means
+ * the server is not attaching RRSIGs, hence complain. */
+
+ dns_server_packet_rrsig_missing(t->server);
+
+ if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
+
+ /* Downgrading is OK? If so, just consider the information unsigned */
+
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_insecure++;
+ changed = true;
+ break;
+ }
+
+ /* Otherwise, fail */
+ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+ return 0;
+ }
}
if (IN_SET(result,