]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
Add compress_certificate extension (RFC8879)
authorZoltan Fridrich <zfridric@redhat.com>
Wed, 12 Jan 2022 13:57:42 +0000 (14:57 +0100)
committerZoltan Fridrich <zfridric@redhat.com>
Tue, 1 Mar 2022 15:32:19 +0000 (16:32 +0100)
Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
34 files changed:
.gitlab-ci.yml
NEWS
configure.ac
devel/libgnutls.abignore
devel/symbols.last
doc/Makefile.am
doc/manpages/Makefile.am
lib/Makefile.am
lib/common.mk
lib/compress.c
lib/compress.h [new file with mode: 0644]
lib/debug.c
lib/ext/Makefile.am
lib/ext/compress_certificate.c [new file with mode: 0644]
lib/ext/compress_certificate.h [new file with mode: 0644]
lib/gnutls.pc.in
lib/gnutls_int.h
lib/handshake.c
lib/hello_ext.c
lib/includes/gnutls/gnutls.h.in
lib/libgnutls.map
lib/str.c
lib/str.h
lib/tls13/certificate.c
src/cli.c
src/common.c
src/common.h
src/gnutls-cli-options.json
src/gnutls-serv-options.json
src/serv.c
tests/Makefile.am
tests/tls13/compress-cert-neg.c [new file with mode: 0644]
tests/tls13/compress-cert-neg2.c [new file with mode: 0644]
tests/tls13/compress-cert.c [new file with mode: 0644]

index 902001175098c3a57b86cb44d49079da544a9af2..d4cc464c84bc05a5b09f36dd0fb99d9203333223 100644 (file)
@@ -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 673d1c7e94dce6168041517f2457b6f29d0243fc..39859a6a633cb8a676885d09d3adaf10d2a40c96 100644 (file)
--- 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
index 53c3aefca1ae2371fba5f325ea0aa3207c26f3de..351cf4593ee67db8b1bba013a2358789f2de693b 100644 (file)
@@ -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)
 
index 61512030a1d47a1be8916f861b0b06bbcdcf2d9d..fd2ee85dbe3a0a317ae878d1de7bfbac5d6cc085 100644 (file)
@@ -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
index 92a74228129e2384746c39c73eadfaa5bb7d1db6..18997b910dd81149cc347285ccd0ec393adde413 100644 (file)
@@ -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
index a89166ff60ecd47f6f17bff0e6720d327e903341..9c8e9141fd699319726d72121c8dddf6b3897157 100644 (file)
@@ -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
index e101b6c576a0c492a2c91e77f42de98fde460775..e1686d390f3624473de11b5e9fb728da5a93493c 100644 (file)
@@ -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
index 35df35ee8da28e07445fc30d521d8cc29b49afd8..9357aeaf3906b87e42ab48c036f08bc1a7947839 100644 (file)
@@ -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
index 796fdf30bdc87cef58427e51ea07722fbd195d1d..2e0d0752d059b3f54ba38fd64efceb7fa9cd20c6 100644 (file)
@@ -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@)
index f9e4bdbd705be3db964bd5abf348b2c2cdf3850a..dbcf4fa1687c5ff8cd39d284e3d35ce2d097db6b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Red Hat, Inc.
+ * Copyright (C) 2017-2022 Red Hat, Inc.
  *
  * Author: Nikos Mavrogiannopoulos
  *
  *
  */
 
-#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:
  * 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 (file)
index 0000000..3221f7d
--- /dev/null
@@ -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 */
index 166dd61caddb403b22cabf38a46542487cfea5ef..991f7a7bedf7c0932a233c925310ee87d7f61c37 100644 (file)
@@ -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:
index 4e010ee9a0c888d942a77e39f5672540febf1e77..81efdbc55a7600940671001a33b5ecd704ae9a8f 100644 (file)
@@ -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 (file)
index 0000000..8144368
--- /dev/null
@@ -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 (file)
index 0000000..88199da
--- /dev/null
@@ -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 */
index 7cdedda5d7ecaab85bddb411085c7b1037de9d46..eeb957cb72a879c18ba0ffe8affc91a97b2d89ac 100644 (file)
@@ -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}
index c8d52475c72fc39a2fd7509252a1797175f8e01c..26d2373c80c7718e65615bcbbde903bfa66a63ff 100644 (file)
@@ -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;
index 82c895bfde7ae4304b4d37bb88802944bf7bcc7c..44c4cc3402f8b46895f6af4720bb51b1a0291047 100644 (file)
@@ -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:
index bb63623efb2b7245968396cfb790b29df34fe424..779638bb839221915b908f6f47d953ca40407b8c 100644 (file)
@@ -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.
         */
index 5458ea7ce78d39ff7e27a3357950c45e7062dc77..b3c502fc7789996d42eb05a54496c884697475cf 100644 (file)
@@ -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 */
 
 /**
index 27be1284f407a33828c49bf4b940d95b85d93452..d16178580d451ef98de688dde17b54ada24f36bc 100644 (file)
@@ -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:
        *;
index 8007340f1ede527a3fa7a3651232de42733d0881..787a6b37e1fa85d99579e90c189f4b4f9f36462e 100644 (file)
--- 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)
@@ -882,6 +907,34 @@ _gnutls_buffer_pop_datum_prefix32(gnutls_buffer_st * buf,
        return 0;
 }
 
+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)
index 65d42081e8d00e71b6c484907892990366333309..c80d2f5a0953cc2d9f7307fe65923618ea760ad5 100644 (file)
--- 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);
index 7483251a53263a47adbf3a1d8ce335b5f822ce48..979262930edaec23dd1f36778bcbd6b3b62eb24b 100644 (file)
  */
 
 #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;
+}
index 5378b722567898cedef091de80077fa737432e24..3b28a7b839624980b902aa034c42387fdb5c6c83 100644 (file)
--- 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);
index 823a8a83fa485f45bcb2f9b9b0975ac8d29dd2e1..719106c032d3280fd36396b784fb10b07f8a3d1c 100644 (file)
@@ -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)
 {
index 884a355a8286a7873a29a9bfeb951c10bfaf736a..f93187cfee4f9eb0d0f6353ae46a7ab6b9b4f2af 100644 (file)
@@ -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);
index bffa18e365650851e31749bc2ef035b47b376419..c7b8ef2e01859aeb6d6d9d623d147e50d6391517 100644 (file)
         "detail": "This option will set and enable the Application Layer Protocol Negotiation  (ALPN) in the TLS protocol.",
         "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",
index ef074385b0280fe73601d1b58687994b6c6d0144..3ef51111f973ceaba11369ac901e392eb786f5e3 100644 (file)
         "detail": "Do not require, but if a client certificate is sent then verify it and close the connection if invalid.",
         "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",
       }
     ]
   }
-]
\ No newline at end of file
+]
index 85e94cd5e6b6ae11bd2304a1c921e3386e55f428..555406b434cc76b859a7d112bcce8731c98302e0 100644 (file)
@@ -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);
index 402bfa7328cd160efaf26b690ac71079899476cc..93780d45077d644e82e53659054fd0eed596df1e 100644 (file)
@@ -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 (file)
index 0000000..5364e88
--- /dev/null
@@ -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 (file)
index 0000000..b083e38
--- /dev/null
@@ -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 (file)
index 0000000..6b867ca
--- /dev/null
@@ -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 */