diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c index 72f4608d99..6be1677e1e 100644 --- a/lib/dns/opensslecdsa_link.c +++ b/lib/dns/opensslecdsa_link.c @@ -17,6 +17,9 @@ #include #include #include +#if !defined(OPENSSL_NO_ENGINE) +#include +#endif #include #include @@ -495,6 +498,7 @@ opensslecdsa_tofile(const dst_key_t *key, const char *directory) { const BIGNUM *privkey; dst_private_t priv; unsigned char *buf = NULL; + unsigned short i; if (key->keydata.pkey == NULL) { return (DST_R_NULLKEY); @@ -512,16 +516,37 @@ opensslecdsa_tofile(const dst_key_t *key, const char *directory) { } privkey = EC_KEY_get0_private_key(eckey); if (privkey == NULL) { - DST_RET(ISC_R_FAILURE); + ret = dst__openssl_toresult(DST_R_OPENSSLFAILURE); + goto err; } buf = isc_mem_get(key->mctx, BN_num_bytes(privkey)); - priv.elements[0].tag = TAG_ECDSA_PRIVATEKEY; - priv.elements[0].length = BN_num_bytes(privkey); + i = 0; + + priv.elements[i].tag = TAG_ECDSA_PRIVATEKEY; + priv.elements[i].length = BN_num_bytes(privkey); BN_bn2bin(privkey, buf); - priv.elements[0].data = buf; - priv.nelements = 1; + priv.elements[i].data = buf; + i++; + + if (key->engine != NULL) { + priv.elements[i].tag = TAG_ECDSA_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_RSA_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: @@ -533,121 +558,327 @@ err: } static isc_result_t -ecdsa_check(EC_KEY *eckey, dst_key_t *pub) { - isc_result_t ret = ISC_R_FAILURE; - EVP_PKEY *pkey; - EC_KEY *pubeckey = NULL; +ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) { const EC_POINT *pubkey; - if (pub == NULL) { - return (ISC_R_SUCCESS); - } - pkey = pub->keydata.pkey; - if (pkey == NULL) { - return (ISC_R_SUCCESS); - } - pubeckey = EVP_PKEY_get1_EC_KEY(pkey); - if (pubeckey == NULL) { - return (ISC_R_SUCCESS); - } pubkey = EC_KEY_get0_public_key(pubeckey); if (pubkey == NULL) { - DST_RET(ISC_R_SUCCESS); + return (ISC_R_SUCCESS); } if (EC_KEY_set_public_key(eckey, pubkey) != 1) { - DST_RET(ISC_R_SUCCESS); + return (ISC_R_SUCCESS); } if (EC_KEY_check_key(eckey) == 1) { - DST_RET(ISC_R_SUCCESS); + return (ISC_R_SUCCESS); } -err: - EC_KEY_free(pubeckey); - return (ret); + return (ISC_R_FAILURE); +} + +static bool +uses_engine(const dst_private_t *priv, const char **engine, + const char **label) { + for (unsigned short i = 0; i < priv->nelements; i++) { + switch (priv->elements[i].tag) { + case TAG_ECDSA_ENGINE: + *engine = (char *)priv->elements[i].data; + break; + case TAG_ECDSA_LABEL: + *label = (char *)priv->elements[i].data; + break; + default: + break; + } + } + if (*label != NULL) { + return (true); + } + + return (false); } static isc_result_t -opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { - dst_private_t priv; - isc_result_t ret; - EVP_PKEY *pkey; - EC_KEY *eckey = NULL; - BIGNUM *privkey = NULL; - int group_nid; - isc_mem_t *mctx = key->mctx; +load_privkey_from_privstruct(EC_KEY *eckey, dst_private_t *priv) { + BIGNUM *privkey = BN_bin2bn(priv->elements[0].data, + priv->elements[0].length, NULL); + isc_result_t result = ISC_R_SUCCESS; - REQUIRE(key->key_alg == DST_ALG_ECDSA256 || - key->key_alg == DST_ALG_ECDSA384); - - /* read private key file */ - ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, mctx, &priv); - if (ret != ISC_R_SUCCESS) { - goto err; + if (privkey == NULL) { + return (ISC_R_NOMEMORY); } - 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 (!EC_KEY_set_private_key(eckey, privkey)) { + result = ISC_R_NOMEMORY; } - if (key->key_alg == DST_ALG_ECDSA256) { - group_nid = NID_X9_62_prime256v1; - } else { - group_nid = NID_secp384r1; + BN_clear_free(privkey); + return (result); +} + +#if !defined(OPENSSL_NO_ENGINE) +static isc_result_t +load_pubkey_from_engine(EC_KEY *eckey, const char *engine, const char *label) { + if (engine == NULL || label == NULL) { + return (DST_R_NOENGINE); } - eckey = EC_KEY_new_by_curve_name(group_nid); + ENGINE *ep = dst__openssl_getengine(engine); + ; + if (ep == NULL) { + return (DST_R_NOENGINE); + } + + EVP_PKEY *pubkey = ENGINE_load_private_key(ep, label, NULL, NULL); + if (pubkey == NULL) { + return (dst__openssl_toresult2("ENGINE_load_public_key", + ISC_R_NOTFOUND)); + } + + eckey = EVP_PKEY_get1_EC_KEY(pubkey); + EVP_PKEY_free(pubkey); + if (eckey == NULL) { return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } - privkey = BN_bin2bn(priv.elements[0].data, priv.elements[0].length, - NULL); - if (privkey == NULL) { - DST_RET(ISC_R_NOMEMORY); - } - if (!EC_KEY_set_private_key(eckey, privkey)) { - DST_RET(ISC_R_NOMEMORY); - } - if (ecdsa_check(eckey, pub) != ISC_R_SUCCESS) { - DST_RET(DST_R_INVALIDPRIVATEKEY); + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_privkey_from_engine(EC_KEY *eckey, const char *engine, const char *label) { + if (engine == NULL || label == NULL) { + return (DST_R_NOENGINE); } - pkey = EVP_PKEY_new(); - if (pkey == NULL) { - DST_RET(ISC_R_NOMEMORY); + ENGINE *ep = dst__openssl_getengine(engine); + ; + if (ep == NULL) { + return (DST_R_NOENGINE); } - if (!EVP_PKEY_set1_EC_KEY(pkey, eckey)) { - EVP_PKEY_free(pkey); - DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + + EVP_PKEY *privkey = ENGINE_load_private_key(ep, label, NULL, NULL); + if (privkey == NULL) { + return (dst__openssl_toresult2("ENGINE_load_private_key", + ISC_R_NOTFOUND)); } + + eckey = EVP_PKEY_get1_EC_KEY(privkey); + EVP_PKEY_free(privkey); + + if (eckey == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + + return (ISC_R_SUCCESS); +} +#else +static isc_result_t +load_pubkey_from_engine(EC_KEY *eckey, const char *engine, const char *label) { + UNUSED(eckey); + UNUSED(engine); + UNUSED(label); + + return (DST_R_NOENGINE); +} + +static isc_result_t +load_privkey_from_engine(EC_KEY *eckey, const char *engine, const char *label) { + UNUSED(eckey); + UNUSED(engine); + UNUSED(label); + + return (DST_R_NOENGINE); +} +#endif + +static isc_result_t +load_privkey(EC_KEY *eckey, dst_private_t *priv, const char **engine, + const char **label) { + if (uses_engine(priv, engine, label)) { + return (load_privkey_from_engine(eckey, *engine, *label)); + } else { + return (load_privkey_from_privstruct(eckey, priv)); + } +} + +static isc_result_t +eckey_to_pkey(EC_KEY *eckey, EVP_PKEY **pkey) { + REQUIRE(pkey != NULL && *pkey == NULL); + + *pkey = EVP_PKEY_new(); + if (*pkey == NULL) { + return (ISC_R_NOMEMORY); + } + if (!EVP_PKEY_set1_EC_KEY(*pkey, eckey)) { + EVP_PKEY_free(*pkey); + *pkey = NULL; + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +finalize_eckey(dst_key_t *key, EC_KEY *eckey, const char *engine, + const char *label) { + isc_result_t result = ISC_R_SUCCESS; + EVP_PKEY *pkey = NULL; + + result = eckey_to_pkey(eckey, &pkey); + if (result != ISC_R_SUCCESS) { + return (result); + } + key->keydata.pkey = pkey; + + if (label != NULL) { + key->label = isc_mem_strdup(key->mctx, label); + key->engine = isc_mem_strdup(key->mctx, engine); + } + if (key->key_alg == DST_ALG_ECDSA256) { key->key_size = DNS_KEY_ECDSA256SIZE * 4; } else { key->key_size = DNS_KEY_ECDSA384SIZE * 4; } - ret = ISC_R_SUCCESS; -err: - if (privkey != NULL) { - BN_clear_free(privkey); + return (ISC_R_SUCCESS); +} + +static isc_result_t +dst__key_to_eckey(dst_key_t *key, EC_KEY **eckey) { + REQUIRE(eckey != NULL && *eckey == NULL); + + int group_nid; + switch (key->key_alg) { + case DST_ALG_ECDSA256: + group_nid = NID_X9_62_prime256v1; + break; + case DST_ALG_ECDSA384: + group_nid = NID_secp384r1; + break; + default: + INSIST(0); + ISC_UNREACHABLE(); + } + *eckey = EC_KEY_new_by_curve_name(group_nid); + if (*eckey == NULL) { + return (dst__openssl_toresult(DST_R_OPENSSLFAILURE)); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { + dst_private_t priv; + isc_result_t result = ISC_R_SUCCESS; + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; + const char *engine = NULL; + const char *label = NULL; + + /* read private key file */ + result = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, key->mctx, + &priv); + if (result != ISC_R_SUCCESS) { + goto end; + } + + if (key->external) { + if (priv.nelements != 0 || pub == NULL) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + key->keydata.pkey = pub->keydata.pkey; + pub->keydata.pkey = NULL; + goto end; + } + + if (pub != NULL && pub->keydata.pkey != NULL) { + pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey); + } + + result = dst__key_to_eckey(key, &eckey); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = load_privkey(eckey, &priv, &engine, &label); + if (result != ISC_R_SUCCESS) { + goto end; + } + + if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + + result = finalize_eckey(key, eckey, engine, label); + +end: + if (pubeckey != NULL) { + EC_KEY_free(pubeckey); } if (eckey != NULL) { EC_KEY_free(eckey); } - dst__privstruct_free(&priv, mctx); + dst__privstruct_free(&priv, key->mctx); isc_safe_memwipe(&priv, sizeof(priv)); - return (ret); + return (result); +} + +static isc_result_t +opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label, + const char *pin) { +#if !defined(OPENSSL_NO_ENGINE) + isc_result_t result = ISC_R_SUCCESS; + EC_KEY *eckey = NULL; + EC_KEY *pubeckey = NULL; + + UNUSED(pin); + + result = dst__key_to_eckey(key, &eckey); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = dst__key_to_eckey(key, &pubeckey); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = load_pubkey_from_engine(pubeckey, engine, label); + if (result != ISC_R_SUCCESS) { + goto end; + } + + result = load_privkey_from_engine(eckey, engine, label); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) { + result = DST_R_INVALIDPRIVATEKEY; + goto end; + } + + result = finalize_eckey(key, eckey, engine, label); + +end: + if (pubeckey != NULL) { + EC_KEY_free(pubeckey); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } + + return (result); +#else + UNUSED(key); + UNUSED(engine); + UNUSED(label); + UNUSED(pin); + return (DST_R_NOENGINE); +#endif } static dst_func_t opensslecdsa_functions = { @@ -668,10 +899,10 @@ static dst_func_t opensslecdsa_functions = { opensslecdsa_fromdns, opensslecdsa_tofile, opensslecdsa_parse, - NULL, /*%< cleanup */ - NULL, /*%< fromlabel */ - NULL, /*%< dump */ - NULL, /*%< restore */ + NULL, /*%< cleanup */ + opensslecdsa_fromlabel, /*%< fromlabel */ + NULL, /*%< dump */ + NULL, /*%< restore */ }; isc_result_t