1 /* $NetBSD: pkinit-ec.c,v 1.3 2023/06/19 21:41:42 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
free_client_ec_param(krb5_context context,EC_KEY * ec_key_pk,EC_KEY * ec_key_key)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
_kdc_pk_free_client_ec_param(krb5_context context,void * ec_key_pk,void * ec_key_key)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
generate_ecdh_keyblock(krb5_context context,EC_KEY * ec_key_pk,EC_KEY ** ec_key_key,unsigned char ** dh_gen_key,size_t * dh_gen_keylen)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
_kdc_generate_ecdh_keyblock(krb5_context context,void * ec_key_pk,void ** ec_key_key,unsigned char ** dh_gen_key,size_t * dh_gen_keylen)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
get_ecdh_param(krb5_context context,krb5_kdc_configuration * config,SubjectPublicKeyInfo * dh_key_info,EC_KEY ** out)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
_kdc_get_ecdh_param(krb5_context context,krb5_kdc_configuration * config,SubjectPublicKeyInfo * dh_key_info,void ** out)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
serialize_ecdh_key(krb5_context context,EC_KEY * key,unsigned char ** out,size_t * out_len)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
_kdc_serialize_ecdh_key(krb5_context context,void * key,unsigned char ** out,size_t * out_len)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