mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 16:59:59 -04:00
chg: test: Add malformed ECDSA DNSKEY tests to dnssec_py
Merge branch 'nicki/pytest-dnssec-py-dnskey-malformed' into 'main' See merge request isc-projects/bind9!12210
This commit is contained in:
commit
b8c1b645ea
9 changed files with 109 additions and 119 deletions
|
|
@ -1,29 +0,0 @@
|
|||
options {
|
||||
query-source address 10.53.0.2;
|
||||
notify-source 10.53.0.2;
|
||||
transfer-source 10.53.0.2;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.2; };
|
||||
listen-on-v6 { none; };
|
||||
allow-transfer { any; };
|
||||
recursion no;
|
||||
dnssec-validation yes;
|
||||
|
||||
/* Keep the order of RRSIGs in the response static. */
|
||||
rrset-order {
|
||||
name "example." order none;
|
||||
};
|
||||
};
|
||||
|
||||
zone example. {
|
||||
type primary;
|
||||
file "example.db.signed.malformed";
|
||||
};
|
||||
|
||||
zone truncated.selfsigned. {
|
||||
type primary;
|
||||
file "truncated.selfsigned.db.signed";
|
||||
};
|
||||
|
||||
include "trusted.conf";
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
trust-anchors {
|
||||
example. static-key 257 3 14 "@ksk_public_key@";
|
||||
|
||||
/*
|
||||
* The key tag in the trust anchor must match that of the revoked
|
||||
* truncated self-signed key in the truncated.selfsigned. zone.
|
||||
*
|
||||
* The DNSKEY contents are intentionally different here, because the
|
||||
* key doesn't have the revoked bit here and that flag is part of the
|
||||
* key tag. The following decodes to key tag 33167, which is the same
|
||||
* as the revoked truncated key in the zone file.
|
||||
*/
|
||||
truncated.selfsigned. static-key 257 3 14 "fYA=";
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
options {
|
||||
query-source address 10.53.0.3;
|
||||
notify-source 10.53.0.3;
|
||||
transfer-source 10.53.0.3;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.3; };
|
||||
listen-on-v6 { none; };
|
||||
allow-transfer { any; };
|
||||
dnssec-validation yes;
|
||||
|
||||
/* This is the default, but the test relies on it. */
|
||||
max-validation-failures-per-fetch 1;
|
||||
};
|
||||
|
||||
zone "example." {
|
||||
type static-stub;
|
||||
server-addresses { 10.53.0.2; };
|
||||
};
|
||||
|
||||
zone "truncated.selfsigned." {
|
||||
type static-stub;
|
||||
server-addresses { 10.53.0.2; };
|
||||
};
|
||||
|
||||
include "trusted.conf";
|
||||
|
|
@ -1 +0,0 @@
|
|||
../ns2/trusted.conf.j2
|
||||
|
|
@ -15,5 +15,7 @@ DNSSEC_PY_MARK = pytest.mark.extra_artifacts(
|
|||
[
|
||||
"ns*/dsset-*",
|
||||
"ns*/trusted.conf",
|
||||
"ns*/zones/*.db",
|
||||
"ns*/zones/*.db.signed",
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
$TTL 300
|
||||
@ IN SOA mname1. . (
|
||||
1 ; serial
|
||||
600 ; refresh
|
||||
600 ; retry
|
||||
1200 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
|
||||
@ NS @
|
||||
@ A 10.53.0.2
|
||||
{% include '_common/zones/soa.partial.db.j2' %}
|
||||
{% include '_common/zones/ns.partial.db.j2' %}
|
||||
|
||||
; All of the following DNSKEYs are malformed and have the same key tag - 20071.
|
||||
; The keys use invalid parameters for the ECDSA curve.
|
||||
|
||||
{% raw %}
|
||||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5Or0NNksES2iAAwmRfEEnH/hzk+8xF
|
||||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfjFg5Y9Ytl2+UR1JO/UNNksES2iAAwmRfEEnH/hzk+8v3
|
||||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sW2HoOfwFg5Y1ctl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3
|
||||
|
|
@ -112,6 +104,7 @@ $TTL 300
|
|||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF534ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5O/UNNksES2h+/wmRfEEnH/hzk+8v3
|
||||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3QngZjMZtl1Wd/fvtHF/3sU3HoOfwFg5ZFAtl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3
|
||||
@ DNSKEY 256 3 14 rdZ3Mr7XEQoEdD5EF1z4ulhFFbCNxbiu1BvD9cNCeMAGu8qmCKB+KmHS3YPgZjMZtl1Wd/fvtHF/3sU3HoOfwFg5Y9Ytl2+URx5O/UNNksES2iAAwmRfEEnH/hzk+8v3
|
||||
{% endraw %}
|
||||
|
||||
malformed-dnskey A 10.53.0.2
|
||||
invalid-rrsig A 10.53.0.2
|
||||
multiple-rrsigs A 10.53.0.2
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
{% raw %}
|
||||
$TTL 300
|
||||
|
||||
@ IN SOA mname1. . (
|
||||
|
|
@ -27,3 +28,4 @@ a A 10.53.0.2
|
|||
a RRSIG A 14 3 86400 20950926153053 20251013153053 33167 @ xxxxv31CNatB9xzj3AfTMlwiO0OqxbpJ cWrHN8zjj1ScXpqrHITfG/CZpoECDLWF wkXshDB/QMxHrnXkPKEcR2c9o5tcQT5R nHvtr7HT4Ob5PcY5DnItf3OWhE+bocmW
|
||||
a NSEC @ A RRSIG NSEC
|
||||
a RRSIG NSEC 14 3 0 20950926153053 20251013153053 33167 @ xxxxwMWbUxb3ScBKEVheQ2wFqujc6cyt 28GVCU0wPrBpK72HSsgdYme7IG8ZXGfa IWSU1Kf/om5+El7Tf2vDs7aI1yI7e7YG D5IxMejQg5v3/wtP7AJZXP5K9ICjq/ph
|
||||
{% endraw %}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from pathlib import Path
|
||||
from re import compile as Re
|
||||
|
||||
import base64
|
||||
|
|
@ -25,8 +26,14 @@ import dns.rdtypes.ANY.RRSIG
|
|||
import dns.zone
|
||||
import pytest
|
||||
|
||||
from dnssec_py.common import DNSSEC_PY_MARK
|
||||
from isctest.template import NS2, TrustAnchor, zones
|
||||
from isctest.zone import Zone, configure_root
|
||||
|
||||
import isctest
|
||||
|
||||
pytestmark = DNSSEC_PY_MARK
|
||||
|
||||
|
||||
def generate_key():
|
||||
algorithm = dns.dnssec.Algorithm.ECDSAP384SHA384
|
||||
|
|
@ -65,20 +72,28 @@ def create_malformed_rr(rr, n=0):
|
|||
|
||||
|
||||
def bootstrap():
|
||||
zone = dns.zone.from_file("ns2/example.db.in", origin="example.")
|
||||
zone = Zone("dnskey-malformed", NS2, signed=True)
|
||||
lifetime = 300
|
||||
|
||||
# geneate KSK, avoid key tag collision with ZSKs
|
||||
# generate KSK, avoid key tag collision with ZSKs
|
||||
while True:
|
||||
ksk_private_key, ksk_dnskey = generate_key()
|
||||
if dns.dnssec.key_id(ksk_dnskey) != MALFORMED_ZSK_KEY_TAG:
|
||||
break
|
||||
keys = [(ksk_private_key, ksk_dnskey)]
|
||||
|
||||
# render unsigned zone file
|
||||
zone.render()
|
||||
|
||||
# read the rendered zone
|
||||
unsigned_path = str(Path(zone.ns.name) / zone.filepath_unsigned)
|
||||
signed_path = str(Path(zone.ns.name) / zone.filepath_signed)
|
||||
zoneobj = dns.zone.from_file(unsigned_path, origin="dnskey-malformed.")
|
||||
|
||||
# sign the zone (including the malformed ZSKs) with KSK
|
||||
with zone.writer() as txn:
|
||||
with zoneobj.writer() as txn:
|
||||
dns.dnssec.sign_zone(
|
||||
zone=zone,
|
||||
zone=zoneobj,
|
||||
txn=txn,
|
||||
keys=keys,
|
||||
lifetime=lifetime,
|
||||
|
|
@ -86,44 +101,51 @@ def bootstrap():
|
|||
deterministic=False, # for OpenSSL<3.2.0 compat
|
||||
)
|
||||
|
||||
# force use of the malformed ZSKs for dnssec verification
|
||||
# malformed-dnskey.example. has only one invalid RRSIG and is only signed
|
||||
# with malformed ZSKs
|
||||
malformed_rrset = zone.get_rdataset("malformed-dnskey", "RRSIG", "A")
|
||||
rr = malformed_rrset.pop()
|
||||
malformed_rrset.add(create_malformed_rr(rr))
|
||||
# force use of the malformed ZSKs for invalid-rrsig.dnskey-malformed;
|
||||
# the record only has one invalid RRSIG signed with a malformed ZSK
|
||||
invalid_rrset = zoneobj.get_rdataset("invalid-rrsig", "RRSIG", "A")
|
||||
rr = invalid_rrset.pop()
|
||||
invalid_rrset.add(create_malformed_rr(rr))
|
||||
|
||||
# multiple-rrsigs.example. contains a lot of RRSIGS with the same invalid
|
||||
# signature using malformed RRSIG, and one valid RRSIG
|
||||
multiple_rrset = zone.get_rdataset("multiple-rrsigs", "RRSIG", "A")
|
||||
# multiple-rrsigs.dnskey-malformed contains a lot of RRSIGs with the same
|
||||
# invalid signature using a malformed key, and one valid RRSIG
|
||||
multiple_rrset = zoneobj.get_rdataset("multiple-rrsigs", "RRSIG", "A")
|
||||
rr = multiple_rrset.pop()
|
||||
for i in range(99):
|
||||
multiple_rrset.add(create_malformed_rr(rr, i))
|
||||
multiple_rrset.add(rr)
|
||||
|
||||
zone.to_file("ns2/example.db.signed.malformed")
|
||||
zoneobj.to_file(signed_path)
|
||||
|
||||
root = configure_root([zone])
|
||||
ksk_key_b64 = base64.b64encode(ksk_dnskey.key).decode()
|
||||
ksk_ta = TrustAnchor("dnskey-malformed", "static-key", f'257 3 14 "{ksk_key_b64}"')
|
||||
|
||||
return {
|
||||
"ksk_public_key": base64.b64encode(ksk_dnskey.key).decode(),
|
||||
"rrset_order_none": ["dnskey-malformed"],
|
||||
"trust_anchors": [*root.trust_anchors(), ksk_ta],
|
||||
"zones": zones([root, zone]),
|
||||
}
|
||||
|
||||
|
||||
def test_malformed_ecdsa(ns3):
|
||||
log_validation_failed = Re(r"malformed-dnskey\.example/A\): validation failed")
|
||||
def test_malformed_ecdsa(ns9):
|
||||
log_validation_failed = Re(
|
||||
r"invalid-rrsig\.dnskey-malformed/A\): validation failed"
|
||||
)
|
||||
log_openssl_failure = Re("EVP_PKEY_fromdata.*failed")
|
||||
log_openssl_version = Re("linked to OpenSSL version: OpenSSL ([0-9]+)")
|
||||
|
||||
msg = isctest.query.create("malformed-dnskey.example", "A")
|
||||
msg = isctest.query.create("invalid-rrsig.dnskey-malformed", "A")
|
||||
|
||||
openssl_vers = ns3.log.grep(log_openssl_version)
|
||||
openssl_vers = ns9.log.grep(log_openssl_version)
|
||||
if (
|
||||
openssl_vers
|
||||
and int(openssl_vers[0].group(1)) >= 3
|
||||
and os.getenv("FEATURE_QUERYTRACE") == "1"
|
||||
):
|
||||
# extra check for OpenSSL 3.0.0+
|
||||
with ns3.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||
with ns9.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(msg, ns9.ip)
|
||||
|
||||
# check the OpenSSL-specific log message appears just once
|
||||
matches = watcher.wait_for_all(
|
||||
|
|
@ -134,25 +156,27 @@ def test_malformed_ecdsa(ns3):
|
|||
)
|
||||
assert len([m for m in matches if m.re == log_openssl_failure]) == 1
|
||||
else:
|
||||
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||
res = isctest.query.tcp(msg, ns9.ip)
|
||||
|
||||
isctest.check.servfail(res)
|
||||
|
||||
|
||||
def test_multiple_rrsigs(ns3):
|
||||
log_validation_failed = Re(r"multiple-rrsigs\.example/A\): validation failed")
|
||||
def test_multiple_rrsigs(ns2, ns9):
|
||||
log_validation_failed = Re(
|
||||
r"multiple-rrsigs\.dnskey-malformed/A\): validation failed"
|
||||
)
|
||||
log_openssl_failure = Re("EVP_PKEY_fromdata.*failed")
|
||||
log_openssl_version = Re("linked to OpenSSL version: OpenSSL ([0-9]+)")
|
||||
|
||||
msg = isctest.query.create("multiple-rrsigs.example", "A")
|
||||
msg = isctest.query.create("multiple-rrsigs.dnskey-malformed", "A")
|
||||
|
||||
# Check the order of returned RRSIGs from auth. Due to rrset-order none;
|
||||
# this should remain constant for the remainder of the test.
|
||||
# Ensure the first two RRSIGs are malformed, otherwise skip the test.
|
||||
res = isctest.query.tcp(msg, "10.53.0.2")
|
||||
res = isctest.query.tcp(msg, ns2.ip)
|
||||
rrsigs = res.get_rrset(
|
||||
res.answer,
|
||||
dns.name.from_text("multiple-rrsigs.example."),
|
||||
dns.name.from_text("multiple-rrsigs.dnskey-malformed."),
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.RRSIG,
|
||||
dns.rdatatype.A,
|
||||
|
|
@ -164,15 +188,15 @@ def test_multiple_rrsigs(ns3):
|
|||
):
|
||||
pytest.skip("valid RRSIG listed first in response, re-run test")
|
||||
|
||||
openssl_vers = ns3.log.grep(log_openssl_version)
|
||||
openssl_vers = ns9.log.grep(log_openssl_version)
|
||||
if (
|
||||
openssl_vers
|
||||
and int(openssl_vers[0].group(1)) >= 3
|
||||
and os.getenv("FEATURE_QUERYTRACE") == "1"
|
||||
):
|
||||
# extra check for OpenSSL 3.0.0+
|
||||
with ns3.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||
with ns9.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(msg, ns9.ip)
|
||||
|
||||
# check the OpenSSL-specific log message appears exactly twice:
|
||||
# one failure is allowed by setting max-validation-failures-per-fetch 1;
|
||||
|
|
@ -184,12 +208,6 @@ def test_multiple_rrsigs(ns3):
|
|||
)
|
||||
assert len([m for m in matches if m.re == log_openssl_failure]) == 2
|
||||
else:
|
||||
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||
res = isctest.query.tcp(msg, ns9.ip)
|
||||
|
||||
isctest.check.servfail(res)
|
||||
|
||||
|
||||
def test_truncated_dnskey():
|
||||
msg = isctest.query.create("a.truncated.selfsigned.", "A")
|
||||
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||
isctest.check.servfail(res)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from re import compile as Re
|
||||
|
||||
from dnssec_py.common import DNSSEC_PY_MARK
|
||||
from isctest.template import NS2, TrustAnchor, zones
|
||||
from isctest.zone import Zone, configure_root
|
||||
|
||||
import isctest
|
||||
|
||||
pytestmark = DNSSEC_PY_MARK
|
||||
|
||||
|
||||
def bootstrap():
|
||||
zone = Zone("truncated.selfsigned", NS2, signed=True)
|
||||
|
||||
root = configure_root([zone], signed=False) # just delegation, TA is added directly
|
||||
|
||||
# The trust anchor key tag must match the revoked truncated self-signed key
|
||||
# in the zone (key tag 33167). The flags differ here (257 vs 385) because
|
||||
# the revoked bit is not part of the trust anchor, but it is part of the key
|
||||
# tag calculation.
|
||||
zone_ta = TrustAnchor("truncated.selfsigned", "static-key", '257 3 14 "fYA="')
|
||||
|
||||
return {
|
||||
"trust_anchors": [zone_ta],
|
||||
"zones": zones([root, zone]),
|
||||
}
|
||||
|
||||
|
||||
def test_truncated_dnskey(ns9):
|
||||
msg = isctest.query.create("a.truncated.selfsigned.", "A")
|
||||
with ns9.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(msg, ns9.ip)
|
||||
watcher.wait_for_line(Re("a.truncated.selfsigned/A.*broken trust chain"))
|
||||
isctest.check.servfail(res)
|
||||
Loading…
Reference in a new issue