Files
heimdal/lib/krb5/pkinit-ec.c
2026-01-18 19:06:17 -06:00

373 lines
14 KiB
C

/*
* Copyright (c) 2016 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Portions Copyright (c) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <config.h>
#include <roken.h>
#ifdef PKINIT
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include "krb5_locl.h"
#include <cms_asn1.h>
#include <pkcs8_asn1.h>
#include <pkcs9_asn1.h>
#include <pkcs12_asn1.h>
#include <pkinit_asn1.h>
#include <asn1_err.h>
#include <der.h>
static const char *
ec_oid2nidname(const heim_oid *oid)
{
if (der_heim_oid_cmp(oid, &asn1_oid_id_X25519) == 0)
return "X25519";
if (der_heim_oid_cmp(oid, &asn1_oid_id_X448) == 0)
return "X448";
if (der_heim_oid_cmp(oid, &asn1_oid_id_ec_group_secp256r1) == 0)
return "prime256v1";
if (der_heim_oid_cmp(oid, &asn1_oid_id_ec_group_secp384r1) == 0)
return "secp384r1";
if (der_heim_oid_cmp(oid, &asn1_oid_id_ec_group_secp521r1) == 0)
return "secp521r1";
return NULL;
}
const heim_oid *
_krb5_ec_nidname2heim_oid(const char *sn)
{
if (strcmp(sn, "X25519") == 0)
return &asn1_oid_id_X25519;
if (strcmp(sn, "X448") == 0)
return &asn1_oid_id_X448;
if (strcmp(sn, "prime256v1") == 0 || strcmp(sn, "P-256") == 0)
return &asn1_oid_id_ec_group_secp256r1;
if (strcmp(sn, "secp384r1") == 0 || strcmp(sn, "P-384") == 0)
return &asn1_oid_id_ec_group_secp384r1;
if (strcmp(sn, "secp521r1") == 0 || strcmp(sn, "P-521") == 0)
return &asn1_oid_id_ec_group_secp521r1;
return NULL;
}
const heim_oid *
_krb5_pkinit_pick_curve(krb5_context context, krb5_pk_init_ctx ctx)
{
TD_DH_PARAMETERS *p = ctx->kdc_dh_algs;
const char *curve = NULL;
const char *dh_min_bits = krb5_config_get_string(context, NULL,
"libdefaults",
"pkinit_dh_min_bits",
NULL);
const heim_oid *oid;
size_t i;
/*
* The user wants a specific curve (this is great for interop testing via
* kinit(1)).
*/
if (ctx->want_dh_alg && (curve = ec_oid2nidname(ctx->want_dh_alg)))
return ctx->want_dh_alg;
/*
* If the server indicated supported DH groups and/or curves, pick the
* first one of those that is a curve that we also allow per local
* configuration.
*/
for (i = 0; p && i < p->len; i++) {
oid = &p->val[i].algorithm;
if (der_heim_oid_cmp(oid, &asn1_oid_id_ecPublicKey) == 0 &&
p->val[i].parameters) {
ECParameters ecp;
memset(&ecp, 0, sizeof(ecp));
if (decode_ECParameters(p->val[i].parameters->data,
p->val[i].parameters->length,
&ecp, NULL))
continue;
if (ecp.element != choice_ECParameters_namedCurve) {
free_ECParameters(&ecp);
continue;
}
curve = ec_oid2nidname(&ecp.u.namedCurve);
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", curve, NULL))
return _krb5_ec_nidname2heim_oid(curve);
} else if ((curve = ec_oid2nidname(oid)) &&
krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", curve, NULL))
/* Normalize to constant OID */
return _krb5_ec_nidname2heim_oid(curve);
}
if (dh_min_bits && (oid = _krb5_ec_nidname2heim_oid(dh_min_bits)))
return oid; /* MIT Kerberos config compat */
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", "X25519", NULL))
return &asn1_oid_id_X25519;
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", "X448", NULL))
return &asn1_oid_id_X448;
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", "prime256v1", NULL))
return &asn1_oid_id_ec_group_secp256r1;
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", "secp384r1", NULL))
return &asn1_oid_id_ec_group_secp384r1;
if (krb5_config_get_bool_default(context, NULL, 1, "libdefaults",
"pkinit_allow_ecdh", "secp521r1", NULL))
return &asn1_oid_id_ec_group_secp521r1;
return &asn1_oid_id_X25519;
}
krb5_error_code
_krb5_pkinit_make_ecdh_key(krb5_context context,
OSSL_LIB_CTX *libctx,
const char *propq,
const heim_oid *alg,
EVP_PKEY **pkeyp)
{
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY_CTX *kctx = NULL;
EVP_PKEY *params = NULL;
EVP_PKEY *pkey = NULL;
const char *curve = ec_oid2nidname(alg);
krb5_error_code ret = HX509_CRYPTO_INTERNAL_ERROR;
*pkeyp = NULL;
curve = (curve) ? curve : "prime256v1";
if (strcmp(curve, "X25519") == 0 || strcmp(curve, "X448") == 0) {
if ((kctx = EVP_PKEY_CTX_new_from_name(libctx, curve, propq)) == NULL ||
EVP_PKEY_keygen_init(kctx) <= 0 ||
EVP_PKEY_keygen(kctx, &pkey) <= 0) {
char *omsg = _krb5_openssl_errors();
krb5_set_error_message(context, ret,
"PKINIT: Could not make a PKEY for %s: %s",
curve, omsg ? omsg : "<no OpenSSL error message>");
free(omsg);
EVP_PKEY_CTX_free(kctx);
return ret;
}
EVP_PKEY_CTX_free(kctx);
*pkeyp = pkey;
return 0;
}
OSSL_PARAM p[] = {
OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
rk_UNCONST(curve), 0),
OSSL_PARAM_END
};
if ((pctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", propq)) == NULL)
return _krb5_set_error_message_openssl(context, ret,
"PKINIT: Could not create EC context");
if (EVP_PKEY_paramgen_init(pctx) <= 0 ||
EVP_PKEY_CTX_set_params(pctx, p) <= 0 ||
EVP_PKEY_paramgen(pctx, &params) <= 0 ||
(kctx = EVP_PKEY_CTX_new_from_pkey(libctx, params, propq)) == NULL ||
EVP_PKEY_keygen_init(kctx) <= 0 ||
EVP_PKEY_keygen(kctx, &pkey) <= 0) {
char *omsg = _krb5_openssl_errors();
krb5_set_error_message(context,
ret,
"PKINIT: Could not make a PKEY for EC curve: %s: %s",
curve, omsg ? omsg : "<no OpenSSL error message>");
free(omsg);
goto out;
}
ret = 0;
out:
*pkeyp = pkey;
EVP_PKEY_CTX_free(kctx);
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(params);
return ret;
}
/*
* OpenSSL does not support use of i2d_PublicKey()/d2i_PublicKey() for all key
* agreement types, instead requiring a more complex API with special cases for
* EC, DH, DHX, and X25519/X448. Instead we do something very fancy and silly:
* use our pkey to format an SPKI with i2d_PUBKEY(), decode it, replace the
* SPK, then import with d2i_PUBKEY(). A bit gross, but less gross than lots
* of special cases.
*/
krb5_error_code
_krb5_ossl_d2i_PublicKey(krb5_context context,
const EVP_PKEY *ours,
heim_bit_string their_spk,
EVP_PKEY **theirs)
{
SubjectPublicKeyInfo spki;
krb5_error_code ret;
const unsigned char *p;
size_t size;
memset(&spki, 0, sizeof(spki));
/* Transform ours into a decoded SPKI */
ret = _krb5_pkinit_pkey2SubjectPublicKeyInfo(context, ours, &spki);
if (ret)
return ret;
/* Replace the decoded SPKI's subjectPublicKey (ours) with theirs */
der_free_bit_string(&spki.subjectPublicKey);
spki.subjectPublicKey = their_spk;
/*
* Encode the SPKI, using the _save as a convenient place to put the
* encoding.
*/
der_free_octet_string(&spki._save);
ASN1_MALLOC_ENCODE(SubjectPublicKeyInfo, spki._save.data,
spki._save.length, &spki, &size, ret);
/*
* Finally! Call d2i_PUBKEY(), which will work.
*
* Look 'ma! No special cases needed for all the kinds of key agreement
* protocols. We did pay a price: having to encode, decode, encode -- a
* bit silly, but the real silliness lies in OpenSSL's API.
*/
p = spki._save.data;
*theirs = d2i_PUBKEY(NULL, &p, size);
spki.subjectPublicKey.data = NULL;
spki.subjectPublicKey.length = 0;
free_SubjectPublicKeyInfo(&spki);
if (*theirs == NULL) {
char *omsg = _krb5_openssl_errors();
krb5_set_error_message(context,
ret = HX509_PARSING_KEY_FAILED,
"PKINIT: Can't parse the KDC's ECDH public key: %s",
omsg ? omsg : "<no OpenSSL error message>");
free(omsg);
return ret;
}
return 0;
}
krb5_error_code
_krb5_pk_rd_pa_reply_ossl_compute_key(krb5_context context,
krb5_pk_init_ctx ctx,
heim_bit_string in,
unsigned char **out,
int *out_sz)
{
krb5_error_code ret;
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY *template = NULL;
EVP_PKEY *public = NULL;
size_t shared_len = 0;
int oret;
ret = _krb5_ossl_d2i_PublicKey(context, ctx->pkey, in, &public);
if (ret)
return ret;
if ((template = EVP_PKEY_new()) == NULL)
ret = krb5_enomem(context);
if (ret == 0 &&
EVP_PKEY_copy_parameters(template, ctx->pkey) != 1)
ret = krb5_enomem(context);
if (ret == 0 && (pctx = EVP_PKEY_CTX_new(ctx->pkey, NULL)) == NULL)
ret = krb5_enomem(context);
if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
ret = krb5_enomem(context);
/* Set the KDF to no KDF because PKINIT does its own KDF */
if (ctx->keyex == USE_DH) {
if (ret == 0 &&
EVP_PKEY_CTX_set_dh_kdf_type(pctx, EVP_PKEY_DH_KDF_NONE) != 1)
ret = krb5_enomem(context);
} else {
if (ret == 0 &&
(oret = EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE)) <= 0 &&
oret != -2)
ret = krb5_enomem(context);
}
if (ret == 0 &&
EVP_PKEY_derive_set_peer_ex(pctx, public, 1) != 1)
krb5_set_error_message(context,
ret = KRB5KRB_ERR_GENERIC,
"Could not derive ECDH shared secret for PKINIT key exchange "
"(EVP_PKEY_derive_set_peer_ex)");
if (ret == 0 &&
(EVP_PKEY_derive(pctx, NULL, &shared_len) != 1 || shared_len == 0))
krb5_set_error_message(context,
ret = KRB5KRB_ERR_GENERIC,
"Could not derive ECDH shared secret for PKINIT key exchange "
"(EVP_PKEY_derive to get length)");
if (ret == 0 && shared_len > INT_MAX)
krb5_set_error_message(context,
ret = KRB5KRB_ERR_GENERIC,
"Could not derive ECDH shared secret for PKINIT key exchange "
"(shared key too large)");
if (ret == 0 && (*out = malloc(shared_len)) == NULL)
ret = krb5_enomem(context);
if (ret == 0 && EVP_PKEY_derive(pctx, *out, &shared_len) != 1)
krb5_set_error_message(context,
ret = KRB5KRB_ERR_GENERIC,
"Could not derive ECDH shared secret for PKINIT key exchange "
"(EVP_PKEY_derive)");
if (ret == 0)
*out_sz = shared_len;
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_free(template);
EVP_PKEY_free(public);
return ret;
}
#else
static char lib_krb5_pkinit_ec_c = '\0';
#endif