diff options
Diffstat (limited to 'crypto/openssl/ssl/ssl_cert_comp.c')
| -rw-r--r-- | crypto/openssl/ssl/ssl_cert_comp.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/crypto/openssl/ssl/ssl_cert_comp.c b/crypto/openssl/ssl/ssl_cert_comp.c new file mode 100644 index 000000000000..018fcc14a569 --- /dev/null +++ b/crypto/openssl/ssl/ssl_cert_comp.c @@ -0,0 +1,469 @@ +/* + * Copyright 2022-2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include <stdio.h> +#include "ssl_local.h" +#include "internal/e_os.h" +#include "internal/refcount.h" +#include "internal/ssl_unwrap.h" + +size_t ossl_calculate_comp_expansion(int alg, size_t length) +{ + size_t ret; + /* + * Uncompressibility expansion: + * ZLIB: N + 11 + 5 * (N >> 14) + * Brotli: per RFC7932: N + 5 + 3 * (N >> 16) + * ZSTD: N + 4 + 14 + 3 * (N >> 17) + 4 + */ + + switch (alg) { + case TLSEXT_comp_cert_zlib: + ret = length + 11 + 5 * (length >> 14); + break; + case TLSEXT_comp_cert_brotli: + ret = length + 5 + 3 * (length >> 16); + break; + case TLSEXT_comp_cert_zstd: + ret = length + 22 + 3 * (length >> 17); + break; + default: + return 0; + } + /* Check for overflow */ + if (ret < length) + return 0; + return ret; +} + +int ossl_comp_has_alg(int a) +{ +#ifndef OPENSSL_NO_COMP_ALG + /* 0 means "any" algorithm */ + if ((a == 0 || a == TLSEXT_comp_cert_brotli) && BIO_f_brotli() != NULL) + return 1; + if ((a == 0 || a == TLSEXT_comp_cert_zstd) && BIO_f_zstd() != NULL) + return 1; + if ((a == 0 || a == TLSEXT_comp_cert_zlib) && BIO_f_zlib() != NULL) + return 1; +#endif + return 0; +} + +/* New operation Helper routine */ +#ifndef OPENSSL_NO_COMP_ALG +static OSSL_COMP_CERT *OSSL_COMP_CERT_new(unsigned char *data, size_t len, size_t orig_len, int alg) +{ + OSSL_COMP_CERT *ret = NULL; + + if (!ossl_comp_has_alg(alg) + || data == NULL + || (ret = OPENSSL_zalloc(sizeof(*ret))) == NULL + || !CRYPTO_NEW_REF(&ret->references, 1)) + goto err; + + ret->data = data; + ret->len = len; + ret->orig_len = orig_len; + ret->alg = alg; + return ret; + err: + ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE); + OPENSSL_free(data); + OPENSSL_free(ret); + return NULL; +} + +__owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_compressed_data(unsigned char *data, size_t len, + size_t orig_len, int alg) +{ + return OSSL_COMP_CERT_new(OPENSSL_memdup(data, len), len, orig_len, alg); +} + +__owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_uncompressed_data(unsigned char *data, size_t len, + int alg) +{ + OSSL_COMP_CERT *ret = NULL; + size_t max_length; + int comp_length; + COMP_METHOD *method; + unsigned char *comp_data = NULL; + COMP_CTX *comp_ctx = NULL; + + switch (alg) { + case TLSEXT_comp_cert_brotli: + method = COMP_brotli_oneshot(); + break; + case TLSEXT_comp_cert_zlib: + method = COMP_zlib_oneshot(); + break; + case TLSEXT_comp_cert_zstd: + method = COMP_zstd_oneshot(); + break; + default: + goto err; + } + + if ((max_length = ossl_calculate_comp_expansion(alg, len)) == 0 + || method == NULL + || (comp_ctx = COMP_CTX_new(method)) == NULL + || (comp_data = OPENSSL_zalloc(max_length)) == NULL) + goto err; + + comp_length = COMP_compress_block(comp_ctx, comp_data, max_length, data, len); + if (comp_length <= 0) + goto err; + + ret = OSSL_COMP_CERT_new(comp_data, comp_length, len, alg); + comp_data = NULL; + + err: + OPENSSL_free(comp_data); + COMP_CTX_free(comp_ctx); + return ret; +} + +void OSSL_COMP_CERT_free(OSSL_COMP_CERT *cc) +{ + int i; + + if (cc == NULL) + return; + + CRYPTO_DOWN_REF(&cc->references, &i); + REF_PRINT_COUNT("OSSL_COMP_CERT", i, cc); + if (i > 0) + return; + REF_ASSERT_ISNT(i < 0); + + OPENSSL_free(cc->data); + CRYPTO_FREE_REF(&cc->references); + OPENSSL_free(cc); +} +int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *cc) +{ + int i; + + if (CRYPTO_UP_REF(&cc->references, &i) <= 0) + return 0; + + REF_PRINT_COUNT("OSSL_COMP_CERT", i, cc); + REF_ASSERT_ISNT(i < 2); + return ((i > 1) ? 1 : 0); +} + +static int ssl_set_cert_comp_pref(int *prefs, int *algs, size_t len) +{ + size_t j = 0; + size_t i; + int found = 0; + int already_set[TLSEXT_comp_cert_limit]; + int tmp_prefs[TLSEXT_comp_cert_limit]; + + /* Note that |len| is the number of |algs| elements */ + /* clear all algorithms */ + if (len == 0 || algs == NULL) { + memset(prefs, 0, sizeof(tmp_prefs)); + return 1; + } + + /* This will 0-terminate the array */ + memset(tmp_prefs, 0, sizeof(tmp_prefs)); + memset(already_set, 0, sizeof(already_set)); + /* Include only those algorithms we support, ignoring duplicates and unknowns */ + for (i = 0; i < len; i++) { + if (algs[i] != 0 && ossl_comp_has_alg(algs[i])) { + /* Check for duplicate */ + if (already_set[algs[i]]) + return 0; + tmp_prefs[j++] = algs[i]; + already_set[algs[i]] = 1; + found = 1; + } + } + if (found) + memcpy(prefs, tmp_prefs, sizeof(tmp_prefs)); + return found; +} + +static size_t ssl_get_cert_to_compress(SSL *ssl, CERT_PKEY *cpk, unsigned char **data) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + WPACKET tmppkt; + BUF_MEM buf = { 0 }; + size_t ret = 0; + + if (sc == NULL + || cpk == NULL + || !sc->server + || !SSL_in_before(ssl)) + return 0; + + /* Use the |tmppkt| for the to-be-compressed data */ + if (!WPACKET_init(&tmppkt, &buf)) + goto out; + + /* no context present, add 0-length context */ + if (!WPACKET_put_bytes_u8(&tmppkt, 0)) + goto out; + + /* + * ssl3_output_cert_chain() may generate an SSLfatal() error, + * for this case, we want to ignore it, argument for_comp = 1 + */ + if (!ssl3_output_cert_chain(sc, &tmppkt, cpk, 1)) + goto out; + WPACKET_get_total_written(&tmppkt, &ret); + + out: + WPACKET_cleanup(&tmppkt); + if (ret != 0 && data != NULL) + *data = (unsigned char *)buf.data; + else + OPENSSL_free(buf.data); + return ret; +} + +static int ssl_compress_one_cert(SSL *ssl, CERT_PKEY *cpk, int alg) +{ + unsigned char *cert_data = NULL; + OSSL_COMP_CERT *comp_cert = NULL; + size_t length; + + if (cpk == NULL + || alg == TLSEXT_comp_cert_none + || !ossl_comp_has_alg(alg)) + return 0; + + if ((length = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) + return 0; + comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, length, alg); + OPENSSL_free(cert_data); + if (comp_cert == NULL) + return 0; + + OSSL_COMP_CERT_free(cpk->comp_cert[alg]); + cpk->comp_cert[alg] = comp_cert; + return 1; +} + +/* alg_in can be 0, meaning any/all algorithms */ +static int ssl_compress_certs(SSL *ssl, CERT_PKEY *cpks, int alg_in) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + int i; + int j; + int alg; + int count = 0; + + if (sc == NULL + || cpks == NULL + || !ossl_comp_has_alg(alg_in)) + return 0; + + /* Look through the preferences to see what we have */ + for (i = 0; i < TLSEXT_comp_cert_limit; i++) { + /* + * alg = 0 means compress for everything, but only for algorithms enabled + * alg != 0 means compress for that algorithm if enabled + */ + alg = sc->cert_comp_prefs[i]; + if ((alg_in == 0 && alg != TLSEXT_comp_cert_none) + || (alg_in != 0 && alg == alg_in)) { + + for (j = 0; j < SSL_PKEY_NUM; j++) { + /* No cert, move on */ + if (cpks[j].x509 == NULL) + continue; + + if (!ssl_compress_one_cert(ssl, &cpks[j], alg)) + return 0; + + /* if the cert expanded, set the value in the CERT_PKEY to NULL */ + if (cpks[j].comp_cert[alg]->len >= cpks[j].comp_cert[alg]->orig_len) { + OSSL_COMP_CERT_free(cpks[j].comp_cert[alg]); + cpks[j].comp_cert[alg] = NULL; + } else { + count++; + } + } + } + } + return (count > 0); +} + +static size_t ssl_get_compressed_cert(SSL *ssl, CERT_PKEY *cpk, int alg, unsigned char **data, + size_t *orig_len) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + size_t cert_len = 0; + size_t comp_len = 0; + unsigned char *cert_data = NULL; + OSSL_COMP_CERT *comp_cert = NULL; + + if (sc == NULL + || cpk == NULL + || data == NULL + || orig_len == NULL + || !sc->server + || !SSL_in_before(ssl) + || !ossl_comp_has_alg(alg)) + return 0; + + if ((cert_len = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) + goto err; + + comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, cert_len, alg); + OPENSSL_free(cert_data); + if (comp_cert == NULL) + goto err; + + comp_len = comp_cert->len; + *orig_len = comp_cert->orig_len; + *data = comp_cert->data; + comp_cert->data = NULL; + err: + OSSL_COMP_CERT_free(comp_cert); + return comp_len; +} + +static int ossl_set1_compressed_cert(CERT *cert, int algorithm, + unsigned char *comp_data, size_t comp_length, + size_t orig_length) +{ + OSSL_COMP_CERT *comp_cert; + + /* No explicit cert set */ + if (cert == NULL || cert->key == NULL) + return 0; + + comp_cert = OSSL_COMP_CERT_from_compressed_data(comp_data, comp_length, + orig_length, algorithm); + if (comp_cert == NULL) + return 0; + + OSSL_COMP_CERT_free(cert->key->comp_cert[algorithm]); + cert->key->comp_cert[algorithm] = comp_cert; + + return 1; +} +#endif + +/*- + * Public API + */ +int SSL_CTX_set1_cert_comp_preference(SSL_CTX *ctx, int *algs, size_t len) +{ +#ifndef OPENSSL_NO_COMP_ALG + return ssl_set_cert_comp_pref(ctx->cert_comp_prefs, algs, len); +#else + return 0; +#endif +} + +int SSL_set1_cert_comp_preference(SSL *ssl, int *algs, size_t len) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + if (sc == NULL) + return 0; + return ssl_set_cert_comp_pref(sc->cert_comp_prefs, algs, len); +#else + return 0; +#endif +} + +int SSL_compress_certs(SSL *ssl, int alg) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + if (sc == NULL || sc->cert == NULL) + return 0; + + return ssl_compress_certs(ssl, sc->cert->pkeys, alg); +#endif + return 0; +} + +int SSL_CTX_compress_certs(SSL_CTX *ctx, int alg) +{ + int ret = 0; +#ifndef OPENSSL_NO_COMP_ALG + SSL *new = SSL_new(ctx); + + if (new == NULL) + return 0; + + ret = ssl_compress_certs(new, ctx->cert->pkeys, alg); + SSL_free(new); +#endif + return ret; +} + +size_t SSL_get1_compressed_cert(SSL *ssl, int alg, unsigned char **data, size_t *orig_len) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + CERT_PKEY *cpk = NULL; + + if (sc == NULL) + return 0; + + if (sc->cert != NULL) + cpk = sc->cert->key; + else + cpk = ssl->ctx->cert->key; + + return ssl_get_compressed_cert(ssl, cpk, alg, data, orig_len); +#else + return 0; +#endif +} + +size_t SSL_CTX_get1_compressed_cert(SSL_CTX *ctx, int alg, unsigned char **data, size_t *orig_len) +{ +#ifndef OPENSSL_NO_COMP_ALG + size_t ret; + SSL *new = SSL_new(ctx); + + ret = ssl_get_compressed_cert(new, ctx->cert->key, alg, data, orig_len); + SSL_free(new); + return ret; +#else + return 0; +#endif +} + +int SSL_CTX_set1_compressed_cert(SSL_CTX *ctx, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length) +{ +#ifndef OPENSSL_NO_COMP_ALG + return ossl_set1_compressed_cert(ctx->cert, algorithm, comp_data, comp_length, orig_length); +#else + return 0; +#endif +} + +int SSL_set1_compressed_cert(SSL *ssl, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + /* Cannot set a pre-compressed certificate on a client */ + if (sc == NULL || !sc->server) + return 0; + + return ossl_set1_compressed_cert(sc->cert, algorithm, comp_data, comp_length, orig_length); +#else + return 0; +#endif +} |
