diff options
Diffstat (limited to 'src/libknot/tsig-op.c')
-rw-r--r-- | src/libknot/tsig-op.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c new file mode 100644 index 0000000..7913859 --- /dev/null +++ b/src/libknot/tsig-op.c @@ -0,0 +1,683 @@ +/* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <inttypes.h> +#include <time.h> +#include <stdint.h> + +#include "libdnssec/error.h" +#include "libdnssec/tsig.h" +#include "libknot/attribute.h" +#include "libknot/tsig-op.h" +#include "libknot/errcode.h" +#include "libknot/descriptor.h" +#include "libknot/rrtype/tsig.h" +#include "libknot/packet/wire.h" +#include "libknot/consts.h" +#include "libknot/packet/rrset-wire.h" +#include "libknot/wire.h" +#include "contrib/string.h" + +const int KNOT_TSIG_MAX_DIGEST_SIZE = 64; // size of HMAC-SHA512 digest +const uint16_t KNOT_TSIG_FUDGE_DEFAULT = 300; // default Fudge value + +static int check_algorithm(const knot_rrset_t *tsig_rr) +{ + if (tsig_rr == NULL) { + return KNOT_EINVAL; + } + + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EMALF; + } + + dnssec_tsig_algorithm_t alg = dnssec_tsig_algorithm_from_dname(alg_name); + if (alg == DNSSEC_TSIG_UNKNOWN) { + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int check_key(const knot_rrset_t *tsig_rr, const knot_tsig_key_t *tsig_key) +{ + if (tsig_rr == NULL || tsig_key == NULL) { + return KNOT_EINVAL; + } + + const knot_dname_t *tsig_name = tsig_rr->owner; + if (!tsig_name) { + return KNOT_EMALF; + } + + if (!knot_dname_is_equal(tsig_name, tsig_key->name)) { + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int compute_digest(const uint8_t *wire, size_t wire_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key) +{ + if (!wire || !digest || !digest_len || !key) { + return KNOT_EINVAL; + } + + if (!key->name) { + return KNOT_EMALF; + } + + dnssec_tsig_ctx_t *ctx = NULL; + int result = dnssec_tsig_new(&ctx, key->algorithm, &key->secret); + if (result != DNSSEC_EOK) { + return KNOT_TSIG_EBADSIG; + } + + dnssec_binary_t cover = { .data = (uint8_t *)wire, .size = wire_len }; + dnssec_tsig_add(ctx, &cover); + + *digest_len = dnssec_tsig_size(ctx); + dnssec_tsig_write(ctx, digest); + dnssec_tsig_free(ctx); + + return KNOT_EOK; +} + +static int check_time_signed(const knot_rrset_t *tsig_rr, uint64_t prev_time_signed) +{ + if (!tsig_rr) { + return KNOT_EINVAL; + } + + /* Get the time signed and fudge values. */ + uint64_t time_signed = knot_tsig_rdata_time_signed(tsig_rr); + if (time_signed == 0) { + return KNOT_TSIG_EBADTIME; + } + uint16_t fudge = knot_tsig_rdata_fudge(tsig_rr); + if (fudge == 0) { + return KNOT_TSIG_EBADTIME; + } + + /* Get the current time. */ + time_t curr_time = time(NULL); + + /*!< \todo bleeding eyes. */ + double diff = difftime(curr_time, (time_t)time_signed); + + if (diff > fudge || diff < -fudge) { + return KNOT_TSIG_EBADTIME; + } + + diff = difftime((time_t)time_signed, prev_time_signed); + + if (diff < 0) { + return KNOT_TSIG_EBADTIME; + } + + return KNOT_EOK; +} + +static int write_tsig_variables(uint8_t *wire, const knot_rrset_t *tsig_rr) +{ + if (wire == NULL || tsig_rr == NULL) { + return KNOT_EINVAL; + } + + /* Copy TSIG variables - starting with key name. */ + const knot_dname_t *tsig_owner = tsig_rr->owner; + if (!tsig_owner) { + return KNOT_EINVAL; + } + + int offset = 0; + + offset += knot_dname_to_wire(wire + offset, tsig_owner, KNOT_DNAME_MAXLEN); + + /*!< \todo which order? */ + + /* Copy class. */ + knot_wire_write_u16(wire + offset, tsig_rr->rclass); + offset += sizeof(uint16_t); + + /* Copy TTL - always 0. */ + knot_wire_write_u32(wire + offset, tsig_rr->ttl); + offset += sizeof(uint32_t); + + /* Copy alg name. */ + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EINVAL; + } + + /* Te algorithm name must be in canonical form, i.e. in lowercase. */ + uint8_t *alg_name_wire = wire + offset; + offset += knot_dname_to_wire(alg_name_wire, alg_name, KNOT_DNAME_MAXLEN); + knot_dname_to_lower(alg_name_wire); + + /* Following data are written in network order. */ + /* Time signed. */ + knot_wire_write_u48(wire + offset, knot_tsig_rdata_time_signed(tsig_rr)); + offset += 6; + /* Fudge. */ + knot_wire_write_u16(wire + offset, knot_tsig_rdata_fudge(tsig_rr)); + offset += sizeof(uint16_t); + /* TSIG error. */ + knot_wire_write_u16(wire + offset, knot_tsig_rdata_error(tsig_rr)); + offset += sizeof(uint16_t); + /* Get other data length. */ + uint16_t other_data_length = knot_tsig_rdata_other_data_length(tsig_rr); + /* Get other data. */ + const uint8_t *other_data = knot_tsig_rdata_other_data(tsig_rr); + if (!other_data) { + return KNOT_EINVAL; + } + + /* + * We cannot write the whole other_data, as it contains its length in + * machine order. + */ + knot_wire_write_u16(wire + offset, other_data_length); + offset += sizeof(uint16_t); + + /* Skip the length. */ + memcpy(wire + offset, other_data, other_data_length); + + return KNOT_EOK; +} + +static int wire_write_timers(uint8_t *wire, const knot_rrset_t *tsig_rr) +{ + if (wire == NULL || tsig_rr == NULL) { + return KNOT_EINVAL; + } + + //write time signed + knot_wire_write_u48(wire, knot_tsig_rdata_time_signed(tsig_rr)); + //write fudge + knot_wire_write_u16(wire + 6, knot_tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +static int create_sign_wire(const uint8_t *msg, size_t msg_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_tsig_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + return KNOT_EINVAL; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig variables. + */ + size_t wire_len = msg_len + request_mac_len + (request_mac_len > 0 ? 2 : 0) + + knot_tsig_rdata_tsig_variables_length(tmp_tsig); + uint8_t *wire = malloc(wire_len); + if (!wire) { + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + uint8_t *pos = wire; + + /* Copy the request MAC - should work even if NULL. */ + if (request_mac_len > 0) { + knot_wire_write_u16(pos, request_mac_len); + pos += 2; + memcpy(pos, request_mac, request_mac_len); + } + pos += request_mac_len; + /* Copy the original message. */ + memcpy(pos, msg, msg_len); + pos += msg_len; + /* Copy TSIG variables. */ + ret = write_tsig_variables(pos, tmp_tsig); + if (ret != KNOT_EOK) { + free(wire); + return ret; + } + + /* Compute digest. */ + ret = compute_digest(wire, wire_len, digest, digest_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + free(wire); + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +static int create_sign_wire_next(const uint8_t *msg, size_t msg_len, + const uint8_t *prev_mac, size_t prev_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_tsig_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + return KNOT_EINVAL; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig variables. + */ + size_t wire_len = msg_len + prev_mac_len + knot_tsig_rdata_tsig_timers_length() + 2; + uint8_t *wire = malloc(wire_len); + if (!wire) { + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + /* Copy the request MAC - should work even if NULL. */ + knot_wire_write_u16(wire, prev_mac_len); + memcpy(wire + 2, prev_mac, prev_mac_len); + /* Copy the original message. */ + memcpy(wire + prev_mac_len + 2, msg, msg_len); + /* Copy TSIG variables. */ + + ret = wire_write_timers(wire + prev_mac_len + msg_len + 2, tmp_tsig); + if (ret != KNOT_EOK) { + free(wire); + return ret; + } + + /* Compute digest. */ + ret = compute_digest(wire, wire_len, digest, digest_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + free(wire); + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +_public_ +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint16_t tsig_rcode, + uint64_t request_time_signed) +{ + if (!msg || !msg_len || !key || digest == NULL || digest_len == NULL) { + return KNOT_EINVAL; + } + + knot_rrset_t *tmp_tsig = knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + uint16_t rdata_rcode = KNOT_RCODE_NOERROR; + if (tsig_rcode == KNOT_RCODE_BADTIME) { + rdata_rcode = tsig_rcode; + } + + const uint8_t *alg_name = dnssec_tsig_algorithm_to_dname(key->algorithm); + size_t alg_size = dnssec_tsig_algorithm_size(key->algorithm); + knot_tsig_create_rdata(tmp_tsig, alg_name, alg_size, rdata_rcode); + + /* Distinguish BADTIME response. */ + if (tsig_rcode == KNOT_RCODE_BADTIME) { + /* Set client's time signed into the time signed field. */ + knot_tsig_rdata_set_time_signed(tmp_tsig, request_time_signed); + + /* Store current time into Other data. */ + uint8_t time_signed[6]; + time_t now = time(NULL); + knot_wire_write_u48(time_signed, now); + + knot_tsig_rdata_set_other_data(tmp_tsig, 6, time_signed); + } else { + knot_tsig_rdata_set_time_signed(tmp_tsig, time(NULL)); + + /* Set other len. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, 0); + } + + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Set original ID */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + int ret = create_sign_wire(msg, *msg_len, /*msg_max_len,*/ + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, tmp_tsig, key); + if (ret != KNOT_EOK) { + knot_rrset_free(tmp_tsig, NULL); + return ret; + } + + /* Set the digest. */ + knot_tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + /* Write RRSet to wire */ + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + *digest_len = 0; + knot_rrset_free(tmp_tsig, NULL); + return ret; + } + + size_t tsig_wire_len = ret; + + knot_rrset_free(tmp_tsig, NULL); + + *msg_len += tsig_wire_len; + + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + /* everything went ok, save the digest to the output parameter */ + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +_public_ +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_tsig_key_t *key, uint8_t *to_sign, + size_t to_sign_len) +{ + if (!msg || !msg_len || !key || !digest || !digest_len) { + return KNOT_EINVAL; + } + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + knot_rrset_t *tmp_tsig = knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + const uint8_t *alg_name = dnssec_tsig_algorithm_to_dname(key->algorithm); + size_t alg_size = dnssec_tsig_algorithm_size(key->algorithm); + knot_tsig_create_rdata(tmp_tsig, alg_name, alg_size, 0); + knot_tsig_rdata_set_time_signed(tmp_tsig, time(NULL)); + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Create wire to be signed. */ + size_t wire_len = prev_digest_len + to_sign_len + KNOT_TSIG_TIMERS_LENGTH + 2; + uint8_t *wire = malloc(wire_len); + if (!wire) { + knot_rrset_free(tmp_tsig, NULL); + return KNOT_ENOMEM; + } + memset(wire, 0, wire_len); + + /* Write previous digest length. */ + knot_wire_write_u16(wire, prev_digest_len); + /* Write previous digest. */ + memcpy(wire + 2, prev_digest, prev_digest_len); + /* Write original message. */ + memcpy(wire + prev_digest_len + 2, to_sign, to_sign_len); + /* Write timers. */ + wire_write_timers(wire + prev_digest_len + to_sign_len + 2, tmp_tsig); + + int ret = compute_digest(wire, wire_len, digest_tmp, &digest_tmp_len, key); + free(wire); + if (ret != KNOT_EOK) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return ret; + } + + if (digest_tmp_len > *digest_len) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return KNOT_ESPACE; + } + + /* Set the MAC. */ + knot_tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + /* Set original id. */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set other data. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, NULL); + + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + knot_rrset_free(tmp_tsig, NULL); + *digest_len = 0; + return ret; + } + + size_t tsig_wire_size = ret; + + knot_rrset_free(tmp_tsig, NULL); + + *msg_len += tsig_wire_size; + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int check_digest(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed, int use_times) +{ + if (!wire || !tsig_key) { + return KNOT_EINVAL; + } + + /* No TSIG record means verification failure. */ + if (tsig_rr == NULL) { + return KNOT_TSIG_EBADKEY; + } + + /* Check that libknot knows the algorithm. */ + int ret = check_algorithm(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + /* Check that key is valid, ie. the same as given in args. */ + ret = check_key(tsig_rr, tsig_key); + if (ret != KNOT_EOK) { + return ret; + } + + uint8_t *wire_to_sign = malloc(size); + if (!wire_to_sign) { + return KNOT_ENOMEM; + } + + memcpy(wire_to_sign, wire, size); + + // restore message ID to which the signature had been created with + knot_wire_set_id(wire_to_sign, knot_tsig_rdata_orig_id(tsig_rr)); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + assert(tsig_rr->rrs.count > 0); + + if (use_times) { + /* Wire is not a single packet, TSIG RRs must be stripped already. */ + ret = create_sign_wire_next(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } else { + ret = create_sign_wire(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } + + assert(tsig_rr->rrs.count > 0); + free(wire_to_sign); + + if (ret != KNOT_EOK) { + return ret; + } + + /* Compare MAC from TSIG RR RDATA with just computed digest. */ + + /*!< \todo move to function. */ + const knot_dname_t *alg_name = knot_tsig_rdata_alg_name(tsig_rr); + dnssec_tsig_algorithm_t alg = dnssec_tsig_algorithm_from_dname(alg_name); + + /*! \todo [TSIG] TRUNCATION */ + uint16_t mac_length = knot_tsig_rdata_mac_length(tsig_rr); + const uint8_t *tsig_mac = knot_tsig_rdata_mac(tsig_rr); + + if (mac_length != dnssec_tsig_algorithm_size(alg)) { + return KNOT_TSIG_EBADSIG; + } + + if (const_time_memcmp(tsig_mac, digest_tmp, mac_length) != 0) { + return KNOT_TSIG_EBADSIG; + } + + /* Check TSIG validity period, must be after the signature check! */ + ret = check_time_signed(tsig_rr, prev_time_signed); + if (ret != KNOT_EOK) { + return ret; + } + + return KNOT_EOK; +} + +_public_ +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_tsig_key_t *tsig_key) +{ + return check_digest(tsig_rr, wire, size, NULL, 0, tsig_key, 0, 0); +} + +_public_ +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed) +{ + return check_digest(tsig_rr, wire, size, request_mac, request_mac_len, + tsig_key, prev_time_signed, 0); +} + +_public_ +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_tsig_key_t *tsig_key, + uint64_t prev_time_signed) +{ + return check_digest(tsig_rr, wire, size, prev_digest, + prev_digest_len, tsig_key, prev_time_signed, 1); +} + +_public_ +int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + uint16_t tsig_rcode, const knot_rrset_t *tsig_rr) +{ + /*! \todo Revise!! */ + + if (!msg || !msg_len || !tsig_rr) { + return KNOT_EINVAL; + } + + /*! \todo What key to use, when we do not sign? Does this even work? */ + knot_rrset_t *tmp_tsig = knot_rrset_new(tsig_rr->owner, KNOT_RRTYPE_TSIG, + KNOT_CLASS_ANY, 0, NULL); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + assert(tsig_rcode != KNOT_RCODE_BADTIME); + knot_tsig_create_rdata(tmp_tsig, knot_tsig_rdata_alg_name(tsig_rr), 0, tsig_rcode); + knot_tsig_rdata_set_time_signed(tmp_tsig, knot_tsig_rdata_time_signed(tsig_rr)); + + /* Comparing to BIND it was found out that the Fudge should always be + * set to the server's value. + */ + knot_tsig_rdata_set_fudge(tmp_tsig, KNOT_TSIG_FUDGE_DEFAULT); + + /* Set original ID */ + knot_tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set other len. */ + knot_tsig_rdata_set_other_data(tmp_tsig, 0, 0); + + /* Append TSIG RR. */ + int ret = knot_tsig_append(msg, msg_len, msg_max_len, tmp_tsig); + + /* key_name already referenced in RRSet, no need to free separately. */ + knot_rrset_free(tmp_tsig, NULL); + + return ret; +} + +_public_ +int knot_tsig_append(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const knot_rrset_t *tsig_rr) +{ + /* Write RRSet to wire */ + int ret = knot_rrset_to_wire(tsig_rr, msg + *msg_len, + msg_max_len - *msg_len, NULL); + if (ret < 0) { + return ret; + } + + *msg_len += ret; + + knot_wire_set_arcount(msg, knot_wire_get_arcount(msg) + 1); + + return KNOT_EOK; +} |