]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add initial support for ECDSA keys via OpenSSL PKCS#11 engine
authorOndřej Surý <ondrej@isc.org>
Fri, 7 Feb 2020 13:20:54 +0000 (14:20 +0100)
committerOndřej Surý <ondrej@sury.org>
Fri, 1 May 2020 08:36:45 +0000 (10:36 +0200)
lib/dns/opensslecdsa_link.c

index 72f4608d99fc4fbc16ed331e7c7227b775fa4627..6be1677e1e4419a734220e4e9e38b25f83847058 100644 (file)
@@ -17,6 +17,9 @@
 #include <openssl/ecdsa.h>
 #include <openssl/err.h>
 #include <openssl/objects.h>
+#if !defined(OPENSSL_NO_ENGINE)
+#include <openssl/engine.h>
+#endif
 
 #include <isc/mem.h>
 #include <isc/safe.h>
@@ -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) {
+       pubkey = EC_KEY_get0_public_key(pubeckey);
+       if (pubkey == NULL) {
                return (ISC_R_SUCCESS);
        }
-       pkey = pub->keydata.pkey;
-       if (pkey == NULL) {
+       if (EC_KEY_set_public_key(eckey, pubkey) != 1) {
                return (ISC_R_SUCCESS);
        }
-       pubeckey = EVP_PKEY_get1_EC_KEY(pkey);
-       if (pubeckey == NULL) {
+       if (EC_KEY_check_key(eckey) == 1) {
                return (ISC_R_SUCCESS);
        }
-       pubkey = EC_KEY_get0_public_key(pubeckey);
-       if (pubkey == NULL) {
-               DST_RET(ISC_R_SUCCESS);
-       }
-       if (EC_KEY_set_public_key(eckey, pubkey) != 1) {
-               DST_RET(ISC_R_SUCCESS);
+
+       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 (EC_KEY_check_key(eckey) == 1) {
-               DST_RET(ISC_R_SUCCESS);
+       if (*label != NULL) {
+               return (true);
        }
 
-err:
-       EC_KEY_free(pubeckey);
-       return (ret);
+       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);
+       if (privkey == NULL) {
+               return (ISC_R_NOMEMORY);
+       }
 
-       /* read private key file */
-       ret = dst__privstruct_parse(key, DST_ALG_ECDSA256, lexer, mctx, &priv);
-       if (ret != ISC_R_SUCCESS) {
-               goto err;
+       if (!EC_KEY_set_private_key(eckey, privkey)) {
+               result = 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);
+       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);
        }
 
-       if (key->key_alg == DST_ALG_ECDSA256) {
-               group_nid = NID_X9_62_prime256v1;
-       } else {
-               group_nid = NID_secp384r1;
+       ENGINE *ep = dst__openssl_getengine(engine);
+       ;
+       if (ep == NULL) {
+               return (DST_R_NOENGINE);
        }
 
-       eckey = EC_KEY_new_by_curve_name(group_nid);
+       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);
+       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);
+       }
+
+       ENGINE *ep = dst__openssl_getengine(engine);
+       ;
+       if (ep == NULL) {
+               return (DST_R_NOENGINE);
+       }
+
+       EVP_PKEY *privkey = ENGINE_load_private_key(ep, label, NULL, NULL);
        if (privkey == NULL) {
-               DST_RET(ISC_R_NOMEMORY);
+               return (dst__openssl_toresult2("ENGINE_load_private_key",
+                                              ISC_R_NOTFOUND));
        }
-       if (!EC_KEY_set_private_key(eckey, privkey)) {
-               DST_RET(ISC_R_NOMEMORY);
+
+       eckey = EVP_PKEY_get1_EC_KEY(privkey);
+       EVP_PKEY_free(privkey);
+
+       if (eckey == NULL) {
+               return (dst__openssl_toresult(DST_R_OPENSSLFAILURE));
        }
-       if (ecdsa_check(eckey, pub) != ISC_R_SUCCESS) {
-               DST_RET(DST_R_INVALIDPRIVATEKEY);
+
+       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));
        }
+}
 
-       pkey = EVP_PKEY_new();
-       if (pkey == NULL) {
-               DST_RET(ISC_R_NOMEMORY);
+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);
-               DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+       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