Home | History | Annotate | Line # | Download | only in kdc
      1 /*	$NetBSD: pkinit-ec.c,v 1.3 2023/06/19 21:41:42 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2016 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  *
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  *
     17  * 2. Redistributions in binary form must reproduce the above copyright
     18  *    notice, this list of conditions and the following disclaimer in the
     19  *    documentation and/or other materials provided with the distribution.
     20  *
     21  * 3. Neither the name of the Institute nor the names of its contributors
     22  *    may be used to endorse or promote products derived from this software
     23  *    without specific prior written permission.
     24  *
     25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     35  * SUCH DAMAGE.
     36  */
     37 
     38 #include <config.h>
     39 #include <krb5/roken.h>
     40 
     41 #ifdef PKINIT
     42 
     43 /*
     44  * As with the other *-ec.c files in Heimdal, this is a bit of a hack.
     45  *
     46  * The idea is to use OpenSSL for EC because hcrypto doesn't have the
     47  * required functionality at this time.  To do this we segregate
     48  * EC-using code into separate source files and then we arrange for them
     49  * to get the OpenSSL headers and not the conflicting hcrypto ones.
     50  *
     51  * Because of auto-generated *-private.h headers, we end up needing to
     52  * make sure various types are defined before we include them, thus the
     53  * strange header include order here.
     54  */
     55 
     56 #ifdef HAVE_HCRYPTO_W_OPENSSL
     57 #include <openssl/ec.h>
     58 #include <openssl/ecdh.h>
     59 #include <openssl/evp.h>
     60 #include <openssl/bn.h>
     61 #define HEIM_NO_CRYPTO_HDRS
     62 #else
     63 #include <hcrypto/des.h>
     64 #endif /* HAVE_HCRYPTO_W_OPENSSL */
     65 
     66 #define NO_HCRYPTO_POLLUTION
     67 
     68 #include "kdc_locl.h"
     69 #include <krb5/heim_asn1.h>
     70 #include <krb5/rfc2459_asn1.h>
     71 #include <krb5/cms_asn1.h>
     72 #include <krb5/pkinit_asn1.h>
     73 
     74 #include <krb5/hx509.h>
     75 
     76 #ifdef HAVE_HCRYPTO_W_OPENSSL
     77 static void
     78 free_client_ec_param(krb5_context context,
     79                      EC_KEY *ec_key_pk,
     80                      EC_KEY *ec_key_key)
     81 {
     82     if (ec_key_pk != NULL)
     83         EC_KEY_free(ec_key_pk);
     84     if (ec_key_key != NULL)
     85         EC_KEY_free(ec_key_key);
     86 }
     87 #endif
     88 
     89 void
     90 _kdc_pk_free_client_ec_param(krb5_context context,
     91                              void *ec_key_pk,
     92                              void *ec_key_key)
     93 {
     94 #ifdef HAVE_HCRYPTO_W_OPENSSL
     95     free_client_ec_param(context, ec_key_pk, ec_key_key);
     96 #endif
     97 }
     98 
     99 #ifdef HAVE_HCRYPTO_W_OPENSSL
    100 static krb5_error_code
    101 generate_ecdh_keyblock(krb5_context context,
    102                        EC_KEY *ec_key_pk,    /* the client's public key */
    103                        EC_KEY **ec_key_key,  /* the KDC's ephemeral private */
    104                        unsigned char **dh_gen_key, /* shared secret */
    105                        size_t *dh_gen_keylen)
    106 {
    107     const EC_GROUP *group;
    108     EC_KEY *ephemeral;
    109     krb5_keyblock key;
    110     krb5_error_code ret;
    111     unsigned char *p;
    112     size_t size;
    113     int len;
    114 
    115     *dh_gen_key = NULL;
    116     *dh_gen_keylen = 0;
    117     *ec_key_key = NULL;
    118 
    119     memset(&key, 0, sizeof(key));
    120 
    121     if (ec_key_pk == NULL) {
    122         ret = KRB5KRB_ERR_GENERIC;
    123         krb5_set_error_message(context, ret, "public_key");
    124         return ret;
    125     }
    126 
    127     group = EC_KEY_get0_group(ec_key_pk);
    128     if (group == NULL) {
    129         ret = KRB5KRB_ERR_GENERIC;
    130         krb5_set_error_message(context, ret, "failed to get the group of "
    131                                "the client's public key");
    132         return ret;
    133     }
    134 
    135     ephemeral = EC_KEY_new();
    136     if (ephemeral == NULL)
    137         return krb5_enomem(context);
    138 
    139     EC_KEY_set_group(ephemeral, group);
    140 
    141     if (EC_KEY_generate_key(ephemeral) != 1) {
    142 	EC_KEY_free(ephemeral);
    143         return krb5_enomem(context);
    144     }
    145 
    146     size = (EC_GROUP_get_degree(group) + 7) / 8;
    147     p = malloc(size);
    148     if (p == NULL) {
    149         EC_KEY_free(ephemeral);
    150         return krb5_enomem(context);
    151     }
    152 
    153     len = ECDH_compute_key(p, size,
    154                            EC_KEY_get0_public_key(ec_key_pk),
    155                            ephemeral, NULL);
    156     if (len <= 0) {
    157         free(p);
    158         EC_KEY_free(ephemeral);
    159         ret = KRB5KRB_ERR_GENERIC;
    160         krb5_set_error_message(context, ret, "Failed to compute ECDH "
    161                                "public shared secret");
    162         return ret;
    163     }
    164 
    165     *ec_key_key = ephemeral;
    166     *dh_gen_key = p;
    167     *dh_gen_keylen = len;
    168 
    169     return 0;
    170 }
    171 #endif /* HAVE_HCRYPTO_W_OPENSSL */
    172 
    173 krb5_error_code
    174 _kdc_generate_ecdh_keyblock(krb5_context context,
    175                             void *ec_key_pk,    /* the client's public key */
    176                             void **ec_key_key,  /* the KDC's ephemeral private */
    177                             unsigned char **dh_gen_key, /* shared secret */
    178                             size_t *dh_gen_keylen)
    179 {
    180 #ifdef HAVE_HCRYPTO_W_OPENSSL
    181     return generate_ecdh_keyblock(context, ec_key_pk,
    182                                   (EC_KEY **)ec_key_key,
    183                                   dh_gen_key, dh_gen_keylen);
    184 #else
    185     return ENOTSUP;
    186 #endif /* HAVE_HCRYPTO_W_OPENSSL */
    187 }
    188 
    189 #ifdef HAVE_HCRYPTO_W_OPENSSL
    190 static krb5_error_code
    191 get_ecdh_param(krb5_context context,
    192                krb5_kdc_configuration *config,
    193                SubjectPublicKeyInfo *dh_key_info,
    194                EC_KEY **out)
    195 {
    196     ECParameters ecp;
    197     EC_KEY *public = NULL;
    198     krb5_error_code ret;
    199     const unsigned char *p;
    200     size_t len;
    201     int nid;
    202 
    203     if (dh_key_info->algorithm.parameters == NULL) {
    204 	krb5_set_error_message(context, KRB5_BADMSGTYPE,
    205 			       "PKINIT missing algorithm parameter "
    206 			       "in clientPublicValue");
    207 	return KRB5_BADMSGTYPE;
    208     }
    209 
    210     memset(&ecp, 0, sizeof(ecp));
    211 
    212     ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
    213 			      dh_key_info->algorithm.parameters->length, &ecp, &len);
    214     if (ret)
    215 	goto out;
    216 
    217     if (ecp.element != choice_ECParameters_namedCurve) {
    218 	ret = KRB5_BADMSGTYPE;
    219 	goto out;
    220     }
    221 
    222     if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0)
    223 	nid = NID_X9_62_prime256v1;
    224     else {
    225 	ret = KRB5_BADMSGTYPE;
    226 	goto out;
    227     }
    228 
    229     /* XXX verify group is ok */
    230 
    231     public = EC_KEY_new_by_curve_name(nid);
    232 
    233     p = dh_key_info->subjectPublicKey.data;
    234     len = dh_key_info->subjectPublicKey.length / 8;
    235     if (o2i_ECPublicKey(&public, &p, len) == NULL) {
    236 	ret = KRB5_BADMSGTYPE;
    237 	krb5_set_error_message(context, ret,
    238 			       "PKINIT failed to decode ECDH key");
    239 	goto out;
    240     }
    241     *out = public;
    242     public = NULL;
    243 
    244  out:
    245     if (public)
    246 	EC_KEY_free(public);
    247     free_ECParameters(&ecp);
    248     return ret;
    249 }
    250 #endif /* HAVE_HCRYPTO_W_OPENSSL */
    251 
    252 krb5_error_code
    253 _kdc_get_ecdh_param(krb5_context context,
    254                     krb5_kdc_configuration *config,
    255                     SubjectPublicKeyInfo *dh_key_info,
    256                     void **out)
    257 {
    258 #ifdef HAVE_HCRYPTO_W_OPENSSL
    259     return get_ecdh_param(context, config, dh_key_info, (EC_KEY **)out);
    260 #else
    261     return ENOTSUP;
    262 #endif /* HAVE_HCRYPTO_W_OPENSSL */
    263 }
    264 
    265 
    266 /*
    267  *
    268  */
    269 
    270 #ifdef HAVE_HCRYPTO_W_OPENSSL
    271 static krb5_error_code
    272 serialize_ecdh_key(krb5_context context,
    273                    EC_KEY *key,
    274                    unsigned char **out,
    275                    size_t *out_len)
    276 {
    277     krb5_error_code ret = 0;
    278     unsigned char *p;
    279     int len;
    280 
    281     *out = NULL;
    282     *out_len = 0;
    283 
    284     len = i2o_ECPublicKey(key, NULL);
    285     if (len <= 0)
    286         return EOVERFLOW;
    287 
    288     *out = malloc(len);
    289     if (*out == NULL)
    290         return krb5_enomem(context);
    291 
    292     p = *out;
    293     len = i2o_ECPublicKey(key, &p);
    294     if (len <= 0) {
    295         free(*out);
    296         *out = NULL;
    297         ret = EINVAL; /* XXX Better error please */
    298 	krb5_set_error_message(context, ret,
    299 			       "PKINIT failed to encode ECDH key");
    300         return ret;
    301     }
    302 
    303     *out_len = len * 8;
    304     return ret;
    305 }
    306 #endif
    307 
    308 krb5_error_code
    309 _kdc_serialize_ecdh_key(krb5_context context,
    310                         void *key,
    311                         unsigned char **out,
    312                         size_t *out_len)
    313 {
    314 #ifdef HAVE_HCRYPTO_W_OPENSSL
    315     return serialize_ecdh_key(context, key, out, out_len);
    316 #else
    317     return ENOTSUP;
    318 #endif
    319 }
    320 
    321 #endif
    322