hostapd: Verify availability of random data when using WPA/WPA2
authorJouni Malinen <j@w1.fi>
Wed, 24 Nov 2010 11:08:03 +0000 (13:08 +0200)
committerJouni Malinen <j@w1.fi>
Wed, 24 Nov 2010 11:08:03 +0000 (13:08 +0200)
On Linux, verify that the kernel entropy pool is capable of providing
strong random data before allowing WPA/WPA2 connection to be
established. If 20 bytes of data cannot be read from /dev/random,
force first two 4-way handshakes to fail while collecting entropy
into the internal pool in hostapd. After that, give up on /dev/random
and allow the AP to function based on the combination of /dev/urandom
and whatever data has been collected into the internal entropy pool.

src/ap/wpa_auth.c
src/ap/wpa_auth_i.h
src/crypto/random.c
src/crypto/random.h

index 851612e..397fa98 100644 (file)
@@ -345,6 +345,12 @@ static struct wpa_group * wpa_group_init(struct wpa_authenticator *wpa_auth,
 
        wpa_group_set_key_len(group, wpa_auth->conf.wpa_group);
 
+       if (random_pool_ready() != 1) {
+               wpa_printf(MSG_INFO, "WPA: Not enough entropy in random pool "
+                          "for secure operations - update keys later when "
+                          "the first station connects");
+       }
+
        /*
         * Set initial GMK/Counter value here. The actual values that will be
         * used in negotiations will be set once the first station tries to
@@ -825,6 +831,25 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
                        return;
                }
                random_add_randomness(key->key_nonce, WPA_NONCE_LEN);
+               if (sm->group->reject_4way_hs_for_entropy) {
+                       /*
+                        * The system did not have enough entropy to generate
+                        * strong random numbers. Reject the first 4-way
+                        * handshake(s) and collect some entropy based on the
+                        * information from it. Once enough entropy is
+                        * available, the next atempt will trigger GMK/Key
+                        * Counter update and the station will be allowed to
+                        * continue.
+                        */
+                       wpa_printf(MSG_DEBUG, "WPA: Reject 4-way handshake to "
+                                  "collect more entropy for random number "
+                                  "generation");
+                       sm->group->reject_4way_hs_for_entropy = FALSE;
+                       random_mark_pool_ready();
+                       sm->group->first_sta_seen = FALSE;
+                       wpa_sta_disconnect(wpa_auth, sm->addr);
+                       return;
+               }
                if (wpa_parse_kde_ies((u8 *) (key + 1), key_data_length,
                                      &kde) < 0) {
                        wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
@@ -1465,6 +1490,11 @@ static void wpa_group_first_station(struct wpa_authenticator *wpa_auth,
         */
        wpa_printf(MSG_DEBUG, "WPA: Re-initialize GMK/Counter on first "
                   "station");
+       if (random_pool_ready() != 1) {
+               wpa_printf(MSG_INFO, "WPA: Not enough entropy in random pool "
+                          "to proceed - reject first 4-way handshake");
+               group->reject_4way_hs_for_entropy = TRUE;
+       }
        wpa_group_init_gmk_and_counter(wpa_auth, group);
        wpa_gtk_update(wpa_auth, group);
        wpa_group_config_group_keys(wpa_auth, group);
index bc6962f..3173144 100644 (file)
@@ -146,6 +146,7 @@ struct wpa_group {
        u8 GNonce[WPA_NONCE_LEN];
        Boolean changed;
        Boolean first_sta_seen;
+       Boolean reject_4way_hs_for_entropy;
 #ifdef CONFIG_IEEE80211W
        u8 IGTK[2][WPA_IGTK_LEN];
        int GN_igtk, GM_igtk;
index 33eae4d..9ea7ee6 100644 (file)
@@ -29,6 +29,9 @@
  */
 
 #include "utils/includes.h"
+#ifdef __linux__
+#include <fcntl.h>
+#endif /* __linux__ */
 
 #include "utils/common.h"
 #include "sha1.h"
 #define POOL_TAP4 7
 #define POOL_TAP5 1
 #define EXTRACT_LEN 16
+#define MIN_READY_MARK 2
 
 static u32 pool[POOL_WORDS];
 static unsigned int input_rotate = 0;
 static unsigned int pool_pos = 0;
-static const u8 dummy_key[20];
+static u8 dummy_key[20];
+static size_t dummy_key_avail = 0;
+static unsigned int own_pool_ready = 0;
 
 #define MIN_COLLECT_ENTROPY 1000
 static unsigned int entropy = 0;
+static unsigned int total_collected = 0;
 
 
 static u32 __ROL32(u32 x, u32 y)
@@ -135,6 +142,7 @@ void random_add_randomness(const void *buf, size_t len)
        wpa_hexdump_key(MSG_EXCESSIVE, "random pool",
                        (const u8 *) pool, sizeof(pool));
        entropy++;
+       total_collected++;
 }
 
 
@@ -174,3 +182,77 @@ int random_get_bytes(void *buf, size_t len)
 
        return ret;
 }
+
+
+int random_pool_ready(void)
+{
+#ifdef __linux__
+       int fd;
+       ssize_t res;
+
+       /*
+        * Make sure that there is reasonable entropy available before allowing
+        * some key derivation operations to proceed.
+        */
+
+       if (dummy_key_avail == sizeof(dummy_key))
+               return 1; /* Already initialized - good to continue */
+
+       /*
+        * Try to fetch some more data from the kernel high quality
+        * /dev/random. There may not be enough data available at this point,
+        * so use non-blocking read to avoid blocking the application
+        * completely.
+        */
+       fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+       if (fd < 0) {
+               int error = errno;
+               perror("open(/dev/random)");
+               wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s",
+                          strerror(error));
+               return -1;
+       }
+
+       res = read(fd, dummy_key + dummy_key_avail,
+                  sizeof(dummy_key) - dummy_key_avail);
+       if (res < 0) {
+               wpa_printf(MSG_ERROR, "random: Cannot read from /dev/random: "
+                          "%s", strerror(errno));
+               res = 0;
+       }
+       wpa_printf(MSG_DEBUG, "random: Got %u/%u bytes from "
+                  "/dev/random", (unsigned) res,
+                  (unsigned) (sizeof(dummy_key) - dummy_key_avail));
+       dummy_key_avail += res;
+       close(fd);
+
+       if (dummy_key_avail == sizeof(dummy_key))
+               return 1;
+
+       wpa_printf(MSG_INFO, "random: Only %u/%u bytes of strong "
+                  "random data available from /dev/random",
+                  (unsigned) dummy_key_avail, (unsigned) sizeof(dummy_key));
+
+       if (own_pool_ready >= MIN_READY_MARK ||
+           total_collected + 10 * own_pool_ready > MIN_COLLECT_ENTROPY) {
+               wpa_printf(MSG_INFO, "random: Allow operation to proceed "
+                          "based on internal entropy");
+               return 1;
+       }
+
+       wpa_printf(MSG_INFO, "random: Not enough entropy pool available for "
+                  "secure operations");
+       return 0;
+#else /* __linux__ */
+       /* TODO: could do similar checks on non-Linux platforms */
+       return 1;
+#endif /* __linux__ */
+}
+
+
+void random_mark_pool_ready(void)
+{
+       own_pool_ready++;
+       wpa_printf(MSG_DEBUG, "random: Mark internal entropy pool to be "
+                  "ready (count=%u/%u)", own_pool_ready, MIN_READY_MARK);
+}
index 65a1900..7b62577 100644 (file)
 #ifdef CONFIG_NO_RANDOM_POOL
 #define random_add_randomness(b, l) do { } while (0)
 #define random_get_bytes(b, l) os_get_random((b), (l))
+#define random_pool_ready() 1
+#define random_mark_pool_ready() do { } while (0)
 #else /* CONFIG_NO_RANDOM_POOL */
 void random_add_randomness(const void *buf, size_t len);
 int random_get_bytes(void *buf, size_t len);
+int random_pool_ready(void);
+void random_mark_pool_ready(void);
 #endif /* CONFIG_NO_RANDOM_POOL */
 
 #endif /* RANDOM_H */