[glib/mcatanzaro/gnutls-hmac: 20/22] Add a gnutls backend for GHmac




commit 4d0434f8b5d38d3afdf82acd270becedd15af78e
Author: Colin Walters <walters verbum org>
Date:   Fri Jun 7 19:36:54 2019 +0000

    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     | 186 ++++++++++++++++++++++++++++++++++++++++++++++++
 glib/ghmac.c            |  15 ++++
 glib/meson.build        |  10 ++-
 meson.build             |   7 ++
 meson_options.txt       |   7 +-
 7 files changed, 259 insertions(+), 7 deletions(-)
---
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..868c31663
--- /dev/null
+++ b/glib/ghmac-gnutls.c
@@ -0,0 +1,186 @@
+/* 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_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->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 cce338969..1f6bb35d1 100644
--- a/glib/meson.build
+++ b/glib/meson.build
@@ -250,7 +250,6 @@ glib_sources = files(
   'gfileutils.c',
   'ggettext.c',
   'ghash.c',
-  'ghmac.c',
   'ghmac-utils.c',
   'ghook.c',
   'ghostutils.c',
@@ -306,6 +305,7 @@ glib_sources = files(
   'guriprivate.h',
   'gutils.c',
   'gutilsprivate.h',
+  'gchecksumprivate.h',
   'guuid.c',
   'gvariant.c',
   'gvariant-core.c',
@@ -350,6 +350,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
@@ -368,7 +374,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, thread_dep, librt] + libintl_deps + libiconv + platform_deps + 
[gnulib_libm_dependency, libm] + [libsysprof_capture_dep],
+  dependencies : [pcre, 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 1b54fdcae..98f8925a2 100644
--- a/meson.build
+++ b/meson.build
@@ -2095,6 +2095,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 6cd7bc90a..65af1d276 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -34,6 +34,11 @@ option('libmount',
        value : 'auto',
        description : 'build with libmount support')
 
+option('gnutls',
+       type : 'boolean',
+       value : false,
+       description : 'build with gnutls support')
+
 option('man',
        type : 'boolean',
        value : false,
@@ -121,4 +126,4 @@ option('glib_checks',
 option('libelf',
        type : 'feature',
        value : 'auto',
-       description : 'Enable support for listing and extracting from ELF resource files with gresource tool')
\ No newline at end of file
+       description : 'Enable support for listing and extracting from ELF resource files with gresource tool')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]