Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: DNSSEC Validation #470

Draft
wants to merge 51 commits into
base: main
Choose a base branch
from
Draft

Conversation

developStorm
Copy link
Member

@developStorm developStorm commented Nov 1, 2024

resolves #441

The level of detail in the validation results depends on the --result-verbosity= flag. In the short mode, validation results are not output (equivalent to no validation). In the normal mode, only the overall validation status is output. In the long mode, all relevant records used for validating this specific result (DS, DNSKEY, RRSIG) and the validation results for each individual RRset are printed. In the trace mode, all validation details for the entire query chain are available along the trace.

Since we are a DNS scanning tool, the resolution results may still hold value even in cases of DNSSEC validation failure. Therefore, a validation failure does not lead to a resolution failure - i.e., the resolution results will still be output normally. It is the responsibility of data consumers to check the DNSSEC validation status before using the results.

Example usage:

go run . A example.com --iterative --validate-dnssec --result-verbosity=long

Example output (long):

{
    "class": "IN",
    "name": "example.com",
    "results": {
        "A": {
            "data": {
                "answers": [
                    {
                        "answer": "93.184.215.14",
                        "class": "IN",
                        "name": "example.com",
                        "ttl": 3600,
                        "type": "A"
                    },
                    {
                        "algorithm": 13,
                        "class": "IN",
                        "expiration": "20241206041826",
                        "inception": "20241115121713",
                        "keytag": 60915,
                        "labels": 2,
                        "name": "example.com",
                        "original_ttl": 3600,
                        "signature": "vRP5fmIUOIHdIXJsnFkSLYL84OEh4isvOeIkPdGR1Ro+YUs4tTNO6J3ajnDvcIZfc5p+j8R35Z/SOOqD9ZY95w==",
                        "signer_name": "example.com.",
                        "ttl": 3600,
                        "type": "RRSIG",
                        "type_covered": 1
                    }
                ],
                "dnssec": {
                    "additionals": [
                        {
                            "error": "",
                            "rrset": {
                                "class": 4096,
                                "name": ".",
                                "type": 41
                            },
                            "sig": null,
                            "status": "Insecure"
                        }
                    ],
                    "answer": [
                        {
                            "error": "",
                            "rrset": {
                                "class": 1,
                                "name": "example.com.",
                                "type": 1
                            },
                            "sig": {
                                "algorithm": 13,
                                "class": "IN",
                                "expiration": "20241206041826",
                                "inception": "20241115121713",
                                "keytag": 60915,
                                "labels": 2,
                                "name": "example.com",
                                "original_ttl": 3600,
                                "signature": "vRP5fmIUOIHdIXJsnFkSLYL84OEh4isvOeIkPdGR1Ro+YUs4tTNO6J3ajnDvcIZfc5p+j8R35Z/SOOqD9ZY95w==",
                                "signer_name": "example.com.",
                                "ttl": 3600,
                                "type": "RRSIG",
                                "type_covered": 1
                            },
                            "status": "Secure"
                        }
                    ],
                    "authoritative": [
                        {
                            "error": "",
                            "rrset": {
                                "class": 1,
                                "name": "example.com.",
                                "type": 2
                            },
                            "sig": {
                                "algorithm": 13,
                                "class": "IN",
                                "expiration": "20241206020003",
                                "inception": "20241115121713",
                                "keytag": 60915,
                                "labels": 2,
                                "name": "example.com",
                                "original_ttl": 86400,
                                "signature": "9W8bAJT/4vA3nH8QU/NeM1n8bZZWn5PBkN26ix827VQGWY0YdMniks/1YCIaVNl16xly4s5IsVDtI9rLvaZORw==",
                                "signer_name": "example.com.",
                                "ttl": 86400,
                                "type": "RRSIG",
                                "type_covered": 2
                            },
                            "status": "Secure"
                        }
                    ],
                    "dnskey": [
                        {
                            "algorithm": 13,
                            "class": "IN",
                            "flags": 256,
                            "name": "example.com",
                            "protocol": 3,
                            "public_key": "ai2pvpijJjeNTpBu4yg6T375JqIStPtLABDTAILb+f4J7XpofUNXGQn6FpQvZ6CARWn2xQapbjGtDRjTf4qYxg==",
                            "ttl": 3600,
                            "type": "DNSKEY"
                        }
                    ],
                    "ds": [
                        {
                            "algorithm": 13,
                            "class": "IN",
                            "digest": "be74359954660069d5c63d200c39f5603827d7dd02b56f120ee9f3a86764247c",
                            "digest_type": 2,
                            "key_tag": 370,
                            "name": "example.com",
                            "ttl": 3600,
                            "type": "DS"
                        }
                    ],
                    "status": "Secure"
                },
                "flags": {
                    "authenticated": false,
                    "authoritative": true,
                    "checking_disabled": false,
                    "error_code": 0,
                    "opcode": 0,
                    "recursion_available": false,
                    "recursion_desired": false,
                    "response": true,
                    "truncated": false
                },
                "protocol": "udp",
            },
            "duration": 1.072630625,
            "status": "NOERROR",
        }
    }
}

Example output (normal):

{
    "name": "example.com",
    "results": {
        "A": {
            "data": {
                "answers": [
                    {
                        "answer": "93.184.215.14",
                        "class": "IN",
                        "name": "example.com",
                        "ttl": 3600,
                        "type": "A"
                    },
                    {
                        "algorithm": 13,
                        "class": "IN",
                        "expiration": "20241206041826",
                        "inception": "20241115121713",
                        "keytag": 60915,
                        "labels": 2,
                        "name": "example.com",
                        "original_ttl": 3600,
                        "signature": "vRP5fmIUOIHdIXJsnFkSLYL84OEh4isvOeIkPdGR1Ro+YUs4tTNO6J3ajnDvcIZfc5p+j8R35Z/SOOqD9ZY95w==",
                        "signer_name": "example.com.",
                        "ttl": 3600,
                        "type": "RRSIG",
                        "type_covered": 1
                    }
                ],
                "dnssec": {
                    "status": "Secure"
                },
                "protocol": "udp"
            },
            "duration": 0.803631958,
            "status": "NOERROR"
        }
    }
}
  • Implement error status handling according to RFC 4035, Section 4.3
  • Ensure proper handling of non-secure cases
  • Share retries
  • Address all linter issues
  • Limit DNSSEC to iterative mode only?
  • CLI option

@developStorm developStorm self-assigned this Nov 1, 2024
@developStorm developStorm linked an issue Nov 1, 2024 that may be closed by this pull request
Base automatically changed from refactor/generic-cache to main November 1, 2024 19:55
@phillip-stephens
Copy link
Contributor

Additionally, let's add integration tests to integration_tests.py for positive/negative test cases, as defined here
https://www.internetsociety.org/resources/deploy360/2013/dnssec-test-sites/

Have this super weird case where additionals from dnssec-tools.org contains an A and RRSIG for each of (nsm|nsw).dnssec-tools.org.
If identify by only type, these two will be clustered under the same set and could not validate.
@developStorm developStorm marked this pull request as ready for review November 18, 2024 15:12
@developStorm developStorm requested a review from a team as a code owner November 18, 2024 15:12
@zakird
Copy link
Member

zakird commented Nov 18, 2024

What have we done to test this? Can we run this against the top million websites or such and see how it goes? A lot of code.

@developStorm
Copy link
Member Author

What have we done to test this?

At this point it's just manual and unit tests performed against the above domains shared by Phillip, and I monitored request sanity with Wireshark. I can look into large scale test later.

@zakird zakird added this to the Version 2.0 milestone Nov 24, 2024
@@ -67,6 +67,7 @@ type QueryOptions struct {
ClassString string `long:"class" default:"INET" description:"DNS class to query. Options: INET, CSNET, CHAOS, HESIOD, NONE, ANY."`
ClientSubnetString string `long:"client-subnet" description:"Client subnet in CIDR format for EDNS0."`
Dnssec bool `long:"dnssec" description:"Requests DNSSEC records by setting the DNSSEC OK (DO) bit"`
ValidateDNSSEC bool `long:"validate-dnssec" description:"Validate DNSSEC records, only applicable with --iterative"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed allowing this even when not in `--iterative``.

DS []*DSAnswer `json:"ds" groups:"dnssec,long,trace"`
DNSKEY []*DNSKEYAnswer `json:"dnskey" groups:"dnssec,long,trace"`
Answer []DNSSECPerSetResult `json:"answer" groups:"dnssec,long,trace"`
Additionals []DNSSECPerSetResult `json:"additionals" groups:"dnssec,long,trace"`
Copy link
Member

@zakird zakird Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be Additional not Additionals per RFC and in matching the other types.

@developStorm developStorm marked this pull request as draft November 25, 2024 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add DNSSEC Validation
3 participants