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
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:
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:
......................
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:
- 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:
udp-max-payload-ipv6: SIZE
key-file: STR
cert-file: STR
+ ca-file: STR ...
edns-client-subnet: BOOL
answer-rotation: BOOL
automatic-acl: BOOL
*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
tls: BOOL
key: key_id
cert-key: BASE64 ...
+ cert-hostname: STR ...
block-notify-after-transfer: BOOL
no-edns: BOOL
automatic-acl: BOOL
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
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 ...
--------
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
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);
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);
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;
}
#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. */
#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. */
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. */
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 },
{ 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 } },
{ 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 }
};
#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"
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;
}
#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;
}
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);
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)
{
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;
#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,
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);
&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;
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;
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;
}
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;
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)
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)
* \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.
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);
/*!
#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;
}
#include <stdint.h>
#include <sys/socket.h>
+#include "knot/conf/conf.h"
#include "libknot/quic/tls_common.h"
struct knot_request;
*
* \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).
}
}
-static int check_file(char *path, char *role)
+static int check_file(const char *path, char *role)
{
if (path == NULL) {
return KNOT_EOK;
{
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) {
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;
}
}
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) {
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);
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;
}
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;
}
KNOT_EEMPTYZONE,
KNOT_ENODB,
KNOT_EUNREACH,
- KNOT_EBADCERTKEY,
+ KNOT_EBADCERT,
KNOT_EFACCES,
KNOT_EBACKUPDATA,
KNOT_ECPUCOMPAT,
{ 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" },
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) {
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;
}
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:
* For more information, see <https://www.knot-dns.cz/>
*/
+#include <assert.h>
#include <fcntl.h>
#include <gnutls/crypto.h>
#include <gnutls/gnutls.h>
#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>
#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_
}
_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;
}
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;
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) {
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;
}
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);
}
#include <stdint.h>
#define KNOT_TLS_PIN_LEN 32
+#define KNOT_TLS_MAX_PINS 4
struct gnutls_priority_st;
struct gnutls_session_int;
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.
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);
* \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);
+
/*! @} */
}
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;
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")
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):
master.check_quic()
+t.gen_ca()
+
t.start()
tcpdump_pcap = t.out_dir + "/traffic.pcap"
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")
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")
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:
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
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):
master.check_quic()
+t.gen_ca()
+
t.start()
tcpdump_pcap = t.out_dir + "/traffic.pcap"
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")
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")
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:
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
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:
% (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)
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:
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:
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:
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:
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:
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 []:
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
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")
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,