From ff90bb8474b1e724727f4014b446e7c851e609bd Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
Date: Fri, 7 Jun 2019 18:44:43 +0000
Subject: [PATCH 1/4] ghmac: Split off wrapper functions into ghmac-utils.c

Prep for adding a GnuTLS HMAC implementation; these are just
utility functions that call the "core" API.
---
 glib/ghmac-utils.c | 145 +++++++++++++++++++++++++++++++++++++++++++++
 glib/ghmac.c       | 112 ----------------------------------
 glib/meson.build   |   1 +
 3 files changed, 146 insertions(+), 112 deletions(-)
 create mode 100644 glib/ghmac-utils.c

diff --git a/glib/ghmac-utils.c b/glib/ghmac-utils.c
new file mode 100644
index 000000000..a17359ff1
--- /dev/null
+++ b/glib/ghmac-utils.c
@@ -0,0 +1,145 @@
+/* ghmac.h - data hashing functions
+ *
+ * Copyright (C) 2011  Collabora Ltd.
+ * Copyright (C) 2019  Red Hat, Inc.
+ *
+ * This library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "ghmac.h"
+
+#include "glib/galloca.h"
+#include "gatomic.h"
+#include "gslice.h"
+#include "gmem.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gtypes.h"
+#include "glibintl.h"
+
+/**
+ * g_compute_hmac_for_data:
+ * @digest_type: a #GChecksumType to use for the HMAC
+ * @key: (array length=key_len): the key to use in the HMAC
+ * @key_len: the length of the key
+ * @data: (array length=length): binary blob to compute the HMAC of
+ * @length: length of @data
+ *
+ * Computes the HMAC for a binary @data of @length. This is a
+ * convenience wrapper for g_hmac_new(), g_hmac_get_string()
+ * and g_hmac_unref().
+ *
+ * The hexadecimal string returned will be in lower case.
+ *
+ * Returns: the HMAC of the binary data as a string in hexadecimal.
+ *   The returned string should be freed with g_free() when done using it.
+ *
+ * Since: 2.30
+ */
+gchar *
+g_compute_hmac_for_data (GChecksumType  digest_type,
+                         const guchar  *key,
+                         gsize          key_len,
+                         const guchar  *data,
+                         gsize          length)
+{
+  GHmac *hmac;
+  gchar *retval;
+
+  g_return_val_if_fail (length == 0 || data != NULL, NULL);
+
+  hmac = g_hmac_new (digest_type, key, key_len);
+  if (!hmac)
+    return NULL;
+
+  g_hmac_update (hmac, data, length);
+  retval = g_strdup (g_hmac_get_string (hmac));
+  g_hmac_unref (hmac);
+
+  return retval;
+}
+
+/**
+ * g_compute_hmac_for_bytes:
+ * @digest_type: a #GChecksumType to use for the HMAC
+ * @key: the key to use in the HMAC
+ * @data: binary blob to compute the HMAC of
+ *
+ * Computes the HMAC for a binary @data. This is a
+ * convenience wrapper for g_hmac_new(), g_hmac_get_string()
+ * and g_hmac_unref().
+ *
+ * The hexadecimal string returned will be in lower case.
+ *
+ * Returns: the HMAC of the binary data as a string in hexadecimal.
+ *   The returned string should be freed with g_free() when done using it.
+ *
+ * Since: 2.50
+ */
+gchar *
+g_compute_hmac_for_bytes (GChecksumType  digest_type,
+                          GBytes        *key,
+                          GBytes        *data)
+{
+  gconstpointer byte_data;
+  gsize length;
+  gconstpointer key_data;
+  gsize key_len;
+
+  g_return_val_if_fail (data != NULL, NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  byte_data = g_bytes_get_data (data, &length);
+  key_data = g_bytes_get_data (key, &key_len);
+  return g_compute_hmac_for_data (digest_type, key_data, key_len, byte_data, length);
+}
+
+
+/**
+ * g_compute_hmac_for_string:
+ * @digest_type: a #GChecksumType to use for the HMAC
+ * @key: (array length=key_len): the key to use in the HMAC
+ * @key_len: the length of the key
+ * @str: the string to compute the HMAC for
+ * @length: the length of the string, or -1 if the string is nul-terminated
+ *
+ * Computes the HMAC for a string.
+ *
+ * The hexadecimal string returned will be in lower case.
+ *
+ * Returns: the HMAC as a hexadecimal string.
+ *     The returned string should be freed with g_free()
+ *     when done using it.
+ *
+ * Since: 2.30
+ */
+gchar *
+g_compute_hmac_for_string (GChecksumType  digest_type,
+                           const guchar  *key,
+                           gsize          key_len,
+                           const gchar   *str,
+                           gssize         length)
+{
+  g_return_val_if_fail (length == 0 || str != NULL, NULL);
+
+  if (length < 0)
+    length = strlen (str);
+
+  return g_compute_hmac_for_data (digest_type, key, key_len,
+                                  (const guchar *) str, length);
+}
diff --git a/glib/ghmac.c b/glib/ghmac.c
index 49fd272f0..4f181f21f 100644
--- a/glib/ghmac.c
+++ b/glib/ghmac.c
@@ -329,115 +329,3 @@ g_hmac_get_digest (GHmac  *hmac,
   g_checksum_update (hmac->digesto, buffer, len);
   g_checksum_get_digest (hmac->digesto, buffer, digest_len);
 }
-
-/**
- * g_compute_hmac_for_data:
- * @digest_type: a #GChecksumType to use for the HMAC
- * @key: (array length=key_len): the key to use in the HMAC
- * @key_len: the length of the key
- * @data: (array length=length): binary blob to compute the HMAC of
- * @length: length of @data
- *
- * Computes the HMAC for a binary @data of @length. This is a
- * convenience wrapper for g_hmac_new(), g_hmac_get_string()
- * and g_hmac_unref().
- *
- * The hexadecimal string returned will be in lower case.
- *
- * Returns: the HMAC of the binary data as a string in hexadecimal.
- *   The returned string should be freed with g_free() when done using it.
- *
- * Since: 2.30
- */
-gchar *
-g_compute_hmac_for_data (GChecksumType  digest_type,
-                         const guchar  *key,
-                         gsize          key_len,
-                         const guchar  *data,
-                         gsize          length)
-{
-  GHmac *hmac;
-  gchar *retval;
-
-  g_return_val_if_fail (length == 0 || data != NULL, NULL);
-
-  hmac = g_hmac_new (digest_type, key, key_len);
-  if (!hmac)
-    return NULL;
-
-  g_hmac_update (hmac, data, length);
-  retval = g_strdup (g_hmac_get_string (hmac));
-  g_hmac_unref (hmac);
-
-  return retval;
-}
-
-/**
- * g_compute_hmac_for_bytes:
- * @digest_type: a #GChecksumType to use for the HMAC
- * @key: the key to use in the HMAC
- * @data: binary blob to compute the HMAC of
- *
- * Computes the HMAC for a binary @data. This is a
- * convenience wrapper for g_hmac_new(), g_hmac_get_string()
- * and g_hmac_unref().
- *
- * The hexadecimal string returned will be in lower case.
- *
- * Returns: the HMAC of the binary data as a string in hexadecimal.
- *   The returned string should be freed with g_free() when done using it.
- *
- * Since: 2.50
- */
-gchar *
-g_compute_hmac_for_bytes (GChecksumType  digest_type,
-                          GBytes        *key,
-                          GBytes        *data)
-{
-  gconstpointer byte_data;
-  gsize length;
-  gconstpointer key_data;
-  gsize key_len;
-
-  g_return_val_if_fail (data != NULL, NULL);
-  g_return_val_if_fail (key != NULL, NULL);
-
-  byte_data = g_bytes_get_data (data, &length);
-  key_data = g_bytes_get_data (key, &key_len);
-  return g_compute_hmac_for_data (digest_type, key_data, key_len, byte_data, length);
-}
-
-
-/**
- * g_compute_hmac_for_string:
- * @digest_type: a #GChecksumType to use for the HMAC
- * @key: (array length=key_len): the key to use in the HMAC
- * @key_len: the length of the key
- * @str: the string to compute the HMAC for
- * @length: the length of the string, or -1 if the string is nul-terminated
- *
- * Computes the HMAC for a string.
- *
- * The hexadecimal string returned will be in lower case.
- *
- * Returns: the HMAC as a hexadecimal string.
- *     The returned string should be freed with g_free()
- *     when done using it.
- *
- * Since: 2.30
- */
-gchar *
-g_compute_hmac_for_string (GChecksumType  digest_type,
-                           const guchar  *key,
-                           gsize          key_len,
-                           const gchar   *str,
-                           gssize         length)
-{
-  g_return_val_if_fail (length == 0 || str != NULL, NULL);
-
-  if (length < 0)
-    length = strlen (str);
-
-  return g_compute_hmac_for_data (digest_type, key, key_len,
-                                  (const guchar *) str, length);
-}
diff --git a/glib/meson.build b/glib/meson.build
index 8c18e6de4..329b8d197 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -253,6 +253,7 @@ glib_sources = files(
   'ggettext.c',
   'ghash.c',
   'ghmac.c',
+  'ghmac-utils.c',
   'ghook.c',
   'ghostutils.c',
   'giochannel.c',
-- 
2.31.1

From 5395d36e6685e0b7377794c59c5820970bb472ef Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
Date: Fri, 7 Jun 2019 19:36:54 +0000
Subject: [PATCH 2/4] Add a gnutls backend for GHmac

For RHEL we want apps to use FIPS-certified crypto libraries,
and HMAC apparently counts as "keyed" and hence needs to
be validated.

Bug: https://bugzilla.redhat.com/show_bug.cgi?id=1630260
Replaces: https://gitlab.gnome.org/GNOME/glib/merge_requests/897

This is a build-time option that backs the GHmac API with GnuTLS.
Most distributors ship glib-networking built with GnuTLS, and
most apps use glib-networking, so this isn't a net-new library
in most cases.

=======================================================================

mcatanzaro note:

I've updated Colin's original patch with several enhancements:

Implement g_hmac_copy() using gnutls_hmac_copy(), which didn't exist
when Colin developed this patch.

Removed use of GSlice

Better error checking in g_hmac_new(). It is possible for
gnutls_hmac_init() to fail if running in FIPS mode and an MD5 digest is
requested. In this case, we should return NULL rather than returning a
broken GHmac with a NULL gnutls_hmac_hd_t. This was leading to a later
null pointer dereference inside gnutls_hmac_update(). Applications are
responsible for checking to ensure the return value of g_hmac_new() is
not NULL since it is annotated as nullable. Added documentation to
indicate this possibility.

Properly handle length -1 in g_hmac_update(). This means we've been
given a NUL-terminated string and should use strlen(). GnuTLS doesn't
accept -1, so let's call strlen() ourselves.

Crash the application with g_error() if gnutls_hmac() fails for any
reason. This is necessary because g_hmac_update() is not fallible, so we
have no way to indicate error. Crashing seems better than returning the
wrong result later when g_hmac_get_string() or g_hmac_get_digest() is
later called. (Those functions are also not fallible.) Fortunately, I
don't think this error should actually be hit in practice.

https://gitlab.gnome.org/GNOME/glib/-/merge_requests/903
---
 glib/gchecksum.c        |   9 +-
 glib/gchecksumprivate.h |  32 +++++++
 glib/ghmac-gnutls.c     | 187 ++++++++++++++++++++++++++++++++++++++++
 glib/ghmac.c            |  15 ++++
 glib/meson.build        |  10 ++-
 meson.build             |   7 ++
 meson_options.txt       |   7 +-
 7 files changed, 260 insertions(+), 7 deletions(-)
 create mode 100644 glib/gchecksumprivate.h
 create mode 100644 glib/ghmac-gnutls.c

diff --git a/glib/gchecksum.c b/glib/gchecksum.c
index 29b479bc6..929958c3a 100644
--- a/glib/gchecksum.c
+++ b/glib/gchecksum.c
@@ -20,7 +20,7 @@
 
 #include <string.h>
 
-#include "gchecksum.h"
+#include "gchecksumprivate.h"
 
 #include "gslice.h"
 #include "gmem.h"
@@ -173,9 +173,9 @@ sha_byte_reverse (guint32 *buffer,
 }
 #endif /* G_BYTE_ORDER == G_BIG_ENDIAN */
 
-static gchar *
-digest_to_string (guint8 *digest,
-                  gsize   digest_len)
+gchar *
+gchecksum_digest_to_string (guint8 *digest,
+                            gsize   digest_len)
 {
   gsize i, len = digest_len * 2;
   gchar *retval;
@@ -194,6 +194,7 @@ digest_to_string (guint8 *digest,
 
   return retval;
 }
+#define digest_to_string gchecksum_digest_to_string
 
 /*
  * MD5 Checksum
diff --git a/glib/gchecksumprivate.h b/glib/gchecksumprivate.h
new file mode 100644
index 000000000..86c7a3b61
--- /dev/null
+++ b/glib/gchecksumprivate.h
@@ -0,0 +1,32 @@
+/* gstdioprivate.h - Private GLib stdio functions
+ *
+ * Copyright 2017 Руслан Ижбулатов
+ *
+ * This library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __G_CHECKSUMPRIVATE_H__
+#define __G_CHECKSUMPRIVATE_H__
+
+#include "gchecksum.h"
+
+G_BEGIN_DECLS
+
+gchar *
+gchecksum_digest_to_string (guint8 *digest,
+                            gsize   digest_len);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/glib/ghmac-gnutls.c b/glib/ghmac-gnutls.c
new file mode 100644
index 000000000..9fb775f89
--- /dev/null
+++ b/glib/ghmac-gnutls.c
@@ -0,0 +1,187 @@
+/* ghmac.h - data hashing functions
+ *
+ * Copyright (C) 2011  Collabora Ltd.
+ * Copyright (C) 2019  Red Hat, Inc.
+ *
+ * This library 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 library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gnutls/crypto.h>
+
+#include "ghmac.h"
+
+#include "glib/galloca.h"
+#include "gatomic.h"
+#include "gslice.h"
+#include "gmem.h"
+#include "gstrfuncs.h"
+#include "gchecksumprivate.h"
+#include "gtestutils.h"
+#include "gtypes.h"
+#include "glibintl.h"
+
+#ifndef HAVE_GNUTLS
+#error "build configuration error"
+#endif
+
+struct _GHmac
+{
+  int ref_count;
+  GChecksumType digest_type;
+  gnutls_hmac_hd_t hmac;
+  gchar *digest_str;
+};
+
+GHmac *
+g_hmac_new (GChecksumType  digest_type,
+            const guchar  *key,
+            gsize          key_len)
+{
+  gnutls_mac_algorithm_t algo;
+  GHmac *hmac = g_new0 (GHmac, 1);
+  int ret;
+
+  hmac->ref_count = 1;
+  hmac->digest_type = digest_type;
+
+  switch (digest_type)
+    {
+    case G_CHECKSUM_MD5:
+      algo = GNUTLS_MAC_MD5;
+      break;
+    case G_CHECKSUM_SHA1:
+      algo = GNUTLS_MAC_SHA1;
+      break;
+    case G_CHECKSUM_SHA256:
+      algo = GNUTLS_MAC_SHA256;
+      break;
+    case G_CHECKSUM_SHA384:
+      algo = GNUTLS_MAC_SHA384;
+      break;
+    case G_CHECKSUM_SHA512:
+      algo = GNUTLS_MAC_SHA512;
+      break;
+    default:
+      g_free (hmac);
+      g_return_val_if_reached (NULL);
+    }
+
+  ret = gnutls_hmac_init (&hmac->hmac, algo, key, key_len);
+  if (ret != 0)
+    {
+      /* There is no way to report an error here, but one possible cause of
+       * failure is that the requested digest may be disabled by FIPS mode.
+       */
+      g_free (hmac);
+      return NULL;
+    }
+
+  return hmac;
+}
+
+GHmac *
+g_hmac_copy (const GHmac *hmac)
+{
+  GHmac *copy;
+
+  g_return_val_if_fail (hmac != NULL, NULL);
+
+  copy = g_new0 (GHmac, 1);
+  copy->ref_count = 1;
+  copy->digest_type = hmac->digest_type;
+  copy->hmac = gnutls_hmac_copy (hmac->hmac);
+
+  /* g_hmac_copy is not allowed to fail, so we'll have to crash on error. */
+  if (!copy->hmac)
+    g_error ("gnutls_hmac_copy failed");
+
+  return copy;
+}
+
+GHmac *
+g_hmac_ref (GHmac *hmac)
+{
+  g_return_val_if_fail (hmac != NULL, NULL);
+
+  g_atomic_int_inc (&hmac->ref_count);
+
+  return hmac;
+}
+
+void
+g_hmac_unref (GHmac *hmac)
+{
+  g_return_if_fail (hmac != NULL);
+
+  if (g_atomic_int_dec_and_test (&hmac->ref_count))
+    {
+      gnutls_hmac_deinit (hmac->hmac, NULL);
+      g_free (hmac->digest_str);
+      g_free (hmac);
+    }
+}
+
+
+void
+g_hmac_update (GHmac        *hmac,
+               const guchar *data,
+               gssize        length)
+{
+  int ret;
+
+  g_return_if_fail (hmac != NULL);
+  g_return_if_fail (length == 0 || data != NULL);
+
+  if (length == -1)
+    length = strlen ((const char *)data);
+
+  /* g_hmac_update is not allowed to fail, so we'll have to crash on error. */
+  ret = gnutls_hmac (hmac->hmac, data, length);
+  if (ret != 0)
+    g_error ("gnutls_hmac failed: %s", gnutls_strerror (ret));
+}
+
+const gchar *
+g_hmac_get_string (GHmac *hmac)
+{
+  guint8 *buffer;
+  gsize digest_len;
+
+  g_return_val_if_fail (hmac != NULL, NULL);
+
+  if (hmac->digest_str)
+    return hmac->digest_str;
+
+  digest_len = g_checksum_type_get_length (hmac->digest_type);
+  buffer = g_alloca (digest_len);
+
+  gnutls_hmac_output (hmac->hmac, buffer);
+  hmac->digest_str = gchecksum_digest_to_string (buffer, digest_len);
+  return hmac->digest_str;
+}
+
+
+void
+g_hmac_get_digest (GHmac  *hmac,
+                   guint8 *buffer,
+                   gsize  *digest_len)
+{
+  g_return_if_fail (hmac != NULL);
+
+  gnutls_hmac_output (hmac->hmac, buffer);
+  *digest_len = g_checksum_type_get_length (hmac->digest_type);
+}
diff --git a/glib/ghmac.c b/glib/ghmac.c
index 4f181f21f..0e39ea40a 100644
--- a/glib/ghmac.c
+++ b/glib/ghmac.c
@@ -33,6 +33,9 @@
 #include "gtypes.h"
 #include "glibintl.h"
 
+#ifdef HAVE_GNUTLS
+#error "build configuration error"
+#endif
 
 /**
  * SECTION:hmac
@@ -84,6 +87,18 @@ struct _GHmac
  * Support for digests of type %G_CHECKSUM_SHA512 has been added in GLib 2.42.
  * Support for %G_CHECKSUM_SHA384 was added in GLib 2.52.
  *
+ * Note that #GHmac creation may fail, in which case this function will
+ * return %NULL. Since there is no error parameter, it is not possible
+ * to indicate why.
+ *
+ * In Fedora, CentOS Stream, and Red Hat Enterprise Linux, GLib is
+ * configured to use GnuTLS to implement #GHmac in order to support FIPS
+ * compliance. This introduces additional failure possibilities that are
+ * not present in upstream GLib. For example, the creation of a #GHmac
+ * will fail if @digest_type is %G_CHECKSUM_MD5 and the system is
+ * running in FIPS mode. #GHmac creation may also fail if GLib is unable
+ * to load GnuTLS.
+ *
  * Returns: the newly created #GHmac, or %NULL.
  *   Use g_hmac_unref() to free the memory allocated by it.
  *
diff --git a/glib/meson.build b/glib/meson.build
index 329b8d197..2417de53d 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -252,7 +252,6 @@ glib_sources = files(
   'gfileutils.c',
   'ggettext.c',
   'ghash.c',
-  'ghmac.c',
   'ghmac-utils.c',
   'ghook.c',
   'ghostutils.c',
@@ -308,6 +307,7 @@ glib_sources = files(
   'guriprivate.h',
   'gutils.c',
   'gutilsprivate.h',
+  'gchecksumprivate.h',
   'guuid.c',
   'gvariant.c',
   'gvariant-core.c',
@@ -352,6 +352,12 @@ else
   glib_dtrace_hdr = []
 endif
 
+if get_option('gnutls')
+  glib_sources += files('ghmac-gnutls.c')
+else
+  glib_sources += files('ghmac.c')
+endif
+
 pcre_static_args = []
 
 if use_pcre_static_flag
@@ -378,7 +384,7 @@ libglib = library('glib-2.0',
   # intl.lib is not compatible with SAFESEH
   link_args : [noseh_link_args, glib_link_flags, win32_ldflags],
   include_directories : configinc,
-  dependencies : pcre_deps + [thread_dep, librt] + libintl_deps + libiconv + platform_deps + [gnulib_libm_dependency, libm] + [libsysprof_capture_dep],
+  dependencies : pcre_deps + [thread_dep, librt] + libgnutls_dep + libintl_deps + libiconv + platform_deps + [gnulib_libm_dependency, libm] + [libsysprof_capture_dep],
   c_args : glib_c_args,
   objc_args : glib_c_args,
 )
diff --git a/meson.build b/meson.build
index e2eba1871..cca15f653 100644
--- a/meson.build
+++ b/meson.build
@@ -2090,6 +2090,13 @@ if host_system == 'linux'
   glib_conf.set('HAVE_LIBMOUNT', libmount_dep.found())
 endif
 
+# gnutls is used optionally by ghmac
+libgnutls_dep = []
+if get_option('gnutls')
+  libgnutls_dep = [dependency('gnutls', version : '>=3.6.9', required : true)]
+  glib_conf.set('HAVE_GNUTLS', 1)
+endif
+
 if host_system == 'windows'
   winsock2 = cc.find_library('ws2_32')
 endif
diff --git a/meson_options.txt b/meson_options.txt
index 072765361..c8f26ac02 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -39,6 +39,11 @@ option('internal_pcre',
        value : false,
        description : 'whether to use internal PCRE')
 
+option('gnutls',
+       type : 'boolean',
+       value : false,
+       description : 'build with gnutls support')
+
 option('man',
        type : 'boolean',
        value : false,
-- 
2.31.1

From 61c175277acb8d1e080305acd444201c5ad1fb81 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 16 Jun 2021 20:35:00 -0500
Subject: [PATCH 3/4] dlopen GnuTLS instead of linking directly

I'd like to enable our GnuTLS GHmac patchset in Fedora in order to
ensure it is receiving sufficient real-world testing, since we've
discovered several bugs thus far. Problem is Fedora has one requirement
that RHEL does not: it needs to build glib as a static lib. This is
needed by QEMU in Fedora for complicated technical reasons that I don't
understand. However, nothing in RHEL needs it. This means we failed to
notice that glib2-static is broken in RHEL, because there is no
gnutls-static! We could fix this by adding a gnutls-static package, but
that seems like overkill, and adding more static libraries where they're
not truly necessary is not the direction we want to move in anyway. So
instead, let's just dlopen GnuTLS to sidestep this problem entirely.

This would not be a good solution for upstream, but upstream has made
clear that this patchset is already non-upstreamable, so it will be fine
for our purposes.
---
 glib/ghmac-gnutls.c | 101 ++++++++++++++++++++++++++++++++++++++++++--
 glib/ghmac.c        |   2 +-
 glib/meson.build    |   2 +-
 meson.build         |   6 +--
 4 files changed, 102 insertions(+), 9 deletions(-)

diff --git a/glib/ghmac-gnutls.c b/glib/ghmac-gnutls.c
index 9fb775f89..1800fc2e0 100644
--- a/glib/ghmac-gnutls.c
+++ b/glib/ghmac-gnutls.c
@@ -19,8 +19,8 @@
 
 #include "config.h"
 
+#include <dlfcn.h>
 #include <string.h>
-#include <gnutls/crypto.h>
 
 #include "ghmac.h"
 
@@ -31,13 +31,16 @@
 #include "gstrfuncs.h"
 #include "gchecksumprivate.h"
 #include "gtestutils.h"
+#include "gthread.h"
 #include "gtypes.h"
 #include "glibintl.h"
 
-#ifndef HAVE_GNUTLS
+#ifndef USE_GNUTLS
 #error "build configuration error"
 #endif
 
+typedef gpointer gnutls_hmac_hd_t;
+
 struct _GHmac
 {
   int ref_count;
@@ -46,15 +49,107 @@ struct _GHmac
   gchar *digest_str;
 };
 
+typedef enum
+{
+  GNUTLS_MAC_MD5 = 2,
+  GNUTLS_MAC_SHA1 = 3,
+  GNUTLS_MAC_SHA256 = 6,
+  GNUTLS_MAC_SHA384 = 7,
+  GNUTLS_MAC_SHA512 = 8,
+} gnutls_mac_algorithm_t;
+
+/* Why are we dlopening GnuTLS instead of linking to it directly? Because we
+ * want to be able to build GLib as a static library without depending on a
+ * static build of GnuTLS. QEMU depends on static linking with GLib, but Fedora
+ * does not ship a static build of GnuTLS, and this allows us to avoid changing
+ * that.
+ */
+static int              (*gnutls_hmac_init)   (gnutls_hmac_hd_t *dig, gnutls_mac_algorithm_t algorithm, const void *key, size_t keylen);
+static gnutls_hmac_hd_t (*gnutls_hmac_copy)   (gnutls_hmac_hd_t handle);
+static void             (*gnutls_hmac_deinit) (gnutls_hmac_hd_t handle, void *digest);
+static int              (*gnutls_hmac)        (gnutls_hmac_hd_t handle, const void *ptext, size_t ptext_len);
+static void             (*gnutls_hmac_output) (gnutls_hmac_hd_t handle, void *digest);
+static const char *     (*gnutls_strerror)    (int error);
+
+static gsize gnutls_initialize_attempted = 0;
+static gboolean gnutls_initialize_successful = FALSE;
+
+static void
+initialize_gnutls (void)
+{
+  gpointer libgnutls;
+
+  libgnutls = dlopen ("libgnutls.so.30", RTLD_LAZY | RTLD_GLOBAL);
+  if (!libgnutls)
+    {
+      g_warning ("Cannot use GHmac: failed to load libgnutls.so.30: %s", dlerror ());
+      return;
+    }
+
+  gnutls_hmac_init = dlsym (libgnutls, "gnutls_hmac_init");
+  if (!gnutls_hmac_init)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_hmac_init: %s", dlerror ());
+      return;
+    }
+
+  gnutls_hmac_copy = dlsym (libgnutls, "gnutls_hmac_copy");
+  if (!gnutls_hmac_copy)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_hmac_copy: %s", dlerror ());
+      return;
+    }
+
+  gnutls_hmac_deinit = dlsym (libgnutls, "gnutls_hmac_deinit");
+  if (!gnutls_hmac_deinit)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_hmac_deinit: %s", dlerror ());
+      return;
+    }
+
+  gnutls_hmac = dlsym (libgnutls, "gnutls_hmac");
+  if (!gnutls_hmac)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_hmac: %s", dlerror ());
+      return;
+    }
+
+  gnutls_hmac_output = dlsym (libgnutls, "gnutls_hmac_output");
+  if (!gnutls_hmac_output)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_hmac_output: %s", dlerror ());
+      return;
+    }
+
+  gnutls_strerror = dlsym (libgnutls, "gnutls_strerror");
+  if (!gnutls_strerror)
+    {
+      g_warning ("Cannot use GHmac: failed to load gnutls_strerror: %s", dlerror ());
+      return;
+    }
+
+  gnutls_initialize_successful = TRUE;
+}
+
 GHmac *
 g_hmac_new (GChecksumType  digest_type,
             const guchar  *key,
             gsize          key_len)
 {
   gnutls_mac_algorithm_t algo;
-  GHmac *hmac = g_new0 (GHmac, 1);
+  GHmac *hmac;
   int ret;
 
+  if (g_once_init_enter (&gnutls_initialize_attempted))
+    {
+      initialize_gnutls ();
+      g_once_init_leave (&gnutls_initialize_attempted, 1);
+    }
+
+  if (!gnutls_initialize_successful)
+    return NULL;
+
+  hmac = g_new0 (GHmac, 1);
   hmac->ref_count = 1;
   hmac->digest_type = digest_type;
 
diff --git a/glib/ghmac.c b/glib/ghmac.c
index 0e39ea40a..2d9be91b8 100644
--- a/glib/ghmac.c
+++ b/glib/ghmac.c
@@ -33,7 +33,7 @@
 #include "gtypes.h"
 #include "glibintl.h"
 
-#ifdef HAVE_GNUTLS
+#ifdef USE_GNUTLS
 #error "build configuration error"
 #endif
 
diff --git a/glib/meson.build b/glib/meson.build
index 2417de53d..ba42951aa 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -384,7 +384,7 @@ libglib = library('glib-2.0',
   # intl.lib is not compatible with SAFESEH
   link_args : [noseh_link_args, glib_link_flags, win32_ldflags],
   include_directories : configinc,
-  dependencies : pcre_deps + [thread_dep, librt] + libgnutls_dep + libintl_deps + libiconv + platform_deps + [gnulib_libm_dependency, libm] + [libsysprof_capture_dep],
+  dependencies : pcre_deps + [thread_dep, librt] + libintl_deps + libiconv + platform_deps + [gnulib_libm_dependency, libm] + [libsysprof_capture_dep] + [libdl_dep],
   c_args : glib_c_args,
   objc_args : glib_c_args,
 )
diff --git a/meson.build b/meson.build
index cca15f653..404ef1790 100644
--- a/meson.build
+++ b/meson.build
@@ -2090,11 +2090,9 @@ if host_system == 'linux'
   glib_conf.set('HAVE_LIBMOUNT', libmount_dep.found())
 endif
 
-# gnutls is used optionally by ghmac
-libgnutls_dep = []
+# gnutls is used optionally by GHmac
 if get_option('gnutls')
-  libgnutls_dep = [dependency('gnutls', version : '>=3.6.9', required : true)]
-  glib_conf.set('HAVE_GNUTLS', 1)
+  glib_conf.set('USE_GNUTLS', 1)
 endif
 
 if host_system == 'windows'
-- 
2.31.1

From 7d1d96311b6ecd4f90ebbdd6fc58d28e06a86887 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 16 Jun 2021 20:46:24 -0500
Subject: [PATCH 4/4] Add test for GHmac in FIPS mode

This will test a few problems that we hit recently:

g_hmac_copy() is broken, https://bugzilla.redhat.com/show_bug.cgi?id=1786538

Crash in g_hmac_update() in FIPS mode, https://bugzilla.redhat.com/show_bug.cgi?id=1971533

Crash when passing -1 length to g_hmac_update() (discovered in #1971533)

We'll also test to ensure MD5 fails, and stop compiling the other MD5
tests.
---
 glib/tests/hmac.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/glib/tests/hmac.c b/glib/tests/hmac.c
index 3ac3206df..2fa447984 100644
--- a/glib/tests/hmac.c
+++ b/glib/tests/hmac.c
@@ -1,7 +1,10 @@
+#include "config.h"
+
 #include <glib.h>
 #include <string.h>
 #include <stdlib.h>
 
+#ifndef USE_GNUTLS
 /* HMAC-MD5 test vectors as per RFC 2202 */
 
 /* Test 1 */
@@ -81,6 +84,7 @@ guint8 key_md5_test7[] = {
 guint8 result_md5_test7[] = {
     0x6f, 0x63, 0x0f, 0xad, 0x67, 0xcd, 0xa0, 0xee, 0x1f, 0xb1,
     0xf5, 0x62, 0xdb, 0x3a, 0xa5, 0x3e };
+#endif
 
 /* HMAC-SHA1, HMAC-SHA256, HMAC-SHA384 and HMAC-SHA512 test vectors
  * as per RFCs 2202 and 4868.
@@ -299,6 +303,7 @@ typedef struct {
   gconstpointer result;
 } HmacCase;
 
+#ifndef USE_GNUTLS
 HmacCase hmac_md5_tests[] = {
   { G_CHECKSUM_MD5, key_md5_test1, 16, "Hi There", 8, result_md5_test1 },
   { G_CHECKSUM_MD5, "Jefe", 4, "what do ya want for nothing?", 28,
@@ -317,6 +322,7 @@ HmacCase hmac_md5_tests[] = {
       73, result_md5_test7 },
   { -1, NULL, 0, NULL, 0, NULL },
 };
+#endif
 
 HmacCase hmac_sha1_tests[] = {
   { G_CHECKSUM_SHA1, key_sha_test1, 20, "Hi There", 8, result_sha1_test1 },
@@ -493,11 +499,45 @@ test_hmac_for_bytes (void)
   g_bytes_unref (data);
 }
 
+#ifdef USE_GNUTLS
+static void
+test_gnutls_fips_mode (void)
+{
+  GHmac *hmac;
+  GHmac *copy;
+
+  /* No MD5 in FIPS mode. */
+  hmac = g_hmac_new (G_CHECKSUM_MD5, "abc123", sizeof ("abc123"));
+  g_assert_null (hmac);
+
+  /* SHA-256 should be good. */
+  hmac = g_hmac_new (G_CHECKSUM_SHA256, "abc123", sizeof ("abc123"));
+  g_assert_nonnull (hmac);
+
+  /* Ensure g_hmac_update() does not crash when called with -1. */
+  g_hmac_update (hmac, "You win again, gravity!", -1);
+
+  /* Ensure g_hmac_copy() does not crash. */
+  copy = g_hmac_copy (hmac);
+  g_assert_nonnull (hmac);
+  g_hmac_unref (hmac);
+
+  g_assert_cmpstr (g_hmac_get_string (copy), ==, "795ba6900bcb22e8ce65c2ec02db4e85697da921deb960ee3143bf88a4a60f83");
+  g_hmac_unref (copy);
+}
+#endif
+
 int
 main (int argc,
     char **argv)
 {
   int i;
+
+#ifdef USE_GNUTLS
+  /* This has to happen before GnuTLS is dlopened. */
+  g_setenv ("GNUTLS_FORCE_FIPS_MODE", "1", FALSE);
+#endif
+
   g_test_init (&argc, &argv, NULL);
 
   for (i = 0 ; hmac_sha1_tests[i].key_len > 0 ; i++)
@@ -532,6 +572,7 @@ main (int argc,
       g_free (name);
     }
 
+#ifndef USE_GNUTLS
   for (i = 0 ; hmac_md5_tests[i].key_len > 0 ; i++)
     {
       gchar *name = g_strdup_printf ("/hmac/md5-%d", i + 1);
@@ -539,6 +580,7 @@ main (int argc,
         (void (*)(const void *)) test_hmac);
       g_free (name);
     }
+#endif
 
   g_test_add_func ("/hmac/ref-unref", test_hmac_ref_unref);
   g_test_add_func ("/hmac/copy", test_hmac_copy);
@@ -546,5 +588,9 @@ main (int argc,
   g_test_add_func ("/hmac/for-string", test_hmac_for_string);
   g_test_add_func ("/hmac/for-bytes", test_hmac_for_bytes);
 
+#ifdef USE_GNUTLS
+  g_test_add_func ("/hmac/gnutls-fips-mode", test_gnutls_fips_mode);
+#endif
+
   return g_test_run ();
 }
-- 
2.31.1
