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

Merged
merged 72 commits into from
Dec 18, 2024
Merged

feat: DNSSEC Validation #470

merged 72 commits into from
Dec 18, 2024

Conversation

developStorm
Copy link
Member

@developStorm developStorm commented Nov 1, 2024

resolves #441

Purpose

Adds support for validating DNSSEC records up to the root's chain-of-trust

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
@developStorm developStorm force-pushed the refactor/generic-cache branch from 63f8763 to 414dc59 Compare November 1, 2024 19:11
Base automatically changed from refactor/generic-cache to main November 1, 2024 19:55
@developStorm developStorm force-pushed the feat/basic-dnssec-validation branch from d907ca8 to 4d62421 Compare November 1, 2024 19:57
@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/

@developStorm developStorm force-pushed the feat/basic-dnssec-validation branch from 2d4c764 to 148eb28 Compare November 13, 2024 01:38
@developStorm
Copy link
Member Author

I conducted another round of tests on the top 10k domains, and the results aligned closely with Cloudflare, with a few explainable non-alignments remain:

  1. Domains under the .my TLD:
    These cannot be verified by us because they use an oversized RSA exponent that Go does not support. This issue is documented here: crypto/rsa: allow exponents larger than 1<<31-1 golang/go#3161. As discussed with @phillip-stephens, we agree that per RFC 4033, this should be classified as bogus.

  2. Specific domains (www.correoargentino.com.ar, xskt.com.vn, and futbollibrehd.pe):
    Cloudflare flagged these as bogus due to their inability to establish Secure Entry Point (SEP). Upon reviewing DNSViz, it appears these domains use NSEC3 records with iterations > 0, which is non-compliant with RFC9276. Cloudflare likely ignored these NSEC3 records, rendering them unable to confirm the non-existence of DS records.

    Since we are not a publicly facing recursive resolver and are not exposed to the risk of DoS attacks from this, I decided that we would continue to validate these NSEC3 records while printing a log: 9f7d891.

I'm comfortable with the shape of this PR now. Feel free to start reviewing @phillip-stephens @zakird.

@developStorm developStorm requested a review from zakird December 15, 2024 05:18
Copy link
Member

@zakird zakird left a comment

Choose a reason for hiding this comment

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

Overall this look great and I'm very excited to see DNSSEC validation added. @phillip-stephens is going to take a closer look.

@zakird
Copy link
Member

zakird commented Dec 15, 2024

Re 1: This might be worth fixing in ZCrypto and using that. I think that we can leave this however as a FR and merge in any case.

Copy link
Contributor

@phillip-stephens phillip-stephens left a comment

Choose a reason for hiding this comment

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

LGTM, I tested it locally on 1k domains and spot-checked a few of them. Also reverted the change to require go 1.20 since not sure we need to do that. Otherwise this looks really great and super excited about it! Thanks @developStorm!

@zakird zakird merged commit 60afb38 into main Dec 18, 2024
3 checks passed
@developStorm developStorm deleted the feat/basic-dnssec-validation branch December 18, 2024 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add DNSSEC Validation
3 participants