From 6811a8490abbba6256a42898eeaeedb5698fadf5 Mon Sep 17 00:00:00 2001 From: Michal Nowak Date: Mon, 23 Feb 2026 17:30:50 +0100 Subject: [PATCH 1/3] Enable Edwards curves with PKCS#11 Ed25519 and Ed448 support (PKCS#11 v3.2) was added to libp11-0.4.17. --- bin/tests/system/enginepkcs11/setup.sh | 4 ++-- bin/tests/system/enginepkcs11/tests.sh | 10 ++++++-- .../system/keyfromlabel/tests_keyfromlabel.py | 23 ++++++++++++++++--- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/bin/tests/system/enginepkcs11/setup.sh b/bin/tests/system/enginepkcs11/setup.sh index 0f6cbc6932..459a6ea623 100644 --- a/bin/tests/system/enginepkcs11/setup.sh +++ b/bin/tests/system/enginepkcs11/setup.sh @@ -49,8 +49,8 @@ mkdir ns1/keys dir="ns1" infile="${dir}/template.db.in" for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \ - ecdsap256sha256:EC:prime256v1 ecdsap384sha384:EC:prime384v1; do # Edwards curves are not yet supported by OpenSC - # ed25519:EC:edwards25519 ed448:EC:edwards448 + ecdsap256sha256:EC:prime256v1 ecdsap384sha384:EC:prime384v1 \ + ed25519:EC:Ed25519 ed448:EC:Ed448; do alg=$(echo "$algtypebits" | cut -f 1 -d :) type=$(echo "$algtypebits" | cut -f 2 -d :) bits=$(echo "$algtypebits" | cut -f 3 -d :) diff --git a/bin/tests/system/enginepkcs11/tests.sh b/bin/tests/system/enginepkcs11/tests.sh index 138b0483a8..0546a7c62a 100644 --- a/bin/tests/system/enginepkcs11/tests.sh +++ b/bin/tests/system/enginepkcs11/tests.sh @@ -50,11 +50,17 @@ check_keys() { cd ns1 for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \ - ecdsap256sha256:EC:prime256v1 ecdsap384sha384:EC:prime384v1; do # Edwards curves are not yet supported by OpenSC - # ed25519:EC:edwards25519 ed448:EC:edwards448 + ecdsap256sha256:EC:prime256v1 ecdsap384sha384:EC:prime384v1 \ + ed25519:EC:Ed25519 ed448:EC:Ed448; do alg=$(echo "$algtypebits" | cut -f 1 -d :) type=$(echo "$algtypebits" | cut -f 2 -d :) bits=$(echo "$algtypebits" | cut -f 3 -d :) + alg_upper=$(echo "$alg" | tr '[:lower:]' '[:upper:]') + supported=$(eval "echo \$${alg_upper}_SUPPORTED") + if [ "${supported}" != 1 ]; then + echo_i "skipping test for ${alg}:${type}:${bits}, not supported by this build" + continue + fi zone="${alg}.example" zonefile="zone.${zone}.db.signed" diff --git a/bin/tests/system/keyfromlabel/tests_keyfromlabel.py b/bin/tests/system/keyfromlabel/tests_keyfromlabel.py index ad3ad01603..948d308ef9 100644 --- a/bin/tests/system/keyfromlabel/tests_keyfromlabel.py +++ b/bin/tests/system/keyfromlabel/tests_keyfromlabel.py @@ -17,6 +17,8 @@ import shutil import pytest +from isctest.util import param + import isctest.mark pytestmark = [ @@ -93,9 +95,24 @@ def token_init_and_cleanup(): ("rsasha512", "rsa", "2048"), ("ecdsap256sha256", "EC", "prime256v1"), ("ecdsap384sha384", "EC", "prime384v1"), - # Edwards curves are not yet supported by OpenSC - # ("ed25519","EC","edwards25519"), - # ("ed448","EC","edwards448") + param( + "ed25519", + "EC", + "Ed25519", + marks=pytest.mark.skipif( + os.environ.get("ED25519_SUPPORTED") != "1", + reason="Ed25519 not supported by this build", + ), + ), + param( + "ed448", + "EC", + "Ed448", + marks=pytest.mark.skipif( + os.environ.get("ED448_SUPPORTED") != "1", + reason="Ed448 not supported by this build", + ), + ), ], ) def test_keyfromlabel(alg_name, alg_type, alg_bits): From 945838e62136f3dfc24ec2ce46c8305e292d9e67 Mon Sep 17 00:00:00 2001 From: Michal Nowak Date: Thu, 14 May 2026 14:50:18 +0000 Subject: [PATCH 2/3] Tolerate non-extractable Ed25519/Ed448 private keys in tofile openssleddsa_tofile() called EVP_PKEY_get_raw_private_key() unconditionally whenever the dst_key_t had a private EVP_PKEY attached and aborted with ISC_R_FAILURE on any error. That is wrong for keys whose private material lives in a hardware token (PKCS#11): the provider deliberately refuses to export the raw bytes, but the keypair is still valid and the .private file should be written containing only the PKCS#11 label, with no raw key material. Without this, "dnssec-keyfromlabel -a ed25519 -l pkcs11:..." fails with "failed to write key ...: failure" even though pkcs11-tool has generated a valid Ed25519 key in SoftHSM. Mirror the behaviour already implemented in opensslecdsa_tofile(): if the raw private key cannot be retrieved AND the key has a PKCS#11 label to fall back on, clear the OpenSSL error queue and fall through to writing just the Label element. If extraction fails and there is no label to fall back on, return the OpenSSL failure rather than silently producing a .private file with neither raw key material nor a label, which would be unusable on the next load. Consolidate buffer cleanup into a single cleanup: path, freeing with the original allocation size (alginfo->key_size) rather than the potentially-modified len output parameter. Assisted-by: Claude:claude-opus-4-7 --- lib/dns/openssleddsa_link.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c index f9f1a0bdf0..26cc360727 100644 --- a/lib/dns/openssleddsa_link.c +++ b/lib/dns/openssleddsa_link.c @@ -371,14 +371,22 @@ openssleddsa_tofile(const dst_key_t *key, const char *directory) { len = alginfo->key_size; buf = isc_mem_get(key->mctx, len); if (EVP_PKEY_get_raw_private_key(key->keydata.pkeypair.priv, - buf, &len) != 1) + buf, &len) == 1) { - CLEANUP(dst__openssl_toresult(ISC_R_FAILURE)); + priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY; + priv.elements[i].length = len; + priv.elements[i].data = buf; + i++; + } else if (key->label != NULL) { + /* + * The raw private key is not extractable + * (e.g. HSM-backed via PKCS#11); fall through to + * writing only the label. + */ + ERR_clear_error(); + } else { + CLEANUP(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } - priv.elements[i].tag = TAG_EDDSA_PRIVATEKEY; - priv.elements[i].length = len; - priv.elements[i].data = buf; - i++; } if (key->label != NULL) { priv.elements[i].tag = TAG_EDDSA_LABEL; @@ -393,7 +401,7 @@ openssleddsa_tofile(const dst_key_t *key, const char *directory) { cleanup: if (buf != NULL) { - isc_mem_put(key->mctx, buf, len); + isc_mem_put(key->mctx, buf, alginfo->key_size); } return result; } From 5ebf17741f1f7ed46b4af49a024ea321d42335c2 Mon Sep 17 00:00:00 2001 From: Michal Nowak Date: Thu, 14 May 2026 14:50:58 +0000 Subject: [PATCH 3/3] Generate Ed25519/Ed448 keys via PKCS#11 when a label is set When a dst_key_t carries a PKCS#11 URI in key->label (as named does for dnssec-policy zones backed by a key-store "hsm"), key generation must happen inside the HSM, not in software. opensslecdsa_generate already branches on key->label and calls the matching pkcs11 wrapper; the EDDSA generator silently ignored the label and produced a software key, which named then wrote to the .private file with both a Label: line and the raw PrivateKey: bytes -- a corrupt hybrid record that prevented zone signing. Add the missing wrapper: - lib/isc/ossl_wrap/ossl3.c gains generate_pkcs11_eddsa_key() and the public isc_ossl_wrap_generate_pkcs11_ed25519_key() / isc_ossl_wrap_generate_pkcs11_ed448_key() entry points. They use EVP_PKEY_CTX_new_from_name(NULL, "ED25519" or "ED448", "provider=pkcs11") with the pkcs11_uri and pkcs11_key_usage parameters, mirroring the existing EC wrapper. - lib/isc/ossl_wrap/ossl1_1.c provides stubs returning ISC_R_NOTIMPLEMENTED for the new EDDSA wrappers; the pkcs11-provider stack requires OpenSSL 3. The pre-existing isc_ossl_wrap_generate_pkcs11_rsa_key() stub used to silently delegate to software keygen -- that hid the same "HSM label on a software key" hazard for RSA on OpenSSL 1.1 builds, so align it with the EDDSA stubs and return ISC_R_NOTIMPLEMENTED too. - lib/isc/include/isc/ossl_wrap.h declares the new wrappers. - lib/dns/openssleddsa_link.c routes openssleddsa_generate() through the new wrappers when key->label is non-NULL, leaving the existing EVP_PKEY_keygen() path untouched for software keys. The Ed448 case is guarded by HAVE_OPENSSL_ED448 to match the surrounding code. Assisted-by: Claude:claude-opus-4-7 --- lib/dns/openssleddsa_link.c | 22 +++++++++++++ lib/isc/include/isc/ossl_wrap.h | 22 +++++++++++++ lib/isc/ossl_wrap/ossl1_1.c | 28 ++++++++++++++-- lib/isc/ossl_wrap/ossl3.c | 57 +++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c index 26cc360727..0d30defcc4 100644 --- a/lib/dns/openssleddsa_link.c +++ b/lib/dns/openssleddsa_link.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -270,6 +271,27 @@ openssleddsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { UNUSED(unused); UNUSED(callback); + if (key->label != NULL) { + switch (key->key_alg) { + case DST_ALG_ED25519: + RETERR(isc_ossl_wrap_generate_pkcs11_ed25519_key( + key->label, &pkey)); + break; +#if HAVE_OPENSSL_ED448 + case DST_ALG_ED448: + RETERR(isc_ossl_wrap_generate_pkcs11_ed448_key( + key->label, &pkey)); + break; +#endif /* HAVE_OPENSSL_ED448 */ + default: + UNREACHABLE(); + } + key->key_size = alginfo->key_size * 8; + key->keydata.pkeypair.priv = pkey; + key->keydata.pkeypair.pub = pkey; + return ISC_R_SUCCESS; + } + ctx = EVP_PKEY_CTX_new_id(alginfo->nid, NULL); if (ctx == NULL) { return dst__openssl_toresult2("EVP_PKEY_CTX_new_id", diff --git a/lib/isc/include/isc/ossl_wrap.h b/lib/isc/include/isc/ossl_wrap.h index 4831fab405..cfb1cf2faf 100644 --- a/lib/isc/include/isc/ossl_wrap.h +++ b/lib/isc/include/isc/ossl_wrap.h @@ -131,6 +131,28 @@ isc_ossl_wrap_generate_pkcs11_p384_key(char *uri, EVP_PKEY **pkeyp); * \li `uri != NULL` and is a NUL-terminated string */ +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed25519_key(char *uri, EVP_PKEY **pkeyp); +/*% + * Generates an Ed25519 key using the PKCS#11 label specified at `uri`. + * + * Requires: + * \li pkeyp != NULL + * \li *pkeyp == NULL + * \li `uri != NULL` and is a NUL-terminated string + */ + +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed448_key(char *uri, EVP_PKEY **pkeyp); +/*% + * Generates an Ed448 key using the PKCS#11 label specified at `uri`. + * + * Requires: + * \li pkeyp != NULL + * \li *pkeyp == NULL + * \li `uri != NULL` and is a NUL-terminated string + */ + isc_result_t isc_ossl_wrap_load_p384_public_from_region(isc_region_t region, EVP_PKEY **pkeyp); diff --git a/lib/isc/ossl_wrap/ossl1_1.c b/lib/isc/ossl_wrap/ossl1_1.c index de325c47c4..6a5740a3b8 100644 --- a/lib/isc/ossl_wrap/ossl1_1.c +++ b/lib/isc/ossl_wrap/ossl1_1.c @@ -410,9 +410,33 @@ cleanup: isc_result_t isc_ossl_wrap_generate_pkcs11_rsa_key(char *uri, size_t bit_size, EVP_PKEY **pkeyp) { - UNUSED(uri); + REQUIRE(uri != NULL); + REQUIRE(pkeyp != NULL && *pkeyp == NULL); - return isc_ossl_wrap_generate_rsa_key(NULL, bit_size, pkeyp); + UNUSED(uri); + UNUSED(bit_size); + UNUSED(pkeyp); + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed25519_key(char *uri, EVP_PKEY **pkeyp) { + REQUIRE(uri != NULL); + REQUIRE(pkeyp != NULL && *pkeyp == NULL); + + UNUSED(uri); + UNUSED(pkeyp); + return ISC_R_NOTIMPLEMENTED; +} + +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed448_key(char *uri, EVP_PKEY **pkeyp) { + REQUIRE(uri != NULL); + REQUIRE(pkeyp != NULL && *pkeyp == NULL); + + UNUSED(uri); + UNUSED(pkeyp); + return ISC_R_NOTIMPLEMENTED; } bool diff --git a/lib/isc/ossl_wrap/ossl3.c b/lib/isc/ossl_wrap/ossl3.c index 92334b2d1c..7021ff5f5d 100644 --- a/lib/isc/ossl_wrap/ossl3.c +++ b/lib/isc/ossl_wrap/ossl3.c @@ -239,6 +239,60 @@ cleanup: return result; } +static isc_result_t +generate_pkcs11_eddsa_key(char *uri, EVP_PKEY **pkeyp, const char *keytype) { + isc_result_t result; + EVP_PKEY_CTX *pctx = NULL; + size_t len; + + INSIST(uri != NULL); + len = strlen(uri); + + const OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string("pkcs11_uri", uri, len), + OSSL_PARAM_utf8_string("pkcs11_key_usage", pkcs11_key_usage, + sizeof(pkcs11_key_usage) - 1), + OSSL_PARAM_END, + }; + + pctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, "provider=pkcs11"); + if (pctx == NULL) { + CLEANUP(OSSL_WRAP_ERROR("EVP_PKEY_CTX_new_from_name")); + } + + if (EVP_PKEY_keygen_init(pctx) != 1) { + CLEANUP(OSSL_WRAP_ERROR("EVP_PKEY_keygen_init")); + } + + if (EVP_PKEY_CTX_set_params(pctx, params) != 1) { + CLEANUP(OSSL_WRAP_ERROR("EVP_PKEY_CTX_set_params")); + } + + if (EVP_PKEY_generate(pctx, pkeyp) != 1) { + CLEANUP(OSSL_WRAP_ERROR("EVP_PKEY_generate")); + } + + result = ISC_R_SUCCESS; + +cleanup: + EVP_PKEY_CTX_free(pctx); + return result; +} + +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed25519_key(char *uri, EVP_PKEY **pkeyp) { + REQUIRE(pkeyp != NULL && *pkeyp == NULL); + REQUIRE(uri != NULL); + return generate_pkcs11_eddsa_key(uri, pkeyp, "ED25519"); +} + +isc_result_t +isc_ossl_wrap_generate_pkcs11_ed448_key(char *uri, EVP_PKEY **pkeyp) { + REQUIRE(pkeyp != NULL && *pkeyp == NULL); + REQUIRE(uri != NULL); + return generate_pkcs11_eddsa_key(uri, pkeyp, "ED448"); +} + static isc_result_t validate_ec_pkey(EVP_PKEY *pkey, const OSSL_PARAM *const curve_params) { isc_result_t result; @@ -523,6 +577,9 @@ isc_ossl_wrap_generate_pkcs11_rsa_key(char *uri, size_t bit_size, int status; size_t len; + REQUIRE(uri != NULL); + REQUIRE(pkeyp != NULL && *pkeyp == NULL); + len = strlen(uri); INSIST(len != 0);