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 | |
parent | dcb3c6ebf9d9b7c62e1e2ef1548ea810ca2ae699 (diff) | |
download | gnutls-5943dd3cd4e9b279156195d73af7ee068e709356.tar.gz |
Add compress_certificate extension (RFC8879)
Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
34 files changed, 1764 insertions, 56 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9020011750..d4cc464c84 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ variables: CHECKJOBS: 16 cache: - key: "$CI_JOB_NAME-ver24" + key: "$CI_JOB_NAME-ver25" paths: - cache/ @@ -5,6 +5,14 @@ Copyright (C) 2000-2016 Free Software Foundation, Inc. Copyright (C) 2013-2019 Nikos Mavrogiannopoulos See the end for copying conditions. +* Version 3.7.4 (unreleased) + +** Added support for certificate compression as defined in RFC8879. + +** API and ABI modifications: +gnutls_compress_certificate_get_selected_method: Added +gnutls_compress_certificate_set_methods: Added + * Version 3.7.3 (released 2022-01-17) ** libgnutls: The allowlisting configuration mode has been added to the system-wide diff --git a/configure.ac b/configure.ac index 53c3aefca1..351cf4593e 100644 --- a/configure.ac +++ b/configure.ac @@ -962,6 +962,83 @@ AC_CHECK_SIZEOF(unsigned long int, 4) AC_CHECK_SIZEOF(unsigned int, 4) AC_CHECK_SIZEOF(time_t, 4) +AC_ARG_WITH(zlib, AS_HELP_STRING([--without-zlib], + [disable zlib compression support]), + ac_zlib=$withval, ac_zlib=yes) +AC_MSG_CHECKING([whether to include zlib compression support]) +if test x$ac_zlib != xno; then + AC_MSG_RESULT(yes) + AC_LIB_HAVE_LINKFLAGS(z,, [#include <zlib.h>], [compress (0, 0, 0, 0);]) + if test x$ac_cv_libz != xyes; then + AC_MSG_WARN( + *** + *** ZLIB was not found. You will not be able to use ZLIB compression.) +fi +else + AC_MSG_RESULT(no) +fi + +PKG_CHECK_EXISTS(zlib, ZLIB_HAS_PKGCONFIG=y, ZLIB_HAS_PKGCONFIG=n) + +if test x$ac_zlib != xno; then + if test "$ZLIB_HAS_PKGCONFIG" = "y" ; then + if test "x$GNUTLS_REQUIRES_PRIVATE" = x; then + GNUTLS_REQUIRES_PRIVATE="Requires.private: zlib" + else + GNUTLS_REQUIRES_PRIVATE="$GNUTLS_REQUIRES_PRIVATE, zlib" + fi + LIBZ_PC="" + else + LIBZ_PC=$LIBZ + fi +fi +AC_SUBST(LIBZ_PC) + +AC_ARG_WITH(libbrotli, + AS_HELP_STRING([--without-brotli], [disable brotli compression support]), + ac_brotli=$withval, ac_brotli=yes) +AC_MSG_CHECKING([whether to include brotli compression support]) +if test x$ac_brotli != xno; then + AC_MSG_RESULT(yes) + PKG_CHECK_MODULES(LIBBROTLIENC, [libbrotlienc >= 1.0.0], [with_libbrotlienc=yes], [with_libbrotlienc=no]) + PKG_CHECK_MODULES(LIBBROTLIDEC, [libbrotlidec >= 1.0.0], [with_libbrotlidec=yes], [with_libbrotlidec=no]) + if test "${with_libbrotlienc}" = "yes" && test "${with_libbrotlidec}" = "yes"; then + AC_DEFINE([HAVE_LIBBROTLI], 1, [Define if BROTLI compression is enabled.]) + if test "x$GNUTLS_REQUIRES_PRIVATE" = "x"; then + GNUTLS_REQUIRES_PRIVATE="Requires.private: libbrotlienc, libbrotlidec" + else + GNUTLS_REQUIRES_PRIVATE="${GNUTLS_REQUIRES_PRIVATE}, libbrotlienc, libbrotlidec" + fi + else + AC_MSG_WARN(*** LIBBROTLI was not found. You will not be able to use BROTLI compression.) + fi +else + AC_MSG_RESULT(no) +fi +AM_CONDITIONAL(HAVE_LIBBROTLI, test "$with_libbrotlienc" != "no" && test "$with_libbrotlidec" != "no") + +AC_ARG_WITH(libzstd, + AS_HELP_STRING([--without-zstd], [disable zstd compression support]), + ac_zstd=$withval, ac_zstd=yes) +AC_MSG_CHECKING([whether to include zstd compression support]) +if test x$ac_zstd != xno; then + AC_MSG_RESULT(yes) + PKG_CHECK_MODULES(LIBZSTD, [libzstd >= 1.3.0], [with_libzstd=yes], [with_libzstd=no]) + if test "${with_libzstd}" = "yes" && test "${has_zstd_h}" = "yes"; then + AC_DEFINE([HAVE_LIBZSTD], 1, [Define if ZSTD compression is enabled.]) + if test "x$GNUTLS_REQUIRES_PRIVATE" = "x"; then + GNUTLS_REQUIRES_PRIVATE="Requires.private: libzstd" + else + GNUTLS_REQUIRES_PRIVATE="${GNUTLS_REQUIRES_PRIVATE}, libzstd" + fi + else + AC_MSG_WARN(*** LIBZSTD was not found. You will not be able to use ZSTD compression.) + fi +else + AC_MSG_RESULT(no) +fi +AM_CONDITIONAL(HAVE_LIBZSTD, test "$with_libzstd" != "no") + # export for use in scripts AC_SUBST(ac_cv_sizeof_time_t) diff --git a/devel/libgnutls.abignore b/devel/libgnutls.abignore index 61512030a1..fd2ee85dbe 100644 --- a/devel/libgnutls.abignore +++ b/devel/libgnutls.abignore @@ -75,3 +75,17 @@ name = gnutls_ciphersuite_get [suppress_function] name = gnutls_record_send_file + +[suppress_function] +name = gnutls_compress_certificate_get_selected_method + +[suppress_function] +name = gnutls_compress_certificate_set_methods + +[suppress_type] +name = gnutls_compression_method_t +changed_enumerators = GNUTLS_COMP_BROTLI, GNUTLS_COMP_ZSTD + +[suppress_type] +name = gnutls_handshake_description_t +changed_enumerators = GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT diff --git a/devel/symbols.last b/devel/symbols.last index 92a7422812..18997b910d 100644 --- a/devel/symbols.last +++ b/devel/symbols.last @@ -150,6 +150,8 @@ gnutls_cipher_suite_get_name@GNUTLS_3_4 gnutls_cipher_suite_info@GNUTLS_3_4 gnutls_cipher_tag@GNUTLS_3_4 gnutls_ciphersuite_get@GNUTLS_3_7_4 +gnutls_compress_certificate_get_selected_method@GNUTLS_3_7_4 +gnutls_compress_certificate_set_methods@GNUTLS_3_7_4 gnutls_compression_get@GNUTLS_3_4 gnutls_compression_get_id@GNUTLS_3_4 gnutls_compression_get_name@GNUTLS_3_4 diff --git a/doc/Makefile.am b/doc/Makefile.am index a89166ff60..9c8e9141fd 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -896,6 +896,10 @@ FUNCS += functions/gnutls_cipher_suite_info FUNCS += functions/gnutls_cipher_suite_info.short FUNCS += functions/gnutls_cipher_tag FUNCS += functions/gnutls_cipher_tag.short +FUNCS += functions/gnutls_compress_certificate_get_selected_method +FUNCS += functions/gnutls_compress_certificate_get_selected_method.short +FUNCS += functions/gnutls_compress_certificate_set_methods +FUNCS += functions/gnutls_compress_certificate_set_methods.short FUNCS += functions/gnutls_compression_get FUNCS += functions/gnutls_compression_get.short FUNCS += functions/gnutls_compression_get_id diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am index e101b6c576..e1686d390f 100644 --- a/doc/manpages/Makefile.am +++ b/doc/manpages/Makefile.am @@ -288,6 +288,8 @@ APIMANS += gnutls_ciphersuite_get.3 APIMANS += gnutls_cipher_suite_get_name.3 APIMANS += gnutls_cipher_suite_info.3 APIMANS += gnutls_cipher_tag.3 +APIMANS += gnutls_compress_certificate_get_selected_method.3 +APIMANS += gnutls_compress_certificate_set_methods.3 APIMANS += gnutls_compression_get.3 APIMANS += gnutls_compression_get_id.3 APIMANS += gnutls_compression_get_name.3 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; +} @@ -820,6 +820,12 @@ gnutls_session_t init_tls_session(const char *host) } } + if (HAVE_OPT(COMPRESS_CERT) && disable_extensions == 0) { + ret = compress_cert_set_methods(session, OPT_ARG(COMPRESS_CERT)); + if (ret < 0) + exit(1); + } + if (HAVE_OPT(HEARTBEAT)) gnutls_heartbeat_enable(session, GNUTLS_HB_PEER_ALLOWED_TO_SEND); diff --git a/src/common.c b/src/common.c index 823a8a83fa..719106c032 100644 --- a/src/common.c +++ b/src/common.c @@ -291,6 +291,58 @@ int cert_verify(gnutls_session_t session, const char *hostname, const char *purp return 1; } +/* Parse input string and set certificate compression methods */ +int compress_cert_set_methods(gnutls_session_t session, const char *string) +{ + int ret = 0, i = 0; + char *s = NULL, *t = NULL, *str = NULL; + size_t methods_len = 0; + gnutls_compression_method_t *methods = NULL; + + if (!string || !*string) + return 0; + + str = strdup(string); + if (!str) { + ret = GNUTLS_E_MEMORY_ERROR; + fprintf(stderr, "Could not set certificate compression methods: %s\n", + gnutls_strerror(ret)); + goto cleanup; + } + + methods_len = 1; + for (s = str; *s; ++s) + if (*s == ',') + ++methods_len; + + methods = gnutls_malloc(methods_len * sizeof(gnutls_compression_method_t)); + if (!methods) { + ret = GNUTLS_E_MEMORY_ERROR; + fprintf(stderr, "Could not set certificate compression methods: %s\n", + gnutls_strerror(ret)); + goto cleanup; + } + + for (s = str, i = 0; (t = strchr(s, ',')); s = t + 1, ++i) { + *t = '\0'; + methods[i] = gnutls_compression_get_id(s); + } + methods[i] = gnutls_compression_get_id(s); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fprintf(stderr, "Could not set certificate compression methods: %s\n", + gnutls_strerror(ret)); + goto cleanup; + } + +cleanup: + free(str); + free(methods); + + return ret; +} + static void print_dh_info(gnutls_session_t session, const char *str, int print) { diff --git a/src/common.h b/src/common.h index 884a355a82..f93187cfee 100644 --- a/src/common.h +++ b/src/common.h @@ -71,6 +71,8 @@ void print_cert_info2(gnutls_session_t, int flag, FILE *fp, int print_cert); void print_list(const char *priorities, int verbose); int cert_verify(gnutls_session_t session, const char *hostname, const char *purpose); +int compress_cert_set_methods(gnutls_session_t session, const char *string); + const char *raw_to_string(const unsigned char *raw, size_t raw_size); const char *raw_to_hex(const unsigned char *raw, size_t raw_size); const char *raw_to_base64(const unsigned char *raw, size_t raw_size); diff --git a/src/gnutls-cli-options.json b/src/gnutls-cli-options.json index bffa18e365..c7b8ef2e01 100644 --- a/src/gnutls-cli-options.json +++ b/src/gnutls-cli-options.json @@ -329,6 +329,12 @@ "max": "NOLIMIT" }, { + "arg-type": "string", + "long-option": "compress-cert", + "desc": "Compress certificate", + "detail": "This option sets a list of supported compression methods for certificate compression. Use comma delimited list of compression methods such as \"zlib,brotli,zstd\"." + }, + { "detail": "", "long-option": "heartbeat", "short-option": "b", diff --git a/src/gnutls-serv-options.json b/src/gnutls-serv-options.json index ef074385b0..3ef51111f9 100644 --- a/src/gnutls-serv-options.json +++ b/src/gnutls-serv-options.json @@ -139,6 +139,12 @@ "long-option": "verify-client-cert" }, { + "arg-type": "string", + "long-option": "compress-cert", + "desc": "Compress certificate", + "detail": "This option sets a list of supported compression methods for certificate compression. Use comma delimited list of compression methods such as \"zlib,brotli,zstd\"." + }, + { "long-option": "heartbeat", "desc": "Activate heartbeat support", "detail": "Regularly ping client via heartbeat extension messages", @@ -329,4 +335,4 @@ } ] } -]
\ No newline at end of file +] diff --git a/src/serv.c b/src/serv.c index 85e94cd5e6..555406b434 100644 --- a/src/serv.c +++ b/src/serv.c @@ -517,6 +517,12 @@ gnutls_session_t initialize_session(int dtls) } } + if (HAVE_OPT(COMPRESS_CERT)) { + ret = compress_cert_set_methods(session, OPT_ARG(COMPRESS_CERT)); + if (ret < 0) + exit(1); + } + if (HAVE_OPT(HEARTBEAT)) gnutls_heartbeat_enable(session, GNUTLS_HB_PEER_ALLOWED_TO_SEND); diff --git a/tests/Makefile.am b/tests/Makefile.am index 402bfa7328..93780d4507 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -121,7 +121,8 @@ ctests = tls13/supported_versions tls13/tls12-no-tls13-exts \ tls12-rollback-detection tls11-rollback-detection \ tls12-check-rollback-val tls11-check-rollback-val \ tls13/post-handshake-with-psk tls13/post-handshake-with-cert-auto \ - tls13/anti_replay + tls13/anti_replay tls13/compress-cert tls13/compress-cert-neg \ + tls13/compress-cert-neg2 ctests += tls13/hello_retry_request diff --git a/tests/tls13/compress-cert-neg.c b/tests/tls13/compress-cert-neg.c new file mode 100644 index 0000000000..5364e88132 --- /dev/null +++ b/tests/tls13/compress-cert-neg.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) || !defined(HAVE_LIBZ) || \ + !defined(HAVE_LIBBROTLI) || !defined(HAVE_LIBZSTD) + +int main(int argc, char **argv) +{ + exit(77); +} + +#else + +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <gnutls/gnutls.h> + +#include "cert-common.h" +#include "utils.h" + +/* This program tests whether the compress_certificate extensions is disabled + * when client and server have incompatible compression methods set */ + +#define PRIO "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3" +#define CHECK(X) assert((X)>=0) + +static pid_t child; +int client_bad; +int server_bad; + +static void terminate(void) +{ + int status = 0; + + if (child) { + kill(child, SIGTERM); + wait(&status); + } + exit(1); +} + +static void client_log_func(int level, const char *str) +{ + fprintf(stderr, "client|<%d>| %s", level, str); +} + +static void server_log_func(int level, const char *str) +{ + fprintf(stderr, "server|<%d>| %s", level, str); +} + +static int client_callback(gnutls_session_t session, unsigned htype, + unsigned post, unsigned incoming, const gnutls_datum_t *msg) +{ + client_bad = 1; + return 0; +} + +static int server_callback(gnutls_session_t session, unsigned htype, + unsigned post, unsigned incoming, const gnutls_datum_t *msg) +{ + server_bad = 1; + return 0; +} + +static void client(int fd) +{ + int ret; + unsigned status; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t method; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_BROTLI, GNUTLS_COMP_ZSTD }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &cli_ca3_cert_chain, + &cli_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_CLIENT)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("client: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, + GNUTLS_HOOK_PRE, client_callback); + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + fail("client: Handshake failed: %s\n", strerror(ret)); + goto cleanup; + } + if (debug) + success("client: Handshake was completed\n"); + if (debug) + success("client: TLS version is: %s\n", + gnutls_protocol_get_name + (gnutls_protocol_get_version(session))); + + method = gnutls_compress_certificate_get_selected_method(session); + if (method != GNUTLS_COMP_UNKNOWN) + fail("client: compression method should should not be set\n"); + + if (client_bad) + fail("client: certificate should not be compressed\n"); + + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) + fail("client: could not verify server certificate: %s\n", gnutls_strerror(ret)); + if (status) + fail("client: certificate verification failed\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + + if (debug) + success("client: finished\n"); + +cleanup: + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +static void server(int fd) +{ + int ret; + unsigned status; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t method; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_ZLIB }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &server_ca3_localhost_cert_chain, + &server_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_SERVER)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("server: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, + GNUTLS_HOOK_PRE, server_callback); + gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUEST); + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + fail("server: Handshake has failed (%s)\n\n", gnutls_strerror(ret)); + goto cleanup; + } + if (debug) + success("server: Handshake was completed\n"); + if (debug) + success("server: TLS version is: %s\n", gnutls_protocol_get_name( + gnutls_protocol_get_version(session))); + + method = gnutls_compress_certificate_get_selected_method(session); + if (method != GNUTLS_COMP_UNKNOWN) + fail("server: compression method should not be set\n"); + + if (server_bad) + fail("server: certificate should not be compressed\n"); + + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) + fail("server: could not verify client certificate: %s\n", gnutls_strerror(ret)); + if (status) + fail("server: certificate verification failed\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + + if (debug) + success("server: finished\n"); + +cleanup: + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +void doit(void) +{ + int fd[2]; + int ret; + + signal(SIGPIPE, SIG_IGN); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + int status = 0; + + server(fd[0]); + wait(&status); + check_wait_status(status); + } else { + close(fd[0]); + client(fd[1]); + exit(0); + } +} + +#endif /* _WIN32 */ diff --git a/tests/tls13/compress-cert-neg2.c b/tests/tls13/compress-cert-neg2.c new file mode 100644 index 0000000000..b083e38427 --- /dev/null +++ b/tests/tls13/compress-cert-neg2.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) || !defined(HAVE_LIBZ) + +int main(int argc, char **argv) +{ + exit(77); +} + +#else + +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <gnutls/gnutls.h> + +#include "cert-common.h" +#include "utils.h" + +/* This program tests whether the compress_certificate extension correctly fails + * in the case of compression/decompression failure */ + +#define PRIO "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3" +#define CHECK(X) assert((X)>=0) + +static pid_t child; + +static void terminate(void) +{ + int status = 0; + + if (child) { + kill(child, SIGTERM); + wait(&status); + } + exit(1); +} + +static void client_log_func(int level, const char *str) +{ + fprintf(stderr, "client|<%d>| %s", level, str); +} + +static void server_log_func(int level, const char *str) +{ + fprintf(stderr, "server|<%d>| %s", level, str); +} + +static int client_callback(gnutls_session_t session, unsigned htype, + unsigned post, unsigned incoming, const gnutls_datum_t *msg) +{ + /* change compression method to BROTLI */ + msg->data[1] = 0x02; + return 0; +} + +static void client(int fd) +{ + int ret; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_ZLIB }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &cli_ca3_cert_chain, + &cli_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_CLIENT)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("client: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, + GNUTLS_HOOK_PRE, client_callback); + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret >= 0) + fail("client: handshake should have failed\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +static void server(int fd) +{ + int ret; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t method; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_ZLIB }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &server_ca3_localhost_cert_chain, + &server_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_SERVER)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("server: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret >= 0) + fail("server: handshake should have failed\n"); + + if (gnutls_alert_get(session) != GNUTLS_A_BAD_CERTIFICATE) + fail("server: didn't receive BAD CERTIFICATE alert\n"); + + method = gnutls_compress_certificate_get_selected_method(session); + if (method != GNUTLS_COMP_ZLIB) + fail("server: compression method should be set to ZLIB\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +void doit(void) +{ + int fd[2]; + int ret; + + signal(SIGPIPE, SIG_IGN); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + int status = 0; + + server(fd[0]); + wait(&status); + check_wait_status(status); + } else { + close(fd[0]); + client(fd[1]); + exit(0); + } +} + +#endif /* _WIN32 */ diff --git a/tests/tls13/compress-cert.c b/tests/tls13/compress-cert.c new file mode 100644 index 0000000000..6b867caaf5 --- /dev/null +++ b/tests/tls13/compress-cert.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Zoltan Fridrich + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) || !defined(HAVE_LIBZ) || \ + !defined(HAVE_LIBBROTLI) || !defined(HAVE_LIBZSTD) + +int main(int argc, char **argv) +{ + exit(77); +} + +#else + +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <gnutls/gnutls.h> + +#include "cert-common.h" +#include "utils.h" + +/* This program tests whether the compress_certificate extensions works as expected */ + +#define PRIO "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3" +#define CHECK(X) assert((X)>=0) + +static pid_t child; +int client_ok; +int server_ok; + +static void terminate(void) +{ + int status = 0; + + if (child) { + kill(child, SIGTERM); + wait(&status); + } + exit(1); +} + +static void client_log_func(int level, const char *str) +{ + fprintf(stderr, "client|<%d>| %s", level, str); +} + +static void server_log_func(int level, const char *str) +{ + fprintf(stderr, "server|<%d>| %s", level, str); +} + +static int client_callback(gnutls_session_t session, unsigned htype, + unsigned post, unsigned incoming, const gnutls_datum_t *msg) +{ + if (incoming == 0) + return 0; + + /* check ZLIB number */ + if (msg->data[0] == 0x00 && msg->data[1] == 0x01) + client_ok = 1; + + return 0; +} + +static int server_callback(gnutls_session_t session, unsigned htype, + unsigned post, unsigned incoming, const gnutls_datum_t *msg) +{ + if (incoming == 0) + return 0; + + /* check BROTLI number */ + if (msg->data[0] == 0x00 && msg->data[1] == 0x02) + server_ok = 1; + + return 0; +} + +static void client(int fd) +{ + int ret; + unsigned status; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t method; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_BROTLI }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &cli_ca3_cert_chain, + &cli_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_CLIENT)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("client: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, + GNUTLS_HOOK_PRE, client_callback); + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + fail("client: Handshake failed: %s\n", strerror(ret)); + goto cleanup; + } + if (debug) + success("client: Handshake was completed\n"); + if (debug) + success("client: TLS version is: %s\n", + gnutls_protocol_get_name + (gnutls_protocol_get_version(session))); + + method = gnutls_compress_certificate_get_selected_method(session); + if (method != GNUTLS_COMP_BROTLI) + fail("client: compression method should be set to BROTLI\n"); + + if (!client_ok) + fail("client: didn't receive cert compressed with ZLIB\n"); + + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) + fail("client: could not verify server certificate: %s\n", gnutls_strerror(ret)); + if (status) + fail("client: certificate verification failed\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + + if (debug) + success("client: finished\n"); + +cleanup: + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +static void server(int fd) +{ + int ret; + unsigned status; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + gnutls_compression_method_t method; + gnutls_compression_method_t methods[] = { GNUTLS_COMP_ZSTD, GNUTLS_COMP_BROTLI, GNUTLS_COMP_ZLIB }; + size_t methods_len = sizeof(methods) / sizeof(gnutls_compression_method_t); + + global_init(); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(4711); + } + + CHECK(gnutls_certificate_allocate_credentials(&x509_cred)); + CHECK(gnutls_certificate_set_x509_trust_mem(x509_cred, &ca3_cert, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_certificate_set_x509_key_mem(x509_cred, &server_ca3_localhost_cert_chain, + &server_ca3_key, GNUTLS_X509_FMT_PEM)); + CHECK(gnutls_init(&session, GNUTLS_SERVER)); + CHECK(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred)); + CHECK(gnutls_priority_set_direct(session, PRIO, NULL)); + + ret = gnutls_compress_certificate_set_methods(session, methods, methods_len); + if (ret < 0) { + fail("server: setting compression method failed (%s)\n\n", gnutls_strerror(ret)); + terminate(); + } + + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, + GNUTLS_HOOK_PRE, server_callback); + gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUEST); + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + fail("server: Handshake has failed (%s)\n\n", gnutls_strerror(ret)); + goto cleanup; + } + if (debug) + success("server: Handshake was completed\n"); + if (debug) + success("server: TLS version is: %s\n", gnutls_protocol_get_name( + gnutls_protocol_get_version(session))); + + method = gnutls_compress_certificate_get_selected_method(session); + if (method != GNUTLS_COMP_ZLIB) + fail("server: compression method should be set to ZLIB\n"); + + if (!server_ok) + fail("server: didn't receive cert compressed with BROTLI\n"); + + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) + fail("server: could not verify client certificate: %s\n", gnutls_strerror(ret)); + if (status) + fail("server: certificate verification failed\n"); + + gnutls_bye(session, GNUTLS_SHUT_WR); + + if (debug) + success("server: finished\n"); + +cleanup: + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); +} + +void doit(void) +{ + int fd[2]; + int ret; + + signal(SIGPIPE, SIG_IGN); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + int status = 0; + + server(fd[0]); + wait(&status); + check_wait_status(status); + } else { + close(fd[0]); + client(fd[1]); + exit(0); + } +} + +#endif /* _WIN32 */ |