diff options
Diffstat (limited to '')
-rw-r--r-- | lib/cookies/alg_containers.c | 72 | ||||
-rw-r--r-- | lib/cookies/alg_containers.h | 49 | ||||
-rw-r--r-- | lib/cookies/alg_sha.c | 122 | ||||
-rw-r--r-- | lib/cookies/alg_sha.h | 30 | ||||
-rw-r--r-- | lib/cookies/control.h | 49 | ||||
-rw-r--r-- | lib/cookies/helper.c | 296 | ||||
-rw-r--r-- | lib/cookies/helper.h | 86 | ||||
-rw-r--r-- | lib/cookies/lru_cache.c | 70 | ||||
-rw-r--r-- | lib/cookies/lru_cache.h | 69 | ||||
-rw-r--r-- | lib/cookies/nonce.c | 34 | ||||
-rw-r--r-- | lib/cookies/nonce.h | 43 |
11 files changed, 920 insertions, 0 deletions
diff --git a/lib/cookies/alg_containers.c b/lib/cookies/alg_containers.c new file mode 100644 index 0000000..a80487d --- /dev/null +++ b/lib/cookies/alg_containers.c @@ -0,0 +1,72 @@ +/* Copyright (C) 2016-2017 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 <stdint.h> +#include <stdlib.h> + +#include <libknot/cookies/alg-fnv64.h> + +#include "lib/cookies/alg_containers.h" +#include "lib/cookies/alg_sha.h" + +const struct knot_cc_alg *kr_cc_alg_get(int id) +{ + /* + * Client algorithm identifiers are used to index this array of + * pointers. + */ + static const struct knot_cc_alg *const cc_algs[] = { + /* 0 */ &knot_cc_alg_fnv64, + /* 1 */ &knot_cc_alg_hmac_sha256_64 + }; + + if (id >= 0 && id < 2) { + return cc_algs[id]; + } + + return NULL; +} + +const knot_lookup_t kr_cc_alg_names[] = { + { 0, "FNV-64" }, + { 1, "HMAC-SHA256-64" }, + { -1, NULL } +}; + +const struct knot_sc_alg *kr_sc_alg_get(int id) +{ + /* + * Server algorithm identifiers are used to index this array of + * pointers. + */ + static const struct knot_sc_alg *const sc_algs[] = { + /* 0 */ &knot_sc_alg_fnv64, + /* 1 */ &knot_sc_alg_hmac_sha256_64 + }; + + if (id >= 0 && id < 2) { + return sc_algs[id]; + } + + return NULL; +} + +const knot_lookup_t kr_sc_alg_names[] = { + { 0, "FNV-64" }, + { 1, "HMAC-SHA256-64" }, + { -1, NULL } +}; diff --git a/lib/cookies/alg_containers.h b/lib/cookies/alg_containers.h new file mode 100644 index 0000000..1e2aef6 --- /dev/null +++ b/lib/cookies/alg_containers.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include <libknot/cookies/client.h> +#include <libknot/cookies/server.h> +#include <libknot/lookup.h> + +#include "lib/defines.h" + +/** + * @brief Returns pointer to client cookie algorithm. + * + * @param id algorithm identifier as defined by lookup table + * @return pointer to algorithm structure with given id or NULL on error + */ +KR_EXPORT +const struct knot_cc_alg *kr_cc_alg_get(int id); + +/** Binds client algorithm identifiers onto names. */ +KR_EXPORT +extern const knot_lookup_t kr_cc_alg_names[]; + +/** + * @brief Returns pointer to server cookie algorithm. + * + * @param id algorithm identifier as defined by lookup table + * @return pointer to algorithm structure with given id or NULL on error + */ +KR_EXPORT +const struct knot_sc_alg *kr_sc_alg_get(int id); + +/** Binds server algorithm identifiers onto names. */ +KR_EXPORT +extern const knot_lookup_t kr_sc_alg_names[]; diff --git a/lib/cookies/alg_sha.c b/lib/cookies/alg_sha.c new file mode 100644 index 0000000..e23c983 --- /dev/null +++ b/lib/cookies/alg_sha.c @@ -0,0 +1,122 @@ +/* Copyright (C) 2016-2017 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 <nettle/hmac.h> +#include <stdint.h> +#include <stdlib.h> + +#include <libknot/errcode.h> +#include <libknot/rrtype/opt-cookie.h> + +#include "lib/cookies/alg_sha.h" +#include "lib/utils.h" + +/** + * @brief Update hash value. + * + * @param ctx HMAC-SHA256 context to be updated. + * @param sa Socket address. + */ +static inline void update_hash(struct hmac_sha256_ctx *ctx, + const struct sockaddr *sa) +{ + assert(ctx && sa); + + int addr_len = kr_inaddr_len(sa); + const uint8_t *addr = (uint8_t *)kr_inaddr(sa); + + if (addr && addr_len > 0) { + hmac_sha256_update(ctx, addr_len, addr); + } +} + +/** + * @brief Compute client cookie using HMAC-SHA256-64. + * @note At least one of the arguments must be non-null. + * @param input input parameters + * @param cc_out buffer for computed client cookie + * @param cc_len buffer size + * @return Non-zero size of written data on success, 0 in case of a failure. + */ +static uint16_t cc_gen_hmac_sha256_64(const struct knot_cc_input *input, + uint8_t *cc_out, uint16_t cc_len) +{ + if (!knot_cc_input_is_valid(input) || + !cc_out || cc_len < KNOT_OPT_COOKIE_CLNT) { + return 0; + } + + struct hmac_sha256_ctx ctx; + hmac_sha256_set_key(&ctx, input->secret_len, input->secret_data); + + if (input->clnt_sockaddr) { + update_hash(&ctx, input->clnt_sockaddr); + } + + if (input->srvr_sockaddr) { + update_hash(&ctx, input->srvr_sockaddr); + } + + /* KNOT_OPT_COOKIE_CLNT <= SHA256_DIGEST_SIZE */ + + hmac_sha256_digest(&ctx, KNOT_OPT_COOKIE_CLNT, cc_out); + + return KNOT_OPT_COOKIE_CLNT; +} + +#define SRVR_HMAC_SHA256_64_HASH_SIZE 8 + +/** + * @brief Compute server cookie hash using HMAC-SHA256-64). + * @note Server cookie = nonce | time | HMAC-SHA256-64( server secret, client cookie | nonce| time | client IP ) + * @param input data to compute cookie from + * @param hash_out hash output buffer + * @param hash_len buffer size + * @return Non-zero size of written data on success, 0 in case of a failure. + */ +static uint16_t sc_gen_hmac_sha256_64(const struct knot_sc_input *input, + uint8_t *hash_out, uint16_t hash_len) +{ + if (!knot_sc_input_is_valid(input) || + !hash_out || hash_len < SRVR_HMAC_SHA256_64_HASH_SIZE) { + return 0; + } + + struct hmac_sha256_ctx ctx; + hmac_sha256_set_key(&ctx, input->srvr_data->secret_len, + input->srvr_data->secret_data); + + hmac_sha256_update(&ctx, input->cc_len, input->cc); + + if (input->nonce && input->nonce_len) { + hmac_sha256_update(&ctx, input->nonce_len, input->nonce); + } + + if (input->srvr_data->clnt_sockaddr) { + update_hash(&ctx, input->srvr_data->clnt_sockaddr); + } + + /* SRVR_HMAC_SHA256_64_HASH_SIZE < SHA256_DIGEST_SIZE */ + + hmac_sha256_digest(&ctx, SRVR_HMAC_SHA256_64_HASH_SIZE, hash_out); + + return SRVR_HMAC_SHA256_64_HASH_SIZE; +} + +const struct knot_cc_alg knot_cc_alg_hmac_sha256_64 = { KNOT_OPT_COOKIE_CLNT, cc_gen_hmac_sha256_64 }; + +const struct knot_sc_alg knot_sc_alg_hmac_sha256_64 = { SRVR_HMAC_SHA256_64_HASH_SIZE, sc_gen_hmac_sha256_64 }; diff --git a/lib/cookies/alg_sha.h b/lib/cookies/alg_sha.h new file mode 100644 index 0000000..5b0d0c9 --- /dev/null +++ b/lib/cookies/alg_sha.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include <libknot/cookies/client.h> +#include <libknot/cookies/server.h> + +#include "lib/defines.h" + +/* These structures are not meant to be part of public interface. */ + +/** HMAC-SHA256-64 client cookie algorithm. */ +extern const struct knot_cc_alg knot_cc_alg_hmac_sha256_64; + +/** HMAC-SHA256-64 server cookie algorithm. */ +extern const struct knot_sc_alg knot_sc_alg_hmac_sha256_64; diff --git a/lib/cookies/control.h b/lib/cookies/control.h new file mode 100644 index 0000000..1bf503f --- /dev/null +++ b/lib/cookies/control.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "lib/defines.h" + +/** Holds secret quantity. */ +struct kr_cookie_secret { + size_t size; /*!< Secret quantity size. */ + uint8_t data[]; /*!< Secret quantity data. */ +}; + +/** Holds settings that have direct influence on cookie values computation. */ +struct kr_cookie_comp { + struct kr_cookie_secret *secr; /*!< Secret data. */ + int alg_id; /*!< Cookie algorithm identifier. */ +}; + +/** Holds settings that control client/server cookie behaviour. */ +struct kr_cookie_settings { + bool enabled; /**< Enable/disables DNS cookies functionality. */ + + struct kr_cookie_comp current; /**< Current cookie settings. */ + struct kr_cookie_comp recent; /**< Recent cookie settings. */ +}; + +/** DNS cookies controlling structure. */ +struct kr_cookie_ctx { + struct kr_cookie_settings clnt; /**< Client settings. */ + struct kr_cookie_settings srvr; /**< Server settings. */ +}; diff --git a/lib/cookies/helper.c b/lib/cookies/helper.c new file mode 100644 index 0000000..e07b8cb --- /dev/null +++ b/lib/cookies/helper.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2016-2017 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 <libknot/rrtype/opt.h> +#include <libknot/rrtype/opt-cookie.h> + +#include "lib/cookies/helper.h" +#include "lib/defines.h" + +/** + * @brief Check whether there is a cached cookie that matches the current + * client cookie. + */ +static const uint8_t *peek_and_check_cc(kr_cookie_lru_t *cache, const void *sa, + const uint8_t *cc, uint16_t cc_len) +{ + assert(cache && sa && cc && cc_len); + + const uint8_t *cached_opt = kr_cookie_lru_get(cache, sa); + if (!cached_opt) { + return NULL; + } + + const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) cached_opt); + + if (cc_len == KNOT_OPT_COOKIE_CLNT && + 0 == memcmp(cc, cached_cc, cc_len)) { + return cached_opt; + } + + return NULL; +} + +/** + * @brief Put a client cookie into the RR Set. + */ +static int opt_rr_put_cookie(knot_rrset_t *opt_rr, uint8_t *data, + uint16_t data_len, knot_mm_t *mm) +{ + assert(opt_rr && data && data_len > 0); + + const uint8_t *cc = NULL, *sc = NULL; + uint16_t cc_len = 0, sc_len = 0; + + int ret = knot_edns_opt_cookie_parse(data, data_len, &cc, &cc_len, + &sc, &sc_len); + if (ret != KNOT_EOK) { + return kr_error(EINVAL); + } + assert(data_len == cc_len + sc_len); + + uint16_t cookies_size = data_len; + uint8_t *cookies_data = NULL; + + ret = knot_edns_reserve_unique_option(opt_rr, KNOT_EDNS_OPTION_COOKIE, + cookies_size, &cookies_data, mm); + if (ret != KNOT_EOK) { + return kr_error(EINVAL); + } + assert(cookies_data != NULL); + + cookies_size = knot_edns_opt_cookie_write(cc, cc_len, sc, sc_len, + cookies_data, cookies_size); + if (cookies_size == 0) { + return kr_error(EINVAL); + } + assert(cookies_size == data_len); + + return kr_ok(); +} + +/** + * @brief Puts entire EDNS option into the RR Set. + */ +static int opt_rr_put_cookie_opt(knot_rrset_t *opt_rr, uint8_t *option, knot_mm_t *mm) +{ + assert(opt_rr && option); + + uint16_t opt_code = knot_edns_opt_get_code(option); + if (opt_code != KNOT_EDNS_OPTION_COOKIE) { + return kr_error(EINVAL); + } + + uint16_t opt_len = knot_edns_opt_get_length(option); + uint8_t *opt_data = knot_edns_opt_get_data(option); + if (!opt_data || opt_len == 0) { + return kr_error(EINVAL); + } + + return opt_rr_put_cookie(opt_rr, opt_data, opt_len, mm); +} + +int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp, + kr_cookie_lru_t *cookie_cache, + const struct sockaddr *clnt_sa, + const struct sockaddr *srvr_sa, + struct kr_request *req) +{ + if (!clnt_comp || !req) { + return kr_error(EINVAL); + } + + if (!req->ctx->opt_rr) { + return kr_ok(); + } + + if (!clnt_comp->secr || (clnt_comp->alg_id < 0) || !cookie_cache) { + return kr_error(EINVAL); + } + + /* + * Generate client cookie from client address, server address and + * secret quantity. + */ + struct knot_cc_input input = { + .clnt_sockaddr = clnt_sa, + .srvr_sockaddr = srvr_sa, + .secret_data = clnt_comp->secr->data, + .secret_len = clnt_comp->secr->size + }; + uint8_t cc[KNOT_OPT_COOKIE_CLNT]; + uint16_t cc_len = KNOT_OPT_COOKIE_CLNT; + const struct knot_cc_alg *cc_alg = kr_cc_alg_get(clnt_comp->alg_id); + if (!cc_alg) { + return kr_error(EINVAL); + } + assert(cc_alg->gen_func); + cc_len = cc_alg->gen_func(&input, cc, cc_len); + if (cc_len != KNOT_OPT_COOKIE_CLNT) { + return kr_error(EINVAL); + } + + const uint8_t *cached_cookie = peek_and_check_cc(cookie_cache, + srvr_sa, cc, cc_len); + + /* Add cookie option. */ + int ret; + if (cached_cookie) { + ret = opt_rr_put_cookie_opt(req->ctx->opt_rr, + (uint8_t *)cached_cookie, + req->ctx->pool); + } else { + ret = opt_rr_put_cookie(req->ctx->opt_rr, cc, cc_len, + req->ctx->pool); + } + + return ret; +} + +int kr_answer_write_cookie(struct knot_sc_input *sc_input, + const struct kr_nonce_input *nonce, + const struct knot_sc_alg *alg, knot_pkt_t *pkt) +{ + if (!sc_input || !sc_input->cc || sc_input->cc_len == 0) { + return kr_error(EINVAL); + } + + if (!sc_input->srvr_data || !sc_input->srvr_data->clnt_sockaddr || + !sc_input->srvr_data->secret_data || + !sc_input->srvr_data->secret_len) { + return kr_error(EINVAL); + } + + if (!nonce) { + return kr_error(EINVAL); + } + + if (!alg || !alg->hash_size || !alg->hash_func) { + return kr_error(EINVAL); + } + + if (!pkt || !pkt->opt_rr) { + return kr_error(EINVAL); + } + + uint16_t nonce_len = KR_NONCE_LEN; + uint16_t hash_len = alg->hash_size; + + /* + * Space for cookie is reserved inside the EDNS OPT RR of + * the answer packet. + */ + uint8_t *cookie = NULL; + uint16_t cookie_len = knot_edns_opt_cookie_data_len(sc_input->cc_len, + nonce_len + hash_len); + if (cookie_len == 0) { + return kr_error(EINVAL); + } + + int ret = knot_edns_reserve_unique_option(pkt->opt_rr, + KNOT_EDNS_OPTION_COOKIE, + cookie_len, &cookie, + &pkt->mm); + if (ret != KNOT_EOK) { + return kr_error(ENOMEM); + } + assert(cookie != NULL); + + /* + * Function knot_edns_opt_cookie_data_len() returns the sum of its + * parameters or zero. Anyway, let's check again. + */ + if (cookie_len < (sc_input->cc_len + nonce_len + hash_len)) { + return kr_error(EINVAL); + } + + /* Copy client cookie data portion. */ + memcpy(cookie, sc_input->cc, sc_input->cc_len); + + if (nonce_len) { + /* Write nonce data portion. */ + kr_nonce_write_wire(cookie + sc_input->cc_len, nonce_len, + nonce); + /* Adjust input for written nonce value. */ + sc_input->nonce = cookie + sc_input->cc_len; + sc_input->nonce_len = nonce_len; + } + + hash_len = alg->hash_func(sc_input, + cookie + sc_input->cc_len + nonce_len, + hash_len); + /* Zero nonce values. */ + sc_input->nonce = NULL; + sc_input->nonce_len = 0; + + return (hash_len != 0) ? kr_ok() : kr_error(EINVAL); +} + +int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode) +{ + /* + * RFC6891 6.1.3 -- extended RCODE forms the upper 8 bits of whole + * 12-bit RCODE (together with the 4 bits of 'normal' RCODE). + * + * | 11 10 09 08 07 06 05 04 | 03 02 01 00 | + * | 12-bit whole RCODE | + * | 8-bit extended RCODE | 4-bit RCODE | + */ + + if (!pkt || !knot_pkt_has_edns(pkt)) { + return kr_error(EINVAL); + } + + uint8_t rcode = whole_rcode & 0x0f; + uint8_t ext_rcode = whole_rcode >> 4; + knot_wire_set_rcode(pkt->wire, rcode); + knot_edns_set_ext_rcode(pkt->opt_rr, ext_rcode); + + return kr_ok(); +} + +uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt) +{ + if (!pkt || knot_wire_get_qdcount(pkt->wire) > 0) { + return false; + } + + if (knot_wire_get_qr(pkt->wire) != 0 || !pkt->opt_rr) { + return false; + } + + return knot_edns_get_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE); +} + +int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies) +{ + if (!cookie_opt || !cookies) { + return kr_error(EINVAL); + } + + const uint8_t *cookie_data = knot_edns_opt_get_data(cookie_opt); + uint16_t cookie_len = knot_edns_opt_get_length(cookie_opt); + if (!cookie_data || cookie_len == 0) { + return kr_error(EINVAL); + } + + int ret = knot_edns_opt_cookie_parse(cookie_data, cookie_len, + &cookies->cc, &cookies->cc_len, + &cookies->sc, &cookies->sc_len); + + return (ret == KNOT_EOK) ? kr_ok() : kr_error(EINVAL); +} diff --git a/lib/cookies/helper.h b/lib/cookies/helper.h new file mode 100644 index 0000000..e5659ce --- /dev/null +++ b/lib/cookies/helper.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include <libknot/packet/pkt.h> + +#include "lib/cookies/alg_containers.h" +#include "lib/cookies/control.h" +#include "lib/cookies/lru_cache.h" +#include "lib/cookies/nonce.h" +#include "lib/defines.h" +#include "lib/resolve.h" + +/** + * @brief Updates DNS cookie in the request EDNS options. + * @note This function must be called before the request packet is finalised. + * @param clnt_comp client cookie control structure + * @param cookie_cache cookie cache + * @param clnt_sa client socket address + * @param srvr_sa server socket address + * @param req name resolution request + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp, + kr_cookie_lru_t *cookie_cache, + const struct sockaddr *clnt_sa, + const struct sockaddr *srvr_sa, + struct kr_request *req); + +/** + * @brief Inserts a cookie option into the OPT RR. It does not write any + * wire data. + * @note The content of @a sc_input is modified. Any pre-set nonce value is + * ignored. After retuning its nonce value will be null. + * @param sc_input data needed to compute server cookie, nonce is ignored + * @param nonce nonce value that is actually used + * @param alg hash algorithm + * @param pkt DNS response packet + */ +KR_EXPORT +int kr_answer_write_cookie(struct knot_sc_input *sc_input, + const struct kr_nonce_input *nonce, + const struct knot_sc_alg *alg, knot_pkt_t *pkt); + +/** + * @brief Set RCODE and extended RCODE. + * @param pkt DNS packet + * @param whole_rcode RCODE value + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode); + +/** + * @brief Check whether packet is a server cookie request according to + * RFC7873 5.4. + * @param pkt Packet to be examined. + * @return Pointer to entire cookie option if is a server cookie query, NULL on + * errors or if packet doesn't contain cookies or if QDCOUNT > 0. + */ +KR_EXPORT +uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt); + +/** + * @brief Parse cookies from cookie option. + * @param cookie_opt Cookie option. + * @param cookies Cookie structure to be set. + * @return kr_ok() on success, error if cookies are malformed. + */ +KR_EXPORT +int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies); diff --git a/lib/cookies/lru_cache.c b/lib/cookies/lru_cache.c new file mode 100644 index 0000000..8ea97ab --- /dev/null +++ b/lib/cookies/lru_cache.c @@ -0,0 +1,70 @@ +/* Copyright (C) 2016-2017 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 <libknot/rrtype/opt.h> +#include <string.h> + +#include "lib/cookies/lru_cache.h" +#include "lib/utils.h" + +const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache, + const struct sockaddr *sa) +{ + if (!cache || !sa) { + return NULL; + } + + int addr_len = kr_inaddr_len(sa); + const char *addr = kr_inaddr(sa); + if (!addr || addr_len <= 0) { + return NULL; + } + + struct cookie_opt_data *cached = lru_get_try(cache, addr, addr_len); + return cached ? cached->opt_data : NULL; +} + +int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa, + uint8_t *opt) +{ + if (!cache || !sa) { + return kr_error(EINVAL); + } + + if (!opt) { + return kr_ok(); + } + + int addr_len = kr_inaddr_len(sa); + const char *addr = kr_inaddr(sa); + if (!addr || addr_len <= 0) { + return kr_error(EINVAL); + } + + uint16_t opt_size = KNOT_EDNS_OPTION_HDRLEN + + knot_edns_opt_get_length(opt); + + if (opt_size > KR_COOKIE_OPT_MAX_LEN) { + return kr_error(EINVAL); + } + + struct cookie_opt_data *cached = lru_get_new(cache, addr, addr_len, NULL); + if (cached) { + memcpy(cached->opt_data, opt, opt_size); + } + + return kr_ok(); +} diff --git a/lib/cookies/lru_cache.h b/lib/cookies/lru_cache.h new file mode 100644 index 0000000..8ce0a01 --- /dev/null +++ b/lib/cookies/lru_cache.h @@ -0,0 +1,69 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include <netinet/in.h> +#include <stdint.h> + +#if defined(ENABLE_COOKIES) +#include <libknot/rrtype/opt.h> +#include <libknot/rrtype/opt-cookie.h> +#else +#define KNOT_OPT_COOKIE_CLNT 8 +#define KNOT_OPT_COOKIE_SRVR_MAX 32 +#endif /* defined(ENABLE_COOKIES) */ + +#include "lib/defines.h" +#include "lib/generic/lru.h" + +/** Maximal size of a cookie option. */ +#define KR_COOKIE_OPT_MAX_LEN (KNOT_EDNS_OPTION_HDRLEN + KNOT_OPT_COOKIE_CLNT + KNOT_OPT_COOKIE_SRVR_MAX) + +/** + * Cookie option entry. + */ +struct cookie_opt_data { + uint8_t opt_data[KR_COOKIE_OPT_MAX_LEN]; +}; + +/** + * DNS cookies tracking. + */ +typedef lru_t(struct cookie_opt_data) kr_cookie_lru_t; + +/** + * @brief Obtain LRU cache entry. + * + * @param cache cookie LRU cache + * @param sa socket address serving as key + * @return pointer to cached option or NULL if not found or error occurred + */ +KR_EXPORT +const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache, + const struct sockaddr *sa); + +/** + * @brief Stores cookie option into LRU cache. + * + * @param cache cookie LRU cache + * @param sa socket address serving as key + * @param opt cookie option to be stored + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa, + uint8_t *opt); diff --git a/lib/cookies/nonce.c b/lib/cookies/nonce.c new file mode 100644 index 0000000..4ad72c8 --- /dev/null +++ b/lib/cookies/nonce.c @@ -0,0 +1,34 @@ +/* Copyright (C) 2016-2017 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 "contrib/wire.h" +#include "lib/cookies/nonce.h" + +uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len, + const struct kr_nonce_input *input) +{ + if (!buf || buf_len < KR_NONCE_LEN || !input) { + return 0; + } + + wire_write_u32(buf, input->rand); + wire_write_u32(buf + sizeof(uint32_t), input->time); + buf_len = 2 * sizeof(uint32_t); + + return buf_len; +} diff --git a/lib/cookies/nonce.h b/lib/cookies/nonce.h new file mode 100644 index 0000000..99c54d3 --- /dev/null +++ b/lib/cookies/nonce.h @@ -0,0 +1,43 @@ +/* Copyright (C) 2016-2017 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/>. + */ + +#pragma once + +#include "lib/defines.h" + +/* RFC7873 Appendix B.2 mentions an algorithm using two values before the + * actual server cookie hash. */ + +/** Nonce value length. */ +#define KR_NONCE_LEN 8 + +/** Input data to generate nonce from. */ +struct kr_nonce_input { + uint32_t rand; /**< some random value */ + uint32_t time; /**< time stamp */ +}; + +/** + * @brief Writes server cookie nonce value into given buffer. + * + * @param buf buffer to write nonce data in wire format into + * @param buf_len buffer size + * @param input data to generate wire data from + * @return non-zero size of written data on success, 0 on failure + */ +KR_EXPORT +uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len, + const struct kr_nonce_input *input); |