]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
srp: Add resistance against guessing usernames
authorAttila Molnar <attilamolnar@hush.com>
Thu, 20 Feb 2014 05:28:12 +0000 (06:28 +0100)
committerNikos Mavrogiannopoulos <nmav@redhat.com>
Thu, 20 Feb 2014 08:33:30 +0000 (09:33 +0100)
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@hush.com>
lib/auth/srp.h
lib/auth/srp_passwd.c
lib/gnutls_srp.c
lib/includes/gnutls/gnutls.h.in
lib/libgnutls.map

index 2bfce81474ac9e6e5f4292f47787e9c44077d085..63b9de22b4ccc1f9d01722206764d8d48429043f 100644 (file)
@@ -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
index b25fe9f683ef1c5f713f1bc833b82e86df39cbb2..5572dc993808454676b92f5b423c16b222feb852 100644 (file)
 #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;
 }
 
index 1ee7473703352c7aa1aa00a883662e1a50417992..8b5bbc37bf4d38e22dba67bc3748771ee745c92b 100644 (file)
@@ -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 */
index 485484d856074b7b1200f6be8babb82ae07d4f16..151a31dad561bfba3aa9cb268acd1646e0b5d9b8 100644 (file)
@@ -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;
index c92d6db4fb9829c0b1914be7446251c6cea2d535..9f24a6e8bab59f666cca467c6eec4a6a68ff89b7 100644 (file)
@@ -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 {