File 0001-Use-HMAC-SHA256-for-cache-passwords-over-MD5.patch of Package cyrus-sasl

From 022c28a31af2b4d9e92703715ff5b08797a6ab63 Mon Sep 17 00:00:00 2001
From: William <william@blackhats.net.au>
Date: Thu, 7 Aug 2025 12:56:43 +1000
Subject: [PATCH] Use HMAC-SHA256 for cache passwords over MD5

Currently the password cache uses MD5 for password caching in the
mmapped cache file. This is not cryptographically secure.

This changes the algorithm to HMAC-SHA256. While other password
verification algorithms are considered cryptographically superior
(such as argon2id) in this case we want to maintain the cache's
high performance, while improving the security of cached passwords.

To achieve this, the HMAC key itself is ephemeral and only ever
stored in memory. This means that per invocation of saslauthd
the HMAC key will be randomised. This results in the cache mmap
file being effectively useless to an attacker who manages to
steal the cache as they lack the HMAC key to attempt to validate
or bruteforce any cached password.

This also means that if saslauthd crashes, the key material to
access the cache is lost, effectively invalidating all records
in that cache.

If an attacker were able to access the HMAC key in memory, then we
can pretty safely assume that the attacker can also access plaintext
password material making the need to access the key irrelevant.

As a result, this change improves security of cached passwords
without a significant loss of performance in the cache's operation.

Signed-off-by: William <william@blackhats.net.au>
---
 saslauthd/Makefile.am |  3 ++-
 saslauthd/cache.c     | 33 ++++++++++++++++++++++++---------
 saslauthd/cache.h     |  5 +++--
 3 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/saslauthd/Makefile.am b/saslauthd/Makefile.am
index 4dd79615..5cbb16ea 100644
--- a/saslauthd/Makefile.am
+++ b/saslauthd/Makefile.am
@@ -25,7 +25,8 @@ EXTRA_saslauthd_sources = getaddrinfo.c getnameinfo.c
 saslauthd_DEPENDENCIES = saslauthd-main.o $(LTLIBOBJS_FULL)
 saslauthd_LDADD	= @SASL_KRB_LIB@ \
 		  @GSSAPIBASE_LIBS@ @LIB_CRYPT@ @LIB_SIA@ \
-		  @LIB_SOCKET@ @SASL_DB_LIB@ @LIB_PAM@ @LDAP_LIBS@ $(LTLIBOBJS_FULL) $(CRYPTO_COMPAT_OBJS) $(LIBSASLDB_OBJS)
+		  @LIB_SOCKET@ @SASL_DB_LIB@ @LIB_PAM@ @LDAP_LIBS@ $(LTLIBOBJS_FULL) $(CRYPTO_COMPAT_OBJS) $(LIBSASLDB_OBJS) \
+		  -lcrypto
 
 testsaslauthd_SOURCES = testsaslauthd.c utils.c
 testsaslauthd_LDADD = @LIB_SOCKET@
diff --git a/saslauthd/cache.c b/saslauthd/cache.c
index 0d78a735..3a097b2e 100644
--- a/saslauthd/cache.c
+++ b/saslauthd/cache.c
@@ -54,8 +54,9 @@
 #include "cache.h"
 #include "utils.h"
 #include "globals.h"
-#include "md5global.h"
-#include "saslauthd_md5.h"
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/hmac.h>
 
 /****************************************
  * module globals
@@ -66,6 +67,7 @@ static  struct bucket	*table = NULL;
 static  struct stats	*table_stats = NULL;
 static  unsigned int	table_size = 0;
 static  unsigned int	table_timeout = 0;
+static  unsigned char	hmac_key[CACHE_HMAC_DIGEST_LEN] = {0};
 
 /****************************************
  * flags               global from saslauthd-main.c
@@ -107,6 +109,17 @@ int cache_init(void) {
 	if (table_timeout == 0)
 		table_timeout = CACHE_DEFAULT_TIMEOUT;
 
+	/**************************************************************
+	 * Setup the HMAC key. This is in-memory only and randomised each
+	 * startup such that exfiltration of the mmap cache file will not
+	 * lead to an attacker able to steal cached password content. If
+	 * an attacker were able to read the memory of this process and
+	 * access the HMAC key, then the attacker can access plaintext
+	 * password material anyway.
+	 **************************************************************/
+	if (RAND_priv_bytes((unsigned char *)&hmac_key, CACHE_HMAC_DIGEST_LEN) != 1)
+		return -1;
+
 	if (flags & VERBOSE) {
 		logger(L_DEBUG, L_FUNC, "bucket size: %d bytes",
 		       sizeof(struct bucket));
@@ -163,8 +176,7 @@ int cache_lookup(const char *user, const char *realm, const char *service, const
 	int			realm_length = 0;
 	int			service_length = 0;
 	int			hash_offset;
-	unsigned char		pwd_digest[16];
-	MD5_CTX			md5_context;
+	unsigned char		pwd_digest[CACHE_HMAC_DIGEST_LEN];
 	time_t			epoch;
 	time_t			epoch_timeout;
 	struct bucket		*ref_bucket;
@@ -211,9 +223,12 @@ int cache_lookup(const char *user, const char *realm, const char *service, const
 
 	hash_offset = cache_pjwhash(userrealmserv);
 
-	_saslauthd_MD5Init(&md5_context);
-	_saslauthd_MD5Update(&md5_context, password, strlen(password));
-	_saslauthd_MD5Final(pwd_digest, &md5_context);
+	if (HMAC(EVP_sha256(), (const void *)&hmac_key, CACHE_HMAC_DIGEST_LEN,
+			(const unsigned char *)password, strlen(password),
+			pwd_digest, NULL) == NULL) {
+		logger(L_DEBUG, L_FUNC, debug, user, service, realm, "unable to HMAC user passwd");
+		return CACHE_FAIL;
+	}
 
 	/**************************************************************
 	 * Loop through the bucket chain to try and find a hit.
@@ -262,7 +277,7 @@ int cache_lookup(const char *user, const char *realm, const char *service, const
 
 	if (read_bucket != NULL) {
 
-		if (memcmp(pwd_digest, read_bucket->pwd_digest, 16) == 0) {
+		if (memcmp(pwd_digest, read_bucket->pwd_digest, CACHE_HMAC_DIGEST_LEN) == 0) {
 
 			if (flags & VERBOSE)
 				logger(L_DEBUG, L_FUNC, debug, user, service, realm, "found with valid passwd");
@@ -296,7 +311,7 @@ int cache_lookup(const char *user, const char *realm, const char *service, const
 	strcpy(result->bucket.creds + result->bucket.realm_offt, realm);	
 	strcpy(result->bucket.creds + result->bucket.service_offt, service);	
 
-	memcpy(result->bucket.pwd_digest, pwd_digest, 16);
+	memcpy(result->bucket.pwd_digest, pwd_digest, CACHE_HMAC_DIGEST_LEN);
 	result->bucket.created = epoch;
 
 	cache_un_lock(hash_offset);
diff --git a/saslauthd/cache.h b/saslauthd/cache.h
index b8c88382..129d5531 100644
--- a/saslauthd/cache.h
+++ b/saslauthd/cache.h
@@ -92,7 +92,8 @@ struct lock_ctl {
 #define CACHE_MAX_BUCKETS_PER		6
 #define CACHE_MMAP_FILE			"/cache.mmap"  /* don't forget the "/" */
 #define CACHE_FLOCK_FILE		"/cache.flock" /* don't forget the "/" */
-
+/* HMAC key len must be less than or equal to the size of the hash function */
+#define CACHE_HMAC_DIGEST_LEN 32
 
 
 /* If debugging uncomment this for always verbose  */
@@ -130,7 +131,7 @@ struct bucket {
         unsigned int		user_offt;
         unsigned int		realm_offt;
         unsigned int		service_offt;
-        unsigned char   	pwd_digest[16];
+        unsigned char   	pwd_digest[CACHE_HMAC_DIGEST_LEN];
         time_t          	created;
 };
 
-- 
2.51.0

openSUSE Build Service is sponsored by