/src/openssl/ssl/ech/ech_store.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2024-2026 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 (BIO_eof(in) || 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, rv = 0, goodsuitefound = 0; |
253 | 0 | X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new(); |
254 | 0 | char *lastlabel = NULL; |
255 | 0 | size_t lllen; |
256 | | |
257 | | /* check local support for some suite */ |
258 | 0 | for (ind = 0; ind != (int)ee->nsuites; ind++) { |
259 | | /* |
260 | | * suite_check says yes to the pseudo-aead for export, but we don't |
261 | | * want to see it here coming from outside in an encoding |
262 | | */ |
263 | 0 | hpke_suite = ee->suites[ind]; |
264 | 0 | if (OSSL_HPKE_suite_check(hpke_suite) == 1 |
265 | 0 | && hpke_suite.aead_id != OSSL_HPKE_AEAD_ID_EXPORTONLY) { |
266 | 0 | goodsuitefound = 1; |
267 | 0 | break; |
268 | 0 | } |
269 | 0 | } |
270 | 0 | if (goodsuitefound == 0) { |
271 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
272 | 0 | goto err; |
273 | 0 | } |
274 | | /* check no mandatory exts (with high bit set in type) */ |
275 | 0 | num = (ee->exts == NULL ? 0 : sk_OSSL_ECHEXT_num(ee->exts)); |
276 | 0 | for (ind = 0; ind != num; ind++) { |
277 | 0 | OSSL_ECHEXT *oe = sk_OSSL_ECHEXT_value(ee->exts, (int)ind); |
278 | |
|
279 | 0 | if (oe->type & 0x8000) { |
280 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
281 | 0 | goto err; |
282 | 0 | } |
283 | 0 | } |
284 | | /* check public_name rules, as per spec section 6.1.7 */ |
285 | 0 | if (ee->public_name == NULL |
286 | 0 | || ee->public_name[0] == '\0' |
287 | 0 | || ee->public_name[0] == '.' |
288 | 0 | || ee->public_name[strlen(ee->public_name) - 1] == '.' |
289 | 0 | || strlen(ee->public_name) > 255) |
290 | 0 | goto err; |
291 | | /* |
292 | | * Use X509_VERIFY_PARAM_add1_host to avoid coding same checks twice. |
293 | | * This checks max 63 octets per label, overall length and some other |
294 | | * DNS label checks. |
295 | | */ |
296 | 0 | if (X509_VERIFY_PARAM_add1_host(vpm, ee->public_name, 0) == 0) |
297 | 0 | goto err; |
298 | | /* |
299 | | * but we still have to check the last label restrictions, which |
300 | | * are intended to avoid confusion with IP address literals in |
301 | | * encodings browsers support, as per WHATWG (convincing, eh:-) |
302 | | */ |
303 | 0 | lastlabel = strrchr(ee->public_name, '.'); |
304 | 0 | if (lastlabel == NULL) /* if there are no dots */ |
305 | 0 | lastlabel = ee->public_name; |
306 | 0 | lllen = strlen(lastlabel); |
307 | 0 | if (lllen < 2) |
308 | 0 | goto err; |
309 | 0 | if (lastlabel[0] == '.') { |
310 | 0 | lastlabel++; |
311 | 0 | lllen--; |
312 | 0 | } |
313 | 0 | if (strspn(lastlabel, "0123456789") == lllen) |
314 | 0 | goto err; |
315 | 0 | if (lastlabel[0] == '0' && lllen > 2 |
316 | 0 | && (lastlabel[1] == 'x' || lastlabel[1] == 'X') |
317 | 0 | && strspn(lastlabel + 2, "0123456789abcdefABCDEF") == (lllen - 2)) |
318 | 0 | goto err; |
319 | 0 | rv = 1; |
320 | 0 | err: |
321 | 0 | X509_VERIFY_PARAM_free(vpm); |
322 | 0 | return rv; |
323 | 0 | } |
324 | | |
325 | | /** |
326 | | * @brief decode one ECHConfig from a packet into an entry |
327 | | * @param rent ptr to an entry allocated within (on success) |
328 | | * @param pkt is the encoding |
329 | | * @param priv is an optional private key (NULL if absent) |
330 | | * @param for_retry says whether to include in a retry_config (if priv present) |
331 | | * @return 1 for success, 0 for error |
332 | | */ |
333 | | static int ech_decode_one_entry(OSSL_ECHSTORE_ENTRY **rent, PACKET *pkt, |
334 | | EVP_PKEY *priv, int for_retry) |
335 | 0 | { |
336 | 0 | size_t ech_content_length = 0; |
337 | 0 | unsigned int tmpi; |
338 | 0 | const unsigned char *tmpecp = NULL; |
339 | 0 | size_t tmpeclen = 0, test_publen = 0; |
340 | 0 | PACKET ver_pkt, pub_pkt, cipher_suites, public_name_pkt, exts; |
341 | 0 | uint16_t thiskemid; |
342 | 0 | size_t suiteoctets = 0; |
343 | 0 | unsigned int ci = 0; |
344 | 0 | unsigned char cipher[OSSL_ECH_CIPHER_LEN], max_name_len; |
345 | 0 | unsigned char test_pub[OSSL_ECH_CRYPTO_VAR_SIZE]; |
346 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
347 | |
|
348 | 0 | if (rent == NULL) { |
349 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
350 | 0 | return 0; |
351 | 0 | } |
352 | 0 | if (pkt == NULL) { |
353 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
354 | 0 | goto err; |
355 | 0 | } |
356 | 0 | ee = OPENSSL_zalloc(sizeof(*ee)); |
357 | 0 | if (ee == NULL) |
358 | 0 | goto err; |
359 | | /* note start of encoding so we can make a copy later */ |
360 | 0 | tmpeclen = PACKET_remaining(pkt); |
361 | 0 | if (PACKET_peek_bytes(pkt, &tmpecp, tmpeclen) != 1 |
362 | 0 | || !PACKET_get_net_2(pkt, &tmpi)) { |
363 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
364 | 0 | goto err; |
365 | 0 | } |
366 | 0 | ee->version = (uint16_t)tmpi; |
367 | | |
368 | | /* grab versioned packet data */ |
369 | 0 | if (!PACKET_get_length_prefixed_2(pkt, &ver_pkt)) { |
370 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
371 | 0 | goto err; |
372 | 0 | } |
373 | 0 | ech_content_length = (unsigned int)PACKET_remaining(&ver_pkt); |
374 | 0 | switch (ee->version) { |
375 | 0 | case OSSL_ECH_RFC9849_VERSION: |
376 | 0 | break; |
377 | 0 | default: |
378 | | /* skip over in case we get something we can handle later */ |
379 | 0 | if (!PACKET_forward(&ver_pkt, ech_content_length)) { |
380 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
381 | 0 | goto err; |
382 | 0 | } |
383 | | /* nothing to return but not a fail */ |
384 | 0 | ossl_echstore_entry_free(ee); |
385 | 0 | *rent = NULL; |
386 | 0 | return 1; |
387 | 0 | } |
388 | 0 | if (!PACKET_copy_bytes(&ver_pkt, &ee->config_id, 1) |
389 | 0 | || !PACKET_get_net_2(&ver_pkt, &tmpi) |
390 | 0 | || !PACKET_get_length_prefixed_2(&ver_pkt, &pub_pkt) |
391 | 0 | || !PACKET_memdup(&pub_pkt, &ee->pub, &ee->pub_len) |
392 | 0 | || !PACKET_get_length_prefixed_2(&ver_pkt, &cipher_suites) |
393 | 0 | || (suiteoctets = PACKET_remaining(&cipher_suites)) <= 0 |
394 | 0 | || (suiteoctets % 2) == 1 |
395 | 0 | || suiteoctets / OSSL_ECH_CIPHER_LEN > UINT_MAX) { |
396 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
397 | 0 | goto err; |
398 | 0 | } |
399 | 0 | thiskemid = (uint16_t)tmpi; |
400 | 0 | ee->nsuites = (unsigned int)(suiteoctets / OSSL_ECH_CIPHER_LEN); |
401 | 0 | ee->suites = OPENSSL_malloc_array(ee->nsuites, sizeof(*ee->suites)); |
402 | 0 | if (ee->suites == NULL) |
403 | 0 | goto err; |
404 | 0 | while (PACKET_copy_bytes(&cipher_suites, cipher, |
405 | 0 | OSSL_ECH_CIPHER_LEN)) { |
406 | 0 | ee->suites[ci].kem_id = thiskemid; |
407 | 0 | ee->suites[ci].kdf_id = cipher[0] << 8 | cipher[1]; |
408 | 0 | ee->suites[ci].aead_id = cipher[2] << 8 | cipher[3]; |
409 | 0 | if (ci++ >= ee->nsuites) { |
410 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
411 | 0 | goto err; |
412 | 0 | } |
413 | 0 | } |
414 | 0 | if (PACKET_remaining(&cipher_suites) > 0 |
415 | 0 | || !PACKET_copy_bytes(&ver_pkt, &max_name_len, 1)) { |
416 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
417 | 0 | goto err; |
418 | 0 | } |
419 | 0 | ee->max_name_length = max_name_len; |
420 | 0 | if (!PACKET_get_length_prefixed_1(&ver_pkt, &public_name_pkt)) { |
421 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
422 | 0 | goto err; |
423 | 0 | } |
424 | 0 | if (PACKET_contains_zero_byte(&public_name_pkt) |
425 | 0 | || PACKET_remaining(&public_name_pkt) < TLSEXT_MINLEN_host_name |
426 | 0 | || !PACKET_strndup(&public_name_pkt, &ee->public_name)) { |
427 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
428 | 0 | goto err; |
429 | 0 | } |
430 | | /* |
431 | | * We don't really handle ECHConfig extensions as of now, |
432 | | * (none are well-defined), so we're only skipping over |
433 | | * whatever we find here. If/when adding real extensions |
434 | | * then it may be necessary to also check that the set of |
435 | | * extensions loaded contain no duplicate types. |
436 | | */ |
437 | 0 | if (!PACKET_get_length_prefixed_2(&ver_pkt, &exts)) { |
438 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
439 | 0 | goto err; |
440 | 0 | } |
441 | 0 | if (PACKET_remaining(&exts) > 0 |
442 | 0 | && ech_decode_echconfig_exts(ee, &exts) != 1) { |
443 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
444 | 0 | goto err; |
445 | 0 | } |
446 | | /* set length of encoding of this ECHConfig */ |
447 | 0 | ee->encoded_len = PACKET_data(&ver_pkt) - tmpecp; |
448 | | /* copy encoded as it might get free'd if a reduce happens */ |
449 | 0 | ee->encoded = OPENSSL_memdup(tmpecp, ee->encoded_len); |
450 | 0 | if (ee->encoded == NULL) |
451 | 0 | goto err; |
452 | 0 | if (priv != NULL) { |
453 | 0 | if (EVP_PKEY_get_octet_string_param(priv, |
454 | 0 | OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, |
455 | 0 | test_pub, OSSL_ECH_CRYPTO_VAR_SIZE, |
456 | 0 | &test_publen) |
457 | 0 | != 1) { |
458 | 0 | ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR); |
459 | 0 | goto err; |
460 | 0 | } |
461 | 0 | if (test_publen == ee->pub_len |
462 | 0 | && !memcmp(test_pub, ee->pub, ee->pub_len)) { |
463 | 0 | EVP_PKEY_up_ref(priv); /* associate the private key */ |
464 | 0 | ee->keyshare = priv; |
465 | 0 | ee->for_retry = for_retry; |
466 | 0 | } |
467 | 0 | } |
468 | 0 | ee->loadtime = time(0); |
469 | 0 | *rent = ee; |
470 | 0 | return 1; |
471 | 0 | err: |
472 | 0 | ossl_echstore_entry_free(ee); |
473 | 0 | *rent = NULL; |
474 | 0 | return 0; |
475 | 0 | } |
476 | | |
477 | | /* |
478 | | * @brief decode and flatten a binary encoded ECHConfigList |
479 | | * @param es an OSSL_ECHSTORE |
480 | | * @param priv is an optional private key (NULL if absent) |
481 | | * @param for_retry says whether to include in a retry_config (if priv present) |
482 | | * @param binbuf binary encoded ECHConfigList (we hope) |
483 | | * @param binlen length of binbuf |
484 | | * @return 1 for success, 0 for error |
485 | | * |
486 | | * We may only get one ECHConfig per list, but there can be more. We want each |
487 | | * element of the output to contain exactly one ECHConfig so that a client |
488 | | * could sensibly down select to the one they prefer later, and so that we have |
489 | | * the specific encoded value of that ECHConfig for inclusion in the HPKE info |
490 | | * parameter when finally encrypting or decrypting an inner ClientHello. |
491 | | * |
492 | | * If a private value is provided then that'll only be associated with the |
493 | | * relevant public value, if >1 public value was present in the ECHConfigList. |
494 | | */ |
495 | | static int ech_decode_and_flatten(OSSL_ECHSTORE *es, EVP_PKEY *priv, int for_retry, |
496 | | unsigned char *binbuf, size_t binblen) |
497 | 0 | { |
498 | 0 | int rv = 0; |
499 | 0 | size_t remaining = 0; |
500 | 0 | PACKET opkt, pkt; |
501 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
502 | |
|
503 | 0 | if (binbuf == NULL || binblen == 0 || binblen < OSSL_ECH_MIN_ECHCONFIG_LEN |
504 | 0 | || binblen >= OSSL_ECH_MAX_ECHCONFIG_LEN) { |
505 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
506 | 0 | goto err; |
507 | 0 | } |
508 | 0 | if (PACKET_buf_init(&opkt, binbuf, binblen) != 1 |
509 | 0 | || !PACKET_get_length_prefixed_2(&opkt, &pkt)) { |
510 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
511 | 0 | goto err; |
512 | 0 | } |
513 | 0 | remaining = PACKET_remaining(&pkt); |
514 | 0 | while (remaining > 0) { |
515 | 0 | if (ech_decode_one_entry(&ee, &pkt, priv, for_retry) != 1) { |
516 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
517 | 0 | goto err; |
518 | 0 | } |
519 | 0 | remaining = PACKET_remaining(&pkt); |
520 | | /* if unsupported version we can skip over */ |
521 | 0 | if (ee == NULL) |
522 | 0 | continue; |
523 | | /* do final checks on suites, exts, and fail if issues */ |
524 | 0 | if (ech_final_config_checks(ee) != 1) |
525 | 0 | goto err; |
526 | | /* push entry into store */ |
527 | 0 | if (es->entries == NULL) |
528 | 0 | es->entries = sk_OSSL_ECHSTORE_ENTRY_new_null(); |
529 | 0 | if (es->entries == NULL) { |
530 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
531 | 0 | goto err; |
532 | 0 | } |
533 | 0 | if (!sk_OSSL_ECHSTORE_ENTRY_push(es->entries, ee)) { |
534 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
535 | 0 | goto err; |
536 | 0 | } |
537 | 0 | ee = NULL; |
538 | 0 | } |
539 | 0 | rv = 1; |
540 | 0 | err: |
541 | 0 | ossl_echstore_entry_free(ee); |
542 | 0 | return rv; |
543 | 0 | } |
544 | | |
545 | | /* |
546 | | * @brief check a private matches some public |
547 | | * @param es is the ECH store |
548 | | * @param priv is the private value |
549 | | * @return 1 if we have a match, zero otherwise |
550 | | */ |
551 | | static int check_priv_matches(OSSL_ECHSTORE *es, EVP_PKEY *priv) |
552 | 0 | { |
553 | 0 | int num, ent, gotone = 0; |
554 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
555 | |
|
556 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
557 | 0 | for (ent = 0; ent != num; ent++) { |
558 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, ent); |
559 | 0 | if (ee == NULL) { |
560 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
561 | 0 | return 0; |
562 | 0 | } |
563 | 0 | if (EVP_PKEY_eq(ee->keyshare, priv)) { |
564 | 0 | gotone = 1; |
565 | 0 | break; |
566 | 0 | } |
567 | 0 | } |
568 | 0 | return gotone; |
569 | 0 | } |
570 | | |
571 | | /* |
572 | | * @brief decode input ECHConfigList and associate optional private info |
573 | | * @param es is the OSSL_ECHSTORE |
574 | | * @param in is the BIO from which we'll get the ECHConfigList |
575 | | * @param priv is an optional private key |
576 | | * @param for_retry 1 if the public related to priv ought be in retry_config |
577 | | */ |
578 | | static int ech_read_priv_echconfiglist(OSSL_ECHSTORE *es, BIO *in, |
579 | | EVP_PKEY *priv, int for_retry) |
580 | 0 | { |
581 | 0 | int rv = 0, detfmt, tdeclen = 0; |
582 | 0 | size_t encodedlen = 0, binlen = 0; |
583 | 0 | unsigned char *encodedval = NULL, *binbuf = NULL; |
584 | 0 | BIO *btmp = NULL, *btmp1 = NULL; |
585 | |
|
586 | 0 | if (es == NULL || in == NULL) { |
587 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
588 | 0 | return 0; |
589 | 0 | } |
590 | 0 | if (ech_bio2buf(in, &encodedval, &encodedlen) != 1) { |
591 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
592 | 0 | return 0; |
593 | 0 | } |
594 | 0 | if (encodedlen >= OSSL_ECH_MAX_ECHCONFIG_LEN) { /* sanity check */ |
595 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
596 | 0 | goto err; |
597 | 0 | } |
598 | 0 | if (ech_check_format(encodedval, encodedlen, &detfmt) != 1) { |
599 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
600 | 0 | goto err; |
601 | 0 | } |
602 | 0 | if (detfmt == OSSL_ECH_FMT_BIN) { /* copy buffer if binary format */ |
603 | 0 | binbuf = OPENSSL_memdup(encodedval, encodedlen); |
604 | 0 | if (binbuf == NULL) |
605 | 0 | goto err; |
606 | 0 | binlen = encodedlen; |
607 | 0 | } |
608 | 0 | if (detfmt == OSSL_ECH_FMT_B64TXT) { |
609 | 0 | btmp = BIO_new_mem_buf(encodedval, (int)encodedlen); |
610 | 0 | if (btmp == NULL) { |
611 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
612 | 0 | goto err; |
613 | 0 | } |
614 | 0 | btmp1 = BIO_new(BIO_f_base64()); |
615 | 0 | if (btmp1 == NULL) { |
616 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
617 | 0 | goto err; |
618 | 0 | } |
619 | 0 | BIO_set_flags(btmp1, BIO_FLAGS_BASE64_NO_NL); |
620 | 0 | btmp = BIO_push(btmp1, btmp); |
621 | | /* overestimate but good enough */ |
622 | 0 | binbuf = OPENSSL_malloc(encodedlen); |
623 | 0 | if (binbuf == NULL) |
624 | 0 | goto err; |
625 | 0 | tdeclen = BIO_read(btmp, binbuf, (int)encodedlen); |
626 | 0 | if (tdeclen <= 0) { /* need int for -1 return in failure case */ |
627 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
628 | 0 | goto err; |
629 | 0 | } |
630 | 0 | binlen = tdeclen; |
631 | 0 | } |
632 | 0 | if (ech_decode_and_flatten(es, priv, for_retry, binbuf, binlen) != 1) { |
633 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
634 | 0 | goto err; |
635 | 0 | } |
636 | 0 | if (priv != NULL && check_priv_matches(es, priv) == 0) |
637 | 0 | goto err; |
638 | 0 | rv = 1; |
639 | 0 | err: |
640 | 0 | BIO_free_all(btmp); |
641 | 0 | OPENSSL_free(binbuf); |
642 | 0 | OPENSSL_free(encodedval); |
643 | 0 | return rv; |
644 | 0 | } |
645 | | |
646 | | /* |
647 | | * API calls built around OSSL_ECHSSTORE |
648 | | */ |
649 | | |
650 | | OSSL_ECHSTORE *OSSL_ECHSTORE_new(OSSL_LIB_CTX *libctx, const char *propq) |
651 | 0 | { |
652 | 0 | OSSL_ECHSTORE *es = NULL; |
653 | |
|
654 | 0 | es = OPENSSL_zalloc(sizeof(*es)); |
655 | 0 | if (es == NULL) |
656 | 0 | return 0; |
657 | 0 | es->libctx = libctx; |
658 | 0 | if (propq != NULL) { |
659 | 0 | es->propq = OPENSSL_strdup(propq); |
660 | 0 | if (es->propq == NULL) { |
661 | 0 | OPENSSL_free(es); |
662 | 0 | return 0; |
663 | 0 | } |
664 | 0 | } |
665 | | |
666 | 0 | return es; |
667 | 0 | } |
668 | | |
669 | | void OSSL_ECHSTORE_free(OSSL_ECHSTORE *es) |
670 | 0 | { |
671 | 0 | if (es == NULL) |
672 | 0 | return; |
673 | 0 | sk_OSSL_ECHSTORE_ENTRY_pop_free(es->entries, ossl_echstore_entry_free); |
674 | 0 | OPENSSL_free(es->propq); |
675 | 0 | OPENSSL_free(es); |
676 | 0 | return; |
677 | 0 | } |
678 | | |
679 | | int OSSL_ECHSTORE_new_config(OSSL_ECHSTORE *es, |
680 | | uint16_t echversion, uint8_t max_name_length, |
681 | | const char *public_name, OSSL_HPKE_SUITE suite) |
682 | 0 | { |
683 | 0 | size_t pnlen = 0, publen = OSSL_ECH_CRYPTO_VAR_SIZE; |
684 | 0 | unsigned char pub[OSSL_ECH_CRYPTO_VAR_SIZE]; |
685 | 0 | int rv = 0; |
686 | 0 | unsigned char *bp = NULL; |
687 | 0 | size_t bblen = 0; |
688 | 0 | EVP_PKEY *privp = NULL; |
689 | 0 | uint8_t config_id = 0; |
690 | 0 | WPACKET epkt; |
691 | 0 | BUF_MEM *epkt_mem = NULL; |
692 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
693 | | |
694 | | /* basic checks */ |
695 | 0 | if (es == NULL) { |
696 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
697 | 0 | return 0; |
698 | 0 | } |
699 | 0 | pnlen = (public_name == NULL ? 0 : strlen(public_name)); |
700 | 0 | if (pnlen == 0 || pnlen > OSSL_ECH_MAX_PUBLICNAME) { |
701 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
702 | 0 | return 0; |
703 | 0 | } |
704 | | /* this used have more versions and will again in future */ |
705 | 0 | switch (echversion) { |
706 | 0 | case OSSL_ECH_RFC9849_VERSION: |
707 | 0 | break; |
708 | 0 | default: |
709 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
710 | 0 | return 0; |
711 | 0 | } |
712 | | /* |
713 | | * Reminder, for draft-13 we want this: |
714 | | * |
715 | | * opaque HpkePublicKey<1..2^16-1>; |
716 | | * uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke |
717 | | * uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke |
718 | | * uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke |
719 | | * struct { |
720 | | * HpkeKdfId kdf_id; |
721 | | * HpkeAeadId aead_id; |
722 | | * } HpkeSymmetricCipherSuite; |
723 | | * struct { |
724 | | * uint8 config_id; |
725 | | * HpkeKemId kem_id; |
726 | | * HpkePublicKey public_key; |
727 | | * HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>; |
728 | | * } HpkeKeyConfig; |
729 | | * struct { |
730 | | * HpkeKeyConfig key_config; |
731 | | * uint8 maximum_name_length; |
732 | | * opaque public_name<1..255>; |
733 | | * Extension extensions<0..2^16-1>; |
734 | | * } ECHConfigContents; |
735 | | * struct { |
736 | | * uint16 version; |
737 | | * uint16 length; |
738 | | * select (ECHConfig.version) { |
739 | | * case 0xfe0d: ECHConfigContents contents; |
740 | | * } |
741 | | * } ECHConfig; |
742 | | * ECHConfig ECHConfigList<1..2^16-1>; |
743 | | */ |
744 | 0 | if ((epkt_mem = BUF_MEM_new()) == NULL |
745 | 0 | || !BUF_MEM_grow(epkt_mem, OSSL_ECH_MAX_ECHCONFIG_LEN) |
746 | 0 | || !WPACKET_init(&epkt, epkt_mem)) { |
747 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
748 | 0 | goto err_no_epkt; |
749 | 0 | } |
750 | | /* random config_id */ |
751 | 0 | if (RAND_bytes_ex(es->libctx, (unsigned char *)&config_id, 1, 0) <= 0) { |
752 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
753 | 0 | goto err; |
754 | 0 | } |
755 | | /* key pair */ |
756 | 0 | if (OSSL_HPKE_keygen(suite, pub, &publen, &privp, NULL, 0, |
757 | 0 | es->libctx, es->propq) |
758 | 0 | != 1) { |
759 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
760 | 0 | goto err; |
761 | 0 | } |
762 | | /* config id, KEM, public, KDF, AEAD, max name len, public_name, exts */ |
763 | 0 | if ((bp = WPACKET_get_curr(&epkt)) == NULL |
764 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
765 | 0 | || !WPACKET_put_bytes_u16(&epkt, echversion) |
766 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
767 | 0 | || !WPACKET_put_bytes_u8(&epkt, config_id) |
768 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.kem_id) |
769 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
770 | 0 | || !WPACKET_memcpy(&epkt, pub, publen) |
771 | 0 | || !WPACKET_close(&epkt) |
772 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
773 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.kdf_id) |
774 | 0 | || !WPACKET_put_bytes_u16(&epkt, suite.aead_id) |
775 | 0 | || !WPACKET_close(&epkt) |
776 | 0 | || !WPACKET_put_bytes_u8(&epkt, max_name_length) |
777 | 0 | || !WPACKET_start_sub_packet_u8(&epkt) |
778 | 0 | || !WPACKET_memcpy(&epkt, public_name, pnlen) |
779 | 0 | || !WPACKET_close(&epkt) |
780 | 0 | || !WPACKET_start_sub_packet_u16(&epkt) |
781 | 0 | || !WPACKET_memcpy(&epkt, NULL, 0) /* no extensions */ |
782 | 0 | || !WPACKET_close(&epkt) |
783 | 0 | || !WPACKET_close(&epkt) |
784 | 0 | || !WPACKET_close(&epkt)) { |
785 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
786 | 0 | goto err; |
787 | 0 | } |
788 | | /* bp, bblen has encoding */ |
789 | 0 | if (!WPACKET_get_total_written(&epkt, &bblen)) { |
790 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
791 | 0 | goto err; |
792 | 0 | } |
793 | 0 | if ((ee = OPENSSL_zalloc(sizeof(*ee))) == NULL) |
794 | 0 | goto err; |
795 | 0 | ee->suites = OPENSSL_malloc(sizeof(*ee->suites)); |
796 | 0 | if (ee->suites == NULL) |
797 | 0 | goto err; |
798 | 0 | ee->version = echversion; |
799 | 0 | ee->pub_len = publen; |
800 | 0 | ee->pub = OPENSSL_memdup(pub, publen); |
801 | 0 | if (ee->pub == NULL) |
802 | 0 | goto err; |
803 | 0 | ee->nsuites = 1; |
804 | 0 | ee->suites[0] = suite; |
805 | 0 | ee->public_name = OPENSSL_strdup(public_name); |
806 | 0 | if (ee->public_name == NULL) |
807 | 0 | goto err; |
808 | 0 | ee->max_name_length = max_name_length; |
809 | 0 | ee->config_id = config_id; |
810 | 0 | ee->keyshare = privp; |
811 | 0 | privp = NULL; /* don't free twice */ |
812 | | /* "steal" the encoding from the memory */ |
813 | 0 | ee->encoded = (unsigned char *)epkt_mem->data; |
814 | 0 | ee->encoded_len = bblen; |
815 | 0 | epkt_mem->data = NULL; |
816 | 0 | epkt_mem->length = 0; |
817 | 0 | ee->loadtime = time(0); |
818 | 0 | if (ech_final_config_checks(ee) != 1) /* check our work */ |
819 | 0 | goto err; |
820 | | /* push entry into store */ |
821 | 0 | if (es->entries == NULL) |
822 | 0 | es->entries = sk_OSSL_ECHSTORE_ENTRY_new_null(); |
823 | 0 | if (es->entries == NULL) { |
824 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
825 | 0 | goto err; |
826 | 0 | } |
827 | 0 | if (!sk_OSSL_ECHSTORE_ENTRY_push(es->entries, ee)) { |
828 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
829 | 0 | goto err; |
830 | 0 | } |
831 | 0 | WPACKET_finish(&epkt); |
832 | 0 | BUF_MEM_free(epkt_mem); |
833 | 0 | return 1; |
834 | | |
835 | 0 | err: |
836 | 0 | ossl_echstore_entry_free(ee); |
837 | 0 | EVP_PKEY_free(privp); |
838 | 0 | WPACKET_cleanup(&epkt); |
839 | 0 | err_no_epkt: |
840 | 0 | BUF_MEM_free(epkt_mem); |
841 | 0 | return rv; |
842 | 0 | } |
843 | | |
844 | | int OSSL_ECHSTORE_write_pem(OSSL_ECHSTORE *es, int index, BIO *out) |
845 | 0 | { |
846 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
847 | 0 | int rv = 0, num = 0, chosen = 0, doall = 0; |
848 | 0 | WPACKET epkt; /* used if we want to merge ECHConfigs for output */ |
849 | 0 | BUF_MEM *epkt_mem = NULL; |
850 | 0 | size_t allencoded_len; |
851 | |
|
852 | 0 | if (es == NULL) { |
853 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
854 | 0 | return 0; |
855 | 0 | } |
856 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
857 | 0 | if (num <= 0) { |
858 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
859 | 0 | return 0; |
860 | 0 | } |
861 | 0 | if (index >= num) { |
862 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
863 | 0 | return 0; |
864 | 0 | } |
865 | 0 | if (index == OSSL_ECHSTORE_ALL) |
866 | 0 | doall = 1; |
867 | 0 | else if (index == OSSL_ECHSTORE_LAST) |
868 | 0 | chosen = num - 1; |
869 | 0 | else |
870 | 0 | chosen = index; |
871 | 0 | memset(&epkt, 0, sizeof(epkt)); |
872 | 0 | if (doall == 0) { |
873 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, chosen); |
874 | 0 | if (ee == NULL || ee->encoded == NULL) { |
875 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
876 | 0 | return 0; |
877 | 0 | } |
878 | | /* private key first */ |
879 | 0 | if (ee->keyshare != NULL |
880 | 0 | && !PEM_write_bio_PrivateKey(out, ee->keyshare, NULL, NULL, 0, |
881 | 0 | NULL, NULL)) { |
882 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
883 | 0 | goto err; |
884 | 0 | } |
885 | 0 | if (PEM_write_bio(out, PEM_STRING_ECHCONFIG, NULL, |
886 | 0 | ee->encoded, (long)ee->encoded_len) |
887 | 0 | <= 0) { |
888 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
889 | 0 | goto err; |
890 | 0 | } |
891 | 0 | } else { |
892 | | /* catenate the encodings into one */ |
893 | 0 | if ((epkt_mem = BUF_MEM_new()) == NULL |
894 | 0 | || !BUF_MEM_grow(epkt_mem, OSSL_ECH_MAX_ECHCONFIG_LEN) |
895 | 0 | || !WPACKET_init(&epkt, epkt_mem) |
896 | 0 | || !WPACKET_start_sub_packet_u16(&epkt)) { |
897 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
898 | 0 | goto err; |
899 | 0 | } |
900 | 0 | for (chosen = 0; chosen != num; chosen++) { |
901 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, chosen); |
902 | 0 | if (ee == NULL || ee->encoded == NULL) { |
903 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
904 | 0 | return 0; |
905 | 0 | } |
906 | 0 | if (!WPACKET_memcpy(&epkt, ee->encoded, ee->encoded_len)) { |
907 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
908 | 0 | goto err; |
909 | 0 | } |
910 | 0 | } |
911 | 0 | if (!WPACKET_close(&epkt)) { |
912 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
913 | 0 | goto err; |
914 | 0 | } |
915 | 0 | if (!WPACKET_get_total_written(&epkt, &allencoded_len)) { |
916 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
917 | 0 | goto err; |
918 | 0 | } |
919 | 0 | if (PEM_write_bio(out, PEM_STRING_ECHCONFIG, NULL, |
920 | 0 | (unsigned char *)epkt_mem->data, |
921 | 0 | (long)allencoded_len) |
922 | 0 | <= 0) { |
923 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
924 | 0 | goto err; |
925 | 0 | } |
926 | 0 | } |
927 | 0 | rv = 1; |
928 | 0 | err: |
929 | 0 | WPACKET_cleanup(&epkt); |
930 | 0 | BUF_MEM_free(epkt_mem); |
931 | 0 | return rv; |
932 | 0 | } |
933 | | |
934 | | int OSSL_ECHSTORE_read_echconfiglist(OSSL_ECHSTORE *es, BIO *in) |
935 | 0 | { |
936 | 0 | return ech_read_priv_echconfiglist(es, in, NULL, 0); |
937 | 0 | } |
938 | | |
939 | | int OSSL_ECHSTORE_get1_info(OSSL_ECHSTORE *es, int index, time_t *loaded_secs, |
940 | | char **public_name, char **echconfig, |
941 | | int *has_private, int *for_retry) |
942 | 0 | { |
943 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
944 | 0 | unsigned int j = 0; |
945 | 0 | int num = 0; |
946 | 0 | BIO *out = NULL; |
947 | 0 | time_t now = time(0); |
948 | 0 | size_t ehlen; |
949 | 0 | unsigned char *ignore = NULL; |
950 | |
|
951 | 0 | if (es == NULL || loaded_secs == NULL || public_name == NULL |
952 | 0 | || echconfig == NULL || has_private == NULL || for_retry == NULL) { |
953 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
954 | 0 | return 0; |
955 | 0 | } |
956 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
957 | 0 | if (num == 0 || index < 0 || index >= num) { |
958 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
959 | 0 | return 0; |
960 | 0 | } |
961 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, index); |
962 | 0 | if (ee == NULL) { |
963 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
964 | 0 | return 0; |
965 | 0 | } |
966 | 0 | *loaded_secs = now - ee->loadtime; |
967 | 0 | *public_name = NULL; |
968 | 0 | *echconfig = NULL; |
969 | 0 | if (ee->public_name != NULL) { |
970 | 0 | *public_name = OPENSSL_strdup(ee->public_name); |
971 | 0 | if (*public_name == NULL) |
972 | 0 | goto err; |
973 | 0 | } |
974 | 0 | *has_private = (ee->keyshare == NULL ? 0 : 1); |
975 | 0 | *for_retry = ee->for_retry; |
976 | | /* Now "print" the ECHConfigList */ |
977 | 0 | out = BIO_new(BIO_s_mem()); |
978 | 0 | if (out == NULL) { |
979 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
980 | 0 | goto err; |
981 | 0 | } |
982 | 0 | if (ee->version != OSSL_ECH_RFC9849_VERSION) { |
983 | | /* just note we don't support that one today */ |
984 | 0 | BIO_printf(out, "[Unsupported version (%04x)]", ee->version); |
985 | 0 | } else { |
986 | | /* version, config_id, public_name, and kem */ |
987 | 0 | BIO_printf(out, "[%04x,%02x,%s,[", ee->version, ee->config_id, |
988 | 0 | ee->public_name != NULL ? (char *)ee->public_name : "NULL"); |
989 | | /* ciphersuites */ |
990 | 0 | for (j = 0; j != ee->nsuites; j++) { |
991 | 0 | BIO_printf(out, "%04x,%04x,%04x", ee->suites[j].kem_id, |
992 | 0 | ee->suites[j].kdf_id, ee->suites[j].aead_id); |
993 | 0 | if (j < (ee->nsuites - 1)) |
994 | 0 | BIO_printf(out, ","); |
995 | 0 | } |
996 | 0 | BIO_printf(out, "],"); |
997 | | /* public key */ |
998 | 0 | for (j = 0; j != ee->pub_len; j++) |
999 | 0 | BIO_printf(out, "%02x", ee->pub[j]); |
1000 | | /* max name length and (only) number of extensions */ |
1001 | 0 | BIO_printf(out, ",%02x,%02x]", ee->max_name_length, |
1002 | 0 | ee->exts == NULL ? 0 : sk_OSSL_ECHEXT_num(ee->exts)); |
1003 | 0 | } |
1004 | 0 | ehlen = BIO_get_mem_data(out, &ignore); |
1005 | 0 | if (ehlen > INT_MAX) |
1006 | 0 | goto err; |
1007 | 0 | *echconfig = OPENSSL_malloc(ehlen + 1); |
1008 | 0 | if (*echconfig == NULL) |
1009 | 0 | goto err; |
1010 | 0 | if (BIO_read(out, *echconfig, (int)ehlen) <= 0) { |
1011 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1012 | 0 | goto err; |
1013 | 0 | } |
1014 | 0 | (*echconfig)[ehlen] = '\0'; |
1015 | 0 | BIO_free(out); |
1016 | 0 | return 1; |
1017 | 0 | err: |
1018 | 0 | BIO_free(out); |
1019 | 0 | OPENSSL_free(*public_name); |
1020 | 0 | *public_name = NULL; |
1021 | 0 | OPENSSL_free(*echconfig); |
1022 | 0 | *echconfig = NULL; |
1023 | 0 | return 0; |
1024 | 0 | } |
1025 | | |
1026 | | int OSSL_ECHSTORE_downselect(OSSL_ECHSTORE *es, int index) |
1027 | 0 | { |
1028 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
1029 | 0 | int i, num = 0, chosen = OSSL_ECHSTORE_ALL; |
1030 | |
|
1031 | 0 | if (es == NULL) { |
1032 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1033 | 0 | return 0; |
1034 | 0 | } |
1035 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1036 | 0 | if (num == 0) { |
1037 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1038 | 0 | return 0; |
1039 | 0 | } |
1040 | 0 | if (index <= OSSL_ECHSTORE_ALL) { |
1041 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1042 | 0 | return 0; |
1043 | 0 | } |
1044 | 0 | if (index == OSSL_ECHSTORE_LAST) { |
1045 | 0 | chosen = num - 1; |
1046 | 0 | } else if (index >= num) { |
1047 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1048 | 0 | return 0; |
1049 | 0 | } else { |
1050 | 0 | chosen = index; |
1051 | 0 | } |
1052 | 0 | for (i = num - 1; i >= 0; i--) { |
1053 | 0 | if (i == chosen) |
1054 | 0 | continue; |
1055 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1056 | 0 | ossl_echstore_entry_free(ee); |
1057 | 0 | sk_OSSL_ECHSTORE_ENTRY_delete(es->entries, i); |
1058 | 0 | } |
1059 | 0 | return 1; |
1060 | 0 | } |
1061 | | |
1062 | | int OSSL_ECHSTORE_set1_key_and_read_pem(OSSL_ECHSTORE *es, EVP_PKEY *priv, |
1063 | | BIO *in, int for_retry) |
1064 | 0 | { |
1065 | 0 | unsigned char *b64 = NULL; |
1066 | 0 | long b64len = 0; |
1067 | 0 | BIO *b64bio = NULL; |
1068 | 0 | int rv = 0; |
1069 | 0 | char *pname = NULL, *pheader = NULL; |
1070 | | |
1071 | | /* we allow for a NULL private key */ |
1072 | 0 | if (es == NULL || in == NULL) { |
1073 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1074 | 0 | return 0; |
1075 | 0 | } |
1076 | 0 | if (PEM_read_bio(in, &pname, &pheader, &b64, &b64len) != 1) { |
1077 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1078 | 0 | return 0; |
1079 | 0 | } |
1080 | 0 | if (pname == NULL || strcmp(pname, PEM_STRING_ECHCONFIG) != 0) { |
1081 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1082 | 0 | goto err; |
1083 | 0 | } |
1084 | 0 | b64bio = BIO_new(BIO_s_mem()); |
1085 | 0 | if (b64bio == NULL |
1086 | 0 | || BIO_write(b64bio, b64, b64len) <= 0 |
1087 | 0 | || ech_read_priv_echconfiglist(es, b64bio, priv, for_retry) != 1) { |
1088 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1089 | 0 | goto err; |
1090 | 0 | } |
1091 | 0 | rv = 1; |
1092 | 0 | err: |
1093 | 0 | OPENSSL_free(pname); |
1094 | 0 | OPENSSL_free(pheader); |
1095 | 0 | BIO_free_all(b64bio); |
1096 | 0 | OPENSSL_free(b64); |
1097 | 0 | return rv; |
1098 | 0 | } |
1099 | | |
1100 | | int OSSL_ECHSTORE_read_pem(OSSL_ECHSTORE *es, BIO *in, int for_retry) |
1101 | 0 | { |
1102 | 0 | EVP_PKEY *priv = NULL; |
1103 | 0 | int rv = 0; |
1104 | 0 | BIO *fbio = BIO_new(BIO_f_buffer()); |
1105 | |
|
1106 | 0 | if (fbio == NULL || es == NULL || in == NULL) { |
1107 | 0 | BIO_free_all(fbio); |
1108 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1109 | 0 | return 0; |
1110 | 0 | } |
1111 | | /* |
1112 | | * Read private key then handoff to set1_key_and_read_pem. |
1113 | | * We allow for no private key as an option, to handle that |
1114 | | * the BIO_f_buffer allows us to seek back to the start. |
1115 | | */ |
1116 | 0 | BIO_push(fbio, in); |
1117 | 0 | if (!PEM_read_bio_PrivateKey_ex(fbio, &priv, NULL, NULL, es->libctx, es->propq) |
1118 | 0 | && BIO_seek(fbio, 0) < 0) { |
1119 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1120 | 0 | goto err; |
1121 | 0 | } |
1122 | 0 | rv = OSSL_ECHSTORE_set1_key_and_read_pem(es, priv, fbio, for_retry); |
1123 | 0 | err: |
1124 | 0 | EVP_PKEY_free(priv); |
1125 | 0 | BIO_pop(fbio); |
1126 | 0 | BIO_free_all(fbio); |
1127 | 0 | return rv; |
1128 | 0 | } |
1129 | | |
1130 | | int OSSL_ECHSTORE_num_entries(const OSSL_ECHSTORE *es, int *numentries) |
1131 | 0 | { |
1132 | 0 | if (es == NULL || numentries == NULL) { |
1133 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1134 | 0 | return 0; |
1135 | 0 | } |
1136 | 0 | *numentries = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1137 | 0 | return 1; |
1138 | 0 | } |
1139 | | |
1140 | | int OSSL_ECHSTORE_num_keys(OSSL_ECHSTORE *es, int *numkeys) |
1141 | 0 | { |
1142 | 0 | int i, num = 0, count = 0; |
1143 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
1144 | |
|
1145 | 0 | if (es == NULL || numkeys == NULL) { |
1146 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1147 | 0 | return 0; |
1148 | 0 | } |
1149 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1150 | 0 | for (i = 0; i != num; i++) { |
1151 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1152 | 0 | if (ee == NULL) { |
1153 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR); |
1154 | 0 | return 0; |
1155 | 0 | } |
1156 | 0 | count += (ee->keyshare != NULL); |
1157 | 0 | } |
1158 | 0 | *numkeys = count; |
1159 | 0 | return 1; |
1160 | 0 | } |
1161 | | |
1162 | | int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age) |
1163 | 0 | { |
1164 | 0 | OSSL_ECHSTORE_ENTRY *ee = NULL; |
1165 | 0 | int i, num = 0; |
1166 | 0 | time_t now = time(0); |
1167 | |
|
1168 | 0 | if (es == NULL) { |
1169 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); |
1170 | 0 | return 0; |
1171 | 0 | } |
1172 | 0 | num = (es->entries == NULL ? 0 : sk_OSSL_ECHSTORE_ENTRY_num(es->entries)); |
1173 | 0 | if (num == 0) { |
1174 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1175 | 0 | return 0; |
1176 | 0 | } |
1177 | 0 | for (i = num - 1; i >= 0; i--) { |
1178 | 0 | ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); |
1179 | 0 | if (ee == NULL) { |
1180 | 0 | ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); |
1181 | 0 | return 0; |
1182 | 0 | } |
1183 | 0 | if (ee->keyshare != NULL && ee->loadtime + age <= now) { |
1184 | 0 | ossl_echstore_entry_free(ee); |
1185 | 0 | sk_OSSL_ECHSTORE_ENTRY_delete(es->entries, i); |
1186 | 0 | } |
1187 | 0 | } |
1188 | 0 | return 1; |
1189 | 0 | } |