diff --git a/bin/named/server.c b/bin/named/server.c index 25eb888e7a..6a9a39846d 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -24,6 +24,8 @@ #include #include +#include + #ifdef HAVE_DNSTAP #include #endif @@ -280,10 +282,10 @@ struct zonelistentry { * asynchronously. */ typedef struct matching_view_ctx { - isc_netaddr_t *srcaddr; - isc_netaddr_t *destaddr; + isc_netaddr_t srcaddr; + isc_netaddr_t destaddr; dns_message_t *message; - dns_aclenv_t *env; + dns_aclenv_t *aclenv; ns_server_t *sctx; isc_loop_t *loop; isc_job_cb cb; @@ -9299,6 +9301,8 @@ get_matching_view_done(void *cbarg) { mvctx->cb(mvctx->cbarg); + dns_aclenv_detach(&mvctx->aclenv); + if (mvctx->quota_result == ISC_R_SUCCESS) { isc_quota_release(&mvctx->sctx->sig0checksquota); } @@ -9340,10 +9344,10 @@ get_matching_view_continue(void *cbarg, isc_result_t result) { tsig = dns_tsigkey_identity(mvctx->message->tsigkey); } - if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients, - mvctx->env) && - dns_acl_allowed(mvctx->destaddr, tsig, - mvctx->view->matchdestinations, mvctx->env) && + if (dns_acl_allowed(&mvctx->srcaddr, tsig, mvctx->view->matchclients, + mvctx->aclenv) && + dns_acl_allowed(&mvctx->destaddr, tsig, + mvctx->view->matchdestinations, mvctx->aclenv) && !(mvctx->view->matchrecursiveonly && (mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0)) { @@ -9415,9 +9419,9 @@ get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx)); *mvctx = (matching_view_ctx_t){ - .srcaddr = srcaddr, - .destaddr = destaddr, - .env = env, + .srcaddr = *srcaddr, + .destaddr = *destaddr, + .aclenv = dns_aclenv_ref(env), .cb = cb, .cbarg = cbarg, .sigresult = sigresult, diff --git a/bin/tests/system/sig0/ns1/named.conf.j2 b/bin/tests/system/sig0/ns1/named.conf.j2 new file mode 100644 index 0000000000..724a40c58c --- /dev/null +++ b/bin/tests/system/sig0/ns1/named.conf.j2 @@ -0,0 +1,41 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +view "v1" { + match-clients { any; }; + zone "." { + type hint; + file "/dev/null"; + }; +}; diff --git a/bin/tests/system/sig0/setup.sh b/bin/tests/system/sig0/setup.sh new file mode 100644 index 0000000000..64a8c3a481 --- /dev/null +++ b/bin/tests/system/sig0/setup.sh @@ -0,0 +1,17 @@ +#!/bin/sh -e + +# 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. + +# shellcheck source=conf.sh +. ../conf.sh + +key=$($KEYGEN -q -a RSASHA256 -b 2048 sig0.) diff --git a/bin/tests/system/sig0/tests_sig0.py b/bin/tests/system/sig0/tests_sig0.py new file mode 100644 index 0000000000..500d2b5eab --- /dev/null +++ b/bin/tests/system/sig0/tests_sig0.py @@ -0,0 +1,119 @@ +# 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. + +import base64 +import glob +import os +import struct +import time + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +import dns.flags +import dns.message +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.renderer +import dns.rrset + +import isctest + + +def load_bind_private_key(filename): + """Parses a BIND 9 .private key file.""" + with open(filename, "r", encoding="utf-8") as f: + lines = f.readlines() + + data = {} + for line in lines: + if ":" in line: + key, value = line.split(":", 1) + data[key.strip()] = value.strip() + + def b64int(k): + return int.from_bytes(base64.b64decode(data[k]), byteorder="big") + + rsa_key = rsa.RSAPrivateNumbers( + p=b64int("Prime1"), + q=b64int("Prime2"), + d=b64int("PrivateExponent"), + dmp1=b64int("Exponent1"), + dmq1=b64int("Exponent2"), + iqmp=b64int("Coefficient"), + public_numbers=rsa.RSAPublicNumbers( + e=b64int("PublicExponent"), n=b64int("Modulus") + ), + ).private_key(default_backend()) + + return rsa_key + + +def make_sig0_query(key_file, key_name_str): + private_key = load_bind_private_key(key_file) + + qname = dns.name.from_text(".") + query = dns.message.make_query(qname, dns.rdatatype.SOA) + query.flags |= dns.flags.RD + + # Render message to bytes (needed for signing) + renderer = dns.renderer.Renderer() + query.to_wire(renderer) + msg_bytes = renderer.get_wire() + + # SIG(0) Constants + basename = os.path.basename(key_file) + key_tag = int(basename.split("+")[2].split(".")[0]) + + now = int(time.time()) + expiration = now + 3600 + inception = now - 3600 + signer_name = dns.name.from_text(key_name_str) + + # Construct SIG RDATA header (0=SIG(0), 8=RSASHA256, 0=Labels) + sig_rdata_header = struct.pack( + "!HBBIIIH", 0, 8, 0, 0, expiration, inception, key_tag + ) + + sig_rdata_pre_sig = sig_rdata_header + signer_name.to_wire() + + # Sign: ( SIG RDATA sans signature ) + ( Message ) + signature = private_key.sign( + sig_rdata_pre_sig + msg_bytes, padding.PKCS1v15(), hashes.SHA256() + ) + + # Create the SIG RR + full_sig_rdata = sig_rdata_pre_sig + signature + sig_rr = dns.rdata.from_wire( + dns.rdataclass.ANY, + dns.rdatatype.SIG, + full_sig_rdata, + 0, + len(full_sig_rdata), + ) + sig_rrset = dns.rrset.from_rdata(qname, 0, sig_rr) + query.additional.append(sig_rrset) + + return query + + +def test_sig0_acl_bypass(): + key_files = glob.glob("Ksig0.+*.private") + assert len(key_files) == 1 + + query = make_sig0_query(key_files[0], "sig0.") + + # Send the query + res = isctest.query.tcp(query, "10.53.0.1") + isctest.check.servfail(res)