373 lines
14 KiB
C
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, ¶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 : "<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
|