xref: /netbsd-src/crypto/external/bsd/heimdal/dist/kdc/pkinit-ec.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
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