Line data Source code
1 : /*
2 : * Copyright (c) 2016 Kungliga Tekniska Högskolan
3 : * (Royal Institute of Technology, Stockholm, Sweden).
4 : * All rights reserved.
5 : *
6 : * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7 : *
8 : * Redistribution and use in source and binary forms, with or without
9 : * modification, are permitted provided that the following conditions
10 : * are met:
11 : *
12 : * 1. Redistributions of source code must retain the above copyright
13 : * notice, this list of conditions and the following disclaimer.
14 : *
15 : * 2. Redistributions in binary form must reproduce the above copyright
16 : * notice, this list of conditions and the following disclaimer in the
17 : * documentation and/or other materials provided with the distribution.
18 : *
19 : * 3. Neither the name of the Institute nor the names of its contributors
20 : * may be used to endorse or promote products derived from this software
21 : * without specific prior written permission.
22 : *
23 : * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 : * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 : * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 : * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 : * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 : * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 : * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 : * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 : * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 : * SUCH DAMAGE.
34 : */
35 :
36 : #include <config.h>
37 : #include <roken.h>
38 :
39 : #ifdef PKINIT
40 :
41 : /*
42 : * As with the other *-ec.c files in Heimdal, this is a bit of a hack.
43 : *
44 : * The idea is to use OpenSSL for EC because hcrypto doesn't have the
45 : * required functionality at this time. To do this we segregate
46 : * EC-using code into separate source files and then we arrange for them
47 : * to get the OpenSSL headers and not the conflicting hcrypto ones.
48 : *
49 : * Because of auto-generated *-private.h headers, we end up needing to
50 : * make sure various types are defined before we include them, thus the
51 : * strange header include order here.
52 : */
53 :
54 : #ifdef HAVE_HCRYPTO_W_OPENSSL
55 : #include <openssl/evp.h>
56 : #include <openssl/ec.h>
57 : #include <openssl/ecdsa.h>
58 : #include <openssl/rsa.h>
59 : #include <openssl/bn.h>
60 : #include <openssl/dh.h>
61 : #include <openssl/objects.h>
62 : #ifdef HAVE_OPENSSL_30
63 : #include <openssl/core_names.h>
64 : #endif
65 : #define HEIM_NO_CRYPTO_HDRS
66 : #endif /* HAVE_HCRYPTO_W_OPENSSL */
67 :
68 : #define NO_HCRYPTO_POLLUTION
69 :
70 : #include "kdc_locl.h"
71 : #include <hcrypto/des.h>
72 : #include <heim_asn1.h>
73 : #include <rfc2459_asn1.h>
74 : #include <cms_asn1.h>
75 : #include <pkinit_asn1.h>
76 :
77 : #include <hx509.h>
78 : #include "../lib/hx509/hx_locl.h"
79 : #include <hx509-private.h>
80 :
81 : void
82 0 : _kdc_pk_free_client_ec_param(krb5_context context,
83 : void *k0,
84 : void *k1)
85 : {
86 : #ifdef HAVE_HCRYPTO_W_OPENSSL
87 : #ifdef HAVE_OPENSSL_30
88 : EVP_PKEY_free(k0);
89 : EVP_PKEY_free(k1);
90 : #else
91 : EC_KEY_free(k0);
92 : EC_KEY_free(k1);
93 : #endif
94 : #endif
95 0 : }
96 :
97 : #ifdef HAVE_HCRYPTO_W_OPENSSL
98 : #ifdef HAVE_OPENSSL_30
99 : static krb5_error_code
100 : generate_ecdh_keyblock_ossl30(krb5_context context,
101 : EVP_PKEY *ec_key_pub, /* the client's public key */
102 : EVP_PKEY **ec_key_priv, /* the KDC's ephemeral private */
103 : unsigned char **dh_gen_key, /* shared secret */
104 : size_t *dh_gen_keylen)
105 : {
106 : EVP_PKEY_CTX *pctx = NULL;
107 : EVP_PKEY *ephemeral = NULL;
108 : krb5_error_code ret = 0;
109 : unsigned char *p = NULL;
110 : size_t size = 0;
111 :
112 : if (ec_key_pub == NULL)
113 : /* XXX This seems like an internal error that should be impossible */
114 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
115 : "Missing client ECDH key agreement public key");
116 : if (ret == 0 &&
117 : (ephemeral =
118 : EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1))) == NULL)
119 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
120 : "Could not generate an ECDH key agreement private key");
121 : if (ret == 0 &&
122 : (pctx = EVP_PKEY_CTX_new(ephemeral, NULL)) == NULL)
123 : ret = krb5_enomem(context);
124 : if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
125 : ret = krb5_enomem(context);
126 : if (ret == 0 &&
127 : EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1)
128 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
129 : "Could not generate an ECDH key agreement private key "
130 : "(EVP_PKEY_CTX_set_dh_kdf_type)");
131 : if (ret == 0 &&
132 : EVP_PKEY_derive_set_peer_ex(pctx, ec_key_pub, 1) != 1)
133 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
134 : "Could not generate an ECDH key agreement private key "
135 : "(EVP_PKEY_derive_set_peer_ex)");
136 : if (ret == 0 &&
137 : (EVP_PKEY_derive(pctx, NULL, &size) != 1 || size == 0))
138 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
139 : "Could not generate an ECDH key agreement private key "
140 : "(EVP_PKEY_derive)");
141 : if (ret == 0 && (p = malloc(size)) == NULL)
142 : ret = krb5_enomem(context);
143 : if (ret == 0 &&
144 : (EVP_PKEY_derive(pctx, p, &size) != 1 || size == 0))
145 : krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
146 : "Could not generate an ECDH key agreement private key "
147 : "(EVP_PKEY_derive)");
148 :
149 : if (ret) {
150 : EVP_PKEY_free(ephemeral);
151 : ephemeral = NULL;
152 : free(p);
153 : p = NULL;
154 : size = 0;
155 : }
156 :
157 : *ec_key_priv = ephemeral;
158 : *dh_gen_keylen = size;
159 : *dh_gen_key = p;
160 :
161 : EVP_PKEY_CTX_free(pctx);
162 : return ret;
163 : }
164 : #else
165 :
166 : /* The empty line above is intentional to work around an mkproto bug */
167 : static krb5_error_code
168 : generate_ecdh_keyblock_ossl11(krb5_context context,
169 : EC_KEY *ec_key_pk, /* the client's public key */
170 : EC_KEY **ec_key_key, /* the KDC's ephemeral private */
171 : unsigned char **dh_gen_key, /* shared secret */
172 : size_t *dh_gen_keylen)
173 : {
174 : const EC_GROUP *group;
175 : EC_KEY *ephemeral;
176 : krb5_keyblock key;
177 : krb5_error_code ret;
178 : unsigned char *p;
179 : size_t size;
180 : int len;
181 :
182 : *dh_gen_key = NULL;
183 : *dh_gen_keylen = 0;
184 : *ec_key_key = NULL;
185 :
186 : memset(&key, 0, sizeof(key));
187 :
188 : if (ec_key_pk == NULL) {
189 : ret = KRB5KRB_ERR_GENERIC;
190 : krb5_set_error_message(context, ret, "public_key");
191 : return ret;
192 : }
193 :
194 : group = EC_KEY_get0_group(ec_key_pk);
195 : if (group == NULL) {
196 : ret = KRB5KRB_ERR_GENERIC;
197 : krb5_set_error_message(context, ret, "failed to get the group of "
198 : "the client's public key");
199 : return ret;
200 : }
201 :
202 : ephemeral = EC_KEY_new();
203 : if (ephemeral == NULL)
204 : return krb5_enomem(context);
205 :
206 : EC_KEY_set_group(ephemeral, group);
207 :
208 : if (EC_KEY_generate_key(ephemeral) != 1) {
209 : EC_KEY_free(ephemeral);
210 : return krb5_enomem(context);
211 : }
212 :
213 : size = (EC_GROUP_get_degree(group) + 7) / 8;
214 : p = malloc(size);
215 : if (p == NULL) {
216 : EC_KEY_free(ephemeral);
217 : return krb5_enomem(context);
218 : }
219 :
220 : len = ECDH_compute_key(p, size,
221 : EC_KEY_get0_public_key(ec_key_pk),
222 : ephemeral, NULL);
223 : if (len <= 0) {
224 : free(p);
225 : EC_KEY_free(ephemeral);
226 : ret = KRB5KRB_ERR_GENERIC;
227 : krb5_set_error_message(context, ret, "Failed to compute ECDH "
228 : "public shared secret");
229 : return ret;
230 : }
231 :
232 : *ec_key_key = ephemeral;
233 : *dh_gen_key = p;
234 : *dh_gen_keylen = len;
235 :
236 : return 0;
237 : }
238 : #endif
239 : #endif /* HAVE_HCRYPTO_W_OPENSSL */
240 :
241 : krb5_error_code
242 0 : _kdc_generate_ecdh_keyblock(krb5_context context,
243 : void *ec_key_pk, /* the client's public key */
244 : void **ec_key_key, /* the KDC's ephemeral private */
245 : unsigned char **dh_gen_key, /* shared secret */
246 : size_t *dh_gen_keylen)
247 : {
248 : #ifdef HAVE_HCRYPTO_W_OPENSSL
249 : #ifdef HAVE_OPENSSL_30
250 : return generate_ecdh_keyblock_ossl30(context, ec_key_pk,
251 : (EVP_PKEY **)ec_key_key,
252 : dh_gen_key, dh_gen_keylen);
253 : #else
254 : return generate_ecdh_keyblock_ossl11(context, ec_key_pk,
255 : (EC_KEY **)ec_key_key,
256 : dh_gen_key, dh_gen_keylen);
257 : #endif
258 : #else
259 0 : return ENOTSUP;
260 : #endif /* HAVE_HCRYPTO_W_OPENSSL */
261 : }
262 :
263 : #ifdef HAVE_HCRYPTO_W_OPENSSL
264 : #ifdef HAVE_OPENSSL_30
265 : static krb5_error_code
266 : get_ecdh_param_ossl30(krb5_context context,
267 : krb5_kdc_configuration *config,
268 : SubjectPublicKeyInfo *dh_key_info,
269 : EVP_PKEY **out)
270 : {
271 : EVP_PKEY_CTX *pctx = NULL;
272 : EVP_PKEY *template = NULL;
273 : EVP_PKEY *public = NULL;
274 : OSSL_PARAM params[2];
275 : krb5_error_code ret = 0;
276 : ECParameters ecp;
277 : const unsigned char *p;
278 : const char *curve_sn = NULL;
279 : size_t len;
280 : char *curve_sn_dup = NULL;
281 : int groupnid = NID_undef;
282 :
283 : /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
284 :
285 : /*
286 : * In order for d2i_PublicKey() to work we need to create a template key
287 : * that has the curve parameters for the subjectPublicKey.
288 : *
289 : * Or maybe we could learn to use the OSSL_DECODER(3) API. But this works,
290 : * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use
291 : * OSSL_DECODER(3).
292 : */
293 :
294 : memset(&ecp, 0, sizeof(ecp));
295 :
296 : if (dh_key_info->algorithm.parameters == NULL)
297 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
298 : "PKINIT missing algorithm parameter "
299 : "in clientPublicValue");
300 : if (ret == 0)
301 : ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
302 : dh_key_info->algorithm.parameters->length,
303 : &ecp, &len);
304 : if (ret == 0 && ecp.element != choice_ECParameters_namedCurve)
305 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
306 : "PKINIT client used an unnamed curve");
307 : if (ret == 0 &&
308 : (groupnid = _hx509_ossl_oid2nid(&ecp.u.namedCurve)) == NID_undef)
309 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
310 : "PKINIT client used an unsupported curve");
311 : if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL)
312 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
313 : "Could not resolve curve NID %d to its short name",
314 : groupnid);
315 : if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL)
316 : ret = krb5_enomem(context);
317 : if (ret == 0) {
318 : if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) != 0)
319 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
320 : "PKINIT client used an unsupported curve");
321 : }
322 : if (ret == 0) {
323 : /*
324 : * Apparently there's no error checking to be done here? Why does
325 : * OSSL_PARAM_construct_utf8_string() want a non-const for the value?
326 : * Is that a bug in OpenSSL?
327 : */
328 : params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
329 : curve_sn_dup, 0);
330 : params[1] = OSSL_PARAM_construct_end();
331 :
332 : if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
333 : ret = krb5_enomem(context);
334 : }
335 : if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1)
336 : ret = krb5_enomem(context);
337 : if (ret == 0 &&
338 : EVP_PKEY_fromdata(pctx, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
339 : params) != 1)
340 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
341 : "Could not set up to parse key for curve %s",
342 : curve_sn);
343 :
344 : p = dh_key_info->subjectPublicKey.data;
345 : len = dh_key_info->subjectPublicKey.length / 8;
346 : if (ret == 0 &&
347 : (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL)
348 : krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
349 : "Could not decode PKINIT client ECDH key");
350 :
351 : if (ret) {
352 : EVP_PKEY_free(public);
353 : public = NULL;
354 : }
355 :
356 : *out = public;
357 :
358 : /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */
359 : EVP_PKEY_CTX_free(pctx);
360 : free_ECParameters(&ecp);
361 : free(curve_sn_dup);
362 : return ret;
363 : }
364 : #else
365 :
366 : static krb5_error_code
367 : get_ecdh_param_ossl11(krb5_context context,
368 : krb5_kdc_configuration *config,
369 : SubjectPublicKeyInfo *dh_key_info,
370 : EC_KEY **out)
371 : {
372 : ECParameters ecp;
373 : EC_KEY *public = NULL;
374 : krb5_error_code ret;
375 : const unsigned char *p;
376 : size_t len;
377 : int nid;
378 :
379 : if (dh_key_info->algorithm.parameters == NULL) {
380 : krb5_set_error_message(context, KRB5_BADMSGTYPE,
381 : "PKINIT missing algorithm parameter "
382 : "in clientPublicValue");
383 : return KRB5_BADMSGTYPE;
384 : }
385 : /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
386 :
387 : memset(&ecp, 0, sizeof(ecp));
388 :
389 : ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
390 : dh_key_info->algorithm.parameters->length, &ecp, &len);
391 : if (ret)
392 : goto out;
393 :
394 : if (ecp.element != choice_ECParameters_namedCurve) {
395 : ret = KRB5_BADMSGTYPE;
396 : goto out;
397 : }
398 :
399 : if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0)
400 : nid = NID_X9_62_prime256v1;
401 : else {
402 : ret = KRB5_BADMSGTYPE;
403 : goto out;
404 : }
405 :
406 : /* XXX verify group is ok */
407 :
408 : public = EC_KEY_new_by_curve_name(nid);
409 :
410 : p = dh_key_info->subjectPublicKey.data;
411 : len = dh_key_info->subjectPublicKey.length / 8;
412 : if (o2i_ECPublicKey(&public, &p, len) == NULL) {
413 : ret = KRB5_BADMSGTYPE;
414 : krb5_set_error_message(context, ret,
415 : "PKINIT failed to decode ECDH key");
416 : goto out;
417 : }
418 : *out = public;
419 : public = NULL;
420 :
421 : out:
422 : if (public)
423 : EC_KEY_free(public);
424 : free_ECParameters(&ecp);
425 : return ret;
426 : }
427 : #endif
428 : #endif /* HAVE_HCRYPTO_W_OPENSSL */
429 :
430 : krb5_error_code
431 0 : _kdc_get_ecdh_param(krb5_context context,
432 : krb5_kdc_configuration *config,
433 : SubjectPublicKeyInfo *dh_key_info,
434 : void **out)
435 : {
436 : #ifdef HAVE_HCRYPTO_W_OPENSSL
437 : #ifdef HAVE_OPENSSL_30
438 : return get_ecdh_param_ossl30(context, config, dh_key_info, (EVP_PKEY **)out);
439 : #else
440 : return get_ecdh_param_ossl11(context, config, dh_key_info, (EC_KEY **)out);
441 : #endif
442 : #else
443 0 : return ENOTSUP;
444 : #endif /* HAVE_HCRYPTO_W_OPENSSL */
445 : }
446 :
447 :
448 : /*
449 : *
450 : */
451 :
452 : #ifdef HAVE_HCRYPTO_W_OPENSSL
453 : #ifdef HAVE_OPENSSL_30
454 : static krb5_error_code
455 : serialize_ecdh_key_ossl30(krb5_context context,
456 : EVP_PKEY *key,
457 : unsigned char **out,
458 : size_t *out_len)
459 : {
460 : unsigned char *p;
461 : int len;
462 :
463 : *out = NULL;
464 : *out_len = 0;
465 :
466 : len = i2d_PublicKey(key, NULL);
467 : if (len <= 0) {
468 : krb5_set_error_message(context, EOVERFLOW,
469 : "PKINIT failed to encode ECDH key");
470 : return EOVERFLOW;
471 : }
472 :
473 : *out = malloc(len);
474 : if (*out == NULL)
475 : return krb5_enomem(context);
476 :
477 : p = *out;
478 : len = i2d_PublicKey(key, &p);
479 : if (len <= 0) {
480 : free(*out);
481 : *out = NULL;
482 : krb5_set_error_message(context, EINVAL /* XXX Better error please */,
483 : "PKINIT failed to encode ECDH key");
484 : return EINVAL;
485 : }
486 :
487 : *out_len = len * 8;
488 : return 0;
489 : }
490 : #else
491 :
492 : static krb5_error_code
493 : serialize_ecdh_key_ossl11(krb5_context context,
494 : EC_KEY *key,
495 : unsigned char **out,
496 : size_t *out_len)
497 : {
498 : unsigned char *p;
499 : int len;
500 :
501 : *out = NULL;
502 : *out_len = 0;
503 :
504 : len = i2o_ECPublicKey(key, NULL);
505 : if (len <= 0) {
506 : krb5_set_error_message(context, EOVERFLOW,
507 : "PKINIT failed to encode ECDH key");
508 : return EOVERFLOW;
509 : }
510 :
511 : *out = malloc(len);
512 : if (*out == NULL)
513 : return krb5_enomem(context);
514 :
515 : p = *out;
516 : len = i2o_ECPublicKey(key, &p);
517 : if (len <= 0) {
518 : free(*out);
519 : *out = NULL;
520 : krb5_set_error_message(context, EINVAL /* XXX Better error please */,
521 : "PKINIT failed to encode ECDH key");
522 : return EINVAL;
523 : }
524 :
525 : *out_len = len * 8;
526 : return 0;
527 : }
528 : #endif
529 : #endif
530 :
531 : krb5_error_code
532 0 : _kdc_serialize_ecdh_key(krb5_context context,
533 : void *key,
534 : unsigned char **out,
535 : size_t *out_len)
536 : {
537 : #ifdef HAVE_HCRYPTO_W_OPENSSL
538 : #ifdef HAVE_OPENSSL_30
539 : return serialize_ecdh_key_ossl30(context, key, out, out_len);
540 : #else
541 : return serialize_ecdh_key_ossl11(context, key, out, out_len);
542 : #endif
543 : #else
544 0 : return ENOTSUP;
545 : #endif
546 : }
547 :
548 : #endif
|