1 /* $NetBSD: pkinit-ec.c,v 1.2 2017/01/28 21:31:44 christos Exp $ */ 2 3 /* 4 * Copyright (c) 2016 Kungliga Tekniska Högskolan 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