2016-10-25 22:45:23

by Jouni Malinen

[permalink] [raw]
Subject: [PATCH 5/8] cfg80211: Add KEK/nonces for FILS association frames

The new nl80211 attributes can be used to provide KEK and nonces to
allow the driver to encrypt and decrypt FILS (Re)Association
Request/Response frames in station mode.

Signed-off-by: Jouni Malinen <[email protected]>
---
include/linux/ieee80211.h | 3 +++
include/net/cfg80211.h | 9 +++++++++
include/uapi/linux/nl80211.h | 8 ++++++++
net/wireless/nl80211.c | 17 +++++++++++++++++
4 files changed, 37 insertions(+)

diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index 9a523d9..a39beb9 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -2076,6 +2076,9 @@ enum ieee80211_key_len {
#define IEEE80211_GCMP_MIC_LEN 16
#define IEEE80211_GCMP_PN_LEN 6

+#define FILS_NONCE_LEN 16
+#define FILS_MAX_KEK_LEN 64
+
/* Public action codes */
enum ieee80211_pub_actioncode {
WLAN_PUB_ACTION_EXT_CHANSW_ANN = 4,
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 2667917..a3045f1 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1842,6 +1842,12 @@ enum cfg80211_assoc_req_flags {
* @ht_capa_mask: The bits of ht_capa which are to be used.
* @vht_capa: VHT capability override
* @vht_capa_mask: VHT capability mask indicating which fields to use
+ * @fils_kek: FILS KEK for protecting (Re)Association Request/Response frame or
+ * %NULL if FILS is not used.
+ * @fils_kek_len: Length of fils_kek in octets
+ * @fils_nonces: FILS nonces (part of AAD) for protecting (Re)Association
+ * Request/Response frame or %NULL if FILS is not used. This field starts
+ * with 16 octets of STA Nonce followed by 16 octets of AP Nonce.
*/
struct cfg80211_assoc_request {
struct cfg80211_bss *bss;
@@ -1853,6 +1859,9 @@ struct cfg80211_assoc_request {
struct ieee80211_ht_cap ht_capa;
struct ieee80211_ht_cap ht_capa_mask;
struct ieee80211_vht_cap vht_capa, vht_capa_mask;
+ const u8 *fils_kek;
+ size_t fils_kek_len;
+ const u8 *fils_nonces;
};

/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 5d36e83..b468a89 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1938,6 +1938,11 @@ enum nl80211_commands {
* attribute.
* @NL80211_ATTR_NAN_MATCH: used to report a match. This is a nested attribute.
* See &enum nl80211_nan_match_attributes.
+ * @NL80211_ATTR_FILS_KEK: KEK for FILS (Re)Association Request/Response frame
+ * protection.
+ * @NL80211_ATTR_FILS_NONCES: Nonces (part of AAD) for FILS (Re)Association
+ * Request/Response frame protection. This attribute contains the 16 octet
+ * STA Nonce followed by 16 octets of AP Nonce.
*
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
@@ -2338,6 +2343,9 @@ enum nl80211_attrs {
NL80211_ATTR_NAN_FUNC,
NL80211_ATTR_NAN_MATCH,

+ NL80211_ATTR_FILS_KEK,
+ NL80211_ATTR_FILS_NONCES,
+
/* add attributes here, update the policy in nl80211.c */

__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ebd7eaf..a06145c 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -414,6 +414,10 @@ enum nl80211_multicast_groups {
[NL80211_ATTR_NAN_MASTER_PREF] = { .type = NLA_U8 },
[NL80211_ATTR_NAN_DUAL] = { .type = NLA_U8 },
[NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
+ [NL80211_ATTR_FILS_KEK] = { .type = NLA_BINARY,
+ .len = FILS_MAX_KEK_LEN },
+ [NL80211_ATTR_FILS_NONCES] = { .type = NLA_BINARY,
+ .len = 2 * FILS_NONCE_LEN },
};

/* policy for the key attributes */
@@ -8019,6 +8023,19 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
req.flags |= ASSOC_REQ_USE_RRM;
}

+ if (info->attrs[NL80211_ATTR_FILS_KEK]) {
+ req.fils_kek = nla_data(info->attrs[NL80211_ATTR_FILS_KEK]);
+ req.fils_kek_len = nla_len(info->attrs[NL80211_ATTR_FILS_KEK]);
+ }
+
+ if (info->attrs[NL80211_ATTR_FILS_NONCES]) {
+ if (nla_len(info->attrs[NL80211_ATTR_FILS_NONCES]) !=
+ 2 * FILS_NONCE_LEN)
+ return -EINVAL;
+ req.fils_nonces =
+ nla_data(info->attrs[NL80211_ATTR_FILS_NONCES]);
+ }
+
err = nl80211_crypto_settings(rdev, info, &req.crypto, 1);
if (!err) {
wdev_lock(dev->ieee80211_ptr);
--
1.9.1


2016-10-26 21:04:54

by Jouni Malinen

[permalink] [raw]
Subject: Re: [PATCH 7/8] mac80211: FILS AEAD protection for station mode association frames

On Wed, Oct 26, 2016 at 07:49:59AM +0200, Johannes Berg wrote:
> > +static u8 *fils_find_session(u8 *pos, u8 *end)

> Hmm. I think we should try to write this in terms of cfg80211_find_ie,
> or perhaps cfg80211_find_ie_match, maybe we need to extend those but
> this won't be the only one using the 255 escape.
>=20
> Perhaps just making the eid passed there be a u16, and extending the
> EID definitions appropriately?

Will do that based on our discussion on the details (a new wrapper
function).

> > + if (!session) {
> > + sdata_info(sdata,

> Should we really print the message this prominently? It seems like an
> error case that we may not want to log at all, in case somebody tries
> to flood us with such frames?
...
> Likewise here. Perhaps make these mlme_dbg() or so instead?

These are pretty useful messages for debugging at least now that FILS is
still so new.. Once it gets more mature and has documented
interoperability between independent implementations, we may consider
removing the messages since they would not really show up during normal
operations and would not help much an actual end user. Anyway, I'll
replace them with mlme_dbg() for now.

> > + if (req->fils_nonces)
> > + assoc_data_len +=3D 2 * FILS_NONCE_LEN;
>=20
> Is this really correct? It seems like a bit of an artifact of initially
> having had the nonces in a variable part of the struct?
>=20
> Or perhaps they have to go into the frame in some place that I missed?
> Please add a comment if so.

Ah, looks like I forgot that there and req->fils_kek_len for that
matter. I had initially thought of adding these as dynamically allocated
components at the end of the buffer, but after seeing how req->ie[] was
used, gave up on that extra complexity and simply used fixed size arrays
since both the KEK and FILS nonces do have a known fixed size and we can
easily "waste" that memory in struct ieee80211_mgd_assoc_data for
non-FILS cases without causing any noticeable impact.

> Also, you're never checking req->fils_nonces_set, so you can get rid of
> that.

Indeed. I'll make the cfg80211 patch enforce that both the KEK and
nonces are set since they are both needed for all FILS cases and remove
fils_nonces_set from mac80211.

--=20
Jouni Malinen PGP id EFC895FA=

2016-10-26 09:23:22

by Jouni Malinen

[permalink] [raw]
Subject: Re: [PATCH 8/8] mac80211: Claim Fast Initial Link Setup (FILS) support

On Wed, Oct 26, 2016 at 07:50:36AM +0200, Johannes Berg wrote:
> On Wed, 2016-10-26 at 01:44 +0300, Jouni Malinen wrote:
> > With the previous commits, initial FILS support is now functional in
> > mac80211-based drivers for both AP and stations roles.
>=20
> That's a bit misleading, I guess AP role is handled entirely in
> hostapd? You documented the extended feature bit to explicitly mean
> station role only :)

Yeah.. In case of mac80211, there was not really changes needed in the
kernel side for AP mode. That may be different with non-mac80211
drivers, though, since they might be easier to handle with the AES-SIV
operations for associations frames handled within the driver.

Would you prefer to split that NL80211_EXT_FEATURE_FILS into two
separate values (_STA and _AP) and have mac80211 advertise both? Or just
add a _STA only case for now and see what we need to do with
NL80211_ATTR_DEVICE_AP_SME cases separately once such a thing is in in
functional state?

--=20
Jouni Malinen PGP id EFC895FA=

2016-10-26 09:18:18

by Jouni Malinen

[permalink] [raw]
Subject: Re: [PATCH 5/8] cfg80211: Add KEK/nonces for FILS association frames

T24gV2VkLCBPY3QgMjYsIDIwMTYgYXQgMDc6MzY6MjdBTSArMDIwMCwgSm9oYW5uZXMgQmVyZyB3
cm90ZToNCj4gPiArKysgYi9uZXQvd2lyZWxlc3Mvbmw4MDIxMS5jDQo+ID4gKwlbTkw4MDIxMV9B
VFRSX0ZJTFNfS0VLXSA9IHsgLnR5cGUgPSBOTEFfQklOQVJZLA0KPiA+ICsJCQkJwqDCoMKgwqAu
bGVuID0gRklMU19NQVhfS0VLX0xFTiB9LA0KPiA+ICsJW05MODAyMTFfQVRUUl9GSUxTX05PTkNF
U10gPSB7IC50eXBlID0gTkxBX0JJTkFSWSwNCj4gPiArCQkJCcKgwqDCoMKgwqDCoMKgLmxlbiA9
IDIgKiBGSUxTX05PTkNFX0xFTiB9LA0KDQo+IElmIHlvdSByZW1vdmUgdGhlIHR5cGUgPSBOTEFf
QklOQVJZIGFuZCBqdXN0IGxlYXZlIHRoZSB0eXBlIHplcm8sIHRoZW4NCj4geW91J2xsIGdldCAq
bWluaW11bSogbGVuZ3RoIHZhbGlkYXRpb24sIHJhdGhlciB0aGFuIGxpbWl0aW5nIHRoZQ0KPiBt
YXhpbXVtIGxlbmd0aC4gVGhhdCBzZWVtcyBtb3JlIGFwcHJvcHJpYXRlIGZvciB0aGUgbm9uY2Vz
Pw0KDQpGSUxTX0tFSyBpcyB2YXJpYWJsZSBsZW5ndGgsIGJ1dCBGSUxTX05PTkNFUyBzaG91bGQg
YmUgZXhhY3RseSAyICoNCkZJTFNfTk9OQ0VfTEVOLiBJIGRpZG4ndCByZW1lbWJlciB0aGUgbWlu
aW11bSBsZW5ndGggY2FzZSBoZXJlLCBidXQgeWVzLA0KdGhhdCBzb3VuZHMgcmVhc29uYWJsZSBm
b3IgRklMU19OT05DRVMuDQoNCj4gPiArCWlmIChpbmZvLT5hdHRyc1tOTDgwMjExX0FUVFJfRklM
U19OT05DRVNdKSB7DQo+ID4gKwkJaWYgKG5sYV9sZW4oaW5mby0+YXR0cnNbTkw4MDIxMV9BVFRS
X0ZJTFNfTk9OQ0VTXSkNCj4gPiAhPQ0KPiA+ICsJCcKgwqDCoMKgMiAqIEZJTFNfTk9OQ0VfTEVO
KQ0KPiA+ICsJCQlyZXR1cm4gLUVJTlZBTDsNCj4gDQo+IFlvdSdyZSB2YWxpZGF0aW5nIHRoZSAq
ZXhhY3QqIGxlbmd0aCBoZXJlLCB3aGljaCB1bmZvcnR1bmF0ZWx5IG5sYXR0cg0KPiBkb2Vzbid0
IHN1cHBvcnQgcmlnaHQgbm93LCBidXQgcGVyaGFwcyB3ZSBjYW4gbGl2ZSB3aXRoIGNoZWNraW5n
IHRoYXQNCj4gaXQncyBhdCBsZWFzdCB0aGF0IG1hbnkgYnl0ZXMsIGFuZCB1c2luZyBvbmx5IDIq
bm9uY2VzPyBXZSBkbyB0aGF0IGZvcg0KPiBtb3N0IG90aGVyIGF0dHJpYnV0ZXMgKGxpa2UgTUFD
IGFkZHJlc3NlcykuDQoNClRoaXMgd2FzIGJlY2F1c2Ugb2YgdGhlIC5sZW4gYWJvdmUgZW5kaW5n
IHVwIGFsbG93aW5nIHNob3J0ZXIgdmFsdWVzLi4NClNpbmNlIHdlIGRvIHRoYXQgbWluaW11bSBs
ZW5ndGggY2hlY2sgb25seSBmb3IgbnVtYmVyIG9mIG90aGVyDQphdHRyaWJ1dGVzLCBJIGd1ZXNz
IHdlIGNhbiBkbyBpdCBoZXJlIGFzIHdlbGwgYW5kIGlnbm9yZSB3aGF0ZXZlciBlbHNlDQp0aGUg
dXNlciBzcGFjZSBtaWdodCBoYXZlIGFkZGVkIGluY29ycmVjdGx5Lg0KDQo+IE9yIGRvIHdlIGV4
cGVjdCB0byBleHRlbmQgdGhpcyB0byBtb3JlIHRoYW4gMiBub25jZXMgaW4gdGhlIGZ1dHVyZSwg
YXQNCj4gd2hpY2ggcG9pbnQgd2UnbGwgbmVlZCB0aGUgbGVuZ3RoPw0KDQpObywgdGhpcyBzaG91
bGQgcmVtYWluIGV4YWN0bHkgdHdvIG5vbmNlcyBhbmQgZWFjaCBvZiB0aG9zZSBoYXZpbmcNCmV4
YWN0bHkgMTYgb2N0ZXRzLg0KDQotLSANCkpvdW5pIE1hbGluZW4gICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgIFBHUCBpZCBFRkM4OTVGQQ==

2016-10-25 22:45:23

by Jouni Malinen

[permalink] [raw]
Subject: [PATCH 7/8] mac80211: FILS AEAD protection for station mode association frames

This adds support for encrypting (Re)Association Request frame and
decryption (Re)Association Response frame when using FILS in station
mode.

Signed-off-by: Jouni Malinen <[email protected]>
---
net/mac80211/Makefile | 1 +
net/mac80211/aes_cmac.c | 8 +-
net/mac80211/aes_cmac.h | 4 +
net/mac80211/fils_aead.c | 354 +++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/fils_aead.h | 19 +++
net/mac80211/ieee80211_i.h | 5 +
net/mac80211/mlme.c | 34 ++++-
7 files changed, 420 insertions(+), 5 deletions(-)
create mode 100644 net/mac80211/fils_aead.c
create mode 100644 net/mac80211/fils_aead.h

diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index f9137a8..0b202b3 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -19,6 +19,7 @@ mac80211-y := \
aes_gcm.o \
aes_cmac.o \
aes_gmac.o \
+ fils_aead.o \
cfg.o \
ethtool.o \
rx.o \
diff --git a/net/mac80211/aes_cmac.c b/net/mac80211/aes_cmac.c
index bdf0790..d0bd5ff 100644
--- a/net/mac80211/aes_cmac.c
+++ b/net/mac80211/aes_cmac.c
@@ -23,7 +23,7 @@
#define AAD_LEN 20


-static void gf_mulx(u8 *pad)
+void gf_mulx(u8 *pad)
{
int i, carry;

@@ -35,9 +35,9 @@ static void gf_mulx(u8 *pad)
pad[AES_BLOCK_SIZE - 1] ^= 0x87;
}

-static void aes_cmac_vector(struct crypto_cipher *tfm, size_t num_elem,
- const u8 *addr[], const size_t *len, u8 *mac,
- size_t mac_len)
+void aes_cmac_vector(struct crypto_cipher *tfm, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac,
+ size_t mac_len)
{
u8 cbc[AES_BLOCK_SIZE], pad[AES_BLOCK_SIZE];
const u8 *pos, *end;
diff --git a/net/mac80211/aes_cmac.h b/net/mac80211/aes_cmac.h
index 3702041..c827e1d 100644
--- a/net/mac80211/aes_cmac.h
+++ b/net/mac80211/aes_cmac.h
@@ -11,6 +11,10 @@

#include <linux/crypto.h>

+void gf_mulx(u8 *pad);
+void aes_cmac_vector(struct crypto_cipher *tfm, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac,
+ size_t mac_len);
struct crypto_cipher *ieee80211_aes_cmac_key_setup(const u8 key[],
size_t key_len);
void ieee80211_aes_cmac(struct crypto_cipher *tfm, const u8 *aad,
diff --git a/net/mac80211/fils_aead.c b/net/mac80211/fils_aead.c
new file mode 100644
index 0000000..358c2cb
--- /dev/null
+++ b/net/mac80211/fils_aead.c
@@ -0,0 +1,354 @@
+/*
+ * FILS AEAD for (Re)Association Request/Response frames
+ * Copyright 2016, Qualcomm Atheros, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <crypto/aes.h>
+#include <crypto/algapi.h>
+#include <crypto/skcipher.h>
+
+#include "ieee80211_i.h"
+#include "aes_cmac.h"
+#include "fils_aead.h"
+
+static int aes_s2v(struct crypto_cipher *tfm,
+ size_t num_elem, const u8 *addr[], size_t len[], u8 *v)
+{
+ u8 d[AES_BLOCK_SIZE], tmp[AES_BLOCK_SIZE];
+ size_t i;
+ const u8 *data[2];
+ size_t data_len[2], data_elems;
+
+ /* D = AES-CMAC(K, <zero>) */
+ memset(tmp, 0, AES_BLOCK_SIZE);
+ data[0] = tmp;
+ data_len[0] = AES_BLOCK_SIZE;
+ aes_cmac_vector(tfm, 1, data, data_len, d, AES_BLOCK_SIZE);
+
+ for (i = 0; i < num_elem - 1; i++) {
+ /* D = dbl(D) xor AES_CMAC(K, Si) */
+ gf_mulx(d); /* dbl */
+ aes_cmac_vector(tfm, 1, &addr[i], &len[i], tmp,
+ AES_BLOCK_SIZE);
+ crypto_xor(d, tmp, AES_BLOCK_SIZE);
+ }
+
+ if (len[i] >= AES_BLOCK_SIZE) {
+ /* len(Sn) >= 128 */
+ size_t j;
+ const u8 *pos;
+
+ /* T = Sn xorend D */
+
+ /* Use a temporary buffer to perform xorend on Sn (addr[i]) to
+ * avoid modifying the const input argument.
+ */
+ data[0] = addr[i];
+ data_len[0] = len[i] - AES_BLOCK_SIZE;
+ pos = addr[i] + data_len[0];
+ for (j = 0; j < AES_BLOCK_SIZE; j++)
+ tmp[j] = pos[j] ^ d[j];
+ data[1] = tmp;
+ data_len[1] = AES_BLOCK_SIZE;
+ data_elems = 2;
+ } else {
+ /* len(Sn) < 128 */
+ /* T = dbl(D) xor pad(Sn) */
+ gf_mulx(d); /* dbl */
+ memset(tmp, 0, AES_BLOCK_SIZE);
+ memcpy(tmp, addr[i], len[i]);
+ tmp[len[i]] = 0x80;
+ crypto_xor(d, tmp, AES_BLOCK_SIZE);
+ data[0] = d;
+ data_len[0] = sizeof(d);
+ data_elems = 1;
+ }
+ /* V = AES-CMAC(K, T) */
+ aes_cmac_vector(tfm, data_elems, data, data_len, v, AES_BLOCK_SIZE);
+
+ return 0;
+}
+
+/* Note: addr[] and len[] needs to have one extra slot at the end. */
+static int aes_siv_encrypt(const u8 *key, size_t key_len,
+ const u8 *plain, size_t plain_len,
+ size_t num_elem, const u8 *addr[],
+ size_t len[], u8 *out)
+{
+ u8 v[AES_BLOCK_SIZE];
+ struct crypto_cipher *tfm;
+ struct crypto_skcipher *tfm2;
+ struct skcipher_request *req;
+ int res;
+ struct scatterlist src[1], dst[1];
+ u8 *tmp;
+
+ key_len /= 2; /* S2V key || CTR key */
+
+ addr[num_elem] = plain;
+ len[num_elem] = plain_len;
+ num_elem++;
+
+ /* S2V */
+
+ tfm = crypto_alloc_cipher("aes", 0, 0);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+ /* K1 for S2V */
+ res = crypto_cipher_setkey(tfm, key, key_len);
+ if (!res)
+ res = aes_s2v(tfm, num_elem, addr, len, v);
+ crypto_free_cipher(tfm);
+ if (res)
+ return res;
+
+ /* Use a temporary buffer of the plaintext to handle need for
+ * overwriting this during AES-CTR.
+ */
+ tmp = kmalloc(plain_len, GFP_KERNEL);
+ if (!tmp) {
+ res = -ENOMEM;
+ goto fail;
+ }
+ memcpy(tmp, plain, plain_len);
+
+ /* IV for CTR before encrypted data */
+ memcpy(out, v, AES_BLOCK_SIZE);
+
+ /* Synthetic IV to be used as the initial counter in CTR:
+ * Q = V bitand (1^64 || 0^1 || 1^31 || 0^1 || 1^31)
+ */
+ v[8] &= 0x7f;
+ v[12] &= 0x7f;
+
+ /* CTR */
+
+ tfm2 = crypto_alloc_skcipher("ctr(aes)", 0, 0);
+ if (IS_ERR(tfm2)) {
+ kfree(tmp);
+ return PTR_ERR(tfm2);
+ }
+ /* K2 for CTR */
+ res = crypto_skcipher_setkey(tfm2, key + key_len, key_len);
+ if (res)
+ goto fail;
+
+ req = skcipher_request_alloc(tfm2, GFP_KERNEL);
+ if (!req) {
+ res = -ENOMEM;
+ goto fail;
+ }
+
+ sg_set_buf(&src[0], tmp, plain_len);
+ sg_set_buf(&dst[0], out + AES_BLOCK_SIZE, plain_len);
+ skcipher_request_set_crypt(req, src, dst, plain_len, v);
+ res = crypto_skcipher_encrypt(req);
+ skcipher_request_free(req);
+fail:
+ kfree(tmp);
+ crypto_free_skcipher(tfm2);
+ return res;
+}
+
+/* Note: addr[] and len[] needs to have one extra slot at the end. */
+static int aes_siv_decrypt(const u8 *key, size_t key_len,
+ const u8 *iv_crypt, size_t iv_c_len,
+ size_t num_elem, const u8 *addr[], size_t len[],
+ u8 *out)
+{
+ struct crypto_cipher *tfm;
+ struct crypto_skcipher *tfm2;
+ struct skcipher_request *req;
+ struct scatterlist src[1], dst[1];
+ size_t crypt_len;
+ int res;
+ u8 frame_iv[AES_BLOCK_SIZE], iv[AES_BLOCK_SIZE];
+ u8 check[AES_BLOCK_SIZE];
+
+ crypt_len = iv_c_len - AES_BLOCK_SIZE;
+ key_len /= 2; /* S2V key || CTR key */
+ addr[num_elem] = out;
+ len[num_elem] = crypt_len;
+ num_elem++;
+
+ memcpy(iv, iv_crypt, AES_BLOCK_SIZE);
+ memcpy(frame_iv, iv_crypt, AES_BLOCK_SIZE);
+
+ /* Synthetic IV to be used as the initial counter in CTR:
+ * Q = V bitand (1^64 || 0^1 || 1^31 || 0^1 || 1^31)
+ */
+ iv[8] &= 0x7f;
+ iv[12] &= 0x7f;
+
+ /* CTR */
+
+ tfm2 = crypto_alloc_skcipher("ctr(aes)", 0, 0);
+ if (IS_ERR(tfm2))
+ return PTR_ERR(tfm2);
+ /* K2 for CTR */
+ res = crypto_skcipher_setkey(tfm2, key + key_len, key_len);
+ if (res) {
+ crypto_free_skcipher(tfm2);
+ return res;
+ }
+
+ req = skcipher_request_alloc(tfm2, GFP_KERNEL);
+ if (!req) {
+ crypto_free_skcipher(tfm2);
+ return -ENOMEM;
+ }
+
+ sg_set_buf(&src[0], iv_crypt + AES_BLOCK_SIZE, crypt_len);
+ sg_set_buf(&dst[0], out, crypt_len);
+ skcipher_request_set_crypt(req, src, dst, crypt_len, iv);
+ res = crypto_skcipher_decrypt(req);
+ skcipher_request_free(req);
+ crypto_free_skcipher(tfm2);
+ if (res)
+ return res;
+
+ /* S2V */
+
+ tfm = crypto_alloc_cipher("aes", 0, 0);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+ /* K1 for S2V */
+ res = crypto_cipher_setkey(tfm, key, key_len);
+ if (!res)
+ res = aes_s2v(tfm, num_elem, addr, len, check);
+ crypto_free_cipher(tfm);
+ if (res)
+ return res;
+ if (memcmp(check, frame_iv, AES_BLOCK_SIZE) != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static u8 *fils_find_session(u8 *pos, u8 *end)
+{
+ while (end - pos > 2 && end - pos >= 2 + pos[1]) {
+ if (pos[0] == 255 && pos[1] == 1 + 8 && pos[2] == 4)
+ return pos;
+ pos += 2 + pos[1];
+ }
+
+ return NULL;
+}
+
+int fils_encrypt_assoc_req(struct sk_buff *skb,
+ struct ieee80211_mgd_assoc_data *assoc_data)
+{
+ struct ieee80211_mgmt *mgmt;
+ u8 *capab, *ies, *session, *encr;
+ const u8 *addr[5 + 1];
+ size_t len[5 + 1];
+ size_t crypt_len;
+
+ mgmt = (struct ieee80211_mgmt *)skb->data;
+ if (ieee80211_is_reassoc_req(mgmt->frame_control)) {
+ capab = (u8 *)&mgmt->u.reassoc_req.capab_info;
+ ies = mgmt->u.reassoc_req.variable;
+ } else {
+ capab = (u8 *)&mgmt->u.assoc_req.capab_info;
+ ies = mgmt->u.assoc_req.variable;
+ }
+
+ session = fils_find_session(ies, skb->data + skb->len);
+ if (!session)
+ return -EINVAL;
+ encr = session + 2 + 1 + 8; /* encrypt after FILS Session element */
+
+ /* AES-SIV AAD vectors */
+
+ /* The STA's MAC address */
+ addr[0] = mgmt->sa;
+ len[0] = ETH_ALEN;
+ /* The AP's BSSID */
+ addr[1] = mgmt->da;
+ len[1] = ETH_ALEN;
+ /* The STA's nonce */
+ addr[2] = assoc_data->fils_nonces;
+ len[2] = FILS_NONCE_LEN;
+ /* The AP's nonce */
+ addr[3] = &assoc_data->fils_nonces[FILS_NONCE_LEN];
+ len[3] = FILS_NONCE_LEN;
+ /* The (Re)Association Request frame from the Capability Information
+ * field to the FILS Session element (both inclusive).
+ */
+ addr[4] = capab;
+ len[4] = encr - capab;
+
+ crypt_len = skb->data + skb->len - encr;
+ skb_put(skb, AES_BLOCK_SIZE);
+ return aes_siv_encrypt(assoc_data->fils_kek, assoc_data->fils_kek_len,
+ encr, crypt_len, 1, addr, len, encr);
+}
+
+int fils_decrypt_assoc_resp(struct ieee80211_sub_if_data *sdata,
+ u8 *frame, size_t *frame_len,
+ struct ieee80211_mgd_assoc_data *assoc_data)
+{
+ struct ieee80211_mgmt *mgmt;
+ u8 *capab, *ies, *session, *encr;
+ const u8 *addr[5 + 1];
+ size_t len[5 + 1];
+ int res;
+ size_t crypt_len;
+
+ if (*frame_len < 24 + 6)
+ return -EINVAL;
+
+ mgmt = (struct ieee80211_mgmt *)frame;
+ capab = (u8 *)&mgmt->u.assoc_resp.capab_info;
+ ies = mgmt->u.assoc_resp.variable;
+ session = fils_find_session(ies, frame + *frame_len);
+ if (!session) {
+ sdata_info(sdata,
+ "No FILS Session element in (Re)Association Response frame from %pM",
+ mgmt->sa);
+ return -EINVAL;
+ }
+ encr = session + 2 + 1 + 8; /* decrypt after FILS Session element */
+
+ /* AES-SIV AAD vectors */
+
+ /* The AP's BSSID */
+ addr[0] = mgmt->sa;
+ len[0] = ETH_ALEN;
+ /* The STA's MAC address */
+ addr[1] = mgmt->da;
+ len[1] = ETH_ALEN;
+ /* The AP's nonce */
+ addr[2] = &assoc_data->fils_nonces[FILS_NONCE_LEN];
+ len[2] = FILS_NONCE_LEN;
+ /* The STA's nonce */
+ addr[3] = assoc_data->fils_nonces;
+ len[3] = FILS_NONCE_LEN;
+ /* The (Re)Association Response frame from the Capability Information
+ * field to the FILS Session element (both inclusive).
+ */
+ addr[4] = capab;
+ len[4] = encr - capab;
+
+ crypt_len = frame + *frame_len - encr;
+ if (crypt_len < AES_BLOCK_SIZE) {
+ sdata_info(sdata,
+ "Not enough room for AES-SIV data after FILS Session element in (Re)Association Response frame from %pM",
+ mgmt->sa);
+ return -EINVAL;
+ }
+ res = aes_siv_decrypt(assoc_data->fils_kek, assoc_data->fils_kek_len,
+ encr, crypt_len, 5, addr, len, encr);
+ if (res != 0) {
+ sdata_info(sdata,
+ "AES-SIV decryption of (Re)Association Response frame from %pM failed",
+ mgmt->sa);
+ return res;
+ }
+ *frame_len -= AES_BLOCK_SIZE;
+ return 0;
+}
diff --git a/net/mac80211/fils_aead.h b/net/mac80211/fils_aead.h
new file mode 100644
index 0000000..fbc6523
--- /dev/null
+++ b/net/mac80211/fils_aead.h
@@ -0,0 +1,19 @@
+/*
+ * FILS AEAD for (Re)Association Request/Response frames
+ * Copyright 2016, Qualcomm Atheros, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef FILS_AEAD_H
+#define FILS_AEAD_H
+
+int fils_encrypt_assoc_req(struct sk_buff *skb,
+ struct ieee80211_mgd_assoc_data *assoc_data);
+int fils_decrypt_assoc_resp(struct ieee80211_sub_if_data *sdata,
+ u8 *frame, size_t *frame_len,
+ struct ieee80211_mgd_assoc_data *assoc_data);
+
+#endif /* FILS_AEAD_H */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index b4e2b6c..4044cc3 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -401,6 +401,11 @@ struct ieee80211_mgd_assoc_data {

struct ieee80211_vht_cap ap_vht_cap;

+ u8 fils_nonces[2 * FILS_NONCE_LEN];
+ bool fils_nonces_set;
+ u8 fils_kek[FILS_MAX_KEK_LEN];
+ size_t fils_kek_len;
+
size_t ie_len;
u8 ie[];
};
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index b815f2d..702bf3d 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -30,6 +30,7 @@
#include "driver-ops.h"
#include "rate.h"
#include "led.h"
+#include "fils_aead.h"

#define IEEE80211_AUTH_TIMEOUT (HZ / 5)
#define IEEE80211_AUTH_TIMEOUT_LONG (HZ / 2)
@@ -652,6 +653,7 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
2 + sizeof(struct ieee80211_ht_cap) + /* HT */
2 + sizeof(struct ieee80211_vht_cap) + /* VHT */
assoc_data->ie_len + /* extra IEs */
+ (assoc_data->fils_kek_len ? 16 /* AES-SIV */ : 0) +
9, /* WMM */
GFP_KERNEL);
if (!skb)
@@ -875,6 +877,12 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
memcpy(pos, assoc_data->ie + offset, noffset - offset);
}

+ if (assoc_data->fils_kek_len &&
+ fils_encrypt_assoc_req(skb, assoc_data) < 0) {
+ dev_kfree_skb(skb);
+ return;
+ }
+
drv_mgd_prepare_tx(local, sdata);

IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
@@ -3146,6 +3154,10 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
reassoc ? "Rea" : "A", mgmt->sa,
capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14))));

+ if (assoc_data->fils_kek_len &&
+ fils_decrypt_assoc_resp(sdata, (u8 *)mgmt, &len, assoc_data) < 0)
+ return;
+
pos = mgmt->u.assoc_resp.variable;
ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), false, &elems);

@@ -4591,13 +4603,17 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct ieee80211_bss *bss = (void *)req->bss->priv;
struct ieee80211_mgd_assoc_data *assoc_data;
+ size_t assoc_data_len;
const struct cfg80211_bss_ies *beacon_ies;
struct ieee80211_supported_band *sband;
const u8 *ssidie, *ht_ie, *vht_ie;
int i, err;
bool override = false;

- assoc_data = kzalloc(sizeof(*assoc_data) + req->ie_len, GFP_KERNEL);
+ assoc_data_len = sizeof(*assoc_data) + req->ie_len + req->fils_kek_len;
+ if (req->fils_nonces)
+ assoc_data_len += 2 * FILS_NONCE_LEN;
+ assoc_data = kzalloc(assoc_data_len, GFP_KERNEL);
if (!assoc_data)
return -ENOMEM;

@@ -4706,6 +4722,22 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
assoc_data->ie_len = req->ie_len;
}

+ if (req->fils_kek) {
+ if (req->fils_kek_len > FILS_MAX_KEK_LEN) {
+ err = -EINVAL;
+ goto err_free;
+ }
+ memcpy(assoc_data->fils_kek, req->fils_kek,
+ req->fils_kek_len);
+ assoc_data->fils_kek_len = req->fils_kek_len;
+ }
+
+ if (req->fils_nonces) {
+ memcpy(assoc_data->fils_nonces, req->fils_nonces,
+ 2 * FILS_NONCE_LEN);
+ assoc_data->fils_nonces_set = true;
+ }
+
assoc_data->bss = req->bss;

if (ifmgd->req_smps == IEEE80211_SMPS_AUTOMATIC) {
--
1.9.1

2016-10-26 05:50:06

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH 7/8] mac80211: FILS AEAD protection for station mode association frames


> +static u8 *fils_find_session(u8 *pos, u8 *end)
> +{
> + while (end - pos > 2 && end - pos >= 2 + pos[1]) {
> + if (pos[0] == 255 && pos[1] == 1 + 8 && pos[2] == 4)
> + return pos;
> + pos += 2 + pos[1];
> + }
> +
> + return NULL;
> +}

Hmm. I think we should try to write this in terms of cfg80211_find_ie,
or perhaps cfg80211_find_ie_match, maybe we need to extend those but
this won't be the only one using the 255 escape.

Perhaps just making the eid passed there be a u16, and extending the
EID definitions appropriately?

The code would probably be shorter as is for now, but still ...

> + if (!session) {
> + sdata_info(sdata,
> +    "No FILS Session element in
> (Re)Association Response frame from %pM",
> +    mgmt->sa);
> + return -EINVAL;

Should we really print the message this prominently? It seems like an
error case that we may not want to log at all, in case somebody tries
to flood us with such frames?

> + crypt_len = frame + *frame_len - encr;
> + if (crypt_len < AES_BLOCK_SIZE) {
> + sdata_info(sdata,
> +    "Not enough room for AES-SIV data after
> FILS Session element in (Re)Association Response frame from %pM",
> +    mgmt->sa);
> + return -EINVAL;
> + }
> + res = aes_siv_decrypt(assoc_data->fils_kek, assoc_data-
> >fils_kek_len,
> +       encr, crypt_len, 5, addr, len, encr);
> + if (res != 0) {
> + sdata_info(sdata,
> +    "AES-SIV decryption of (Re)Association
> Response frame from %pM failed",
> +    mgmt->sa);
> + return res;
> + }

Likewise here. Perhaps make these mlme_dbg() or so instead?

> + if (req->fils_nonces)
> + assoc_data_len += 2 * FILS_NONCE_LEN;

Is this really correct? It seems like a bit of an artifact of initially
having had the nonces in a variable part of the struct?

Or perhaps they have to go into the frame in some place that I missed?
Please add a comment if so.

Also, you're never checking req->fils_nonces_set, so you can get rid of
that.

johannes

2016-10-25 22:45:29

by Jouni Malinen

[permalink] [raw]
Subject: [PATCH 8/8] mac80211: Claim Fast Initial Link Setup (FILS) support

With the previous commits, initial FILS support is now functional in
mac80211-based drivers for both AP and stations roles.

Signed-off-by: Jouni Malinen <[email protected]>
---
net/mac80211/main.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 0d9163c..3e9ca80 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -549,6 +549,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
NL80211_FEATURE_MAC_ON_CREATE |
NL80211_FEATURE_USERSPACE_MPM |
NL80211_FEATURE_FULL_AP_CLIENT_STATE;
+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_FILS);

if (!ops->hw_scan)
wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN |
--
1.9.1

2016-10-25 22:45:23

by Jouni Malinen

[permalink] [raw]
Subject: [PATCH 6/8] mac80211: Add FILS auth alg mapping

Signed-off-by: Jouni Malinen <[email protected]>
---
net/mac80211/mlme.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index b6222f2..b815f2d 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -2618,6 +2618,9 @@ static void ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
case WLAN_AUTH_LEAP:
case WLAN_AUTH_FT:
case WLAN_AUTH_SAE:
+ case WLAN_AUTH_FILS_SK:
+ case WLAN_AUTH_FILS_SK_PFS:
+ case WLAN_AUTH_FILS_PK:
break;
case WLAN_AUTH_SHARED_KEY:
if (ifmgd->auth_data->expected_transaction != 4) {
@@ -4479,6 +4482,15 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
case NL80211_AUTHTYPE_SAE:
auth_alg = WLAN_AUTH_SAE;
break;
+ case NL80211_AUTHTYPE_FILS_SK:
+ auth_alg = WLAN_AUTH_FILS_SK;
+ break;
+ case NL80211_AUTHTYPE_FILS_SK_PFS:
+ auth_alg = WLAN_AUTH_FILS_SK_PFS;
+ break;
+ case NL80211_AUTHTYPE_FILS_PK:
+ auth_alg = WLAN_AUTH_FILS_PK;
+ break;
default:
return -EOPNOTSUPP;
}
--
1.9.1

2016-10-26 06:25:10

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH 8/8] mac80211: Claim Fast Initial Link Setup (FILS) support

On Wed, 2016-10-26 at 01:44 +0300, Jouni Malinen wrote:
> With the previous commits, initial FILS support is now functional in
> mac80211-based drivers for both AP and stations roles.

That's a bit misleading, I guess AP role is handled entirely in
hostapd? You documented the extended feature bit to explicitly mean
station role only :)

johannes

2016-10-26 05:36:32

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH 5/8] cfg80211: Add KEK/nonces for FILS association frames


> +++ b/net/wireless/nl80211.c
> @@ -414,6 +414,10 @@ enum nl80211_multicast_groups {
>   [NL80211_ATTR_NAN_MASTER_PREF] = { .type = NLA_U8 },
>   [NL80211_ATTR_NAN_DUAL] = { .type = NLA_U8 },
>   [NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
> + [NL80211_ATTR_FILS_KEK] = { .type = NLA_BINARY,
> +     .len = FILS_MAX_KEK_LEN },
> + [NL80211_ATTR_FILS_NONCES] = { .type = NLA_BINARY,
> +        .len = 2 * FILS_NONCE_LEN },
>  };

If you remove the type = NLA_BINARY and just leave the type zero, then
you'll get *minimum* length validation, rather than limiting the
maximum length. That seems more appropriate for the nonces?

> + if (info->attrs[NL80211_ATTR_FILS_NONCES]) {
> + if (nla_len(info->attrs[NL80211_ATTR_FILS_NONCES])
> !=
> +     2 * FILS_NONCE_LEN)
> + return -EINVAL;

You're validating the *exact* length here, which unfortunately nlattr
doesn't support right now, but perhaps we can live with checking that
it's at least that many bytes, and using only 2*nonces? We do that for
most other attributes (like MAC addresses).

Or do we expect to extend this to more than 2 nonces in the future, at
which point we'll need the length?

johannes

2016-10-26 09:26:34

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH 8/8] mac80211: Claim Fast Initial Link Setup (FILS) support

On Wed, 2016-10-26 at 09:23 +0000, Malinen, Jouni wrote:
> On Wed, Oct 26, 2016 at 07:50:36AM +0200, Johannes Berg wrote:
> >
> > On Wed, 2016-10-26 at 01:44 +0300, Jouni Malinen wrote:
> > >
> > > With the previous commits, initial FILS support is now functional
> > > in
> > > mac80211-based drivers for both AP and stations roles.
> >
> > That's a bit misleading, I guess AP role is handled entirely in
> > hostapd? You documented the extended feature bit to explicitly mean
> > station role only :)
>
> Yeah.. In case of mac80211, there was not really changes needed in
> the kernel side for AP mode. That may be different with non-mac80211
> drivers, though, since they might be easier to handle with the AES-
> SIV operations for associations frames handled within the driver.

Right.

> Would you prefer to split that NL80211_EXT_FEATURE_FILS into two
> separate values (_STA and _AP)

I think having it called _STA may be a little clearer, but I think I'm
OK with it the way it is (documented as station) as well.

> and have mac80211 advertise both?

I wouldn't do that, there's nothing that makes it have that capability.

> Or just add a _STA only case for now and see what we need to do with
> NL80211_ATTR_DEVICE_AP_SME cases separately once such a thing is in
> in functional state?

Yeah, we should do that.

Really all I thought you should do was reword the commit message to
make it clear that the flag only implied the station case, and that the
AP case needed no changes.

Perhaps renaming the flag to ..._STA will make that a bit clearer.

johannes