2 * Copyright (c) 2016 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
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
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
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>
65 #define HEIM_NO_CRYPTO_HDRS
66 #endif /* HAVE_HCRYPTO_W_OPENSSL */
68 #define NO_HCRYPTO_POLLUTION
71 #include <hcrypto/des.h>
72 #include <heim_asn1.h>
73 #include <rfc2459_asn1.h>
75 #include <pkinit_asn1.h>
78 #include "../lib/hx509/hx_locl.h"
79 #include <hx509-private.h>
82 _kdc_pk_free_client_ec_param(krb5_context context
,
86 #ifdef HAVE_HCRYPTO_W_OPENSSL
87 #ifdef HAVE_OPENSSL_30
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
;
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");
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");
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
);
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)");
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)");
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
);
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)");
150 EVP_PKEY_free(ephemeral
);
157 *ec_key_priv
= ephemeral
;
158 *dh_gen_keylen
= size
;
161 EVP_PKEY_CTX_free(pctx
);
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
;
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");
194 group
= EC_KEY_get0_group(ec_key_pk
);
196 ret
= KRB5KRB_ERR_GENERIC
;
197 krb5_set_error_message(context
, ret
, "failed to get the group of "
198 "the client's public key");
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;
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
),
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");
232 *ec_key_key
= ephemeral
;
234 *dh_gen_keylen
= len
;
239 #endif /* HAVE_HCRYPTO_W_OPENSSL */
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
);
254 return generate_ecdh_keyblock_ossl11(context
, ec_key_pk
,
255 (EC_KEY
**)ec_key_key
,
256 dh_gen_key
, dh_gen_keylen
);
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
,
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;
277 const unsigned char *p
;
278 const char *curve_sn
= NULL
;
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
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");
301 ret
= decode_ECParameters(dh_key_info
->algorithm
.parameters
->data
,
302 dh_key_info
->algorithm
.parameters
->length
,
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");
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",
315 if (ret
== 0 && (curve_sn_dup
= strdup(curve_sn
)) == NULL
)
316 ret
= krb5_enomem(context
);
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");
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
,
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
);
338 EVP_PKEY_fromdata(pctx
, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
,
340 krb5_set_error_message(context
, ret
= KRB5_BADMSGTYPE
,
341 "Could not set up to parse key for curve %s",
344 p
= dh_key_info
->subjectPublicKey
.data
;
345 len
= dh_key_info
->subjectPublicKey
.length
/ 8;
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");
352 EVP_PKEY_free(public);
358 /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */
359 EVP_PKEY_CTX_free(pctx
);
360 free_ECParameters(&ecp
);
366 static krb5_error_code
367 get_ecdh_param_ossl11(krb5_context context
,
368 krb5_kdc_configuration
*config
,
369 SubjectPublicKeyInfo
*dh_key_info
,
373 EC_KEY
*public = NULL
;
375 const unsigned char *p
;
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
);
394 if (ecp
.element
!= choice_ECParameters_namedCurve
) {
395 ret
= KRB5_BADMSGTYPE
;
399 if (der_heim_oid_cmp(&ecp
.u
.namedCurve
, &asn1_oid_id_ec_group_secp256r1
) == 0)
400 nid
= NID_X9_62_prime256v1
;
402 ret
= KRB5_BADMSGTYPE
;
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");
424 free_ECParameters(&ecp
);
428 #endif /* HAVE_HCRYPTO_W_OPENSSL */
431 _kdc_get_ecdh_param(krb5_context context
,
432 krb5_kdc_configuration
*config
,
433 SubjectPublicKeyInfo
*dh_key_info
,
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
);
440 return get_ecdh_param_ossl11(context
, config
, dh_key_info
, (EC_KEY
**)out
);
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
,
466 len
= i2d_PublicKey(key
, NULL
);
468 krb5_set_error_message(context
, EOVERFLOW
,
469 "PKINIT failed to encode ECDH key");
475 return krb5_enomem(context
);
478 len
= i2d_PublicKey(key
, &p
);
482 krb5_set_error_message(context
, EINVAL
/* XXX Better error please */,
483 "PKINIT failed to encode ECDH key");
492 static krb5_error_code
493 serialize_ecdh_key_ossl11(krb5_context context
,
504 len
= i2o_ECPublicKey(key
, NULL
);
506 krb5_set_error_message(context
, EOVERFLOW
,
507 "PKINIT failed to encode ECDH key");
513 return krb5_enomem(context
);
516 len
= i2o_ECPublicKey(key
, &p
);
520 krb5_set_error_message(context
, EINVAL
/* XXX Better error please */,
521 "PKINIT failed to encode ECDH key");
532 _kdc_serialize_ecdh_key(krb5_context context
,
537 #ifdef HAVE_HCRYPTO_W_OPENSSL
538 #ifdef HAVE_OPENSSL_30
539 return serialize_ecdh_key_ossl30(context
, key
, out
, out_len
);
541 return serialize_ecdh_key_ossl11(context
, key
, out
, out_len
);