[gnutls-devel] [PATCH] srp: Add resistance against guessing usernames

Attila Molnar attilamolnar at hush.com
Thu Feb 20 06:28:12 CET 2014


>From aa892147f5da4868613a6accf2a5f93a484b4d20 Mon Sep 17 00:00:00 2001
From: Attila Molnar <attilamolnar at hush.com>
Date: Mon, 17 Feb 2014 14:10:22 +0100
Subject: [PATCH] srp: Add resistance against guessing usernames

When a client tries to authenticate using an unknown username, instead of
generating a random salt every time, generate the salt based on the
username and a secret seed.

The seed is settable by the application, allowing servers to re-use the
same seed after a restart.

A random seed is generated for each newly allocated SRP server credentials
structure, meaning that applications not using the new API to set the seed
continue to work and gain limited advantage (because they use a different
seed after every restart).

For further information see section 2.5.1.3. in RFC 5054.

Signed-off-by: Attila Molnar <attilamolnar at hush.com>
---
 lib/auth/srp.h                  |  6 +++
 lib/auth/srp_passwd.c           | 52 +++++++++++++++--------
 lib/gnutls_srp.c                | 91 +++++++++++++++++++++++++++++++++++++++--
 lib/includes/gnutls/gnutls.h.in |  6 +++
 lib/libgnutls.map               |  1 +
 5 files changed, 135 insertions(+), 21 deletions(-)

diff --git a/lib/auth/srp.h b/lib/auth/srp.h
index 2bfce81..63b9de2 100644
--- a/lib/auth/srp.h
+++ b/lib/auth/srp.h
@@ -38,6 +38,8 @@ typedef struct gnutls_srp_server_credentials_st {
 	 * password files.
 	 */
 	gnutls_srp_server_credentials_function *pwd_callback;
+	gnutls_datum_t fake_salt_seed;
+	unsigned int fake_salt_length;
 } srp_server_cred_st;
 
 /* these structures should not use allocated data */
@@ -60,6 +62,10 @@ int _gnutls_proc_srp_client_kx(gnutls_session_t, uint8_t *, size_t);
 
 typedef struct srp_server_auth_info_st srp_server_auth_info_st;
 
+/* MAC algorithm used to generate fake salts for unknown usernames
+ */
+#define SRP_FAKE_SALT_MAC GNUTLS_MAC_SHA1
+
 #endif				/* ENABLE_SRP */
 
 #endif
diff --git a/lib/auth/srp_passwd.c b/lib/auth/srp_passwd.c
index b25fe9f..5572dc9 100644
--- a/lib/auth/srp_passwd.c
+++ b/lib/auth/srp_passwd.c
@@ -38,8 +38,11 @@
 #include <gnutls_datum.h>
 #include <gnutls_num.h>
 #include <random.h>
+#include <algorithms.h>
 
-static int _randomize_pwd_entry(SRP_PWD_ENTRY * entry);
+static int _randomize_pwd_entry(SRP_PWD_ENTRY * entry,
+				gnutls_srp_server_credentials_t cred,
+				const char * username);
 
 /* this function parses tpasswd.conf file. Format is:
  * string(username):base64(v):base64(salt):int(index)
@@ -273,7 +276,7 @@ _gnutls_srp_pwd_read_entry(gnutls_session_t state, char *username,
 
 		if (ret == 1) {	/* the user does not exist */
 			if (entry->g.size != 0 && entry->n.size != 0) {
-				ret = _randomize_pwd_entry(entry);
+				ret = _randomize_pwd_entry(entry, cred, username);
 				if (ret < 0) {
 					gnutls_assert();
 					goto cleanup;
@@ -348,7 +351,7 @@ _gnutls_srp_pwd_read_entry(gnutls_session_t state, char *username,
 	 * the last index found and randomize the entry.
 	 */
 	if (pwd_read_conf(cred->password_conf_file, entry, 1) == 0) {
-		ret = _randomize_pwd_entry(entry);
+		ret = _randomize_pwd_entry(entry, cred, username);
 		if (ret < 0) {
 			gnutls_assert();
 			goto cleanup;
@@ -373,26 +376,23 @@ found:
 }
 
 /* Randomizes the given password entry. It actually sets the verifier
- * and the salt. Returns 0 on success.
+ * to random data and sets the salt based on fake_salt_seed and
+ * username. Returns 0 on success.
  */
-static int _randomize_pwd_entry(SRP_PWD_ENTRY * entry)
+static int _randomize_pwd_entry(SRP_PWD_ENTRY * entry,
+				gnutls_srp_server_credentials_t sc,
+				const char * username)
 {
-	unsigned char rnd;
 	int ret;
+	const mac_entry_st *me = mac_to_entry(SRP_FAKE_SALT_MAC);
+	mac_hd_st ctx;
+	size_t username_len = strlen(username);
 
 	if (entry->g.size == 0 || entry->n.size == 0) {
 		gnutls_assert();
 		return GNUTLS_E_INTERNAL_ERROR;
 	}
 
-	ret = _gnutls_rnd(GNUTLS_RND_NONCE, &rnd, 1);
-	if (ret < 0) {
-		gnutls_assert();
-		return ret;
-	}
-
-	entry->salt.size = (rnd % 10) + 9;
-
 	entry->v.data = gnutls_malloc(20);
 	entry->v.size = 20;
 	if (entry->v.data == NULL) {
@@ -406,20 +406,36 @@ static int _randomize_pwd_entry(SRP_PWD_ENTRY * entry)
 		return ret;
 	}
 
-	entry->salt.data = gnutls_malloc(entry->salt.size);
+	/* Always allocate and work with the output size of the MAC,
+	 * even if they don't need salts that long, for convenience.
+	 *
+	 * In case an error occurs 'entry' (and the salt inside)
+	 * is deallocated by our caller: _gnutls_srp_pwd_read_entry().
+	 */
+	entry->salt.data = gnutls_malloc(me->output_size);
 	if (entry->salt.data == NULL) {
 		gnutls_assert();
 		return GNUTLS_E_MEMORY_ERROR;
 	}
 
-	ret =
-	    _gnutls_rnd(GNUTLS_RND_NONCE, entry->salt.data,
-			entry->salt.size);
+	ret = _gnutls_mac_init(&ctx, me, sc->fake_salt_seed.data,
+			sc->fake_salt_seed.size);
+
 	if (ret < 0) {
 		gnutls_assert();
 		return ret;
 	}
 
+	_gnutls_mac(&ctx, "salt", 4);
+	_gnutls_mac(&ctx, username, username_len);
+	_gnutls_mac_deinit(&ctx, entry->salt.data);
+
+	/* Set length to the actual number of bytes they asked for.
+	 * This is always less than or equal to the output size of
+	 * the MAC, enforced by gnutls_srp_set_server_fake_salt_seed().
+	 */
+	entry->salt.size = sc->fake_salt_length;
+
 	return 0;
 }
 
diff --git a/lib/gnutls_srp.c b/lib/gnutls_srp.c
index 1ee7473..8b5bbc3 100644
--- a/lib/gnutls_srp.c
+++ b/lib/gnutls_srp.c
@@ -33,6 +33,7 @@
 #include <gnutls_num.h>
 #include <gnutls_helper.h>
 #include <algorithms.h>
+#include <random.h>
 
 #include "debug.h"
 
@@ -489,10 +490,23 @@ void gnutls_srp_free_server_credentials(gnutls_srp_server_credentials_t sc)
 {
 	gnutls_free(sc->password_file);
 	gnutls_free(sc->password_conf_file);
+	_gnutls_free_datum(&sc->fake_salt_seed);
 
 	gnutls_free(sc);
 }
 
+/* Size of the default (random) seed if
+ * gnutls_srp_set_server_fake_salt_seed() is not called to set
+ * a seed.
+ */
+#define DEFAULT_FAKE_SALT_SEED_SIZE 20
+
+/* Size of the fake salts generated if
+ * gnutls_srp_set_server_fake_salt_seed() is not called to set
+ * another size.
+ */
+#define DEFAULT_FAKE_SALT_SIZE 16
+
 /**
  * gnutls_srp_allocate_server_credentials:
  * @sc: is a pointer to a #gnutls_srp_server_credentials_t structure.
@@ -507,12 +521,36 @@ int
 gnutls_srp_allocate_server_credentials(gnutls_srp_server_credentials_t *
 				       sc)
 {
+	int ret;
 	*sc = gnutls_calloc(1, sizeof(srp_server_cred_st));
 
 	if (*sc == NULL)
 		return GNUTLS_E_MEMORY_ERROR;
 
+	(*sc)->fake_salt_seed.size = DEFAULT_FAKE_SALT_SEED_SIZE;
+	(*sc)->fake_salt_seed.data = gnutls_malloc(
+					DEFAULT_FAKE_SALT_SEED_SIZE);
+	if ((*sc)->fake_salt_seed.data == NULL) {
+		ret = GNUTLS_E_MEMORY_ERROR;
+		gnutls_assert();
+		goto cleanup;
+	}
+
+	ret = _gnutls_rnd(GNUTLS_RND_RANDOM, (*sc)->fake_salt_seed.data,
+				DEFAULT_FAKE_SALT_SEED_SIZE);
+
+	if (ret < 0) {
+		gnutls_assert();
+		goto cleanup;
+	}
+
+	(*sc)->fake_salt_length = DEFAULT_FAKE_SALT_SIZE;
 	return 0;
+
+cleanup:
+	_gnutls_free_datum(&(*sc)->fake_salt_seed);
+	gnutls_free(*sc);
+	return ret;
 }
 
 /**
@@ -586,13 +624,13 @@ gnutls_srp_set_server_credentials_file(gnutls_srp_server_credentials_t res,
  * in using the gnutls_malloc(). For convenience @prime and @generator
  * may also be one of the static parameters defined in gnutls.h.
  *
- * In case the callback returned a negative number then gnutls will
- * assume that the username does not exist.
- *
  * In order to prevent attackers from guessing valid usernames,
  * if a user does not exist, g and n values should be filled in
  * using a random user's parameters. In that case the callback must
  * return the special value (1).
+ * See #gnutls_srp_set_server_fake_salt_seed too.
+ * If this is not required for your application, return a negative
+ * number from the callback to abort the handshake.
  *
  * The callback function will only be called once per handshake.
  * The callback function should return 0 on success, while
@@ -745,4 +783,51 @@ void gnutls_srp_set_prime_bits(gnutls_session_t session, unsigned int bits)
 	session->internals.srp_prime_bits = bits;
 }
 
+/**
+ * gnutls_srp_set_server_fake_salt_seed:
+ * @cred: is a #gnutls_srp_server_credentials_t structure
+ * @seed: is the seed data, only needs to be valid until the function
+ * returns; size of the seed must be greater than zero
+ * @salt_length: is the length of the generated fake salts
+ *
+ * This function sets the seed that is used to generate salts for
+ * invalid (non-existent) usernames.
+ *
+ * In order to prevent attackers from guessing valid usernames,
+ * when a user does not exist gnutls generates a salt and a verifier
+ * and proceeds with the protocol as usual.
+ * The authentication will ultimately fail, but the client cannot tell
+ * whether the username is valid (exists) or invalid.
+ *
+ * If an attacker learns the seed, given a salt (which is part of the
+ * handshake) which was generated when the seed was in use, it can tell
+ * whether or not the authentication failed because of an unknown username.
+ * This seed cannot be used to reveal application data or passwords.
+ *
+ * @salt_length should represent the salt length your application uses.
+ * Generating fake salts longer than 20 bytes is not supported.
+ *
+ * By default the seed is a random value, different each time a
+ * #gnutls_srp_server_credentials_t is allocated and fake salts are
+ * 16 bytes long.
+ *
+ * Since: 3.3.0
+ **/
+void
+gnutls_srp_set_server_fake_salt_seed(gnutls_srp_server_credentials_t cred,
+				     const gnutls_datum_t * seed,
+				     unsigned int salt_length)
+{
+	_gnutls_free_datum(&cred->fake_salt_seed);
+	_gnutls_set_datum(&cred->fake_salt_seed, seed->data, seed->size);
+
+	/* Cap the salt length at the output size of the MAC algorithm
+	 * we are using to generate the fake salts.
+	 */
+	const mac_entry_st * me = mac_to_entry(SRP_FAKE_SALT_MAC);
+	const size_t mac_len = me->output_size;
+
+	cred->fake_salt_length = (salt_length < mac_len ? salt_length : mac_len);
+}
+
 #endif				/* ENABLE_SRP */
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index 485484d..151a31d 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -1679,6 +1679,12 @@ int gnutls_srp_base64_decode(const gnutls_datum_t * b64_data, char *result,
 int gnutls_srp_base64_decode_alloc(const gnutls_datum_t * b64_data,
 				   gnutls_datum_t * result);
 
+void
+gnutls_srp_set_server_fake_salt_seed(gnutls_srp_server_credentials_t
+				     sc,
+				     const gnutls_datum_t * seed,
+				     unsigned int salt_length);
+
 /* PSK stuff */
 typedef struct gnutls_psk_server_credentials_st
 *gnutls_psk_server_credentials_t;
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index c92d6db..9f24a6e 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -953,6 +953,7 @@ GNUTLS_3_1_0 {
 	gnutls_x509_name_constraints_get_excluded;
 	gnutls_x509_name_constraints_check;
 	gnutls_x509_name_constraints_check_crt;
+	gnutls_srp_set_server_fake_salt_seed;
 } GNUTLS_3_0_0;
 
 GNUTLS_PRIVATE {
-- 
1.8.5.3





More information about the Gnutls-devel mailing list