/* * 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 #include #ifdef PKINIT #include #include #include #include #include #include "krb5_locl.h" #include #include #include #include #include #include #include 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 : ""); 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, ¶ms) <= 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 : ""); 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 : ""); 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