diff options
author | Zoltan Fridrich <zfridric@redhat.com> | 2022-01-12 14:57:42 +0100 |
---|---|---|
committer | Zoltan Fridrich <zfridric@redhat.com> | 2022-03-01 16:32:19 +0100 |
commit | 5943dd3cd4e9b279156195d73af7ee068e709356 (patch) | |
tree | 703729f9dd820ac959e013afd0266b16ab21fb0c /lib | |
parent | dcb3c6ebf9d9b7c62e1e2ef1548ea810ca2ae699 (diff) | |
download | gnutls-5943dd3cd4e9b279156195d73af7ee068e709356.tar.gz |
Add compress_certificate extension (RFC8879)
Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 13 | ||||
-rw-r--r-- | lib/common.mk | 3 | ||||
-rw-r--r-- | lib/compress.c | 219 | ||||
-rw-r--r-- | lib/compress.h | 34 | ||||
-rw-r--r-- | lib/debug.c | 2 | ||||
-rw-r--r-- | lib/ext/Makefile.am | 3 | ||||
-rw-r--r-- | lib/ext/compress_certificate.c | 252 | ||||
-rw-r--r-- | lib/ext/compress_certificate.h | 41 | ||||
-rw-r--r-- | lib/gnutls.pc.in | 2 | ||||
-rw-r--r-- | lib/gnutls_int.h | 5 | ||||
-rw-r--r-- | lib/handshake.c | 3 | ||||
-rw-r--r-- | lib/hello_ext.c | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 13 | ||||
-rw-r--r-- | lib/libgnutls.map | 2 | ||||
-rw-r--r-- | lib/str.c | 97 | ||||
-rw-r--r-- | lib/str.h | 15 | ||||
-rw-r--r-- | lib/tls13/certificate.c | 145 |
17 files changed, 798 insertions, 53 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 35df35ee8d..9357aeaf39 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -44,7 +44,8 @@ AM_CPPFLAGS = \ -I$(srcdir)/x509 \ $(LIBTASN1_CFLAGS) \ $(P11_KIT_CFLAGS) \ - $(TPM2_CFLAGS) + $(TPM2_CFLAGS) \ + $(LIBZSTD_CFLAGS) if !HAVE_LIBUNISTRING SUBDIRS += unistring @@ -121,7 +122,7 @@ if ENABLE_NETTLE SUBDIRS += nettle endif -HFILES = abstract_int.h debug.h cipher.h \ +HFILES = abstract_int.h debug.h compress.h cipher.h \ buffers.h errors.h gnutls_int.h dtls.h \ handshake.h num.h algorithms.h \ dh.h kx.h hash_int.h cipher_int.h \ @@ -158,6 +159,14 @@ libgnutls_la_LIBADD = ../gl/libgnu.la x509/libgnutls_x509.la \ thirdparty_libadd = $(LTLIBZ) $(LTLIBINTL) $(LIBSOCKET) $(LTLIBNSL) \ $(P11_KIT_LIBS) $(LIB_SELECT) $(TSS2_LIBS) $(GNUTLS_LIBS_PRIVATE) +if HAVE_LIBBROTLI +thirdparty_libadd += $(LIBBROTLIENC_LIBS) $(LIBBROTLIDEC_LIBS) +endif + +if HAVE_LIBZSTD +thirdparty_libadd += $(LIBZSTD_LIBS) +endif + if HAVE_LIBIDN2 thirdparty_libadd += $(LIBIDN2_LIBS) endif diff --git a/lib/common.mk b/lib/common.mk index 796fdf30bd..2e0d0752d0 100644 --- a/lib/common.mk +++ b/lib/common.mk @@ -1,5 +1,6 @@ AM_CFLAGS = $(WERROR_CFLAGS) $(WSTACK_CFLAGS) $(WARN_CFLAGS) $(NETTLE_CFLAGS) \ - $(LIBTASN1_CFLAGS) $(LIBIDN2_CFLAGS) $(P11_KIT_CFLAGS) $(CODE_COVERAGE_CFLAGS) + $(LIBTASN1_CFLAGS) $(LIBIDN2_CFLAGS) $(P11_KIT_CFLAGS) $(LIBZSTD_CFLAGS) \ + $(CODE_COVERAGE_CFLAGS) COMMON_LINK_FLAGS = $(CODE_COVERAGE_LDFLAGS) V_GPERF = $(V_GPERF_@AM_V@) diff --git a/lib/compress.c b/lib/compress.c index f9e4bdbd70..dbcf4fa168 100644 --- a/lib/compress.c +++ b/lib/compress.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2017-2022 Red Hat, Inc. * * Author: Nikos Mavrogiannopoulos * @@ -20,10 +20,53 @@ * */ -#include "gnutls_int.h" -#include "c-strcase.h" +#include "compress.h" -/* Compatibility compression functions */ +#ifdef HAVE_LIBZ +#include <zlib.h> +#endif + +#ifdef HAVE_LIBBROTLI +#include <brotli/decode.h> +#include <brotli/encode.h> +#endif + +#ifdef HAVE_LIBZSTD +#include <zstd.h> +#endif + +typedef struct { + gnutls_compression_method_t id; + const char *name; +} comp_entry; + +static const comp_entry comp_algs[] = { + { GNUTLS_COMP_NULL, "NULL" }, +#ifdef HAVE_LIBZ + { GNUTLS_COMP_ZLIB, "ZLIB" }, +#endif +#ifdef HAVE_LIBBROTLI + { GNUTLS_COMP_BROTLI, "BROTLI" }, +#endif +#ifdef HAVE_LIBZSTD + { GNUTLS_COMP_ZSTD, "ZSTD" }, +#endif + { GNUTLS_COMP_UNKNOWN, NULL } +}; + +static const gnutls_compression_method_t alg_list[] = { + GNUTLS_COMP_NULL, +#ifdef HAVE_LIBZ + GNUTLS_COMP_ZLIB, +#endif +#ifdef HAVE_LIBBROTLI + GNUTLS_COMP_BROTLI, +#endif +#ifdef HAVE_LIBZSTD + GNUTLS_COMP_ZSTD, +#endif + 0 +}; /** * gnutls_compression_get_name: @@ -34,11 +77,14 @@ * Returns: a pointer to a string that contains the name of the * specified compression algorithm, or %NULL. **/ -const char *gnutls_compression_get_name(gnutls_compression_method_t - algorithm) +const char * +gnutls_compression_get_name(gnutls_compression_method_t algorithm) { - if (algorithm == GNUTLS_COMP_NULL) - return "NULL"; + const comp_entry *p; + + for (p = comp_algs; p->name; ++p) + if (p->id == algorithm) + return p->name; return NULL; } @@ -52,10 +98,14 @@ const char *gnutls_compression_get_name(gnutls_compression_method_t * Returns: an id of the specified in a string compression method, or * %GNUTLS_COMP_UNKNOWN on error. **/ -gnutls_compression_method_t gnutls_compression_get_id(const char *name) +gnutls_compression_method_t +gnutls_compression_get_id(const char *name) { - if (c_strcasecmp(name, "NULL") == 0) - return GNUTLS_COMP_NULL; + const comp_entry *p; + + for (p = comp_algs; p->name; ++p) + if (!strcasecmp(p->name, name)) + return p->id; return GNUTLS_COMP_UNKNOWN; } @@ -68,8 +118,149 @@ gnutls_compression_method_t gnutls_compression_get_id(const char *name) * Returns: a zero-terminated list of #gnutls_compression_method_t * integers indicating the available compression methods. **/ -const gnutls_compression_method_t *gnutls_compression_list(void) +const gnutls_compression_method_t * +gnutls_compression_list(void) +{ + return alg_list; +} + + +/*************************/ +/* Compression functions */ +/*************************/ + + +size_t +_gnutls_compress_bound(gnutls_compression_method_t alg, size_t src_len) +{ + switch (alg) { +#ifdef HAVE_LIBZ + case GNUTLS_COMP_ZLIB: + return compressBound(src_len); +#endif +#ifdef HAVE_LIBBROTLI + case GNUTLS_COMP_BROTLI: + return BrotliEncoderMaxCompressedSize(src_len); +#endif +#ifdef HAVE_LIBZSTD + case GNUTLS_COMP_ZSTD: + return ZSTD_compressBound(src_len); +#endif + default: + return 0; + } + return 0; +} + +int +_gnutls_compress(gnutls_compression_method_t alg, + uint8_t * dst, size_t dst_len, + const uint8_t * src, size_t src_len) { - static const gnutls_compression_method_t list[2] = {GNUTLS_COMP_NULL, 0}; - return list; + int ret = GNUTLS_E_COMPRESSION_FAILED; + + switch (alg) { +#ifdef HAVE_LIBZ + case GNUTLS_COMP_ZLIB: + { + int err; + uLongf comp_len = dst_len; + + err = compress(dst, &comp_len, src, src_len); + if (err != Z_OK) + return gnutls_assert_val(GNUTLS_E_COMPRESSION_FAILED); + ret = comp_len; + } + break; +#endif +#ifdef HAVE_LIBBROTLI + case GNUTLS_COMP_BROTLI: + { + BROTLI_BOOL err; + size_t comp_len = dst_len; + + err = BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, + BROTLI_DEFAULT_WINDOW, + BROTLI_DEFAULT_MODE, + src_len, src, &comp_len, dst); + if (!err) + return gnutls_assert_val(GNUTLS_E_COMPRESSION_FAILED); + ret = comp_len; + } + break; +#endif +#ifdef HAVE_LIBZSTD + case GNUTLS_COMP_ZSTD: + { + size_t comp_len; + + comp_len = ZSTD_compress(dst, dst_len, src, src_len, ZSTD_CLEVEL_DEFAULT); + if (ZSTD_isError(comp_len)) + return gnutls_assert_val(GNUTLS_E_COMPRESSION_FAILED); + ret = comp_len; + } + break; +#endif + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + +#ifdef COMPRESSION_DEBUG + _gnutls_debug_log("Compression ratio: %f\n", (float)((float)ret / (float)src_len)); +#endif + + return ret; +} + +int +_gnutls_decompress(gnutls_compression_method_t alg, + uint8_t * dst, size_t dst_len, + const uint8_t * src, size_t src_len) +{ + int ret = GNUTLS_E_DECOMPRESSION_FAILED; + + switch (alg) { +#ifdef HAVE_LIBZ + case GNUTLS_COMP_ZLIB: + { + int err; + uLongf plain_len = dst_len; + + err = uncompress(dst, &plain_len, src, src_len); + if (err != Z_OK) + return gnutls_assert_val(GNUTLS_E_DECOMPRESSION_FAILED); + ret = plain_len; + } + break; +#endif +#ifdef HAVE_LIBBROTLI + case GNUTLS_COMP_BROTLI: + { + BrotliDecoderResult err; + size_t plain_len = dst_len; + + err = BrotliDecoderDecompress(src_len, src, &plain_len, dst); + if (err != BROTLI_DECODER_RESULT_SUCCESS) + return gnutls_assert_val(GNUTLS_E_DECOMPRESSION_FAILED); + ret = plain_len; + } + break; +#endif +#ifdef HAVE_LIBZSTD + case GNUTLS_COMP_ZSTD: + { + size_t plain_len; + + plain_len = ZSTD_decompress(dst, dst_len, src, src_len); + if (ZSTD_isError(plain_len)) + return gnutls_assert_val(GNUTLS_E_DECOMPRESSION_FAILED); + ret = plain_len; + } + break; +#endif + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + return ret; } diff --git a/lib/compress.h b/lib/compress.h new file mode 100644 index 0000000000..3221f7d67a --- /dev/null +++ b/lib/compress.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +#ifndef GNUTLS_LIB_COMPRESS_H +#define GNUTLS_LIB_COMPRESS_H + +#include "gnutls_int.h" + +size_t _gnutls_compress_bound(gnutls_compression_method_t alg, size_t src_len); +int _gnutls_compress(gnutls_compression_method_t alg, uint8_t * dst, size_t dst_len, + const uint8_t * src, size_t src_len); +int _gnutls_decompress(gnutls_compression_method_t alg, uint8_t * dst, size_t dst_len, + const uint8_t * src, size_t src_len); + +#endif /* GNUTLS_LIB_COMPRESS_H */ diff --git a/lib/debug.c b/lib/debug.c index 166dd61cad..991f7a7bed 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -118,6 +118,8 @@ const char return "FINISHED"; case GNUTLS_HANDSHAKE_KEY_UPDATE: return "KEY_UPDATE"; + case GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT: + return "COMPRESSED CERTIFICATE"; case GNUTLS_HANDSHAKE_SUPPLEMENTAL: return "SUPPLEMENTAL"; case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: diff --git a/lib/ext/Makefile.am b/lib/ext/Makefile.am index 4e010ee9a0..81efdbc55a 100644 --- a/lib/ext/Makefile.am +++ b/lib/ext/Makefile.am @@ -51,7 +51,8 @@ libgnutls_ext_la_SOURCES = max_record.c \ record_size_limit.c record_size_limit.h \ client_cert_type.c client_cert_type.h \ server_cert_type.c server_cert_type.h \ - cert_types.h + cert_types.h \ + compress_certificate.c compress_certificate.h if ENABLE_ALPN libgnutls_ext_la_SOURCES += alpn.c alpn.h diff --git a/lib/ext/compress_certificate.c b/lib/ext/compress_certificate.c new file mode 100644 index 0000000000..8144368faa --- /dev/null +++ b/lib/ext/compress_certificate.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "errors.h" +#include "gnutls_int.h" +#include "hello_ext_lib.h" +#include "num.h" +#include <ext/compress_certificate.h> + +/* Check whether certificate compression method is valid, ie. supported by gnutls */ +static inline int +is_valid_method(gnutls_compression_method_t method) +{ + switch (method) { +#ifdef HAVE_LIBZ + case GNUTLS_COMP_ZLIB: + return 1; +#endif +#ifdef HAVE_LIBBROTLI + case GNUTLS_COMP_BROTLI: + return 1; +#endif +#ifdef HAVE_LIBZSTD + case GNUTLS_COMP_ZSTD: + return 1; +#endif + default: + return 0; + } +} + +/* Converts compression algorithm number established in RFC8879 to internal compression method type */ +gnutls_compression_method_t +_gnutls_compress_certificate_num2method(uint16_t num) +{ + switch (num) { + case 1: + return GNUTLS_COMP_ZLIB; + case 2: + return GNUTLS_COMP_BROTLI; + case 3: + return GNUTLS_COMP_ZSTD; + default: + return GNUTLS_COMP_UNKNOWN; + } +} + +/* Converts compression method type to compression algorithm number established in RFC8879 */ +int +_gnutls_compress_certificate_method2num(gnutls_compression_method_t method) +{ + switch (method) { + case GNUTLS_COMP_ZLIB: + return 1; + case GNUTLS_COMP_BROTLI: + return 2; + case GNUTLS_COMP_ZSTD: + return 3; + default: + return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + } +} + +/** + * gnutls_compress_certificate_get_selected_method: + * @session: is a #gnutls_session_t type. + * + * This function returns the certificate compression method that has been + * selected to compress the certificate before sending it to the peer. + * The selection is done based on the local list of supported compression + * methods and the peer's requested compression methods. + * + * Returns: selected certificate compression method. + * + * Since 3.7.4 + **/ +gnutls_compression_method_t +gnutls_compress_certificate_get_selected_method(gnutls_session_t session) +{ + return session->internals.compress_certificate_method; +} + +/** + * gnutls_compress_certificate_set_methods: + * @session: is a #gnutls_session_t type. + * @methods: is a list of supported compression methods. + * @methods_len: number of compression methods in @methods + * + * This function sets the supported compression methods for certificate compression + * for the given session. The list of supported compression methods will be used + * for a) requesting the compression of peer's certificate and b) selecting the + * method to compress the local certificate before sending it to the peer. + * The order of compression methods inside the list does matter as the method + * that appears earlier in the list will be preffered before the later ones. + * Note that even if you set the list of supported compression methods, the + * compression might not be used if the peer does not support any of your chosen + * compression methods. + * + * The list of supported compression methods must meet the following criteria: + * Argument @methods must be an array of valid compression methods of type + * #gnutls_compression_method_t. Argument @methods_len must contain the number of + * compression methods stored in the @methods array and must be within range <1, 127>. + * The length constraints are defined by %MIN_COMPRESS_CERTIFICATE_METHODS + * and %MAX_COMPRESS_CERTIFICATE_METHODS macros located in the header file + * compress_certificate.h. + * + * If either @methods or @methods_len is equal to 0, current list of supported + * compression methods will be unset. + * + * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. + * + * Since 3.7.4 + **/ +int +gnutls_compress_certificate_set_methods(gnutls_session_t session, + const gnutls_compression_method_t * methods, + size_t methods_len) +{ + unsigned i; + compress_certificate_ext_st *priv; + + if (methods == NULL || methods_len == 0) { + _gnutls_hello_ext_unset_priv(session, GNUTLS_EXTENSION_COMPRESS_CERTIFICATE); + return 0; + } + + if (methods_len > MAX_COMPRESS_CERTIFICATE_METHODS) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + for (i = 0; i < methods_len; ++i) + if (!is_valid_method(methods[i])) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + priv = gnutls_malloc(sizeof(*priv)); + if (priv == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + priv->methods_len = methods_len; + memcpy(priv->methods, methods, methods_len * sizeof(*methods)); + _gnutls_hello_ext_set_priv(session, GNUTLS_EXTENSION_COMPRESS_CERTIFICATE, priv); + + return 0; +} + +static int +_gnutls_compress_certificate_recv_params(gnutls_session_t session, + const uint8_t * data, + size_t data_size) +{ + int ret; + unsigned i, j; + uint16_t num; + uint8_t bytes_len; + size_t methods_len; + gnutls_compression_method_t methods[MAX_COMPRESS_CERTIFICATE_METHODS]; + gnutls_compression_method_t method = GNUTLS_COMP_UNKNOWN; + compress_certificate_ext_st *priv; + gnutls_ext_priv_data_t epriv; + + ret = _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_COMPRESS_CERTIFICATE, &epriv); + if (ret < 0) + return 0; + priv = epriv; + + DECR_LEN(data_size, 1); + bytes_len = *data; + + if (bytes_len < 2 || bytes_len > 254 || bytes_len % 2 == 1) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + DECR_LEN(data_size, bytes_len); + methods_len = bytes_len / 2; + + for (i = 0; i < methods_len; ++i) { + num = _gnutls_read_uint16(data + i + i + 1); + methods[i] = _gnutls_compress_certificate_num2method(num); + if (methods[i] == GNUTLS_COMP_UNKNOWN) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + for (i = 0; i < methods_len; ++i) + for (j = 0; j < priv->methods_len; ++j) + if (methods[i] == priv->methods[j]) { + method = methods[i]; + goto endloop; + } +endloop: + session->internals.compress_certificate_method = method; + + return 0; +} + +static int +_gnutls_compress_certificate_send_params(gnutls_session_t session, + gnutls_buffer_st * data) +{ + int ret, num; + unsigned i; + uint8_t bytes_len; + uint8_t bytes[2 * MAX_COMPRESS_CERTIFICATE_METHODS]; + compress_certificate_ext_st *priv; + gnutls_ext_priv_data_t epriv; + + ret = _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_COMPRESS_CERTIFICATE, &epriv); + if (ret < 0) + return 0; + priv = epriv; + + bytes_len = 2 * priv->methods_len; + for (i = 0; i < priv->methods_len; ++i) { + num = _gnutls_compress_certificate_method2num(priv->methods[i]); + _gnutls_write_uint16(num, bytes + i + i); + } + + ret = _gnutls_buffer_append_data_prefix(data, 8, bytes, bytes_len); + if (ret < 0) + return gnutls_assert_val(ret); + + return bytes_len + 1; +} + +const hello_ext_entry_st ext_mod_compress_certificate = { + .name = "Compress Certificate", + .tls_id = 27, + .gid = GNUTLS_EXTENSION_COMPRESS_CERTIFICATE, + .client_parse_point = GNUTLS_EXT_TLS, + .server_parse_point = GNUTLS_EXT_TLS, + .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | + GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO, + .recv_func = _gnutls_compress_certificate_recv_params, + .send_func = _gnutls_compress_certificate_send_params, + .deinit_func = _gnutls_hello_ext_default_deinit +}; diff --git a/lib/ext/compress_certificate.h b/lib/ext/compress_certificate.h new file mode 100644 index 0000000000..88199da187 --- /dev/null +++ b/lib/ext/compress_certificate.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#ifndef GNUTLS_LIB_EXT_COMPRESS_CERTIFICATE_H +#define GNUTLS_LIB_EXT_COMPRESS_CERTIFICATE_H + +#include <hello_ext.h> + +#define MIN_COMPRESS_CERTIFICATE_METHODS 1 +#define MAX_COMPRESS_CERTIFICATE_METHODS 127 + +typedef struct { + gnutls_compression_method_t methods[MAX_COMPRESS_CERTIFICATE_METHODS]; + size_t methods_len; +} compress_certificate_ext_st; + +extern const hello_ext_entry_st ext_mod_compress_certificate; + +gnutls_compression_method_t _gnutls_compress_certificate_num2method(uint16_t num); +int _gnutls_compress_certificate_method2num(gnutls_compression_method_t method); + +#endif /* GNUTLS_LIB_EXT_COMPRESS_CERTIFICATE_H */ diff --git a/lib/gnutls.pc.in b/lib/gnutls.pc.in index 7cdedda5d7..eeb957cb72 100644 --- a/lib/gnutls.pc.in +++ b/lib/gnutls.pc.in @@ -19,6 +19,6 @@ Description: Transport Security Layer implementation for the GNU system URL: https://www.gnutls.org/ Version: @VERSION@ Libs: -L${libdir} -lgnutls -Libs.private: @LIBINTL@ @LIBSOCKET@ @INET_PTON_LIB@ @LIBPTHREAD@ @LIB_SELECT@ @TSS_LIBS@ @GMP_LIBS@ @LIBUNISTRING@ @LIBATOMIC_LIBS@ @GNUTLS_LIBS_PRIVATE@ +Libs.private: @LIBZ_PC@ @LIBINTL@ @LIBSOCKET@ @INET_PTON_LIB@ @LIBPTHREAD@ @LIB_SELECT@ @TSS_LIBS@ @GMP_LIBS@ @LIBUNISTRING@ @LIBATOMIC_LIBS@ @GNUTLS_LIBS_PRIVATE@ @GNUTLS_REQUIRES_PRIVATE@ Cflags: -I${includedir} diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index c8d52475c7..26d2373c80 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -333,6 +333,7 @@ typedef enum extensions_t { GNUTLS_EXTENSION_PSK_KE_MODES, GNUTLS_EXTENSION_RECORD_SIZE_LIMIT, GNUTLS_EXTENSION_MAX_RECORD_SIZE, + GNUTLS_EXTENSION_COMPRESS_CERTIFICATE, /* * pre_shared_key and dumbfw must always be the last extensions, * in that order */ @@ -1497,6 +1498,10 @@ typedef struct { /* indicates whether or not was KTLS initialized properly. */ int ktls_enabled; + + /* Compression method for certificate compression */ + gnutls_compression_method_t compress_certificate_method; + /* If you add anything here, check _gnutls_handshake_internal_state_clear(). */ } internals_st; diff --git a/lib/handshake.c b/lib/handshake.c index 82c895bfde..44c4cc3402 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -1409,6 +1409,7 @@ _gnutls_send_handshake2(gnutls_session_t session, mbuffer_st * bufel, case GNUTLS_HANDSHAKE_ENCRYPTED_EXTENSIONS: /* followed by finished or cert */ case GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST: /* followed by certificate */ case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: /* this one is followed by cert verify */ + case GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT: /* as above */ case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: /* followed by finished */ ret = 0; /* cache */ break; @@ -1423,6 +1424,7 @@ _gnutls_send_handshake2(gnutls_session_t session, mbuffer_st * bufel, case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: /* this one is followed by ServerHelloDone * or ClientKeyExchange always. */ + case GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT: /* as above */ case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: case GNUTLS_HANDSHAKE_SERVER_KEY_EXCHANGE: /* as above */ case GNUTLS_HANDSHAKE_SERVER_HELLO: /* as above */ @@ -1726,6 +1728,7 @@ _gnutls_recv_handshake(gnutls_session_t session, } break; case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: + case GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT: case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: case GNUTLS_HANDSHAKE_FINISHED: case GNUTLS_HANDSHAKE_ENCRYPTED_EXTENSIONS: diff --git a/lib/hello_ext.c b/lib/hello_ext.c index bb63623efb..779638bb83 100644 --- a/lib/hello_ext.c +++ b/lib/hello_ext.c @@ -57,6 +57,7 @@ #include <num.h> #include <ext/client_cert_type.h> #include <ext/server_cert_type.h> +#include <ext/compress_certificate.h> #include "intprops.h" static void @@ -98,6 +99,7 @@ static hello_ext_entry_st const *extfunc[MAX_EXT_TYPES+1] = { [GNUTLS_EXTENSION_RECORD_SIZE_LIMIT] = &ext_mod_record_size_limit, [GNUTLS_EXTENSION_MAX_RECORD_SIZE] = &ext_mod_max_record_size, [GNUTLS_EXTENSION_PSK_KE_MODES] = &ext_mod_psk_ke_modes, + [GNUTLS_EXTENSION_COMPRESS_CERTIFICATE] = &ext_mod_compress_certificate, [GNUTLS_EXTENSION_PRE_SHARED_KEY] = &ext_mod_pre_shared_key, /* This must be the last extension registered. */ diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 5458ea7ce7..b3c502fc77 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -425,7 +425,9 @@ typedef enum { GNUTLS_COMP_UNKNOWN = 0, GNUTLS_COMP_NULL = 1, GNUTLS_COMP_DEFLATE = 2, - GNUTLS_COMP_ZLIB = GNUTLS_COMP_DEFLATE + GNUTLS_COMP_ZLIB = GNUTLS_COMP_DEFLATE, + GNUTLS_COMP_BROTLI = 3, + GNUTLS_COMP_ZSTD = 4 } gnutls_compression_method_t; @@ -640,6 +642,7 @@ typedef enum { * @GNUTLS_HANDSHAKE_FINISHED: Finished. * @GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: Certificate status (OCSP). * @GNUTLS_HANDSHAKE_KEY_UPDATE: TLS1.3 key update message. + * @GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT: Compressed certificate packet. * @GNUTLS_HANDSHAKE_SUPPLEMENTAL: Supplemental. * @GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC: Change Cipher Spec. * @GNUTLS_HANDSHAKE_CLIENT_HELLO_V2: SSLv2 Client Hello. @@ -665,6 +668,7 @@ typedef enum { GNUTLS_HANDSHAKE_CERTIFICATE_STATUS = 22, GNUTLS_HANDSHAKE_SUPPLEMENTAL = 23, GNUTLS_HANDSHAKE_KEY_UPDATE = 24, + GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT = 25, GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC = 254, GNUTLS_HANDSHAKE_CLIENT_HELLO_V2 = 1024, GNUTLS_HANDSHAKE_HELLO_RETRY_REQUEST = 1025, @@ -1726,6 +1730,13 @@ int gnutls_srtp_set_mki(gnutls_session_t session, const gnutls_datum_t * mki); int gnutls_srtp_get_mki(gnutls_session_t session, gnutls_datum_t * mki); +/* COMPRESS_CERTIFICATE extension, RFC8879 */ +gnutls_compression_method_t +gnutls_compress_certificate_get_selected_method(gnutls_session_t session); +int gnutls_compress_certificate_set_methods(gnutls_session_t session, + const gnutls_compression_method_t * methods, + size_t methods_len); + /* ALPN TLS extension */ /** diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 27be1284f4..d16178580d 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1384,6 +1384,8 @@ GNUTLS_3_7_4 { global: gnutls_ciphersuite_get; + gnutls_compress_certificate_get_selected_method; + gnutls_compress_certificate_set_methods; gnutls_record_send_file; local: *; @@ -781,50 +781,47 @@ _gnutls_buffer_append_prefix(gnutls_buffer_st * buf, int pfx_size, return _gnutls_buffer_append_data(buf, ss, pfx_size); } -/* Reads an uint32 number from the buffer. If check is non zero it will also check whether - * the number read, is less than the data in the buffer - */ -int -_gnutls_buffer_pop_prefix32(gnutls_buffer_st * buf, size_t * data_size, - int check) +int _gnutls_buffer_pop_prefix8(gnutls_buffer_st *buf, uint8_t *data, int check) { - size_t size; - - if (buf->length < 4) { + if (buf->length < 1) { gnutls_assert(); return GNUTLS_E_PARSING_ERROR; } - size = _gnutls_read_uint32(buf->data); - if (check && size > buf->length - 4) { + *data = buf->data[0]; + + if (check && *data > buf->length - 1) { gnutls_assert(); return GNUTLS_E_PARSING_ERROR; } - buf->data += 4; - buf->length -= 4; - - *data_size = size; + buf->data++; + buf->length--; return 0; } -int _gnutls_buffer_pop_prefix8(gnutls_buffer_st *buf, uint8_t *data, int check) +int +_gnutls_buffer_pop_prefix16(gnutls_buffer_st * buf, size_t * data_size, + int check) { - if (buf->length < 1) { + size_t size; + + if (buf->length < 2) { gnutls_assert(); return GNUTLS_E_PARSING_ERROR; } - *data = buf->data[0]; - - if (check && *data > buf->length - 1) { + size = _gnutls_read_uint16(buf->data); + if (check && size > buf->length - 2) { gnutls_assert(); return GNUTLS_E_PARSING_ERROR; } - buf->data++; - buf->length--; + buf->data += 2; + buf->length -= 2; + + *data_size = size; return 0; } @@ -854,6 +851,34 @@ _gnutls_buffer_pop_prefix24(gnutls_buffer_st * buf, size_t * data_size, return 0; } +/* Reads an uint32 number from the buffer. If check is non zero it will also check whether + * the number read, is less than the data in the buffer + */ +int +_gnutls_buffer_pop_prefix32(gnutls_buffer_st * buf, size_t * data_size, + int check) +{ + size_t size; + + if (buf->length < 4) { + gnutls_assert(); + return GNUTLS_E_PARSING_ERROR; + } + + size = _gnutls_read_uint32(buf->data); + if (check && size > buf->length - 4) { + gnutls_assert(); + return GNUTLS_E_PARSING_ERROR; + } + + buf->data += 4; + buf->length -= 4; + + *data_size = size; + + return 0; +} + int _gnutls_buffer_pop_datum_prefix32(gnutls_buffer_st * buf, gnutls_datum_t * data) @@ -883,6 +908,34 @@ _gnutls_buffer_pop_datum_prefix32(gnutls_buffer_st * buf, } int +_gnutls_buffer_pop_datum_prefix24(gnutls_buffer_st * buf, + gnutls_datum_t * data) +{ + size_t size; + int ret; + + ret = _gnutls_buffer_pop_prefix24(buf, &size, 1); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + if (size > 0) { + size_t osize = size; + _gnutls_buffer_pop_datum(buf, data, size); + if (osize != data->size) { + gnutls_assert(); + return GNUTLS_E_PARSING_ERROR; + } + } else { + data->size = 0; + data->data = NULL; + } + + return 0; +} + +int _gnutls_buffer_pop_datum_prefix16(gnutls_buffer_st * buf, gnutls_datum_t * data) { @@ -123,19 +123,20 @@ int _gnutls_buffer_pop_data(gnutls_buffer_st *, void *, size_t size); void _gnutls_buffer_pop_datum(gnutls_buffer_st *, gnutls_datum_t *, size_t max_size); -int _gnutls_buffer_pop_prefix8(gnutls_buffer_st *, uint8_t *, int check); - /* 32-bit prefix */ -int _gnutls_buffer_pop_prefix32(gnutls_buffer_st * buf, size_t * data_size, - int check); - -int _gnutls_buffer_pop_prefix24(gnutls_buffer_st * buf, size_t * data_size, - int check); +int _gnutls_buffer_pop_prefix32(gnutls_buffer_st * buf, size_t * data_size, int check); +int _gnutls_buffer_pop_prefix24(gnutls_buffer_st * buf, size_t * data_size, int check); +int _gnutls_buffer_pop_prefix16(gnutls_buffer_st * buf, size_t * data_size, int check); +int _gnutls_buffer_pop_prefix8(gnutls_buffer_st *, uint8_t *, int check); /* 32-bit prefix */ int _gnutls_buffer_pop_datum_prefix32(gnutls_buffer_st * buf, gnutls_datum_t * data); +/* 24-bit prefix */ +int _gnutls_buffer_pop_datum_prefix24(gnutls_buffer_st * buf, + gnutls_datum_t * data); + /* 16-bit prefix */ int _gnutls_buffer_pop_datum_prefix16(gnutls_buffer_st * buf, gnutls_datum_t * data); diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c index 7483251a53..979262930e 100644 --- a/lib/tls13/certificate.c +++ b/lib/tls13/certificate.c @@ -21,20 +21,25 @@ */ #include "gnutls_int.h" +#include "compress.h" #include "errors.h" #include "extv.h" #include "handshake.h" #include "tls13/certificate.h" #include "auth/cert.h" #include "mbuffers.h" +#include "ext/compress_certificate.h" #include "ext/status_request.h" static int parse_cert_extension(void *ctx, unsigned tls_id, const uint8_t *data, unsigned data_size); static int parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size); +static int compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark, + gnutls_compression_method_t comp_method); +static int decompress_certificate(gnutls_buffer_st * buf); int _gnutls13_recv_certificate(gnutls_session_t session) { - int ret; + int ret, err, decompress_cert = 0; gnutls_buffer_st buf; unsigned optional = 0; @@ -52,6 +57,14 @@ int _gnutls13_recv_certificate(gnutls_session_t session) } ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_PKT, 0, &buf); + if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { + /* check if we received compressed certificate */ + err = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, 0, &buf); + if (err >= 0) { + decompress_cert = 1; + ret = err; + } + } if (ret < 0) { if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET && session->internals.send_cert_req) return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND); @@ -65,6 +78,15 @@ int _gnutls13_recv_certificate(gnutls_session_t session) goto cleanup; } + if (decompress_cert) { + ret = decompress_certificate(&buf); + if (ret < 0) { + gnutls_assert(); + gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + goto cleanup; + } + } + if (session->internals.initial_negotiation_completed && session->internals.post_handshake_cr_context.size > 0) { gnutls_datum_t context; @@ -195,16 +217,23 @@ int append_status_request(void *_ctx, gnutls_buffer_st *buf) int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) { - int ret; + int ret, compress_cert; gnutls_pcert_st *apr_cert_list = NULL; gnutls_privkey_t apr_pkey = NULL; int apr_cert_list_length = 0; mbuffer_st *bufel = NULL; gnutls_buffer_st buf; - unsigned pos_mark, ext_pos_mark; + unsigned pos_mark, ext_pos_mark, cert_pos_mark; unsigned i; struct ocsp_req_ctx_st ctx; gnutls_certificate_credentials_t cred; + gnutls_compression_method_t comp_method; + gnutls_handshake_description_t h_type; + + comp_method = gnutls_compress_certificate_get_selected_method(session); + compress_cert = comp_method != GNUTLS_COMP_UNKNOWN; + h_type = compress_cert ? GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT + : GNUTLS_HANDSHAKE_CERTIFICATE_PKT; if (again == 0) { if (!session->internals.initial_negotiation_completed && @@ -236,6 +265,8 @@ int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) if (ret < 0) return gnutls_assert_val(ret); + cert_pos_mark = buf.length; + if (session->security_parameters.entity == GNUTLS_CLIENT) { ret = _gnutls_buffer_append_data_prefix(&buf, 8, session->internals.post_handshake_cr_context.data, @@ -312,10 +343,18 @@ int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) _gnutls_write_uint24(buf.length-pos_mark-3, &buf.data[pos_mark]); + if (compress_cert) { + ret = compress_certificate(&buf, cert_pos_mark, comp_method); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } + bufel = _gnutls_buffer_to_mbuffer(&buf); } - return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_CERTIFICATE_PKT); + return _gnutls_send_handshake(session, bufel, h_type); cleanup: _gnutls_buffer_clear(&buf); @@ -523,3 +562,101 @@ parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size) } +static int +compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark, + gnutls_compression_method_t comp_method) +{ + int ret, method_num; + size_t comp_bound; + gnutls_datum_t plain, comp = { NULL, 0 }; + + method_num = _gnutls_compress_certificate_method2num(comp_method); + if (method_num == GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + plain.data = buf->data + cert_pos_mark; + plain.size = buf->length - cert_pos_mark; + + comp_bound = _gnutls_compress_bound(comp_method, plain.size); + if (comp_bound == 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + comp.data = gnutls_malloc(comp_bound); + if (comp.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + ret = _gnutls_compress(comp_method, comp.data, comp_bound, plain.data, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + comp.size = ret; + + buf->length = cert_pos_mark; + ret = _gnutls_buffer_append_prefix(buf, 16, method_num); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + ret = _gnutls_buffer_append_prefix(buf, 24, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + ret = _gnutls_buffer_append_data_prefix(buf, 24, comp.data, comp.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +cleanup: + gnutls_free(comp.data); + return ret; +} + +static int +decompress_certificate(gnutls_buffer_st * buf) +{ + int ret; + size_t method_num, plain_exp_len; + gnutls_datum_t comp, plain = { NULL, 0 }; + gnutls_compression_method_t comp_method; + + ret = _gnutls_buffer_pop_prefix16(buf, &method_num, 0); + if (ret < 0) + return gnutls_assert_val(ret); + comp_method = _gnutls_compress_certificate_num2method(method_num); + + ret = _gnutls_buffer_pop_prefix24(buf, &plain_exp_len, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_pop_datum_prefix24(buf, &comp); + if (ret < 0) + return gnutls_assert_val(ret); + + plain.data = gnutls_malloc(plain_exp_len); + if (plain.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + ret = _gnutls_decompress(comp_method, plain.data, plain_exp_len, comp.data, comp.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + plain.size = ret; + + if (plain.size != plain_exp_len) { + gnutls_assert(); + ret = GNUTLS_E_DECOMPRESSION_FAILED; + goto cleanup; + } + + _gnutls_buffer_clear(buf); + ret = _gnutls_buffer_append_data(buf, plain.data, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +cleanup: + gnutls_free(plain.data); + return ret; +} |