[gnutls-devel] [PATCH] srp: Add resistance against guessing usernames
Attila Molnar
attilamolnar at hush.com
Tue Feb 18 00:59:20 CET 2014
>From 3d05730d668d4dd5be31a93bf596be2ec1a8dcfe 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 | 2 +
lib/auth/srp_passwd.c | 52 ++++++++++++++++---------
lib/gnutls_srp.c | 84 +++++++++++++++++++++++++++++++++++++++--
lib/includes/gnutls/gnutls.h.in | 6 +++
lib/libgnutls.map | 1 +
5 files changed, 124 insertions(+), 21 deletions(-)
diff --git a/lib/auth/srp.h b/lib/auth/srp.h
index 2bfce81..c11aaac 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 */
diff --git a/lib/auth/srp_passwd.c b/lib/auth/srp_passwd.c
index b25fe9f..61599ce 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(GNUTLS_MAC_SHA1);
+ 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 20 bytes 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(20);
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 <= 20 bytes, 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..b798898 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,44 @@ 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 salts are 16
+ * bytes long.
+ *
+ * Since: 3.3
+ **/
+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);
+ cred->fake_salt_length = (salt_length < 20 ? salt_length : 20);
+}
+
#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 b4cdd3c..dd4bb53 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -952,6 +952,7 @@ GNUTLS_3_1_0 {
gnutls_x509_name_constraints_get_permitted;
gnutls_x509_name_constraints_get_excluded;
gnutls_x509_name_constraints_check;
+ gnutls_srp_set_server_fake_salt_seed;
} GNUTLS_3_0_0;
GNUTLS_PRIVATE {
--
More information about the Gnutls-devel
mailing list