diff --git a/kdc/pkinit-ec.c b/kdc/pkinit-ec.c index c718aa799..31a5fe7de 100644 --- a/kdc/pkinit-ec.c +++ b/kdc/pkinit-ec.c @@ -52,10 +52,16 @@ */ #ifdef HAVE_HCRYPTO_W_OPENSSL -#include <openssl/ec.h> -#include <openssl/ecdh.h> #include <openssl/evp.h> +#include <openssl/ec.h> +#include <openssl/ecdsa.h> +#include <openssl/rsa.h> #include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/objects.h> +#ifdef HAVE_OPENSSL_30 +#include <openssl/core_names.h> +#endif #define HEIM_NO_CRYPTO_HDRS #endif /* HAVE_HCRYPTO_W_OPENSSL */ @@ -69,37 +75,101 @@ #include <pkinit_asn1.h> #include <hx509.h> - -#ifdef HAVE_HCRYPTO_W_OPENSSL -static void -free_client_ec_param(krb5_context context, - EC_KEY *ec_key_pk, - EC_KEY *ec_key_key) -{ - if (ec_key_pk != NULL) - EC_KEY_free(ec_key_pk); - if (ec_key_key != NULL) - EC_KEY_free(ec_key_key); -} -#endif +#include "../lib/hx509/hx_locl.h" +#include <hx509-private.h> void _kdc_pk_free_client_ec_param(krb5_context context, - void *ec_key_pk, - void *ec_key_key) + void *k0, + void *k1) { #ifdef HAVE_HCRYPTO_W_OPENSSL - free_client_ec_param(context, ec_key_pk, ec_key_key); +#ifdef HAVE_OPENSSL_30 + EVP_PKEY_free(k0); + EVP_PKEY_free(k1); +#else + EC_KEY_free(k0); + EC_KEY_free(k1); +#endif #endif } #ifdef HAVE_HCRYPTO_W_OPENSSL +#ifdef HAVE_OPENSSL_30 static krb5_error_code -generate_ecdh_keyblock(krb5_context context, - EC_KEY *ec_key_pk, /* the client's public key */ - EC_KEY **ec_key_key, /* the KDC's ephemeral private */ - unsigned char **dh_gen_key, /* shared secret */ - size_t *dh_gen_keylen) +generate_ecdh_keyblock_ossl30(krb5_context context, + EVP_PKEY *ec_key_pub, /* the client's public key */ + EVP_PKEY **ec_key_priv, /* the KDC's ephemeral private */ + unsigned char **dh_gen_key, /* shared secret */ + size_t *dh_gen_keylen) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *ephemeral = NULL; + krb5_error_code ret = 0; + unsigned char *p = NULL; + size_t size = 0; + + if (ec_key_pub == NULL) + /* XXX This seems like an internal error that should be impossible */ + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Missing client ECDH key agreement public key"); + if (ret == 0 && + (ephemeral = + EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1))) == NULL) + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Could not generate an ECDH key agreement private key"); + if (ret == 0 && + (pctx = EVP_PKEY_CTX_new(ephemeral, NULL)) == NULL) + ret = krb5_enomem(context); + if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1) + ret = krb5_enomem(context); + if (ret == 0 && + EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1) + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Could not generate an ECDH key agreement private key " + "(EVP_PKEY_CTX_set_dh_kdf_type)"); + if (ret == 0 && + EVP_PKEY_derive_set_peer_ex(pctx, ec_key_pub, 1) != 1) + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Could not generate an ECDH key agreement private key " + "(EVP_PKEY_derive_set_peer_ex)"); + if (ret == 0 && + (EVP_PKEY_derive(pctx, NULL, &size) != 1 || size == 0)) + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Could not generate an ECDH key agreement private key " + "(EVP_PKEY_derive)"); + if (ret == 0 && (p = malloc(size)) == NULL) + ret = krb5_enomem(context); + if (ret == 0 && + (EVP_PKEY_derive(pctx, p, &size) != 1 || size == 0)) + krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC, + "Could not generate an ECDH key agreement private key " + "(EVP_PKEY_derive)"); + + if (ret) { + EVP_PKEY_free(ephemeral); + ephemeral = NULL; + free(p); + p = NULL; + size = 0; + } + + *ec_key_priv = ephemeral; + *dh_gen_keylen = size; + *dh_gen_key = p; + + EVP_PKEY_CTX_free(pctx); + return ret; +} +#else + +/* The empty line above is intentional to work around an mkproto bug */ +static krb5_error_code +generate_ecdh_keyblock_ossl11(krb5_context context, + EC_KEY *ec_key_pk, /* the client's public key */ + EC_KEY **ec_key_key, /* the KDC's ephemeral private */ + unsigned char **dh_gen_key, /* shared secret */ + size_t *dh_gen_keylen) { const EC_GROUP *group; EC_KEY *ephemeral; @@ -136,7 +206,7 @@ generate_ecdh_keyblock(krb5_context context, EC_KEY_set_group(ephemeral, group); if (EC_KEY_generate_key(ephemeral) != 1) { - EC_KEY_free(ephemeral); + EC_KEY_free(ephemeral); return krb5_enomem(context); } @@ -165,6 +235,7 @@ generate_ecdh_keyblock(krb5_context context, return 0; } +#endif #endif /* HAVE_HCRYPTO_W_OPENSSL */ krb5_error_code @@ -175,20 +246,128 @@ _kdc_generate_ecdh_keyblock(krb5_context context, size_t *dh_gen_keylen) { #ifdef HAVE_HCRYPTO_W_OPENSSL - return generate_ecdh_keyblock(context, ec_key_pk, - (EC_KEY **)ec_key_key, - dh_gen_key, dh_gen_keylen); +#ifdef HAVE_OPENSSL_30 + return generate_ecdh_keyblock_ossl30(context, ec_key_pk, + (EVP_PKEY **)ec_key_key, + dh_gen_key, dh_gen_keylen); +#else + return generate_ecdh_keyblock_ossl11(context, ec_key_pk, + (EC_KEY **)ec_key_key, + dh_gen_key, dh_gen_keylen); +#endif #else return ENOTSUP; #endif /* HAVE_HCRYPTO_W_OPENSSL */ } #ifdef HAVE_HCRYPTO_W_OPENSSL +#ifdef HAVE_OPENSSL_30 static krb5_error_code -get_ecdh_param(krb5_context context, - krb5_kdc_configuration *config, - SubjectPublicKeyInfo *dh_key_info, - EC_KEY **out) +get_ecdh_param_ossl30(krb5_context context, + krb5_kdc_configuration *config, + SubjectPublicKeyInfo *dh_key_info, + EVP_PKEY **out) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *template = NULL; + EVP_PKEY *public = NULL; + OSSL_PARAM params[2]; + krb5_error_code ret = 0; + ECParameters ecp; + const unsigned char *p; + const char *curve_sn = NULL; + size_t len; + char *curve_sn_dup = NULL; + int groupnid = NID_undef; + + /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */ + + /* + * In order for d2i_PublicKey() to work we need to create a template key + * that has the curve parameters for the subjectPublicKey. + * + * Or maybe we could learn to use the OSSL_DECODER(3) API. But this works, + * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use + * OSSL_DECODER(3). + */ + + memset(&ecp, 0, sizeof(ecp)); + + if (dh_key_info->algorithm.parameters == NULL) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "PKINIT missing algorithm parameter " + "in clientPublicValue"); + if (ret == 0) + ret = decode_ECParameters(dh_key_info->algorithm.parameters->data, + dh_key_info->algorithm.parameters->length, + &ecp, &len); + if (ret == 0 && ecp.element != choice_ECParameters_namedCurve) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "PKINIT client used an unnamed curve"); + if (ret == 0 && + (groupnid = _hx509_ossl_oid2nid(&ecp.u.namedCurve)) == NID_undef) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "PKINIT client used an unsupported curve"); + if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "Could not resolve curve NID %d to its short name", + groupnid); + if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL) + ret = krb5_enomem(context); + if (ret == 0) { + if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) != 0) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "PKINIT client used an unsupported curve"); + } + if (ret == 0) { + /* + * Apparently there's no error checking to be done here? Why does + * OSSL_PARAM_construct_utf8_string() want a non-const for the value? + * Is that a bug in OpenSSL? + */ + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, + curve_sn_dup, 0); + params[1] = OSSL_PARAM_construct_end(); + + if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL) + ret = krb5_enomem(context); + } + if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1) + ret = krb5_enomem(context); + if (ret == 0 && + EVP_PKEY_fromdata(pctx, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + params) != 1) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "Could not set up to parse key for curve %s", + curve_sn); + + p = dh_key_info->subjectPublicKey.data; + len = dh_key_info->subjectPublicKey.length / 8; + if (ret == 0 && + (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL) + krb5_set_error_message(context, ret = KRB5_BADMSGTYPE, + "Could not decode PKINIT client ECDH key"); + + if (ret) { + EVP_PKEY_free(public); + public = NULL; + } + + *out = public; + + /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */ + EVP_PKEY_CTX_free(pctx); + free_ECParameters(&ecp); + free(curve_sn_dup); + return ret; +} +#else + +static krb5_error_code +get_ecdh_param_ossl11(krb5_context context, + krb5_kdc_configuration *config, + SubjectPublicKeyInfo *dh_key_info, + EC_KEY **out) { ECParameters ecp; EC_KEY *public = NULL; @@ -198,30 +377,31 @@ get_ecdh_param(krb5_context context, int nid; if (dh_key_info->algorithm.parameters == NULL) { - krb5_set_error_message(context, KRB5_BADMSGTYPE, - "PKINIT missing algorithm parameter " - "in clientPublicValue"); - return KRB5_BADMSGTYPE; + krb5_set_error_message(context, KRB5_BADMSGTYPE, + "PKINIT missing algorithm parameter " + "in clientPublicValue"); + return KRB5_BADMSGTYPE; } + /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */ memset(&ecp, 0, sizeof(ecp)); ret = decode_ECParameters(dh_key_info->algorithm.parameters->data, - dh_key_info->algorithm.parameters->length, &ecp, &len); + dh_key_info->algorithm.parameters->length, &ecp, &len); if (ret) - goto out; + goto out; if (ecp.element != choice_ECParameters_namedCurve) { - ret = KRB5_BADMSGTYPE; - goto out; + ret = KRB5_BADMSGTYPE; + goto out; } if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0) - nid = NID_X9_62_prime256v1; + nid = NID_X9_62_prime256v1; else { - ret = KRB5_BADMSGTYPE; - goto out; - } + ret = KRB5_BADMSGTYPE; + goto out; + } /* XXX verify group is ok */ @@ -230,20 +410,21 @@ get_ecdh_param(krb5_context context, p = dh_key_info->subjectPublicKey.data; len = dh_key_info->subjectPublicKey.length / 8; if (o2i_ECPublicKey(&public, &p, len) == NULL) { - ret = KRB5_BADMSGTYPE; - krb5_set_error_message(context, ret, - "PKINIT failed to decode ECDH key"); - goto out; + ret = KRB5_BADMSGTYPE; + krb5_set_error_message(context, ret, + "PKINIT failed to decode ECDH key"); + goto out; } *out = public; public = NULL; out: if (public) - EC_KEY_free(public); + EC_KEY_free(public); free_ECParameters(&ecp); return ret; } +#endif #endif /* HAVE_HCRYPTO_W_OPENSSL */ krb5_error_code @@ -253,7 +434,11 @@ _kdc_get_ecdh_param(krb5_context context, void **out) { #ifdef HAVE_HCRYPTO_W_OPENSSL - return get_ecdh_param(context, config, dh_key_info, (EC_KEY **)out); +#ifdef HAVE_OPENSSL_30 + return get_ecdh_param_ossl30(context, config, dh_key_info, (EVP_PKEY **)out); +#else + return get_ecdh_param_ossl11(context, config, dh_key_info, (EC_KEY **)out); +#endif #else return ENOTSUP; #endif /* HAVE_HCRYPTO_W_OPENSSL */ @@ -265,13 +450,51 @@ _kdc_get_ecdh_param(krb5_context context, */ #ifdef HAVE_HCRYPTO_W_OPENSSL +#ifdef HAVE_OPENSSL_30 static krb5_error_code -serialize_ecdh_key(krb5_context context, - EC_KEY *key, - unsigned char **out, - size_t *out_len) +serialize_ecdh_key_ossl30(krb5_context context, + EVP_PKEY *key, + unsigned char **out, + size_t *out_len) +{ + unsigned char *p; + int len; + + *out = NULL; + *out_len = 0; + + len = i2d_PublicKey(key, NULL); + if (len <= 0) { + krb5_set_error_message(context, EOVERFLOW, + "PKINIT failed to encode ECDH key"); + return EOVERFLOW; + } + + *out = malloc(len); + if (*out == NULL) + return krb5_enomem(context); + + p = *out; + len = i2d_PublicKey(key, &p); + if (len <= 0) { + free(*out); + *out = NULL; + krb5_set_error_message(context, EINVAL /* XXX Better error please */, + "PKINIT failed to encode ECDH key"); + return EINVAL; + } + + *out_len = len * 8; + return 0; +} +#else + +static krb5_error_code +serialize_ecdh_key_ossl11(krb5_context context, + EC_KEY *key, + unsigned char **out, + size_t *out_len) { - krb5_error_code ret = 0; unsigned char *p; int len; @@ -279,8 +502,11 @@ serialize_ecdh_key(krb5_context context, *out_len = 0; len = i2o_ECPublicKey(key, NULL); - if (len <= 0) + if (len <= 0) { + krb5_set_error_message(context, EOVERFLOW, + "PKINIT failed to encode ECDH key"); return EOVERFLOW; + } *out = malloc(len); if (*out == NULL) @@ -291,16 +517,16 @@ serialize_ecdh_key(krb5_context context, if (len <= 0) { free(*out); *out = NULL; - ret = EINVAL; /* XXX Better error please */ - krb5_set_error_message(context, ret, + krb5_set_error_message(context, EINVAL /* XXX Better error please */, "PKINIT failed to encode ECDH key"); - return ret; + return EINVAL; } *out_len = len * 8; - return ret; + return 0; } #endif +#endif krb5_error_code _kdc_serialize_ecdh_key(krb5_context context, @@ -309,7 +535,11 @@ _kdc_serialize_ecdh_key(krb5_context context, size_t *out_len) { #ifdef HAVE_HCRYPTO_W_OPENSSL - return serialize_ecdh_key(context, key, out, out_len); +#ifdef HAVE_OPENSSL_30 + return serialize_ecdh_key_ossl30(context, key, out, out_len); +#else + return serialize_ecdh_key_ossl11(context, key, out, out_len); +#endif #else return ENOTSUP; #endif