tests: Use here-doc kadmin in Java test
[heimdal.git] / kdc / pkinit-ec.c
blob31a5fe7dec5adc44d610ccb867a105d6ca25c2b8
1 /*
2 * Copyright (c) 2016 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
36 #include <config.h>
37 #include <roken.h>
39 #ifdef PKINIT
42 * As with the other *-ec.c files in Heimdal, this is a bit of a hack.
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.
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.
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 */
68 #define NO_HCRYPTO_POLLUTION
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>
77 #include <hx509.h>
78 #include "../lib/hx509/hx_locl.h"
79 #include <hx509-private.h>
81 void
82 _kdc_pk_free_client_ec_param(krb5_context context,
83 void *k0,
84 void *k1)
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
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)
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;
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)");
149 if (ret) {
150 EVP_PKEY_free(ephemeral);
151 ephemeral = NULL;
152 free(p);
153 p = NULL;
154 size = 0;
157 *ec_key_priv = ephemeral;
158 *dh_gen_keylen = size;
159 *dh_gen_key = p;
161 EVP_PKEY_CTX_free(pctx);
162 return ret;
164 #else
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)
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;
182 *dh_gen_key = NULL;
183 *dh_gen_keylen = 0;
184 *ec_key_key = NULL;
186 memset(&key, 0, sizeof(key));
188 if (ec_key_pk == NULL) {
189 ret = KRB5KRB_ERR_GENERIC;
190 krb5_set_error_message(context, ret, "public_key");
191 return ret;
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;
202 ephemeral = EC_KEY_new();
203 if (ephemeral == NULL)
204 return krb5_enomem(context);
206 EC_KEY_set_group(ephemeral, group);
208 if (EC_KEY_generate_key(ephemeral) != 1) {
209 EC_KEY_free(ephemeral);
210 return krb5_enomem(context);
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);
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;
232 *ec_key_key = ephemeral;
233 *dh_gen_key = p;
234 *dh_gen_keylen = len;
236 return 0;
238 #endif
239 #endif /* HAVE_HCRYPTO_W_OPENSSL */
241 krb5_error_code
242 _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)
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 return ENOTSUP;
260 #endif /* HAVE_HCRYPTO_W_OPENSSL */
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)
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;
283 /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
286 * In order for d2i_PublicKey() to work we need to create a template key
287 * that has the curve parameters for the subjectPublicKey.
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).
294 memset(&ecp, 0, sizeof(ecp));
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");
322 if (ret == 0) {
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?
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();
332 if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
333 ret = krb5_enomem(context);
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);
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");
351 if (ret) {
352 EVP_PKEY_free(public);
353 public = NULL;
356 *out = public;
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;
364 #else
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)
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;
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;
385 /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
387 memset(&ecp, 0, sizeof(ecp));
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;
394 if (ecp.element != choice_ECParameters_namedCurve) {
395 ret = KRB5_BADMSGTYPE;
396 goto out;
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;
406 /* XXX verify group is ok */
408 public = EC_KEY_new_by_curve_name(nid);
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;
418 *out = public;
419 public = NULL;
421 out:
422 if (public)
423 EC_KEY_free(public);
424 free_ECParameters(&ecp);
425 return ret;
427 #endif
428 #endif /* HAVE_HCRYPTO_W_OPENSSL */
430 krb5_error_code
431 _kdc_get_ecdh_param(krb5_context context,
432 krb5_kdc_configuration *config,
433 SubjectPublicKeyInfo *dh_key_info,
434 void **out)
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 return ENOTSUP;
444 #endif /* HAVE_HCRYPTO_W_OPENSSL */
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)
460 unsigned char *p;
461 int len;
463 *out = NULL;
464 *out_len = 0;
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;
473 *out = malloc(len);
474 if (*out == NULL)
475 return krb5_enomem(context);
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;
487 *out_len = len * 8;
488 return 0;
490 #else
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)
498 unsigned char *p;
499 int len;
501 *out = NULL;
502 *out_len = 0;
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;
511 *out = malloc(len);
512 if (*out == NULL)
513 return krb5_enomem(context);
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;
525 *out_len = len * 8;
526 return 0;
528 #endif
529 #endif
531 krb5_error_code
532 _kdc_serialize_ecdh_key(krb5_context context,
533 void *key,
534 unsigned char **out,
535 size_t *out_len)
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 return ENOTSUP;
545 #endif
548 #endif