diff options
Diffstat (limited to 'src/libdnssec')
66 files changed, 8839 insertions, 0 deletions
diff --git a/src/libdnssec/Makefile.inc b/src/libdnssec/Makefile.inc new file mode 100644 index 0000000..112f95b --- /dev/null +++ b/src/libdnssec/Makefile.inc @@ -0,0 +1,87 @@ +lib_LTLIBRARIES += libdnssec.la +pkgconfig_DATA += libdnssec.pc + +noinst_LTLIBRARIES += libshared.la + +libshared_la_CPPFLAGS = $(AM_CPPFLAGS) $(gnutls_CFLAGS) + +libshared_la_SOURCES = \ + libdnssec/shared/bignum.c \ + libdnssec/shared/bignum.h \ + libdnssec/shared/binary_wire.h \ + libdnssec/shared/dname.c \ + libdnssec/shared/dname.h \ + libdnssec/shared/fs.c \ + libdnssec/shared/fs.h \ + libdnssec/shared/hex.c \ + libdnssec/shared/hex.h \ + libdnssec/shared/keyid_gnutls.c \ + libdnssec/shared/keyid_gnutls.h \ + libdnssec/shared/pem.c \ + libdnssec/shared/pem.h \ + libdnssec/shared/shared.h + +libdnssec_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(gnutls_CFLAGS) +libdnssec_la_LDFLAGS = $(AM_LDFLAGS) $(libdnssec_VERSION_INFO) $(gnutls_LIBS) +if EXCLUDE_LIBS_LIBDNSSEC +libdnssec_la_LDFLAGS += $(LDFLAG_EXCLUDE_LIBS) +endif +libdnssec_la_LIBADD = libshared.la +if ENABLE_PKCS11 +libdnssec_la_LIBADD += $(pthread_LIBS) +endif + +include_libdnssecdir = $(includedir)/libdnssec +include_libdnssec_HEADERS = \ + libdnssec/binary.h \ + libdnssec/crypto.h \ + libdnssec/dnssec.h \ + libdnssec/error.h \ + libdnssec/key.h \ + libdnssec/keyid.h \ + libdnssec/keystore.h \ + libdnssec/keytag.h \ + libdnssec/list.h \ + libdnssec/nsec.h \ + libdnssec/random.h \ + libdnssec/sign.h \ + libdnssec/tsig.h \ + libdnssec/version.h + +libdnssec_la_SOURCES = \ + libdnssec/contrib/vpool.c \ + libdnssec/contrib/vpool.h \ + libdnssec/binary.c \ + libdnssec/crypto.c \ + libdnssec/error.c \ + libdnssec/key/algorithm.c \ + libdnssec/key/algorithm.h \ + libdnssec/key/convert.c \ + libdnssec/key/convert.h \ + libdnssec/key/dnskey.c \ + libdnssec/key/dnskey.h \ + libdnssec/key/ds.c \ + libdnssec/key/internal.h \ + libdnssec/key/key.c \ + libdnssec/key/keytag.c \ + libdnssec/key/privkey.c \ + libdnssec/key/privkey.h \ + libdnssec/key/simple.c \ + libdnssec/keyid.c \ + libdnssec/keystore/internal.h \ + libdnssec/keystore/keystore.c \ + libdnssec/keystore/pkcs11.c \ + libdnssec/keystore/pkcs8.c \ + libdnssec/keystore/pkcs8_dir.c \ + libdnssec/list/list.c \ + libdnssec/list/ucw_clists.h \ + libdnssec/nsec/bitmap.c \ + libdnssec/nsec/hash.c \ + libdnssec/nsec/nsec.c \ + libdnssec/p11/p11.c \ + libdnssec/p11/p11.h \ + libdnssec/random.c \ + libdnssec/sign/der.c \ + libdnssec/sign/der.h \ + libdnssec/sign/sign.c \ + libdnssec/tsig.c diff --git a/src/libdnssec/binary.c b/src/libdnssec/binary.c new file mode 100644 index 0000000..a57b421 --- /dev/null +++ b/src/libdnssec/binary.c @@ -0,0 +1,169 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" + +// Workaround for symbol redefinition if linked statically. +#define base64_encode base64encode +#define base64_decode base64decode +#define base64_encode_alloc base64encodealloc +#define base64_decode_alloc base64decodealloc +#include "contrib/base64.c" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_binary_alloc(dnssec_binary_t *data, size_t size) +{ + if (!data || size == 0) { + return DNSSEC_EINVAL; + } + + uint8_t *new_data = calloc(1, size); + if (!new_data) { + return DNSSEC_ENOMEM; + } + + data->data = new_data; + data->size = size; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_binary_free(dnssec_binary_t *binary) +{ + if (!binary) { + return; + } + + free(binary->data); + clear_struct(binary); +} + +_public_ +int dnssec_binary_dup(const dnssec_binary_t *from, dnssec_binary_t *to) +{ + if (!from || !to) { + return DNSSEC_EINVAL; + } + + uint8_t *copy = malloc(from->size); + if (copy == NULL) { + return DNSSEC_ENOMEM; + } + + memmove(copy, from->data, from->size); + + to->size = from->size; + to->data = copy; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_resize(dnssec_binary_t *data, size_t new_size) +{ + if (!data) { + return DNSSEC_EINVAL; + } + + uint8_t *new_data = realloc(data->data, new_size); + if (new_size > 0 && new_data == NULL) { + return DNSSEC_ENOMEM; + } + + data->data = new_data; + data->size = new_size; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two) +{ + if (one == two) { + return 0; + } + + uint8_t *data_one = one && one->size > 0 ? one->data : NULL; + uint8_t *data_two = two && two->size > 0 ? two->data : NULL; + + if (data_one == data_two) { + return 0; + } else if (data_one == NULL) { + return -1; + } else if (data_two == NULL) { + return +1; + } + + size_t min_size = one->size <= two->size ? one->size : two->size; + int cmp = memcmp(data_one, data_two, min_size); + if (cmp != 0) { + return cmp; + } else if (one->size == two->size) { + return 0; + } else if (one->size < two->size) { + return -1; + } else { + return +1; + } +} + +_public_ +int dnssec_binary_from_base64(const dnssec_binary_t *base64, + dnssec_binary_t *binary) +{ + if (!base64 || !binary) { + return DNSSEC_EINVAL; + } + + uint8_t *data; + int32_t size = base64_decode_alloc(base64->data, base64->size, &data); + if (size < 0) { + return DNSSEC_EINVAL; + } + + binary->data = data; + binary->size = size; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_to_base64(const dnssec_binary_t *binary, + dnssec_binary_t *base64) +{ + if (!binary || !base64) { + return DNSSEC_EINVAL; + } + + uint8_t *data; + int32_t size = base64_encode_alloc(binary->data, binary->size, &data); + if (size < 0) { + return DNSSEC_EINVAL; + } + + base64->data = data; + base64->size = size; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/binary.h b/src/libdnssec/binary.h new file mode 100644 index 0000000..fb0cf9b --- /dev/null +++ b/src/libdnssec/binary.h @@ -0,0 +1,133 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup binary + * + * \brief Universal binary data container. + * + * The module provides universal binary data container extensively used by + * a lot of functions provided by the library. + * + * Example of use: + * ~~~~~ {.c} + * + * dnssec_binary_t data = { 0 }; + * + * int result = dnssec_binary_alloc(&data, 32); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * memcpy(&data.data, buffer, data.size); + * + * // ... + * + * dnssec_binary_free(&data); + * + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdlib.h> + +/*! + * Universal structure to hold binary data. + */ +typedef struct dnssec_binary { + size_t size; /*!< Size of the binary data. */ + uint8_t *data; /*!< Stored data. */ +} dnssec_binary_t; + +/*! + * Allocate new binary data structure. + * + * \param[out] data Binary to be allocated. + * \param[in] size Requested size of the binary. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_alloc(dnssec_binary_t *data, size_t size); + +/*! + * Free content of binary structure. + * + * \param binary Binary structure to be freed. + */ +void dnssec_binary_free(dnssec_binary_t *binary); + +/*! + * Create a copy of a binary structure. + * + * \param[in] from Source of the copy. + * \param[out] to Target of the copy. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_dup(const dnssec_binary_t *from, dnssec_binary_t *to); + +/*! + * Resize binary structure to a new size. + * + * Internally uses realloc, which means that this function can be also used + * as a malloc or free. + * + * \param data Binary to be resized. + * \param new_size New size. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_resize(dnssec_binary_t *data, size_t new_size); + +/*! + * Compare two binary structures (equivalent of memcmp). + * + * \note NULL sorts before data. + * + * \param one First binary. + * \param two Second binary. + * + * \return 0 if one equals two, <0 if one sorts before two, >0 otherwise. + */ +int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two); + +/*! + * Allocate binary from Base64 encoded string. + * + * \param[in] base64 Base64 encoded data. + * \param[out] binary Decoded binary data. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_from_base64(const dnssec_binary_t *base64, + dnssec_binary_t *binary); + +/*! + * Create Base64 encoded string from binary data. + * + * \param[in] binary Binary data. + * \param[out] base64 Base64 encode data. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_to_base64(const dnssec_binary_t *binary, + dnssec_binary_t *base64); +/*! @} */ diff --git a/src/libdnssec/contrib/vpool.c b/src/libdnssec/contrib/vpool.c new file mode 100644 index 0000000..d2e1006 --- /dev/null +++ b/src/libdnssec/contrib/vpool.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/contrib/vpool.h" + +static void vpool_shift(struct vpool *pool); +static int vpool_new_size(struct vpool *pool, size_t datsize, + size_t *size); +static int vpool_resize(struct vpool *pool, size_t datsize); + +static void +vpool_shift(struct vpool *pool) +{ + if (pool->v_buf != pool->v_basebuf) { + memmove(pool->v_basebuf, pool->v_buf, pool->v_off); + pool->v_buf = pool->v_basebuf; + } +} + +static int +vpool_new_size(struct vpool *pool, size_t datsize, size_t *size) +{ + size_t need; + size_t rem; + + if (datsize <= pool->v_size - pool->v_off) { + *size = pool->v_size; + return (0); + } + + /* Check limit of new requested size */ + if (pool->v_limit - pool->v_off < datsize) { + return (EFBIG); + } + need = pool->v_off + datsize; + + /* Check limit of new size aligned to block size */ + rem = need % pool->v_blksize; + if (rem != 0) { + if (pool->v_limit - pool->v_off >= + datsize + (pool->v_blksize - rem)) { + need += pool->v_blksize - rem; + } else { + need = pool->v_limit; + } + } + + *size = need; + return (0); +} + +static int +vpool_resize(struct vpool *pool, size_t datsize) +{ + void *ret; + size_t size; + int error; + + error = vpool_new_size(pool, datsize, &size); + if (error != 0) { + return (error); + } + + if (size > pool->v_size) { + ret = malloc(size); + if (ret == NULL) { + return (ENOMEM); + } + + memcpy(ret, pool->v_buf, pool->v_off); + free(pool->v_basebuf); + pool->v_basebuf = pool->v_buf = ret; + pool->v_size = size; + } else if ((pool->v_size - pool->v_off) - + (pool->v_buf - pool->v_basebuf) < datsize) { + vpool_shift(pool); + } + + return (0); +} + +void +vpool_init(struct vpool *pool, size_t blksize, size_t limit) +{ + + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + + pool->v_blksize = (blksize == 0) ? 4096 : blksize; /* XXX */ + pool->v_limit = (limit == 0) ? SIZE_MAX : limit; + + pool->v_lasterr = 0; +} + +void +vpool_final(struct vpool *pool) +{ + free(pool->v_basebuf); +} + +void +vpool_reset(struct vpool *pool) +{ + free(pool->v_basebuf); + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + pool->v_lasterr = 0; +} + +void +vpool_wipe(struct vpool *pool) +{ + pool->v_off = 0; + pool->v_lasterr = 0; +} + +void * +vpool_insert(struct vpool *pool, size_t where, void *data, size_t datsize) +{ + void *ret; + int error; + + error = vpool_resize(pool, datsize); + if (error != 0) { + pool->v_lasterr = error; + return (NULL); + } + + /* + * If ``where'' is greater than or equal to offset then + * we are appending data to the end of the buffer. + */ + if (where > pool->v_off) { + where = pool->v_off; + } + + ret = (uint8_t *)pool->v_buf + where; + if (pool->v_off - where > 0) { + memmove(ret + datsize, ret, pool->v_off - where); + } + memcpy(ret, data, datsize); + pool->v_off += datsize; + pool->v_lasterr = 0; + + return (ret); +} + +void * +vpool_expand(struct vpool *pool, size_t where, size_t size) +{ + void *ret; + int error; + + error = vpool_resize(pool, size); + if (error != 0) { + pool->v_lasterr = error; + return (NULL); + } + + /* + * If ``where'' is greater than or equal to offset then + * we are appending data to the end of the buffer. + */ + if (where > pool->v_off) { + where = pool->v_off; + } + + ret = (uint8_t *)pool->v_buf + where; + if (pool->v_off - where > 0) { + memmove(ret + size, ret, pool->v_off - where); + } + pool->v_off += size; + pool->v_lasterr = 0; + + return (ret); +} + +int +vpool_truncate(struct vpool *pool, + size_t where, size_t size, enum vpool_trunc how) +{ + /* Check if caller wants to remove more data than we have */ + if (where >= pool->v_off || + size > pool->v_off || pool->v_off - size < where) { + pool->v_lasterr = ERANGE; + return (pool->v_lasterr); + } + + if (how == VPOOL_EXCLUDE) { + if (where == 0) { + /* + * Optimization. + * Don't move data, just adjust pointer. + */ + pool->v_buf = (uint8_t *)pool->v_buf + size; + } else { + memmove((uint8_t *)pool->v_buf + where, + (uint8_t *)pool->v_buf + where + size, + pool->v_off - size - where); + } + pool->v_off -= size; + } else { + pool->v_buf = (uint8_t *)pool->v_buf + where; + pool->v_off = size; + } + + pool->v_lasterr = 0; + return (0); +} + +void +vpool_export(struct vpool *pool, void **buf, size_t *size) +{ + vpool_shift(pool); + *buf = pool->v_buf; + *size = pool->v_off; + pool->v_basebuf = pool->v_buf = NULL; + pool->v_off = pool->v_size = 0; + pool->v_lasterr = 0; +} diff --git a/src/libdnssec/contrib/vpool.h b/src/libdnssec/contrib/vpool.h new file mode 100644 index 0000000..82e3d66 --- /dev/null +++ b/src/libdnssec/contrib/vpool.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * VPool: implementation of pool of data with a variable size. + */ +#ifndef _VPOOL_H_ +#define _VPOOL_H_ + +#include <stddef.h> +#include <limits.h> + +struct vpool { + void *v_basebuf; /* pointer returned by (re|m)alloc() */ + void *v_buf; /* actual data starts here */ + size_t v_off; + size_t v_size; + + size_t v_blksize; + size_t v_limit; + int v_lasterr; +}; + +enum vpool_trunc {VPOOL_EXCLUDE, VPOOL_INCLUDE}; +#define VPOOL_TAIL UINT_MAX + +void vpool_init(struct vpool *pool, size_t blksize, size_t limit); +void vpool_final(struct vpool *pool); + +void vpool_reset(struct vpool *pool); +void vpool_wipe(struct vpool *pool); + +void * vpool_insert(struct vpool *pool, + size_t where, void *data, size_t datsize); +void * vpool_expand(struct vpool *pool, size_t where, size_t size); + +int vpool_truncate(struct vpool *pool, + size_t where, size_t size, enum vpool_trunc how); + +#define vpool_is_empty(pool) ((pool)->v_off == 0) +#define vpool_get_buf(pool) ((pool)->v_buf) +#define vpool_get_length(pool) ((pool)->v_off) +#define vpool_get_error(pool) ((pool)->v_lasterr) + +void vpool_export(struct vpool *pool, void **buf, size_t *size); + +#endif /* !_VPOOL_H_ */ diff --git a/src/libdnssec/crypto.c b/src/libdnssec/crypto.c new file mode 100644 index 0000000..f54e20f --- /dev/null +++ b/src/libdnssec/crypto.c @@ -0,0 +1,42 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <gnutls/gnutls.h> +#include <gnutls/pkcs11.h> + +#include "libdnssec/crypto.h" +#include "libdnssec/p11/p11.h" +#include "libdnssec/shared/shared.h" + +_public_ +void dnssec_crypto_init(void) +{ + p11_init(); + gnutls_global_init(); +} + +_public_ +void dnssec_crypto_cleanup(void) +{ + gnutls_global_deinit(); + p11_cleanup(); +} + +_public_ +void dnssec_crypto_reinit(void) +{ + p11_reinit(); +} diff --git a/src/libdnssec/crypto.h b/src/libdnssec/crypto.h new file mode 100644 index 0000000..46541b4 --- /dev/null +++ b/src/libdnssec/crypto.h @@ -0,0 +1,74 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup crypto + * + * \brief Cryptographic backend initialization. + * + * For most cryptographic operations, the library requires global + * initialization. Also, if the application creates a subprocess, the + * library has to be reinitialized in the child process after \c fork(). + * + * ~~~~~ {.c} + * int main(void) + * { + * int exit_code = 0; + * + * dnssec_crypto_init(); + * + * pid_t child_pid = fork(); + * if (child_pid < 0) { + * perror("fork"); + * exit_code = 1; + * } else if (child_pid == 0) { + * dnssec_crypto_reinit(); + * exit_code = child(); + * } else { + * exit_code = parent(); + * } + * + * dnssec_crypto_cleanup(); + * return exit_code; + * } + * ~~~~~ + * + * @{ + */ + +#pragma once + +/*! + * Initialize cryptographic backend. + */ +void dnssec_crypto_init(void); + +/*! + * Reinitialize cryptographic backend. + * + * Must be called after fork() by the child. + */ +void dnssec_crypto_reinit(void); + +/*! + * Deinitialize cryptographic backend. + * + * Should be called when terminating the application. + */ +void dnssec_crypto_cleanup(void); + +/*! @} */ diff --git a/src/libdnssec/dnssec.h b/src/libdnssec/dnssec.h new file mode 100644 index 0000000..8383901 --- /dev/null +++ b/src/libdnssec/dnssec.h @@ -0,0 +1,35 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/** + * \file + * + * Convenient header to include all library modules. + */ + +#pragma once + +#include <libdnssec/binary.h> +#include <libdnssec/crypto.h> +#include <libdnssec/error.h> +#include <libdnssec/key.h> +#include <libdnssec/keyid.h> +#include <libdnssec/keystore.h> +#include <libdnssec/keytag.h> +#include <libdnssec/list.h> +#include <libdnssec/nsec.h> +#include <libdnssec/random.h> +#include <libdnssec/sign.h> +#include <libdnssec/tsig.h> diff --git a/src/libdnssec/error.c b/src/libdnssec/error.c new file mode 100644 index 0000000..97f7f8f --- /dev/null +++ b/src/libdnssec/error.c @@ -0,0 +1,84 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" + +typedef struct error_message_t { + int code; + const char *text; +} error_message_t; + +static const error_message_t ERROR_MESSAGES[] = { + { DNSSEC_EOK, "no error" }, + + { DNSSEC_ENOMEM, "not enough memory" }, + { DNSSEC_EINVAL, "invalid argument" }, + { DNSSEC_ENOENT, "no such file or directory" }, + + { DNSSEC_ERROR, "unspecified error" }, + { DNSSEC_NOT_IMPLEMENTED_ERROR, "not implemented" }, + { DNSSEC_MALFORMED_DATA, "malformed data" }, + { DNSSEC_NOT_FOUND, "not found" }, + + { DNSSEC_PKCS8_IMPORT_ERROR, "PKCS #8 import error" }, + { DNSSEC_KEY_EXPORT_ERROR, "key export error" }, + { DNSSEC_KEY_IMPORT_ERROR, "key import error" }, + { DNSSEC_KEY_GENERATE_ERROR, "key generation error" }, + + { DNSSEC_INVALID_PUBLIC_KEY, "invalid public key" }, + { DNSSEC_INVALID_PRIVATE_KEY, "invalid private key" }, + { DNSSEC_INVALID_KEY_ALGORITHM, "invalid key algorithm" }, + { DNSSEC_INVALID_KEY_SIZE, "invalid key size" }, + { DNSSEC_INVALID_KEY_ID, "invalid key ID" }, + { DNSSEC_INVALID_KEY_NAME, "invalid key name" }, + + { DNSSEC_NO_PUBLIC_KEY, "no public key" }, + { DNSSEC_NO_PRIVATE_KEY, "no private key" }, + { DNSSEC_KEY_ALREADY_PRESENT, "key already present" }, + + { DNSSEC_SIGN_INIT_ERROR, "signing initialization error" }, + { DNSSEC_SIGN_ERROR, "signing error" }, + { DNSSEC_INVALID_SIGNATURE, "invalid signature" }, + + { DNSSEC_INVALID_NSEC3_ALGORITHM, "invalid NSEC3 algorithm" }, + { DNSSEC_NSEC3_HASHING_ERROR, "NSEC3 hashing error" }, + + { DNSSEC_INVALID_DS_ALGORITHM, "invalid DS algorithm" }, + { DNSSEC_DS_HASHING_ERROR, "DS hashing error" }, + + { DNSSEC_KEYSTORE_INVALID_CONFIG, "invalid KASP keystore configuration" }, + + { DNSSEC_P11_FAILED_TO_LOAD_MODULE, "failed to load PKCS #11 module" }, + { DNSSEC_P11_TOO_MANY_MODULES, "too many PKCS #11 modules loaded" }, + { DNSSEC_P11_TOKEN_NOT_AVAILABLE, "PKCS #11 token not available" }, + + { 0 } +}; + +_public_ +const char *dnssec_strerror(int error) +{ + for (const error_message_t *m = ERROR_MESSAGES; m->text; m++) { + if (m->code == error) { + return m->text; + } + } + + return NULL; +} diff --git a/src/libdnssec/error.h b/src/libdnssec/error.h new file mode 100644 index 0000000..eaa6afd --- /dev/null +++ b/src/libdnssec/error.h @@ -0,0 +1,112 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup error + * + * \brief Error codes and error reporting. + * + * The module defines all error codes used in the library, and functions + * to convert the error codes to sensible error strings. + * + * ~~~~~ {.c} + * int result; + * + * result = dnssec_key_set_pubkey(NULL, NULL); + * assert(result == DNSSEC_EINVAL); + * + * fprintf(stderr, "Error: %s.\n", dnssec_strerror(result)); + * // Error: Invalid argument. + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <errno.h> + +/*! + * Library error codes. + */ +enum dnssec_error { + DNSSEC_EOK = 0, + + DNSSEC_ENOMEM = -ENOMEM, + DNSSEC_EINVAL = -EINVAL, + DNSSEC_ENOENT = -ENOENT, + + DNSSEC_ERROR_MIN = -1500, + + DNSSEC_ERROR = DNSSEC_ERROR_MIN, + DNSSEC_NOT_IMPLEMENTED_ERROR, + DNSSEC_MALFORMED_DATA, + DNSSEC_NOT_FOUND, + + DNSSEC_PKCS8_IMPORT_ERROR, + DNSSEC_KEY_EXPORT_ERROR, + DNSSEC_KEY_IMPORT_ERROR, + DNSSEC_KEY_GENERATE_ERROR, + + DNSSEC_INVALID_PUBLIC_KEY, + DNSSEC_INVALID_PRIVATE_KEY, + DNSSEC_INVALID_KEY_ALGORITHM, + DNSSEC_INVALID_KEY_SIZE, + DNSSEC_INVALID_KEY_ID, + DNSSEC_INVALID_KEY_NAME, + + DNSSEC_NO_PUBLIC_KEY, + DNSSEC_NO_PRIVATE_KEY, + DNSSEC_KEY_ALREADY_PRESENT, + + DNSSEC_SIGN_INIT_ERROR, + DNSSEC_SIGN_ERROR, + DNSSEC_INVALID_SIGNATURE, + + DNSSEC_INVALID_NSEC3_ALGORITHM, + DNSSEC_NSEC3_HASHING_ERROR, + + DNSSEC_INVALID_DS_ALGORITHM, + DNSSEC_DS_HASHING_ERROR, + + DNSSEC_KEYSTORE_INVALID_CONFIG, + + DNSSEC_P11_FAILED_TO_LOAD_MODULE, + DNSSEC_P11_TOO_MANY_MODULES, + DNSSEC_P11_TOKEN_NOT_AVAILABLE, + + DNSSEC_ERROR_MAX = -1001 +}; + +/*! + * Translate error code to error message. + * + * \param error Error code. + * + * \return Statically allocated error message string or NULL if unknown. + */ +const char *dnssec_strerror(int error); + +/*! + * Convert errno value to DNSSEC error code. + */ +static inline int dnssec_errno_to_error(int ecode) +{ + return -ecode; +} + +/*! @} */ diff --git a/src/libdnssec/key.h b/src/libdnssec/key.h new file mode 100644 index 0000000..27de778 --- /dev/null +++ b/src/libdnssec/key.h @@ -0,0 +1,318 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup key + * + * \brief DNSSEC public and private key manipulation. + * + * The dnssec_key_t is an abstraction for a DNSSEC key pair. If the key + * key is initialized with a public key data only, it can be used only for + * signature verification. In order to use the key for signing, private key + * has to be loaded. If only a private key is loaded into the structure, + * the public key is automatically constructed. + * + * The module interface provides various functions to retrieve information + * about the key. But the key is mostly used by other modules of the library. + * + * The following example shows construction of a key from DNSKEY RDATA: + * + * ~~~~~ {.c} + * + * dnssec_binary_t rdata = // ...; + * + * int result; + * dnssec_key_t *key = NULL; + * + * // create new DNSSEC key + * result = dnssec_key_new(&key); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * // load the DNSKEY RDATA + * result = dnssec_key_set_rdata(key, &rdata); + * if (result != DNSSEC_EOK) { + * dnssec_key_free(key); + * return result; + * } + * + * // print key tag + * printf("key %s\n", dnssec_key_get_keytag(key)); + * + * // make sure what we can do with the key + * assert(dnssec_key_can_verify(key) == true); + * assert(dnssec_key_can_sign(key) == false); + * + * // ... + * + * // cleanup + * dnssec_key_free(key); + * + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <libdnssec/binary.h> + +/*! + * DNSKEY algorithm numbers. + * + * \see https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml + */ +typedef enum dnssec_key_algorithm { + DNSSEC_KEY_ALGORITHM_INVALID = 0, + DNSSEC_KEY_ALGORITHM_RSA_SHA1 = 5, + DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3 = 7, + DNSSEC_KEY_ALGORITHM_RSA_SHA256 = 8, + DNSSEC_KEY_ALGORITHM_RSA_SHA512 = 10, + DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256 = 13, + DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384 = 14, + DNSSEC_KEY_ALGORITHM_ED25519 = 15, + DNSSEC_KEY_ALGORITHM_ED448 = 16, +} dnssec_key_algorithm_t; + +struct dnssec_key; + +/*! + * DNSSEC key. + */ +typedef struct dnssec_key dnssec_key_t; + +/*! + * Check whether a DNSKEY algorithm is supported. + * + * @note: less secure algorithms may go unsupported on purpose. + */ +bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algo); + +/*! + * Allocate new DNSSEC key. + * + * The protocol field of the key is set to 3 (DNSSEC). + * The flags field of the key is set to 256 (zone key, no SEP). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_key_new(dnssec_key_t **key); + +/*! + * Clear the DNSSEC key. + * + * Has the same effect as calling \ref dnssec_key_free and \ref dnssec_key_new. + */ +void dnssec_key_clear(dnssec_key_t *key); + +/*! + * Free the key allocated by \ref dnssec_key_new. + */ +void dnssec_key_free(dnssec_key_t *key); + +/*! + * Create a copy of a DNSSEC key. + * + * Only a public part of the key is copied. + */ +dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key); + +/*! + * Get the key tag of the DNSSEC key. + */ +uint16_t dnssec_key_get_keytag(const dnssec_key_t *key); + +/*! + * Get the domain name of the DNSSEC key. + */ +const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key); + +/*! + * Set the domain name of the DNSSEC key. + */ +int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname); + +/*! + * Get the flags field of the DNSSEC key. + */ +uint16_t dnssec_key_get_flags(const dnssec_key_t *key); + +/*! + * Set the flags field of the DNSSEC key. + */ +int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags); + +/*! + * Get the protocol field of the DNSSEC key. + */ +uint8_t dnssec_key_get_protocol(const dnssec_key_t *key); + +/*! + * Get the protocol field of the DNSSEC key. + */ +int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol); + +/*! + * Get the algorithm field of the DNSSEC key. + */ +uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key); + +/*! + * Set the algorithm field of the DNSSEC key. + * + * The function will fail if the algorithm is incompatible with the + * loaded key. This means, that the function can be used to set the initial + * algorithm and later, only the hashing algorithm can be changed. + */ +int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm); + +/*! + * Get the public key field of the DNSSEC key. + * + * The returned content must not be modified by the caller. A reference + * to internally allocated structure is returned. + */ +int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey); + +/*! + * Set the public key field of the DNSSEC key. + * + * A valid algorithm has to be set prior to calling this function. + * + * The function will fail if the key is already loaded in the structure. + */ +int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey); + +/*! + * Get the bit size of the cryptographic key used with the DNSSEC key. + */ +unsigned dnssec_key_get_size(const dnssec_key_t *key); + +/*! + * \brief Compute key ID from public key. + * + * \param key Key structure holding the public key. + * \param id Output: key ID in hex. + * + * \return DNSSEC_E* + */ +int dnssec_key_get_keyid(const dnssec_key_t *key, char **id); + +/*! + * Get the RDATA of the DNSSEC key. + * + * The returned content must not be modified by the caller. A reference + * to internally allocated structure is returned. + */ +int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata); + +/*! + * Set the RDATA of the DNSSEC key. + * + * Calling this function has the same effect as setting the individual + * fields of the key step-by-step. The same limitations apply. + */ +int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata); + +/*! + * Load PKCS #8 private key in the unencrypted PEM format. + * + * At least an algorithm must be set prior to calling this function. + * + * The function will create public key, unless it was already set (using + * \ref dnssec_key_set_pubkey or \ref dnssec_key_set_rdata). If the public key + * was set, the function will prevent loading of non-matching private key. + */ +int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem); + +/*! + * Check if the key can be used for signing. + */ +bool dnssec_key_can_sign(const dnssec_key_t *key); + +/*! + * Check if the key can be used for verification. + */ +bool dnssec_key_can_verify(const dnssec_key_t *key); + +/*! + * Get private key size range for a DNSSEC algorithm. + * + * \param[in] algorithm DNSKEY algorithm. + * \param[out] min Minimal size of the private key (can be NULL). + * \param[out] max Maximal size of the private key (can be NULL). + * + * \return DNSSEC_EOK for valid parameters. + */ +int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm, + unsigned *min, unsigned *max); + +/*! + * Check if the private key size matches DNSKEY constraints. + * + * \param algorithm DNSKEY algorithm. + * \param bits Private key size. + * + * \return DNSKEY algorithm matches the key size constraints. + */ +bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm, + unsigned bits); + +/*! + * Get default key size for given algorithm. + * + * The default size is balance between security and response lengths with + * respect to use in DNS. + */ +int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm); + +/*! + * DS algorithm numbers. + * + * \see https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml + */ +typedef enum dnssec_key_digest { + DNSSEC_KEY_DIGEST_INVALID = 0, + DNSSEC_KEY_DIGEST_SHA1 = 1, + DNSSEC_KEY_DIGEST_SHA256 = 2, + DNSSEC_KEY_DIGEST_SHA384 = 4, +} dnssec_key_digest_t; + +/*! + * Check whether a DS algorithm is supported. + * + * @note: less secure algorithms may go unsupported on purpose. + */ +bool dnssec_algorithm_digest_support(dnssec_key_digest_t algo); + +/*! + * Create DS (Delgation Signer) RDATA from DNSSEC key. + * + * \param[in] key DNSSEC key. + * \param[in] digest Digest algorithm to be used. + * \param[out] rdata Allocated DS RDATA. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_key_create_ds(const dnssec_key_t *key, dnssec_key_digest_t digest, + dnssec_binary_t *rdata); + +/** @} */ diff --git a/src/libdnssec/key/algorithm.c b/src/libdnssec/key/algorithm.c new file mode 100644 index 0000000..84f5386 --- /dev/null +++ b/src/libdnssec/key/algorithm.c @@ -0,0 +1,163 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <gnutls/gnutls.h> + +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/shared/shared.h" + +/* -- internal ------------------------------------------------------------- */ + +struct limits { + unsigned min; + unsigned max; + unsigned def; + bool (*validate)(unsigned bits); +}; + +static const struct limits *get_limits(dnssec_key_algorithm_t algorithm) +{ + static const struct limits RSA = { + .min = 1024, + .max = 4096, + .def = 2048, + }; + + static const struct limits EC256 = { + .min = 256, + .max = 256, + .def = 256, + }; + + static const struct limits EC384 = { + .min = 384, + .max = 384, + .def = 384, + }; + + static const struct limits ED25519 = { + .min = 256, + .max = 256, + .def = 256, + }; + + static const struct limits ED448 = { + .min = 456, + .max = 456, + .def = 456, + }; + + switch (algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return &RSA; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return &EC256; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return &EC384; + case DNSSEC_KEY_ALGORITHM_ED25519: + return &ED25519; + case DNSSEC_KEY_ALGORITHM_ED448: + return &ED448; + default: + return NULL; + } +} + +/* -- internal API --------------------------------------------------------- */ + +gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec) +{ + switch (dnssec) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return GNUTLS_PK_RSA; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_PK_EC; + case DNSSEC_KEY_ALGORITHM_ED25519: +#ifdef HAVE_ED25519 + return GNUTLS_PK_EDDSA_ED25519; +#endif + case DNSSEC_KEY_ALGORITHM_ED448: + default: + return GNUTLS_PK_UNKNOWN; + } +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algo) +{ + return algorithm_to_gnutls(algo) != GNUTLS_PK_UNKNOWN; +} + +_public_ +int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm, + unsigned *min_ptr, unsigned *max_ptr) +{ + if (!min_ptr && !max_ptr) { + return DNSSEC_EINVAL; + } + + const struct limits *limits = get_limits(algorithm); + if (!limits) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (min_ptr) { + *min_ptr = limits->min; + } + if (max_ptr) { + *max_ptr = limits->max; + } + + return DNSSEC_EOK; +} + +_public_ +bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm, + unsigned bits) +{ + const struct limits *limits = get_limits(algorithm); + if (!limits) { + return false; + } + + if (bits < limits->min || bits > limits->max) { + return false; + } + + if (limits->validate && !limits->validate(bits)) { + return false; + } + + return true; +} + +_public_ +int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm) +{ + const struct limits *limits = get_limits(algorithm); + return limits ? limits->def : 0; +} diff --git a/src/libdnssec/key/algorithm.h b/src/libdnssec/key/algorithm.h new file mode 100644 index 0000000..586682c --- /dev/null +++ b/src/libdnssec/key/algorithm.h @@ -0,0 +1,30 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/gnutls.h> + +#include "libdnssec/key.h" + +/*! + * Convert DNSKEY algorithm identifier to GnuTLS identifier. + * + * \param dnssec DNSSEC DNSKEY algorithm identifier. + * + * \return GnuTLS private key algorithm identifier, GNUTLS_PK_UNKNOWN on error. + */ +gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec); diff --git a/src/libdnssec/key/convert.c b/src/libdnssec/key/convert.c new file mode 100644 index 0000000..b8ebb7d --- /dev/null +++ b/src/libdnssec/key/convert.c @@ -0,0 +1,375 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/shared/bignum.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- wrappers for GnuTLS types -------------------------------------------- */ + +static size_t bignum_size_u_datum(const gnutls_datum_t *_bignum) +{ + const dnssec_binary_t bignum = binary_from_datum(_bignum); + return bignum_size_u(&bignum); +} + +static void wire_write_bignum_datum(wire_ctx_t *ctx, size_t width, + const gnutls_datum_t *_bignum) +{ + const dnssec_binary_t bignum = binary_from_datum(_bignum); + bignum_write(ctx, width, &bignum); +} + +static gnutls_datum_t wire_take_datum(wire_ctx_t *ctx, size_t count) +{ + gnutls_datum_t result = { .data = ctx->position, .size = count }; + ctx->position += count; + + return result; +} + +/* -- DNSSEC to crypto ------------------------------------------------------*/ + +/*! + * Convert RSA public key to DNSSEC format. + */ +static int rsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t modulus = { 0 }; + _cleanup_datum_ gnutls_datum_t exponent = { 0 }; + + int result = gnutls_pubkey_get_pk_rsa_raw(key, &modulus, &exponent); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t exponent_size = bignum_size_u_datum(&exponent); + if (exponent_size > UINT8_MAX) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t modulus_size = bignum_size_u_datum(&modulus); + + result = dnssec_binary_alloc(rdata, 1 + exponent_size + modulus_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_write_u8(&wire, exponent_size); + wire_write_bignum_datum(&wire, exponent_size, &exponent); + wire_write_bignum_datum(&wire, modulus_size, &modulus); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Get point size for an ECDSA curve. + */ +static size_t ecdsa_curve_point_size(gnutls_ecc_curve_t curve) +{ + switch (curve) { + case GNUTLS_ECC_CURVE_SECP256R1: return 32; + case GNUTLS_ECC_CURVE_SECP384R1: return 48; + default: return 0; + } +} + +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static size_t eddsa_curve_point_size(gnutls_ecc_curve_t curve) +{ + switch (curve) { +#ifdef HAVE_ED25519 + case GNUTLS_ECC_CURVE_ED25519: return 32; +#endif +#ifdef HAVE_ED448 + case GNUTLS_ECC_CURVE_ED448: return 57; +#endif + default: return 0; + } +} +#endif + +/*! + * Convert ECDSA public key to DNSSEC format. + */ +static int ecdsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t point_x = { 0 }; + _cleanup_datum_ gnutls_datum_t point_y = { 0 }; + gnutls_ecc_curve_t curve = { 0 }; + + int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, &point_y); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t point_size = ecdsa_curve_point_size(curve); + if (point_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + result = dnssec_binary_alloc(rdata, 2 * point_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_write_bignum_datum(&wire, point_size, &point_x); + wire_write_bignum_datum(&wire, point_size, &point_y); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Convert EDDSA public key to DNSSEC format. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static int eddsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t point_x = { 0 }; + gnutls_ecc_curve_t curve = { 0 }; + + int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, NULL); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t point_size = eddsa_curve_point_size(curve); + if (point_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + result = dnssec_binary_alloc(rdata, point_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_write_bignum_datum(&wire, point_size, &point_x); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} +#endif + +/* -- crypto to DNSSEC ------------------------------------------------------*/ + +/*! + * Convert RSA key in DNSSEC format to crypto key. + */ +static int rsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + if (rdata->size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + wire_ctx_t ctx = binary_init(rdata); + + // parse public exponent + + uint8_t exponent_size = wire_ctx_read_u8(&ctx); + if (exponent_size == 0 || wire_ctx_available(&ctx) < exponent_size) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_datum_t exponent = wire_take_datum(&ctx, exponent_size); + + // parse modulus + + size_t modulus_size = wire_ctx_available(&ctx); + if (modulus_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_datum_t modulus = wire_take_datum(&ctx, modulus_size); + + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_rsa_raw(key, &modulus, &exponent); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} + +/** + * Get ECDSA curve based on DNSKEY RDATA size. + */ +static gnutls_ecc_curve_t ecdsa_curve_from_rdata_size(size_t rdata_size) +{ + switch (rdata_size) { + case 64: return GNUTLS_ECC_CURVE_SECP256R1; + case 96: return GNUTLS_ECC_CURVE_SECP384R1; + default: return GNUTLS_ECC_CURVE_INVALID; + } +} + +/** + * Get EDDSA curve based on DNSKEY RDATA size. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static gnutls_ecc_curve_t eddsa_curve_from_rdata_size(size_t rdata_size) +{ + switch (rdata_size) { +#ifdef HAVE_ED25519 + case 32: return GNUTLS_ECC_CURVE_ED25519; +#endif +#ifdef HAVE_ED448 + case 57: return GNUTLS_ECC_CURVE_ED448; +#endif + default: return GNUTLS_ECC_CURVE_INVALID; + } +} +#endif + +/*! + * Convert ECDSA key in DNSSEC format to crypto key. + */ +static int ecdsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_ecc_curve_t curve = ecdsa_curve_from_rdata_size(rdata->size); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + // parse points + + wire_ctx_t ctx = binary_init(rdata); + + size_t point_size = wire_ctx_available(&ctx) / 2; + gnutls_datum_t point_x = wire_take_datum(&ctx, point_size); + gnutls_datum_t point_y = wire_take_datum(&ctx, point_size); + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, &point_y); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} + +/*! + * Convert EDDSA key in DNSSEC format to crypto key. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static int eddsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_ecc_curve_t curve = eddsa_curve_from_rdata_size(rdata->size); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + wire_ctx_t ctx = binary_init(rdata); + + size_t point_size = wire_ctx_available(&ctx); + gnutls_datum_t point_x = wire_take_datum(&ctx, point_size); + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, NULL); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} +#endif + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Encode public key to the format used in DNSKEY RDATA. + */ +int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + int algorithm = gnutls_pubkey_get_pk_algorithm(key, NULL); + if (algorithm < 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + switch ((gnutls_pk_algorithm_t)algorithm) { + case GNUTLS_PK_RSA: return rsa_pubkey_to_rdata(key, rdata); + case GNUTLS_PK_EC: return ecdsa_pubkey_to_rdata(key, rdata); +#ifdef HAVE_ED25519 + case GNUTLS_PK_EDDSA_ED25519: return eddsa_pubkey_to_rdata(key, rdata); +#endif +#ifdef HAVE_ED448 + case GNUTLS_PK_EDDSA_ED448: return eddsa_pubkey_to_rdata(key, rdata); +#endif + default: return DNSSEC_INVALID_KEY_ALGORITHM; + } +} + +/*! + * Create public key from the format encoded in DNSKEY RDATA. + */ +int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata, + gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_pk_algorithm_t gnutls_alg = algorithm_to_gnutls(algorithm); + + switch(gnutls_alg) { + case GNUTLS_PK_RSA: return rsa_rdata_to_pubkey(rdata, key); + case GNUTLS_PK_EC: return ecdsa_rdata_to_pubkey(rdata, key); +#ifdef HAVE_ED25519 + case GNUTLS_PK_EDDSA_ED25519: return eddsa_rdata_to_pubkey(rdata, key); +#endif +#ifdef HAVE_ED448 + case GNUTLS_PK_EDDSA_ED448: return eddsa_rdata_to_pubkey(rdata, key); +#endif + default: return DNSSEC_INVALID_KEY_ALGORITHM; + } +} diff --git a/src/libdnssec/key/convert.h b/src/libdnssec/key/convert.h new file mode 100644 index 0000000..bd2196e --- /dev/null +++ b/src/libdnssec/key/convert.h @@ -0,0 +1,44 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/key.h" + +/*! + * Encode public key into the format used in DNSKEY RDATA. + * + * \param[in] key Public key to be encoded. + * \param[out] rdata Encoded key (allocated). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata); + +/*! + * Create public key from the format encoded in DNSKEY RDATA. + * + * \param[in] algorithm DNSSEC algorithm identification. + * \param[in] rdata Public key in DNSKEY RDATA format. + * \param[out] key GnuTLS public key (initialized). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata, + gnutls_pubkey_t key); diff --git a/src/libdnssec/key/dnskey.c b/src/libdnssec/key/dnskey.c new file mode 100644 index 0000000..dc89bcd --- /dev/null +++ b/src/libdnssec/key/dnskey.c @@ -0,0 +1,82 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Update 'Public key' field of DNSKEY RDATA. + */ +int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata, const dnssec_binary_t *pubkey) +{ + assert(rdata); + assert(pubkey); + + size_t new_size = DNSKEY_RDATA_OFFSET_PUBKEY + pubkey->size; + int result = dnssec_binary_resize(rdata, new_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_write(&wire, pubkey); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Create a GnuTLS public key from DNSKEY RDATA. + * + * \param rdata DNSKEY RDATA. + * \param key_ptr Resulting public key. + */ +int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata, gnutls_pubkey_t *key_ptr) +{ + assert(rdata); + assert(key_ptr); + + uint8_t algorithm = 0; + dnssec_binary_t rdata_pubkey = { 0 }; + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + algorithm = wire_ctx_read_u8(&wire); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_available(&wire, &rdata_pubkey); + + gnutls_pubkey_t key = NULL; + int result = gnutls_pubkey_init(&key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + result = convert_dnskey_to_pubkey(algorithm, &rdata_pubkey, key); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(key); + return result; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/dnskey.h b/src/libdnssec/key/dnskey.h new file mode 100644 index 0000000..de681c8 --- /dev/null +++ b/src/libdnssec/key/dnskey.h @@ -0,0 +1,46 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +/*! + * DNSKEY RDATA fields offsets. + * + * \see RFC 4034 (section 2.1) + */ +enum dnskey_rdata_offsets { + DNSKEY_RDATA_OFFSET_FLAGS = 0, + DNSKEY_RDATA_OFFSET_PROTOCOL = 2, + DNSKEY_RDATA_OFFSET_ALGORITHM = 3, + DNSKEY_RDATA_OFFSET_PUBKEY = 4, +}; + +/*! + * Update 'Public key' field of DNSKEY RDATA. + */ +int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata, + const dnssec_binary_t *pubkey); + +/*! + * Create a GnuTLS public key from DNSKEY RDATA. + */ +int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata, + gnutls_pubkey_t *key_ptr); diff --git a/src/libdnssec/key/ds.c b/src/libdnssec/key/ds.c new file mode 100644 index 0000000..dcb4afe --- /dev/null +++ b/src/libdnssec/key/ds.c @@ -0,0 +1,115 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/dname.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +/*! + * Convert DNSSEC DS digest algorithm to GnuTLS digest algorithm. + */ +static gnutls_digest_algorithm_t lookup_algorithm(dnssec_key_digest_t algorithm) +{ + switch (algorithm) { + case DNSSEC_KEY_DIGEST_SHA1: return GNUTLS_DIG_SHA1; + case DNSSEC_KEY_DIGEST_SHA256: return GNUTLS_DIG_SHA256; + case DNSSEC_KEY_DIGEST_SHA384: return GNUTLS_DIG_SHA384; + default: + return GNUTLS_DIG_UNKNOWN; + }; +} + +_public_ +bool dnssec_algorithm_digest_support(dnssec_key_digest_t algo) +{ + return lookup_algorithm(algo) != GNUTLS_DIG_UNKNOWN; +} + +static void wire_write_digest(wire_ctx_t *wire, + gnutls_hash_hd_t digest, int digest_size) +{ + assert(wire_ctx_available(wire) >= digest_size); + gnutls_hash_output(digest, wire->position); + wire->position += digest_size; +} + +_public_ +int dnssec_key_create_ds(const dnssec_key_t *key, + dnssec_key_digest_t ds_algorithm, + dnssec_binary_t *rdata_ptr) +{ + if (!key || !rdata_ptr) { + return DNSSEC_EINVAL; + } + + if (!key->dname) { + return DNSSEC_INVALID_KEY_NAME; + } + + if (!key->public_key){ + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_digest_algorithm_t algorithm = lookup_algorithm(ds_algorithm); + if (algorithm == GNUTLS_DIG_UNKNOWN) { + return DNSSEC_INVALID_DS_ALGORITHM; + } + + // compute DS hash + + _cleanup_hash_ gnutls_hash_hd_t digest = NULL; + int r = gnutls_hash_init(&digest, algorithm); + if (r < 0) { + return DNSSEC_DS_HASHING_ERROR; + } + + if (gnutls_hash(digest, key->dname, dname_length(key->dname)) != 0 || + gnutls_hash(digest, key->rdata.data, key->rdata.size) != 0 + ) { + return DNSSEC_DS_HASHING_ERROR; + } + + // build DS RDATA + + int digest_size = gnutls_hash_get_len(algorithm); + if (digest_size == 0) { + return DNSSEC_DS_HASHING_ERROR; + } + + dnssec_binary_t rdata = { 0 }; + r = dnssec_binary_alloc(&rdata, 4 + digest_size); + if (r != DNSSEC_EOK) { + return r; + } + + wire_ctx_t wire = binary_init(&rdata); + wire_ctx_write_u16(&wire, dnssec_key_get_keytag(key)); + wire_ctx_write_u8(&wire, dnssec_key_get_algorithm(key)); + wire_ctx_write_u8(&wire, ds_algorithm); + wire_write_digest(&wire, digest, digest_size); + assert(wire_ctx_offset(&wire) == wire.size); + + *rdata_ptr = rdata; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/internal.h b/src/libdnssec/key/internal.h new file mode 100644 index 0000000..10c9c8e --- /dev/null +++ b/src/libdnssec/key/internal.h @@ -0,0 +1,35 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/abstract.h> +#include <stdint.h> + +#include "libdnssec/key.h" +#include "libdnssec/shared/dname.h" + +/*! + * DNSSEC key. + */ +struct dnssec_key { + uint8_t *dname; + dnssec_binary_t rdata; + + gnutls_pubkey_t public_key; + gnutls_privkey_t private_key; + unsigned bits; +}; diff --git a/src/libdnssec/key/key.c b/src/libdnssec/key/key.c new file mode 100644 index 0000000..6f24f87 --- /dev/null +++ b/src/libdnssec/key/key.c @@ -0,0 +1,440 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keytag.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" +#include "contrib/wire_ctx.h" + +/*! + * Minimal size of DNSKEY RDATA. + */ +#define DNSKEY_RDATA_MIN_SIZE DNSKEY_RDATA_OFFSET_PUBKEY + +/*! + * RDATA template for newly allocated keys. + */ +static const dnssec_binary_t DNSKEY_RDATA_TEMPLATE = { + .size = 4, + .data = (uint8_t []) { 0x01, 0x00, 0x03, 0x00 } +}; + +/* -- key allocation ------------------------------------------------------- */ + +_public_ +int dnssec_key_new(dnssec_key_t **key_ptr) +{ + if (!key_ptr) { + return DNSSEC_EINVAL; + } + + dnssec_key_t *key = calloc(1, sizeof(*key)); + if (!key) { + return DNSSEC_ENOMEM; + } + + int r = dnssec_binary_dup(&DNSKEY_RDATA_TEMPLATE, &key->rdata); + if (r != DNSSEC_EOK) { + free(key); + return DNSSEC_ENOMEM; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} + +/*! + * Clear allocated fields inside the key structure, except RDATA. + */ +static void key_free_internals(dnssec_key_t *key) +{ + assert(key); + + free(key->dname); + key->dname = NULL; + + gnutls_privkey_deinit(key->private_key); + key->private_key = NULL; + + gnutls_pubkey_deinit(key->public_key); + key->public_key = NULL; +} + +_public_ +void dnssec_key_clear(dnssec_key_t *key) +{ + if (!key) { + return; + } + + // reuse RDATA + dnssec_binary_t rdata = key->rdata; + + // clear the structure + key_free_internals(key); + clear_struct(key); + + // restore template RDATA (downsize, no need to realloc) + assert(rdata.size >= DNSKEY_RDATA_MIN_SIZE); + rdata.size = DNSKEY_RDATA_MIN_SIZE; + memmove(rdata.data, DNSKEY_RDATA_TEMPLATE.data, rdata.size); + + key->rdata = rdata; +} + +_public_ +void dnssec_key_free(dnssec_key_t *key) +{ + if (!key) { + return; + } + + key_free_internals(key); + dnssec_binary_free(&key->rdata); + + free(key); +} + +_public_ +dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key) +{ + if (!key) { + return NULL; + } + + dnssec_key_t *dup = NULL; + + if (dnssec_key_new(&dup) != DNSSEC_EOK || + dnssec_key_set_dname(dup, key->dname) != DNSSEC_EOK || + dnssec_key_set_rdata(dup, &key->rdata) != DNSSEC_EOK + ) { + dnssec_key_free(dup); + return NULL; + } + + return dup; +} + +/* -- freely modifiable attributes ----------------------------------------- */ + +_public_ +const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key) +{ + if (!key) { + return NULL; + } + + return key->dname; +} + +_public_ +int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + uint8_t *copy = NULL; + if (dname) { + copy = dname_copy(dname); + if (!copy) { + return DNSSEC_ENOMEM; + } + + dname_normalize(copy); + } + + free(key->dname); + key->dname = copy; + + return DNSSEC_EOK; +} + +_public_ +uint16_t dnssec_key_get_flags(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS); + return wire_ctx_read_u16(&wire); +} + +_public_ +int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS); + wire_ctx_write_u16(&wire, flags); + + return DNSSEC_EOK; +} + +_public_ +uint8_t dnssec_key_get_protocol(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL); + return wire_ctx_read_u8(&wire); +} + +_public_ +int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL); + wire_ctx_write_u8(&wire, protocol); + + return DNSSEC_EOK; +} + +/* -- restricted attributes ------------------------------------------------ */ + +_public_ +uint16_t dnssec_key_get_keytag(const dnssec_key_t *key) +{ + uint16_t keytag = 0; + if (dnssec_key_can_verify(key)) { + dnssec_keytag(&key->rdata, &keytag); + } + + return keytag; +} + +/*! + * Check if current public key algorithm matches with the new algorithm. + */ +static bool can_change_algorithm(dnssec_key_t *key, uint8_t algorithm) +{ + assert(key); + + if (!key->public_key) { + return true; + } + + gnutls_pk_algorithm_t update = algorithm_to_gnutls(algorithm); + if (update == GNUTLS_PK_UNKNOWN) { + return false; + } + + int current = gnutls_pubkey_get_pk_algorithm(key->public_key, NULL); + assert(current >= 0); + + return current == update; +} + +_public_ +uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + return wire_ctx_read_u8(&wire); +} + +_public_ +int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + if (!can_change_algorithm(key, algorithm)) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + wire_ctx_write_u8(&wire, algorithm); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey) +{ + if (!key || !pubkey) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_available(&wire, pubkey); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey) +{ + if (!key || !pubkey || !pubkey->data) { + return DNSSEC_EINVAL; + } + + if (key->public_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + if (dnssec_key_get_algorithm(key) == 0) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + int result = dnskey_rdata_set_pubkey(&key->rdata, pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + result = dnskey_rdata_to_crypto_key(&key->rdata, &key->public_key); + if (result != DNSSEC_EOK) { + key->rdata.size = DNSKEY_RDATA_OFFSET_PUBKEY; // downsize + return result; + } + + return DNSSEC_EOK; +} + +_public_ +unsigned dnssec_key_get_size(const dnssec_key_t *key) +{ + if (!key || !key->public_key) { + return 0; + } + + unsigned bits = 0; + uint8_t algorithm = dnssec_key_get_algorithm(key); + switch (algorithm) { + case 13: + bits = 256; + break; + case 14: + bits = 384; + break; + case 15: + bits = 256; + break; + case 16: + bits = 456; + break; + default: + gnutls_pubkey_get_pk_algorithm(key->public_key, &bits); + } + + return bits; +} + +_public_ +int dnssec_key_get_keyid(const dnssec_key_t *key, char **id) +{ + if (!key || !id) { + return DNSSEC_EINVAL; + } + + return keyid_pubkey_hex(key->public_key, id); +} + +_public_ +int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata) +{ + if (!key || !rdata) { + return DNSSEC_EINVAL; + } + + *rdata = key->rdata; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata) +{ + if (!key || !rdata || !rdata->data) { + return DNSSEC_EINVAL; + } + + if (rdata->size < DNSKEY_RDATA_MIN_SIZE) { + return DNSSEC_MALFORMED_DATA; + } + + if (key->public_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + gnutls_pubkey_t new_pubkey = NULL; + int result = dnskey_rdata_to_crypto_key(rdata, &new_pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + result = dnssec_binary_resize(&key->rdata, rdata->size); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(new_pubkey); + return result; + } + + // commit result + memmove(key->rdata.data, rdata->data, rdata->size); + key->public_key = new_pubkey; + + return DNSSEC_EOK; +} + +/* -- key presence checking ------------------------------------------------ */ + +_public_ +bool dnssec_key_can_sign(const dnssec_key_t *key) +{ + return key && key->private_key; +} + +_public_ +bool dnssec_key_can_verify(const dnssec_key_t *key) +{ + return key && key->public_key; +} diff --git a/src/libdnssec/key/keytag.c b/src/libdnssec/key/keytag.c new file mode 100644 index 0000000..edff684 --- /dev/null +++ b/src/libdnssec/key/keytag.c @@ -0,0 +1,88 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/shared/shared.h" + +/*! + * Compute keytag for RSA/MD5 key. + * + * \see RFC 2537 (section 2), RFC 4034 (appendix B.1) + */ +static uint16_t keytag_compat(const dnssec_binary_t *rdata) +{ + assert(rdata); + assert(rdata->data); + + if (rdata->size < 9) { // in fact, the condition could be stricter + return 0; + } + + uint8_t msb = rdata->data[rdata->size - 3]; + uint8_t lsb = rdata->data[rdata->size - 2]; + + return (msb << 8) + lsb; +} + +/*! + * Compute keytag for other than RSA/MD5 key. + * + * \see RFC 4034 (appendix B) + */ +static uint16_t keytag_current(const dnssec_binary_t *rdata) +{ + assert(rdata); + assert(rdata->data); + + uint32_t ac = 0; + for (int i = 0; i < rdata->size; i++) { + ac += (i & 1) ? rdata->data[i] : rdata->data[i] << 8; + } + + return (ac >> 16) + ac; +} + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Compute keytag for a DNSSEC key. + */ +_public_ +int dnssec_keytag(const dnssec_binary_t *rdata, uint16_t *keytag) +{ + if (!rdata || !keytag) { + return DNSSEC_EINVAL; + } + + if (!rdata->data || rdata->size < DNSKEY_RDATA_OFFSET_PUBKEY) { + return DNSSEC_MALFORMED_DATA; + } + + uint8_t algorithm = rdata->data[DNSKEY_RDATA_OFFSET_ALGORITHM]; + if (algorithm == 1) { + *keytag = keytag_compat(rdata); + } else { + *keytag = keytag_current(rdata); + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/privkey.c b/src/libdnssec/key/privkey.c new file mode 100644 index 0000000..ec1dcbd --- /dev/null +++ b/src/libdnssec/key/privkey.c @@ -0,0 +1,140 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- internal functions --------------------------------------------------- */ + +/*! + * Check if the algorithm number is valid for given DNSKEY. + */ +static bool valid_algorithm(dnssec_key_t *key, gnutls_privkey_t privkey) +{ + uint8_t current = dnssec_key_get_algorithm(key); + int gnu_algorithm = gnutls_privkey_get_pk_algorithm(privkey, NULL); + + return (gnu_algorithm == algorithm_to_gnutls(current)); +} + +/*! + * Create GnuTLS public key from private key. + */ +static int public_from_private(gnutls_privkey_t privkey, gnutls_pubkey_t *pubkey) +{ + assert(privkey); + assert(pubkey); + + gnutls_pubkey_t new_key = NULL; + int result = gnutls_pubkey_init(&new_key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + result = gnutls_pubkey_import_privkey(new_key, privkey, 0, 0); + if (result != GNUTLS_E_SUCCESS) { + gnutls_pubkey_deinit(new_key); + return DNSSEC_KEY_IMPORT_ERROR; + } + + *pubkey = new_key; + + return DNSSEC_EOK; +} + +/*! + * Create public key (GnuTLS and DNSKEY RDATA) from a private key. + */ +static int create_public_key(gnutls_privkey_t privkey, + gnutls_pubkey_t *pubkey_ptr, + dnssec_binary_t *rdata) +{ + assert(privkey); + assert(pubkey_ptr); + assert(rdata); + + // crypto public key + + gnutls_pubkey_t pubkey = NULL; + int result = public_from_private(privkey, &pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + // dnssec public key + + _cleanup_binary_ dnssec_binary_t rdata_pubkey = { 0 }; + result = convert_pubkey_to_dnskey(pubkey, &rdata_pubkey); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(pubkey); + return result; + } + + size_t rdata_size = DNSKEY_RDATA_OFFSET_PUBKEY + rdata_pubkey.size; + result = dnssec_binary_resize(rdata, rdata_size); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(pubkey); + return result; + } + + // updated RDATA + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_write(&wire, &rdata_pubkey); + assert(wire_ctx_offset(&wire) == rdata->size); + + *pubkey_ptr = pubkey; + + return DNSSEC_EOK; +} + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Load a private key into a DNSSEC key, create a public part if necessary. + */ +int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey) +{ + assert(key); + assert(privkey); + assert(key->private_key == NULL); + + if (!valid_algorithm(key, privkey)) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (!key->public_key) { + int r = create_public_key(privkey, &key->public_key, &key->rdata); + if (r != DNSSEC_EOK) { + return r; + } + } + + key->private_key = privkey; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/privkey.h b/src/libdnssec/key/privkey.h new file mode 100644 index 0000000..c0c5bb2 --- /dev/null +++ b/src/libdnssec/key/privkey.h @@ -0,0 +1,35 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/key.h" + +/*! + * Load a private key into a DNSSEC key, create a public part if necessary. + * + * If the public key is not loaded, at least an algorithm must be set. + * + * Updates private key, public key, RDATA, and key identifiers. + * + * \param key DNSSEC key to be updated. + * \param privkey Private key to be set. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey); diff --git a/src/libdnssec/key/simple.c b/src/libdnssec/key/simple.c new file mode 100644 index 0000000..a2d8ea4 --- /dev/null +++ b/src/libdnssec/key/simple.c @@ -0,0 +1,55 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem) +{ + if (!key || !pem || !pem->data) { + return DNSSEC_EINVAL; + } + + if (dnssec_key_get_algorithm(key) == 0) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + gnutls_privkey_t privkey = NULL; + int r = pem_privkey(pem, &privkey); + if (r != DNSSEC_EOK) { + return r; + } + + r = key_set_private_key(key, privkey); + if (r != DNSSEC_EOK) { + gnutls_privkey_deinit(privkey); + return r; + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/keyid.c b/src/libdnssec/keyid.c new file mode 100644 index 0000000..c99a657 --- /dev/null +++ b/src/libdnssec/keyid.c @@ -0,0 +1,87 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/shared.h" + +#include "contrib/ctype.h" +#include "contrib/tolower.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +bool dnssec_keyid_is_valid(const char *id) +{ + if (!id) { + return false; + } + + if (strlen(id) != DNSSEC_KEYID_SIZE) { + return false; + } + + for (int i = 0; i < DNSSEC_KEYID_SIZE; i++) { + if (!is_xdigit(id[i])) { + return false; + } + } + + return true; +} + +_public_ +void dnssec_keyid_normalize(char *id) +{ + if (!id) { + return; + } + + for (size_t i = 0; i < DNSSEC_KEYID_SIZE; i++) { + assert(id[i] != '\0' && is_xdigit(id[i])); + id[i] = knot_tolower(id[i]); + } +} + +_public_ +char *dnssec_keyid_copy(const char *id) +{ + if (!id) { + return NULL; + } + + char *copy = strdup(id); + if (!copy) { + return NULL; + } + + dnssec_keyid_normalize(copy); + + return copy; +} + +_public_ +bool dnssec_keyid_equal(const char *one, const char *two) +{ + if (!one || !two) { + return NULL; + } + + return (strcasecmp(one, two) == 0); +} diff --git a/src/libdnssec/keyid.h b/src/libdnssec/keyid.h new file mode 100644 index 0000000..24e201e --- /dev/null +++ b/src/libdnssec/keyid.h @@ -0,0 +1,80 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup keyid + * + * \brief DNSSEC key ID manipulation. + * + * The module contains auxiliary functions for manipulation with key IDs. + * + * Example: + * + * ~~~~~ {.c} + * + * char *key_id = "ef26672cafede0732dd18fba6488fa390b5589af"; + * assert(dnssec_keyid_is_valid(key_id)); + * + * char copy[DNSSEC_KEY_ID_SIZE + 1] = { 0 }; + * memcpy(copy, key_id, sizeof(copy)); + * for (int i = 0; i < DNSSEC_KEY_ID_SIZE; i++) { + * copy[i] = toupper((unsigned char)copy[i]); + * } + * + * assert(dnssec_keyid_equal(key_id, copy)); + * + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +/*! + * Length of the key ID in presentation form (ASCII). + */ +#define DNSSEC_KEYID_SIZE 40 + +/*! + * Length of the key ID in internal form (binary). + */ +#define DNSSEC_KEYID_BINARY_SIZE 20 + +/*! + * Check if a provided string is a valid key ID string. + */ +bool dnssec_keyid_is_valid(const char *id); + +/*! + * Normalize the key ID string. + */ +void dnssec_keyid_normalize(char *id); + +/*! + * Create a normalized copy if the key ID. + */ +char *dnssec_keyid_copy(const char *id); + +/*! + * Check if two key IDs are equal. + */ +bool dnssec_keyid_equal(const char *one, const char *two); + +/*! @} */ diff --git a/src/libdnssec/keystore.h b/src/libdnssec/keystore.h new file mode 100644 index 0000000..2382518 --- /dev/null +++ b/src/libdnssec/keystore.h @@ -0,0 +1,297 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup keystore + * + * \brief Private key store access. + * + * The module provides abstraction for private key store. Basically, PKCS #8 + * and PKCS #11 interfaces are supported. + * + * PKCS #8 uses unencrypted PEM, and allows implementation of custom stores. + * + * PKCS #11 provides access Hardware Security Modules. + * + * Example of using default PKCS #8 and to generate an RSA key: + * + * ~~~~~ {.c} + * + * int result; + * dnssec_keystore_t *store = NULL; + * + * // create key store access context + * dnssec_keystore_init_pkcs8_dir(&store); + * + * // open the key store + * result = dnssec_keystore_open(&store, "/path/to/keydb"); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * // generate new private key in the key store + * int algorithm = DNSSEC_KEY_ALGORITHM_RSA_SHA256; + * unsigned bits = 2048; + * char *id = NULL; + * int dnssec_keystore_generate_key(store, algorithm, bits, &key_id); + * if (result != DNSSEC_EOK) { + * dnssec_keystore_close(store); + * return result; + * } + * printf("ID of the new key: %s\n", key_id); + * + * // create new signing key + * dnssec_key_t *key = NULL; + * result = dnssec_key_new(&key); + * if (result != DNSSEC_EOK) { + * free(key_id); + * dnssec_keystore_close(store); + * return result; + * } + * + * // import the key from the key store + * result = dnssec_key_import_keystore(key, store, key_id, algorithm); + * if (result != DNSSEC_EOK) { + * free(key_id); + * dnssec_key_free(key); + * dnssec_keystore_close(store); + * return result; + * } + * + * // use the key for signing ... + * + * // cleanup + * free(key_id); + * dnssec_key_free(key); + * dnssec_keystore_close(store); + * dnssec_keystore_deinit(store); + * + * ~~~~~ + * @{ + */ + +#pragma once + +#include <libdnssec/binary.h> +#include <libdnssec/key.h> +#include <libdnssec/list.h> + +struct dnssec_keystore; + +/*! + * DNSSEC private keys store. + */ +typedef struct dnssec_keystore dnssec_keystore_t; + +/*! + * PKCS #8 key store callback functions for custom providers. + */ +typedef struct dnssec_keystore_pkcs8_functions { + /*! + * Callback to allocate key store handle. + * + * \param[out] handle_ptr Allocated key store handle. + */ + int (*handle_new)(void **handle_ptr); + + /*! + * Callback to deallocate key store handle. + * + * \param handle Key store handle. + */ + int (*handle_free)(void *handle); + + /*! + * Callback to initialize the key store. + * + * \param handle Key store handle. + * \param config Configuration string. + */ + int (*init)(void *handle, const char *config); + + /*! + * Callback to open the key store. + * + * \param[out] handle Key store handle. + * \param[in] config Configuration string. + */ + int (*open)(void *handle, const char *config); + + /*! + * Callback to close the key store. + * + * \param handle Key store handle. + */ + int (*close)(void *handle); + + /*! + * Callback to read a PEM key. + * + * \param[in] handle Key store handle. + * \param[in] id Key ID of the key to be retrieved (ASCII form). + * \param[out] pem Key material in uncencrypted PEM format. + */ + int (*read)(void *handle, const char *id, dnssec_binary_t *pem); + + /*! + * Callback to write a PEM key. + * + * \param handle Key store handle. + * \param id Key ID of the key to be saved (ASCII form). + * \param pem Key material in unencrypted PEM format. + */ + int (*write)(void *handle, const char *id, const dnssec_binary_t *pem); + + /*! + * Callback to get a list of all PEM key IDs. + * + * \param[in] handle Key store handle. + * \param[out] list Allocated list of key IDs. + */ + int (*list)(void *handle, dnssec_list_t **list); + + /*! + * Callback to remove a PEM key. + * + * \param handle Key store handle. + * \param id Key ID of the key to be removed (ASCII form). + */ + int (*remove)(void *handle, const char *id); +} dnssec_keystore_pkcs8_functions_t; + +/*! + * Create default PKCS #8 private key store context. + * + * The default store maintains the private keys in one directory on the file + * system. The private keys are stored in unencrypted PEM format, named + * key-id.pem. The configuration string is a path to the directory. + * + * \param[out] store Opened key store. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_init_pkcs8_dir(dnssec_keystore_t **store); + +/*! + * Create custom PKCS #8 private key store context. + * + * \param[out] store Opened key store. + * \param[in] impl Implementation of the key store provider. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_init_pkcs8_custom(dnssec_keystore_t **store, + const dnssec_keystore_pkcs8_functions_t *impl); + +/*! + * Crate new PKCS #11 private key store context. + * + * \param[out] store Opened key store. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store); + +/*! + * Deinitialize private key store context. + * + * \param store Key store to be deinitialized. + */ +int dnssec_keystore_deinit(dnssec_keystore_t *store); + +/*! + * Initialize new private key store. + */ +int dnssec_keystore_init(dnssec_keystore_t *store, const char *config); + +/*! + * Open private key store. + */ +int dnssec_keystore_open(dnssec_keystore_t *store, const char *config); + +/*! + * Close private key store. + * + * \param store Key store to be closed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_close(dnssec_keystore_t *store); + +/*! + * Get a list of key IDs stored in the key store. + * + * \todo Not implemented. + * + * \param[in] store Key store. + * \param[out] list Resulting list of key IDs. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_list_keys(dnssec_keystore_t *store, dnssec_list_t **list); + +/*! + * Generate a new key in the key store. + * + * \param[in] store Key store. + * \param[in] algorithm Algorithm. + * \param[in] bits Bit length of the key to be generated. + * \param[out] id_ptr ID of the generated key. Must be freed by the caller. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_generate_key(dnssec_keystore_t *store, + dnssec_key_algorithm_t algorithm, + unsigned bits, char **id_ptr); + +/*! + * Import an existing key into the key store. + * + * \param[in] store Key store. + * \param[in] pem Private key material in PEM format. + * \param[out] id_ptr ID of the imported key. Must be freed by the caller. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem, + char **id_ptr); + +/*! + * Remove a private key from the key store. + * + * \param store Key store. + * \param id ID of the private key to be deleted. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_remove_key(dnssec_keystore_t *store, const char *id); + +/*! + * Import public and/or private key from the key store into a DNSSEC key. + * + * The key algorithm has to be set before calling this function. + * + * \param key DNSSEC key to be initialized. + * \param keystore Private key store. + * \param id ID of the key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_key_import_keystore(dnssec_key_t *key, dnssec_keystore_t *keystore, + const char *id); + +/*! @} */ diff --git a/src/libdnssec/keystore/internal.h b/src/libdnssec/keystore/internal.h new file mode 100644 index 0000000..a87dd03 --- /dev/null +++ b/src/libdnssec/keystore/internal.h @@ -0,0 +1,52 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/gnutls.h> +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/key.h" +#include "libdnssec/keystore.h" +#include "libdnssec/list.h" + +typedef struct keystore_functions { + // construction of internal context + int (*ctx_new)(void **ctx_ptr, void *data); + int (*ctx_free)(void *ctx); + // keystore init/open/close + int (*init)(void *ctx, const char *config); + int (*open)(void *ctx, const char *config); + int (*close)(void *ctx); + // keystore access + int (*list_keys)(void *ctx, dnssec_list_t **list); + int (*generate_key)(void *ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, char **id_ptr); + int (*import_key)(void *ctx, const dnssec_binary_t *pem, char **id_ptr); + int (*remove_key)(void *ctx, const char *id); + // private key access + int (*get_private)(void *ctx, const char *id, gnutls_privkey_t *key_ptr); +} keystore_functions_t; + +struct dnssec_keystore { + const keystore_functions_t *functions; + void *ctx; +}; + +int keystore_create(dnssec_keystore_t **store_ptr, + const keystore_functions_t *functions, + void *ctx_custom_data); diff --git a/src/libdnssec/keystore/keystore.c b/src/libdnssec/keystore/keystore.c new file mode 100644 index 0000000..203441a --- /dev/null +++ b/src/libdnssec/keystore/keystore.c @@ -0,0 +1,183 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <stdlib.h> + +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/keyid.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/shared/shared.h" + +/* -- internal API --------------------------------------------------------- */ + +int keystore_create(dnssec_keystore_t **store_ptr, + const keystore_functions_t *functions, + void *ctx_custom_data) +{ + assert(store_ptr); + assert(functions); + + dnssec_keystore_t *store = calloc(1, sizeof(*store)); + if (!store) { + return DNSSEC_ENOMEM; + } + + store->functions = functions; + + int result = functions->ctx_new(&store->ctx, ctx_custom_data); + if (result != DNSSEC_EOK) { + free(store); + return DNSSEC_ENOMEM; + } + + *store_ptr = store; + return DNSSEC_EOK; +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_deinit(dnssec_keystore_t *store) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + dnssec_keystore_close(store); + store->functions->ctx_free(store->ctx); + + free(store); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_keystore_init(dnssec_keystore_t *store, const char *config) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->init(store->ctx, config); +} + +_public_ +int dnssec_keystore_open(dnssec_keystore_t *store, const char *config) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->open(store->ctx, config); +} + +_public_ +int dnssec_keystore_close(dnssec_keystore_t *store) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->close(store->ctx); +} + +_public_ +int dnssec_keystore_list_keys(dnssec_keystore_t *store, dnssec_list_t **list) +{ + if (!store || !list) { + return DNSSEC_EINVAL; + } + + return store->functions->list_keys(store->ctx, list); +} + +_public_ +int dnssec_keystore_generate_key(dnssec_keystore_t *store, + dnssec_key_algorithm_t _algorithm, + unsigned bits, char **id_ptr) +{ + if (!store || !_algorithm || !id_ptr) { + return DNSSEC_EINVAL; + } + + // prepare parameters + + gnutls_pk_algorithm_t algorithm = algorithm_to_gnutls(_algorithm); + if (algorithm == GNUTLS_PK_UNKNOWN) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (!dnssec_algorithm_key_size_check(_algorithm, bits)) { + return DNSSEC_INVALID_KEY_SIZE; + } + + return store->functions->generate_key(store->ctx, algorithm, bits, id_ptr); +} + +_public_ +int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem, + char **id_ptr) +{ + if (!store || !pem || !id_ptr) { + return DNSSEC_EINVAL; + } + + return store->functions->import_key(store->ctx, pem, id_ptr); +} + +_public_ +int dnssec_keystore_remove_key(dnssec_keystore_t *store, const char *key_id) +{ + if (!store || !key_id) { + return DNSSEC_EINVAL; + } + + return store->functions->remove_key(store->ctx, key_id); +} + +_public_ +int dnssec_key_import_keystore(dnssec_key_t *key, dnssec_keystore_t *store, + const char *id) +{ + if (!key || !store || !id || dnssec_key_get_algorithm(key) == 0) { + return DNSSEC_EINVAL; + } + + if (key->private_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + gnutls_privkey_t privkey = NULL; + int r = store->functions->get_private(store->ctx, id, &privkey); + if (r != DNSSEC_EOK) { + return r; + } + + r = key_set_private_key(key, privkey); + if (r != DNSSEC_EOK) { + gnutls_privkey_deinit(privkey); + return r; + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/keystore/pkcs11.c b/src/libdnssec/keystore/pkcs11.c new file mode 100644 index 0000000..dbe1a37 --- /dev/null +++ b/src/libdnssec/keystore/pkcs11.c @@ -0,0 +1,440 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <gnutls/gnutls.h> +#include <pthread.h> + +#include "libdnssec/error.h" +#include "libdnssec/shared/hex.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/p11/p11.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" + +#ifdef ENABLE_PKCS11 + +struct pkcs11_ctx { + char *url; +}; + +typedef struct pkcs11_ctx pkcs11_ctx_t; + +/*! + * Flags used when generating/import key into the token. + */ +static const int TOKEN_ADD_FLAGS = GNUTLS_PKCS11_OBJ_FLAG_MARK_SENSITIVE + | GNUTLS_PKCS11_OBJ_FLAG_MARK_PRIVATE; + +static int key_url(const char *token_uri, const char *key_id, char **url_ptr) +{ + assert(token_uri); + assert(key_id); + assert(url_ptr); + + if (!dnssec_keyid_is_valid(key_id)) { + return DNSSEC_INVALID_KEY_ID; + } + + size_t token_len = strlen(token_uri); + size_t id_len = strlen(key_id); + + // url: <token-url>;id=%aa%bb%cc.. + + size_t len = token_len + 4 + (id_len / 2 * 3); + char *url = malloc(len + 1); + if (!url) { + return DNSSEC_ENOMEM; + } + + size_t prefix = snprintf(url, len, "%s;id=", token_uri); + if (prefix != token_len + 4) { + free(url); + return DNSSEC_ENOMEM; + } + + assert(id_len % 2 == 0); + char *pos = url + prefix; + for (int i = 0; i < id_len; i += 2, pos += 3) { + pos[0] = '%'; + pos[1] = key_id[i]; + pos[2] = key_id[i+1]; + } + assert(url + len == pos); + url[len] = '\0'; + + *url_ptr = url; + return DNSSEC_EOK; +} + +/** + * Parse configuration string. Accepted format: "<pkcs11-url> <module-path>" + */ +static int parse_config(const char *config, char **uri_ptr, char **module_ptr) +{ + const char *space = strchr(config, ' '); + if (!space) { + return DNSSEC_KEYSTORE_INVALID_CONFIG; + } + + char *url = strndup(config, space - config); + char *module = strdup(space + 1); + + if (!url || !module) { + free(url); + free(module); + return DNSSEC_ENOMEM; + } + + *uri_ptr = url; + *module_ptr = module; + + return DNSSEC_EOK; +} + +/*! + * Load PKCS #11 module and check if the token is available. + */ +static int safe_open(const char *config, char **url_ptr) +{ + char *url = NULL; + char *module = NULL; + + int r = parse_config(config, &url, &module); + if (r != DNSSEC_EOK) { + return r; + } + + r = p11_load_module(module); + free(module); + if (r != GNUTLS_E_SUCCESS) { + free(url); + return DNSSEC_P11_FAILED_TO_LOAD_MODULE; + } + + unsigned int flags = 0; + r = gnutls_pkcs11_token_get_flags(url, &flags); + if (r != GNUTLS_E_SUCCESS) { + free(url); + return DNSSEC_P11_TOKEN_NOT_AVAILABLE; + } + + *url_ptr = url; + + return DNSSEC_EOK; +} + +/* -- internal API --------------------------------------------------------- */ + +static void disable_pkcs11_callbacks(void) +{ + gnutls_pkcs11_set_pin_function(NULL, NULL); + gnutls_pkcs11_set_token_function(NULL, NULL); +} + +static int pkcs11_ctx_new(void **ctx_ptr, _unused_ void *data) +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, disable_pkcs11_callbacks); + + pkcs11_ctx_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +static int pkcs11_ctx_free(void *ctx) +{ + if (ctx) { + free(ctx); + } + + return DNSSEC_EOK; +} + +static int pkcs11_init(void *ctx, const char *config) +{ + /* + * Current keystore initialization is idempotent. We don't really + * initialize the token because don't want to wipe the data. We just + * check that the token is available the same way pkcs11_open() does. + */ + + _cleanup_free_ char *url = NULL; + + return safe_open(config, &url); +} + +static int pkcs11_open(void *_ctx, const char *config) +{ + pkcs11_ctx_t *ctx = _ctx; + + return safe_open(config, &ctx->url); +} + +static int pkcs11_close(void *_ctx) +{ + pkcs11_ctx_t *ctx = _ctx; + + free(ctx->url); + clear_struct(ctx); + + return DNSSEC_EOK; +} + +static char *get_object_id(gnutls_pkcs11_obj_t object) +{ + assert(object); + + uint8_t buffer[DNSSEC_KEYID_BINARY_SIZE] = { 0 }; + size_t size = sizeof(buffer); + + int r = gnutls_pkcs11_obj_get_info(object, GNUTLS_PKCS11_OBJ_ID, buffer, &size); + if (r != GNUTLS_E_SUCCESS || size != sizeof(buffer)) { + return NULL; + } + + const dnssec_binary_t bin = { .data = buffer, .size = sizeof(buffer) }; + char *id = NULL; + if (bin_to_hex(&bin, &id) != DNSSEC_EOK) { + return NULL; + } + + assert(strlen(id) == DNSSEC_KEYID_SIZE); + + return id; +} + +static int pkcs11_list_keys(void *_ctx, dnssec_list_t **list) +{ + pkcs11_ctx_t *ctx = _ctx; + + dnssec_list_t *ids = dnssec_list_new(); + if (!ids) { + return DNSSEC_ENOMEM; + } + + gnutls_pkcs11_obj_t *objects = NULL; + unsigned count = 0; + + int flags = GNUTLS_PKCS11_OBJ_FLAG_PRIVKEY | + GNUTLS_PKCS11_OBJ_FLAG_LOGIN; + + int r = gnutls_pkcs11_obj_list_import_url4(&objects, &count, ctx->url, flags); + if (r != GNUTLS_E_SUCCESS) { + dnssec_list_free(ids); + return DNSSEC_P11_TOKEN_NOT_AVAILABLE; + } + + for (unsigned i = 0; i < count; i++) { + gnutls_pkcs11_obj_t object = objects[i]; + char *id = get_object_id(object); + dnssec_list_append(ids, id); + gnutls_pkcs11_obj_deinit(object); + } + + gnutls_free(objects); + + *list = ids; + return DNSSEC_EOK; +} + +static int pkcs11_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, char **id_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + uint8_t buf[20] = { 0 }; + gnutls_rnd(GNUTLS_RND_RANDOM, buf, sizeof(buf)); + dnssec_binary_t cka_id = { .data = buf, .size = sizeof(buf) }; + + int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN; + gnutls_datum_t gt_cka_id = binary_to_datum(&cka_id); + int r = gnutls_pkcs11_privkey_generate3(ctx->url, algorithm, bits, NULL, >_cka_id, 0, NULL, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_GENERATE_ERROR; + } + + char *id = NULL; + r = bin_to_hex(&cka_id, &id); + if (r != DNSSEC_EOK) { + return DNSSEC_ENOMEM; + } + + *id_ptr = id; + + return DNSSEC_EOK; +} + +static int import_pem(const dnssec_binary_t *pem, + gnutls_x509_privkey_t *key_ptr, + gnutls_pubkey_t *pubkey_ptr) +{ + gnutls_x509_privkey_t x509_key = NULL; + gnutls_privkey_t key = NULL; + gnutls_pubkey_t pubkey = NULL; + + int r = pem_x509(pem, &x509_key); + if (r != DNSSEC_EOK) { + goto fail; + } + + if (gnutls_privkey_init(&key) != GNUTLS_E_SUCCESS || + gnutls_pubkey_init(&pubkey) != GNUTLS_E_SUCCESS + ) { + r = DNSSEC_ENOMEM; + goto fail; + } + + if (gnutls_privkey_import_x509(key, x509_key, 0) != GNUTLS_E_SUCCESS || + gnutls_pubkey_import_privkey(pubkey, key, 0, 0) != GNUTLS_E_SUCCESS + ) { + r = DNSSEC_KEY_IMPORT_ERROR; + goto fail; + } + +fail: + gnutls_privkey_deinit(key); + + if (r == DNSSEC_EOK) { + *key_ptr = x509_key; + *pubkey_ptr = pubkey; + } else { + gnutls_x509_privkey_deinit(x509_key); + gnutls_pubkey_deinit(pubkey); + } + + return r; +} + +static int pkcs11_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + _cleanup_pubkey_ gnutls_pubkey_t pubkey = NULL; + + int r = import_pem(pem, &key, &pubkey); + if (r != DNSSEC_EOK) { + return r; + } + + _cleanup_binary_ dnssec_binary_t id = { 0 }; + r = keyid_x509(key, &id); + if (r != DNSSEC_EOK) { + return r; + } + + int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN; + gnutls_datum_t gid = binary_to_datum(&id); + + r = gnutls_pkcs11_copy_x509_privkey2(ctx->url, key, NULL, &gid, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + r = gnutls_pkcs11_copy_pubkey(ctx->url, pubkey, NULL, &gid, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + // note, we result with dangling private key in the token + return DNSSEC_KEY_IMPORT_ERROR; + } + + return bin_to_hex(&id, id_ptr); +} + +static int pkcs11_remove_key(void *_ctx, const char *id) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_free_ char *url = NULL; + int r = key_url(ctx->url, id, &url); + if (r != DNSSEC_EOK) { + return r; + } + + r = gnutls_pkcs11_delete_url(url, GNUTLS_PKCS11_OBJ_FLAG_LOGIN); + if (r < 0) { + return DNSSEC_ERROR; + } else if (r == 0) { + return DNSSEC_ENOENT; + } + + return DNSSEC_EOK; +} + +static int pkcs11_get_private(void *_ctx, const char *id, gnutls_privkey_t *key_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_free_ char *url = NULL; + int r = key_url(ctx->url, id, &url); + if (r != DNSSEC_EOK) { + return r; + } + + gnutls_privkey_t key = NULL; + r = gnutls_privkey_init(&key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + r = gnutls_privkey_import_pkcs11_url(key, url); + if (r != GNUTLS_E_SUCCESS) { + gnutls_privkey_deinit(key); + return DNSSEC_NOT_FOUND; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr) +{ + static const keystore_functions_t IMPLEMENTATION = { + .ctx_new = pkcs11_ctx_new, + .ctx_free = pkcs11_ctx_free, + .init = pkcs11_init, + .open = pkcs11_open, + .close = pkcs11_close, + .list_keys = pkcs11_list_keys, + .generate_key = pkcs11_generate_key, + .import_key = pkcs11_import_key, + .remove_key = pkcs11_remove_key, + .get_private = pkcs11_get_private, + }; + + return keystore_create(store_ptr, &IMPLEMENTATION, NULL); +} + +#else // !ENABLE_PKCS11 + +_public_ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr) +{ + return DNSSEC_NOT_IMPLEMENTED_ERROR; +} + +#endif diff --git a/src/libdnssec/keystore/pkcs8.c b/src/libdnssec/keystore/pkcs8.c new file mode 100644 index 0000000..f05eda0 --- /dev/null +++ b/src/libdnssec/keystore/pkcs8.c @@ -0,0 +1,211 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "libdnssec/error.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/keyid.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" + +/*! + * PKCS #8 key store context. + */ +typedef struct pkcs8_ctx { + /*! Storage implementation callbacks. */ + const dnssec_keystore_pkcs8_functions_t *functions; + /*! Implementation specific context data. */ + void *data; +} pkcs8_ctx_t; + +/* -- internal API --------------------------------------------------------- */ + +static int pkcs8_ctx_new(void **ctx_ptr, void *_functions) +{ + assert(ctx_ptr); + assert(_functions); + + pkcs8_ctx_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + ctx->functions = _functions; + + int r = ctx->functions->handle_new(&ctx->data); + if (r != DNSSEC_EOK) { + free(ctx); + return r; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +static int pkcs8_ctx_free(void *_ctx) +{ + pkcs8_ctx_t *ctx = _ctx; + int r = ctx->functions->handle_free(ctx->data); + + free(ctx); + + return r; +} + +static int pkcs8_init(void *_ctx, const char *config) +{ + pkcs8_ctx_t *ctx = _ctx; + return ctx->functions->init(ctx->data, config); +} + +static int pkcs8_open(void *_ctx, const char *config) +{ + pkcs8_ctx_t *ctx = _ctx; + return ctx->functions->open(ctx->data, config); +} + +static int pkcs8_close(void *_ctx) +{ + pkcs8_ctx_t *ctx = _ctx; + return ctx->functions->close(ctx->data); +} + +static int pkcs8_list_keys(void *_ctx, dnssec_list_t **list) +{ + pkcs8_ctx_t *ctx = _ctx; + return ctx->functions->list(ctx->data, list); +} + +static int pkcs8_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, char **id_ptr) +{ + assert(_ctx); + assert(id_ptr); + + pkcs8_ctx_t *ctx = _ctx; + + // generate key + + char *new_id = NULL; + _cleanup_binary_ dnssec_binary_t data = { 0 }; + int r = pem_generate(algorithm, bits, &data, &new_id); + if (r != DNSSEC_EOK) { + return r; + } + + // save key + + r = ctx->functions->write(ctx->data, new_id, &data); + if (r != DNSSEC_EOK) { + return r; + } + + // finish + + *id_ptr = new_id; + + return DNSSEC_EOK; +} + +static int pkcs8_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_ptr) +{ + pkcs8_ctx_t *ctx = _ctx; + + // retrieve key ID + + char *id = NULL; + int r = pem_keyid(pem, &id); + if (r != DNSSEC_EOK) { + return r; + } + + // save the key + + r = ctx->functions->write(ctx->data, id, pem); + if (r != DNSSEC_EOK) { + free(id); + return r; + } + + *id_ptr = id; + + return DNSSEC_EOK; +} + +static int pkcs8_remove_key(void *_ctx, const char *id) +{ + pkcs8_ctx_t *ctx = _ctx; + return ctx->functions->remove(ctx->data, id); +} + +static int pkcs8_get_private(void *_ctx, const char *id, gnutls_privkey_t *key_ptr) +{ + assert(_ctx); + assert(id); + assert(key_ptr); + + pkcs8_ctx_t *ctx = _ctx; + + // load private key data + + _cleanup_binary_ dnssec_binary_t pem = { 0 }; + int r = ctx->functions->read(ctx->data, id, &pem); + if (r != DNSSEC_EOK) { + return r; + } + + // construct the key + + gnutls_privkey_t key = NULL; + r = pem_privkey(&pem, &key); + if (r != DNSSEC_EOK) { + return r; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} + +static const keystore_functions_t PKCS8_FUNCTIONS = { + .ctx_new = pkcs8_ctx_new, + .ctx_free = pkcs8_ctx_free, + .init = pkcs8_init, + .open = pkcs8_open, + .close = pkcs8_close, + .list_keys = pkcs8_list_keys, + .generate_key = pkcs8_generate_key, + .import_key = pkcs8_import_key, + .remove_key = pkcs8_remove_key, + .get_private = pkcs8_get_private, +}; + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_init_pkcs8_custom(dnssec_keystore_t **store_ptr, + const dnssec_keystore_pkcs8_functions_t *store_functions) +{ + if (!store_ptr || !store_functions) { + return DNSSEC_EINVAL; + } + + return keystore_create(store_ptr, &PKCS8_FUNCTIONS, (void *)store_functions); +} diff --git a/src/libdnssec/keystore/pkcs8_dir.c b/src/libdnssec/keystore/pkcs8_dir.c new file mode 100644 index 0000000..bc67ad5 --- /dev/null +++ b/src/libdnssec/keystore/pkcs8_dir.c @@ -0,0 +1,379 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/fs.h" +#include "libdnssec/key.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/shared/shared.h" + +#define DIR_INIT_MODE 0750 + +/*! + * Context for PKCS #8 key directory. + */ +typedef struct pkcs8_dir_handle { + char *dir_name; +} pkcs8_dir_handle_t; + +/* -- internal functions --------------------------------------------------- */ + +/*! + * Get path to a private key in PKCS #8 PEM format. + */ +static char *key_path(const char *dir, const char *id) +{ + char *strp = NULL; + + int ret = asprintf(&strp, "%s/%s.pem", dir, id); + if (ret < 0) { + return NULL; + } + return strp; +} + +/*! + * Get size of the file and reset the position to the beginning of the file. + */ +static int file_size(int fd, size_t *size) +{ + off_t offset = lseek(fd, 0, SEEK_END); + if (offset == -1) { + return dnssec_errno_to_error(errno); + } + + if (lseek(fd, 0, SEEK_SET) == -1) { + return dnssec_errno_to_error(errno); + } + + assert(offset >= 0); + *size = offset; + + return DNSSEC_EOK; +} + +/*! + * Open a key file and get the descriptor. + */ +static int key_open(const char *dir_name, const char *id, int flags, + mode_t mode, int *fd_ptr) +{ + assert(dir_name); + assert(id); + assert(fd_ptr); + + _cleanup_free_ char *filename = key_path(dir_name, id); + if (!filename) { + return DNSSEC_ENOMEM; + } + + int fd = open(filename, flags, mode); + if (fd == -1) { + return dnssec_errno_to_error(errno); + } + + *fd_ptr = fd; + + return DNSSEC_EOK; +} + +static int key_open_read(const char *dir_name, const char *id, int *fd_ptr) +{ + return key_open(dir_name, id, O_RDONLY, 0, fd_ptr); +} + +static int key_open_write(const char *dir_name, const char *id, int *fd_ptr) +{ + return key_open(dir_name, id, O_WRONLY|O_CREAT|O_EXCL, + S_IRUSR|S_IWUSR|S_IRGRP, fd_ptr); +} + +/*! + * Strip '.pem' suffix, basename has to be valid key ID. + */ +static char *filename_to_keyid(const char *filename) +{ + assert(filename); + + size_t len = strlen(filename); + + const char ext[] = ".pem"; + const size_t ext_len = sizeof(ext) - 1; + + if (len < ext_len || strcmp(filename + len - ext_len, ext) != 0) { + return NULL; + } + + return strndup(filename, len - ext_len); +} + +/* -- PKCS #8 dir access API ----------------------------------------------- */ + +static int pkcs8_dir_handle_new(void **handle_ptr) +{ + if (!handle_ptr) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = calloc(1, sizeof(*handle)); + if (!handle) { + return DNSSEC_ENOMEM; + } + + *handle_ptr = handle; + + return DNSSEC_EOK; +} + +static int pkcs8_dir_handle_free(void *handle) +{ + free(handle); + + return DNSSEC_EOK; +} + +static int pkcs8_dir_init(void *handle, const char *path) +{ + if (!handle || !path) { + return DNSSEC_EINVAL; + } + + return fs_mkdir(path, DIR_INIT_MODE, true); +} + +static int pkcs8_dir_open(void *_handle, const char *config) +{ + if (!_handle || !config) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = _handle; + + char *path = realpath(config, NULL); + if (!path) { + return DNSSEC_NOT_FOUND; + } + + handle->dir_name = path; + + return DNSSEC_EOK; +} + +static int pkcs8_dir_close(void *_handle) +{ + if (!_handle) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = _handle; + + free(handle->dir_name); + memset(handle, 0, sizeof(*handle)); + + return DNSSEC_EOK; +} + +static int pkcs8_dir_read(void *_handle, const char *id, dnssec_binary_t *pem) +{ + if (!_handle || !id || !pem) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = _handle; + + // open file and get it's size + + _cleanup_close_ int file = 0; + int result = key_open_read(handle->dir_name, id, &file); + if (result != DNSSEC_EOK) { + return result; + } + + size_t size = 0; + result = file_size(file, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size == 0) { + return DNSSEC_MALFORMED_DATA; + } + + // read the stored data + + dnssec_binary_t read_pem = { 0 }; + result = dnssec_binary_alloc(&read_pem, size); + if (result != DNSSEC_EOK) { + return result; + } + + ssize_t read_count = read(file, read_pem.data, read_pem.size); + if (read_count == -1) { + dnssec_binary_free(&read_pem); + return dnssec_errno_to_error(errno); + } + + assert(read_count == read_pem.size); + *pem = read_pem; + + return DNSSEC_EOK; +} + +static bool key_is_duplicate(int open_error, pkcs8_dir_handle_t *handle, + const char *id, const dnssec_binary_t *pem) +{ + assert(handle); + assert(id); + assert(pem); + + if (open_error != dnssec_errno_to_error(EEXIST)) { + return false; + } + + _cleanup_binary_ dnssec_binary_t old = { 0 }; + int r = pkcs8_dir_read(handle, id, &old); + if (r != DNSSEC_EOK) { + return false; + } + + return dnssec_binary_cmp(&old, pem) == 0; +} + +static int pkcs8_dir_write(void *_handle, const char *id, const dnssec_binary_t *pem) +{ + if (!_handle || !id || !pem) { + return DNSSEC_EINVAL; + } + + if (pem->size == 0 || pem->data == NULL) { + return DNSSEC_MALFORMED_DATA; + } + + pkcs8_dir_handle_t *handle = _handle; + + // create the file + + _cleanup_close_ int file = 0; + int result = key_open_write(handle->dir_name, id, &file); + if (result != DNSSEC_EOK) { + if (key_is_duplicate(result, handle, id, pem)) { + return DNSSEC_EOK; + } + return result; + } + + // write the data + + ssize_t wrote_count = write(file, pem->data, pem->size); + if (wrote_count == -1) { + return dnssec_errno_to_error(errno); + } + + assert(wrote_count == pem->size); + + return DNSSEC_EOK; +} + +static int pkcs8_dir_list(void *_handle, dnssec_list_t **list_ptr) +{ + if (!_handle || !list_ptr) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = _handle; + + _cleanup_closedir_ DIR *dir = opendir(handle->dir_name); + if (!dir) { + return DNSSEC_NOT_FOUND; + } + + dnssec_list_t *list = dnssec_list_new(); + if (!list) { + return DNSSEC_ENOMEM; + } + + errno = 0; + struct dirent *result; + while ((result = readdir(dir)) != NULL) { + char *keyid = filename_to_keyid(result->d_name); + if (keyid) { + dnssec_list_append(list, keyid); + } + } + + if (errno != 0) { + dnssec_list_free_full(list, NULL, NULL); + return dnssec_errno_to_error(errno); + } + + *list_ptr = list; + + return DNSSEC_EOK; +} + +static int pkcs8_dir_remove(void *_handle, const char *id) +{ + if (!_handle || !id) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = _handle; + + _cleanup_free_ char *filename = key_path(handle->dir_name, id); + if (!filename) { + return DNSSEC_ENOMEM; + } + + if (unlink(filename) == -1) { + return dnssec_errno_to_error(errno); + } + + return DNSSEC_EOK; +} + +const dnssec_keystore_pkcs8_functions_t PKCS8_DIR_FUNCTIONS = { + .handle_new = pkcs8_dir_handle_new, + .handle_free = pkcs8_dir_handle_free, + .init = pkcs8_dir_init, + .open = pkcs8_dir_open, + .close = pkcs8_dir_close, + .read = pkcs8_dir_read, + .write = pkcs8_dir_write, + .list = pkcs8_dir_list, + .remove = pkcs8_dir_remove, +}; + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_init_pkcs8_dir(dnssec_keystore_t **store_ptr) +{ + if (!store_ptr) { + return DNSSEC_EINVAL; + } + + return dnssec_keystore_init_pkcs8_custom(store_ptr, &PKCS8_DIR_FUNCTIONS); +} diff --git a/src/libdnssec/keytag.h b/src/libdnssec/keytag.h new file mode 100644 index 0000000..5a8424e --- /dev/null +++ b/src/libdnssec/keytag.h @@ -0,0 +1,63 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup keytag + * + * \brief Low-level key tag computation API. + * + * The module provides simple interface for DNSKEY key id computation. + * + * Example: + * + * ~~~~~ {.c} + * + * dnssec_binary_t dnskey_rdata = // ... ; + * + * int result; + * uint16_t keytag = 0; + * + * result = dnssec_keytag(&dnskey_rdata, &keytag); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * printf("keytag: %s\n", keytag); + * + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <libdnssec/binary.h> + +/*! + * Compute a key tag for a DNSSEC key. + * + * \param[in] rdata DNSKEY RDATA. + * \param[out] keytag Computed keytag. + * + * \return Error code, DNSSEC_EOK of successful. + */ +int dnssec_keytag(const dnssec_binary_t *rdata, uint16_t *keytag); + +/*! + * @} + */ diff --git a/src/libdnssec/list.h b/src/libdnssec/list.h new file mode 100644 index 0000000..838c0b5 --- /dev/null +++ b/src/libdnssec/list.h @@ -0,0 +1,85 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup list + * + * \brief DNSSEC generic lists. + * + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stdlib.h> + +struct dnssec_list; +typedef struct dnssec_list dnssec_list_t; + +struct dnssec_item; +typedef struct dnssec_item dnssec_item_t; + +void *dnssec_item_get(const dnssec_item_t *item); +void dnssec_item_set(dnssec_item_t *item, void *data); + +dnssec_list_t *dnssec_list_new(void); +void dnssec_list_free(dnssec_list_t *list); + +typedef void (*dnssec_item_free_cb)(void *data, void *ctx); + +/*! + * Free the list including contained items. + * + * If \c free_cb is NULL, standard libc \c free will used to free the items. + * + * \param list List to be freed. + * \param free_cb Free function called for each item in the list. + * \param free_ctx Context passed to item free. + */ +void dnssec_list_free_full(dnssec_list_t *list, dnssec_item_free_cb free_cb, + void *free_ctx); + +void dnssec_list_clear(dnssec_list_t *list); +void dnssec_list_clear_full(dnssec_list_t *list, dnssec_item_free_cb free_cb, + void *free_ctx); + +dnssec_item_t *dnssec_list_head(dnssec_list_t *list); +dnssec_item_t *dnssec_list_tail(dnssec_list_t *list); +dnssec_item_t *dnssec_list_next(dnssec_list_t *list, dnssec_item_t *item); +dnssec_item_t *dnssec_list_prev(dnssec_list_t *list, dnssec_item_t *item); +dnssec_item_t *dnssec_list_nth(dnssec_list_t *list, size_t position); + +bool dnssec_list_is_empty(dnssec_list_t *list); +size_t dnssec_list_size(dnssec_list_t *list); + +int dnssec_list_insert_after(dnssec_item_t *item, void *data); +int dnssec_list_insert_before(dnssec_item_t *item, void *data); +int dnssec_list_append(dnssec_list_t *list, void *data); +int dnssec_list_prepend(dnssec_list_t *list, void *data); + +void dnssec_list_remove(dnssec_item_t *item); + +dnssec_item_t *dnssec_list_search(dnssec_list_t *list, void *data); +bool dnssec_list_contains(dnssec_list_t *list, void *data); + +#define dnssec_list_foreach(var, list) \ + for (dnssec_item_t *__tmp, *var = dnssec_list_head(list); \ + __tmp = dnssec_list_next(list, var), var != NULL; \ + var = __tmp) + +/*! @} */ diff --git a/src/libdnssec/list/list.c b/src/libdnssec/list/list.c new file mode 100644 index 0000000..5d9aded --- /dev/null +++ b/src/libdnssec/list/list.c @@ -0,0 +1,298 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> + +#include "libdnssec/list.h" +#include "libdnssec/list/ucw_clists.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" + +struct dnssec_list { + clist list; +}; + +struct dnssec_item { + cnode node; + void *data; +}; + +/*! + * Allocate new list item and set item data. + */ +static dnssec_item_t *item_new(void *data) +{ + dnssec_item_t *item = malloc(sizeof(*item)); + if (item) { + clear_struct(item); + item->data = data; + } + + return item; +} + +/*! + * Wrapper around libc free with unused context. + */ +static void wrap_free(void *ptr, void *ctx _unused_) +{ + free(ptr); +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +void *dnssec_item_get(const dnssec_item_t *item) +{ + return item ? item->data : NULL; +} + +_public_ +void dnssec_item_set(dnssec_item_t *item, void *data) +{ + if (item) { + item->data = data; + } +} + +_public_ +dnssec_list_t *dnssec_list_new(void) +{ + dnssec_list_t *list = malloc(sizeof(*list)); + if (list) { + clist_init(&list->list); + } + + return list; +} + +_public_ +void dnssec_list_clear(dnssec_list_t *list) +{ + if (!list) { + return; + } + + dnssec_list_foreach(item, list) { + free(item); + } +} + +_public_ +void dnssec_list_clear_full(dnssec_list_t *list, dnssec_item_free_cb free_cb, + void *free_ctx) +{ + if (!list) { + return; + } + + if (!free_cb) { + free_cb = wrap_free; + } + + dnssec_list_foreach(item, list) { + free_cb(item->data, free_ctx); + free(item); + } +} + +_public_ +void dnssec_list_free(dnssec_list_t *list) +{ + if (!list) { + return; + } + + dnssec_list_clear(list); + free(list); +} + +_public_ +void dnssec_list_free_full(dnssec_list_t *list, dnssec_item_free_cb free_cb, + void *free_ctx) +{ + if (!list) { + return; + } + + dnssec_list_clear_full(list, free_cb, free_ctx); + free(list); +} + +_public_ +dnssec_item_t *dnssec_list_head(dnssec_list_t *list) +{ + if (!list) { + return NULL; + } + + return clist_head(&list->list); +} + +_public_ +dnssec_item_t *dnssec_list_tail(dnssec_list_t *list) +{ + if (!list) { + return NULL; + } + + return clist_tail(&list->list); +} + +_public_ +dnssec_item_t *dnssec_list_next(dnssec_list_t *list, dnssec_item_t *item) +{ + if (!list || !item) { + return NULL; + } + + return clist_next(&list->list, &item->node); +} + +_public_ +dnssec_item_t *dnssec_list_prev(dnssec_list_t *list, dnssec_item_t *item) +{ + if (!list || !item) { + return NULL; + } + + return clist_prev(&list->list, &item->node); +} + +_public_ +dnssec_item_t *dnssec_list_nth(dnssec_list_t *list, size_t position) +{ + size_t index = 0; + dnssec_item_t *item = dnssec_list_head(list); + + while (item) { + if (index == position) { + return item; + } else { + item = dnssec_list_next(list, item); + index += 1; + } + } + + return NULL; +} + +_public_ +bool dnssec_list_is_empty(dnssec_list_t *list) +{ + return !list || clist_empty(&list->list); +} + +_public_ +size_t dnssec_list_size(dnssec_list_t *list) +{ + return list ? clist_size(&list->list) : 0; +} + +_public_ +int dnssec_list_insert_before(dnssec_item_t *item, void *data) +{ + if (!item) { + return DNSSEC_EINVAL; + } + + dnssec_item_t *add = item_new(data); + if (!add) { + return DNSSEC_ENOMEM; + } + + clist_insert_before(&add->node, &item->node); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_list_insert_after(dnssec_item_t *item, void *data) +{ + if (!item) { + return DNSSEC_EINVAL; + } + + dnssec_item_t *add = item_new(data); + if (!add) { + return DNSSEC_ENOMEM; + } + + clist_insert_after(&add->node, &item->node); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_list_append(dnssec_list_t *list, void *data) +{ + if (!list) { + return DNSSEC_EINVAL; + } + + dnssec_item_t *add = item_new(data); + if (!add) { + return DNSSEC_ENOMEM; + } + + clist_add_tail(&list->list , &add->node); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_list_prepend(dnssec_list_t *list, void *data) +{ + if (!list) { + return DNSSEC_EINVAL; + } + + dnssec_item_t *add = item_new(data); + if (!add) { + return DNSSEC_ENOMEM; + } + + clist_add_head(&list->list , &add->node); + + return DNSSEC_EOK; +} + +_public_ +void dnssec_list_remove(dnssec_item_t *item) +{ + if (item) { + clist_remove(&item->node); + free(item); + } +} + +_public_ +dnssec_item_t *dnssec_list_search(dnssec_list_t *list, void *data) +{ + dnssec_list_foreach(item, list) { + if (item->data == data) { + return item; + } + } + + return NULL; +} + +_public_ +bool dnssec_list_contains(dnssec_list_t *list, void *data) +{ + return dnssec_list_search(list, data) != NULL; +} diff --git a/src/libdnssec/list/ucw_clists.h b/src/libdnssec/list/ucw_clists.h new file mode 100644 index 0000000..080d01e --- /dev/null +++ b/src/libdnssec/list/ucw_clists.h @@ -0,0 +1,260 @@ +/* + * UCW Library -- Circular Linked Lists + * + * (c) 2003--2010 Martin Mares <mj@ucw.cz> + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#ifndef _UCW_CLISTS_H +#define _UCW_CLISTS_H + +/** + * Common header for list nodes. + **/ +typedef struct cnode { + struct cnode *next, *prev; +} cnode; + +/** + * Circular doubly linked list. + **/ +typedef struct clist { + struct cnode head; +} clist; + +/** + * Initialize a new circular linked list. Must be called before any other function. + **/ +static inline void clist_init(clist *l) +{ + cnode *head = &l->head; + head->next = head->prev = head; +} + +/** + * Return the first node on \p l or NULL if \p l is empty. + **/ +static inline void *clist_head(clist *l) +{ + return (l->head.next != &l->head) ? l->head.next : NULL; +} + +/** + * Return the last node on \p l or NULL if \p l is empty. + **/ +static inline void *clist_tail(clist *l) +{ + return (l->head.prev != &l->head) ? l->head.prev : NULL; +} + +/** + * Find the next node to \p n or NULL if \p n is the last one. + **/ +static inline void *clist_next(clist *l, cnode *n) +{ + return (n->next != &l->head) ? (void *) n->next : NULL; +} + +/** + * Find the previous node to \p n or NULL if \p n is the first one. + **/ +static inline void *clist_prev(clist *l, cnode *n) +{ + return (n->prev != &l->head) ? (void *) n->prev : NULL; +} + +/** + * Return a non-zero value iff \p l is empty. + **/ +static inline int clist_empty(clist *l) +{ + return (l->head.next == &l->head); +} + +/** + * Loop over all nodes in the \ref list and perform the next C statement on them. The current node is stored in \p n which must be defined before as pointer to any type. + * The list should not be changed during this loop command. + **/ +#define CLIST_WALK(n,list) for(n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next) + +/** + * Same as \ref CLIST_WALK(), but allows removal of the current node. This macro requires one more variable to store some temporary pointers. + **/ +#define CLIST_WALK_DELSAFE(n,list,tmp) for(n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp) + +/** + * Same as \ref CLIST_WALK(), but it defines the variable for the current node in place. \p type should be a pointer type. + **/ +#define CLIST_FOR_EACH(type,n,list) for(type n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next) + +/** + * Same as \ref CLIST_WALK_DELSAFE(), but it defines the variable for the current node in place. \p type should be a pointer type. The temporary variable must be still known before. + **/ +#define CLIST_FOR_EACH_DELSAFE(type,n,list,tmp) for(type n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp) + +/** + * Reversed version of \ref CLIST_FOR_EACH(). + **/ +#define CLIST_FOR_EACH_BACKWARDS(type,n,list) for(type n=(void*)(list).head.prev; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->prev) + +/** + * Insert a new node just after the node \p after. To insert at the head of the list, use \ref clist_add_head() instead. + **/ +static inline void clist_insert_after(cnode *what, cnode *after) +{ + cnode *before = after->next; + what->next = before; + what->prev = after; + before->prev = what; + after->next = what; +} + +/** + * Insert a new node just before the node \p before. To insert at the tail of the list, use \ref clist_add_tail() instead. + **/ +static inline void clist_insert_before(cnode *what, cnode *before) +{ + cnode *after = before->prev; + what->next = before; + what->prev = after; + before->prev = what; + after->next = what; +} + +/** + * Insert a new node in front of all other nodes. + **/ +static inline void clist_add_head(clist *l, cnode *n) +{ + clist_insert_after(n, &l->head); +} + +/** + * Insert a new node after all other nodes. + **/ +static inline void clist_add_tail(clist *l, cnode *n) +{ + clist_insert_before(n, &l->head); +} + +/** + * Remove node \p n. + **/ +static inline void clist_remove(cnode *n) +{ + cnode *before = n->prev; + cnode *after = n->next; + before->next = after; + after->prev = before; +} + +/** + * Remove the first node in \p l, if it exists. Return the pointer to that node or NULL. + **/ +static inline void *clist_remove_head(clist *l) +{ + cnode *n = clist_head(l); + if (n) + clist_remove(n); + return n; +} + +/** + * Remove the last node in \p l, if it exists. Return the pointer to that node or NULL. + **/ +static inline void *clist_remove_tail(clist *l) +{ + cnode *n = clist_tail(l); + if (n) + clist_remove(n); + return n; +} + +/** + * Merge two lists by inserting the list \p what just after the node \p after in a different list. + * The first list is then cleared. + **/ +static inline void clist_insert_list_after(clist *what, cnode *after) +{ + if (!clist_empty(what)) + { + cnode *w = &what->head; + w->prev->next = after->next; + after->next->prev = w->prev; + w->next->prev = after; + after->next = w->next; + clist_init(what); + } +} + +/** + * Move all items from a source list to a destination list. The source list + * becomes empty, the original contents of the destination list are destroyed. + **/ +static inline void clist_move(clist *to, clist *from) +{ + clist_init(to); + clist_insert_list_after(from, &to->head); + clist_init(from); +} + +/** + * Compute the number of nodes in \p l. Beware of linear time complexity. + **/ +static inline unsigned int clist_size(clist *l) +{ + unsigned int i = 0; + CLIST_FOR_EACH(cnode *, n, *l) + i++; + return i; +} + +/** + * Remove a node \p n and mark it as unlinked by setting the previous and next pointers to NULL. + **/ +static inline void clist_unlink(cnode *n) +{ + clist_remove(n); + n->prev = n->next = NULL; +} + +/** + * Remove the first node on \p l and mark it as unlinked. + * Return the pointer to that node or NULL. + **/ +static inline void *clist_unlink_head(clist *l) +{ + cnode *n = clist_head(l); + if (n) + clist_unlink(n); + return n; +} + +/** + * Remove the last node on \p l and mark it as unlinked. + * Return the pointer to that node or NULL. + **/ +static inline void *clist_unlink_tail(clist *l) +{ + cnode *n = clist_tail(l); + if (n) + clist_unlink(n); + return n; +} + +/** + * Check if a node is linked a list. Unlinked nodes are recognized by having their + * previous and next pointers equal to NULL. Returns 0 or 1. + * + * Nodes initialized to all zeroes are unlinked, inserting a node anywhere in a list + * makes it linked. Normal removal functions like \ref clist_remove() do not mark nodes + * as unlinked, you need to call \ref clist_unlink() instead. + **/ +static inline int clist_is_linked(cnode *n) +{ + return !!n->next; +} + +#endif diff --git a/src/libdnssec/nsec.h b/src/libdnssec/nsec.h new file mode 100644 index 0000000..ce69752 --- /dev/null +++ b/src/libdnssec/nsec.h @@ -0,0 +1,214 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup nsec + * + * \brief NSEC bitmap and NSEC3 hash computation API. + * + * The module provides interface for computation of NSEC3 hashes and for + * construction of bit maps used in NSEC and NSEC3 records. + * + * Example of NSEC3 hash computation: + * + * ~~~~~ {.c} + * + * int result; + * + * // NSEC3 parameters for hashing + * nssec_nsec3_params_t params = { + * .algorithm = DNSSEC_NSEC3_ALGORITHM_SHA1, + * .flags = 0, + * .iterations = 10, + * .salt = { + * .size = 4, + * .data = (uint8_t *){ 0xc0, 0x1d, 0xca, 0xfe } + * } + * }; + * + * // domain name (in wire format) + * uint8_t *dname = "\0x08""knot-dns""\0x02""cz"; + * + * // resulting hash + * dnssec_binary_t hash = { 0 }; + * + * result = dnssec_nsec3_hash(&dname, ¶ms, &hash); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * assert(hash.size == 20); + * // hash.data contains binary data, which encoded in Base32 would be: + * // 7PTVGE7QV67EM61ROS9238P5RAKR2DM7 + * + * dnssec_binary_free(&hash); + * + * ~~~~~ + * + * Example of NSEC/NSEC3 bitmap construction. + * + * ~~~~~ {.c} + * + * int result; + * dnssec_nsec_bitmap_t *ctx; + * dnssec_binary_t bitmap; + * + * // create encoding context + * ctx = dnssec_nsec_bitmap_new(); + * if (ctx == NULL) { + * return KNOT_ENOMEM; + * } + * + * // add resource records into the bitmap + * dnssec_nsec_bitmap_add(ctx, 1); // A RR type + * dnssec_nsec_bitmap_add(ctx, 28); // AAAA RR type + * + * // allocate space for the encoded bitmap + * size_t size = dnssec_nsec_bitmap_size(ctx); + * result = dnssec_binary_alloc(&bitmap, size); + * if (result != DNSSEC_EOK) { + * dnssec_nsec_bitmap_free(ctx); + * return result; + * } + * + * // write the encoded bitmap and free the context + * dnssec_nsec_bitmap_write(ctx, &bitmap); + * dnssec_nsec_bitmap_free(ctx); + * + * // use the bitmap ... + * + * dnssec_binary_free(&bitmap); + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include <libdnssec/binary.h> + +/*! + * DNSSEC NSEC3 algorithm numbers. + */ +typedef enum dnssec_nsec_algorithm { + DNSSEC_NSEC3_ALGORITHM_UNKNOWN = 0, + DNSSEC_NSEC3_ALGORITHM_SHA1 = 1, +} dnssec_nsec3_algorithm_t; + +/*! + * DNSSEC NSEC3 parameters. + */ +typedef struct dnssec_nsec3_params { + dnssec_nsec3_algorithm_t algorithm; /*!< NSEC3 algorithm. */ + uint8_t flags; /*!< NSEC3 flags. */ + uint16_t iterations; /*!< NSEC3 iterations count. */ + dnssec_binary_t salt; /*!< NSEC3 salt. */ +} dnssec_nsec3_params_t; + +/*! + * Free NSEC3 parameters. + */ +void dnssec_nsec3_params_free(dnssec_nsec3_params_t *params); + +/*! + * Parse NSEC3 parameters from NSEC3PARAM RDATA. + * + * \param params Output parameters. + * \param rdata NSEC3PARAM RDATA. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_nsec3_params_from_rdata(dnssec_nsec3_params_t *params, + const dnssec_binary_t *rdata); + +/*! + * Check whether a given NSEC bitmap contains a given RR type. + * + * \param bitmap Bitmap of an NSEC record. + * \param size Size of the bitmap. + * \param type RR type to check for. + * + * \return true if bitmap contains type, false otherwise. + */ +bool dnssec_nsec_bitmap_contains(const uint8_t *bitmap, uint16_t size, uint16_t type); + +/*! + * Compute NSEC3 hash for given data. + * + * \todo Input data must be converted to lowercase! + * + * \param[in] data Data to be hashed (usually domain name). + * \param[in] params NSEC3 parameters. + * \param[out] hash Computed hash (will be allocated or resized). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_nsec3_hash(const dnssec_binary_t *data, + const dnssec_nsec3_params_t *params, + dnssec_binary_t *hash); + +/*! + * Get length of raw NSEC3 hash for a given algorithm. + * + * \param algorithm NSEC3 algorithm number. + * + * \return Length of raw NSEC3 hash, zero on error. + */ +size_t dnssec_nsec3_hash_length(dnssec_nsec3_algorithm_t algorithm); + +struct dnssec_nsec_bitmap; + +/*! + * Context for encoding of RR types bitmap used in NSEC/NSEC3. + */ +typedef struct dnssec_nsec_bitmap dnssec_nsec_bitmap_t; + +/*! + * Allocate new bit map encoding context. + */ +dnssec_nsec_bitmap_t *dnssec_nsec_bitmap_new(void); + +/*! + * Clear existing bit map encoding context. + */ +void dnssec_nsec_bitmap_clear(dnssec_nsec_bitmap_t *bitmap); + +/*! + * Free bit map encoding context. + */ +void dnssec_nsec_bitmap_free(dnssec_nsec_bitmap_t *bitmap); + +/*! + * Add one RR type into the bitmap. + */ +void dnssec_nsec_bitmap_add(dnssec_nsec_bitmap_t *bitmap, uint16_t type); + +/*! + * Compute the size of the encoded bitmap. + */ +size_t dnssec_nsec_bitmap_size(const dnssec_nsec_bitmap_t *bitmap); + +/*! + * Write encoded bitmap into the given buffer. + */ +void dnssec_nsec_bitmap_write(const dnssec_nsec_bitmap_t *bitmap, uint8_t *output); + +/*! @} */ diff --git a/src/libdnssec/nsec/bitmap.c b/src/libdnssec/nsec/bitmap.c new file mode 100644 index 0000000..001613a --- /dev/null +++ b/src/libdnssec/nsec/bitmap.c @@ -0,0 +1,142 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <limits.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" + +#define BITMAP_WINDOW_SIZE 256 +#define BITMAP_WINDOW_BYTES (BITMAP_WINDOW_SIZE/CHAR_BIT) +#define BITMAP_WINDOW_COUNT 256 + +/*! + * One window of an NSEC bitmap. + */ +typedef struct window { + uint8_t used; + uint8_t data[BITMAP_WINDOW_BYTES]; +} window_t; + +struct dnssec_nsec_bitmap { + int used; + window_t windows[BITMAP_WINDOW_COUNT]; +}; + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Allocate new bit map encoding context. + */ +_public_ +dnssec_nsec_bitmap_t *dnssec_nsec_bitmap_new(void) +{ + dnssec_nsec_bitmap_t *bitmap = malloc(sizeof(*bitmap)); + if (!bitmap) { + return NULL; + } + + dnssec_nsec_bitmap_clear(bitmap); + + return bitmap; +} + +/*! + * Clear existing bit map encoding context. + */ +_public_ +void dnssec_nsec_bitmap_clear(dnssec_nsec_bitmap_t *bitmap) +{ + clear_struct(bitmap); +} + +/*! + * Free bit map encoding context. + */ +_public_ +void dnssec_nsec_bitmap_free(dnssec_nsec_bitmap_t *bitmap) +{ + free(bitmap); +} + +/*! + * Add one RR type into the bitmap. + */ +_public_ +void dnssec_nsec_bitmap_add(dnssec_nsec_bitmap_t *bitmap, uint16_t type) +{ + int win = type / BITMAP_WINDOW_SIZE; + int bit = type % BITMAP_WINDOW_SIZE; + + if (bitmap->used <= win) { + bitmap->used = win + 1; + } + + int win_byte = bit / CHAR_BIT; + int win_bit = bit % CHAR_BIT; + + window_t *window = &bitmap->windows[win]; + window->data[win_byte] |= 0x80 >> win_bit; + if (window->used <= win_byte) { + window->used = win_byte + 1; + } +} + +/*! + * Compute the size of the encoded bitmap. + */ +_public_ +size_t dnssec_nsec_bitmap_size(const dnssec_nsec_bitmap_t *bitmap) +{ + size_t result = 0; + + for (int i = 0; i < bitmap->used; i++) { + int used = bitmap->windows[i].used; + if (used == 0) { + continue; + } + + result += 2 + used; // windows number, window size, data + } + + return result; +} + +/*! + * Write encoded bitmap into the given buffer. + */ +_public_ +void dnssec_nsec_bitmap_write(const dnssec_nsec_bitmap_t *bitmap, uint8_t *output) +{ + uint8_t *write_ptr = output; + for (int win = 0; win < bitmap->used; win++) { + int used = bitmap->windows[win].used; + if (used == 0) { + continue; + } + + *write_ptr = (uint8_t)win; + write_ptr += 1; + + *write_ptr = (uint8_t)used; + write_ptr += 1; + + memmove(write_ptr, bitmap->windows[win].data, used); + write_ptr += used; + } +} diff --git a/src/libdnssec/nsec/hash.c b/src/libdnssec/nsec/hash.c new file mode 100644 index 0000000..897320f --- /dev/null +++ b/src/libdnssec/nsec/hash.c @@ -0,0 +1,125 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" + +/*! + * Compute NSEC3 hash for given data and algorithm. + * + * \see RFC 5155 + * + * \todo Input data should be converted to lowercase. + */ +static int nsec3_hash(gnutls_digest_algorithm_t algorithm, int iterations, + const dnssec_binary_t *salt, const dnssec_binary_t *data, + dnssec_binary_t *hash) +{ + assert(salt); + assert(data); + assert(hash); + + int hash_size = gnutls_hash_get_len(algorithm); + if (hash_size <= 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + int result = dnssec_binary_resize(hash, hash_size); + if (result != DNSSEC_EOK) { + return result; + } + + _cleanup_hash_ gnutls_hash_hd_t digest = NULL; + result = gnutls_hash_init(&digest, algorithm); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + const uint8_t *in = data->data; + size_t in_size = data->size; + + for (int i = 0; i <= iterations; i++) { + result = gnutls_hash(digest, in, in_size); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + result = gnutls_hash(digest, salt->data, salt->size); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + gnutls_hash_output(digest, hash->data); + + in = hash->data; + in_size = hash->size; + } + + return DNSSEC_EOK; +} + +/*! + * Get GnuTLS digest algorithm from DNSSEC algorithm number. + */ +static gnutls_digest_algorithm_t algorithm_d2g(dnssec_nsec3_algorithm_t dnssec) +{ + switch (dnssec) { + case DNSSEC_NSEC3_ALGORITHM_SHA1: return GNUTLS_DIG_SHA1; + default: return GNUTLS_DIG_UNKNOWN; + } +} + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Compute NSEC3 hash for given data. + */ +_public_ +int dnssec_nsec3_hash(const dnssec_binary_t *data, + const dnssec_nsec3_params_t *params, + dnssec_binary_t *hash) +{ + if (!data || !params || !hash) { + return DNSSEC_EINVAL; + } + + gnutls_digest_algorithm_t algorithm = algorithm_d2g(params->algorithm); + if (algorithm == GNUTLS_DIG_UNKNOWN) { + return DNSSEC_INVALID_NSEC3_ALGORITHM; + } + + return nsec3_hash(algorithm, params->iterations, ¶ms->salt, data, hash); +} + +/*! + * Get length of raw NSEC3 hash for a given algorithm. + */ +_public_ +size_t dnssec_nsec3_hash_length(dnssec_nsec3_algorithm_t algorithm) +{ + gnutls_digest_algorithm_t gnutls = algorithm_d2g(algorithm); + if (gnutls == GNUTLS_DIG_UNKNOWN) { + return 0; + } + + return gnutls_hash_get_len(gnutls); +} diff --git a/src/libdnssec/nsec/nsec.c b/src/libdnssec/nsec/nsec.c new file mode 100644 index 0000000..bb6084a --- /dev/null +++ b/src/libdnssec/nsec/nsec.c @@ -0,0 +1,116 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +/*! + * Free NSEC3 parameters. + */ +_public_ +void dnssec_nsec3_params_free(dnssec_nsec3_params_t *params) +{ + if (!params) { + return; + } + + dnssec_binary_free(¶ms->salt); + clear_struct(params); +} + +/*! + * Parse NSEC3 parameters from NSEC3PARAM RDATA. + * + * \see RFC 5155 (section 4.2) + */ +_public_ +int dnssec_nsec3_params_from_rdata(dnssec_nsec3_params_t *params, + const dnssec_binary_t *rdata) +{ + if (!params || !rdata || !rdata->data) { + return DNSSEC_EINVAL; + } + + dnssec_nsec3_params_t new_params = { 0 }; + + wire_ctx_t wire = binary_init(rdata); + + if (wire_ctx_available(&wire) < 5) { + return DNSSEC_MALFORMED_DATA; + } + + new_params.algorithm = wire_ctx_read_u8(&wire); + new_params.flags = wire_ctx_read_u8(&wire); + new_params.iterations = wire_ctx_read_u16(&wire); + new_params.salt.size = wire_ctx_read_u8(&wire); + + if (wire_ctx_available(&wire) != new_params.salt.size) { + return DNSSEC_MALFORMED_DATA; + } + + new_params.salt.data = malloc(new_params.salt.size); + if (new_params.salt.data == NULL) { + return DNSSEC_ENOMEM; + } + + binary_read(&wire, &new_params.salt); + assert(wire_ctx_offset(&wire) == rdata->size); + + *params = new_params; + + return DNSSEC_EOK; +} + +_public_ +bool dnssec_nsec_bitmap_contains(const uint8_t *bitmap, uint16_t size, uint16_t type) +{ + if (!bitmap || size == 0) { + return false; + } + + const uint8_t type_hi = (type >> 8); // Which window block contains type. + const uint8_t type_lo = (type & 0xff); + const uint8_t bitmap_idx = (type_lo >> 3); // Which byte in the window block contains type. + const uint8_t bit_mask = 1 << (7 - (type_lo & 0x07)); // Which bit in the byte represents type. + + size_t bitmap_pos = 0; + while (bitmap_pos + 3 <= size) { + uint8_t block_idx = bitmap[bitmap_pos++]; // Skip window block No. + uint8_t block_size = bitmap[bitmap_pos++]; // Skip window block size. + + // Size checks. + if (block_size == 0 || bitmap_pos + block_size > size) { + return false; + } + + // Check whether we found the correct window block. + if (block_idx == type_hi) { + if (bitmap_idx < block_size) { + // Check if the bit for type is set. + return bitmap[bitmap_pos + bitmap_idx] & bit_mask; + } + return false; + } else { + bitmap_pos += block_size; + } + } + + return false; +} diff --git a/src/libdnssec/p11/p11.c b/src/libdnssec/p11/p11.c new file mode 100644 index 0000000..07f34f2 --- /dev/null +++ b/src/libdnssec/p11/p11.c @@ -0,0 +1,113 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <gnutls/pkcs11.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/p11/p11.h" +#include "libdnssec/error.h" + +#ifdef ENABLE_PKCS11 + +#define PKCS11_MODULES_MAX 16 + +static char *pkcs11_modules[PKCS11_MODULES_MAX] = { 0 }; +static int pkcs11_modules_count = 0; + +static int map_result(int gnutls_result) +{ + return gnutls_result == GNUTLS_E_SUCCESS ? DNSSEC_EOK : DNSSEC_ERROR; +} + +int p11_init(void) +{ + int r = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); + return map_result(r); +} + +int p11_reinit(void) +{ + int r = gnutls_pkcs11_reinit(); + return map_result(r); +} + +int p11_load_module(const char *module) +{ + for (int i = 0; i < pkcs11_modules_count; i++) { + if (strcmp(pkcs11_modules[i], module) == 0) { + return DNSSEC_EOK; + } + } + + assert(pkcs11_modules_count <= PKCS11_MODULES_MAX); + if (pkcs11_modules_count == PKCS11_MODULES_MAX) { + return DNSSEC_P11_TOO_MANY_MODULES; + } + + char *copy = strdup(module); + if (!copy) { + return DNSSEC_ENOMEM; + } + + int r = gnutls_pkcs11_add_provider(module, NULL); + if (r != GNUTLS_E_SUCCESS) { + free(copy); + return DNSSEC_P11_FAILED_TO_LOAD_MODULE; + } + + pkcs11_modules[pkcs11_modules_count] = copy; + pkcs11_modules_count += 1; + + return DNSSEC_EOK; +} + +void p11_cleanup(void) +{ + for (int i = 0; i < pkcs11_modules_count; i++) { + free(pkcs11_modules[i]); + pkcs11_modules[i] = NULL; + } + + pkcs11_modules_count = 0; + + gnutls_pkcs11_deinit(); +} + +#else + +int p11_init(void) +{ + return DNSSEC_EOK; +} + +int p11_reinit(void) +{ + return DNSSEC_EOK; +} + +int p11_load_module(const char *module) +{ + return DNSSEC_NOT_IMPLEMENTED_ERROR; +} + +void p11_cleanup(void) +{ + // this function intentionally left blank +} + +#endif diff --git a/src/libdnssec/p11/p11.h b/src/libdnssec/p11/p11.h new file mode 100644 index 0000000..a0f9969 --- /dev/null +++ b/src/libdnssec/p11/p11.h @@ -0,0 +1,41 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +/*! + * Initialize PKCS11 global context. + */ +int p11_init(void); + +/*! + * Reinitialize PKCS11 global context after fork(). + */ +int p11_reinit(void); + +/*! + * Load PKCS11 module unless the module was already loaded. + * + * Duplicates are detected based on the module path. + */ +int p11_load_module(const char *name); + +/*! + * Clenaup PKCS11 global context. + * + * Should be called when the library is deinitialized to prevent memory leaks. + */ +void p11_cleanup(void); diff --git a/src/libdnssec/random.c b/src/libdnssec/random.c new file mode 100644 index 0000000..30b0c2d --- /dev/null +++ b/src/libdnssec/random.c @@ -0,0 +1,53 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <stddef.h> +#include <stdint.h> + +#include "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libdnssec/shared/shared.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_random_buffer(uint8_t *data, size_t size) +{ + if (!data) { + return DNSSEC_EINVAL; + } + + int result = gnutls_rnd(GNUTLS_RND_RANDOM, data, size); + if (result != 0) { + assert_unreachable(); + return DNSSEC_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_random_binary(dnssec_binary_t *binary) +{ + if (!binary) { + return DNSSEC_EINVAL; + } + + return dnssec_random_buffer(binary->data, binary->size); +} diff --git a/src/libdnssec/random.h b/src/libdnssec/random.h new file mode 100644 index 0000000..0886e51 --- /dev/null +++ b/src/libdnssec/random.h @@ -0,0 +1,85 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup random + * + * \brief Pseudo-random number generating API. + * + * The module provides generating of pseudo-random numbers and buffers. + * + * Example: + * + * ~~~ + * + * uint16_t transaction_id = dnssec_random_uint16_t(); + * + * ~~~ + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <libdnssec/binary.h> + +/*! + * Fill a buffer with pseudo-random data. + * + * \param data Pointer to the output buffer. + * \param size Size of the output buffer. + * + * \return Error code, DNSEC_EOK if successful. + */ +int dnssec_random_buffer(uint8_t *data, size_t size); + +/*! + * Fill a binary structure with random data. + * + * \param data Preallocated binary structure to be filled. + * + * \return Error code, DNSEC_EOK if successful. + */ +int dnssec_random_binary(dnssec_binary_t *data); + +/*! + * Declare function dnssec_random_<type>(). + */ +#define dnssec_register_random_type(type) \ + static inline type dnssec_random_##type(void) { \ + type value; \ + dnssec_random_buffer((uint8_t *)&value, sizeof(value)); \ + return value; \ + } + +/*! + * Generate pseudo-random 16-bit number. + */ +static inline uint16_t dnssec_random_uint16_t(void); + +/*! + * Generate pseudo-random 32-bit number. + */ +static inline uint32_t dnssec_random_uint32_t(void); + +/*! \cond */ +dnssec_register_random_type(uint16_t); +dnssec_register_random_type(uint32_t); +/*! \endcond */ + +/*! @} */ diff --git a/src/libdnssec/shared/bignum.c b/src/libdnssec/shared/bignum.c new file mode 100644 index 0000000..921a1e6 --- /dev/null +++ b/src/libdnssec/shared/bignum.c @@ -0,0 +1,64 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <string.h> + +#include "libdnssec/shared/bignum.h" + +static void skip_leading_zeroes(dnssec_binary_t *value) +{ + while (value->size > 0 && value->data[0] == 0) { + value->data += 1; + value->size -= 1; + } +} + +size_t bignum_size_u(const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + if (value.size == 0) { + return value.size + 1; + } else { + return value.size; + } +} + +size_t bignum_size_s(const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + if (value.size == 0 || value.data[0] & 0x80) { + return value.size + 1; + } else { + return value.size; + } +} + +void bignum_write(wire_ctx_t *ctx, size_t width, const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + size_t padding_len = width - value.size; + if (padding_len > 0) { + wire_ctx_clear(ctx, padding_len); + } + wire_ctx_write(ctx, value.data, value.size); +} diff --git a/src/libdnssec/shared/bignum.h b/src/libdnssec/shared/bignum.h new file mode 100644 index 0000000..4186a63 --- /dev/null +++ b/src/libdnssec/shared/bignum.h @@ -0,0 +1,41 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdlib.h> + +#include "libdnssec/binary.h" +#include "contrib/wire_ctx.h" + +/*! + * Size needed to write unsigned number in unsigned encoding. + */ +size_t bignum_size_u(const dnssec_binary_t *value); + +/*! + * Size needed to write unsigned number in signed encoding. + * + * Signed encoding expects the MSB to be zero. + */ +size_t bignum_size_s(const dnssec_binary_t *value); + +/*! + * Write unsigned number on a fixed width in a big-endian byte order. + * + * The destination size has to be set properly to accommodate used encoding. + */ +void bignum_write(wire_ctx_t *ctx, size_t width, const dnssec_binary_t *value); diff --git a/src/libdnssec/shared/binary_wire.h b/src/libdnssec/shared/binary_wire.h new file mode 100644 index 0000000..78ccff0 --- /dev/null +++ b/src/libdnssec/shared/binary_wire.h @@ -0,0 +1,53 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdlib.h> + +#include "contrib/wire_ctx.h" +#include "libdnssec/binary.h" + +static inline wire_ctx_t binary_init(const dnssec_binary_t *binary) +{ + assert(binary); + + return wire_ctx_init(binary->data, binary->size); +} + +static inline void binary_read(wire_ctx_t *ctx, dnssec_binary_t *data) +{ + assert(data); + + wire_ctx_read(ctx, data->data, data->size); +} + +static inline void binary_available(wire_ctx_t *ctx, dnssec_binary_t *data) +{ + assert(ctx); + assert(data); + + data->data = ctx->position; + data->size = wire_ctx_available(ctx); +} + +static inline void binary_write(wire_ctx_t *ctx, const dnssec_binary_t *data) +{ + assert(ctx); + assert(data); + + wire_ctx_write(ctx, data->data, data->size); +} diff --git a/src/libdnssec/shared/dname.c b/src/libdnssec/shared/dname.c new file mode 100644 index 0000000..51605f6 --- /dev/null +++ b/src/libdnssec/shared/dname.c @@ -0,0 +1,165 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/shared/dname.h" +#include "libdnssec/shared/shared.h" +#include "contrib/tolower.h" + +/*! + * Get length of a domain name in wire format. + */ +size_t dname_length(const uint8_t *dname) +{ + if (!dname) { + return 0; + } + + const uint8_t *scan = dname; + uint8_t label_len; + do { + label_len = *scan; + scan += 1 + label_len; + } while (label_len > 0); + assert(scan > dname); + + size_t length = scan - dname; + if (length > DNAME_MAX_LENGTH) { + return 0; + } + + return length; +} + +/*! + * Copy domain name in wire format. + */ +uint8_t *dname_copy(const uint8_t *dname) +{ + if (!dname) { + return NULL; + } + + size_t length = dname_length(dname); + if (length == 0) { + return NULL; + } + + uint8_t *copy = malloc(length); + if (!copy) { + return NULL; + } + + memmove(copy, dname, length); + return copy; +} + +/*! + * Normalize dname label in-place. + * + * \return Number of processed bytes, 0 if we encounter the last label. + */ +static uint8_t normalize_label(uint8_t *label) +{ + assert(label); + + uint8_t len = *label; + if (len == 0 || len > DNAME_MAX_LABEL_LENGTH) { + return 0; + } + + for (uint8_t *scan = label + 1, *end = scan + len; scan < end; scan++) { + *scan = knot_tolower(*scan); + } + + return len + 1; +} + +/*! + * Normalize domain name in wire format. + */ +void dname_normalize(uint8_t *dname) +{ + if (!dname) { + return; + } + + uint8_t read, *scan = dname; + do { + read = normalize_label(scan); + scan += read; + } while (read > 0); +} + +/*! + * Compare dname labels case insensitively. + */ +static int label_casecmp(const uint8_t *a, const uint8_t *b, uint8_t len) +{ + assert(a); + assert(b); + + for (const uint8_t *a_end = a + len; a < a_end; a++, b++) { + if (knot_tolower(*a) != knot_tolower(*b)) { + return false; + } + } + + return true; +} + +/*! + * Check if two dnames are equal. + */ +bool dname_equal(const uint8_t *one, const uint8_t *two) +{ + if (!one || !two) { + return false; + } + + const uint8_t *scan_one = one; + const uint8_t *scan_two = two; + + for (;;) { + if (*scan_one != *scan_two) { + return false; + } + + uint8_t len = *scan_one; + if (len == 0) { + return true; + } else if (len > DNAME_MAX_LABEL_LENGTH) { + return false; + } + + scan_one += 1; + scan_two += 1; + + if (!label_casecmp(scan_one, scan_two, len)) { + return false; + } + + scan_one += len; + scan_two += len; + } + + return true; +} diff --git a/src/libdnssec/shared/dname.h b/src/libdnssec/shared/dname.h new file mode 100644 index 0000000..82adeb7 --- /dev/null +++ b/src/libdnssec/shared/dname.h @@ -0,0 +1,57 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +/*! + * Maximal length of domain name including labels and length bytes. + * \see RFC 1035 + */ +#define DNAME_MAX_LENGTH 255 + +/*! + * Maximal length of the domain name label, excluding the label size. + * \see RFC 1035 + */ +#define DNAME_MAX_LABEL_LENGTH 63 + +/*! + * Get length of a domain name in wire format. + */ +size_t dname_length(const uint8_t *dname); + +/*! + * Copy domain name in wire format. + */ +uint8_t *dname_copy(const uint8_t *dname); + +/*! + * Normalize domain name in wire format. + * + * Currently converts all letters to lowercase. + */ +void dname_normalize(uint8_t *dname); + +/*! + * Check if two dnames are equal. + * + * Case insensitive. + */ +bool dname_equal(const uint8_t *one, const uint8_t *two); diff --git a/src/libdnssec/shared/fs.c b/src/libdnssec/shared/fs.c new file mode 100644 index 0000000..10c25d9 --- /dev/null +++ b/src/libdnssec/shared/fs.c @@ -0,0 +1,47 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "libdnssec/error.h" + +int fs_mkdir(const char *path, mode_t mode, bool ignore_existing) +{ + if (mkdir(path, mode) == 0) { + return DNSSEC_EOK; + } + + if (!ignore_existing || errno != EEXIST) { + return dnssec_errno_to_error(errno); + } + + assert(errno == EEXIST); + + struct stat st = { 0 }; + if (stat(path, &st) != 0) { + return dnssec_errno_to_error(errno); + } + + if (!S_ISDIR(st.st_mode)) { + return dnssec_errno_to_error(ENOTDIR); + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/shared/fs.h b/src/libdnssec/shared/fs.h new file mode 100644 index 0000000..542c87b --- /dev/null +++ b/src/libdnssec/shared/fs.h @@ -0,0 +1,25 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdbool.h> +#include <sys/types.h> + +/*! + * Equivalent to mkdir(2), can succeed if the directory already exists. + */ +int fs_mkdir(const char *path, mode_t mode, bool ignore_existing); diff --git a/src/libdnssec/shared/hex.c b/src/libdnssec/shared/hex.c new file mode 100644 index 0000000..03c1491 --- /dev/null +++ b/src/libdnssec/shared/hex.c @@ -0,0 +1,167 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <string.h> +#include <stdbool.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +#include "contrib/ctype.h" + +/* -- binary to hex -------------------------------------------------------- */ + +static const char BIN_TO_HEX[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +int bin_to_hex_static(const dnssec_binary_t *bin, dnssec_binary_t *hex) +{ + if (!bin || !hex) { + return DNSSEC_EINVAL; + } + + if (bin->size * 2 != hex->size) { + return DNSSEC_EINVAL; + } + + for (size_t i = 0; i < bin->size; i++) { + hex->data[2*i] = BIN_TO_HEX[bin->data[i] >> 4]; + hex->data[2*i+1] = BIN_TO_HEX[bin->data[i] & 0x0f]; + } + + return DNSSEC_EOK; +} + +int bin_to_hex(const dnssec_binary_t *bin, char **hex_ptr) +{ + if (!bin || !hex_ptr) { + return DNSSEC_EINVAL; + } + + size_t hex_size = bin->size * 2; + char *hex = malloc(hex_size + 1); + if (!hex) { + return DNSSEC_ENOMEM; + } + + dnssec_binary_t hex_bin = { .data = (uint8_t *)hex, .size = hex_size }; + bin_to_hex_static(bin, &hex_bin); + hex[hex_size] = '\0'; + + *hex_ptr = hex; + + return DNSSEC_EOK; +} + +/* -- hex to binary -------------------------------------------------------- */ + +/*! + * Convert HEX character to numeric value (assumes valid and lowercase input). + */ +static uint8_t hex_to_number(const char hex) +{ + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } else { + assert(hex >= 'A' && hex <= 'F'); + return hex - 'A' + 10; + } +} + +/*! + * Check if the input string has valid size and contains valid characters. + */ +static bool hex_valid_input(const dnssec_binary_t *hex) +{ + assert(hex); + + if (hex->size % 2 != 0) { + return false; + } + + for (int i = 0; i < hex->size; i++) { + if (!is_xdigit(hex->data[i])) { + return false; + } + } + + return true; +} + +/*! + * Perform hex to bin conversion without checking the validity. + */ +static void hex_to_bin_convert(const dnssec_binary_t *hex, dnssec_binary_t *bin) +{ + assert(hex); + assert(bin); + + for (size_t i = 0; i < bin->size; i++) { + uint8_t high = hex_to_number(hex->data[2 * i]); + uint8_t low = hex_to_number(hex->data[2 * i + 1]); + bin->data[i] = high << 4 | low; + } +} + +int hex_to_bin_static(const dnssec_binary_t *hex, dnssec_binary_t *bin) +{ + if (!hex || !bin) { + return DNSSEC_EINVAL; + } + + if (hex->size / 2 != bin->size) { + return DNSSEC_EINVAL; + } + + if (!hex_valid_input(hex)) { + return DNSSEC_MALFORMED_DATA; + } + + hex_to_bin_convert(hex, bin); + + return DNSSEC_EOK; +} + +int hex_to_bin(const char *hex_str, dnssec_binary_t *bin) +{ + if (!hex_str || !bin) { + return DNSSEC_EINVAL; + } + + dnssec_binary_t hex = { .data = (uint8_t *)hex_str, .size = strlen(hex_str) }; + if (!hex_valid_input(&hex)) { + return DNSSEC_MALFORMED_DATA; + } + + size_t bin_size = hex.size / 2; + if (bin_size == 0) { + bin->size = 0; + bin->data = NULL; + return DNSSEC_EOK; + } + + int result = dnssec_binary_alloc(bin, bin_size); + if (result != DNSSEC_EOK) { + return result; + } + + hex_to_bin_static(&hex, bin); + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/shared/hex.h b/src/libdnssec/shared/hex.h new file mode 100644 index 0000000..5a47cf4 --- /dev/null +++ b/src/libdnssec/shared/hex.h @@ -0,0 +1,39 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "libdnssec/binary.h" + +/*! + * Convert binary data to preallocated hexadecimal string. + */ +int bin_to_hex_static(const dnssec_binary_t *bin, dnssec_binary_t *hex); + +/** + * Convert binary data to hexadecimal string. + */ +int bin_to_hex(const dnssec_binary_t *bin, char **hex_ptr); + +/*! + * Convert hex encoded string to preallocated binary data. + */ +int hex_to_bin_static(const dnssec_binary_t *hex, dnssec_binary_t *bin); + +/*! + * Convert hex encoded string to binary data. + */ +int hex_to_bin(const char *hex, dnssec_binary_t *bin); diff --git a/src/libdnssec/shared/keyid_gnutls.c b/src/libdnssec/shared/keyid_gnutls.c new file mode 100644 index 0000000..4fde08e --- /dev/null +++ b/src/libdnssec/shared/keyid_gnutls.c @@ -0,0 +1,94 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/hex.h" + +/*! + * Get binary key ID from a key (public or private). + */ +static int keyid_bin(gnutls_x509_privkey_t key, gnutls_pubkey_t pubkey, dnssec_binary_t *id) +{ + assert(key || pubkey); + assert(id); + + // Flags can be used to enable SHA-2 since GnuTLS 3.4.7. + + int flags = 0; + uint8_t *buffer = alloca(DNSSEC_KEYID_BINARY_SIZE); + size_t size = DNSSEC_KEYID_SIZE; + + int r = key ? gnutls_x509_privkey_get_key_id(key, flags, buffer, &size) + : gnutls_pubkey_get_key_id(pubkey, flags, buffer, &size); + + if (r != GNUTLS_E_SUCCESS || size != DNSSEC_KEYID_BINARY_SIZE) { + return DNSSEC_INVALID_KEY_ID; + } + + assert(size == DNSSEC_KEYID_BINARY_SIZE); + r = dnssec_binary_resize(id, size); + if (r != DNSSEC_EOK) { + return r; + } + + memcpy(id->data, buffer, size); + + return DNSSEC_EOK; +} + +/*! + * Get hexadecimal key ID from a key (public or private). + */ +static int keyid_hex(gnutls_x509_privkey_t key, gnutls_pubkey_t pubkey, char **id) +{ + _cleanup_binary_ dnssec_binary_t bin = { 0 }; + int r = keyid_bin(key, pubkey, &bin); + if (r != DNSSEC_EOK) { + return r; + } + + return bin_to_hex(&bin, id); +} + +int keyid_x509(gnutls_x509_privkey_t key, dnssec_binary_t *id) +{ + return keyid_bin(key, NULL, id); +} + +int keyid_x509_hex(gnutls_x509_privkey_t key, char **id) +{ + return keyid_hex(key, NULL, id); +} + +int keyid_pubkey(gnutls_pubkey_t pubkey, dnssec_binary_t *id) +{ + return keyid_bin(NULL, pubkey, id); +} + +int keyid_pubkey_hex(gnutls_pubkey_t pubkey, char **id) +{ + return keyid_hex(NULL, pubkey, id); +} diff --git a/src/libdnssec/shared/keyid_gnutls.h b/src/libdnssec/shared/keyid_gnutls.h new file mode 100644 index 0000000..27ee4cd --- /dev/null +++ b/src/libdnssec/shared/keyid_gnutls.h @@ -0,0 +1,30 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" + +int keyid_x509(gnutls_x509_privkey_t key, dnssec_binary_t *id); + +int keyid_x509_hex(gnutls_x509_privkey_t key, char **id); + +int keyid_pubkey(gnutls_pubkey_t pubkey, dnssec_binary_t *id); + +int keyid_pubkey_hex(gnutls_pubkey_t pubkey, char **id); diff --git a/src/libdnssec/shared/pem.c b/src/libdnssec/shared/pem.c new file mode 100644 index 0000000..0e5ba00 --- /dev/null +++ b/src/libdnssec/shared/pem.c @@ -0,0 +1,198 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Create GnuTLS X.509 private key from unencrypted PEM data. + */ +int pem_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key) +{ + assert(pem); + assert(key); + + gnutls_datum_t data = binary_to_datum(pem); + + gnutls_x509_privkey_t _key = NULL; + int r = gnutls_x509_privkey_init(&_key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + int format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + r = gnutls_x509_privkey_import_pkcs8(_key, &data, format, password, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(_key); + return DNSSEC_PKCS8_IMPORT_ERROR; + } + + *key = _key; + + return DNSSEC_EOK; +} + +/*! + * Create GnuTLS private key from unencrypted PEM data. + */ +int pem_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key) +{ + assert(pem); + assert(key); + + gnutls_x509_privkey_t key_x509 = NULL; + int r = pem_x509(pem, &key_x509); + if (r != DNSSEC_EOK) { + return r; + } + + gnutls_privkey_t key_abs = NULL; + r = gnutls_privkey_init(&key_abs); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + return DNSSEC_ENOMEM; + } + + int flags = GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE; + r = gnutls_privkey_import_x509(key_abs, key_x509, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + gnutls_privkey_deinit(key_abs); + return DNSSEC_ENOMEM; + } + + *key = key_abs; + + return DNSSEC_EOK; +} + +/*! + * Generate new key and export it in the PEM format. + */ +int pem_generate(gnutls_pk_algorithm_t algorithm, unsigned bits, + dnssec_binary_t *pem, char **id) +{ + assert(pem); + assert(id); + + // generate key + + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int r = gnutls_x509_privkey_init(&key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + r = gnutls_x509_privkey_generate(key, algorithm, bits, 0); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_GENERATE_ERROR; + } + + // convert to PEM and export the ID + + dnssec_binary_t _pem = { 0 }; + r = pem_from_x509(key, &_pem); + if (r != DNSSEC_EOK) { + return r; + } + + // export key ID + + char *_id = NULL; + r = keyid_x509_hex(key, &_id); + if (r != DNSSEC_EOK) { + dnssec_binary_free(&_pem); + return r; + } + + *id = _id; + *pem = _pem; + + return DNSSEC_EOK; +} + +static int try_export_pem(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + assert(key); + + gnutls_x509_crt_fmt_t format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + + return gnutls_x509_privkey_export_pkcs8(key, format, password, flags, + pem->data, &pem->size); +} + +/*! + * Export GnuTLS X.509 private key to PEM binary. + */ +int pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + assert(key); + assert(pem); + + dnssec_binary_t _pem = { 0 }; + int r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SHORT_MEMORY_BUFFER || _pem.size == 0) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + r = dnssec_binary_alloc(&_pem, _pem.size); + if (r != DNSSEC_EOK) { + return r; + } + + r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SUCCESS) { + dnssec_binary_free(&_pem); + return DNSSEC_KEY_EXPORT_ERROR; + } + + *pem = _pem; + + return DNSSEC_EOK; +} + +/*! + * Get key ID of a private key in PEM format. + */ +int pem_keyid(const dnssec_binary_t *pem, char **id) +{ + assert(pem && pem->size > 0 && pem->data); + assert(id); + + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int r = pem_x509(pem, &key); + if (r != DNSSEC_EOK) { + return r; + } + + return keyid_x509_hex(key, id); +} diff --git a/src/libdnssec/shared/pem.h b/src/libdnssec/shared/pem.h new file mode 100644 index 0000000..c96065b --- /dev/null +++ b/src/libdnssec/shared/pem.h @@ -0,0 +1,74 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" + +/*! + * Create GnuTLS X.509 private key from unencrypted PEM data. + * + * \param[in] pem PEM binary data. + * \param[out] key Resulting private key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int pem_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key); + +/*! + * Create GnuTLS private key from unencrypted PEM data. + * + * \param[in] pem PEM binary data. + * \param[out] key Resulting private key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int pem_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key); + +/*! + * Generate a private key and export it in the PEM format. + * + * \param[in] algorithm Algorithm to be used. + * \param[in] bits Size of the key to be generated. + * \param[out] pem Generated key in unencrypted PEM format. + * \param[out] id Key ID of the generated key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int pem_generate(gnutls_pk_algorithm_t algorithm, unsigned bits, + dnssec_binary_t *pem, char **id); + +/*! + * Export GnuTLS X.509 private key to PEM binary. + * + * \param[in] key Key to be exported. + * \param[out] pem Generated key in unencrypted PEM format. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem); + +/*! + * Get key ID of a private key in PEM format. + * + * \param[in] pem Key in unencrypted PEM format. + * \param[out] id ID of the key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int pem_keyid(const dnssec_binary_t *pem, char **id); diff --git a/src/libdnssec/shared/shared.h b/src/libdnssec/shared/shared.h new file mode 100644 index 0000000..1f55033 --- /dev/null +++ b/src/libdnssec/shared/shared.h @@ -0,0 +1,129 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <assert.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <gnutls/abstract.h> +#include <gnutls/crypto.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "libdnssec/binary.h" + +#define _public_ __attribute__((visibility("default"))) +#define _hidden_ __attribute__((visibility("hidden"))) + +#define _unused_ __attribute__((unused)) + +/** + * Macro to clear a structure of known size. + * + * \param pointer Pointer to the structure. + */ +#define clear_struct(pointer) memset((pointer), '\0', sizeof(*(pointer))) + +#define streq(one, two) (strcmp((one), (two)) == 0) + +/* -- cleanup macros ------------------------------------------------------- */ + +#define _cleanup_(var) __attribute__((cleanup(var))) + +static inline void free_ptr(void *ptr) +{ + free(*(void **)ptr); +} + +static inline void close_ptr(int *ptr) +{ + if (*ptr != -1) { + close(*ptr); + } +} + +static inline void fclose_ptr(FILE **ptr) +{ + if (*ptr) { + fclose(*ptr); + } +} + +static inline void closedir_ptr(DIR **ptr) +{ + if (*ptr) { + closedir(*ptr); + } +} + +static inline void free_gnutls_datum_ptr(gnutls_datum_t *ptr) +{ + gnutls_free(ptr->data); +} + +static inline void free_x509_privkey_ptr(gnutls_x509_privkey_t *ptr) +{ + if (*ptr) { + gnutls_x509_privkey_deinit(*ptr); + } +} + +static inline void free_pubkey_ptr(gnutls_pubkey_t *ptr) +{ + if (*ptr) { + gnutls_pubkey_deinit(*ptr); + } +} + +static inline void free_gnutls_hash_ptr(gnutls_hash_hd_t *ptr) +{ + if (*ptr) { + gnutls_hash_deinit(*ptr, NULL); + } +} + +#define _cleanup_free_ _cleanup_(free_ptr) +#define _cleanup_close_ _cleanup_(close_ptr) +#define _cleanup_fclose_ _cleanup_(fclose_ptr) +#define _cleanup_closedir_ _cleanup_(closedir_ptr) +#define _cleanup_binary_ _cleanup_(dnssec_binary_free) +#define _cleanup_datum_ _cleanup_(free_gnutls_datum_ptr) +#define _cleanup_x509_privkey_ _cleanup_(free_x509_privkey_ptr) +#define _cleanup_pubkey_ _cleanup_(free_pubkey_ptr) +#define _cleanup_hash_ _cleanup_(free_gnutls_hash_ptr) + +/* -- assertions ----------------------------------------------------------- */ + +#define assert_unreachable() assert(0) + +/* -- crypto helpers ------------------------------------------------------- */ + +static inline gnutls_datum_t binary_to_datum(const dnssec_binary_t *from) +{ + gnutls_datum_t to = { .size = from->size, .data = from->data }; + return to; +} + +static inline dnssec_binary_t binary_from_datum(const gnutls_datum_t *from) +{ + dnssec_binary_t to = { .size = from->size, .data = from->data }; + return to; +} diff --git a/src/libdnssec/sign.h b/src/libdnssec/sign.h new file mode 100644 index 0000000..4313533 --- /dev/null +++ b/src/libdnssec/sign.h @@ -0,0 +1,137 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup sign + * + * \brief DNSSEC signing API + * + * The module provides the low level DNSSEC signing and verification. + * + * Example of signature validation: + * + * ~~~~~ {.c} + * + * dnssec_key_t *dnskey = // ... ; + * dnssec_binary_t *rrsig_header = // ... ; + * dnssec_binary_t *covered_rdata = // ... ; + * dnssec_binary_t *signature = // ... ; + * + * int result; + * dnssec_sign_ctx_t *ctx = NULL; + * + * result = dnssec_sign_new(&ctx, dnskey); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * dnssec_sign_add(ctx, rrsig_header); + * dnssec_sign_add(ctx, covered_rdata); + * + * result = dnssec_sign_verify(ctx, signature); + * if (result == DNSSEC_EOK) { + * // valid signature + * } else if (result == DNSSEC_INVALID_SIGNATURE) { + * // invalid signature + * } else { + * // error + * } + * + * dnssec_sign_free(ctx); + * + * ~~~~~ + * + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdlib.h> + +#include <libdnssec/binary.h> +#include <libdnssec/key.h> + +struct dnssec_sign_ctx; + +/*! + * DNSSEC signing context. + */ +typedef struct dnssec_sign_ctx dnssec_sign_ctx_t; + +/*! + * Create new DNSSEC signing context. + * + * \note \ref dnssec_sign_init is called as a part of this function. + * + * \param ctx_ptr Pointer to context to be allocated. + * \param key DNSSEC key to be used. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_new(dnssec_sign_ctx_t **ctx_ptr, const dnssec_key_t *key); + +/*! + * Free DNSSEC signing context. + * + * \param ctx Signing context to be freed. + */ +void dnssec_sign_free(dnssec_sign_ctx_t *ctx); + +/*! + * Reinitialize DNSSEC signing context to start a new operation. + * + * \param ctx Signing context. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_init(dnssec_sign_ctx_t *ctx); + +/*! + * Add data to be covered by DNSSEC signature. + * + * \param ctx Signing context. + * \param data Data to be signed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_add(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *data); + +/*! + * Write down the DNSSEC signature. + * + * \param ctx Signing context. + * \param signature Signature to be allocated and written. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_binary_t *signature); + +/*! + * Verify DNSSEC signature. + * + * \param ctx Signing context. + * \param signature Signature to be verified. + * + * \return Error code. + * \retval DNSSEC_EOK Validation successful, valid signature. + * \retval DNSSEC_INVALID_SIGNATURE Validation successful, invalid signature. + */ +int dnssec_sign_verify(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *signature); + +/** @} */ diff --git a/src/libdnssec/sign/der.c b/src/libdnssec/sign/der.c new file mode 100644 index 0000000..4d79876 --- /dev/null +++ b/src/libdnssec/sign/der.c @@ -0,0 +1,229 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <stdbool.h> + +#include "libdnssec/shared/bignum.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/sign/der.h" +#include "libdnssec/shared/binary_wire.h" + +/* + * In fact, this is a very tiny subset of ASN.1 encoding format implementation, + * which is necessary for the purpose of DNSSEC. + * + * References: RFC 3279 (X.509 PKI), X.690, RFC 6605 (ECDSA), RFC8080 (EDDSA) + * + * Dss-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER } + */ + +#define ASN1_TYPE_SEQUENCE 0x30 +#define ASN1_TYPE_INTEGER 0x02 + +#define ASN1_MAX_SIZE 127 + +/*! + * Check if the next object has a given type. + */ +static bool asn1_expect_type(wire_ctx_t *wire, uint8_t type) +{ + assert(wire); + return (wire_ctx_available(wire) >= 1 && wire_ctx_read_u8(wire) == type); +} + +/*! + * Decode the size of the object (only short format is supported). + */ +static int asn1_decode_size(wire_ctx_t *wire, size_t *size) +{ + assert(wire); + assert(size); + + if (wire_ctx_available(wire) < 1) { + return DNSSEC_MALFORMED_DATA; + } + + uint8_t byte = wire_ctx_read_u8(wire); + if (byte & 0x80) { + // long form, we do not need it for DNSSEC + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + *size = byte; + + return DNSSEC_EOK; +} + +/*! + * Decode an unsigned integer object. + */ +static int asn1_decode_integer(wire_ctx_t *wire, dnssec_binary_t *_value) +{ + assert(wire); + assert(_value); + + if (!asn1_expect_type(wire, ASN1_TYPE_INTEGER)) { + return DNSSEC_MALFORMED_DATA; + } + + size_t size; + int result = asn1_decode_size(wire, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size == 0 || size > wire_ctx_available(wire)) { + return DNSSEC_MALFORMED_DATA; + } + + dnssec_binary_t value = { .data = wire->position, .size = size }; + wire->position += size; + + // skip leading zeroes (unless equal to zero) + while (value.size > 1 && value.data[0] == 0) { + value.data += 1; + value.size -= 1; + } + + *_value = value; + + return DNSSEC_EOK; +} + +/*! + * Encode object header (type and length). + */ +static void asn1_write_header(wire_ctx_t *wire, uint8_t type, size_t length) +{ + assert(wire); + assert(length < ASN1_MAX_SIZE); + + wire_ctx_write_u8(wire, type); + wire_ctx_write_u8(wire, length); +} + +/*! + * Encode unsigned integer object. + */ +static void asn1_write_integer(wire_ctx_t *wire, size_t integer_size, + const dnssec_binary_t *integer) +{ + assert(wire); + assert(integer); + assert(integer->data); + + asn1_write_header(wire, ASN1_TYPE_INTEGER, integer_size); + bignum_write(wire, integer_size, integer); +} + +/*! + * Decode signature parameters from X.509 ECDSA signature. + */ +int dss_sig_value_decode(const dnssec_binary_t *der, + dnssec_binary_t *r, dnssec_binary_t *s) +{ + if (!der || !der->data || !r || !s) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(der); + + size_t size; + int result; + + // decode the sequence + + if (!asn1_expect_type(&wire, ASN1_TYPE_SEQUENCE)) { + return DNSSEC_MALFORMED_DATA; + } + + result = asn1_decode_size(&wire, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size != wire_ctx_available(&wire)) { + return DNSSEC_MALFORMED_DATA; + } + + // decode the 'r' and 's' values + + dnssec_binary_t der_r; + result = asn1_decode_integer(&wire, &der_r); + if (result != DNSSEC_EOK) { + return result; + } + + dnssec_binary_t der_s; + result = asn1_decode_integer(&wire, &der_s); + if (result != DNSSEC_EOK) { + return result; + } + + if (wire_ctx_available(&wire) != 0) { + return DNSSEC_MALFORMED_DATA; + } + + *r = der_r; + *s = der_s; + + return DNSSEC_EOK; +} + +/*! + * Encode signature parameters from X.509 ECDSA signature. + */ +int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s, + dnssec_binary_t *der) +{ + if (!r || !r->data || !s || !s->data || !der) { + return DNSSEC_EINVAL; + } + + size_t r_size = bignum_size_s(r); + size_t s_size = bignum_size_s(s); + + // check supported inputs range + + if (r_size > ASN1_MAX_SIZE || s_size > ASN1_MAX_SIZE) { + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + size_t seq_size = 2 + r_size + 2 + s_size; + if (seq_size > ASN1_MAX_SIZE) { + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + // encode result + + size_t total_size = 2 + seq_size; + + dnssec_binary_t _der = { 0 }; + if (dnssec_binary_alloc(&_der, total_size)) { + return DNSSEC_ENOMEM; + } + + wire_ctx_t wire = binary_init(&_der); + asn1_write_header(&wire, ASN1_TYPE_SEQUENCE, seq_size); + asn1_write_integer(&wire, r_size, r); + asn1_write_integer(&wire, s_size, s); + assert(wire_ctx_available(&wire) == 0); + + *der = _der; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/sign/der.h b/src/libdnssec/sign/der.h new file mode 100644 index 0000000..db8e910 --- /dev/null +++ b/src/libdnssec/sign/der.h @@ -0,0 +1,56 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "libdnssec/binary.h" + +/* + * The ECDSA signatures in DNSSEC are encoded differently than in X.509 + * (PKCS #1). The cryptographic libraries usually produce the signatures in + * X.509 format, which uses Dss-Sig-Value to encapsulate 'r' and 's' values + * of the signature. + * + * This module provides decoding and encoding of this format. + * + * The 'r' and 's' values are treated as unsigned values: The leading zeroes + * are stripped on decoding; an extra leading zero is added on encoding in case + * the value starts with a set bit. + */ + +/*! + * Decode signature parameters from X.509 ECDSA signature. + * + * \param[in] der X.509 encoded signature. + * \param[out] s Value 's' of the signature, will point to the data in DER. + * \param[out] r Value 'r' of the signature, will point to the data in DER. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dss_sig_value_decode(const dnssec_binary_t *der, + dnssec_binary_t *r, dnssec_binary_t *s); + +/*! + * Encode signature parameters from X.509 ECDSA signature. + * + * \param[in] s Value 's' of the signature. + * \param[in] r Value 'r' of the signature. + * \param[out] der X.509 signature, the content will be allocated. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s, + dnssec_binary_t *der); diff --git a/src/libdnssec/sign/sign.c b/src/libdnssec/sign/sign.c new file mode 100644 index 0000000..16b4e64 --- /dev/null +++ b/src/libdnssec/sign/sign.c @@ -0,0 +1,408 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#include "contrib/macros.h" +#include "libdnssec/shared/bignum.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/sign.h" +#include "libdnssec/sign/der.h" +#include "libdnssec/shared/binary_wire.h" +#include "libdnssec/contrib/vpool.h" + +/*! + * Signature format conversion callback. + * + * \param ctx DNSSEC signing context. + * \param from Data in source format. + * \param to Allocated data in target format. + * + * \return Error code, DNSSEC_EOK if successful. + */ +typedef int (*signature_convert_cb)(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *from, + dnssec_binary_t *to); + +/*! + * Algorithm specific callbacks. + */ +typedef struct algorithm_functions { + //! Convert X.509 signature to DNSSEC format. + signature_convert_cb x509_to_dnssec; + //! Convert DNSSEC signature to X.509 format. + signature_convert_cb dnssec_to_x509; +} algorithm_functions_t; + +typedef struct dnssec_buffer { + uint8_t *allocd; //!< Pointer to allocated data. + uint8_t *data; //!< API: pointer to data to copy from. + size_t max_length; + size_t length; //!< API: current length. +} dnssec_buffer_t; + +/*! + * DNSSEC signing context. + */ +struct dnssec_sign_ctx { + const dnssec_key_t *key; //!< Signing key. + const algorithm_functions_t *functions; //!< Implementation specific. + + gnutls_sign_algorithm_t sign_algorithm; //!< Used algorithm for signing. + struct vpool buffer; //!< Buffer for the data to be signed. +}; + +/* -- signature format conversions ----------------------------------------- */ + +/*! + * Conversion of RSA signature between X.509 and DNSSEC format is a NOOP. + * + * \note Described in RFC 3110. + */ +static int rsa_copy_signature(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *from, + dnssec_binary_t *to) +{ + assert(ctx); + assert(from); + assert(to); + + return dnssec_binary_dup(from, to); +} + +static const algorithm_functions_t rsa_functions = { + .x509_to_dnssec = rsa_copy_signature, + .dnssec_to_x509 = rsa_copy_signature, +}; + +static size_t ecdsa_sign_integer_size(dnssec_sign_ctx_t *ctx) +{ + assert(ctx); + + switch (ctx->sign_algorithm) { + case GNUTLS_SIGN_ECDSA_SHA256: return 32; + case GNUTLS_SIGN_ECDSA_SHA384: return 48; + default: return 0; + }; +} + +/*! + * Convert ECDSA signature to DNSSEC format. + * + * \note Described in RFC 6605. + */ +static int ecdsa_x509_to_dnssec(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *x509, + dnssec_binary_t *dnssec) +{ + assert(ctx); + assert(x509); + assert(dnssec); + + dnssec_binary_t value_r = { 0 }; + dnssec_binary_t value_s = { 0 }; + + int result = dss_sig_value_decode(x509, &value_r, &value_s); + if (result != DNSSEC_EOK) { + return result; + } + + size_t int_size = ecdsa_sign_integer_size(ctx); + size_t r_size = bignum_size_u(&value_r); + size_t s_size = bignum_size_u(&value_s); + + if (r_size > int_size || s_size > int_size) { + return DNSSEC_MALFORMED_DATA; + } + + result = dnssec_binary_alloc(dnssec, 2 * int_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(dnssec); + bignum_write(&wire, int_size, &value_r); + bignum_write(&wire, int_size, &value_s); + assert(wire_ctx_offset(&wire) == dnssec->size); + + return DNSSEC_EOK; +} + +static int ecdsa_dnssec_to_x509(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *dnssec, + dnssec_binary_t *x509) +{ + assert(ctx); + assert(x509); + assert(dnssec); + + size_t int_size = ecdsa_sign_integer_size(ctx); + + if (dnssec->size != 2 * int_size) { + return DNSSEC_INVALID_SIGNATURE; + } + + const dnssec_binary_t value_r = { .size = int_size, .data = dnssec->data }; + const dnssec_binary_t value_s = { .size = int_size, .data = dnssec->data + int_size }; + + return dss_sig_value_encode(&value_r, &value_s, x509); +} + +static const algorithm_functions_t ecdsa_functions = { + .x509_to_dnssec = ecdsa_x509_to_dnssec, + .dnssec_to_x509 = ecdsa_dnssec_to_x509, +}; + +#define eddsa_copy_signature rsa_copy_signature +static const algorithm_functions_t eddsa_functions = { + .x509_to_dnssec = eddsa_copy_signature, + .dnssec_to_x509 = eddsa_copy_signature, +}; + +/* -- crypto helper functions --------------------------------------------- */ + +static const algorithm_functions_t *get_functions(const dnssec_key_t *key) +{ + uint8_t algorithm = dnssec_key_get_algorithm(key); + + switch ((dnssec_key_algorithm_t)algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return &rsa_functions; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return &ecdsa_functions; + case DNSSEC_KEY_ALGORITHM_ED25519: + case DNSSEC_KEY_ALGORITHM_ED448: + return &eddsa_functions; + default: + return NULL; + } +} + +#ifndef HAVE_SIGN_DATA2 +/** + * Get digest algorithm used with a given key. + */ +static gnutls_digest_algorithm_t get_digest_algorithm(const dnssec_key_t *key) +{ + uint8_t algorithm = dnssec_key_get_algorithm(key); + + switch ((dnssec_key_algorithm_t)algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + return GNUTLS_DIG_SHA1; + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return GNUTLS_DIG_SHA256; + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return GNUTLS_DIG_SHA512; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_DIG_SHA384; + case DNSSEC_KEY_ALGORITHM_ED25519: + return GNUTLS_DIG_SHA512; + case DNSSEC_KEY_ALGORITHM_ED448: + default: + return GNUTLS_DIG_UNKNOWN; + } +} +#endif + +static gnutls_sign_algorithm_t get_sign_algorithm(const dnssec_key_t *key) +{ + uint8_t algorithm = dnssec_key_get_algorithm(key); + + switch ((dnssec_key_algorithm_t)algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + return GNUTLS_SIGN_RSA_SHA1; + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + return GNUTLS_SIGN_RSA_SHA256; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return GNUTLS_SIGN_ECDSA_SHA256; + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return GNUTLS_SIGN_RSA_SHA512; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_SIGN_ECDSA_SHA384; + case DNSSEC_KEY_ALGORITHM_ED25519: +#ifdef HAVE_ED25519 + return GNUTLS_SIGN_EDDSA_ED25519; +#endif + case DNSSEC_KEY_ALGORITHM_ED448: +#ifdef HAVE_ED448 + return GNUTLS_SIGN_EDDSA_ED448; +#endif + default: + return GNUTLS_SIGN_UNKNOWN; + } +} + +/* -- public API ---------------------------------------------------------- */ + +_public_ +int dnssec_sign_new(dnssec_sign_ctx_t **ctx_ptr, const dnssec_key_t *key) +{ + if (!ctx_ptr) { + return DNSSEC_EINVAL; + } + + dnssec_sign_ctx_t *ctx = calloc(1, sizeof(*ctx)); + + ctx->key = key; + + ctx->functions = get_functions(key); + if (ctx->functions == NULL) { + free(ctx); + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + ctx->sign_algorithm = get_sign_algorithm(key); + int result = dnssec_sign_init(ctx); + if (result != DNSSEC_EOK) { + free(ctx); + return result; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_sign_free(dnssec_sign_ctx_t *ctx) +{ + if (!ctx) { + return; + } + + vpool_reset(&ctx->buffer); + + free(ctx); +} + +_public_ +int dnssec_sign_init(dnssec_sign_ctx_t *ctx) +{ + if (!ctx) { + return DNSSEC_EINVAL; + } + + if (vpool_get_buf(&ctx->buffer) != NULL) { + vpool_wipe(&ctx->buffer); + } else { + vpool_init(&ctx->buffer, 1024, 0); + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_sign_add(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *data) +{ + if (!ctx || !data || !data->data) { + return DNSSEC_EINVAL; + } + + void *result = vpool_insert(&ctx->buffer, vpool_get_length(&ctx->buffer), data->data, data->size); + if (result == NULL) { + return DNSSEC_SIGN_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_binary_t *signature) +{ + if (!ctx || !signature) { + return DNSSEC_EINVAL; + } + + if (!dnssec_key_can_sign(ctx->key)) { + return DNSSEC_NO_PRIVATE_KEY; + } + + gnutls_datum_t data = { + .data = vpool_get_buf(&ctx->buffer), + .size = vpool_get_length(&ctx->buffer) + }; + + assert(ctx->key->private_key); + _cleanup_datum_ gnutls_datum_t raw = { 0 }; +#ifdef HAVE_SIGN_DATA2 + int result = gnutls_privkey_sign_data2(ctx->key->private_key, + ctx->sign_algorithm, + 0, &data, &raw); +#else + gnutls_digest_algorithm_t digest_algorithm = get_digest_algorithm(ctx->key); + int result = gnutls_privkey_sign_data(ctx->key->private_key, + digest_algorithm, + 0, &data, &raw); +#endif + if (result < 0) { + return DNSSEC_SIGN_ERROR; + } + + dnssec_binary_t bin_raw = binary_from_datum(&raw); + + return ctx->functions->x509_to_dnssec(ctx, &bin_raw, signature); +} + +_public_ +int dnssec_sign_verify(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *signature) +{ + if (!ctx || !signature) { + return DNSSEC_EINVAL; + } + + if (!dnssec_key_can_verify(ctx->key)) { + return DNSSEC_NO_PUBLIC_KEY; + } + + gnutls_datum_t data = { + .data = vpool_get_buf(&ctx->buffer), + .size = vpool_get_length(&ctx->buffer) + }; + + _cleanup_binary_ dnssec_binary_t bin_raw = { 0 }; + int result = ctx->functions->dnssec_to_x509(ctx, signature, &bin_raw); + if (result != DNSSEC_EOK) { + return result; + } + + gnutls_datum_t raw = binary_to_datum(&bin_raw); + + assert(ctx->key->public_key); + result = gnutls_pubkey_verify_data2(ctx->key->public_key, + ctx->sign_algorithm, + 0, &data, &raw); + if (result == GNUTLS_E_PK_SIG_VERIFY_FAILED) { + return DNSSEC_INVALID_SIGNATURE; + } else if (result < 0) { + return DNSSEC_ERROR; + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/tsig.c b/src/libdnssec/tsig.c new file mode 100644 index 0000000..3edc8a3 --- /dev/null +++ b/src/libdnssec/tsig.c @@ -0,0 +1,242 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/shared/dname.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/tsig.h" + +struct dnssec_tsig_ctx { + gnutls_mac_algorithm_t algorithm; + gnutls_hmac_hd_t hash; +}; + +/*! + * TSIG algorithm indentifiers. + */ +typedef struct { + dnssec_tsig_algorithm_t id; + gnutls_mac_algorithm_t gnutls_id; + const char *name; + const char *dname; +} algorithm_id_t; + +/*! + * DNAME to algorithm conversion table. + */ +static const algorithm_id_t ALGORITHM_ID_TABLE[] = { + // RFC 4635 + { DNSSEC_TSIG_HMAC_SHA1, GNUTLS_MAC_SHA1, "hmac-sha1", "\x9hmac-sha1" }, + { DNSSEC_TSIG_HMAC_SHA224, GNUTLS_MAC_SHA224, "hmac-sha224", "\xbhmac-sha224" }, + { DNSSEC_TSIG_HMAC_SHA256, GNUTLS_MAC_SHA256, "hmac-sha256", "\xbhmac-sha256" }, + { DNSSEC_TSIG_HMAC_SHA384, GNUTLS_MAC_SHA384, "hmac-sha384", "\xbhmac-sha384" }, + { DNSSEC_TSIG_HMAC_SHA512, GNUTLS_MAC_SHA512, "hmac-sha512", "\xbhmac-sha512" }, + // RFC 2845 + { DNSSEC_TSIG_HMAC_MD5, GNUTLS_MAC_MD5, "hmac-md5", "\x8hmac-md5\x7sig-alg\x3reg\x3int" }, + { 0 } +}; + +/*! + * Algorithm match callback prototype. + */ +typedef bool (*algorithm_match_cb)(const algorithm_id_t *m, const void *data); + +/*! + * Lookup an algorithm in the algorithm table. + */ +static const algorithm_id_t *lookup_algorithm(algorithm_match_cb match, + const void *data) +{ + assert(match); + + for (const algorithm_id_t *a = ALGORITHM_ID_TABLE; a->id; a++) { + if (match(a, data)) { + return a; + } + } + + return NULL; +} + +static bool match_dname(const algorithm_id_t *algorithm, const void *data) +{ + const uint8_t *search = data; + return dname_equal(search, (uint8_t *)algorithm->dname); +} + +static bool match_name(const algorithm_id_t *algorithm, const void *data) +{ + const char *search = data; + return strcasecmp(search, algorithm->name) == 0; +} + +static bool match_id(const algorithm_id_t *algorithm, const void *data) +{ + dnssec_tsig_algorithm_t search = (dnssec_tsig_algorithm_t)data; + return algorithm->id == search; +} + +/*! + * Convert TSIG algorithm identifier to GnuTLS identifier. + */ +static gnutls_mac_algorithm_t algorithm_to_gnutls(dnssec_tsig_algorithm_t tsig) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, (void *)tsig); + return (found ? found->gnutls_id : GNUTLS_MAC_UNKNOWN); +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_dname(const uint8_t *dname) +{ + if (!dname) { + return DNSSEC_TSIG_UNKNOWN; + } + + const algorithm_id_t *found = lookup_algorithm(match_dname, dname); + return (found ? found->id : DNSSEC_TSIG_UNKNOWN); +} + +_public_ +const uint8_t *dnssec_tsig_algorithm_to_dname(dnssec_tsig_algorithm_t algorithm) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, (void *)algorithm); + return (found ? (uint8_t *)found->dname : NULL); +} + +_public_ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_name(const char *name) +{ + if (!name) { + return DNSSEC_TSIG_UNKNOWN; + } + + const algorithm_id_t *found = lookup_algorithm(match_name, name); + return (found ? found->id : DNSSEC_TSIG_UNKNOWN); +} + +_public_ +const char *dnssec_tsig_algorithm_to_name(dnssec_tsig_algorithm_t algorithm) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, (void *)algorithm); + return (found ? found->name : NULL); +} + +_public_ +int dnssec_tsig_optimal_key_size(dnssec_tsig_algorithm_t tsig) +{ + gnutls_mac_algorithm_t mac = algorithm_to_gnutls(tsig); + if (mac == GNUTLS_MAC_UNKNOWN) { + return 0; + } + + return gnutls_mac_get_key_size(mac) * CHAR_BIT; +} + +_public_ +int dnssec_tsig_new(dnssec_tsig_ctx_t **ctx_ptr, + dnssec_tsig_algorithm_t algorithm, + const dnssec_binary_t *key) +{ + if (!ctx_ptr || !key) { + return DNSSEC_EINVAL; + } + + dnssec_tsig_ctx_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + ctx->algorithm = algorithm_to_gnutls(algorithm); + if (ctx->algorithm == GNUTLS_MAC_UNKNOWN) { + free(ctx); + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + int result = gnutls_hmac_init(&ctx->hash, ctx->algorithm, key->data, key->size); + if (result != 0) { + free(ctx); + return DNSSEC_SIGN_INIT_ERROR; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_tsig_free(dnssec_tsig_ctx_t *ctx) +{ + if (!ctx) { + return; + } + + gnutls_hmac_deinit(ctx->hash, NULL); + free(ctx); +} + +_public_ +int dnssec_tsig_add(dnssec_tsig_ctx_t *ctx, const dnssec_binary_t *data) +{ + if (!ctx || !data) { + return DNSSEC_EINVAL; + } + + int result = gnutls_hmac(ctx->hash, data->data, data->size); + if (result != 0) { + return DNSSEC_SIGN_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +size_t dnssec_tsig_size(dnssec_tsig_ctx_t *ctx) +{ + if (!ctx) { + return 0; + } + + return gnutls_hmac_get_len(ctx->algorithm); +} + +_public_ +size_t dnssec_tsig_algorithm_size(dnssec_tsig_algorithm_t algorithm) +{ + int gnutls_algorithm = algorithm_to_gnutls(algorithm); + return gnutls_hmac_get_len(gnutls_algorithm); +} + +_public_ +int dnssec_tsig_write(dnssec_tsig_ctx_t *ctx, uint8_t *mac) +{ + if (!ctx || !mac) { + return DNSSEC_EINVAL; + } + + gnutls_hmac_output(ctx->hash, mac); + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/tsig.h b/src/libdnssec/tsig.h new file mode 100644 index 0000000..e861982 --- /dev/null +++ b/src/libdnssec/tsig.h @@ -0,0 +1,207 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/*! + * \file + * + * \addtogroup tsig + * + * \brief Low-level TSIG signing API. + * + * Example: + * + * ~~~~~ {.c} + * + * int result; + * + * dnssec_binary_t *covered = // ... ; + * dnssec_binary_t *signature = // ... ; + * + * // convert algorithm from textual representation + * dnssec_tsig_algorithm_t algorithm; + * algorithm = dnssec_tsig_algorithm_from_name("hmac-sha256"); + * assert(algorithm == DNSSEC_TSIG_HMAC_SHA256); + * + * // get shared key + * dnssec_binary_t key = { + * .size = 4, + * .data = (uint8_t *) { 0x11, 0x22, 0x33, 0x44 } + * }; + * + * // create computation context + * dnssec_tsig_ctx_t *ctx; + * result = dnssec_tsig_new(&ctx, algorithm, &key); + * if (result != DNSSEC_EOK) { + * return result; + * } + * + * // add data to be covered by the signature + * dnssec_tsig_add(ctx, covered); + * + * // compute the expected signature (MAC) + * size_t size = dnssec_tsig_size(ctx); + * dnssec_binary_t expected_signature = { 0 }; + * result = dnssec_binary_alloc(&expected_signature, size); + * if (result != DNSSEC_EOK) { + * dnssec_tsig_free(ctx); + * return result; + * } + * dnssec_tsig_write(ctx, &expected_signature); + * + * // compare the signatures + * if (dnssec_binary_cmp(signature, &expected_signature) == 0) { + * // valid signature + * } else { + * // invalid signature + * } + * + * // cleanup + * dnssec_binary_free(&expected_signature); + * dnssec_tsig_free(ctx); + * + * ~~~~~ + * + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include <libdnssec/binary.h> + +/*! + * TSIG algorithms. + * + * \note The numeric values are library specific. + */ +typedef enum dnssec_tsig_algorithm { + DNSSEC_TSIG_UNKNOWN = 0, + DNSSEC_TSIG_HMAC_MD5, + DNSSEC_TSIG_HMAC_SHA1, + DNSSEC_TSIG_HMAC_SHA224, + DNSSEC_TSIG_HMAC_SHA256, + DNSSEC_TSIG_HMAC_SHA384, + DNSSEC_TSIG_HMAC_SHA512 +} dnssec_tsig_algorithm_t; + +/*! + * Get TSIG algorithm number from domain name. + * + * \see https://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xhtml + * + * \param dname Domain name of the algorithm (e.g., 0x0b hmac-sha256). + * + * \return TSIG algorithm. + */ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_dname(const uint8_t *dname); + +/*! + * Get a domain name of the TSIG algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return Domain name of the TSIG algorithm. + */ +const uint8_t *dnssec_tsig_algorithm_to_dname(dnssec_tsig_algorithm_t algorithm); + +/*! + * Get TSIG algorithm from a MAC name. + * + * \param name MAC name (e.g., hmac-sha256). + * + * \return TSIG algorithm. + */ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_name(const char *name); + +/*! + * Get MAC name from a TSIG algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return MAC name of the TSIG algorithm. + */ +const char *dnssec_tsig_algorithm_to_name(dnssec_tsig_algorithm_t algorithm); + +/*! + * Get optimal size of a TSIG algorithm. + */ +int dnssec_tsig_optimal_key_size(dnssec_tsig_algorithm_t algorithm); + +struct dnssec_tsig_ctx; + +/*! + * TSIG signing context. + */ +typedef struct dnssec_tsig_ctx dnssec_tsig_ctx_t; + +/*! + * Create new TSIG signing context. + * + * \param[out] ctx Resulting TSIG context. + * \param[in] algorithm TSIG algorithm. + * \param[in] key Shared key to be used for signing. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_new(dnssec_tsig_ctx_t **ctx, dnssec_tsig_algorithm_t algorithm, + const dnssec_binary_t *key); + +/*! + * Free the TSIG signing context. + * + * \param ctx TSIG signing context to be freed. + */ +void dnssec_tsig_free(dnssec_tsig_ctx_t *ctx); + +/*! + * Add data to be signed by TSIG. + * + * \param ctx TSIG signing context. + * \param data Data to be signed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_add(dnssec_tsig_ctx_t *ctx, const dnssec_binary_t *data); + +/*! + * Get size of the TSIG signature for given signing context. + * + * \param ctx TSIG signing context. + * + * \return The size of the TSIG signature. + */ +size_t dnssec_tsig_size(dnssec_tsig_ctx_t *ctx); + +/*! + * Get size of the TSIG signature for given algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return The size of the TSIG signature. + */ +size_t dnssec_tsig_algorithm_size(dnssec_tsig_algorithm_t algorithm); + +/*! + * Write TSIG signature. + * + * \param[in] ctx TSIG signing context. + * \param[out] mac Resulting TSIG signature. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_write(dnssec_tsig_ctx_t *ctx, uint8_t *mac); + +/*! @} */ diff --git a/src/libdnssec/version.h b/src/libdnssec/version.h new file mode 100644 index 0000000..e2c362f --- /dev/null +++ b/src/libdnssec/version.h @@ -0,0 +1,25 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#define DNSSEC_VERSION_MAJOR 2 +#define DNSSEC_VERSION_MINOR 7 +#define DNSSEC_VERSION_PATCH 0x06 + +#define DNSSEC_VERSION_HEX ((DNSSEC_VERSION_MAJOR << 16) | \ + (DNSSEC_VERSION_MINOR << 8) | \ + (DNSSEC_VERSION_PATCH)) diff --git a/src/libdnssec/version.h.in b/src/libdnssec/version.h.in new file mode 100644 index 0000000..de8752f --- /dev/null +++ b/src/libdnssec/version.h.in @@ -0,0 +1,25 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#define DNSSEC_VERSION_MAJOR @KNOT_VERSION_MAJOR@ +#define DNSSEC_VERSION_MINOR @KNOT_VERSION_MINOR@ +#define DNSSEC_VERSION_PATCH 0x0@KNOT_VERSION_PATCH@ + +#define DNSSEC_VERSION_HEX ((DNSSEC_VERSION_MAJOR << 16) | \ + (DNSSEC_VERSION_MINOR << 8) | \ + (DNSSEC_VERSION_PATCH)) |