diff options
Diffstat (limited to '')
-rw-r--r-- | lib/dns/hmac_link.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/lib/dns/hmac_link.c b/lib/dns/hmac_link.c new file mode 100644 index 0000000..9f8e94b --- /dev/null +++ b/lib/dns/hmac_link.c @@ -0,0 +1,527 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Copyright (C) Network Associates, Inc. + * + * 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 ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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 <arpa/inet.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/hmac.h> +#include <isc/lex.h> +#include <isc/md.h> +#include <isc/mem.h> +#include <isc/nonce.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/safe.h> +#include <isc/string.h> +#include <isc/util.h> + +#include "dst_internal.h" +#ifdef HAVE_FIPS_MODE +#include "dst_openssl.h" /* FIPS_mode() prototype */ +#endif /* ifdef HAVE_FIPS_MODE */ +#include "dst_parse.h" + +#define ISC_MD_md5 ISC_MD_MD5 +#define ISC_MD_sha1 ISC_MD_SHA1 +#define ISC_MD_sha224 ISC_MD_SHA224 +#define ISC_MD_sha256 ISC_MD_SHA256 +#define ISC_MD_sha384 ISC_MD_SHA384 +#define ISC_MD_sha512 ISC_MD_SHA512 + +#define hmac_register_algorithm(alg) \ + static isc_result_t hmac##alg##_createctx(dst_key_t *key, \ + dst_context_t *dctx) { \ + return (hmac_createctx(ISC_MD_##alg, key, dctx)); \ + } \ + static void hmac##alg##_destroyctx(dst_context_t *dctx) { \ + hmac_destroyctx(dctx); \ + } \ + static isc_result_t hmac##alg##_adddata(dst_context_t *dctx, \ + const isc_region_t *data) { \ + return (hmac_adddata(dctx, data)); \ + } \ + static isc_result_t hmac##alg##_sign(dst_context_t *dctx, \ + isc_buffer_t *sig) { \ + return (hmac_sign(dctx, sig)); \ + } \ + static isc_result_t hmac##alg##_verify(dst_context_t *dctx, \ + const isc_region_t *sig) { \ + return (hmac_verify(dctx, sig)); \ + } \ + static bool hmac##alg##_compare(const dst_key_t *key1, \ + const dst_key_t *key2) { \ + return (hmac_compare(ISC_MD_##alg, key1, key2)); \ + } \ + static isc_result_t hmac##alg##_generate( \ + dst_key_t *key, int pseudorandom_ok, void (*callback)(int)) { \ + UNUSED(pseudorandom_ok); \ + UNUSED(callback); \ + return (hmac_generate(ISC_MD_##alg, key)); \ + } \ + static bool hmac##alg##_isprivate(const dst_key_t *key) { \ + return (hmac_isprivate(key)); \ + } \ + static void hmac##alg##_destroy(dst_key_t *key) { hmac_destroy(key); } \ + static isc_result_t hmac##alg##_todns(const dst_key_t *key, \ + isc_buffer_t *data) { \ + return (hmac_todns(key, data)); \ + } \ + static isc_result_t hmac##alg##_fromdns(dst_key_t *key, \ + isc_buffer_t *data) { \ + return (hmac_fromdns(ISC_MD_##alg, key, data)); \ + } \ + static isc_result_t hmac##alg##_tofile(const dst_key_t *key, \ + const char *directory) { \ + return (hmac_tofile(ISC_MD_##alg, key, directory)); \ + } \ + static isc_result_t hmac##alg##_parse( \ + dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { \ + const char *file = isc_lex_getsourcename(lexer); \ + isc_result_t result; \ + result = hmac_parse(ISC_MD_##alg, key, lexer, pub); \ + if (result == ISC_R_SUCCESS && file != NULL) { \ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, \ + DNS_LOGMODULE_CRYPTO, ISC_LOG_WARNING, \ + "%s: Use of K* file pairs for HMAC is " \ + "deprecated\n", \ + file); \ + } \ + return (result); \ + } \ + static dst_func_t hmac##alg##_functions = { \ + hmac##alg##_createctx, \ + NULL, /*%< createctx2 */ \ + hmac##alg##_destroyctx, \ + hmac##alg##_adddata, \ + hmac##alg##_sign, \ + hmac##alg##_verify, \ + NULL, /*%< verify2 */ \ + NULL, /*%< computesecret */ \ + hmac##alg##_compare, \ + NULL, /*%< paramcompare */ \ + hmac##alg##_generate, \ + hmac##alg##_isprivate, \ + hmac##alg##_destroy, \ + hmac##alg##_todns, \ + hmac##alg##_fromdns, \ + hmac##alg##_tofile, \ + hmac##alg##_parse, \ + NULL, /*%< cleanup */ \ + NULL, /*%< fromlabel */ \ + NULL, /*%< dump */ \ + NULL, /*%< restore */ \ + }; \ + isc_result_t dst__hmac##alg##_init(dst_func_t **funcp) { \ + REQUIRE(funcp != NULL); \ + if (*funcp == NULL) { \ + *funcp = &hmac##alg##_functions; \ + } \ + return (ISC_R_SUCCESS); \ + } + +static isc_result_t +hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data); + +struct dst_hmac_key { + uint8_t key[ISC_MAX_BLOCK_SIZE]; +}; + +static isc_result_t +getkeybits(dst_key_t *key, struct dst_private_element *element) { + uint16_t *bits = (uint16_t *)element->data; + + if (element->length != 2) { + return (DST_R_INVALIDPRIVATEKEY); + } + + key->key_bits = ntohs(*bits); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_createctx(const isc_md_type_t *type, const dst_key_t *key, + dst_context_t *dctx) { + isc_result_t result; + const dst_hmac_key_t *hkey = key->keydata.hmac_key; + isc_hmac_t *ctx = isc_hmac_new(); /* Either returns or abort()s */ + + result = isc_hmac_init(ctx, hkey->key, isc_md_type_get_block_size(type), + type); + if (result != ISC_R_SUCCESS) { + isc_hmac_free(ctx); + return (DST_R_UNSUPPORTEDALG); + } + + dctx->ctxdata.hmac_ctx = ctx; + return (ISC_R_SUCCESS); +} + +static void +hmac_destroyctx(dst_context_t *dctx) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + REQUIRE(ctx != NULL); + + isc_hmac_free(ctx); + dctx->ctxdata.hmac_ctx = NULL; +} + +static isc_result_t +hmac_adddata(const dst_context_t *dctx, const isc_region_t *data) { + isc_result_t result; + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + + REQUIRE(ctx != NULL); + + result = isc_hmac_update(ctx, data->base, data->length); + if (result != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_sign(const dst_context_t *dctx, isc_buffer_t *sig) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + REQUIRE(ctx != NULL); + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen = sizeof(digest); + + if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_buffer_availablelength(sig) < digestlen) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem(sig, digest, digestlen); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_verify(const dst_context_t *dctx, const isc_region_t *sig) { + isc_hmac_t *ctx = dctx->ctxdata.hmac_ctx; + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen = sizeof(digest); + + REQUIRE(ctx != NULL); + + if (isc_hmac_final(ctx, digest, &digestlen) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (isc_hmac_reset(ctx) != ISC_R_SUCCESS) { + return (DST_R_OPENSSLFAILURE); + } + + if (sig->length > digestlen) { + return (DST_R_VERIFYFAILURE); + } + + return (isc_safe_memequal(digest, sig->base, sig->length) + ? ISC_R_SUCCESS + : DST_R_VERIFYFAILURE); +} + +static bool +hmac_compare(const isc_md_type_t *type, const dst_key_t *key1, + const dst_key_t *key2) { + dst_hmac_key_t *hkey1, *hkey2; + + hkey1 = key1->keydata.hmac_key; + hkey2 = key2->keydata.hmac_key; + + if (hkey1 == NULL && hkey2 == NULL) { + return (true); + } else if (hkey1 == NULL || hkey2 == NULL) { + return (false); + } + + return (isc_safe_memequal(hkey1->key, hkey2->key, + isc_md_type_get_block_size(type))); +} + +static isc_result_t +hmac_generate(const isc_md_type_t *type, dst_key_t *key) { + isc_buffer_t b; + isc_result_t ret; + unsigned int bytes, len; + unsigned char data[ISC_MAX_MD_SIZE] = { 0 }; + + len = isc_md_type_get_block_size(type); + + bytes = (key->key_size + 7) / 8; + + if (bytes > len) { + bytes = len; + key->key_size = len * 8; + } + + isc_nonce_buf(data, bytes); + + isc_buffer_init(&b, data, bytes); + isc_buffer_add(&b, bytes); + + ret = hmac_fromdns(type, key, &b); + + isc_safe_memwipe(data, sizeof(data)); + + return (ret); +} + +static bool +hmac_isprivate(const dst_key_t *key) { + UNUSED(key); + return (true); +} + +static void +hmac_destroy(dst_key_t *key) { + dst_hmac_key_t *hkey = key->keydata.hmac_key; + isc_safe_memwipe(hkey, sizeof(*hkey)); + isc_mem_put(key->mctx, hkey, sizeof(*hkey)); + key->keydata.hmac_key = NULL; +} + +static isc_result_t +hmac_todns(const dst_key_t *key, isc_buffer_t *data) { + REQUIRE(key != NULL && key->keydata.hmac_key != NULL); + dst_hmac_key_t *hkey = key->keydata.hmac_key; + unsigned int bytes; + + bytes = (key->key_size + 7) / 8; + if (isc_buffer_availablelength(data) < bytes) { + return (ISC_R_NOSPACE); + } + isc_buffer_putmem(data, hkey->key, bytes); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +hmac_fromdns(const isc_md_type_t *type, dst_key_t *key, isc_buffer_t *data) { + dst_hmac_key_t *hkey; + unsigned int keylen; + isc_region_t r; + + isc_buffer_remainingregion(data, &r); + if (r.length == 0) { + return (ISC_R_SUCCESS); + } + + hkey = isc_mem_get(key->mctx, sizeof(dst_hmac_key_t)); + + memset(hkey->key, 0, sizeof(hkey->key)); + + /* Hash the key if the key is longer then chosen MD block size */ + if (r.length > (unsigned int)isc_md_type_get_block_size(type)) { + if (isc_md(type, r.base, r.length, hkey->key, &keylen) != + ISC_R_SUCCESS) + { + isc_mem_put(key->mctx, hkey, sizeof(dst_hmac_key_t)); + return (DST_R_OPENSSLFAILURE); + } + } else { + memmove(hkey->key, r.base, r.length); + keylen = r.length; + } + + key->key_size = keylen * 8; + key->keydata.hmac_key = hkey; + + isc_buffer_forward(data, r.length); + + return (ISC_R_SUCCESS); +} + +static int +hmac__get_tag_key(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (TAG_HMACMD5_KEY); + } else if (type == ISC_MD_SHA1) { + return (TAG_HMACSHA1_KEY); + } else if (type == ISC_MD_SHA224) { + return (TAG_HMACSHA224_KEY); + } else if (type == ISC_MD_SHA256) { + return (TAG_HMACSHA256_KEY); + } else if (type == ISC_MD_SHA384) { + return (TAG_HMACSHA384_KEY); + } else if (type == ISC_MD_SHA512) { + return (TAG_HMACSHA512_KEY); + } else { + UNREACHABLE(); + } +} + +static int +hmac__get_tag_bits(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (TAG_HMACMD5_BITS); + } else if (type == ISC_MD_SHA1) { + return (TAG_HMACSHA1_BITS); + } else if (type == ISC_MD_SHA224) { + return (TAG_HMACSHA224_BITS); + } else if (type == ISC_MD_SHA256) { + return (TAG_HMACSHA256_BITS); + } else if (type == ISC_MD_SHA384) { + return (TAG_HMACSHA384_BITS); + } else if (type == ISC_MD_SHA512) { + return (TAG_HMACSHA512_BITS); + } else { + UNREACHABLE(); + } +} + +static isc_result_t +hmac_tofile(const isc_md_type_t *type, const dst_key_t *key, + const char *directory) { + dst_hmac_key_t *hkey; + dst_private_t priv; + int bytes = (key->key_size + 7) / 8; + uint16_t bits; + + if (key->keydata.hmac_key == NULL) { + return (DST_R_NULLKEY); + } + + if (key->external) { + return (DST_R_EXTERNALKEY); + } + + hkey = key->keydata.hmac_key; + + priv.elements[0].tag = hmac__get_tag_key(type); + priv.elements[0].length = bytes; + priv.elements[0].data = hkey->key; + + bits = htons(key->key_bits); + + priv.elements[1].tag = hmac__get_tag_bits(type); + priv.elements[1].length = sizeof(bits); + priv.elements[1].data = (uint8_t *)&bits; + + priv.nelements = 2; + + return (dst__privstruct_writefile(key, &priv, directory)); +} + +static int +hmac__to_dst_alg(const isc_md_type_t *type) { + if (type == ISC_MD_MD5) { + return (DST_ALG_HMACMD5); + } else if (type == ISC_MD_SHA1) { + return (DST_ALG_HMACSHA1); + } else if (type == ISC_MD_SHA224) { + return (DST_ALG_HMACSHA224); + } else if (type == ISC_MD_SHA256) { + return (DST_ALG_HMACSHA256); + } else if (type == ISC_MD_SHA384) { + return (DST_ALG_HMACSHA384); + } else if (type == ISC_MD_SHA512) { + return (DST_ALG_HMACSHA512); + } else { + UNREACHABLE(); + } +} + +static isc_result_t +hmac_parse(const isc_md_type_t *type, dst_key_t *key, isc_lex_t *lexer, + dst_key_t *pub) { + dst_private_t priv; + isc_result_t result, tresult; + isc_buffer_t b; + isc_mem_t *mctx = key->mctx; + unsigned int i; + + UNUSED(pub); + /* read private key file */ + result = dst__privstruct_parse(key, hmac__to_dst_alg(type), lexer, mctx, + &priv); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (key->external) { + result = DST_R_EXTERNALKEY; + } + + key->key_bits = 0; + for (i = 0; i < priv.nelements && result == ISC_R_SUCCESS; i++) { + switch (priv.elements[i].tag) { + case TAG_HMACMD5_KEY: + case TAG_HMACSHA1_KEY: + case TAG_HMACSHA224_KEY: + case TAG_HMACSHA256_KEY: + case TAG_HMACSHA384_KEY: + case TAG_HMACSHA512_KEY: + isc_buffer_init(&b, priv.elements[i].data, + priv.elements[i].length); + isc_buffer_add(&b, priv.elements[i].length); + tresult = hmac_fromdns(type, key, &b); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + } + break; + case TAG_HMACMD5_BITS: + case TAG_HMACSHA1_BITS: + case TAG_HMACSHA224_BITS: + case TAG_HMACSHA256_BITS: + case TAG_HMACSHA384_BITS: + case TAG_HMACSHA512_BITS: + tresult = getkeybits(key, &priv.elements[i]); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + } + break; + default: + result = DST_R_INVALIDPRIVATEKEY; + break; + } + } + dst__privstruct_free(&priv, mctx); + isc_safe_memwipe(&priv, sizeof(priv)); + return (result); +} + +hmac_register_algorithm(md5); +hmac_register_algorithm(sha1); +hmac_register_algorithm(sha224); +hmac_register_algorithm(sha256); +hmac_register_algorithm(sha384); +hmac_register_algorithm(sha512); + +/*! \file */ |