]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
conf: implemented certificate hostname validation
authorJan Doskočil <jan.doskocil@nic.cz>
Wed, 14 May 2025 11:03:39 +0000 (13:03 +0200)
committerDaniel Salzman <daniel.salzman@nic.cz>
Mon, 23 Jun 2025 15:55:14 +0000 (17:55 +0200)
29 files changed:
distro/pkg/deb/libknot15.symbols
doc/configuration.rst
doc/reference.rst
src/knot/conf/conf.c
src/knot/conf/conf.h
src/knot/conf/schema.c
src/knot/conf/schema.h
src/knot/conf/tools.c
src/knot/modules/dnsproxy/dnsproxy.c
src/knot/query/quic-requestor.c
src/knot/query/quic-requestor.h
src/knot/query/requestor.c
src/knot/query/requestor.h
src/knot/query/tls-requestor.c
src/knot/query/tls-requestor.h
src/knot/server/server.c
src/knot/updates/acl.c
src/libknot/errcode.h
src/libknot/error.c
src/libknot/quic/quic.c
src/libknot/quic/tls.c
src/libknot/quic/tls_common.c
src/libknot/quic/tls_common.h
src/utils/kxdpgun/main.c
tests-extra/tests/quic/xfr/test.py
tests-extra/tests/tls/xfr/test.py
tests-extra/tools/dnstest/server.py
tests-extra/tools/dnstest/test.py
tests/knot/test_requestor.c

index ee8452a438998d86f9bf920ca1a9a5969dfa0149..0c7250b5500904e1249ed863bc155607b2b31840 100644 (file)
@@ -193,6 +193,8 @@ libknot.so.15 libknot15 #MINVER#
  knot_tcp_sweep@Base 3.4.0
  knot_tcp_table_free@Base 3.4.0
  knot_tcp_table_new@Base 3.4.0
+ knot_tls_cert_check@Base 3.5.0
+ knot_tls_cert_check_hostnames@Base 3.5.0
  knot_tls_conn_block@Base 3.4.0
  knot_tls_conn_del@Base 3.4.0
  knot_tls_conn_new@Base 3.4.0
index 24d8d1ebeb8e960dccd0a71856724ded28ad70ba..5cd51faee9414d96d90a1e03f27d8c69365b910a 100644 (file)
@@ -981,6 +981,8 @@ Strict authentication:
 Note that the automatic ACL doesn't work in this case due to asymmetrical
 configuration. The secondary can authenticate using TSIG.
 
+With PIN checks:
+
 .. panels::
 
   Primary:
@@ -1042,6 +1044,72 @@ configuration. The secondary can authenticate using TSIG.
         master: primary
         acl: primary_notify
 
+With CA and hostname checks:
+
+.. panels::
+
+  Primary
+
+  .. code-block:: console
+
+    server:
+        listen-quic: ::1
+        cert-file: primary-cert.pem
+        key-file: primary-key.pem
+
+    key:
+      - id: secondary_key
+        algorithm: hmac-sha256
+        secret: S059OFJv1SCDdR2P6JKENgWaM409iq2X44igcJdERhc=
+
+    remote:
+      - id: secondary
+        address: ::2
+        quic: on
+
+    acl:
+      - id: secondary_xfr
+        address: ::2
+        key: secondary_key  # TSIG for secondary authentication
+        action: transfer
+
+    zone:
+      - domain: example.com
+        notify: secondary
+        acl: secondary_xfr
+
+  ---
+
+  Secondary:
+
+  .. code-block:: console
+
+    server:
+        listen-quic: ::2
+        ca-file: ca-cert.pem
+
+    key:
+      - id: secondary_key
+        algorithm: hmac-sha256
+        secret: S059OFJv1SCDdR2P6JKENgWaM409iq2X44igcJdERhc=
+
+    remote:
+      - id: primary
+        address: ::1
+        key: secondary_key  # TSIG for secondary authentication
+        quic: on
+
+    acl:
+      - id: primary_notify
+        address: ::1
+        cert-hostname: "Primary Knot"
+        action: notify
+
+    zone:
+      - domain: example.com
+        master: primary
+        acl: primary_notify
+
 Mutual authentication:
 ......................
 
@@ -1049,6 +1117,8 @@ The :rfc:`mutual authentication<9103#section-9.3.3>` guarantees authentication
 for both the primary and the secondary. In this case, TSIG would be redundant.
 This mode is recommended if possible.
 
+With PIN checks:
+
 .. panels::
 
   Primary:
@@ -1089,11 +1159,102 @@ This mode is recommended if possible.
       - domain: example.com
         master: primary
 
+With CA and hostname checks:
+
+.. panels::
+
+  Primary:
+
+  .. code-block:: console
+
+    server:
+        listen-quic: ::1
+        ca-file: ca-cert.pem
+        cert-file: primary-cert.pem
+        key-file: primary-key.pem
+        automatic-acl: on
+
+    remote:
+      - id: secondary
+        address: ::2
+        quic: on
+        cert-hostname: "Secondary Knot"
+
+    zone:
+      - domain: example.com
+        notify: secondary
+
+  ---
+
+  Secondary:
+
+  .. code-block:: console
+
+    server:
+        listen-quic: ::2
+        ca-file: ca-cert.pem
+        cert-file: secondary-cert.pem
+        key-file: secondary-key.pem
+        automatic-acl: on
+
+    remote:
+      - id: primary
+        address: ::1
+        quic: on
+        cert-hostname: "Primary Knot"
+
+    zone:
+      - domain: example.com
+        master: primary
+
+.. TIP::
+
+  Using GnuTLS certtool you can generate a CA certificate with its private key:
+
+  .. code-block:: console
+
+    $ certtool --generate-privkey --key-type ed25519 --outfile ca-key.pem
+    $ echo -e "cn = \"My Example CA\"\nca\ncert_signing_key\nexpiration_days = 3650" >ca-template.info
+    $ certtool --generate-self-signed --load-privkey ca-key.pem \
+               --template ca-template.info --outfile ca-cert.pem
+
+  Then create certificates signed with this CA like so:
+
+  .. code-block:: console
+
+    $ CERT_NAME="primary"
+    $ certtool --generate-privkey --key-type ed25519 --outfile ${CERT_NAME}-key.pem
+    $ echo -e "dns_name = \"${CERT_NAME} server\"\nexpiration_days = 365" >${CERT_NAME}-template.info
+    $ certtool --generate-certificate --load-privkey ${CERT_NAME}-key.pem \
+               --load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem \
+               --template ${CERT_NAME}-template.info --outfile ${CERT_NAME}-cert.pem
+
+  If you want to use a wildcard DNSName in your certificate, beware that
+  GnuTLS, which is the TLS backend for Knot DNS, **will not verify** wildcard
+  names directly under TLDs (like ``*.example``).
+
+  To see a server's TLS hostnames:
+
+  .. code-block:: console
+
+    $ kdig @1.1.1.1 +tls -dd
+    ;; DEBUG: Querying for owner(.), class(1), type(2), server(1.1.1.1), port(853), protocol(TCP)
+    ;; DEBUG: TLS, received certificate hierarchy:
+    ;; DEBUG:  #1, CN=cloudflare-dns.com,O=Cloudflare\, Inc.,L=San Francisco,ST=California,C=US
+    ;; DEBUG:      Subject Alternative Name:
+    ;; DEBUG:        DNSname: cloudflare-dns.com
+    ;; DEBUG:        DNSname: *.cloudflare-dns.com
+    ;; DEBUG:        DNSname: one.one.one.one
+    [...]
+
+  Knot DNS will only verify hostnames under the *Subject Alternative Name*
+  extension in compliance with :rfc:`8310#section-8.1`.
+
 .. NOTE::
 
-  Instead of certificate verification with specified authentication domain name,
-  Knot DNS uses certificate public key pinning. This approach has much lower
-  overhead and in most cases simplifies configuration and certificate management.
+  Certificate validation by CA and hostname is more computationally expensive
+  than by PIN, but PIN checking has the disadvantage of relying on constantness
+  of the public key.
 
 .. _DNS_over_TLS:
 
index bf57d88f7eeeb1f9a4fe5a56ee76ca4bb0f33aa5..c22b69acc7bde86f243f0cbde71514be84ee35f8 100644 (file)
@@ -210,6 +210,7 @@ General options related to the server.
      udp-max-payload-ipv6: SIZE
      key-file: STR
      cert-file: STR
+     ca-file: STR ...
      edns-client-subnet: BOOL
      answer-rotation: BOOL
      automatic-acl: BOOL
@@ -582,6 +583,17 @@ A non-absolute path is relative to the :file:`@config_dir@` directory.
 
 *Default:* one-time in-memory certificate
 
+.. _server_ca-file:
+
+ca-file
+-------
+
+Specifies one or more paths to load trusted Certificate Authorities (CAs) from.
+An empty string ("") means the system’s default trusted CAs. The loaded CAs are used 
+for remote certificate validation (:ref:`acl_cert-hostname` and :ref:`remote_cert-hostname`).
+
+*Default:* not set
+
 .. _server_edns-client-subnet:
 
 edns-client-subnet
@@ -1457,6 +1469,7 @@ transfer, target for a notification, etc.).
      tls: BOOL
      key: key_id
      cert-key: BASE64 ...
+     cert-hostname: STR ...
      block-notify-after-transfer: BOOL
      no-edns: BOOL
      automatic-acl: BOOL
@@ -1561,17 +1574,29 @@ the communication with the remote server.
 cert-key
 --------
 
-An ordered list of remote certificate public key PINs. If the list is non-empty,
-communication with the remote is possible only via QUIC or TLS protocols and
+An ordered list of up to 4 remote certificate public key PINs. If the list is non-empty,
+communication with the remote is only possible via QUIC or TLS protocols, and
 a peer certificate is required. The peer certificate key must match one of the
 specified PINs.
 
 A PIN is a unique identifier that represents the public key of the peer certificate.
-It's a base64-encoded SHA-256 hash of the public key. This identifier
+It's a base64-encoded SHA-256 hash of the public key. This identifier usually
 remains the same on a certificate renewal.
 
 *Default:* not set
 
+.. _remote_cert-hostname:
+
+cert-hostname
+-------------
+
+An ordered list of up to 4 hostnames to match against peer's certificate. At least
+one must match for successful certificate validation (see :ref:`server_ca-file`).
+If the list is non-empty, communication with the remote is only possible via
+QUIC or TLS protocols, and a peer certificate is required.
+
+*Default:* not set
+
 .. _remote_block-notify-after-transfer:
 
 block-notify-after-transfer
@@ -1665,6 +1690,7 @@ which don't require authorization are always allowed.
      address: ADDR[/INT] | ADDR-ADDR | STR ...
      key: key_id ...
      cert-key: BASE64 ...
+     cert-hostname: STR ...
      remote: remote_id | remotes_id ...
      action: query | notify | transfer | update ...
      protocol: udp | tcp | tls | quic ...
@@ -1709,16 +1735,28 @@ cert-key
 --------
 
 An ordered list of remote certificate public key PINs. If the list is non-empty,
-communication with the remote is possible only via QUIC or TLS protocols and
+communication with the remote is only possible via QUIC or TLS protocols, and
 a peer certificate is required. The peer certificate key must match one of the
 specified PINs.
 
 A PIN is a unique identifier that represents the public key of the peer certificate.
-It's a base64-encoded SHA-256 hash of the public key. This identifier
+It's a base64-encoded SHA-256 hash of the public key. This identifier usually
 remains the same on a certificate renewal.
 
 *Default:* not set
 
+.. _acl_cert-hostname:
+
+cert-hostname
+-------------
+
+An ordered list of hostnames to match against peer's certificate. At least one
+must match for successful certificate validation (see :ref:`server_ca-file`).
+If the list is non-empty, communication with the remote is only possible via
+QUIC or TLS protocols, and a peer certificate is required.
+
+*Default:* not set
+
 .. _acl_remote:
 
 remote
index b3f60d8d6ea96efd1ed4521f8a08ff51e326e809..c6e920b6d96e319958936d0d2bf53d943ad761f8 100644 (file)
@@ -1380,6 +1380,10 @@ conf_remote_t conf_remote_txn(
        out.quic = conf_bool(&val);
        val = conf_id_get_txn(conf, txn, C_RMT, C_TLS, id);
        out.tls = conf_bool(&val);
+       val = conf_id_get_txn(conf, txn, C_RMT, C_NO_EDNS, id);
+       out.no_edns = conf_bool(&val);
+       val = conf_id_get_txn(conf, txn, C_RMT, C_BLOCK_NOTIFY_XFR, id);
+       out.block_notify_after_xfr = conf_bool(&val);
 
        conf_val_t rundir_val = conf_get_txn(conf, txn, C_SRV, C_RUNDIR);
        char *rundir = conf_abs_path(&rundir_val, NULL);
@@ -1415,8 +1419,7 @@ conf_remote_t conf_remote_txn(
                conf_val_next(&val);
        }
 
-       val = conf_id_get_txn(conf, txn, C_RMT, C_CERT_KEY, id);
-       out.pin = (uint8_t *)conf_bin(&val, &out.pin_len);
+       free(rundir);
 
        // Get TSIG key (optional).
        conf_val_t key_id = conf_id_get_txn(conf, txn, C_RMT, C_KEY, id);
@@ -1430,13 +1433,21 @@ conf_remote_t conf_remote_txn(
                out.key.secret.data = (uint8_t *)conf_bin(&val, &out.key.secret.size);
        }
 
-       free(rundir);
-
-       val = conf_id_get_txn(conf, txn, C_RMT, C_BLOCK_NOTIFY_XFR, id);
-       out.block_notify_after_xfr = conf_bool(&val);
+       memset(out.hostnames, 0, sizeof(out.hostnames));
+       val = conf_id_get_txn(conf, txn, C_RMT, C_CERT_HOSTNAME, id);
+       for (size_t i = 0; val.code == KNOT_EOK; i++) {
+               out.hostnames[i] = conf_str(&val);
+               conf_val_next(&val);
+       }
 
-       val = conf_id_get_txn(conf, txn, C_RMT, C_NO_EDNS, id);
-       out.no_edns = conf_bool(&val);
+       memset(out.pins, 0, sizeof(out.pins));
+       val = conf_id_get_txn(conf, txn, C_RMT, C_CERT_KEY, id);
+       for (size_t i = 0; val.code == KNOT_EOK; i++) {
+               size_t len;
+               out.pins[i] = (uint8_t *)conf_bin(&val, &len);
+               assert(len == KNOT_TLS_PIN_LEN);
+               conf_val_next(&val);
+       }
 
        return out;
 }
index 6a6c1650bd14eac4443800312b791908de331d08..a253ba171be94110ca4bc790dfc9cb9d8fb371e7 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "knot/conf/base.h"
 #include "knot/conf/schema.h"
+#include "libknot/quic/tls_common.h"
 
 /*! Configuration schema additional flags. */
 #define CONF_IO_FACTIVE                YP_FUSR1  /*!< Active confio transaction indicator. */
@@ -25,6 +26,8 @@
 #define CONF_IO_FRLD_ALL       (CONF_IO_FRLD_SRV | CONF_IO_FRLD_LOG | \
                                 CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES)
 
+#define RMT_MAX_PINS           KNOT_TLS_MAX_PINS
+
 /*! Configuration remote getter output. */
 typedef struct {
        /*! Target socket address. */
@@ -35,16 +38,16 @@ typedef struct {
        bool quic;
        /*! TLS context. */
        bool tls;
-       /*! TSIG key. */
-       knot_tsig_key_t key;
-       /*! Suppress sending NOTIFY after zone transfer from this master. */
-       bool block_notify_after_xfr;
        /*! Disable EDNS on XFR queries. */
        bool no_edns;
-       /*! Possible remote certificate PIN. */
-       const uint8_t *pin;
-       /*! Length of the remote certificate PIN. Zero if PIN not specified. */
-       size_t pin_len;
+       /*! Suppress sending NOTIFY after zone transfer from this master. */
+       bool block_notify_after_xfr;
+       /*! TSIG key. */
+       knot_tsig_key_t key;
+       /*! Remote certificate permittable hostnames. */
+       const char *hostnames[RMT_MAX_PINS];
+       /*! Remote certificate permittable PINs. */
+       const uint8_t *pins[RMT_MAX_PINS];
 } conf_remote_t;
 
 /*! Configuration section iterator. */
index 514444db3c31ad15e9b22e5ad23edb71646aec02..fe1eee5cdf2da9cba609658d29bd5134385cf052 100644 (file)
@@ -236,6 +236,7 @@ static const yp_item_t desc_server[] = {
                                                        1232, YP_SSIZE } },
        { C_CERT_FILE,            YP_TSTR,  YP_VNONE, YP_FNONE },
        { C_KEY_FILE,             YP_TSTR,  YP_VNONE, YP_FNONE },
+       { C_CA_FILE,              YP_TSTR,  YP_VNONE, YP_FMULTI },
        { C_ECS,                  YP_TBOOL, YP_VNONE },
        { C_ANS_ROTATION,         YP_TBOOL, YP_VNONE },
        { C_AUTO_ACL,             YP_TBOOL, YP_VNONE },
@@ -341,6 +342,7 @@ static const yp_item_t desc_remote[] = {
        { C_TLS,              YP_TBOOL, YP_VNONE },
        { C_KEY,              YP_TREF,  YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
        { C_CERT_KEY,         YP_TB64,  YP_VNONE, YP_FMULTI, { check_cert_pin } },
+       { C_CERT_HOSTNAME,    YP_TSTR,  YP_VNONE, YP_FMULTI },
        { C_BLOCK_NOTIFY_XFR, YP_TBOOL, YP_VNONE },
        { C_NO_EDNS,          YP_TBOOL, YP_VNONE },
        { C_AUTO_ACL,         YP_TBOOL, YP_VBOOL = { true } },
@@ -370,6 +372,7 @@ static const yp_item_t desc_acl[] = {
        { C_UPDATE_OWNER_NAME,  YP_TDATA,  YP_VDATA = { 0, NULL, rdname_to_bin, rdname_to_txt },
                                           YP_FMULTI, },
        { C_CERT_KEY,           YP_TB64,   YP_VNONE, YP_FMULTI, { check_cert_pin } },
+       { C_CERT_HOSTNAME,      YP_TSTR,   YP_VNONE, YP_FMULTI },
        { C_COMMENT,            YP_TSTR,   YP_VNONE },
        { NULL }
 };
index cd64dc2ce040b48ad216c90085baac590e7d4523..87984df91ad009c5234e47c23e72ab54a5f101d4 100644 (file)
 #define C_CATALOG_ROLE         "\x0C""catalog-role"
 #define C_CATALOG_TPL          "\x10""catalog-template"
 #define C_CATALOG_ZONE         "\x0C""catalog-zone"
+#define C_CA_FILE              "\x07""ca-file"
 #define C_CDS_CDNSKEY          "\x13""cds-cdnskey-publish"
 #define C_CDS_DIGESTTYPE       "\x0F""cds-digest-type"
 #define C_CERT_FILE            "\x09""cert-file"
+#define C_CERT_HOSTNAME                "\x0D""cert-hostname"
 #define C_CERT_KEY             "\x08""cert-key"
 #define C_CHK_INTERVAL         "\x0E""check-interval"
 #define C_CLEAR                        "\x05""clear"
index b31190670c6d60aad1b812fcaa25f629455929b3..7e0b079403ab3f81fe210bdc617808771f52bdcb 100644 (file)
@@ -873,11 +873,14 @@ int check_acl(
                                            C_KEY, args->id, args->id_len);
        conf_val_t proto = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
                                              C_PROTOCOL, args->id, args->id_len);
+       conf_val_t hostname = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
+                                                C_CERT_HOSTNAME, args->id, args->id_len);
        conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
                                               C_RMT, args->id, args->id_len);
        if (remote.code != KNOT_ENOENT &&
-           (addr.code != KNOT_ENOENT || key.code != KNOT_ENOENT || proto.code != KNOT_ENOENT)) {
-               args->err_str = "specified ACL/remote together with address, key, or protocol";
+           (addr.code != KNOT_ENOENT || key.code != KNOT_ENOENT ||
+            proto.code != KNOT_ENOENT || hostname.code != KNOT_ENOENT)) {
+               args->err_str = "specified ACL/remote together with address, key, protocol, or hostname";
                return KNOT_EINVAL;
        }
 
@@ -928,6 +931,19 @@ int check_remote(
 #endif
        }
 
+       conf_val_t pin = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT,
+                                           C_CERT_KEY, args->id, args->id_len);
+       if (conf_val_count(&pin) > RMT_MAX_PINS) {
+               args->err_str = "too many cert-keys";
+               return KNOT_EINVAL;
+       }
+       conf_val_t cert_host = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT,
+                                                 C_CERT_HOSTNAME, args->id, args->id_len);
+       if (conf_val_count(&cert_host) > RMT_MAX_PINS) {
+               args->err_str = "too many cert-hosts";
+               return KNOT_EINVAL;
+       }
+
        return KNOT_EOK;
 }
 
index 695cd04142a86848eea4d514f41f662604dac72b..2e310d1167aadd3976ed66cec67bc51c69672ad0 100644 (file)
@@ -103,8 +103,8 @@ static int fwd(dnsproxy_t *proxy, knot_pkt_t *pkt, knotd_qdata_t *qdata, int add
        if (addr_pos < proxy->via.count) { // Simplified via address selection!
                src = &proxy->via.multi[addr_pos].addr;
        }
-       knot_request_t *req = knot_request_make_generic(re.mm, dst, src, query,
-                                                       NULL, NULL, NULL, NULL, 0, flags);
+       knot_request_t *req = knot_request_make_generic(re.mm, dst, src, query, NULL,
+                                                       NULL, NULL, NULL, NULL, flags);
        if (req == NULL) {
                knot_requestor_clear(&re);
                knot_pkt_free(query);
index cef7cce4abcd79f6dbdd9a937b09ad6c561ed523..cfeec479541a4931dfdf6b667dc3e05dc0e6b9f1 100644 (file)
@@ -151,8 +151,8 @@ int knot_qreq_connect(struct knot_quic_reply **out,
                       struct sockaddr_storage *remote,
                       struct sockaddr_storage *local,
                       const struct knot_creds *local_creds,
-                      const uint8_t *peer_pin,
-                      uint8_t peer_pin_len,
+                      const char *const peer_hostnames[RMT_MAX_PINS],
+                      const uint8_t *const peer_pins[RMT_MAX_PINS],
                       bool *reused_fd,
                       int timeout_ms)
 {
@@ -173,7 +173,7 @@ int knot_qreq_connect(struct knot_quic_reply **out,
        r->send_reply = qr_send_reply;
        r->free_reply = qr_free_reply;
 
-       struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_pin, peer_pin_len);
+       struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_hostnames, peer_pins);
        if (creds == NULL) {
                free(r);
                return KNOT_ENOMEM;
index 3c01bac6e9c9fae4df179e4800edeb702374d872..757e07c39cf3ec680a6d9930ee08b0dad6847caf 100644 (file)
@@ -6,6 +6,7 @@
 #pragma once
 
 #include "contrib/sockaddr.h"
+#include "knot/conf/conf.h"
 #include "libknot/quic/quic.h"
 
 int knot_qreq_connect(struct knot_quic_reply **out,
@@ -13,8 +14,8 @@ int knot_qreq_connect(struct knot_quic_reply **out,
                       struct sockaddr_storage *remote,
                       struct sockaddr_storage *local,
                       const struct knot_creds *local_creds,
-                      const uint8_t *peer_pin,
-                      uint8_t peer_pin_len,
+                      const char *const peer_hostnames[RMT_MAX_PINS],
+                      const uint8_t *const peer_pins[RMT_MAX_PINS],
                       bool *reused_fd,
                       int timeout_ms);
 
index cb0b45af4abc17a8128be8354bc35b158e623b6e..433fafd81f699154d085f8b3f440df416bdcb11f 100644 (file)
@@ -84,11 +84,10 @@ static int request_ensure_connected(knot_request_t *request, bool *reused_fd, in
                                          &local_len);
                }
 #ifdef ENABLE_QUIC
-               int ret = knot_qreq_connect(&request->quic_ctx,
-                                           request->fd, &request->remote,
-                                           &request->source, request->creds,
-                                           request->pin, request->pin_len,
-                                           reused_fd, timeout_ms);
+               int ret = knot_qreq_connect(&request->quic_ctx, request->fd,
+                                           &request->remote, &request->source,
+                                           request->creds, request->hostnames,
+                                           request->pins, reused_fd, timeout_ms);
                if (ret != KNOT_EOK) {
                        close(request->fd);
                        request->fd = -1;
@@ -104,8 +103,8 @@ static int request_ensure_connected(knot_request_t *request, bool *reused_fd, in
 
                int ret = knot_tls_req_ctx_init(&request->tls_req_ctx, request->fd,
                                                &request->remote, &request->source,
-                                               request->creds, request->pin,
-                                               request->pin_len, reused_fd, timeout_ms);
+                                               request->creds, request->hostnames,
+                                               request->pins, reused_fd, timeout_ms);
                if (ret != KNOT_EOK) {
                        close(request->fd);
                        request->fd = -1;
@@ -210,15 +209,15 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm,
                                           const struct knot_creds *creds,
                                           const query_edns_data_t *edns,
                                           const knot_tsig_key_t *tsig_key,
-                                          const uint8_t *pin,
-                                          size_t pin_len,
+                                          const char *const hostnames[RMT_MAX_PINS],
+                                          const uint8_t *const pins[RMT_MAX_PINS],
                                           knot_request_flag_t flags)
 {
        if (remote == NULL || query == NULL) {
                return NULL;
        }
 
-       knot_request_t *request = mm_calloc(mm, 1, sizeof(*request) + pin_len);
+       knot_request_t *request = mm_calloc(mm, 1, sizeof(*request));
        if (request == NULL) {
                return NULL;
        }
@@ -247,9 +246,13 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm,
 
        request->edns = edns;
        request->creds = creds;
-       if ((flags & (KNOT_REQUEST_QUIC | KNOT_REQUEST_TLS)) && pin_len > 0) {
-               request->pin_len = pin_len;
-               memcpy(request->pin, pin, pin_len);
+       if (flags & (KNOT_REQUEST_QUIC | KNOT_REQUEST_TLS)) {
+               if (pins != NULL) {
+                       memcpy(request->pins, pins, sizeof(request->pins));
+               }
+               if (hostnames != NULL) {
+                       memcpy(request->hostnames, hostnames, sizeof(request->hostnames));
+               }
        }
 
        return request;
@@ -269,9 +272,9 @@ knot_request_t *knot_request_make(knot_mm_t *mm,
                flags |= KNOT_REQUEST_TLS;
        }
 
-       return knot_request_make_generic(mm, &remote->addr, &remote->via,
-                                        query, creds, edns, &remote->key, remote->pin,
-                                        remote->pin_len, flags);
+       return knot_request_make_generic(mm, &remote->addr, &remote->via, query,
+                                        creds, edns,  &remote->key, remote->hostnames,
+                                        remote->pins, flags);
 }
 
 void knot_request_free(knot_request_t *request, knot_mm_t *mm)
index e3bd4163437de92a3762fc5a395d0e2a2959971b..0adef1d974a29acef53da60a6a34d7f04185f45b 100644 (file)
@@ -64,8 +64,9 @@ typedef struct knot_request {
        knot_sign_context_t sign; /*!< Required for async. DDNS processing. */
 
        const struct knot_creds *creds;
-       size_t pin_len;
-       uint8_t pin[];
+
+       const char *hostnames[RMT_MAX_PINS];
+       const uint8_t *pins[RMT_MAX_PINS];
 } knot_request_t;
 
 static inline knotd_query_proto_t flags2proto(unsigned layer_flags)
@@ -89,8 +90,8 @@ static inline knotd_query_proto_t flags2proto(unsigned layer_flags)
  * \param creds     Local (server) credentials.
  * \param edns      EDNS parameters.
  * \param tsig_key  TSIG key for authentication.
- * \param pin       Possible remote certificate PIN.
- * \param pin_len   Length of the remote certificate PIN.
+ * \param hostnames Permittable remote certificate hostnames.
+ * \param pins      Permittable remote certificate PINs.
  * \param flags     Request flags.
  *
  * \return Prepared request or NULL in case of error.
@@ -102,8 +103,8 @@ knot_request_t *knot_request_make_generic(knot_mm_t *mm,
                                           const struct knot_creds *creds,
                                           const query_edns_data_t *edns,
                                           const knot_tsig_key_t *tsig_key,
-                                          const uint8_t *pin,
-                                          size_t pin_len,
+                                          const char *const hostnames[RMT_MAX_PINS],
+                                          const uint8_t *const pins[RMT_MAX_PINS],
                                           knot_request_flag_t flags);
 
 /*!
index 04159e48ea59cfc5e91a835bfdc6ea79c47b2e41..935ac60d363b419b8141b37447a52b350ffeb1f2 100644 (file)
 #include "libknot/quic/tls.h"
 #include "contrib/conn_pool.h"
 
-int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx, int fd,
+int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx,
+                          int fd,
                           const struct sockaddr_storage *remote,
                           const struct sockaddr_storage *local,
                           const struct knot_creds *local_creds,
-                          const uint8_t *peer_pin, uint8_t peer_pin_len,
-                          bool *reused_fd, int io_timeout_ms)
+                          const char *const peer_hostnames[RMT_MAX_PINS],
+                          const uint8_t *const peer_pins[RMT_MAX_PINS],
+                          bool *reused_fd,
+                          int io_timeout_ms)
 {
-       struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_pin, peer_pin_len);
+       struct knot_creds *creds = knot_creds_init_peer(local_creds, peer_hostnames, peer_pins);
        if (creds == NULL) {
                return KNOT_ENOMEM;
        }
index 6843d71aa57b84a2e54b9001c88aa51ccdb98141..63e1c5247eecb6c452ca5e38ae5ca1084042add1 100644 (file)
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include <sys/socket.h>
 
+#include "knot/conf/conf.h"
 #include "libknot/quic/tls_common.h"
 
 struct knot_request;
@@ -25,19 +26,25 @@ typedef struct knot_tls_req_ctx {
  *
  * \param ctx               Context structure to be initialized.
  * \param fd                Opened TCP connection file descriptor.
+ * \param remote            Remote address for purposes of TLS session resumption.
+ * \param local             Local address for purposes of TLS session resumption.
  * \param local_creds       Local TLS credentials.
- * \param peer_pin          TLS peer pin.
- * \param peer_pin_len      TLS peer pin length.
+ * \param peer_hostnames    Permittable TLS peer hostnames.
+ * \param peer_pins         Permittable TLS peer PINs.
+ * \param reused_fd[out]    Indicates successful TLS session resumption.
  * \param io_timeout_ms     Configured io-timeout for TLS connection.
  *
  * \return KNOT_E*
  */
-int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx, int fd,
+int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx,
+                          int fd,
                           const struct sockaddr_storage *remote,
                           const struct sockaddr_storage *local,
                           const struct knot_creds *local_creds,
-                          const uint8_t *peer_pin, uint8_t peer_pin_len,
-                          bool *reused_fd, int io_timeout_ms);
+                          const char *const peer_hostnames[RMT_MAX_PINS],
+                          const uint8_t *const peer_pins[RMT_MAX_PINS],
+                          bool *reused_fd,
+                          int io_timeout_ms);
 
 /*!
  * \brief Maintain the TLS requestor context (update session ticket).
index 3500593a94c29f64307a37786b01120427eb74e3..6b77777f714cffa977ad2062d388d604f4b6a07c 100644 (file)
@@ -551,7 +551,7 @@ static void log_sock_conf(conf_t *conf)
        }
 }
 
-static int check_file(char *path, char *role)
+static int check_file(const char *path, char *role)
 {
        if (path == NULL) {
                return KNOT_EOK;
@@ -579,6 +579,10 @@ static int init_creds(conf_t *conf, server_t *server)
 {
        char *cert_file = conf_tls(conf, C_CERT_FILE);
        char *key_file = conf_tls(conf, C_KEY_FILE);
+       conf_val_t cafiles_val = conf_get(conf, C_SERVER, C_CA_FILE);
+       size_t nfiles = conf_val_count(&cafiles_val);
+       const char *ca_files[nfiles + 1];
+       bool system_ca = false;
 
        int ret = check_file(cert_file, "certificate");
        if (ret != KNOT_EOK) {
@@ -615,16 +619,32 @@ static int init_creds(conf_t *conf, server_t *server)
                goto failed;
        }
 
+       memset(ca_files, 0, sizeof(ca_files));
+       for (size_t i = 0; cafiles_val.code == KNOT_EOK; conf_val_next(&cafiles_val)) {
+               const char *file = conf_str(&cafiles_val);
+               if (*file == '\0') {
+                       system_ca = true;
+               } else if ((ret = check_file(file, "ca")) != KNOT_EOK) {
+                       goto failed;
+               } else {
+                       ca_files[i++] = file;
+               }
+       }
+
        if (server->quic_creds == NULL) {
-               server->quic_creds = knot_creds_init(key_file, cert_file, uid, gid);
-               if (server->quic_creds == NULL) {
-                       log_error(QUIC_LOG "failed to initialize server credentials");
-                       ret = KNOT_ERROR;
+               ret = knot_creds_init(&server->quic_creds, key_file, cert_file, ca_files,
+                                     system_ca, uid, gid);
+               if (ret != KNOT_EOK) {
+                       log_error(QUIC_LOG "failed to initialize credentials or to load certificates (%s)",
+                                 knot_strerror(ret));
                        goto failed;
                }
        } else {
-               ret = knot_creds_update(server->quic_creds, key_file, cert_file, uid, gid);
+               ret = knot_creds_update(server->quic_creds, key_file, cert_file, ca_files,
+                                       system_ca, uid, gid);
                if (ret != KNOT_EOK) {
+                       log_error(QUIC_LOG "failed to initialize credentials or to load certificates (%s)",
+                                 knot_strerror(ret));
                        goto failed;
                }
        }
index 613aa143768e5a112d3dca70adb5cb8e65490dd6..ebcd3f69c7b1bf7b7d74f7f90b3ff505ef1af2db 100644 (file)
@@ -30,6 +30,25 @@ static bool cert_pin_check(const uint8_t *session_pin, size_t session_pin_size,
        return false;
 }
 
+static bool cert_check(struct gnutls_session_int *tls_session, conf_val_t *hostname_val)
+{
+       if (hostname_val->code == KNOT_ENOENT) { // No hostname authentication required.
+               return true;
+       }
+
+       size_t count = conf_val_count(hostname_val);
+       const char *hostnames[count + 1];
+
+       const char **hostname = hostnames;
+       while (hostname_val->code == KNOT_EOK) {
+               *hostname++ = conf_str(hostname_val);
+               conf_val_next(hostname_val);
+       }
+       *hostname = NULL;
+
+       return knot_tls_cert_check_hostnames(tls_session, hostnames) == KNOT_EOK;
+}
+
 static bool match_type(uint16_t type, conf_val_t *types)
 {
        if (types == NULL) {
@@ -342,18 +361,19 @@ bool acl_allowed(conf_t *conf, conf_val_t *acl, acl_action_t action,
                conf_val_t deny_val = conf_id_get(conf, C_ACL, C_DENY, acl);
                bool deny = conf_bool(&deny_val);
 
-               /* Check if a remote matches given address and key. */
-               conf_val_t addr_val, key_val, pin_val;
+               /* Check if a remote matches given params. */
+               conf_val_t addr_val, key_val, pin_val, hostname_val;
                conf_mix_iter_t iter;
                conf_mix_iter_init(conf, &rmt_val, &iter);
                while (iter.id->code == KNOT_EOK) {
                        addr_val = conf_id_get(conf, C_RMT, C_ADDR, iter.id);
                        key_val = conf_id_get(conf, C_RMT, C_KEY, iter.id);
                        pin_val = conf_id_get(conf, C_RMT, C_CERT_KEY, iter.id);
-                       if (check_addr_key(conf, &addr_val, &key_val, remote, addr,
-                                          tsig, &pin_val, session_pin, session_pin_size,
-                                          deny, forward)
-                           && check_proto_rmt(conf, proto, iter.id)) {
+                       hostname_val = conf_id_get(conf, C_RMT, C_CERT_HOSTNAME, iter.id);
+                       if (check_addr_key(conf, &addr_val, &key_val, remote, addr, tsig, &pin_val,
+                                          session_pin, session_pin_size, deny, forward)
+                           && check_proto_rmt(conf, proto, iter.id)
+                           && cert_check(tls_session, &hostname_val)) {
                                break;
                        }
                        conf_mix_iter_next(&iter);
@@ -361,14 +381,15 @@ bool acl_allowed(conf_t *conf, conf_val_t *acl, acl_action_t action,
                if (iter.id->code == KNOT_EOF) {
                        goto next_acl;
                }
-               /* Or check if acl address/key matches given address and key. */
+               /* Or check if acl matches given params. */
                if (!remote) {
                        addr_val = conf_id_get(conf, C_ACL, C_ADDR, acl);
                        key_val = conf_id_get(conf, C_ACL, C_KEY, acl);
                        pin_val = conf_id_get(conf, C_ACL, C_CERT_KEY, acl);
-                       if (!check_addr_key(conf, &addr_val, &key_val, remote, addr,
-                                           tsig, &pin_val, session_pin, session_pin_size,
-                                           deny, forward)) {
+                       hostname_val = conf_id_get(conf, C_ACL, C_CERT_HOSTNAME, acl);
+                       if (!check_addr_key(conf, &addr_val, &key_val, remote, addr, tsig, &pin_val,
+                                           session_pin, session_pin_size, deny, forward)
+                           || !cert_check(tls_session, &hostname_val)) {
                                goto next_acl;
                        }
 
@@ -449,8 +470,13 @@ bool rmt_allowed(conf_t *conf, conf_val_t *rmts, const struct sockaddr_storage *
                        goto next_remote;
                }
 
-               conf_val_t pin_val = conf_id_get(conf, C_RMT, C_CERT_KEY, iter.id);
-               if (!cert_pin_check(session_pin, session_pin_size, &pin_val)) {
+               val = conf_id_get(conf, C_RMT, C_CERT_KEY, iter.id);
+               if (!cert_pin_check(session_pin, session_pin_size, &val)) {
+                       goto next_remote;
+               }
+
+               val = conf_id_get(conf, C_RMT, C_CERT_HOSTNAME, iter.id);
+               if (!cert_check(tls_session, &val)) {
                        goto next_remote;
                }
 
index d87c4138f2657891c1b6a854d0d26979f3858b67..c9b07198922ecb1260da2c598546f78f4496aae8 100644 (file)
@@ -95,7 +95,7 @@ enum knot_error {
        KNOT_EEMPTYZONE,
        KNOT_ENODB,
        KNOT_EUNREACH,
-       KNOT_EBADCERTKEY,
+       KNOT_EBADCERT,
        KNOT_EFACCES,
        KNOT_EBACKUPDATA,
        KNOT_ECPUCOMPAT,
index 5e00b7256440769dcfd79802e10cc0cffacf2034..f1eb1f00876f80d40f7eaf339337c431a3ddfcc9 100644 (file)
@@ -94,7 +94,7 @@ static const struct error errors[] = {
        { KNOT_EEMPTYZONE,   "zone is empty" },
        { KNOT_ENODB,        "database does not exist" },
        { KNOT_EUNREACH,     "remote known to be unreachable" },
-       { KNOT_EBADCERTKEY,  "unknown certificate key" },
+       { KNOT_EBADCERT,     "invalid certificate" },
        { KNOT_EFACCES,      "file permission denied" },
        { KNOT_EBACKUPDATA,  "requested data not in backup" },
        { KNOT_ECPUCOMPAT,   "incompatible CPU architecture" },
index 9bdc4e8eb16f8f090f247b0811b17280e9887d3b..e19cb1f40c6e403a55e1548e1e0c6b4a8ce0a4c4 100644 (file)
@@ -289,8 +289,10 @@ static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data)
        ctx->flags |= KNOT_QUIC_CONN_HANDSHAKE_DONE;
 
        if (!ngtcp2_conn_is_server(conn)) {
-               return knot_tls_pin_check(ctx->tls_session, ctx->quic_table->creds)
-                      == KNOT_EOK ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
+               return knot_tls_pin_check(ctx->tls_session, ctx->quic_table->creds) == KNOT_EOK
+                      && knot_tls_cert_check(ctx->tls_session, ctx->quic_table->creds) == KNOT_EOK
+                              ? 0
+                              : NGTCP2_ERR_CALLBACK_FAILURE;
        }
 
        if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) {
@@ -723,7 +725,7 @@ int knot_quic_handle(knot_quic_table_t *table, knot_quic_reply_t *reply,
                goto finish;
        } else if (ngtcp2_err_is_fatal(ret)) { // connection doomed
                if (ret == NGTCP2_ERR_CALLBACK_FAILURE) {
-                       ret = KNOT_EBADCERTKEY;
+                       ret = KNOT_EBADCERT;
                } else {
                        ret = KNOT_ECONN;
                }
index 5edaaf88a06c3b43a70628c6a6588bb0cda5bc59..89942c733fd728874978c38120e10ddecf02ce5e 100644 (file)
@@ -170,7 +170,10 @@ int knot_tls_handshake(knot_tls_conn_t *conn, bool oneshot)
        switch (ret) {
        case GNUTLS_E_SUCCESS:
                conn->flags |= KNOT_TLS_CONN_HANDSHAKE_DONE;
-               return knot_tls_pin_check(conn->session, conn->ctx->creds);
+               return knot_tls_pin_check(conn->session, conn->ctx->creds) == KNOT_EOK
+                      && knot_tls_cert_check(conn->session, conn->ctx->creds) == KNOT_EOK
+                              ? KNOT_EOK
+                              : KNOT_EBADCERT;
        case GNUTLS_E_TIMEDOUT:
                return KNOT_NET_ETIMEOUT;
        default:
index 5df73b8b3ab8e1350904c20bc58de9e325187adb..8bf9872e46910d902276489c315fe238e20d8565 100644 (file)
@@ -3,6 +3,7 @@
  *  For more information, see <https://www.knot-dns.cz/>
  */
 
+#include <assert.h>
 #include <fcntl.h>
 #include <gnutls/crypto.h>
 #include <gnutls/gnutls.h>
@@ -10,6 +11,7 @@
 #include <gnutls/x509-ext.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <time.h>
 #include <unistd.h>
@@ -17,6 +19,7 @@
 #include "libknot/quic/tls_common.h"
 
 #include "contrib/atomic.h"
+#include "contrib/openbsd/siphash.h"
 #include "contrib/sockaddr.h"
 #include "contrib/string.h"
 #include "libknot/attribute.h"
 
 typedef struct knot_creds {
        knot_atomic_ptr_t cert_creds; // Current credentials.
+       uint64_t creds_hash; // Hashed creds sources to detect changes.
        gnutls_certificate_credentials_t cert_creds_prev; // Previous credentials (for pending connections).
        gnutls_anti_replay_t tls_anti_replay;
        gnutls_datum_t tls_ticket_key;
        bool peer;
-       uint8_t peer_pin_len;
-       uint8_t peer_pin[];
+       const char *peer_hostnames[KNOT_TLS_MAX_PINS + 1];
+       const uint8_t *peer_pins[KNOT_TLS_MAX_PINS + 1];
 } knot_creds_t;
 
 _public_
@@ -175,43 +179,53 @@ finish:
 }
 
 _public_
-struct knot_creds *knot_creds_init(const char *key_file, const char *cert_file,
-                                   int uid, int gid)
+int knot_creds_init(struct knot_creds **out,
+                    const char *key_file,
+                    const char *cert_file,
+                    const char **ca_files,
+                    bool system_ca,
+                    int uid,
+                    int gid)
 {
+       if (out == NULL) {
+               return KNOT_EINVAL;
+       }
+
        knot_creds_t *creds = calloc(1, sizeof(*creds));
        if (creds == NULL) {
-               return NULL;
+               return KNOT_ENOMEM;
        }
 
-       int ret = knot_creds_update(creds, key_file, cert_file, uid, gid);
+       int ret = knot_creds_update(creds, key_file, cert_file, ca_files, system_ca, uid, gid);
        if (ret != KNOT_EOK) {
                goto fail;
        }
 
-       ret = gnutls_anti_replay_init(&creds->tls_anti_replay);
-       if (ret != GNUTLS_E_SUCCESS) {
+       if (gnutls_anti_replay_init(&creds->tls_anti_replay) != GNUTLS_E_SUCCESS) {
+               ret = KNOT_ENOMEM;
                goto fail;
        }
        gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func);
        gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL);
 
-       ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key);
-       if (ret != GNUTLS_E_SUCCESS) {
+       if (gnutls_session_ticket_key_generate(&creds->tls_ticket_key) != GNUTLS_E_SUCCESS) {
+               ret = KNOT_ENOMEM;
                goto fail;
        }
 
-       return creds;
+       *out = creds;
+       return KNOT_EOK;
 fail:
        knot_creds_free(creds);
-       return NULL;
+       return ret;
 }
 
 _public_
 struct knot_creds *knot_creds_init_peer(const struct knot_creds *local_creds,
-                                        const uint8_t *peer_pin,
-                                        uint8_t peer_pin_len)
+                                        const char *const peer_hostnames[KNOT_TLS_MAX_PINS],
+                                        const uint8_t *const peer_pins[KNOT_TLS_MAX_PINS])
 {
-       knot_creds_t *creds = calloc(1, sizeof(*creds) + peer_pin_len);
+       knot_creds_t *creds = calloc(1, sizeof(*creds));
        if (creds == NULL) {
                return NULL;
        }
@@ -229,9 +243,13 @@ struct knot_creds *knot_creds_init_peer(const struct knot_creds *local_creds,
                ATOMIC_INIT(creds->cert_creds, new_creds);
        }
 
-       if (peer_pin_len > 0 && peer_pin != NULL) {
-               memcpy(creds->peer_pin, peer_pin, peer_pin_len);
-               creds->peer_pin_len = peer_pin_len;
+       if (peer_pins != NULL) {
+               memcpy(creds->peer_pins, peer_pins,
+                      sizeof(peer_pins[0]) * KNOT_TLS_MAX_PINS);
+       }
+       if (peer_hostnames != NULL) {
+               memcpy(creds->peer_hostnames, peer_hostnames,
+                      sizeof(peer_hostnames[0]) * KNOT_TLS_MAX_PINS);
        }
 
        return creds;
@@ -255,63 +273,71 @@ static int creds_cert(gnutls_certificate_credentials_t creds,
        return KNOT_ERROR;
 }
 
-static int creds_changed(gnutls_certificate_credentials_t creds,
-                         gnutls_certificate_credentials_t prev,
-                         bool self_cert, bool *changed)
+static void hash_file(SIPHASH_CTX *ctx, const char *file_name)
 {
-       if (creds == NULL || prev == NULL) {
-               *changed = true;
-               return KNOT_EOK;
+       assert(ctx);
+       assert(file_name);
+
+       char *data;
+       struct stat file_stat;
+       int fd = open(file_name, O_RDONLY);
+       if (fd == -1 ||
+           fstat(fd, &file_stat) == -1 ||
+           (data = mmap(0, file_stat.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+               close(fd);
+               return;
        }
 
-       gnutls_x509_crt_t cert = NULL, cert_prev = NULL;
+       SipHash24_Update(ctx, data, file_stat.st_size);
 
-       int ret = creds_cert(creds, &cert);
-       if (ret != KNOT_EOK) {
-               goto failed;
-       }
-       ret = creds_cert(prev, &cert_prev);
-       if (ret != KNOT_EOK) {
-               goto failed;
-       }
+       munmap(data, file_stat.st_size);
+       close(fd);
+}
 
-       if (self_cert) {
-               uint8_t pin[KNOT_TLS_PIN_LEN], pin_prev[KNOT_TLS_PIN_LEN];
-               size_t pin_size = sizeof(pin), pin_prev_size = sizeof(pin_prev);
+static uint64_t creds_hash(const char *key_file,
+                           const char *cert_file,
+                           const char **ca_files,
+                           bool system_ca)
+{
+       SIPHASH_CTX ctx;
+       SIPHASH_KEY key = { 0 };
+       SipHash24_Init(&ctx, &key);
 
-               ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256,
-                                                pin, &pin_size);
-               if (ret != KNOT_EOK) {
-                       goto failed;
-               }
-               ret = gnutls_x509_crt_get_key_id(cert_prev, GNUTLS_KEYID_USE_SHA256,
-                                                pin_prev, &pin_prev_size);
-               if (ret != KNOT_EOK) {
-                       goto failed;
+       assert(key_file);
+       hash_file(&ctx, key_file);
+       if (cert_file != NULL) {
+               hash_file(&ctx, cert_file);
+       }
+       if (ca_files != NULL) {
+               for (const char **file = ca_files; *file != NULL; file++) {
+                       hash_file(&ctx, *file);
                }
-
-               *changed = (pin_size != pin_prev_size) ||
-                          memcmp(pin, pin_prev, pin_size) != 0;
-       } else {
-               *changed = (gnutls_x509_crt_equals(cert, cert_prev) == 0);
+       }
+       if (system_ca) {
+               SipHash24_Update(&ctx, "\x01", 1);
        }
 
-       ret = KNOT_EOK;
-failed:
-       gnutls_x509_crt_deinit(cert);
-       gnutls_x509_crt_deinit(cert_prev);
-
-       return ret;
+       return SipHash24_End(&ctx);
 }
 
 _public_
-int knot_creds_update(struct knot_creds *creds, const char *key_file, const char *cert_file,
-                      int uid, int gid)
+int knot_creds_update(struct knot_creds *creds,
+                      const char *key_file,
+                      const char *cert_file,
+                      const char **ca_files,
+                      bool system_ca,
+                      int uid,
+                      int gid)
 {
        if (creds == NULL || key_file == NULL) {
                return KNOT_EINVAL;
        }
 
+       uint64_t new_hash = creds_hash(key_file, cert_file, ca_files, system_ca);
+       if (creds->creds_hash == new_hash) {
+               return KNOT_EOK;
+       }
+
        gnutls_certificate_credentials_t new_creds;
        int ret = gnutls_certificate_allocate_credentials(&new_creds);
        if (ret != GNUTLS_E_SUCCESS) {
@@ -330,23 +356,28 @@ int knot_creds_update(struct knot_creds *creds, const char *key_file, const char
                return KNOT_EFILE;
        }
 
-       bool changed = false;
-       ret = creds_changed(new_creds, ATOMIC_GET(creds->cert_creds),
-                           cert_file == NULL, &changed);
-       if (ret != KNOT_EOK) {
-               gnutls_certificate_free_credentials(new_creds);
-               return ret;
+       if (system_ca) {
+               if (gnutls_certificate_set_x509_system_trust(new_creds) < 0) {
+                       gnutls_certificate_free_credentials(new_creds);
+                       return KNOT_EBADCERT;
+               }
        }
-
-       if (changed) {
-               if (creds->cert_creds_prev != NULL) {
-                       gnutls_certificate_free_credentials(creds->cert_creds_prev);
+       if (ca_files != NULL) {
+               for (const char **file = ca_files; *file != NULL; file++) {
+                       if (gnutls_certificate_set_x509_trust_file(new_creds, *file,
+                                                                  GNUTLS_X509_FMT_PEM) < 0) {
+                               gnutls_certificate_free_credentials(new_creds);
+                               return KNOT_EBADCERT;
+                       }
                }
-               creds->cert_creds_prev = ATOMIC_XCHG(creds->cert_creds, new_creds);
-       } else {
-               gnutls_certificate_free_credentials(new_creds);
        }
 
+       if (creds->cert_creds_prev != NULL) {
+               gnutls_certificate_free_credentials(creds->cert_creds_prev);
+       }
+       creds->cert_creds_prev = ATOMIC_XCHG(creds->cert_creds, new_creds);
+       creds->creds_hash = new_hash;
+
        return KNOT_EOK;
 }
 
@@ -493,17 +524,82 @@ _public_
 int knot_tls_pin_check(struct gnutls_session_int *session,
                        struct knot_creds *creds)
 {
-       if (creds->peer_pin_len == 0) {
+       // if no pin set -> opportunistic mode
+       if (creds->peer_pins[0] == NULL) {
                return KNOT_EOK;
        }
 
        uint8_t pin[KNOT_TLS_PIN_LEN];
        size_t pin_size = sizeof(pin);
        knot_tls_pin(session, pin, &pin_size, false);
-       if (pin_size != creds->peer_pin_len ||
-           const_time_memcmp(pin, creds->peer_pin, pin_size) != 0) {
-               return KNOT_EBADCERTKEY;
+       if (pin_size != KNOT_TLS_PIN_LEN) {
+               return KNOT_EBADCERT;
        }
 
-       return KNOT_EOK;
+       for (const uint8_t **it = creds->peer_pins; *it != NULL; it++) {
+               if (const_time_memcmp(pin, *it, KNOT_TLS_PIN_LEN) == 0) {
+                       return KNOT_EOK;
+               }
+       }
+
+       return KNOT_EBADCERT;
+}
+
+_public_
+int knot_tls_cert_check_hostnames(struct gnutls_session_int *session,
+                                  const char *hostnames[])
+{
+       // if no hostname set -> opportunistic mode
+       if (hostnames == NULL || hostnames[0] == NULL) {
+               return KNOT_EOK;
+       }
+
+       if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
+               return KNOT_EBADCERT;
+       }
+
+       unsigned status = 0;
+       int ret = gnutls_certificate_verify_peers2(session, &status);
+       if (ret != GNUTLS_E_SUCCESS || status != 0) {
+               return KNOT_EBADCERT;
+       }
+
+       unsigned count = 0;
+       const gnutls_datum_t *cert_list = gnutls_certificate_get_peers(session, &count);
+       if (count == 0) {
+               return KNOT_EBADCERT;
+       }
+
+       gnutls_x509_crt_t cert;
+       ret = gnutls_x509_crt_init(&cert);
+       if (ret != GNUTLS_E_SUCCESS) {
+               return KNOT_EBADCERT;
+       }
+
+       // standard compliant servers send an ordered cert list, so the 0th cert is peer's
+       ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
+       if (ret != GNUTLS_E_SUCCESS) {
+               gnutls_x509_crt_deinit(cert);
+               return KNOT_EBADCERT;
+       }
+
+       // using gnutls_x509_crt_check_hostname() to enforce SAN-only hostname checking
+       // see https://datatracker.ietf.org/doc/html/rfc8310#section-8.1
+       for (const char **hostname = hostnames; *hostname != NULL; hostname++) {
+               if (gnutls_x509_crt_check_hostname(cert, *hostname)) {
+                       gnutls_x509_crt_deinit(cert);
+                       return KNOT_EOK;
+               }
+       }
+
+       gnutls_x509_crt_deinit(cert);
+
+       return KNOT_EBADCERT;
+}
+
+_public_
+int knot_tls_cert_check(struct gnutls_session_int *session,
+                        struct knot_creds *creds)
+{
+       return knot_tls_cert_check_hostnames(session, creds->peer_hostnames);
 }
index b2e73f114ff0d1e32ee54614ce91716aa51d95ba..77668d3032893e1a50bd2b71165f874f3e887c60 100644 (file)
@@ -19,6 +19,7 @@
 #include <stdint.h>
 
 #define KNOT_TLS_PIN_LEN    32
+#define KNOT_TLS_MAX_PINS    4
 
 struct gnutls_priority_st;
 struct gnutls_session_int;
@@ -44,44 +45,60 @@ typedef enum {
 const char *knot_tls_priority(bool tls12);
 
 /*!
- * \brief Init server TLS key and certificate for DoQ.
+ * \brief Init server credentials.
  *
+ * \param out           Server credentials to initialize.
  * \param key_file      Key PEM file path/name.
  * \param cert_file     X509 certificate PEM file path/name (NULL if auto-generated).
+ * \param ca_files      Which additional certificate indicators to import. NULL terminated.
+ * \param system_ca     Whether to import system certificate indicators.
  * \param uid           Generated key file owner id.
  * \param gid           Generated key file group id.
  *
  * \return Initialized creds.
  */
-struct knot_creds *knot_creds_init(const char *key_file, const char *cert_file,
-                                   int uid, int gid);
+int knot_creds_init(struct knot_creds **out,
+                    const char *key_file,
+                    const char *cert_file,
+                    const char **ca_files,
+                    bool system_ca,
+                    int uid,
+                    int gid
+);
 
 /*!
- * \brief Init peer TLS key and certificate for DoQ.
+ * \brief Init peer credentials.
  *
- * \param local_creds   Local credentials if server.
- * \param peer_pin      Optional peer certificate pin to check.
- * \param peer_pin_len  Length of the peer pin. Set 0 if not specified.
+ * \param local_creds           Local credentials if server.
+ * \param peer_hostname         Optional peer certificate hostnames to check.
+ * \param peer_pin              Optional peer certificate PINs to check.
  *
  * \return Initialized creds.
  */
 struct knot_creds *knot_creds_init_peer(const struct knot_creds *local_creds,
-                                        const uint8_t *peer_pin,
-                                        uint8_t peer_pin_len);
+                                        const char *const peer_hostname[KNOT_TLS_MAX_PINS],
+                                        const uint8_t *const peer_pin[KNOT_TLS_MAX_PINS]);
 
 /*!
- * \brief Load new server TLS key and certificate for DoQ.
+ * \brief Update server credentials.
  *
  * \param creds         Server credentials where key/cert pair will be updated.
  * \param key_file      Key PEM file path/name.
  * \param cert_file     X509 certificate PEM file path/name (NULL if auto-generated).
+ * \param ca_files      Which additional certificate indicators to import. NULL terminated.
+ * \param system_ca     Whether to import system certificate indicators.
  * \param uid           Generated key file owner id.
  * \param gid           Generated key file group id.
  *
  * \return KNOT_E*
  */
-int knot_creds_update(struct knot_creds *creds, const char *key_file, const char *cert_file,
-                      int uid, int gid);
+int knot_creds_update(struct knot_creds *creds,
+                      const char *key_file,
+                      const char *cert_file,
+                      const char **ca_files,
+                      bool system_ca,
+                      int uid,
+                      int gid);
 
 /*!
  * \brief Gets the certificate from credentials.
@@ -94,7 +111,7 @@ int knot_creds_update(struct knot_creds *creds, const char *key_file, const char
 int knot_creds_cert(struct knot_creds *creds, struct gnutls_x509_crt_int **cert);
 
 /*!
- * \brief Deinit server TLS certificate for DoQ.
+ * \brief Deinit credentials.
  */
 void knot_creds_free(struct knot_creds *creds);
 
@@ -132,9 +149,31 @@ void knot_tls_pin(struct gnutls_session_int *session, uint8_t *pin,
  * \param session   TLS connection.
  * \param creds     TLS credentials.
  *
- * \return KNOT_EOK or KNOT_EBADCERTKEY
+ * \return KNOT_EOK or KNOT_EBADCERT
  */
 int knot_tls_pin_check(struct gnutls_session_int *session,
                        struct knot_creds *creds);
 
+/*!
+ * \brief Checks remote certificate validity against hostname strings.
+ *
+ * \param session    TLS connection.
+ * \param hostnames  NULL terminated array of possible hostnames.
+ *
+ * \return KNOT_EOK or KNOT_EBADCERT
+ */
+int knot_tls_cert_check_hostnames(struct gnutls_session_int *session,
+                                  const char *hostnames[]);
+
+/*!
+ * \brief Checks remote certificate validity against credentials.
+ *
+ * \param session   TLS connection.
+ * \param creds     TLS credentials.
+ *
+ * \return KNOT_EOK or KNOT_EBADCERT
+ */
+int knot_tls_cert_check(struct gnutls_session_int *session,
+                        struct knot_creds *creds);
+
 /*! @} */
index b21c7ec0e02afaff29f504a984da6d56aa768438..459e2bcf51f256d12e35bfdb2300727210bc1dbd 100644 (file)
@@ -349,7 +349,7 @@ void *xdp_gun_thread(void *_ctx)
        }
        if (ctx->quic) {
 #ifdef ENABLE_QUIC
-               quic_creds = knot_creds_init_peer(NULL, NULL, 0);
+               quic_creds = knot_creds_init_peer(NULL, NULL, NULL);
                if (quic_creds == NULL) {
                        ERR2("failed to initialize QUIC context");
                        goto cleanup;
index e29afbae4062981b5d074a9215e77ddd6daf7d3e..dd35c2d56d185a28a282f1a094afd67341117aa8 100644 (file)
@@ -7,6 +7,8 @@ from dnstest.utils import *
 import random
 import subprocess
 
+use_hostname = random.choice([True, False])
+
 t = Test(quic=True, tsig=True) # TSIG needed to skip weaker ACL rules
 
 master = t.server("knot")
@@ -33,7 +35,7 @@ if slave.valgrind:
 MSG_DENIED_NOTIFY = "ACL, denied, action notify"
 MSG_DENIED_TRANSFER = "ACL, denied, action transfer"
 MSG_RMT_NOTAUTH = "server responded with error 'NOTAUTH'"
-MSG_RMT_BADCERT = "failed (unknown certificate key)"
+MSG_RMT_BADCERT = "failed (invalid certificate)"
 MSG_TSIG_ERROR = "failed (failed to verify TSIG)"
 
 def check_error(server, msg):
@@ -53,6 +55,8 @@ def upd_check_zones(master, slave, zones, prev_serials):
 
 master.check_quic()
 
+t.gen_ca()
+
 t.start()
 
 tcpdump_pcap = t.out_dir + "/traffic.pcap"
@@ -72,7 +76,10 @@ try:
     t.xfr_diff(master, slave, zones)
 
     # Check master not authenticated due to bad cert-key
-    master.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+    if use_hostname:
+        master.cert_hostname = ["unknown"]
+    else:
+        master.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
     slave.gen_confile()
     slave.reload()
     master.ctl("zone-notify")
@@ -82,13 +89,25 @@ try:
     check_error(slave, MSG_RMT_BADCERT)
 
     # Check IXFR with cert-key-based authenticated master
-    master.fill_cert_key()
+    if use_hostname:
+        master.gen_cert()
+        master.cert_hostname = [master.name, "bad2"]
+        slave.set_ca()
+        master.gen_confile()
+        master.reload()
+    else:
+        master.fill_cert_key()
     slave.gen_confile()
-    slave.reload()
+    #slave.reload() doesn't work for hostname, restart instead till fixed
+    slave.stop()
+    slave.start()
     serials = upd_check_zones(master, slave, rnd_zones, serials)
 
     # Check slave not authenticated due to bad cert-key
-    slave.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+    if use_hostname:
+        slave.cert_hostname = ["unknown"]
+    else:
+        slave.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
     master.gen_confile()
     master.reload()
     master.ctl("zone-notify")
@@ -98,9 +117,18 @@ try:
     check_error(master, MSG_DENIED_TRANSFER)
 
     # Check IXFR with cert-key-based authenticated slave
-    slave.fill_cert_key()
+    if use_hostname:
+        slave.gen_cert()
+        slave.cert_hostname = ["bad1", "bad2", "bad3", slave.name]
+        master.set_ca()
+        slave.gen_confile()
+        slave.reload()
+    else:
+        slave.fill_cert_key()
     master.gen_confile()
-    master.reload()
+    #master.reload() doesn't work for hostname, restart instead till fixed
+    master.stop()
+    master.start()
     serials = upd_check_zones(master, slave, rnd_zones, serials)
 
 finally:
index ba986587fd37581f628098347d2bf4c4334c614c..92292b242ccb8d17e959754030356dc5cddcd8f5 100644 (file)
@@ -7,6 +7,8 @@ from dnstest.utils import *
 import random
 import subprocess
 
+use_hostname = random.choice([True, False])
+
 t = Test(tls=True, tsig=True, # TSIG needed to skip weaker ACL rules
          quic=random.choice([False, True])) # QUIC should have no effect
 
@@ -34,7 +36,7 @@ if slave.valgrind:
 MSG_DENIED_NOTIFY = "ACL, denied, action notify"
 MSG_DENIED_TRANSFER = "ACL, denied, action transfer"
 MSG_RMT_NOTAUTH = "server responded with error 'NOTAUTH'"
-MSG_RMT_BADCERT = "failed (unknown certificate key)"
+MSG_RMT_BADCERT = "failed (invalid certificate)"
 MSG_TSIG_ERROR = "failed (failed to verify TSIG)"
 
 def check_error(server, msg):
@@ -54,6 +56,8 @@ def upd_check_zones(master, slave, zones, prev_serials):
 
 master.check_quic()
 
+t.gen_ca()
+
 t.start()
 
 tcpdump_pcap = t.out_dir + "/traffic.pcap"
@@ -73,7 +77,10 @@ try:
     t.xfr_diff(master, slave, zones)
 
     # Check master not authenticated due to bad cert-key
-    master.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+    if use_hostname:
+        master.cert_hostname = ["unknown"]
+    else:
+        master.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
     slave.gen_confile()
     slave.reload()
     master.ctl("zone-notify")
@@ -83,13 +90,25 @@ try:
     check_error(slave, MSG_RMT_BADCERT)
 
     # Check IXFR with cert-key-based authenticated master
-    master.fill_cert_key()
+    if use_hostname:
+        master.gen_cert()
+        master.cert_hostname = [master.name, "bad2"]
+        slave.set_ca()
+        master.gen_confile()
+        master.reload()
+    else:
+        master.fill_cert_key()
     slave.gen_confile()
-    slave.reload()
+    #slave.reload() doesn't work for hostname, restart instead till fixed
+    slave.stop()
+    slave.start()
     serials = upd_check_zones(master, slave, rnd_zones, serials)
 
     # Check slave not authenticated due to bad cert-key
-    slave.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
+    if use_hostname:
+        slave.cert_hostname = ["unknown"]
+    else:
+        slave.cert_key = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY="
     master.gen_confile()
     master.reload()
     master.ctl("zone-notify")
@@ -99,9 +118,18 @@ try:
     check_error(master, MSG_DENIED_TRANSFER)
 
     # Check IXFR with cert-key-based authenticated slave
-    slave.fill_cert_key()
+    if use_hostname:
+        slave.gen_cert()
+        slave.cert_hostname = ["bad1", "bad2", "bad3", slave.name]
+        master.set_ca()
+        slave.gen_confile()
+        slave.reload()
+    else:
+        slave.fill_cert_key()
     master.gen_confile()
-    master.reload()
+    #master.reload() doesn't work for hostname, restart instead till fixed
+    master.stop()
+    master.start()
     serials = upd_check_zones(master, slave, rnd_zones, serials)
 
 finally:
index 47dce788764c6cddd0e40112a1df9191f11c3580..8a8bde2da58d7fc8b87aafb82e3891f110709bbf 100644 (file)
@@ -168,6 +168,8 @@ class Server(object):
         self.quic_port = None
         self.tls_port = None
         self.cert_key = str()
+        self.cert_hostname = list()
+        self.ca_file = str()
         self.cert_key_file = None # quadruple (key_file, cert_file, hostname, pin)
         self.udp_workers = None
         self.tcp_workers = None
@@ -617,6 +619,28 @@ class Server(object):
         hostname3 = socket.gethostname()
         return ("", certfile, hostname1 or hostname2 or hostname3, ssearch(gcli_s, r'pin-sha256:([^\n]*)'))
 
+    def gen_cert(self):
+        try:
+            ca_key = os.path.join(Context().test.out_dir, "ca-key.pem")
+            ca_cert = os.path.join(Context().test.out_dir, "ca-cert.pem")
+            srv_key = os.path.join(self.dir, f"{self.name}-key.pem")
+            srv_cert = os.path.join(self.dir, f"{self.name}-cert.pem")
+            srv_tpl = os.path.join(self.dir, f"{self.name}-tpl.info")
+            check_call(["certtool", "--generate-privkey", "--key-type", "ed25519",
+                        "--outfile", srv_key], stdout=DEVNULL, stderr=DEVNULL)
+            with open(srv_tpl, "w") as tpl:
+                print(f"dns_name = \"{self.name}\"\nexpiration_days = 365", file=tpl)
+            check_call(["certtool", "--generate-certificate", "--load-privkey", srv_key,
+                        "--load-ca-certificate", ca_cert, "--load-ca-privkey", ca_key,
+                        "--template", srv_tpl, "--outfile", srv_cert],
+                       stdout=DEVNULL, stderr=DEVNULL)
+            self.cert_key_file = (srv_key, srv_cert, self.name, fsearch(srv_key, r'pin-sha256:([^\n]*)'))
+        except CalledProcessError as e:
+            raise Failed("Failed to generate server certificate")
+
+    def set_ca(self):
+        self.ca_file = os.path.join(Context().test.out_dir, "ca-cert.pem")
+
     def kdig(self, rname, rtype, rclass="IN", dnssec=None, validate=None, msgdelay=None):
         cmd = [ params.kdig_bin, "@" + self.addr, "-p", str(self.port), "-q", rname, "-t", rtype, "-c", rclass ]
         if dnssec:
@@ -1267,9 +1291,8 @@ class Bind(Server):
                                   % (slave.addr, slave.port, self.tsig.name)
                     else:
                         slaves += "%s port %s" % (slave.addr, slave.port)
-                    #if slave.tls_port:
-                    #    slaves += " tls %s" % (slave.name if slave.cert_key_file else "ephemeral")
-                    # TODO Bind9 fails to send NOTIFYoverTLS, until fixed https://gitlab.isc.org/isc-projects/bind9/-/issues/4821
+                    if slave.tls_port:
+                        slaves += " tls %s" % (slave.name if slave.cert_key_file else "ephemeral")
                     slaves += "; "
                 if slaves:
                     s.item("also-notify", "{ %s}" % slaves)
@@ -1522,6 +1545,8 @@ class Knot(Server):
         if self.cert_key_file:
             s.item_str("key-file", self.cert_key_file[0])
             s.item_str("cert-file", self.cert_key_file[1])
+        if self.ca_file:
+            s.item_str("ca-file", self.ca_file)
         s.end()
 
         if self.xdp_port is not None and self.xdp_port > 0:
@@ -1569,6 +1594,8 @@ class Knot(Server):
                         s.item_str("tls" if master.tls_port else "quic", "on")
                         if master.cert_key:
                             s.item_str("cert-key", master.cert_key)
+                        elif master.cert_hostname:
+                            s.item_list("cert-hostname", master.cert_hostname)
                         elif master.cert_key_file:
                             s.item_str("cert-key", master.cert_key_file[3])
                     else:
@@ -1594,6 +1621,8 @@ class Knot(Server):
                         s.item_str("tls" if slave.tls_port else "quic", "on")
                         if slave.cert_key:
                             s.item_str("cert-key", slave.cert_key)
+                        elif slave.cert_hostname:
+                            s.item_list("cert-hostname", slave.cert_hostname)
                         elif slave.cert_key_file:
                             s.item_str("cert-key", slave.cert_key_file[3])
                     else:
@@ -1630,6 +1659,8 @@ class Knot(Server):
                         s.item_str("tls" if remote.tls_port else "quic", "on")
                         if remote.cert_key:
                             s.item_str("cert-key", remote.cert_key)
+                        elif remote.cert_hostname:
+                            s.item_list("cert-hostname", remote.cert_hostname)
                         elif remote.cert_key_file:
                             s.item_str("cert-key", remote.cert_key_file[3])
                     else:
@@ -1665,8 +1696,10 @@ class Knot(Server):
                         s.item_str("address", master.addr)
                     if master.tsig:
                         s.item_str("key", master.tsig.name)
-                    if master.cert_key and not isinstance(master, Bind): # TODO until fixed https://gitlab.isc.org/isc-projects/bind9/-/issues/4821
+                    if master.cert_key:
                         s.item_str("cert-key", master.cert_key)
+                    if master.cert_hostname:
+                        s.item_list("cert-hostname", master.cert_hostname)
                     s.item("action", "notify")
                     servers.add(master.name)
             for slave in z.slaves:
@@ -1681,6 +1714,8 @@ class Knot(Server):
                     s.item_str("key", slave.tsig.name)
                 if slave.cert_key:
                     s.item_str("cert-key", slave.cert_key)
+                if slave.cert_hostname:
+                    s.item_list("cert-hostname", slave.cert_hostname)
                 s.item("action", "[transfer" + (", update" if z.ddns else "") + "]")
                 servers.add(slave.name)
             for remote in z.dnssec.dnskey_sync if z.dnssec.dnskey_sync else []:
index 883948684b2b70a20d2f2804d73d2692ba52b861..53bc43e5584c4b471867633ff380fd0b8e6948fc 100644 (file)
@@ -11,6 +11,7 @@ import time
 import dns.name
 import dns.zone
 import zone_generate
+from subprocess import Popen, PIPE, check_call, CalledProcessError, check_output, run, DEVNULL
 from dnstest.utils import *
 from dnstest.context import Context
 import dnstest.params as params
@@ -628,3 +629,17 @@ class Test(object):
 
         resp_ixfr.check_axfr_style_ixfr(resp_axfr)
 
+    def gen_ca(self):
+        try:
+            ca_key = os.path.join(self.out_dir, "ca-key.pem")
+            ca_cert = os.path.join(self.out_dir, "ca-cert.pem")
+            ca_tpl = os.path.join(self.out_dir, "ca-tpl.info")
+            check_call(["certtool", "--generate-privkey", "--key-type", "ed25519",
+                        "--outfile", ca_key], stdout=DEVNULL, stderr=DEVNULL)
+            with open(ca_tpl, "w") as tpl:
+                print(f"cn = \"CA\"\nca\ncert_signing_key\nexpiration_days = 3650", file=tpl)
+            check_call(["certtool", "--generate-self-signed", "--load-privkey",
+                        ca_key, "--template", ca_tpl, "--outfile", ca_cert],
+                       stdout=DEVNULL, stderr=DEVNULL)
+        except CalledProcessError as e:
+            raise Failed("Failed to generate CA")
index 1001d89f74067157fba98d89d9587ea04984163c..6199e71292428e6a12be4f8b50da825d626a1ac9 100644 (file)
@@ -82,7 +82,7 @@ static knot_request_t *make_query(knot_requestor_t *requestor,
        knot_request_flag_t flags = TFO ? KNOT_REQUEST_TFO: KNOT_REQUEST_NONE;
 
        return knot_request_make_generic(requestor->mm, dst, src, pkt, NULL,
-                                        NULL, NULL, NULL, 0, flags);
+                                        NULL, NULL, NULL, NULL, flags);
 }
 
 static void test_disconnected(knot_requestor_t *requestor,