/src/openssl/ssl/ech/ech_store.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved. |
3 | | * |
4 | | * Licensed under the OpenSSL license (the "License"). You may not use |
5 | | * this file except in compliance with the License. You can obtain a copy |
6 | | * in the file LICENSE in the source distribution or at |
7 | | * https://www.openssl.org/source/license.html |
8 | | */ |
9 | | |
10 | | #include <openssl/ssl.h> |
11 | | #include <openssl/ech.h> |
12 | | #include "../ssl_local.h" |
13 | | #include "ech_local.h" |
14 | | #include <openssl/rand.h> |
15 | | #include <openssl/evp.h> |
16 | | #include <openssl/core_names.h> |
17 | | |
18 | | /* a size for some crypto vars */ |
19 | 0 | #define OSSL_ECH_CRYPTO_VAR_SIZE 2048 |
20 | | |
21 | | /* |
22 | | * Used for ech_bio2buf, when reading from a BIO we allocate in chunks sized |
23 | | * as per below, with a max number of chunks as indicated, we don't expect to |
24 | | * go beyond one chunk in almost all cases |
25 | | */ |
26 | 0 | #define OSSL_ECH_BUFCHUNK 512 |
27 | 0 | #define OSSL_ECH_MAXITER 32 |
28 | | |
29 | | /* |
30 | | * ECHConfigList input to OSSL_ECHSTORE_read_echconfiglist() |
31 | | * can be either binary encoded ECHConfigList or a base64 |
32 | | * encoded ECHConfigList. |
33 | | */ |
34 | 0 | #define OSSL_ECH_FMT_BIN 1 /* binary ECHConfigList */ |
35 | 0 | #define OSSL_ECH_FMT_B64TXT 2 /* base64 ECHConfigList */ |
36 | | |
37 | | /* |
38 | | * Telltales we use when guessing which form of encoded input we've |
39 | | * been given for an RR value or ECHConfig. |
40 | | * We give these the EBCDIC treatment as well - why not? :-) |
41 | | */ |
42 | | static const char B64_alphabet[] = "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52" |
43 | | "\x53\x54\x55\x56\x57\x58\x59\x5a\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a" |
44 | | "\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x30\x31" |
45 | | "\x32\x33\x34\x35\x36\x37\x38\x39\x2b\x2f\x3d\x3b"; |
46 | | |
47 | | #ifndef TLSEXT_MINLEN_host_name |
48 | | /* The shortest DNS name we allow, e.g. "a.bc" */ |
49 | 0 | #define TLSEXT_MINLEN_host_name 4 |
50 | | #endif |
51 | | |
52 | | /* |
53 | | * local functions - public APIs are at the end |
54 | | */ |
55 | | |
56 | | void ossl_echext_free(OSSL_ECHEXT *e) |
57 | 0 | { |
58 | 0 | if (e == NULL) |
59 | 0 | return; |
60 | 0 | OPENSSL_free(e->val); |
61 | 0 | OPENSSL_free(e); |
62 | 0 | return; |
63 | 0 | } |
64 | | |
65 | | OSSL_ECHEXT *ossl_echext_dup(const OSSL_ECHEXT *src) |
66 | 0 | { |
67 | 0 | OSSL_ECHEXT *ext = OPENSSL_zalloc(sizeof(*src)); |
68 | |
|
69 | 0 | if (ext == NULL) |
70 | 0 | return NULL; |
71 | 0 | *ext = *src; |
72 | 0 | ext->val = NULL; |
73 | 0 | if (ext->len != 0) { |
74 | 0 | ext->val = OPENSSL_memdup(src->val, src->len); |
75 | 0 | if (ext->val == NULL) { |
76 | 0 | ossl_echext_free(ext); |
77 | 0 | return NULL; |
78 | 0 | } |
79 | 0 | } |
80 | 0 | return ext; |
81 | 0 | } |
82 | | |
83 | | void ossl_echstore_entry_free(OSSL_ECHSTORE_ENTRY *ee) |
84 | 0 | { |
85 | 0 | if (ee == NULL) |
86 | 0 | return; |
87 | 0 | OPENSSL_free(ee->public_name); |
88 | 0 | OPENSSL_free(ee->pub); |
89 | 0 | EVP_PKEY_free(ee->keyshare); |
90 | 0 | OPENSSL_free(ee->encoded); |
91 | 0 | OPENSSL_free(ee->suites); |
92 | 0 | sk_OSSL_ECHEXT_pop_free(ee->exts, ossl_echext_free); |
93 | 0 | OPENSSL_free(ee); |
94 | 0 | return; |
95 | 0 | } |
96 | | |
97 | | /* |
98 | | * @brief Read a buffer from an input 'till eof |
99 | | * @param in is the BIO input |
100 | | * @param buf is where to put the buffer, allocated inside here |
101 | | * @param len is the length of that buffer |
102 | | * |
103 | | * This is intended for small inputs, either files or buffers and |
104 | | * not other kinds of BIO. |
105 | | */ |
106 | | static int ech_bio2buf(BIO *in, unsigned char **buf, size_t *len) |
107 | 0 | { |
108 | 0 | unsigned char *lptr = NULL, *lbuf = NULL, *tmp = NULL; |
109 | 0 | size_t sofar = 0, readbytes = 0; |
110 | 0 | int done = 0, brv, iter = 0; |
111 | |
|
112 | 0 | if (buf == NULL || len == NULL) |
113 | 0 | return 0; |
114 | 0 | sofar = OSSL_ECH_BUFCHUNK; |
115 | 0 | lbuf = OPENSSL_zalloc(sofar); |
116 | 0 | if (lbuf == NULL) |
117 | 0 | return 0; |
118 | 0 | lptr = lbuf; |
119 | 0 | while (!BIO_eof(in) && !done && iter++ < OSSL_ECH_MAXITER) { |
120 | 0 | brv = BIO_read_ex(in, lptr, OSSL_ECH_BUFCHUNK, &readbytes); |
121 | 0 | if (brv != 1) |
122 | 0 | goto err; |
123 | 0 | if (readbytes < OSSL_ECH_BUFCHUNK) { |
124 | 0 | done = 1; |
125 | 0 | break; |
126 | 0 | } |
127 | 0 | sofar += OSSL_ECH_BUFCHUNK; |
128 | 0 | tmp = OPENSSL_realloc(lbuf, sofar); |
129 | 0 | if (tmp == NULL) |
130 | 0 | goto err; |
131 | 0 | lbuf = tmp; |
132 | 0 | lptr = lbuf + sofar - OSSL_ECH_BUFCHUNK; |
133 | 0 | } |
134 | 0 | if (BIO_eof(in) && done == 1) { |
135 | 0 | *len = sofar + readbytes - OSSL_ECH_BUFCHUNK; |
136 | 0 | *buf = lbuf; |
137 | 0 | return 1; |
138 | 0 | } |
139 | 0 | err: |
140 | 0 | OPENSSL_free(lbuf); |
141 | 0 | return 0; |
142 | 0 | } |
143 | | |
144 | | /* |
145 | | * @brief Figure out ECHConfig encoding |
146 | | * @param val is a buffer with the encoding |
147 | | * @param len is the length of that buffer |
148 | | * @param fmt is the detected format |
149 | | * @return 1 for success, 0 for error |
150 | | */ |
151 | | static int ech_check_format(const unsigned char *val, size_t len, int *fmt) |
152 | 0 | { |
153 | 0 | size_t span = 0; |
154 | 0 | char *copy_with_NUL = NULL; |
155 | |
|
156 | 0 | if (fmt == NULL || len <= 4 || val == NULL) |
157 | 0 | return 0; |
158 | | /* binary encoding starts with two octet length and ECH version */ |
159 | 0 | if (len == 2 + ((size_t)(val[0]) * 256 + (size_t)(val[1])) |
160 | 0 | && val[2] == ((OSSL_ECH_RFC9849_VERSION / 256) & 0xff) |
161 | 0 | && val[3] == ((OSSL_ECH_RFC9849_VERSION % 256) & 0xff)) { |
162 | 0 | *fmt = OSSL_ECH_FMT_BIN; |
163 | 0 | return 1; |
164 | 0 | } |
165 | | /* ensure we always end with a NUL so strspn is safe */ |
166 | 0 | copy_with_NUL = OPENSSL_malloc(len + 1); |
167 | 0 | if (copy_with_NUL == NULL) |
168 | 0 | return 0; |
169 | 0 | memcpy(copy_with_NUL, val, len); |
170 | 0 | copy_with_NUL[len] = '\0'; |
171 | 0 | span = strspn(copy_with_NUL, B64_alphabet); |
172 | 0 | OPENSSL_free(copy_with_NUL); |
173 | 0 | if (len <= span) { |
174 | 0 | *fmt = OSSL_ECH_FMT_B64TXT; |
175 | 0 | return 1; |
176 | 0 | } |
177 | 0 | return 0; |
178 | 0 | } |
179 | | |
180 | | /* |
181 | | * @brief helper to decode ECHConfig extensions |
182 | | * @param ee is the OSSL_ECHSTORE entry for these |
183 | | * @param exts is the binary form extensions |
184 | | * @return 1 for good, 0 for error |
185 | | */ |
186 | | static int ech_decode_echconfig_exts(OSSL_ECHSTORE_ENTRY *ee, PACKET *exts) |
187 | 0 | { |
188 | 0 | unsigned int exttype = 0; |
189 | 0 | size_t extlen = 0; |
190 | 0 | unsigned char *extval = NULL; |
191 | 0 | OSSL_ECHEXT *oe = NULL; |
192 | 0 | PACKET ext; |
193 | | |
194 | | /* |
195 | | * reminder: exts is a two-octet length prefixed list of: |
196 | | * - two octet extension type |
197 | | * - two octet extension length (can be zero) |
198 | | * - length octets |
199 | | * we've consumed the overall length before getting here |
200 | | */ |
201 | 0 | while (PACKET_remaining(exts) > 0) { |
202 | 0 | exttype = 0, extlen = 0; |
203 | 0 | extval = NULL; |
204 | 0 | oe = NULL; |
205 | 0 | if (!PACKET_get_net_2(exts, &exttype) || !PACKET_get_length_prefixed_2(exts, &ext)) { |
206 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_BAD_ECHCONFIG_EXTENSION); |
207 | 0 | goto err; |
208 | 0 | } |
209 | 0 | if (PACKET_remaining(&ext) >= OSSL_ECH_MAX_ECHCONFIGEXT_LEN) { |
210 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_BAD_ECHCONFIG_EXTENSION); |
211 | 0 | goto err; |
212 | 0 | } |
213 | 0 | if (!PACKET_memdup(&ext, &extval, &extlen)) { |
214 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_BAD_ECHCONFIG_EXTENSION); |
215 | 0 | goto err; |
216 | 0 | } |
217 | 0 | oe = OPENSSL_malloc(sizeof(*oe)); |
218 | 0 | if (oe == NULL) |
219 | 0 | goto err; |
220 | 0 | oe->type = (uint16_t)exttype; |
221 | 0 | oe->val = extval; |
222 | 0 | extval = NULL; /* avoid double free */ |
223 | 0 | oe->len = (uint16_t)extlen; |
224 | 0 | if (ee->exts == NULL) |
225 | 0 | ee->exts = sk_OSSL_ECHEXT_new_null(); |
226 | 0 | if (ee->exts == NULL) { |
227 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
228 | 0 | goto err; |
229 | 0 | } |
230 | 0 | if (!sk_OSSL_ECHEXT_push(ee->exts, oe)) { |
231 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
232 | 0 | goto err; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | return 1; |
236 | 0 | err: |
237 | 0 | sk_OSSL_ECHEXT_pop_free(ee->exts, ossl_echext_free); |
238 | 0 | ee->exts = NULL; |
239 | 0 | ossl_echext_free(oe); |
240 | 0 | OPENSSL_free(extval); |
241 | 0 | return 0; |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * @brief Check entry to see if looks good or bad |
246 | | * @param ee is the ECHConfig to check |
247 | | * @return 1 for all good, 0 otherwise |
248 | | */ |
249 | | static int ech_final_config_checks(OSSL_ECHSTORE_ENTRY *ee) |
250 | 0 | { |
251 | 0 | OSSL_HPKE_SUITE hpke_suite; |
252 | 0 | int ind, num; |
253 | 0 | int goodsuitefound = 0; |
254 | | |
255 | | /* check local support for some suite */ |
256 | 0 | for (ind = 0; ind != (int)ee->nsuites; ind++) { |
257 | | /* |
258 | | * suite_check says yes to the pseudo-aead for export, but we don't |
259 | | * want to see it here coming from outside in an encoding |
260 | | */ |
261 | 0 | hpke_suite = ee->suites[ind]; |
262 | 0 | if (OSSL_HPKE_suite_check(hpke_suite) == 1 |
263 | 0 | && hpke_suite.aead_id != OSSL_HPKE_AEAD_ID_EXPORTONLY) { |
264 | 0 | goodsuitefound = 1; |
265 | 0 | break; |
266 | 0 | } |
267 | 0 | } |
268 | 0 | if (goodsuitefound == 0) { |
269 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
270 | 0 | return 0; |
271 | 0 | } |
272 | | /* check no mandatory exts (with high bit set in type) */ |
273 | 0 | num = (ee->exts == NULL ? 0 : sk_OSSL_ECHEXT_num(ee->exts)); |
274 | 0 | for (ind = 0; ind != num; ind++) { |
275 | 0 | OSSL_ECHEXT *oe = sk_OSSL_ECHEXT_value(ee->exts, (int)ind); |
276 | |
|
277 | 0 | if (oe->type & 0x8000) { |
278 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
279 | 0 | return 0; |
280 | 0 | } |
281 | 0 | } |
282 | | /* check public_name rules, as per spec section 4 */ |
283 | 0 | if (ee->public_name == NULL |
284 | 0 | || ee->public_name[0] == '\0' |
285 | 0 | || ee->public_name[0] == '.' |
286 | 0 | || ee->public_name[strlen(ee->public_name) - 1] == '.') |
287 | 0 | return 0; |
288 | 0 | return 1; |
289 | 0 | } |
290 | | |
291 | | /** |
292 | | * @brief decode one ECHConfig from a packet into an entry |
293 | | * @param rent ptr to an entry allocated within (on success) |
294 | | * @param pkt is the encoding |
295 | | * @param priv is an optional private key (NULL if absent) |
296 | | * @param for_retry says whether to include in a retry_config (if priv present) |
297 | | * @return 1 for success, 0 for error |
298 | | */ |
299 | | static int ech_decode_one_entry(OSSL_ECHSTORE_ENTRY **rent, PACKET *pkt, |
300 | | EVP_PKEY *priv, int for_retry) |
301 | 0 | { |
302 | 0 | size_t ech_content_length = 0; |
303 | 0 | unsigned int tmpi; |
304 | 0 | const unsigned char *tmpecp = NULL; |
305 | 0 | size_t tmpeclen = 0, test_publen = 0; |
306 | 0 | PACKET ver_pkt, pub_pkt, cipher_suites, public_name_pkt, exts; |
307 | 0 | uint16_t thiskemid; |
308 | 0 | size_t suiteoctets = 0; |
309 | 0 | unsigned int ci = 0; |
310 | 0 | unsigned char cipher[OSSL_ECH_CIPHER_LEN], max_name_len; |
311 | 0 | unsigned char test_pub[OSSL_ECH_CRYPTO_VAR_SIZE]; |
312 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
313 | |
|
314 | 0 | if (rent == NULL) { |
315 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
316 | 0 | return 0; |
317 | 0 | } |
318 | 0 | if (pkt == NULL) { |
319 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
320 | 0 | goto err; |
321 | 0 | } |
322 | 0 | ee = OPENSSL_zalloc(sizeof(*ee)); |
323 | 0 | if (ee == NULL) |
324 | 0 | goto err; |
325 | | /* note start of encoding so we can make a copy later */ |
326 | 0 | tmpeclen = PACKET_remaining(pkt); |
327 | 0 | if (PACKET_peek_bytes(pkt, &tmpecp, tmpeclen) != 1 |
328 | 0 | || !PACKET_get_net_2(pkt, &tmpi)) { |
329 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
330 | 0 | goto err; |
331 | 0 | } |
332 | 0 | ee->version = (uint16_t)tmpi; |
333 | | |
334 | | /* grab versioned packet data */ |
335 | 0 | if (!PACKET_get_length_prefixed_2(pkt, &ver_pkt)) { |
336 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
337 | 0 | goto err; |
338 | 0 | } |
339 | 0 | ech_content_length = (unsigned int)PACKET_remaining(&ver_pkt); |
340 | 0 | switch (ee->version) { |
341 | 0 | case OSSL_ECH_RFC9849_VERSION: |
342 | 0 | break; |
343 | 0 | default: |
344 | | /* skip over in case we get something we can handle later */ |
345 | 0 | if (!PACKET_forward(&ver_pkt, ech_content_length)) { |
346 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
347 | 0 | goto err; |
348 | 0 | } |
349 | | /* nothing to return but not a fail */ |
350 | 0 | ossl_echstore_entry_free(ee); |
351 | 0 | *rent = NULL; |
352 | 0 | return 1; |
353 | 0 | } |
354 | 0 | if (!PACKET_copy_bytes(&ver_pkt, &ee->config_id, 1) |
355 | 0 | || !PACKET_get_net_2(&ver_pkt, &tmpi) |
356 | 0 | || !PACKET_get_length_prefixed_2(&ver_pkt, &pub_pkt) |
357 | 0 | || !PACKET_memdup(&pub_pkt, &ee->pub, &ee->pub_len) |
358 | 0 | || !PACKET_get_length_prefixed_2(&ver_pkt, &cipher_suites) |
359 | 0 | || (suiteoctets = PACKET_remaining(&cipher_suites)) <= 0 |
360 | 0 | || (suiteoctets % 2) == 1 |
361 | 0 | || suiteoctets / OSSL_ECH_CIPHER_LEN > UINT_MAX) { |
362 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
363 | 0 | goto err; |
364 | 0 | } |
365 | 0 | thiskemid = (uint16_t)tmpi; |
366 | 0 | ee->nsuites = (unsigned int)(suiteoctets / OSSL_ECH_CIPHER_LEN); |
367 | 0 | ee->suites = OPENSSL_malloc_array(ee->nsuites, sizeof(*ee->suites)); |
368 | 0 | if (ee->suites == NULL) |
369 | 0 | goto err; |
370 | 0 | while (PACKET_copy_bytes(&cipher_suites, cipher, |
371 | 0 | OSSL_ECH_CIPHER_LEN)) { |
372 | 0 | ee->suites[ci].kem_id = thiskemid; |
373 | 0 | ee->suites[ci].kdf_id = cipher[0] << 8 | cipher[1]; |
374 | 0 | ee->suites[ci].aead_id = cipher[2] << 8 | cipher[3]; |
375 | 0 | if (ci++ >= ee->nsuites) { |
376 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
377 | 0 | goto err; |
378 | 0 | } |
379 | 0 | } |
380 | 0 | if (PACKET_remaining(&cipher_suites) > 0 |
381 | 0 | || !PACKET_copy_bytes(&ver_pkt, &max_name_len, 1)) { |
382 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
383 | 0 | goto err; |
384 | 0 | } |
385 | 0 | ee->max_name_length = max_name_len; |
386 | 0 | if (!PACKET_get_length_prefixed_1(&ver_pkt, &public_name_pkt)) { |
387 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
388 | 0 | goto err; |
389 | 0 | } |
390 | 0 | if (PACKET_contains_zero_byte(&public_name_pkt) |
391 | 0 | || PACKET_remaining(&public_name_pkt) < TLSEXT_MINLEN_host_name |
392 | 0 | || !PACKET_strndup(&public_name_pkt, &ee->public_name)) { |
393 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
394 | 0 | goto err; |
395 | 0 | } |
396 | 0 | if (!PACKET_get_length_prefixed_2(&ver_pkt, &exts)) { |
397 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
398 | 0 | goto err; |
399 | 0 | } |
400 | 0 | if (PACKET_remaining(&exts) > 0 |
401 | 0 | && ech_decode_echconfig_exts(ee, &exts) != 1) { |
402 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
403 | 0 | goto err; |
404 | 0 | } |
405 | | /* set length of encoding of this ECHConfig */ |
406 | 0 | ee->encoded_len = PACKET_data(&ver_pkt) - tmpecp; |
407 | | /* copy encoded as it might get free'd if a reduce happens */ |
408 | 0 | ee->encoded = OPENSSL_memdup(tmpecp, ee->encoded_len); |
409 | 0 | if (ee->encoded == NULL) |
410 | 0 | goto err; |
411 | 0 | if (priv != NULL) { |
412 | 0 | if (EVP_PKEY_get_octet_string_param(priv, |
413 | 0 | OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, |
414 | 0 | test_pub, OSSL_ECH_CRYPTO_VAR_SIZE, |
415 | 0 | &test_publen) |
416 | 0 | != 1) { |
417 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
418 | 0 | goto err; |
419 | 0 | } |
420 | 0 | if (test_publen == ee->pub_len |
421 | 0 | && !memcmp(test_pub, ee->pub, ee->pub_len)) { |
422 | 0 | EVP_PKEY_up_ref(priv); /* associate the private key */ |
423 | 0 | ee->keyshare = priv; |
424 | 0 | ee->for_retry = for_retry; |
425 | 0 | } |
426 | 0 | } |
427 | 0 | ee->loadtime = time(0); |
428 | 0 | *rent = ee; |
429 | 0 | return 1; |
430 | 0 | err: |
431 | 0 | ossl_echstore_entry_free(ee); |
432 | 0 | *rent = NULL; |
433 | 0 | return 0; |
434 | 0 | } |
435 | | |
436 | | /* |
437 | | * @brief decode and flatten a binary encoded ECHConfigList |
438 | | * @param es an OSSL_ECHSTORE |
439 | | * @param priv is an optional private key (NULL if absent) |
440 | | * @param for_retry says whether to include in a retry_config (if priv present) |
441 | | * @param binbuf binary encoded ECHConfigList (we hope) |
442 | | * @param binlen length of binbuf |
443 | | * @return 1 for success, 0 for error |
444 | | * |
445 | | * We may only get one ECHConfig per list, but there can be more. We want each |
446 | | * element of the output to contain exactly one ECHConfig so that a client |
447 | | * could sensibly down select to the one they prefer later, and so that we have |
448 | | * the specific encoded value of that ECHConfig for inclusion in the HPKE info |
449 | | * parameter when finally encrypting or decrypting an inner ClientHello. |
450 | | * |
451 | | * If a private value is provided then that'll only be associated with the |
452 | | * relevant public value, if >1 public value was present in the ECHConfigList. |
453 | | */ |
454 | | static int ech_decode_and_flatten(OSSL_ECHSTORE *es, EVP_PKEY *priv, int for_retry, |
455 | | unsigned char *binbuf, size_t binblen) |
456 | 0 | { |
457 | 0 | int rv = 0; |
458 | 0 | size_t remaining = 0; |
459 | 0 | PACKET opkt, pkt; |
460 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
461 | |
|
462 | 0 | if (binbuf == NULL || binblen == 0 || binblen < OSSL_ECH_MIN_ECHCONFIG_LEN |
463 | 0 | || binblen >= OSSL_ECH_MAX_ECHCONFIG_LEN) { |
464 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
465 | 0 | goto err; |
466 | 0 | } |
467 | 0 | if (PACKET_buf_init(&opkt, binbuf, binblen) != 1 |
468 | 0 | || !PACKET_get_length_prefixed_2(&opkt, &pkt)) { |
469 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
470 | 0 | goto err; |
471 | 0 | } |
472 | 0 | remaining = PACKET_remaining(&pkt); |
473 | 0 | while (remaining > 0) { |
474 | 0 | if (ech_decode_one_entry(&ee, &pkt, priv, for_retry) != 1) { |
475 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
476 | 0 | goto err; |
477 | 0 | } |
478 | 0 | remaining = PACKET_remaining(&pkt); |
479 | | /* if unsupported version we can skip over */ |
480 | 0 | if (ee == NULL) |
481 | 0 | continue; |
482 | | /* do final checks on suites, exts, and fail if issues */ |
483 | 0 | if (ech_final_config_checks(ee) != 1) |
484 | 0 | goto err; |
485 | | /* push entry into store */ |
486 | 0 | if (es->entries == NULL) |
487 | 0 | es->entries = sk_OSSL_ECHSTORE_ENTRY_new_null(); |
488 | 0 | if (es->entries == NULL) { |
489 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
490 | 0 | goto err; |
491 | 0 | } |
492 | 0 | if (!sk_OSSL_ECHSTORE_ENTRY_push(es->entries, ee)) { |
493 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
494 | 0 | goto err; |
495 | 0 | } |
496 | 0 | ee = NULL; |
497 | 0 | } |
498 | 0 | rv = 1; |
499 | 0 | err: |
500 | 0 | ossl_echstore_entry_free(ee); |
501 | 0 | return rv; |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * @brief check a private matches some public |
506 | | * @param es is the ECH store |
507 | | * @param priv is the private value |
508 | | * @return 1 if we have a match, zero otherwise |
509 | | */ |
510 | | static int check_priv_matches(OSSL_ECHSTORE *es, EVP_PKEY *priv) |
511 | 0 | { |
512 | 0 | int num, ent, gotone = 0; |
513 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
514 | |
|
515 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
516 | 0 | for (ent = 0; ent != num; ent++) { |
517 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, ent); |
518 | 0 | if (ee == NULL) { |
519 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
520 | 0 | return 0; |
521 | 0 | } |
522 | 0 | if (EVP_PKEY_eq(ee->keyshare, priv)) { |
523 | 0 | gotone = 1; |
524 | 0 | break; |
525 | 0 | } |
526 | 0 | } |
527 | 0 | return gotone; |
528 | 0 | } |
529 | | |
530 | | /* |
531 | | * @brief decode input ECHConfigList and associate optional private info |
532 | | * @param es is the OSSL_ECHSTORE |
533 | | * @param in is the BIO from which we'll get the ECHConfigList |
534 | | * @param priv is an optional private key |
535 | | * @param for_retry 1 if the public related to priv ought be in retry_config |
536 | | */ |
537 | | static int ech_read_priv_echconfiglist(OSSL_ECHSTORE *es, BIO *in, |
538 | | EVP_PKEY *priv, int for_retry) |
539 | 0 | { |
540 | 0 | int rv = 0, detfmt, tdeclen = 0; |
541 | 0 | size_t encodedlen = 0, binlen = 0; |
542 | 0 | unsigned char *encodedval = NULL, *binbuf = NULL; |
543 | 0 | BIO *btmp = NULL, *btmp1 = NULL; |
544 | |
|
545 | 0 | if (es == NULL || in == NULL) { |
546 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
547 | 0 | return 0; |
548 | 0 | } |
549 | 0 | if (ech_bio2buf(in, &encodedval, &encodedlen) != 1) { |
550 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
551 | 0 | return 0; |
552 | 0 | } |
553 | 0 | if (encodedlen >= OSSL_ECH_MAX_ECHCONFIG_LEN) { /* sanity check */ |
554 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
555 | 0 | goto err; |
556 | 0 | } |
557 | 0 | if (ech_check_format(encodedval, encodedlen, &detfmt) != 1) { |
558 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
559 | 0 | goto err; |
560 | 0 | } |
561 | 0 | if (detfmt == OSSL_ECH_FMT_BIN) { /* copy buffer if binary format */ |
562 | 0 | binbuf = OPENSSL_memdup(encodedval, encodedlen); |
563 | 0 | if (binbuf == NULL) |
564 | 0 | goto err; |
565 | 0 | binlen = encodedlen; |
566 | 0 | } |
567 | 0 | if (detfmt == OSSL_ECH_FMT_B64TXT) { |
568 | 0 | btmp = BIO_new_mem_buf(encodedval, (int)encodedlen); |
569 | 0 | if (btmp == NULL) { |
570 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
571 | 0 | goto err; |
572 | 0 | } |
573 | 0 | btmp1 = BIO_new(BIO_f_base64()); |
574 | 0 | if (btmp1 == NULL) { |
575 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
576 | 0 | goto err; |
577 | 0 | } |
578 | 0 | BIO_set_flags(btmp1, BIO_FLAGS_BASE64_NO_NL); |
579 | 0 | btmp = BIO_push(btmp1, btmp); |
580 | | /* overestimate but good enough */ |
581 | 0 | binbuf = OPENSSL_malloc(encodedlen); |
582 | 0 | if (binbuf == NULL) |
583 | 0 | goto err; |
584 | 0 | tdeclen = BIO_read(btmp, binbuf, (int)encodedlen); |
585 | 0 | if (tdeclen <= 0) { /* need int for -1 return in failure case */ |
586 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
587 | 0 | goto err; |
588 | 0 | } |
589 | 0 | binlen = tdeclen; |
590 | 0 | } |
591 | 0 | if (ech_decode_and_flatten(es, priv, for_retry, binbuf, binlen) != 1) { |
592 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
593 | 0 | goto err; |
594 | 0 | } |
595 | 0 | if (priv != NULL && check_priv_matches(es, priv) == 0) |
596 | 0 | goto err; |
597 | 0 | rv = 1; |
598 | 0 | err: |
599 | 0 | BIO_free_all(btmp); |
600 | 0 | OPENSSL_free(binbuf); |
601 | 0 | OPENSSL_free(encodedval); |
602 | 0 | return rv; |
603 | 0 | } |
604 | | |
605 | | /* |
606 | | * API calls built around OSSL_ECHSSTORE |
607 | | */ |
608 | | |
609 | | OSSL_ECHSTORE *OSSL_ECHSTORE_new(OSSL_LIB_CTX *libctx, const char *propq) |
610 | 0 | { |
611 | 0 | OSSL_ECHSTORE *es = NULL; |
612 | |
|
613 | 0 | es = OPENSSL_zalloc(sizeof(*es)); |
614 | 0 | if (es == NULL) |
615 | 0 | return 0; |
616 | 0 | es->libctx = libctx; |
617 | 0 | if (propq != NULL) { |
618 | 0 | es->propq = OPENSSL_strdup(propq); |
619 | 0 | if (es->propq == NULL) { |
620 | 0 | OPENSSL_free(es); |
621 | 0 | return 0; |
622 | 0 | } |
623 | 0 | } |
624 | | |
625 | 0 | return es; |
626 | 0 | } |
627 | | |
628 | | void OSSL_ECHSTORE_free(OSSL_ECHSTORE *es) |
629 | 0 | { |
630 | 0 | if (es == NULL) |
631 | 0 | return; |
632 | 0 | sk_OSSL_ECHSTORE_ENTRY_pop_free(es->entries, ossl_echstore_entry_free); |
633 | 0 | OPENSSL_free(es->propq); |
634 | 0 | OPENSSL_free(es); |
635 | 0 | return; |
636 | 0 | } |
637 | | |
638 | | int OSSL_ECHSTORE_new_config(OSSL_ECHSTORE *es, |
639 | | uint16_t echversion, uint8_t max_name_length, |
640 | | const char *public_name, OSSL_HPKE_SUITE suite) |
641 | 0 | { |
642 | 0 | size_t pnlen = 0, publen = OSSL_ECH_CRYPTO_VAR_SIZE; |
643 | 0 | unsigned char pub[OSSL_ECH_CRYPTO_VAR_SIZE]; |
644 | 0 | int rv = 0; |
645 | 0 | unsigned char *bp = NULL; |
646 | 0 | size_t bblen = 0; |
647 | 0 | EVP_PKEY *privp = NULL; |
648 | 0 | uint8_t config_id = 0; |
649 | 0 | WPACKET epkt; |
650 | 0 | BUF_MEM *epkt_mem = NULL; |
651 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
652 | | |
653 | | /* basic checks */ |
654 | 0 | if (es == NULL) { |
655 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
656 | 0 | return 0; |
657 | 0 | } |
658 | 0 | pnlen = (public_name == NULL ? 0 : strlen(public_name)); |
659 | 0 | if (pnlen == 0 || pnlen > OSSL_ECH_MAX_PUBLICNAME) { |
660 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
661 | 0 | return 0; |
662 | 0 | } |
663 | | /* this used have more versions and will again in future */ |
664 | 0 | switch (echversion) { |
665 | 0 | case OSSL_ECH_RFC9849_VERSION: |
666 | 0 | break; |
667 | 0 | default: |
668 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
669 | 0 | return 0; |
670 | 0 | } |
671 | | /* |
672 | | * Reminder, for draft-13 we want this: |
673 | | * |
674 | | * opaque HpkePublicKey<1..2^16-1>; |
675 | | * uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke |
676 | | * uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke |
677 | | * uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke |
678 | | * struct { |
679 | | * HpkeKdfId kdf_id; |
680 | | * HpkeAeadId aead_id; |
681 | | * } HpkeSymmetricCipherSuite; |
682 | | * struct { |
683 | | * uint8 config_id; |
684 | | * HpkeKemId kem_id; |
685 | | * HpkePublicKey public_key; |
686 | | * HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>; |
687 | | * } HpkeKeyConfig; |
688 | | * struct { |
689 | | * HpkeKeyConfig key_config; |
690 | | * uint8 maximum_name_length; |
691 | | * opaque public_name<1..255>; |
692 | | * Extension extensions<0..2^16-1>; |
693 | | * } ECHConfigContents; |
694 | | * struct { |
695 | | * uint16 version; |
696 | | * uint16 length; |
697 | | * select (ECHConfig.version) { |
698 | | * case 0xfe0d: ECHConfigContents contents; |
699 | | * } |
700 | | * } ECHConfig; |
701 | | * ECHConfig ECHConfigList<1..2^16-1>; |
702 | | */ |
703 | 0 | if ((epkt_mem = BUF_MEM_new()) == NULL |
704 | 0 | || !BUF_MEM_grow(epkt_mem, OSSL_ECH_MAX_ECHCONFIG_LEN) |
705 | 0 | || !WPACKET_init(&epkt, epkt_mem)) { |
706 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
707 | 0 | goto err_no_epkt; |
708 | 0 | } |
709 | | /* random config_id */ |
710 | 0 | if (RAND_bytes_ex(es->libctx, (unsigned char *)&config_id, 1, 0) <= 0) { |
711 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
712 | 0 | goto err; |
713 | 0 | } |
714 | | /* key pair */ |
715 | 0 | if (OSSL_HPKE_keygen(suite, pub, &publen, &privp, NULL, 0, |
716 | 0 | es->libctx, es->propq) |
717 | 0 | != 1) { |
718 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
719 | 0 | goto err; |
720 | 0 | } |
721 | | /* config id, KEM, public, KDF, AEAD, max name len, public_name, exts */ |
722 | 0 | if ((bp = WPACKET_get_curr(&epkt)) == NULL |
723 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
724 | 0 | || !WPACKET_put_bytes_u16(&epkt, echversion) |
725 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
726 | 0 | || !WPACKET_put_bytes_u8(&epkt, config_id) |
727 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.kem_id) |
728 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
729 | 0 | || !WPACKET_memcpy(&epkt, pub, publen) |
730 | 0 | || !WPACKET_close(&epkt) |
731 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
732 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.kdf_id) |
733 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.aead_id) |
734 | 0 | || !WPACKET_close(&epkt) |
735 | 0 | || !WPACKET_put_bytes_u8(&epkt, max_name_length) |
736 | 0 | || !WPACKET_start_sub_packet_u8(&epkt) |
737 | 0 | || !WPACKET_memcpy(&epkt, public_name, pnlen) |
738 | 0 | || !WPACKET_close(&epkt) |
739 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
740 | 0 | || !WPACKET_memcpy(&epkt, NULL, 0) /* no extensions */ |
741 | 0 | || !WPACKET_close(&epkt) |
742 | 0 | || !WPACKET_close(&epkt) |
743 | 0 | || !WPACKET_close(&epkt)) { |
744 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
745 | 0 | goto err; |
746 | 0 | } |
747 | | /* bp, bblen has encoding */ |
748 | 0 | if (!WPACKET_get_total_written(&epkt, &bblen)) { |
749 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
750 | 0 | goto err; |
751 | 0 | } |
752 | 0 | if ((ee = OPENSSL_zalloc(sizeof(*ee))) == NULL) |
753 | 0 | goto err; |
754 | 0 | ee->suites = OPENSSL_malloc(sizeof(*ee->suites)); |
755 | 0 | if (ee->suites == NULL) |
756 | 0 | goto err; |
757 | 0 | ee->version = echversion; |
758 | 0 | ee->pub_len = publen; |
759 | 0 | ee->pub = OPENSSL_memdup(pub, publen); |
760 | 0 | if (ee->pub == NULL) |
761 | 0 | goto err; |
762 | 0 | ee->nsuites = 1; |
763 | 0 | ee->suites[0] = suite; |
764 | 0 | ee->public_name = OPENSSL_strdup(public_name); |
765 | 0 | if (ee->public_name == NULL) |
766 | 0 | goto err; |
767 | 0 | ee->max_name_length = max_name_length; |
768 | 0 | ee->config_id = config_id; |
769 | 0 | ee->keyshare = privp; |
770 | 0 | privp = NULL; /* don't free twice */ |
771 | | /* "steal" the encoding from the memory */ |
772 | 0 | ee->encoded = (unsigned char *)epkt_mem->data; |
773 | 0 | ee->encoded_len = bblen; |
774 | 0 | epkt_mem->data = NULL; |
775 | 0 | epkt_mem->length = 0; |
776 | 0 | ee->loadtime = time(0); |
777 | | /* push entry into store */ |
778 | 0 | if (es->entries == NULL) |
779 | 0 | es->entries = sk_OSSL_ECHSTORE_ENTRY_new_null(); |
780 | 0 | if (es->entries == NULL) { |
781 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
782 | 0 | goto err; |
783 | 0 | } |
784 | 0 | if (!sk_OSSL_ECHSTORE_ENTRY_push(es->entries, ee)) { |
785 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
786 | 0 | goto err; |
787 | 0 | } |
788 | 0 | WPACKET_finish(&epkt); |
789 | 0 | BUF_MEM_free(epkt_mem); |
790 | 0 | return 1; |
791 | | |
792 | 0 | err: |
793 | 0 | ossl_echstore_entry_free(ee); |
794 | 0 | EVP_PKEY_free(privp); |
795 | 0 | WPACKET_cleanup(&epkt); |
796 | 0 | err_no_epkt: |
797 | 0 | BUF_MEM_free(epkt_mem); |
798 | 0 | return rv; |
799 | 0 | } |
800 | | |
801 | | int OSSL_ECHSTORE_write_pem(OSSL_ECHSTORE *es, int index, BIO *out) |
802 | 0 | { |
803 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
804 | 0 | int rv = 0, num = 0, chosen = 0, doall = 0; |
805 | 0 | WPACKET epkt; /* used if we want to merge ECHConfigs for output */ |
806 | 0 | BUF_MEM *epkt_mem = NULL; |
807 | 0 | size_t allencoded_len; |
808 | |
|
809 | 0 | if (es == NULL) { |
810 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
811 | 0 | return 0; |
812 | 0 | } |
813 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
814 | 0 | if (num <= 0) { |
815 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
816 | 0 | return 0; |
817 | 0 | } |
818 | 0 | if (index >= num) { |
819 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
820 | 0 | return 0; |
821 | 0 | } |
822 | 0 | if (index == OSSL_ECHSTORE_ALL) |
823 | 0 | doall = 1; |
824 | 0 | else if (index == OSSL_ECHSTORE_LAST) |
825 | 0 | chosen = num - 1; |
826 | 0 | else |
827 | 0 | chosen = index; |
828 | 0 | memset(&epkt, 0, sizeof(epkt)); |
829 | 0 | if (doall == 0) { |
830 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, chosen); |
831 | 0 | if (ee == NULL || ee->encoded == NULL) { |
832 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
833 | 0 | return 0; |
834 | 0 | } |
835 | | /* private key first */ |
836 | 0 | if (ee->keyshare != NULL |
837 | 0 | && !PEM_write_bio_PrivateKey(out, ee->keyshare, NULL, NULL, 0, |
838 | 0 | NULL, NULL)) { |
839 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
840 | 0 | goto err; |
841 | 0 | } |
842 | 0 | if (PEM_write_bio(out, PEM_STRING_ECHCONFIG, NULL, |
843 | 0 | ee->encoded, (long)ee->encoded_len) |
844 | 0 | <= 0) { |
845 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
846 | 0 | goto err; |
847 | 0 | } |
848 | 0 | } else { |
849 | | /* catenate the encodings into one */ |
850 | 0 | if ((epkt_mem = BUF_MEM_new()) == NULL |
851 | 0 | || !BUF_MEM_grow(epkt_mem, OSSL_ECH_MAX_ECHCONFIG_LEN) |
852 | 0 | || !WPACKET_init(&epkt, epkt_mem) |
853 | 0 | || !WPACKET_start_sub_packet_u16(&epkt)) { |
854 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
855 | 0 | goto err; |
856 | 0 | } |
857 | 0 | for (chosen = 0; chosen != num; chosen++) { |
858 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, chosen); |
859 | 0 | if (ee == NULL || ee->encoded == NULL) { |
860 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
861 | 0 | return 0; |
862 | 0 | } |
863 | 0 | if (!WPACKET_memcpy(&epkt, ee->encoded, ee->encoded_len)) { |
864 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
865 | 0 | goto err; |
866 | 0 | } |
867 | 0 | } |
868 | 0 | if (!WPACKET_close(&epkt)) { |
869 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
870 | 0 | goto err; |
871 | 0 | } |
872 | 0 | if (!WPACKET_get_total_written(&epkt, &allencoded_len)) { |
873 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
874 | 0 | goto err; |
875 | 0 | } |
876 | 0 | if (PEM_write_bio(out, PEM_STRING_ECHCONFIG, NULL, |
877 | 0 | (unsigned char *)epkt_mem->data, |
878 | 0 | (long)allencoded_len) |
879 | 0 | <= 0) { |
880 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
881 | 0 | goto err; |
882 | 0 | } |
883 | 0 | } |
884 | 0 | rv = 1; |
885 | 0 | err: |
886 | 0 | WPACKET_cleanup(&epkt); |
887 | 0 | BUF_MEM_free(epkt_mem); |
888 | 0 | return rv; |
889 | 0 | } |
890 | | |
891 | | int OSSL_ECHSTORE_read_echconfiglist(OSSL_ECHSTORE *es, BIO *in) |
892 | 0 | { |
893 | 0 | return ech_read_priv_echconfiglist(es, in, NULL, 0); |
894 | 0 | } |
895 | | |
896 | | int OSSL_ECHSTORE_get1_info(OSSL_ECHSTORE *es, int index, time_t *loaded_secs, |
897 | | char **public_name, char **echconfig, |
898 | | int *has_private, int *for_retry) |
899 | 0 | { |
900 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
901 | 0 | unsigned int j = 0; |
902 | 0 | int num = 0; |
903 | 0 | BIO *out = NULL; |
904 | 0 | time_t now = time(0); |
905 | 0 | size_t ehlen; |
906 | 0 | unsigned char *ignore = NULL; |
907 | |
|
908 | 0 | if (es == NULL || loaded_secs == NULL || public_name == NULL |
909 | 0 | || echconfig == NULL || has_private == NULL || for_retry == NULL) { |
910 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
911 | 0 | return 0; |
912 | 0 | } |
913 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
914 | 0 | if (num == 0 || index < 0 || index >= num) { |
915 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
916 | 0 | return 0; |
917 | 0 | } |
918 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, index); |
919 | 0 | if (ee == NULL) { |
920 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
921 | 0 | return 0; |
922 | 0 | } |
923 | 0 | *loaded_secs = now - ee->loadtime; |
924 | 0 | *public_name = NULL; |
925 | 0 | *echconfig = NULL; |
926 | 0 | if (ee->public_name != NULL) { |
927 | 0 | *public_name = OPENSSL_strdup(ee->public_name); |
928 | 0 | if (*public_name == NULL) |
929 | 0 | goto err; |
930 | 0 | } |
931 | 0 | *has_private = (ee->keyshare == NULL ? 0 : 1); |
932 | 0 | *for_retry = ee->for_retry; |
933 | | /* Now "print" the ECHConfigList */ |
934 | 0 | out = BIO_new(BIO_s_mem()); |
935 | 0 | if (out == NULL) { |
936 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
937 | 0 | goto err; |
938 | 0 | } |
939 | 0 | if (ee->version != OSSL_ECH_RFC9849_VERSION) { |
940 | | /* just note we don't support that one today */ |
941 | 0 | BIO_printf(out, "[Unsupported version (%04x)]", ee->version); |
942 | 0 | } else { |
943 | | /* version, config_id, public_name, and kem */ |
944 | 0 | BIO_printf(out, "[%04x,%02x,%s,[", ee->version, ee->config_id, |
945 | 0 | ee->public_name != NULL ? (char *)ee->public_name : "NULL"); |
946 | | /* ciphersuites */ |
947 | 0 | for (j = 0; j != ee->nsuites; j++) { |
948 | 0 | BIO_printf(out, "%04x,%04x,%04x", ee->suites[j].kem_id, |
949 | 0 | ee->suites[j].kdf_id, ee->suites[j].aead_id); |
950 | 0 | if (j < (ee->nsuites - 1)) |
951 | 0 | BIO_printf(out, ","); |
952 | 0 | } |
953 | 0 | BIO_printf(out, "],"); |
954 | | /* public key */ |
955 | 0 | for (j = 0; j != ee->pub_len; j++) |
956 | 0 | BIO_printf(out, "%02x", ee->pub[j]); |
957 | | /* max name length and (only) number of extensions */ |
958 | 0 | BIO_printf(out, ",%02x,%02x]", ee->max_name_length, |
959 | 0 | ee->exts == NULL ? 0 : sk_OSSL_ECHEXT_num(ee->exts)); |
960 | 0 | } |
961 | 0 | ehlen = BIO_get_mem_data(out, &ignore); |
962 | 0 | if (ehlen > INT_MAX) |
963 | 0 | goto err; |
964 | 0 | *echconfig = OPENSSL_malloc(ehlen + 1); |
965 | 0 | if (*echconfig == NULL) |
966 | 0 | goto err; |
967 | 0 | if (BIO_read(out, *echconfig, (int)ehlen) <= 0) { |
968 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
969 | 0 | goto err; |
970 | 0 | } |
971 | 0 | (*echconfig)[ehlen] = '\0'; |
972 | 0 | BIO_free(out); |
973 | 0 | return 1; |
974 | 0 | err: |
975 | 0 | BIO_free(out); |
976 | 0 | OPENSSL_free(*public_name); |
977 | 0 | *public_name = NULL; |
978 | 0 | OPENSSL_free(*echconfig); |
979 | 0 | *echconfig = NULL; |
980 | 0 | return 0; |
981 | 0 | } |
982 | | |
983 | | int OSSL_ECHSTORE_downselect(OSSL_ECHSTORE *es, int index) |
984 | 0 | { |
985 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
986 | 0 | int i, num = 0, chosen = OSSL_ECHSTORE_ALL; |
987 | |
|
988 | 0 | if (es == NULL) { |
989 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
990 | 0 | return 0; |
991 | 0 | } |
992 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
993 | 0 | if (num == 0) { |
994 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
995 | 0 | return 0; |
996 | 0 | } |
997 | 0 | if (index <= OSSL_ECHSTORE_ALL) { |
998 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
999 | 0 | return 0; |
1000 | 0 | } |
1001 | 0 | if (index == OSSL_ECHSTORE_LAST) { |
1002 | 0 | chosen = num - 1; |
1003 | 0 | } else if (index >= num) { |
1004 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1005 | 0 | return 0; |
1006 | 0 | } else { |
1007 | 0 | chosen = index; |
1008 | 0 | } |
1009 | 0 | for (i = num - 1; i >= 0; i--) { |
1010 | 0 | if (i == chosen) |
1011 | 0 | continue; |
1012 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1013 | 0 | ossl_echstore_entry_free(ee); |
1014 | 0 | sk_OSSL_ECHSTORE_ENTRY_delete(es->entries, i); |
1015 | 0 | } |
1016 | 0 | return 1; |
1017 | 0 | } |
1018 | | |
1019 | | int OSSL_ECHSTORE_set1_key_and_read_pem(OSSL_ECHSTORE *es, EVP_PKEY *priv, |
1020 | | BIO *in, int for_retry) |
1021 | 0 | { |
1022 | 0 | unsigned char *b64 = NULL; |
1023 | 0 | long b64len = 0; |
1024 | 0 | BIO *b64bio = NULL; |
1025 | 0 | int rv = 0; |
1026 | 0 | char *pname = NULL, *pheader = NULL; |
1027 | | |
1028 | | /* we allow for a NULL private key */ |
1029 | 0 | if (es == NULL || in == NULL) { |
1030 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1031 | 0 | return 0; |
1032 | 0 | } |
1033 | 0 | if (PEM_read_bio(in, &pname, &pheader, &b64, &b64len) != 1) { |
1034 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1035 | 0 | return 0; |
1036 | 0 | } |
1037 | 0 | if (pname == NULL || strcmp(pname, PEM_STRING_ECHCONFIG) != 0) { |
1038 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1039 | 0 | goto err; |
1040 | 0 | } |
1041 | 0 | b64bio = BIO_new(BIO_s_mem()); |
1042 | 0 | if (b64bio == NULL |
1043 | 0 | || BIO_write(b64bio, b64, b64len) <= 0 |
1044 | 0 | || ech_read_priv_echconfiglist(es, b64bio, priv, for_retry) != 1) { |
1045 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1046 | 0 | goto err; |
1047 | 0 | } |
1048 | 0 | rv = 1; |
1049 | 0 | err: |
1050 | 0 | OPENSSL_free(pname); |
1051 | 0 | OPENSSL_free(pheader); |
1052 | 0 | BIO_free_all(b64bio); |
1053 | 0 | OPENSSL_free(b64); |
1054 | 0 | return rv; |
1055 | 0 | } |
1056 | | |
1057 | | int OSSL_ECHSTORE_read_pem(OSSL_ECHSTORE *es, BIO *in, int for_retry) |
1058 | 0 | { |
1059 | 0 | EVP_PKEY *priv = NULL; |
1060 | 0 | int rv = 0; |
1061 | 0 | BIO *fbio = BIO_new(BIO_f_buffer()); |
1062 | |
|
1063 | 0 | if (fbio == NULL || es == NULL || in == NULL) { |
1064 | 0 | BIO_free_all(fbio); |
1065 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1066 | 0 | return 0; |
1067 | 0 | } |
1068 | | /* |
1069 | | * Read private key then handoff to set1_key_and_read_pem. |
1070 | | * We allow for no private key as an option, to handle that |
1071 | | * the BIO_f_buffer allows us to seek back to the start. |
1072 | | */ |
1073 | 0 | BIO_push(fbio, in); |
1074 | 0 | if (!PEM_read_bio_PrivateKey(fbio, &priv, NULL, NULL) |
1075 | 0 | && BIO_seek(fbio, 0) < 0) { |
1076 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1077 | 0 | goto err; |
1078 | 0 | } |
1079 | 0 | rv = OSSL_ECHSTORE_set1_key_and_read_pem(es, priv, fbio, for_retry); |
1080 | 0 | err: |
1081 | 0 | EVP_PKEY_free(priv); |
1082 | 0 | BIO_pop(fbio); |
1083 | 0 | BIO_free_all(fbio); |
1084 | 0 | return rv; |
1085 | 0 | } |
1086 | | |
1087 | | int OSSL_ECHSTORE_num_entries(const OSSL_ECHSTORE *es, int *numentries) |
1088 | 0 | { |
1089 | 0 | if (es == NULL || numentries == NULL) { |
1090 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1091 | 0 | return 0; |
1092 | 0 | } |
1093 | 0 | *numentries = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1094 | 0 | return 1; |
1095 | 0 | } |
1096 | | |
1097 | | int OSSL_ECHSTORE_num_keys(OSSL_ECHSTORE *es, int *numkeys) |
1098 | 0 | { |
1099 | 0 | int i, num = 0, count = 0; |
1100 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
1101 | |
|
1102 | 0 | if (es == NULL || numkeys == NULL) { |
1103 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1104 | 0 | return 0; |
1105 | 0 | } |
1106 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1107 | 0 | for (i = 0; i != num; i++) { |
1108 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1109 | 0 | if (ee == NULL) { |
1110 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1111 | 0 | return 0; |
1112 | 0 | } |
1113 | 0 | count += (ee->keyshare != NULL); |
1114 | 0 | } |
1115 | 0 | *numkeys = count; |
1116 | 0 | return 1; |
1117 | 0 | } |
1118 | | |
1119 | | int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age) |
1120 | 0 | { |
1121 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
1122 | 0 | int i, num = 0; |
1123 | 0 | time_t now = time(0); |
1124 | |
|
1125 | 0 | if (es == NULL) { |
1126 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1127 | 0 | return 0; |
1128 | 0 | } |
1129 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1130 | 0 | if (num == 0) { |
1131 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1132 | 0 | return 0; |
1133 | 0 | } |
1134 | 0 | for (i = num - 1; i >= 0; i--) { |
1135 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1136 | 0 | if (ee == NULL) { |
1137 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1138 | 0 | return 0; |
1139 | 0 | } |
1140 | 0 | if (ee->keyshare != NULL && ee->loadtime + age <= now) { |
1141 | 0 | ossl_echstore_entry_free(ee); |
1142 | 0 | sk_OSSL_ECHSTORE_ENTRY_delete(es->entries, i); |
1143 | 0 | } |
1144 | 0 | } |
1145 | 0 | return 1; |
1146 | 0 | } |