summaryrefslogtreecommitdiffstats
path: root/ssl
diff options
context:
space:
mode:
authorTodd Short <tshort@akamai.com>2021-08-09 16:56:50 -0400
committerTodd Short <todd.short@me.com>2022-10-18 09:30:22 -0400
commitb67cb09f8ddf258cf326f3e7b20be095fb53457c (patch)
treeb31a978e8c71e972e84fd03b4de92491deff032a /ssl
parent59d21298df9176b64b41cc8583c7024f7f5895d4 (diff)
Add support for compressed certificates (RFC8879)
* Compressed Certificate extension (server/client) * Server certificates (send/receive) * Client certificate (send/receive) Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Hugo Landau <hlandau@openssl.org> (Merged from https://github.com/openssl/openssl/pull/18186)
Diffstat (limited to 'ssl')
-rw-r--r--ssl/build.info1
-rw-r--r--ssl/ssl_cert.c35
-rw-r--r--ssl/ssl_cert_comp.c479
-rw-r--r--ssl/ssl_conf.c12
-rw-r--r--ssl/ssl_err.c2
-rw-r--r--ssl/ssl_lib.c25
-rw-r--r--ssl/ssl_local.h41
-rw-r--r--ssl/ssl_stat.c16
-rw-r--r--ssl/statem/extensions.c125
-rw-r--r--ssl/statem/extensions_cust.c1
-rw-r--r--ssl/statem/statem.c10
-rw-r--r--ssl/statem/statem.h1
-rw-r--r--ssl/statem/statem_clnt.c162
-rw-r--r--ssl/statem/statem_lib.c73
-rw-r--r--ssl/statem/statem_local.h23
-rw-r--r--ssl/statem/statem_srvr.c117
-rw-r--r--ssl/t1_trce.c89
17 files changed, 1194 insertions, 18 deletions
diff --git a/ssl/build.info b/ssl/build.info
index 81631c7497..00e9e8fd7d 100644
--- a/ssl/build.info
+++ b/ssl/build.info
@@ -19,6 +19,7 @@ SOURCE[../libssl]=\
ssl_asn1.c ssl_txt.c ssl_init.c ssl_conf.c ssl_mcnf.c \
bio_ssl.c ssl_err.c ssl_err_legacy.c tls_srp.c t1_trce.c ssl_utst.c \
statem/statem.c \
+ ssl_cert_comp.c \
tls_depr.c
# For shared builds we need to include the libcrypto packet.c and quic_vlint.c
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index 8ec36b9c19..d2bc61476d 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -74,6 +74,9 @@ CERT *ssl_cert_dup(CERT *cert)
{
CERT *ret = OPENSSL_zalloc(sizeof(*ret));
int i;
+#ifndef OPENSSL_NO_COMP_ALG
+ int j;
+#endif
if (ret == NULL)
return NULL;
@@ -98,6 +101,7 @@ CERT *ssl_cert_dup(CERT *cert)
for (i = 0; i < SSL_PKEY_NUM; i++) {
CERT_PKEY *cpk = cert->pkeys + i;
CERT_PKEY *rpk = ret->pkeys + i;
+
if (cpk->x509 != NULL) {
rpk->x509 = cpk->x509;
X509_up_ref(rpk->x509);
@@ -115,16 +119,22 @@ CERT *ssl_cert_dup(CERT *cert)
goto err;
}
}
- if (cert->pkeys[i].serverinfo != NULL) {
+ if (cpk->serverinfo != NULL) {
/* Just copy everything. */
- ret->pkeys[i].serverinfo =
- OPENSSL_malloc(cert->pkeys[i].serverinfo_length);
- if (ret->pkeys[i].serverinfo == NULL)
+ rpk->serverinfo = OPENSSL_memdup(cpk->serverinfo, cpk->serverinfo_length);
+ if (rpk->serverinfo == NULL)
goto err;
- ret->pkeys[i].serverinfo_length = cert->pkeys[i].serverinfo_length;
- memcpy(ret->pkeys[i].serverinfo,
- cert->pkeys[i].serverinfo, cert->pkeys[i].serverinfo_length);
+ rpk->serverinfo_length = cpk->serverinfo_length;
+ }
+#ifndef OPENSSL_NO_COMP_ALG
+ for (j = TLSEXT_comp_cert_none; j < TLSEXT_comp_cert_limit; j++) {
+ if (cpk->comp_cert[j] != NULL) {
+ if (!OSSL_COMP_CERT_up_ref(cpk->comp_cert[j]))
+ goto err;
+ rpk->comp_cert[j] = cpk->comp_cert[j];
+ }
}
+#endif
}
/* Configured sigalgs copied across */
@@ -198,6 +208,10 @@ CERT *ssl_cert_dup(CERT *cert)
void ssl_cert_clear_certs(CERT *c)
{
int i;
+#ifndef OPENSSL_NO_COMP_ALG
+ int j;
+#endif
+
if (c == NULL)
return;
for (i = 0; i < SSL_PKEY_NUM; i++) {
@@ -211,6 +225,13 @@ void ssl_cert_clear_certs(CERT *c)
OPENSSL_free(cpk->serverinfo);
cpk->serverinfo = NULL;
cpk->serverinfo_length = 0;
+#ifndef OPENSSL_NO_COMP_ALG
+ for (j = 0; j < TLSEXT_comp_cert_limit; j++) {
+ OSSL_COMP_CERT_free(cpk->comp_cert[j]);
+ cpk->comp_cert[j] = NULL;
+ cpk->cert_comp_used = 0;
+ }
+#endif
}
}
diff --git a/ssl/ssl_cert_comp.c b/ssl/ssl_cert_comp.c
new file mode 100644
index 0000000000..a86282279c
--- /dev/null
+++ b/ssl/ssl_cert_comp.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2022 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"
+
+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
+ || (ret->lock = CRYPTO_THREAD_lock_new()) == NULL)
+ goto err;
+
+ ret->references = 1;
+ 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();
+ 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, cc->lock);
+ REF_PRINT_COUNT("OSSL_COMP_CERT", cc);
+ if (i > 0)
+ return;
+ REF_ASSERT_ISNT(i < 0);
+
+ OPENSSL_free(cc->data);
+ CRYPTO_THREAD_lock_free(cc->lock);
+ OPENSSL_free(cc);
+}
+int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *cc)
+{
+ int i;
+
+ if (CRYPTO_UP_REF(&cc->references, &i, cc->lock) <= 0)
+ return 0;
+
+ REF_PRINT_COUNT("OSSL_COMP_CERT", 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;
+ const SSL_METHOD *method = NULL;
+
+ 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 SSLfata() error,
+ * for this case, we want to ignore it
+ */
+ sc->statem.ignore_fatal = 1;
+ ERR_set_mark();
+ /* Must get the certificate as TLSv1.3, restore before returning */
+ method = SSL_get_ssl_method(ssl);
+ if (!SSL_set_ssl_method(ssl, tlsv1_3_server_method()))
+ goto out;
+
+ if (!ssl3_output_cert_chain(sc, &tmppkt, cpk))
+ goto out;
+ WPACKET_get_total_written(&tmppkt, &ret);
+
+ out:
+ /* Don't leave errors in the queue */
+ ERR_pop_to_mark();
+ sc->statem.ignore_fatal = 0;
+ if (method != NULL && !SSL_set_ssl_method(ssl, method))
+ ret = 0;
+ 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->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
+}
diff --git a/ssl/ssl_conf.c b/ssl/ssl_conf.c
index e1a4bda9ed..bebfc501a9 100644
--- a/ssl/ssl_conf.c
+++ b/ssl/ssl_conf.c
@@ -397,7 +397,9 @@ static int cmd_Options(SSL_CONF_CTX *cctx, const char *value)
SSL_FLAG_TBL_INV("ExtendedMasterSecret", SSL_OP_NO_EXTENDED_MASTER_SECRET),
SSL_FLAG_TBL_INV("CANames", SSL_OP_DISABLE_TLSEXT_CA_NAMES),
SSL_FLAG_TBL("KTLS", SSL_OP_ENABLE_KTLS),
- SSL_FLAG_TBL_CERT("StrictCertCheck", SSL_CERT_FLAG_TLS_STRICT)
+ SSL_FLAG_TBL_CERT("StrictCertCheck", SSL_CERT_FLAG_TLS_STRICT),
+ SSL_FLAG_TBL_INV("TxCertificateCompression", SSL_OP_NO_TX_CERTIFICATE_COMPRESSION),
+ SSL_FLAG_TBL_INV("RxCertificateCompression", SSL_OP_NO_RX_CERTIFICATE_COMPRESSION),
};
if (value == NULL)
return -3;
@@ -707,6 +709,10 @@ static const ssl_conf_cmd_tbl ssl_conf_cmds[] = {
SSL_CONF_CMD_SWITCH("bugs", 0),
SSL_CONF_CMD_SWITCH("no_comp", 0),
SSL_CONF_CMD_SWITCH("comp", 0),
+ SSL_CONF_CMD_SWITCH("no_tx_cert_comp", 0),
+ SSL_CONF_CMD_SWITCH("tx_cert_comp", 0),
+ SSL_CONF_CMD_SWITCH("no_rx_cert_comp", 0),
+ SSL_CONF_CMD_SWITCH("rx_cert_comp", 0),
SSL_CONF_CMD_SWITCH("ecdh_single", SSL_CONF_FLAG_SERVER),
SSL_CONF_CMD_SWITCH("no_ticket", 0),
SSL_CONF_CMD_SWITCH("serverpref", SSL_CONF_FLAG_SERVER),
@@ -787,6 +793,10 @@ static const ssl_switch_tbl ssl_cmd_switches[] = {
{SSL_OP_ALL, 0}, /* bugs */
{SSL_OP_NO_COMPRESSION, 0}, /* no_comp */
{SSL_OP_NO_COMPRESSION, SSL_TFLAG_INV}, /* comp */
+ {SSL_OP_NO_TX_CERTIFICATE_COMPRESSION, 0}, /* no_tx_cert_comp */
+ {SSL_OP_NO_TX_CERTIFICATE_COMPRESSION, SSL_TFLAG_INV}, /* tx_cert_comp */
+ {SSL_OP_NO_RX_CERTIFICATE_COMPRESSION, 0}, /* no_rx_cert_comp */
+ {SSL_OP_NO_RX_CERTIFICATE_COMPRESSION, SSL_TFLAG_INV}, /* rx_cert_comp */
{SSL_OP_SINGLE_ECDH_USE, 0}, /* ecdh_single */
{SSL_OP_NO_TICKET, 0}, /* no_ticket */
{SSL_OP_CIPHER_SERVER_PREFERENCE, 0}, /* serverpref */
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c
index fb825eb371..1a4f441a9f 100644
--- a/ssl/ssl_err.c
+++ b/ssl/ssl_err.c
@@ -26,6 +26,8 @@ static const ERR_STRING_DATA SSL_str_reasons[] = {
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_CHANGE_CIPHER_SPEC),
"bad change cipher spec"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_CIPHER), "bad cipher"},
+ {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_COMPRESSION_ALGORITHM),
+ "bad compression algorithm"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_DATA), "bad data"},
{ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_DATA_RETURNED_BY_CALLBACK),
"bad data returned by callback"},
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index fb43b9b369..186e60f34c 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -615,6 +615,9 @@ int ossl_ssl_connection_reset(SSL *s)
sc->first_packet = 0;
sc->key_update = SSL_KEY_UPDATE_NONE;
+ memset(sc->ext.compress_certificate_from_peer, 0,
+ sizeof(sc->ext.compress_certificate_from_peer));
+ sc->ext.compress_certificate_sent = 0;
EVP_MD_CTX_free(sc->pha_dgst);
sc->pha_dgst = NULL;
@@ -890,6 +893,10 @@ SSL *ossl_ssl_connection_new(SSL_CTX *ctx)
s->job = NULL;
+#ifndef OPENSSL_NO_COMP_ALG
+ memcpy(s->cert_comp_prefs, ctx->cert_comp_prefs, sizeof(s->cert_comp_prefs));
+#endif
+
#ifndef OPENSSL_NO_CT
if (!SSL_set_ct_validation_callback(ssl, ctx->ct_validation_callback,
ctx->ct_validation_callback_arg))
@@ -3658,6 +3665,9 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
const SSL_METHOD *meth)
{
SSL_CTX *ret = NULL;
+#ifndef OPENSSL_NO_COMP_ALG
+ int i;
+#endif
if (meth == NULL) {
ERR_raise(ERR_LIB_SSL, SSL_R_NULL_SSL_METHOD_PASSED);
@@ -3832,6 +3842,21 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
}
# endif
#endif
+
+#ifndef OPENSSL_NO_COMP_ALG
+ /*
+ * Set the default order: brotli, zlib, zstd
+ * Including only those enabled algorithms
+ */
+ memset(ret->cert_comp_prefs, 0, sizeof(ret->cert_comp_prefs));
+ i = 0;
+ if (ossl_comp_has_alg(TLSEXT_comp_cert_brotli))
+ ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_brotli;
+ if (ossl_comp_has_alg(TLSEXT_comp_cert_zlib))
+ ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_zlib;
+ if (ossl_comp_has_alg(TLSEXT_comp_cert_zstd))
+ ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_zstd;
+#endif
/*
* Disable compression by default to prevent CRIME. Applications can
* re-enable compression by configuring
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 75614c9fc9..2580064ebd 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -19,8 +19,8 @@
# include "internal/common.h" /* for HAS_PREFIX */
# include <openssl/buffer.h>
-# include <openssl/comp.h>
# include <openssl/bio.h>
+# include <openssl/comp.h>
# include <openssl/dsa.h>
# include <openssl/err.h>
# include <openssl/ssl.h>
@@ -771,6 +771,7 @@ typedef enum tlsext_index_en {
TLSEXT_IDX_key_share,
TLSEXT_IDX_cookie,
TLSEXT_IDX_cryptopro_bug,
+ TLSEXT_IDX_compress_certificate,
TLSEXT_IDX_early_data,
TLSEXT_IDX_certificate_authorities,
TLSEXT_IDX_padding,
@@ -1212,6 +1213,11 @@ struct ssl_ctx_st {
uint32_t disabled_mac_mask;
uint32_t disabled_mkey_mask;
uint32_t disabled_auth_mask;
+
+#ifndef OPENSSL_NO_COMP_ALG
+ /* certificate compression preferences */
+ int cert_comp_prefs[TLSEXT_comp_cert_limit];
+#endif
};
typedef struct cert_pkey_st CERT_PKEY;
@@ -1699,6 +1705,11 @@ struct ssl_connection_st {
* selected.
*/
int tick_identity;
+
+ /* This is the list of algorithms the peer supports that we also support */
+ int compress_certificate_from_peer[TLSEXT_comp_cert_limit];
+ /* indicate that we sent the extension, so we'll accept it */
+ int compress_certificate_sent;
} ext;
/*
@@ -1814,6 +1825,11 @@ struct ssl_connection_st {
*/
const struct sigalg_lookup_st **shared_sigalgs;
size_t shared_sigalgslen;
+
+#ifndef OPENSSL_NO_COMP_ALG
+ /* certificate compression preferences */
+ int cert_comp_prefs[TLSEXT_comp_cert_limit];
+#endif
};
# define SSL_CONNECTION_FROM_SSL_ONLY_int(ssl, c) \
@@ -1986,6 +2002,21 @@ typedef struct dtls1_state_st {
# define EXPLICIT_CHAR2_CURVE_TYPE 2
# define NAMED_CURVE_TYPE 3
+# ifndef OPENSSL_NO_COMP_ALG
+struct ossl_comp_cert_st {
+ unsigned char *data;
+ size_t len;
+ size_t orig_len;
+ CRYPTO_REF_COUNT references;
+ CRYPTO_RWLOCK *lock;
+ int alg;
+};
+typedef struct ossl_comp_cert_st OSSL_COMP_CERT;
+
+void OSSL_COMP_CERT_free(OSSL_COMP_CERT *c);
+int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *c);
+# endif
+
struct cert_pkey_st {
X509 *x509;
EVP_PKEY *privatekey;
@@ -2000,6 +2031,11 @@ struct cert_pkey_st {
*/
unsigned char *serverinfo;
size_t serverinfo_length;
+# ifndef OPENSSL_NO_COMP_ALG
+ /* Compressed certificate data - index 0 is unused */
+ OSSL_COMP_CERT *comp_cert[TLSEXT_comp_cert_limit];
+ int cert_comp_used;
+# endif
};
/* Retrieve Suite B flags */
# define tls1_suiteb(s) (s->cert->cert_flags & SSL_CERT_FLAG_SUITEB_128_LOS)
@@ -2956,4 +2992,7 @@ static ossl_unused ossl_inline void ssl_tsan_counter(const SSL_CTX *ctx,
}
}
+int ossl_comp_has_alg(int a);
+size_t ossl_calculate_comp_expansion(int alg, size_t length);
+
#endif
diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c
index 8854abcbd1..8b93ccd4ac 100644
--- a/ssl/ssl_stat.c
+++ b/ssl/ssl_stat.c
@@ -37,6 +37,8 @@ const char *SSL_state_string_long(const SSL *s)
return "SSLv3/TLS read server hello";
case TLS_ST_CR_CERT:
return "SSLv3/TLS read server certificate";
+ case TLS_ST_CR_COMP_CERT:
+ return "TLSv1.3 read server compressed certificate";
case TLS_ST_CR_KEY_EXCH:
return "SSLv3/TLS read server key exchange";
case TLS_ST_CR_CERT_REQ:
@@ -47,6 +49,8 @@ const char *SSL_state_string_long(const SSL *s)
return "SSLv3/TLS read server done";
case TLS_ST_CW_CERT:
return "SSLv3/TLS write client certificate";
+ case TLS_ST_CW_COMP_CERT:
+ return "TLSv1.3 write client compressed certificate";
case TLS_ST_CW_KEY_EXCH:
return "SSLv3/TLS write client key exchange";
case TLS_ST_CW_CERT_VRFY:
@@ -71,6 +75,8 @@ const char *SSL_state_string_long(const SSL *s)
return "SSLv3/TLS write server hello";
case TLS_ST_SW_CERT:
return "SSLv3/TLS write certificate";
+ case TLS_ST_SW_COMP_CERT:
+ return "TLSv1.3 write server compressed certificate";
case TLS_ST_SW_KEY_EXCH:
return "SSLv3/TLS write key exchange";
case TLS_ST_SW_CERT_REQ:
@@ -81,6 +87,8 @@ const char *SSL_state_string_long(const SSL *s)
return "SSLv3/TLS write server done";
case TLS_ST_SR_CERT:
return "SSLv3/TLS read client certificate";
+ case TLS_ST_SR_COMP_CERT:
+ return "TLSv1.3 read client compressed certificate";
case TLS_ST_SR_KEY_EXCH:
return "SSLv3/TLS read client key exchange";
case TLS_ST_SR_CERT_VRFY:
@@ -150,6 +158,8 @@ const char *SSL_state_string(const SSL *s)
return "TRSH";
case TLS_ST_CR_CERT:
return "TRSC";
+ case TLS_ST_CR_COMP_CERT:
+ return "TRSCC";
case TLS_ST_CR_KEY_EXCH:
return "TRSKE";
case TLS_ST_CR_CERT_REQ:
@@ -158,6 +168,8 @@ const char *SSL_state_string(const SSL *s)
return "TRSD";
case TLS_ST_CW_CERT:
return "TWCC";
+ case TLS_ST_CW_COMP_CERT:
+ return "TWCCC";
case TLS_ST_CW_KEY_EXCH:
return "TWCKE";
case TLS_ST_CW_CERT_VRFY:
@@ -182,6 +194,8 @@ const char *SSL_state_string(const SSL *s)
return "TWSH";
case TLS_ST_SW_CERT:
return "TWSC";
+ case TLS_ST_SW_COMP_CERT:
+ return "TWSCC";
case TLS_ST_SW_KEY_EXCH:
return "TWSKE";
case TLS_ST_SW_CERT_REQ:
@@ -190,6 +204,8 @@ const char *SSL_state_string(const SSL *s)
return "TWSD";
case TLS_ST_SR_CERT:
return "TRCC";
+ case TLS_ST_SR_COMP_CERT:
+ return "TRCCC";
case TLS_ST_SR_KEY_EXCH:
return "TRCKE";
case TLS_ST_SR_CERT_VRFY:
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index 880189d998..2cfc9f2b7d 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -62,6 +62,13 @@ static int final_maxfragmentlen(SSL_CONNECTION *s, unsigned int context,
int sent);
static int init_post_handshake_auth(SSL_CONNECTION *s, unsigned int context);
static int final_psk(SSL_CONNECTION *s, unsigned int context, int sent);
+static int tls_init_compress_certificate(SSL_CONNECTION *sc, unsigned int context);
+static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET *pkt,
+ unsigned int context,
+ X509 *x, size_t chainidx);
+static int tls_parse_compress_certificate(SSL_CONNECTION *sc, PACKET *pkt,
+ unsigned int context,
+ X509 *x, size_t chainidx);
/* Structure to define a built-in extension */
typedef struct extensions_definition_st {
@@ -359,6 +366,15 @@ static const EXTENSION_DEFINITION ext_defs[] = {
NULL, NULL, NULL, tls_construct_stoc_cryptopro_bug, NULL, NULL
},
{
+ TLSEXT_TYPE_compress_certificate,
+ SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST
+ | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY,
+ tls_init_compress_certificate,
+ tls_parse_compress_certificate, tls_parse_compress_certificate,
+ tls_construct_compress_certificate, tls_construct_compress_certificate,
+ NULL
+ },
+ {
TLSEXT_TYPE_early_data,
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS
| SSL_EXT_TLS1_3_NEW_SESSION_TICKET | SSL_EXT_TLS1_3_ONLY,
@@ -1746,3 +1762,112 @@ static int final_psk(SSL_CONNECTION *s, unsigned int context, int sent)
return 1;
}
+
+static int tls_init_compress_certificate(SSL_CONNECTION *sc, unsigned int context)
+{
+ memset(sc->ext.compress_certificate_from_peer, 0,
+ sizeof(sc->ext.compress_certificate_from_peer));
+ return 1;
+}
+
+/* The order these are put into the packet imply a preference order: [brotli, zlib, zstd] */
+static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET *pkt,
+ unsigned int context,
+ X509 *x, size_t chainidx)
+{
+#ifndef OPENSSL_NO_COMP_ALG
+ int i;
+
+ if (!ossl_comp_has_alg(0)