Commit 50600859 authored by Ondřej Surý's avatar Ondřej Surý

Merge branch 'kdig-dns-over-tls' into 'master'

DNS over TLS in kdig



See merge request !569
parents 5aad9c51 c22e726b
......@@ -433,6 +433,8 @@ src/libknot/yparser/ypscheme.c
src/libknot/yparser/ypscheme.h
src/libknot/yparser/yptrafo.c
src/libknot/yparser/yptrafo.h
src/utils/common/cert.c
src/utils/common/cert.h
src/utils/common/exec.c
src/utils/common/exec.h
src/utils/common/hex.c
......@@ -449,6 +451,8 @@ src/utils/common/resolv.c
src/utils/common/resolv.h
src/utils/common/sign.c
src/utils/common/sign.h
src/utils/common/tls.c
src/utils/common/tls.h
src/utils/common/token.c
src/utils/common/token.h
src/utils/kdig/kdig_exec.c
......@@ -572,6 +576,7 @@ tests/requestor.c
tests/rrl.c
tests/server.c
tests/test_conf.h
tests/utils/test_cert.c
tests/utils/test_lookup.c
tests/worker_pool.c
tests/worker_queue.c
......
......@@ -201,6 +201,21 @@ Use the TCP protocol (default is UDP for standard query and TCP for AXFR/IXFR).
\fB+\fP[\fBno\fP]\fBignore\fP
Don\(aqt use TCP automatically if a truncated reply is received.
.TP
\fB+\fP[\fBno\fP]\fBtls\fP
Use TLS with the Opportunistic privacy profile.
.TP
\fB+\fP[\fBno\fP]\fBtls\-ca\fP[=\fIFILE\fP]
Use TLS with the Out\-Of\-Band privacy profile, use a specified PEM file
(default is system certificate storage if no argument is provided).
Can be specified multiple times.
.TP
\fB+\fP[\fBno\fP]\fBtls\-pin\fP=\fIBASE64\fP
Use TLS with a pinned certificate check. The PIN must be a Base64 encoded
SHA\-256 hash of the X.509 SubjectPublicKeyInfo. Can be specified multiple times.
.TP
\fB+\fP[\fBno\fP]\fBtls\-hostname\fP=\fISTR\fP
Use TLS with a remote server hostname check.
.TP
\fB+\fP[\fBno\fP]\fBnsid\fP
Request the nameserver identifier (NSID).
.TP
......@@ -276,6 +291,21 @@ $ kdig +tcp example.com \-t A @192.0.2.1 \-x 2001:DB8::1 @192.0.2.2
.fi
.UNINDENT
.UNINDENT
.IP 4. 3
Get SOA record for example.com, use TLS, use system certificates, check
for specified hostname, check for certificate pin, and print additional
debug info:
.INDENT 3.0
.INDENT 3.5
.sp
.nf
.ft C
$ kdig \-d @185.49.141.38 +tls\-ca +tls\-host=getdnsapi.net \e
+tls\-pin=foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9S= soa example.com
.ft P
.fi
.UNINDENT
.UNINDENT
.UNINDENT
.SH FILES
.sp
......
......@@ -178,6 +178,21 @@ Options
**+**\ [\ **no**\ ]\ **ignore**
Don't use TCP automatically if a truncated reply is received.
**+**\ [\ **no**\ ]\ **tls**
Use TLS with the Opportunistic privacy profile.
**+**\ [\ **no**\ ]\ **tls-ca**\[\ =\ *FILE*\]
Use TLS with the Out-Of-Band privacy profile, use a specified PEM file
(default is system certificate storage if no argument is provided).
Can be specified multiple times.
**+**\ [\ **no**\ ]\ **tls-pin**\ =\ *BASE64*
Use TLS with a pinned certificate check. The PIN must be a Base64 encoded
SHA-256 hash of the X.509 SubjectPublicKeyInfo. Can be specified multiple times.
**+**\ [\ **no**\ ]\ **tls-hostname**\ =\ *STR*
Use TLS with a remote server hostname check.
**+**\ [\ **no**\ ]\ **nsid**
Request the nameserver identifier (NSID).
......@@ -232,6 +247,13 @@ Examples
$ kdig +tcp example.com -t A @192.0.2.1 -x 2001:DB8::1 @192.0.2.2
4. Get SOA record for example.com, use TLS, use system certificates, check
for specified hostname, check for certificate pin, and print additional
debug info::
$ kdig -d @185.49.141.38 +tls-ca +tls-host=getdnsapi.net \
+tls-pin=foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9S= soa example.com
Files
-----
......
......@@ -200,6 +200,8 @@ endif
# static: utilities shared
libknotus_la_SOURCES = \
utils/common/cert.c \
utils/common/cert.h \
utils/common/exec.c \
utils/common/exec.h \
utils/common/hex.c \
......@@ -216,6 +218,8 @@ libknotus_la_SOURCES = \
utils/common/resolv.h \
utils/common/sign.c \
utils/common/sign.h \
utils/common/tls.c \
utils/common/tls.h \
utils/common/token.c \
utils/common/token.h
......
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gnutls/abstract.h>
#include <gnutls/crypto.h>
#include "utils/common/cert.h"
#include "libknot/error.h"
static int spki_hash(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t alg,
uint8_t *hash, size_t size)
{
if (!cert || !hash || gnutls_hash_get_len(alg) != size) {
return KNOT_EINVAL;
}
gnutls_pubkey_t key = { 0 };
if (gnutls_pubkey_init(&key) != GNUTLS_E_SUCCESS) {
return KNOT_ENOMEM;
}
if (gnutls_pubkey_import_x509(key, cert, 0) != GNUTLS_E_SUCCESS) {
gnutls_pubkey_deinit(key);
return KNOT_ERROR;
}
gnutls_datum_t der = { 0 };
if (gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &der) != GNUTLS_E_SUCCESS) {
gnutls_pubkey_deinit(key);
return KNOT_ERROR;
}
int ret = gnutls_hash_fast(alg, der.data, der.size, hash);
gnutls_free(der.data);
gnutls_pubkey_deinit(key);
if (ret != GNUTLS_E_SUCCESS) {
return KNOT_ERROR;
}
return KNOT_EOK;
}
int cert_get_pin(gnutls_x509_crt_t cert, uint8_t *pin, size_t size)
{
return spki_hash(cert, GNUTLS_DIG_SHA256, pin, size);
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gnutls/x509.h>
#include <stdint.h>
#include <stdlib.h>
#define CERT_PIN_LEN 32
/*!
* \brief Get certificate pin value.
*
* The pin is a SHA-256 hash of the X.509 SubjectPublicKeyInfo.
*
* \param[in] crt Certificate.
* \param[out] pin Pin.
* \param[in] size Length of the pin, must be CERT_PIN_LEN.
*
* \return Error code, KNOT_EOK if successful.
*/
int cert_get_pin(gnutls_x509_crt_t crt, uint8_t *pin, size_t size);
......@@ -597,6 +597,7 @@ void print_packet(const knot_pkt_t *packet,
// Print packet information header.
if (style->show_header) {
print_tls(&net->tls);
print_header(packet, style, rcode);
}
......
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -28,10 +28,11 @@
#include "utils/common/netio.h"
#include "utils/common/msg.h"
#include "utils/common/tls.h"
#include "libknot/libknot.h"
#include "contrib/sockaddr.h"
srv_info_t* srv_info_create(const char *name, const char *service)
srv_info_t *srv_info_create(const char *name, const char *service)
{
if (name == NULL || service == NULL) {
DBG_NULL;
......@@ -99,7 +100,7 @@ int get_socktype(const protocol_t proto, const uint16_t type)
}
}
const char* get_sockname(const int socktype)
const char *get_sockname(const int socktype)
{
switch (socktype) {
case SOCK_STREAM:
......@@ -157,12 +158,13 @@ void get_addr_str(const struct sockaddr_storage *ss,
}
}
int net_init(const srv_info_t *local,
const srv_info_t *remote,
const int iptype,
const int socktype,
const int wait,
net_t *net)
int net_init(const srv_info_t *local,
const srv_info_t *remote,
const int iptype,
const int socktype,
const int wait,
const tls_params_t *tls_params,
net_t *net)
{
if (remote == NULL || net == NULL) {
DBG_NULL;
......@@ -194,14 +196,19 @@ int net_init(const srv_info_t *local,
net->local = local;
net->remote = remote;
// Prepare for TLS.
if (tls_params != NULL && tls_params->enable) {
int ret = tls_ctx_init(&net->tls, tls_params, net->wait);
if (ret != KNOT_EOK) {
return ret;
}
}
return KNOT_EOK;
}
int net_connect(net_t *net)
{
struct pollfd pfd;
int sockfd;
if (net == NULL || net->srv == NULL) {
DBG_NULL;
return KNOT_EINVAL;
......@@ -212,16 +219,18 @@ int net_connect(net_t *net)
net->socktype, &net->remote_str);
// Create socket.
sockfd = socket(net->srv->ai_family, net->socktype, 0);
int sockfd = socket(net->srv->ai_family, net->socktype, 0);
if (sockfd == -1) {
WARN("can't create socket for %s\n", net->remote_str);
return KNOT_NET_ESOCKET;
}
// Initialize poll descriptor structure.
pfd.fd = sockfd;
pfd.events = POLLOUT;
pfd.revents = 0;
struct pollfd pfd = {
.fd = sockfd,
.events = POLLOUT,
.revents = 0,
};
// Set non-blocking socket.
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
......@@ -264,6 +273,15 @@ int net_connect(net_t *net)
close(sockfd);
return KNOT_NET_ECONNECT;
}
// Establish TLS connection.
if (net->tls.params != NULL) {
int ret = tls_ctx_connect(&net->tls, sockfd, net->remote->name);
if (ret != KNOT_EOK) {
close(sockfd);
return ret;
}
}
}
// Store socket descriptor.
......@@ -313,7 +331,22 @@ int net_send(const net_t *net, const uint8_t *buf, const size_t buf_len)
return KNOT_EINVAL;
}
if (net->socktype == SOCK_STREAM) {
// Send data over UDP.
if (net->socktype == SOCK_DGRAM) {
if (sendto(net->sockfd, buf, buf_len, 0, net->srv->ai_addr,
net->srv->ai_addrlen) != (ssize_t)buf_len) {
WARN("can't send query to %s\n", net->remote_str);
return KNOT_NET_ESEND;
}
// Send data over TLS.
} else if (net->tls.params != NULL) {
int ret = tls_ctx_send((tls_ctx_t *)&net->tls, buf, buf_len);
if (ret != KNOT_EOK) {
WARN("can't send query to %s\n", net->remote_str);
return KNOT_NET_ESEND;
}
// Send data over TCP.
} else {
struct iovec iov[2];
// Leading packet length bytes.
......@@ -332,13 +365,6 @@ int net_send(const net_t *net, const uint8_t *buf, const size_t buf_len)
WARN("can't send query to %s\n", net->remote_str);
return KNOT_NET_ESEND;
}
} else {
// Send data.
if (sendto(net->sockfd, buf, buf_len, 0, net->srv->ai_addr,
net->srv->ai_addrlen) != (ssize_t)buf_len) {
WARN("can't send query to %s\n", net->remote_str);
return KNOT_NET_ESEND;
}
}
return KNOT_EOK;
......@@ -346,50 +372,71 @@ int net_send(const net_t *net, const uint8_t *buf, const size_t buf_len)
int net_receive(const net_t *net, uint8_t *buf, const size_t buf_len)
{
ssize_t ret;
struct pollfd pfd;
if (net == NULL || buf == NULL) {
DBG_NULL;
return KNOT_EINVAL;
}
// Initialize poll descriptor structure.
pfd.fd = net->sockfd;
pfd.events = POLLIN;
pfd.revents = 0;
struct pollfd pfd = {
.fd = net->sockfd,
.events = POLLIN,
.revents = 0,
};
// Receive data over UDP.
if (net->socktype == SOCK_DGRAM) {
struct sockaddr_storage from;
memset(&from, '\0', sizeof(from));
if (net->socktype == SOCK_STREAM) {
uint16_t msg_len = 0;
uint32_t total = 0;
// Receive replies unless correct reply or timeout.
while (true) {
socklen_t from_len = sizeof(from);
// Receive TCP message header.
while (total < sizeof(msg_len)) {
// Wait for datagram data.
if (poll(&pfd, 1, 1000 * net->wait) != 1) {
WARN("response timeout for %s\n",
net->remote_str);
return KNOT_NET_ETIMEOUT;
}
// Receive piece of message.
ret = recv(net->sockfd, (uint8_t *)&msg_len + total,
sizeof(msg_len) - total, 0);
// Receive whole UDP datagram.
ssize_t ret = recvfrom(net->sockfd, buf, buf_len, 0,
(struct sockaddr *)&from, &from_len);
if (ret <= 0) {
WARN("can't receive reply from %s\n",
net->remote_str);
return KNOT_NET_ERECV;
}
total += ret;
}
// Compare reply address with the remote one.
if (from_len > sizeof(from) ||
memcmp(&from, net->srv->ai_addr, from_len) != 0) {
char *src = NULL;
get_addr_str(&from, net->socktype, &src);
WARN("unexpected reply source %s\n", src);
free(src);
continue;
}
// Convert number to host format.
msg_len = ntohs(msg_len);
return ret;
}
// Receive data over TLS.
} else if (net->tls.params != NULL) {
int ret = tls_ctx_receive((tls_ctx_t *)&net->tls, buf, buf_len);
if (ret < 0) {
WARN("can't receive reply from %s\n", net->remote_str);
return KNOT_NET_ERECV;
}
total = 0;
return ret;
// Receive data over TCP.
} else {
uint32_t total = 0;
// Receive whole answer message by parts.
while (total < msg_len) {
uint16_t msg_len = 0;
// Receive TCP message header.
while (total < sizeof(msg_len)) {
if (poll(&pfd, 1, 1000 * net->wait) != 1) {
WARN("response timeout for %s\n",
net->remote_str);
......@@ -397,53 +444,43 @@ int net_receive(const net_t *net, uint8_t *buf, const size_t buf_len)
}
// Receive piece of message.
ret = recv(net->sockfd, buf + total, msg_len - total, 0);
ssize_t ret = recv(net->sockfd, (uint8_t *)&msg_len + total,
sizeof(msg_len) - total, 0);
if (ret <= 0) {
WARN("can't receive reply from %s\n",
net->remote_str);
return KNOT_NET_ERECV;
}
total += ret;
}
return total;
} else {
struct sockaddr_storage from;
memset(&from, '\0', sizeof(from));
// Convert number to host format.
msg_len = ntohs(msg_len);
if (msg_len > buf_len) {
return KNOT_ESPACE;
}
// Receive replies unless correct reply or timeout.
while (true) {
socklen_t from_len = sizeof(from);
total = 0;
// Wait for datagram data.
// Receive whole answer message by parts.
while (total < msg_len) {
if (poll(&pfd, 1, 1000 * net->wait) != 1) {
WARN("response timeout for %s\n",
net->remote_str);
return KNOT_NET_ETIMEOUT;
}
// Receive whole UDP datagram.
ret = recvfrom(net->sockfd, buf, buf_len, 0,
(struct sockaddr *)&from, &from_len);
// Receive piece of message.
ssize_t ret = recv(net->sockfd, buf + total, msg_len - total, 0);
if (ret <= 0) {
WARN("can't receive reply from %s\n",
net->remote_str);
return KNOT_NET_ERECV;
}
// Compare reply address with the remote one.
if (from_len > sizeof(from) ||
memcmp(&from, net->srv->ai_addr, from_len) != 0) {
char *src = NULL;
get_addr_str(&from, net->socktype, &src);
WARN("unexpected reply source %s\n", src);
free(src);
continue;
}
return ret;
total += ret;
}
return total;
}
return KNOT_NET_ERECV;
......@@ -456,6 +493,7 @@ void net_close(net_t *net)
return;
}
tls_ctx_close(&net->tls);
close(net->sockfd);
net->sockfd = -1;
}
......@@ -477,4 +515,6 @@ void net_clean(net_t *net)
if (net->remote_info != NULL) {
freeaddrinfo(net->remote_info);
}
tls_ctx_deinit(&net->tls);
}
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -14,9 +14,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*!
* \file netio.h
*
* \author Daniel Salzman <daniel.salzman@nic.cz>
* \file
*
* \brief Networking abstraction for utilities.
*
......@@ -31,6 +29,7 @@
#include <sys/socket.h>
#include "utils/common/params.h"
#include "utils/common/tls.h"
/*! \brief Structure containing server information. */
typedef struct {
......@@ -74,6 +73,9 @@ typedef struct {
* used.
*/
struct addrinfo *local_info;
/*! TLS context. */
tls_ctx_t tls;
} net_t;
/*!
......@@ -85,7 +87,7 @@ typedef struct {
* \retval server if success.
* \retval NULL if error.
*/
srv_info_t* srv_info_create(const char *name, const char *service);
srv_info_t *srv_info_create(const char *name, const char *service);
/*!
* \brief Destroys server structure.
......@@ -121,7 +123,7 @@ int get_socktype(const protocol_t proto, const uint16_t type);
*
* \retval "TCP" or "UDP".
*/
const char* get_sockname(const int socktype);
const char *get_sockname(const int socktype);
/*!
* \brief Translates int socket type to the common string one.
......@@ -142,17 +144,19 @@ void get_addr_str(const struct sockaddr_storage *ss,
* \param iptype IP version.
* \param socktype Socket type.
* \param wait Network timeout interval.
* \param tls_params TLS parameters.
* \param net Network structure to initialize.
*
* \retval KNOT_EOK if success.
* \retval errcode if error.
*/
int net_init(const srv_info_t *local,
const srv_info_t *remote,
const int iptype,
const int socktype,
const int wait,
net_t *net);
int net_init(const srv_info_t *local,
const srv_info_t *remote,
const int iptype,
const int socktype,
const int wait,
const tls_params_t *tls_params,
net_t *net);
/*!
* \brief Creates socket and connects (if TCP) to remote address specified
......
......@@ -119,10 +119,8 @@ int best_param(const char *str, const size_t str_len, const param_t *tbl,
case -1:
continue;
case 0:
best_pos = i;
best_match = 0;
matches = 1;
break;
*unique = true;
return i;
default:
if (ret < best_match) {
best_pos = i;
......
......@@ -34,6 +34,7 @@
#define DEFAULT_IPV4_NAME "127.0.0.1"
#define DEFAULT_IPV6_NAME "::1"
#define DEFAULT_DNS_PORT "53"
#define DEFAULT_DNS_TLS_PORT "853"
#define DEFAULT_UDP_SIZE 512
#define DEFAULT_EDNS_SIZE 4096
#define MAX_PACKET_SIZE 65535
......
This diff is collapsed.
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <gnutls/gnutls.h>
#include "contrib/ucw/lists.h"
/*! \brief TLS parameters. */
typedef struct {
/*! Use TLS indicator. */
bool enable;
/*! Import system certificates indicator. */
bool system_ca;
/*! Certificate files to import. */
list_t ca_files;
/*! Pinned certificates. */
list_t pins;
/*! Required server hostname. */
char *hostname;
} tls_params_t;
/*! \brief TLS context. */
typedef struct {
/*! TLS handshake timeout. */
int wait;
/*! TLS parameters. */
const tls_params_t *params;
/*! GnuTLS session handle. */
gnutls_session_t session;
/*! GnuTLS credentials handle. */
gnutls_certificate_credentials_t credentials;
} tls_ctx_t;
void tls_params_init(tls_params_t *params);
int tls_params_copy(tls_params_t *dst, const tls_params_t *src);
void tls_params_clean(tls_params_t *params);
int tls_ctx_init(tls_ctx_t *ctx, const tls_params_t *params, int wait);
int tls_ctx_connect(tls_ctx_t *ctx, int sockfd, const char *remote);
int tls_ctx_send(tls_ctx_t *ctx, const uint8_t *buf, const size_t buf_len);
int tls_ctx_receive(tls_ctx_t *ctx, uint8_t *buf, const size_t buf_len);
void tls_ctx_close(tls_ctx_t *ctx);
void tls_ctx_deinit(tls_ctx_t *ctx);
void print_tls(const tls_ctx_t *ctx);
......@@ -705,7 +705,7 @@ static void process_query(const query_t *query)
for (size_t i = 0; i <= query->retries; i++) {
// Initialize network structure for current server.
ret = net_init(query->local, remote, iptype, socktype,
query->wait, &net);
query->wait, &query->tls, &net);
if (ret != KNOT_EOK) {
continue;
}
......@@ -987,8 +987,8 @@ static void process_xfr(const query_t *query)
get_sockname(socktype));
// Initialize network structure.
ret = net_init(query->local, remote, iptype, socktype,
query->wait, &net);
ret = net_init(query->local, remote, iptype, socktype, query->wait,
&query->tls, &net);
if (ret != KNOT_EOK) {
sign_context_deinit(&sign_ctx);
knot_pkt_free(&out_packet);
......
This diff is collapsed.
......@@ -136,6 +136,8 @@ struct query {
int32_t padding;
/*!< Query alignment with EDNS0 padding (0 ~ uninitialized). */
uint16_t alignment;
/*!< TLS parameters. */
tls_params_t tls;
#if USE_DNSTAP
/*!< Context for dnstap reader input. */
dt_reader_t *dt_reader;
......
......@@ -434,6 +434,7 @@ static int pkt_sendrecv(knsupdate_params_t *params)
get_iptype(params->ip),
get_socktype(params->protocol, KNOT_RRTYPE_SOA),
params->wait,
NULL,
&net);
if (ret != KNOT_EOK) {