summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZoltan Fridrich <zfridric@redhat.com>2022-01-12 14:57:42 +0100
committerZoltan Fridrich <zfridric@redhat.com>2022-03-01 16:32:19 +0100
commit5943dd3cd4e9b279156195d73af7ee068e709356 (patch)
tree703729f9dd820ac959e013afd0266b16ab21fb0c
parentdcb3c6ebf9d9b7c62e1e2ef1548ea810ca2ae699 (diff)
downloadgnutls-5943dd3cd4e9b279156195d73af7ee068e709356.tar.gz
Add compress_certificate extension (RFC8879)
Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--NEWS8
-rw-r--r--configure.ac77
-rw-r--r--devel/libgnutls.abignore14
-rw-r--r--devel/symbols.last2
-rw-r--r--doc/Makefile.am4
-rw-r--r--doc/manpages/Makefile.am2
-rw-r--r--lib/Makefile.am13
-rw-r--r--lib/common.mk3
-rw-r--r--lib/compress.c219
-rw-r--r--lib/compress.h34
-rw-r--r--lib/debug.c2
-rw-r--r--lib/ext/Makefile.am3
-rw-r--r--lib/ext/compress_certificate.c252
-rw-r--r--lib/ext/compress_certificate.h41
-rw-r--r--lib/gnutls.pc.in2
-rw-r--r--lib/gnutls_int.h5
-rw-r--r--lib/handshake.c3
-rw-r--r--lib/hello_ext.c2
-rw-r--r--lib/includes/gnutls/gnutls.h.in13
-rw-r--r--lib/libgnutls.map2
-rw-r--r--lib/str.c97
-rw-r--r--lib/str.h15
-rw-r--r--lib/tls13/certificate.c145
-rw-r--r--src/cli.c6
-rw-r--r--src/common.c52
-rw-r--r--src/common.h2
-rw-r--r--src/gnutls-cli-options.json6
-rw-r--r--src/gnutls-serv-options.json8
-rw-r--r--src/serv.c6
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/tls13/compress-cert-neg.c275
-rw-r--r--tests/tls13/compress-cert-neg2.c216
-rw-r--r--tests/tls13/compress-cert.c286
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/
diff --git a/NEWS b/NEWS
index 673d1c7e94..39859a6a63 100644
--- a/NEWS
+++ b/NEWS
@@ -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:
*;
diff --git a/lib/str.c b/lib/str.c
index 8007340f1e..787a6b37e1 100644
--- a/lib/str.c
+++ b/lib/str.c
@@ -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)
{
diff --git a/lib/str.h b/lib/str.h
index 65d42081e8..c80d2f5a09 100644
--- a/lib/str.h
+++ b/lib/str.h
@@ -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;
+}
diff --git a/src/cli.c b/src/cli.c
index 5378b72256..3b28a7b839 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -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 */