[GH-ISSUE #1443] Document DNSSEC usage #671

Open
opened 2026-03-15 23:46:04 +03:00 by kerem · 8 comments
Owner

Originally created by @daxpedda on GitHub (Apr 7, 2021).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1443

Is your feature request related to a problem? Please describe.
I found no clear documentation on how DNSSEC is used except activating the crate feature.

Describe the solution you'd like
A better documentation. Either inside the rust documentation or in the README. As far as I understand, this has to be true:
https://docs.rs/trust-dns-resolver/0.20.1/trust_dns_resolver/config/struct.ResolverOpts.html#structfield.validate
It would also help to document what exactly this does, it is my understanding that this enforces DNSSEC.

Originally created by @daxpedda on GitHub (Apr 7, 2021). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1443 **Is your feature request related to a problem? Please describe.** I found no clear documentation on how DNSSEC is used except activating the crate feature. **Describe the solution you'd like** A better documentation. Either inside the rust documentation or in the README. As far as I understand, this has to be true: https://docs.rs/trust-dns-resolver/0.20.1/trust_dns_resolver/config/struct.ResolverOpts.html#structfield.validate It would also help to document what exactly this does, it is my understanding that this **enforces** DNSSEC.
Author
Owner

@wiktor-k commented on GitHub (May 24, 2021):

For people that arrive here looking for a working example:

let mut opts = ResolverOpts::default();
opts.validate = true;

let config = ResolverConfig::default();
let resolver = TokioAsyncResolver::tokio(config, opts)?;

let answers = resolver.lookup("cloudflare.com.", RecordType::TXT, Default::default()).await?;

Additionally one needs to enable one of the dnssec features in Cargo.toml:

trust-dns-resolver = { version = "0.20", features = ["dnssec-ring"] }

Without this feature ResolverOpts::validate seems to be a no-op.

Enabling dnssec feature but not a specific one (e.g. dnssec-ring) will yield an ResolveError { kind: Proto(ProtoError { kind: Message("self-signed dnskey is invalid") }) } error.

<!-- gh-comment-id:846978564 --> @wiktor-k commented on GitHub (May 24, 2021): For people that arrive here looking for a working example: ```rust let mut opts = ResolverOpts::default(); opts.validate = true; let config = ResolverConfig::default(); let resolver = TokioAsyncResolver::tokio(config, opts)?; let answers = resolver.lookup("cloudflare.com.", RecordType::TXT, Default::default()).await?; ``` Additionally one needs to enable [one of the dnssec features](https://github.com/bluejekyll/trust-dns/blob/main/crates/resolver/Cargo.toml#L51-L52) in Cargo.toml: ``` trust-dns-resolver = { version = "0.20", features = ["dnssec-ring"] } ``` Without this feature `ResolverOpts::validate` seems to be a no-op. Enabling `dnssec` feature but not a specific one (e.g. `dnssec-ring`) will yield an `ResolveError { kind: Proto(ProtoError { kind: Message("self-signed dnskey is invalid") }) }` error.
Author
Owner

@kevincox commented on GitHub (May 13, 2022):

I would like to see some docs here too. Because you would think that validate is what you want. But this doesn't seem to work for "correctly" unsigned records. For example when setting validate and trying to lookup the MX records for gmail.com (which is not signed) I get:

[src/main.rs:596] global.dns.mx_lookup("gmail.com").await = Err(
    ResolveError {
        kind: Proto(
            ProtoError {
                kind: RrsigsNotPresent {
                    name: Name("gmail.com."),
                    record_type: MX,
                },
            },
        ),
    },
)

This does work correctly for signed domains, but not for unsigned. I think the vast majority of people who want DNSSEC validation want it for validated domains but to fall back for unvalidated domains. How do do this isn't clear to me.

[src/main.rs:596] global.dns.mx_lookup("kevincox.ca").await = Ok(...)
<!-- gh-comment-id:1126058080 --> @kevincox commented on GitHub (May 13, 2022): I would like to see some docs here too. Because you would think that `validate` is what you want. But this doesn't seem to work for "correctly" unsigned records. For example when setting validate and trying to lookup the MX records for `gmail.com` (which is not signed) I get: ```rust [src/main.rs:596] global.dns.mx_lookup("gmail.com").await = Err( ResolveError { kind: Proto( ProtoError { kind: RrsigsNotPresent { name: Name("gmail.com."), record_type: MX, }, }, ), }, ) ``` This does work correctly for signed domains, but not for unsigned. I think the vast majority of people who want DNSSEC validation want it for validated domains but to fall back for unvalidated domains. How do do this isn't clear to me. ```rust [src/main.rs:596] global.dns.mx_lookup("kevincox.ca").await = Ok(...) ```
Author
Owner

@bluejekyll commented on GitHub (May 13, 2022):

This is something I dislike about DNSSEC, there is no elegant way to discern if a domain must be DNSSEC protected or not. The presence of DNSSEC records is clear, but the lack of DNSSEC records is not, which is exactly the scenario that you'd want to catch.

I have been thinking it would be better to start exposing records in a different way when DNSSEC validation is done so that they could be more easily used if DNSSEC is not present or other scenarios like DNSSEC validation is incomplete or fails. It would be good to find ways to expose this in the API, if folks have ideas the feedback would be really welcome.

<!-- gh-comment-id:1126268687 --> @bluejekyll commented on GitHub (May 13, 2022): This is something I dislike about DNSSEC, there is no elegant way to discern if a domain must be DNSSEC protected or not. The presence of DNSSEC records is clear, but the lack of DNSSEC records is not, which is exactly the scenario that you'd want to catch. I have been thinking it would be better to start exposing records in a different way when DNSSEC validation is done so that they could be more easily used if DNSSEC is not present or other scenarios like DNSSEC validation is incomplete or fails. It would be good to find ways to expose this in the API, if folks have ideas the feedback would be really welcome.
Author
Owner

@kevincox commented on GitHub (May 13, 2022):

Can't you get a confirmation of records not existing? For example looking at https://dnsviz.net/d/gmail.com/dnssec/ to continue using gmail.com as an example I see that it suggests that there was an NSEC3 record signed by com. validating that there is no DS record for gmail.com.

I'm not a DNSSEC expert though so I may be missing something.

<!-- gh-comment-id:1126276935 --> @kevincox commented on GitHub (May 13, 2022): Can't you get a confirmation of records not existing? For example looking at https://dnsviz.net/d/gmail.com/dnssec/ to continue using gmail.com as an example I see that it suggests that there was an `NSEC3` record signed by `com.` validating that there is no `DS` record for `gmail.com`. I'm not a DNSSEC expert though so I may be missing something.
Author
Owner

@kevincox commented on GitHub (May 13, 2022):

See also https://security.stackexchange.com/a/128159/10114.

<!-- gh-comment-id:1126278627 --> @kevincox commented on GitHub (May 13, 2022): See also https://security.stackexchange.com/a/128159/10114.
Author
Owner

@bluejekyll commented on GitHub (May 13, 2022):

Yes, you can do that, but It makes a bunch of assumptions about the nature of an attack on the system performing the resolution, that is, UDP responses could easily strip the DS and NSEC response from the parent zone. So it assumes that the MITM is only occurring on the gmail.com. zone in the example you've given and not also on the com. zone.

I think you need to essentially have some out-of-bounds exchange that requires pre-knowledge about which zones are signed and which are not, for example, we all know that com. is signed, therefore the lack of an NSEC(3) record for the DS would imply and MITM on the gmail.com. zone. Anyway, my complaint is just that it takes a lot of additional round-trips to evaluate all of this and requires predefined knowledge about the root and com. zones being signed.

<!-- gh-comment-id:1126287024 --> @bluejekyll commented on GitHub (May 13, 2022): Yes, you *can* do that, but It makes a bunch of assumptions about the nature of an attack on the system performing the resolution, that is, UDP responses could easily strip the `DS` and `NSEC` response from the parent zone. So it assumes that the MITM is only occurring on the `gmail.com.` zone in the example you've given and not also on the `com.` zone. I think you need to essentially have some out-of-bounds exchange that requires pre-knowledge about which zones are signed and which are not, for example, we all know that `com.` is signed, therefore the lack of an `NSEC(3)` record for the `DS` would imply and MITM on the `gmail.com.` zone. Anyway, my complaint is just that it takes a lot of additional round-trips to evaluate all of this and requires predefined knowledge about the root and `com.` zones being signed.
Author
Owner

@kevincox commented on GitHub (May 13, 2022):

I don't see how that attack could work. If the DS and NSEC responses are stripped the response will be lacking a signed record that states that the domain doesn't contain a DS record so you would treat it is invalid. This does mean that someone on the network path could DoS you but they could do that by just blocking all traffic.

I think you need to essentially have some out-of-bounds exchange that requires pre-knowledge about which zones are signed and which are not,

The root zone is signed and will tell you which TLD are signed. Then it just recurses up the chain.

Anyway, my complaint is just that it takes a lot of additional round-trips to evaluate

That may be true, but for many people that is worth it.

<!-- gh-comment-id:1126290895 --> @kevincox commented on GitHub (May 13, 2022): I don't see how that attack could work. If the `DS` and `NSEC` responses are stripped the response will be lacking a signed record that states that the domain doesn't contain a DS record so you would treat it is invalid. This does mean that someone on the network path could DoS you but they could do that by just blocking all traffic. > I think you need to essentially have some out-of-bounds exchange that requires pre-knowledge about which zones are signed and which are not, The root zone is signed and will tell you which TLD are signed. Then it just recurses up the chain. > Anyway, my complaint is just that it takes a lot of additional round-trips to evaluate That may be true, but for many people that is worth it.
Author
Owner

@kevincox commented on GitHub (May 13, 2022):

Anyways, I have broken off this feature request into https://github.com/bluejekyll/trust-dns/issues/1708. However as part of this issue I think this shuold definitely be documented as it is very surprising (to me at least) behaviour. I am not aware of any other resolver that behaves this way.

<!-- gh-comment-id:1126296904 --> @kevincox commented on GitHub (May 13, 2022): Anyways, I have broken off this feature request into https://github.com/bluejekyll/trust-dns/issues/1708. However as part of this issue I think this shuold definitely be documented as it is very surprising (to me at least) behaviour. I am not aware of any other resolver that behaves this way.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hickory-dns#671
No description provided.