summaryrefslogtreecommitdiffstats
path: root/src/libdnssec/sign
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libdnssec/sign.h114
-rw-r--r--src/libdnssec/sign/der.c229
-rw-r--r--src/libdnssec/sign/der.h56
-rw-r--r--src/libdnssec/sign/sign.c433
4 files changed, 832 insertions, 0 deletions
diff --git a/src/libdnssec/sign.h b/src/libdnssec/sign.h
new file mode 100644
index 0000000..0311b52
--- /dev/null
+++ b/src/libdnssec/sign.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \addtogroup sign
+ *
+ * \brief DNSSEC signing API
+ *
+ * The module provides the low level DNSSEC signing and verification.
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <libdnssec/binary.h>
+#include <libdnssec/key.h>
+
+struct dnssec_sign_ctx;
+
+typedef enum {
+ DNSSEC_SIGN_NORMAL = 0,
+ DNSSEC_SIGN_REPRODUCIBLE = (1 << 0),
+} dnssec_sign_flags_t;
+
+/*!
+ * 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 flags Additional flags to be used for signing.
+ * \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_sign_flags_t flags,
+ dnssec_binary_t *signature);
+
+/*!
+ * Verify DNSSEC signature.
+ *
+ * \param ctx Signing context.
+ * \param sign_cmp Verify by signing and comparing signatures.
+ * Not possible for non-deterministic algorithms!
+ * \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, bool sign_cmp,
+ 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..cd6102d
--- /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 <https://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..687b061
--- /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 <https://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..3a7bcba
--- /dev/null
+++ b/src/libdnssec/sign/sign.c
@@ -0,0 +1,433 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include "contrib/macros.h"
+#include "contrib/vpool/vpool.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"
+
+/*!
+ * 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:
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ return GNUTLS_DIG_SHA512;
+ default:
+ return GNUTLS_DIG_UNKNOWN;
+ }
+}
+#endif
+
+static gnutls_sign_algorithm_t algo_dnssec2gnutls(dnssec_key_algorithm_t algorithm)
+{
+ switch (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;
+#ifdef HAVE_ED25519
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+ return GNUTLS_SIGN_EDDSA_ED25519;
+#endif
+#ifdef HAVE_ED448
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ return GNUTLS_SIGN_EDDSA_ED448;
+#endif
+ default:
+ return GNUTLS_SIGN_UNKNOWN;
+ }
+}
+
+/* -- public API ---------------------------------------------------------- */
+
+_public_
+bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algorithm)
+{
+ gnutls_sign_algorithm_t a = algo_dnssec2gnutls(algorithm);
+ return a != GNUTLS_SIGN_UNKNOWN && gnutls_sign_is_secure(a);
+}
+
+_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;
+ }
+
+ const uint8_t algo_raw = dnssec_key_get_algorithm(key);
+ ctx->sign_algorithm = algo_dnssec2gnutls((dnssec_key_algorithm_t)algo_raw);
+ 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_sign_flags_t flags, 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)
+ };
+
+ unsigned gnutls_flags = 0;
+#ifdef HAVE_GNUTLS_REPRODUCIBLE
+ if (flags & DNSSEC_SIGN_REPRODUCIBLE) {
+ gnutls_flags |= GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE;
+ }
+#endif
+
+ 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,
+ gnutls_flags, &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,
+ gnutls_flags, &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, bool sign_cmp, const dnssec_binary_t *signature)
+{
+ if (!ctx || !signature) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (sign_cmp && dnssec_key_can_sign(ctx->key)) {
+ dnssec_binary_t sign = { 0 };
+ int ret = dnssec_sign_write(ctx, DNSSEC_SIGN_REPRODUCIBLE, &sign);
+ if (ret == KNOT_EOK) {
+ ret = dnssec_binary_cmp(&sign, signature)
+ ? DNSSEC_INVALID_SIGNATURE
+ : DNSSEC_EOK;
+ }
+ dnssec_binary_free(&sign);
+ return ret;
+ }
+
+ 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;
+}