/* $NetBSD: openssleddsa_link.c,v 1.7.2.2 2024/02/25 15:46:51 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * 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. */ /*! \file */ #if HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 #include #include #include #include #include #if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 #include #endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ #include #include #include #include #include #include #include "dst_internal.h" #include "dst_openssl.h" #include "dst_parse.h" #include "openssl_shim.h" #define DST_RET(a) \ { \ ret = a; \ goto err; \ } #if HAVE_OPENSSL_ED25519 #ifndef NID_ED25519 #error "Ed25519 group is not known (NID_ED25519)" #endif /* ifndef NID_ED25519 */ #endif /* HAVE_OPENSSL_ED25519 */ #if HAVE_OPENSSL_ED448 #ifndef NID_ED448 #error "Ed448 group is not known (NID_ED448)" #endif /* ifndef NID_ED448 */ #endif /* HAVE_OPENSSL_ED448 */ static isc_result_t raw_key_to_ossl(unsigned int key_alg, int private, const unsigned char *key, size_t *key_len, EVP_PKEY **pkey) { isc_result_t ret; int pkey_type = EVP_PKEY_NONE; size_t len = 0; #if HAVE_OPENSSL_ED25519 if (key_alg == DST_ALG_ED25519) { pkey_type = EVP_PKEY_ED25519; len = DNS_KEY_ED25519SIZE; } #endif /* HAVE_OPENSSL_ED25519 */ #if HAVE_OPENSSL_ED448 if (key_alg == DST_ALG_ED448) { pkey_type = EVP_PKEY_ED448; len = DNS_KEY_ED448SIZE; } #endif /* HAVE_OPENSSL_ED448 */ if (pkey_type == EVP_PKEY_NONE) { return (ISC_R_NOTIMPLEMENTED); } ret = (private ? DST_R_INVALIDPRIVATEKEY : DST_R_INVALIDPUBLICKEY); if (*key_len < len) { return (ret); } if (private) { *pkey = EVP_PKEY_new_raw_private_key(pkey_type, NULL, key, len); } else { *pkey = EVP_PKEY_new_raw_public_key(pkey_type, NULL, key, len); } if (*pkey == NULL) { return (dst__openssl_toresult(ret)); } *key_len = len; return (ISC_R_SUCCESS); } static isc_result_t openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label, const char *pin); static isc_result_t openssleddsa_createctx(dst_key_t *key, dst_context_t *dctx) { isc_buffer_t *buf = NULL; UNUSED(key); REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || dctx->key->key_alg == DST_ALG_ED448); isc_buffer_allocate(dctx->mctx, &buf, 64); dctx->ctxdata.generic = buf; return (ISC_R_SUCCESS); } static void openssleddsa_destroyctx(dst_context_t *dctx) { isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || dctx->key->key_alg == DST_ALG_ED448); if (buf != NULL) { isc_buffer_free(&buf); } dctx->ctxdata.generic = NULL; } static isc_result_t openssleddsa_adddata(dst_context_t *dctx, const isc_region_t *data) { isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; isc_buffer_t *nbuf = NULL; isc_region_t r; unsigned int length; isc_result_t result; REQUIRE(dctx->key->key_alg == DST_ALG_ED25519 || dctx->key->key_alg == DST_ALG_ED448); result = isc_buffer_copyregion(buf, data); if (result == ISC_R_SUCCESS) { return (ISC_R_SUCCESS); } length = isc_buffer_length(buf) + data->length + 64; isc_buffer_allocate(dctx->mctx, &nbuf, length); isc_buffer_usedregion(buf, &r); (void)isc_buffer_copyregion(nbuf, &r); (void)isc_buffer_copyregion(nbuf, data); isc_buffer_free(&buf); dctx->ctxdata.generic = nbuf; return (ISC_R_SUCCESS); } static isc_result_t openssleddsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { isc_result_t ret; dst_key_t *key = dctx->key; isc_region_t tbsreg; isc_region_t sigreg; EVP_PKEY *pkey = key->keydata.pkey; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; size_t siglen; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); if (ctx == NULL) { return (ISC_R_NOMEMORY); } if (key->key_alg == DST_ALG_ED25519) { siglen = DNS_SIG_ED25519SIZE; } else { siglen = DNS_SIG_ED448SIZE; } isc_buffer_availableregion(sig, &sigreg); if (sigreg.length < (unsigned int)siglen) { DST_RET(ISC_R_NOSPACE); } isc_buffer_usedregion(buf, &tbsreg); if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) != 1) { DST_RET(dst__openssl_toresult3( dctx->category, "EVP_DigestSignInit", ISC_R_FAILURE)); } if (EVP_DigestSign(ctx, sigreg.base, &siglen, tbsreg.base, tbsreg.length) != 1) { DST_RET(dst__openssl_toresult3(dctx->category, "EVP_DigestSign", DST_R_SIGNFAILURE)); } isc_buffer_add(sig, (unsigned int)siglen); ret = ISC_R_SUCCESS; err: EVP_MD_CTX_free(ctx); isc_buffer_free(&buf); dctx->ctxdata.generic = NULL; return (ret); } static isc_result_t openssleddsa_verify(dst_context_t *dctx, const isc_region_t *sig) { isc_result_t ret; dst_key_t *key = dctx->key; int status; isc_region_t tbsreg; EVP_PKEY *pkey = key->keydata.pkey; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); isc_buffer_t *buf = (isc_buffer_t *)dctx->ctxdata.generic; unsigned int siglen = 0; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); if (ctx == NULL) { return (dst__openssl_toresult(ISC_R_NOMEMORY)); } #if HAVE_OPENSSL_ED25519 if (key->key_alg == DST_ALG_ED25519) { siglen = DNS_SIG_ED25519SIZE; } #endif /* if HAVE_OPENSSL_ED25519 */ #if HAVE_OPENSSL_ED448 if (key->key_alg == DST_ALG_ED448) { siglen = DNS_SIG_ED448SIZE; } #endif /* if HAVE_OPENSSL_ED448 */ if (siglen == 0) { DST_RET(ISC_R_NOTIMPLEMENTED); } if (sig->length != siglen) { DST_RET(DST_R_VERIFYFAILURE); } isc_buffer_usedregion(buf, &tbsreg); if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) != 1) { DST_RET(dst__openssl_toresult3( dctx->category, "EVP_DigestVerifyInit", ISC_R_FAILURE)); } status = EVP_DigestVerify(ctx, sig->base, siglen, tbsreg.base, tbsreg.length); switch (status) { case 1: ret = ISC_R_SUCCESS; break; case 0: ret = dst__openssl_toresult(DST_R_VERIFYFAILURE); break; default: ret = dst__openssl_toresult3(dctx->category, "EVP_DigestVerify", DST_R_VERIFYFAILURE); break; } err: EVP_MD_CTX_free(ctx); isc_buffer_free(&buf); dctx->ctxdata.generic = NULL; return (ret); } static bool openssleddsa_compare(const dst_key_t *key1, const dst_key_t *key2) { int status; EVP_PKEY *pkey1 = key1->keydata.pkey; EVP_PKEY *pkey2 = key2->keydata.pkey; if (pkey1 == NULL && pkey2 == NULL) { return (true); } else if (pkey1 == NULL || pkey2 == NULL) { return (false); } status = EVP_PKEY_eq(pkey1, pkey2); if (status == 1) { return (true); } return (false); } static isc_result_t openssleddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { isc_result_t ret; EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *ctx = NULL; int nid = 0, status; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); UNUSED(unused); UNUSED(callback); #if HAVE_OPENSSL_ED25519 if (key->key_alg == DST_ALG_ED25519) { nid = NID_ED25519; key->key_size = DNS_KEY_ED25519SIZE * 8; } #endif /* if HAVE_OPENSSL_ED25519 */ #if HAVE_OPENSSL_ED448 if (key->key_alg == DST_ALG_ED448) { nid = NID_ED448; key->key_size = DNS_KEY_ED448SIZE * 8; } #endif /* if HAVE_OPENSSL_ED448 */ if (nid == 0) { return (ISC_R_NOTIMPLEMENTED); } ctx = EVP_PKEY_CTX_new_id(nid, NULL); if (ctx == NULL) { return (dst__openssl_toresult2("EVP_PKEY_CTX_new_id", DST_R_OPENSSLFAILURE)); } status = EVP_PKEY_keygen_init(ctx); if (status != 1) { DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", DST_R_OPENSSLFAILURE)); } status = EVP_PKEY_keygen(ctx, &pkey); if (status != 1) { DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen", DST_R_OPENSSLFAILURE)); } key->keydata.pkey = pkey; ret = ISC_R_SUCCESS; err: EVP_PKEY_CTX_free(ctx); return (ret); } static bool openssleddsa_isprivate(const dst_key_t *key) { EVP_PKEY *pkey = key->keydata.pkey; size_t len; if (pkey == NULL) { return (false); } if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) == 1 && len > 0) { return (true); } /* can check if first error is EC_R_INVALID_PRIVATE_KEY */ while (ERR_get_error() != 0) { /**/ } return (false); } static void openssleddsa_destroy(dst_key_t *key) { EVP_PKEY *pkey = key->keydata.pkey; EVP_PKEY_free(pkey); key->keydata.pkey = NULL; } static isc_result_t openssleddsa_todns(const dst_key_t *key, isc_buffer_t *data) { EVP_PKEY *pkey = key->keydata.pkey; isc_region_t r; size_t len; REQUIRE(pkey != NULL); REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); if (key->key_alg == DST_ALG_ED25519) { len = DNS_KEY_ED25519SIZE; } else { len = DNS_KEY_ED448SIZE; } isc_buffer_availableregion(data, &r); if (r.length < len) { return (ISC_R_NOSPACE); } if (EVP_PKEY_get_raw_public_key(pkey, r.base, &len) != 1) { return (dst__openssl_toresult(ISC_R_FAILURE)); } isc_buffer_add(data, len); return (ISC_R_SUCCESS); } static isc_result_t openssleddsa_fromdns(dst_key_t *key, isc_buffer_t *data) { isc_result_t ret; isc_region_t r; size_t len; EVP_PKEY *pkey; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); isc_buffer_remainingregion(data, &r); if (r.length == 0) { return (ISC_R_SUCCESS); } len = r.length; ret = raw_key_to_ossl(key->key_alg, 0, r.base, &len, &pkey); if (ret != ISC_R_SUCCESS) { return ret; } isc_buffer_forward(data, len); key->keydata.pkey = pkey; key->key_size = len * 8; return (ISC_R_SUCCESS); } static isc_result_t openssleddsa_tofile(const dst_key_t *key, const char *directory) { isc_result_t ret; dst_private_t priv; unsigned char *buf = NULL; size_t len; int i; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); if (key->keydata.pkey == NULL) { return (DST_R_NULLKEY); } if (key->external) { priv.nelements = 0; return (dst__privstruct_writefile(key, &priv, directory)); } i = 0; if (openssleddsa_isprivate(key)) { if (key->key_alg == DST_ALG_ED25519) { len = DNS_KEY_ED25519SIZE; } else { len = DNS_KEY_ED448SIZE; } buf = isc_mem_get(key->mctx, len); if (EVP_PKEY_get_raw_private_key(key->keydata.pkey, buf, &len) != 1) { DST_RET(dst__openssl_toresult(ISC_R_FAILURE)); } priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY; priv.elements[i].length = len; priv.elements[i].data = buf; i++; } if (key->engine != NULL) { priv.elements[i].tag = TAG_EDDSA_ENGINE; priv.elements[i].length = (unsigned short)strlen(key->engine) + 1; priv.elements[i].data = (unsigned char *)key->engine; i++; } if (key->label != NULL) { priv.elements[i].tag = TAG_EDDSA_LABEL; priv.elements[i].length = (unsigned short)strlen(key->label) + 1; priv.elements[i].data = (unsigned char *)key->label; i++; } priv.nelements = i; ret = dst__privstruct_writefile(key, &priv, directory); err: if (buf != NULL) { isc_mem_put(key->mctx, buf, len); } return (ret); } static isc_result_t eddsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) { if (pubpkey == NULL) { return (ISC_R_SUCCESS); } if (EVP_PKEY_eq(pkey, pubpkey) == 1) { return (ISC_R_SUCCESS); } return (ISC_R_FAILURE); } static isc_result_t openssleddsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { dst_private_t priv; isc_result_t ret; int i, privkey_index = -1; const char *engine = NULL, *label = NULL; EVP_PKEY *pkey = NULL, *pubpkey = NULL; size_t len; isc_mem_t *mctx = key->mctx; REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); /* read private key file */ ret = dst__privstruct_parse(key, DST_ALG_ED25519, lexer, mctx, &priv); if (ret != ISC_R_SUCCESS) { goto err; } if (key->external) { if (priv.nelements != 0) { DST_RET(DST_R_INVALIDPRIVATEKEY); } if (pub == NULL) { DST_RET(DST_R_INVALIDPRIVATEKEY); } key->keydata.pkey = pub->keydata.pkey; pub->keydata.pkey = NULL; dst__privstruct_free(&priv, mctx); isc_safe_memwipe(&priv, sizeof(priv)); return (ISC_R_SUCCESS); } if (pub != NULL) { pubpkey = pub->keydata.pkey; } for (i = 0; i < priv.nelements; i++) { switch (priv.elements[i].tag) { case TAG_EDDSA_ENGINE: engine = (char *)priv.elements[i].data; break; case TAG_EDDSA_LABEL: label = (char *)priv.elements[i].data; break; case TAG_EDDSA_PRIVATEKEY: privkey_index = i; break; default: break; } } if (label != NULL) { ret = openssleddsa_fromlabel(key, engine, label, NULL); if (ret != ISC_R_SUCCESS) { goto err; } if (eddsa_check(key->keydata.pkey, pubpkey) != ISC_R_SUCCESS) { DST_RET(DST_R_INVALIDPRIVATEKEY); } DST_RET(ISC_R_SUCCESS); } if (privkey_index < 0) { DST_RET(DST_R_INVALIDPRIVATEKEY); } len = priv.elements[privkey_index].length; ret = raw_key_to_ossl(key->key_alg, 1, priv.elements[privkey_index].data, &len, &pkey); if (ret != ISC_R_SUCCESS) { goto err; } if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) { EVP_PKEY_free(pkey); DST_RET(DST_R_INVALIDPRIVATEKEY); } key->keydata.pkey = pkey; key->key_size = len * 8; ret = ISC_R_SUCCESS; err: dst__privstruct_free(&priv, mctx); isc_safe_memwipe(&priv, sizeof(priv)); return (ret); } static isc_result_t openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label, const char *pin) { #if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 isc_result_t ret; ENGINE *e; EVP_PKEY *pkey = NULL, *pubpkey = NULL; int baseid = EVP_PKEY_NONE; UNUSED(pin); REQUIRE(key->key_alg == DST_ALG_ED25519 || key->key_alg == DST_ALG_ED448); #if HAVE_OPENSSL_ED25519 if (key->key_alg == DST_ALG_ED25519) { baseid = EVP_PKEY_ED25519; } #endif /* if HAVE_OPENSSL_ED25519 */ #if HAVE_OPENSSL_ED448 if (key->key_alg == DST_ALG_ED448) { baseid = EVP_PKEY_ED448; } #endif /* if HAVE_OPENSSL_ED448 */ if (baseid == EVP_PKEY_NONE) { return (ISC_R_NOTIMPLEMENTED); } if (engine == NULL) { return (DST_R_NOENGINE); } e = dst__openssl_getengine(engine); if (e == NULL) { return (DST_R_NOENGINE); } pkey = ENGINE_load_private_key(e, label, NULL, NULL); if (pkey == NULL) { return (dst__openssl_toresult2("ENGINE_load_private_key", ISC_R_NOTFOUND)); } if (EVP_PKEY_base_id(pkey) != baseid) { DST_RET(DST_R_INVALIDPRIVATEKEY); } pubpkey = ENGINE_load_public_key(e, label, NULL, NULL); if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) { DST_RET(DST_R_INVALIDPRIVATEKEY); } key->engine = isc_mem_strdup(key->mctx, engine); key->label = isc_mem_strdup(key->mctx, label); key->key_size = EVP_PKEY_bits(pkey); key->keydata.pkey = pkey; pkey = NULL; ret = ISC_R_SUCCESS; err: if (pubpkey != NULL) { EVP_PKEY_free(pubpkey); } if (pkey != NULL) { EVP_PKEY_free(pkey); } return (ret); #else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ UNUSED(key); UNUSED(engine); UNUSED(label); UNUSED(pin); return (DST_R_NOENGINE); #endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */ } static dst_func_t openssleddsa_functions = { openssleddsa_createctx, NULL, /*%< createctx2 */ openssleddsa_destroyctx, openssleddsa_adddata, openssleddsa_sign, openssleddsa_verify, NULL, /*%< verify2 */ NULL, /*%< computesecret */ openssleddsa_compare, NULL, /*%< paramcompare */ openssleddsa_generate, openssleddsa_isprivate, openssleddsa_destroy, openssleddsa_todns, openssleddsa_fromdns, openssleddsa_tofile, openssleddsa_parse, NULL, /*%< cleanup */ openssleddsa_fromlabel, NULL, /*%< dump */ NULL, /*%< restore */ }; isc_result_t dst__openssleddsa_init(dst_func_t **funcp) { REQUIRE(funcp != NULL); if (*funcp == NULL) { *funcp = &openssleddsa_functions; } return (ISC_R_SUCCESS); } #endif /* HAVE_OPENSSL_ED25519 || HAVE_OPENSSL_ED448 */