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
This commit is contained in:
Colin Walters 2019-06-07 19:36:54 +00:00 committed by David King
parent 9ef5cacbf9
commit 3ef71255bb
7 changed files with 260 additions and 5 deletions

View File

@ -22,7 +22,7 @@
#include <string.h> #include <string.h>
#include "gchecksum.h" #include "gchecksumprivate.h"
#include "gslice.h" #include "gslice.h"
#include "gmem.h" #include "gmem.h"
@ -176,8 +176,8 @@ sha_byte_reverse (guint32 *buffer,
} }
#endif /* G_BYTE_ORDER == G_BIG_ENDIAN */ #endif /* G_BYTE_ORDER == G_BIG_ENDIAN */
static gchar * gchar *
digest_to_string (guint8 *digest, gchecksum_digest_to_string (guint8 *digest,
gsize digest_len) gsize digest_len)
{ {
gsize i, len = digest_len * 2; gsize i, len = digest_len * 2;
@ -197,6 +197,7 @@ digest_to_string (guint8 *digest,
return retval; return retval;
} }
#define digest_to_string gchecksum_digest_to_string
/* /*
* MD5 Checksum * MD5 Checksum

32
glib/gchecksumprivate.h Normal file
View File

@ -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

187
glib/ghmac-gnutls.c Normal file
View File

@ -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);
}

View File

@ -35,6 +35,9 @@
#include "gtypes.h" #include "gtypes.h"
#include "glibintl.h" #include "glibintl.h"
#ifdef HAVE_GNUTLS
#error "build configuration error"
#endif
/** /**
* GHmac: * GHmac:
@ -89,6 +92,18 @@ struct _GHmac
* Support for digests of type %G_CHECKSUM_SHA512 has been added in GLib 2.42. * 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. * 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: (nullable) (transfer full): the newly created #GHmac, or %NULL. * Returns: (nullable) (transfer full): the newly created #GHmac, or %NULL.
* Use g_hmac_unref() to free the memory allocated by it. * Use g_hmac_unref() to free the memory allocated by it.
* *

View File

@ -288,7 +288,6 @@ glib_sources += files(
'gfileutils.c', 'gfileutils.c',
'ggettext.c', 'ggettext.c',
'ghash.c', 'ghash.c',
'ghmac.c',
'ghmac-utils.c', 'ghmac-utils.c',
'ghook.c', 'ghook.c',
'ghostutils.c', 'ghostutils.c',
@ -342,6 +341,8 @@ glib_sources += files(
'gunidecomp.c', 'gunidecomp.c',
'guri.c', 'guri.c',
'gutils.c', 'gutils.c',
'gutilsprivate.h',
'gchecksumprivate.h',
'guuid.c', 'guuid.c',
'gvariant.c', 'gvariant.c',
'gvariant-core.c', 'gvariant-core.c',
@ -401,6 +402,12 @@ else
glib_dtrace_hdr = [] glib_dtrace_hdr = []
endif endif
if get_option('gnutls')
glib_sources += files('ghmac-gnutls.c')
else
glib_sources += files('ghmac.c')
endif
pcre2_static_args = [] pcre2_static_args = []
if use_pcre2_static_flag if use_pcre2_static_flag
@ -422,6 +429,7 @@ libglib = library('glib-2.0',
dependencies : [ dependencies : [
atomic_dep, atomic_dep,
gnulib_libm_dependency, gnulib_libm_dependency,
libgnutls_dep,
libiconv, libiconv,
libintl_deps, libintl_deps,
libm, libm,

View File

@ -2324,6 +2324,13 @@ if host_system == 'linux'
endif endif
endif 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' if host_system == 'windows'
winsock2 = cc.find_library('ws2_32') winsock2 = cc.find_library('ws2_32')
else else

View File

@ -37,6 +37,11 @@ option('libmount',
value : 'auto', value : 'auto',
description : 'build with libmount support') description : 'build with libmount support')
option('gnutls',
type : 'boolean',
value : false,
description : 'build with gnutls support')
option('man', option('man',
type : 'boolean', type : 'boolean',
value : false, value : false,