diff options
Diffstat (limited to '')
-rw-r--r-- | src/libdnssec/shared/bignum.c | 64 | ||||
-rw-r--r-- | src/libdnssec/shared/bignum.h | 41 | ||||
-rw-r--r-- | src/libdnssec/shared/binary_wire.h | 53 | ||||
-rw-r--r-- | src/libdnssec/shared/dname.c | 165 | ||||
-rw-r--r-- | src/libdnssec/shared/dname.h | 57 | ||||
-rw-r--r-- | src/libdnssec/shared/fs.c | 47 | ||||
-rw-r--r-- | src/libdnssec/shared/fs.h | 25 | ||||
-rw-r--r-- | src/libdnssec/shared/hex.c | 167 | ||||
-rw-r--r-- | src/libdnssec/shared/hex.h | 39 | ||||
-rw-r--r-- | src/libdnssec/shared/keyid_gnutls.c | 94 | ||||
-rw-r--r-- | src/libdnssec/shared/keyid_gnutls.h | 30 | ||||
-rw-r--r-- | src/libdnssec/shared/pem.c | 198 | ||||
-rw-r--r-- | src/libdnssec/shared/pem.h | 74 | ||||
-rw-r--r-- | src/libdnssec/shared/shared.h | 129 |
14 files changed, 1183 insertions, 0 deletions
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; +} |