]>
Commit | Line | Data |
---|---|---|
b67cb09f | 1 | /* |
da1c088f | 2 | * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved. |
b67cb09f TS |
3 | * |
4 | * Licensed under the Apache License 2.0 (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 <stdio.h> | |
11 | #include "ssl_local.h" | |
12 | #include "internal/e_os.h" | |
13 | #include "internal/refcount.h" | |
14 | ||
15 | size_t ossl_calculate_comp_expansion(int alg, size_t length) | |
16 | { | |
17 | size_t ret; | |
18 | /* | |
19 | * Uncompressibility expansion: | |
20 | * ZLIB: N + 11 + 5 * (N >> 14) | |
21 | * Brotli: per RFC7932: N + 5 + 3 * (N >> 16) | |
22 | * ZSTD: N + 4 + 14 + 3 * (N >> 17) + 4 | |
23 | */ | |
24 | ||
25 | switch (alg) { | |
26 | case TLSEXT_comp_cert_zlib: | |
27 | ret = length + 11 + 5 * (length >> 14); | |
28 | break; | |
29 | case TLSEXT_comp_cert_brotli: | |
30 | ret = length + 5 + 3 * (length >> 16); | |
31 | break; | |
32 | case TLSEXT_comp_cert_zstd: | |
33 | ret = length + 22 + 3 * (length >> 17); | |
34 | break; | |
35 | default: | |
36 | return 0; | |
37 | } | |
38 | /* Check for overflow */ | |
39 | if (ret < length) | |
40 | return 0; | |
41 | return ret; | |
42 | } | |
43 | ||
44 | int ossl_comp_has_alg(int a) | |
45 | { | |
46 | #ifndef OPENSSL_NO_COMP_ALG | |
47 | /* 0 means "any" algorithm */ | |
48 | if ((a == 0 || a == TLSEXT_comp_cert_brotli) && BIO_f_brotli() != NULL) | |
49 | return 1; | |
50 | if ((a == 0 || a == TLSEXT_comp_cert_zstd) && BIO_f_zstd() != NULL) | |
51 | return 1; | |
52 | if ((a == 0 || a == TLSEXT_comp_cert_zlib) && BIO_f_zlib() != NULL) | |
53 | return 1; | |
54 | #endif | |
55 | return 0; | |
56 | } | |
57 | ||
58 | /* New operation Helper routine */ | |
59 | #ifndef OPENSSL_NO_COMP_ALG | |
60 | static OSSL_COMP_CERT *OSSL_COMP_CERT_new(unsigned char *data, size_t len, size_t orig_len, int alg) | |
61 | { | |
62 | OSSL_COMP_CERT *ret = NULL; | |
63 | ||
64 | if (!ossl_comp_has_alg(alg) | |
65 | || data == NULL | |
66 | || (ret = OPENSSL_zalloc(sizeof(*ret))) == NULL | |
43a07d6d | 67 | || !CRYPTO_NEW_REF(&ret->references, 1)) |
b67cb09f TS |
68 | goto err; |
69 | ||
b67cb09f TS |
70 | ret->data = data; |
71 | ret->len = len; | |
72 | ret->orig_len = orig_len; | |
73 | ret->alg = alg; | |
74 | return ret; | |
75 | err: | |
76 | ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE); | |
77 | OPENSSL_free(data); | |
78 | OPENSSL_free(ret); | |
79 | return NULL; | |
80 | } | |
81 | ||
82 | __owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_compressed_data(unsigned char *data, size_t len, | |
83 | size_t orig_len, int alg) | |
84 | { | |
85 | return OSSL_COMP_CERT_new(OPENSSL_memdup(data, len), len, orig_len, alg); | |
86 | } | |
87 | ||
88 | __owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_uncompressed_data(unsigned char *data, size_t len, | |
89 | int alg) | |
90 | { | |
91 | OSSL_COMP_CERT *ret = NULL; | |
92 | size_t max_length; | |
93 | int comp_length; | |
94 | COMP_METHOD *method; | |
95 | unsigned char *comp_data = NULL; | |
96 | COMP_CTX *comp_ctx = NULL; | |
97 | ||
98 | switch (alg) { | |
99 | case TLSEXT_comp_cert_brotli: | |
100 | method = COMP_brotli_oneshot(); | |
101 | break; | |
102 | case TLSEXT_comp_cert_zlib: | |
3840271e | 103 | method = COMP_zlib_oneshot(); |
b67cb09f TS |
104 | break; |
105 | case TLSEXT_comp_cert_zstd: | |
106 | method = COMP_zstd_oneshot(); | |
107 | break; | |
108 | default: | |
109 | goto err; | |
110 | } | |
111 | ||
112 | if ((max_length = ossl_calculate_comp_expansion(alg, len)) == 0 | |
113 | || method == NULL | |
114 | || (comp_ctx = COMP_CTX_new(method)) == NULL | |
115 | || (comp_data = OPENSSL_zalloc(max_length)) == NULL) | |
116 | goto err; | |
117 | ||
118 | comp_length = COMP_compress_block(comp_ctx, comp_data, max_length, data, len); | |
119 | if (comp_length <= 0) | |
120 | goto err; | |
121 | ||
122 | ret = OSSL_COMP_CERT_new(comp_data, comp_length, len, alg); | |
123 | comp_data = NULL; | |
124 | ||
125 | err: | |
126 | OPENSSL_free(comp_data); | |
127 | COMP_CTX_free(comp_ctx); | |
128 | return ret; | |
129 | } | |
130 | ||
131 | void OSSL_COMP_CERT_free(OSSL_COMP_CERT *cc) | |
132 | { | |
133 | int i; | |
134 | ||
135 | if (cc == NULL) | |
136 | return; | |
137 | ||
43a07d6d | 138 | CRYPTO_DOWN_REF(&cc->references, &i); |
b67cb09f TS |
139 | REF_PRINT_COUNT("OSSL_COMP_CERT", cc); |
140 | if (i > 0) | |
141 | return; | |
142 | REF_ASSERT_ISNT(i < 0); | |
143 | ||
144 | OPENSSL_free(cc->data); | |
43a07d6d | 145 | CRYPTO_FREE_REF(&cc->references); |
b67cb09f TS |
146 | OPENSSL_free(cc); |
147 | } | |
148 | int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *cc) | |
149 | { | |
150 | int i; | |
151 | ||
43a07d6d | 152 | if (CRYPTO_UP_REF(&cc->references, &i) <= 0) |
b67cb09f TS |
153 | return 0; |
154 | ||
155 | REF_PRINT_COUNT("OSSL_COMP_CERT", cc); | |
156 | REF_ASSERT_ISNT(i < 2); | |
157 | return ((i > 1) ? 1 : 0); | |
158 | } | |
159 | ||
160 | static int ssl_set_cert_comp_pref(int *prefs, int *algs, size_t len) | |
161 | { | |
162 | size_t j = 0; | |
163 | size_t i; | |
164 | int found = 0; | |
165 | int already_set[TLSEXT_comp_cert_limit]; | |
166 | int tmp_prefs[TLSEXT_comp_cert_limit]; | |
167 | ||
168 | /* Note that |len| is the number of |algs| elements */ | |
169 | /* clear all algorithms */ | |
170 | if (len == 0 || algs == NULL) { | |
171 | memset(prefs, 0, sizeof(tmp_prefs)); | |
172 | return 1; | |
173 | } | |
174 | ||
175 | /* This will 0-terminate the array */ | |
176 | memset(tmp_prefs, 0, sizeof(tmp_prefs)); | |
177 | memset(already_set, 0, sizeof(already_set)); | |
178 | /* Include only those algorithms we support, ignoring duplicates and unknowns */ | |
179 | for (i = 0; i < len; i++) { | |
180 | if (algs[i] != 0 && ossl_comp_has_alg(algs[i])) { | |
181 | /* Check for duplicate */ | |
182 | if (already_set[algs[i]]) | |
183 | return 0; | |
184 | tmp_prefs[j++] = algs[i]; | |
185 | already_set[algs[i]] = 1; | |
186 | found = 1; | |
187 | } | |
188 | } | |
189 | if (found) | |
190 | memcpy(prefs, tmp_prefs, sizeof(tmp_prefs)); | |
191 | return found; | |
192 | } | |
193 | ||
194 | static size_t ssl_get_cert_to_compress(SSL *ssl, CERT_PKEY *cpk, unsigned char **data) | |
195 | { | |
196 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
197 | WPACKET tmppkt; | |
198 | BUF_MEM buf = { 0 }; | |
199 | size_t ret = 0; | |
b67cb09f TS |
200 | |
201 | if (sc == NULL | |
202 | || cpk == NULL | |
203 | || !sc->server | |
204 | || !SSL_in_before(ssl)) | |
205 | return 0; | |
206 | ||
207 | /* Use the |tmppkt| for the to-be-compressed data */ | |
208 | if (!WPACKET_init(&tmppkt, &buf)) | |
209 | goto out; | |
210 | ||
211 | /* no context present, add 0-length context */ | |
212 | if (!WPACKET_put_bytes_u8(&tmppkt, 0)) | |
213 | goto out; | |
214 | ||
215 | /* | |
72620ac7 TS |
216 | * ssl3_output_cert_chain() may generate an SSLfatal() error, |
217 | * for this case, we want to ignore it, argument for_comp = 1 | |
b67cb09f | 218 | */ |
72620ac7 | 219 | if (!ssl3_output_cert_chain(sc, &tmppkt, cpk, 1)) |
b67cb09f TS |
220 | goto out; |
221 | WPACKET_get_total_written(&tmppkt, &ret); | |
222 | ||
223 | out: | |
b67cb09f TS |
224 | WPACKET_cleanup(&tmppkt); |
225 | if (ret != 0 && data != NULL) | |
226 | *data = (unsigned char *)buf.data; | |
227 | else | |
228 | OPENSSL_free(buf.data); | |
229 | return ret; | |
230 | } | |
231 | ||
232 | static int ssl_compress_one_cert(SSL *ssl, CERT_PKEY *cpk, int alg) | |
233 | { | |
234 | unsigned char *cert_data = NULL; | |
235 | OSSL_COMP_CERT *comp_cert = NULL; | |
236 | size_t length; | |
237 | ||
238 | if (cpk == NULL | |
239 | || alg == TLSEXT_comp_cert_none | |
240 | || !ossl_comp_has_alg(alg)) | |
241 | return 0; | |
242 | ||
243 | if ((length = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) | |
244 | return 0; | |
245 | comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, length, alg); | |
246 | OPENSSL_free(cert_data); | |
247 | if (comp_cert == NULL) | |
248 | return 0; | |
249 | ||
250 | OSSL_COMP_CERT_free(cpk->comp_cert[alg]); | |
251 | cpk->comp_cert[alg] = comp_cert; | |
252 | return 1; | |
253 | } | |
254 | ||
255 | /* alg_in can be 0, meaning any/all algorithms */ | |
256 | static int ssl_compress_certs(SSL *ssl, CERT_PKEY *cpks, int alg_in) | |
257 | { | |
258 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
259 | int i; | |
260 | int j; | |
261 | int alg; | |
262 | int count = 0; | |
263 | ||
264 | if (sc == NULL | |
265 | || cpks == NULL | |
266 | || !ossl_comp_has_alg(alg_in)) | |
267 | return 0; | |
268 | ||
269 | /* Look through the preferences to see what we have */ | |
270 | for (i = 0; i < TLSEXT_comp_cert_limit; i++) { | |
271 | /* | |
272 | * alg = 0 means compress for everything, but only for algorithms enabled | |
273 | * alg != 0 means compress for that algorithm if enabled | |
274 | */ | |
275 | alg = sc->cert_comp_prefs[i]; | |
276 | if ((alg_in == 0 && alg != TLSEXT_comp_cert_none) | |
277 | || (alg_in != 0 && alg == alg_in)) { | |
278 | ||
279 | for (j = 0; j < SSL_PKEY_NUM; j++) { | |
280 | /* No cert, move on */ | |
281 | if (cpks[j].x509 == NULL) | |
282 | continue; | |
283 | ||
284 | if (!ssl_compress_one_cert(ssl, &cpks[j], alg)) | |
285 | return 0; | |
286 | ||
287 | /* if the cert expanded, set the value in the CERT_PKEY to NULL */ | |
288 | if (cpks[j].comp_cert[alg]->len >= cpks[j].comp_cert[alg]->orig_len) { | |
289 | OSSL_COMP_CERT_free(cpks[j].comp_cert[alg]); | |
290 | cpks[j].comp_cert[alg] = NULL; | |
291 | } else { | |
292 | count++; | |
293 | } | |
294 | } | |
295 | } | |
296 | } | |
297 | return (count > 0); | |
298 | } | |
299 | ||
300 | static size_t ssl_get_compressed_cert(SSL *ssl, CERT_PKEY *cpk, int alg, unsigned char **data, | |
301 | size_t *orig_len) | |
302 | { | |
303 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
304 | size_t cert_len = 0; | |
305 | size_t comp_len = 0; | |
306 | unsigned char *cert_data = NULL; | |
307 | OSSL_COMP_CERT *comp_cert = NULL; | |
308 | ||
309 | if (sc == NULL | |
310 | || cpk == NULL | |
311 | || data == NULL | |
312 | || orig_len == NULL | |
313 | || !sc->server | |
314 | || !SSL_in_before(ssl) | |
315 | || !ossl_comp_has_alg(alg)) | |
316 | return 0; | |
317 | ||
318 | if ((cert_len = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) | |
319 | goto err; | |
320 | ||
321 | comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, cert_len, alg); | |
322 | OPENSSL_free(cert_data); | |
323 | if (comp_cert == NULL) | |
324 | goto err; | |
325 | ||
326 | comp_len = comp_cert->len; | |
327 | *orig_len = comp_cert->orig_len; | |
328 | *data = comp_cert->data; | |
329 | comp_cert->data = NULL; | |
330 | err: | |
331 | OSSL_COMP_CERT_free(comp_cert); | |
332 | return comp_len; | |
333 | } | |
334 | ||
335 | static int ossl_set1_compressed_cert(CERT *cert, int algorithm, | |
336 | unsigned char *comp_data, size_t comp_length, | |
337 | size_t orig_length) | |
338 | { | |
339 | OSSL_COMP_CERT *comp_cert; | |
340 | ||
341 | /* No explicit cert set */ | |
342 | if (cert == NULL || cert->key == NULL) | |
343 | return 0; | |
344 | ||
345 | comp_cert = OSSL_COMP_CERT_from_compressed_data(comp_data, comp_length, | |
346 | orig_length, algorithm); | |
347 | if (comp_cert == NULL) | |
348 | return 0; | |
349 | ||
350 | OSSL_COMP_CERT_free(cert->key->comp_cert[algorithm]); | |
351 | cert->key->comp_cert[algorithm] = comp_cert; | |
352 | ||
353 | return 1; | |
354 | } | |
355 | #endif | |
356 | ||
357 | /*- | |
358 | * Public API | |
359 | */ | |
360 | int SSL_CTX_set1_cert_comp_preference(SSL_CTX *ctx, int *algs, size_t len) | |
361 | { | |
362 | #ifndef OPENSSL_NO_COMP_ALG | |
363 | return ssl_set_cert_comp_pref(ctx->cert_comp_prefs, algs, len); | |
364 | #else | |
365 | return 0; | |
366 | #endif | |
367 | } | |
368 | ||
369 | int SSL_set1_cert_comp_preference(SSL *ssl, int *algs, size_t len) | |
370 | { | |
371 | #ifndef OPENSSL_NO_COMP_ALG | |
372 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
373 | ||
374 | if (sc == NULL) | |
375 | return 0; | |
376 | return ssl_set_cert_comp_pref(sc->cert_comp_prefs, algs, len); | |
377 | #else | |
378 | return 0; | |
379 | #endif | |
380 | } | |
381 | ||
382 | int SSL_compress_certs(SSL *ssl, int alg) | |
383 | { | |
384 | #ifndef OPENSSL_NO_COMP_ALG | |
385 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
386 | ||
387 | if (sc == NULL || sc->cert == NULL) | |
388 | return 0; | |
389 | ||
390 | return ssl_compress_certs(ssl, sc->cert->pkeys, alg); | |
391 | #endif | |
392 | return 0; | |
393 | } | |
394 | ||
395 | int SSL_CTX_compress_certs(SSL_CTX *ctx, int alg) | |
396 | { | |
397 | int ret = 0; | |
398 | #ifndef OPENSSL_NO_COMP_ALG | |
399 | SSL *new = SSL_new(ctx); | |
400 | ||
401 | if (new == NULL) | |
402 | return 0; | |
403 | ||
404 | ret = ssl_compress_certs(new, ctx->cert->pkeys, alg); | |
405 | SSL_free(new); | |
406 | #endif | |
407 | return ret; | |
408 | } | |
409 | ||
410 | size_t SSL_get1_compressed_cert(SSL *ssl, int alg, unsigned char **data, size_t *orig_len) | |
411 | { | |
412 | #ifndef OPENSSL_NO_COMP_ALG | |
413 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
414 | CERT_PKEY *cpk = NULL; | |
415 | ||
416 | if (sc->cert != NULL) | |
417 | cpk = sc->cert->key; | |
418 | else | |
419 | cpk = ssl->ctx->cert->key; | |
420 | ||
421 | return ssl_get_compressed_cert(ssl, cpk, alg, data, orig_len); | |
422 | #else | |
423 | return 0; | |
424 | #endif | |
425 | } | |
426 | ||
427 | size_t SSL_CTX_get1_compressed_cert(SSL_CTX *ctx, int alg, unsigned char **data, size_t *orig_len) | |
428 | { | |
429 | #ifndef OPENSSL_NO_COMP_ALG | |
430 | size_t ret; | |
431 | SSL *new = SSL_new(ctx); | |
432 | ||
433 | ret = ssl_get_compressed_cert(new, ctx->cert->key, alg, data, orig_len); | |
434 | SSL_free(new); | |
435 | return ret; | |
436 | #else | |
437 | return 0; | |
438 | #endif | |
439 | } | |
440 | ||
441 | int SSL_CTX_set1_compressed_cert(SSL_CTX *ctx, int algorithm, unsigned char *comp_data, | |
442 | size_t comp_length, size_t orig_length) | |
443 | { | |
444 | #ifndef OPENSSL_NO_COMP_ALG | |
445 | return ossl_set1_compressed_cert(ctx->cert, algorithm, comp_data, comp_length, orig_length); | |
446 | #else | |
447 | return 0; | |
448 | #endif | |
449 | } | |
450 | ||
451 | int SSL_set1_compressed_cert(SSL *ssl, int algorithm, unsigned char *comp_data, | |
452 | size_t comp_length, size_t orig_length) | |
453 | { | |
454 | #ifndef OPENSSL_NO_COMP_ALG | |
455 | SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); | |
456 | ||
457 | /* Cannot set a pre-compressed certificate on a client */ | |
458 | if (sc == NULL || !sc->server) | |
459 | return 0; | |
460 | ||
461 | return ossl_set1_compressed_cert(sc->cert, algorithm, comp_data, comp_length, orig_length); | |
462 | #else | |
463 | return 0; | |
464 | #endif | |
465 | } |