git: 9front

Download patch

ref: d3aca0398d0e0bb8358403f6fc2d247f2bffb1b4
parent: 104b82d6f4eba4307c9d6cc7600cdd5293bc5439
author: cinap_lenrek <cinap_lenrek@gmx.de>
date: Sat Mar 9 12:28:41 EST 2013

wifi: add experimental wpa / tkip encryption support

--- a/sys/src/9/pc/etheriwl.c
+++ b/sys/src/9/pc/etheriwl.c
@@ -1578,8 +1578,9 @@
 	put32(d, PCIWADDR(c));	d += 4;
 	put16(d, size << 4); d += 2;
 	if(block != nil){
+		size = BLEN(block);
 		put32(d, PCIWADDR(block->rp)); d += 4;
-		put16(d, BLEN(block) << 4);
+		put16(d, size << 4);
 	}
 
 	coherence();
@@ -1695,7 +1696,7 @@
 	char *err;
 
 	ctlr = edev->ctlr;
-	filter = FilterMulticast | FilterBeacon;
+	filter = FilterNoDecrypt | FilterMulticast | FilterBeacon;
 	if(ctlr->prom){
 		filter |= FilterPromisc;
 		bss = nil;
--- a/sys/src/9/pc/wifi.c
+++ b/sys/src/9/pc/wifi.c
@@ -12,6 +12,8 @@
 #include "etherif.h"
 #include "wifi.h"
 
+#include <libsec.h>
+
 typedef struct SNAP SNAP;
 struct SNAP
 {
@@ -23,6 +25,7 @@
 };
 
 enum {
+	WIFIHDRSIZE = 2+2+3*6+2,
 	SNAPHDRSIZE = 8,
 };
 
@@ -32,20 +35,61 @@
 static char Sunauth[] = "unauthentictaed";
 static char Sassoc[] = "associated";
 static char Sunassoc[] = "unassociated";
+static char Sblocked[] = "blocked";	/* no keys negotiated. only pass EAPOL frames */
 
+static Block* wifidecrypt(Wifi *, Wnode *, Block *);
+static Block* wifiencrypt(Wifi *, Wnode *, Block *);
+
+static uchar*
+srcaddr(Wifipkt *w)
+{
+	if((w->fc[1] & 3) == 0x02)
+		return w->a3;
+	else
+		return w->a2;
+}
+static uchar*
+dstaddr(Wifipkt *w)
+{
+	if((w->fc[1] & 3) == 0x01)
+		return w->a3;
+	else
+		return w->a1;
+}
+
+static int
+wifihdrlen(Wifipkt *w)
+{
+	int n;
+
+	n = WIFIHDRSIZE;
+	if((w->fc[0] & 0x0c) == 0x08)
+		if((w->fc[0] & 0xf0) == 0x80){	/* QOS */
+			n += 2;
+			if(w->fc[1] & 0x80)
+				n += 4;
+		}
+	return n;
+}
+
 void
 wifiiq(Wifi *wifi, Block *b)
 {
 	SNAP s;
-	Wifipkt w;
+	Wifipkt h, *w;
 	Etherpkt *e;
 
 	if(BLEN(b) < WIFIHDRSIZE)
 		goto drop;
-	memmove(&w, b->rp, WIFIHDRSIZE);
-	switch(w.fc[0] & 0x0c){
+	w = (Wifipkt*)b->rp;
+	if(w->fc[1] & 0x40){
+		/* encrypted */
+		qpass(wifi->iq, b);
+		return;
+	}
+	switch(w->fc[0] & 0x0c){
 	case 0x00:	/* management */
-		if((w.fc[1] & 3) != 0x00)	/* STA->STA */
+		if((w->fc[1] & 3) != 0x00)	/* STA->STA */
 			break;
 		qpass(wifi->iq, b);
 		return;
@@ -52,16 +96,15 @@
 	case 0x04:	/* control */
 		break;
 	case 0x08:	/* data */
-		b->rp += WIFIHDRSIZE;
-		switch(w.fc[0] & 0xf0){
-		case 0x80:
-			b->rp += 2;
-			if(w.fc[1] & 0x80)
-				b->rp += 4;
-		case 0x00:
+		if((w->fc[1] & 3) == 0x03)	/* AP->AP */
 			break;
+		b->rp += wifihdrlen(w);
+		switch(w->fc[0] & 0xf0){
 		default:
 			goto drop;
+		case 0x80:	/* QOS */
+		case 0x00:
+			break;
 		}
 		if(BLEN(b) < SNAPHDRSIZE)
 			break;
@@ -71,23 +114,10 @@
 		if(s.orgcode[0] != 0 || s.orgcode[1] != 0 || s.orgcode[2] != 0)
 			break;
 		b->rp += SNAPHDRSIZE-ETHERHDRSIZE;
+		h = *w;
 		e = (Etherpkt*)b->rp;
-		switch(w.fc[1] & 0x03){
-		case 0x00:	/* STA->STA */
-			memmove(e->d, w.a1, Eaddrlen);
-			memmove(e->s, w.a2, Eaddrlen);
-			break;
-		case 0x01:	/* STA->AP */
-			memmove(e->d, w.a3, Eaddrlen);
-			memmove(e->s, w.a2, Eaddrlen);
-			break;
-		case 0x02:	/* AP->STA */
-			memmove(e->d, w.a1, Eaddrlen);
-			memmove(e->s, w.a3, Eaddrlen);
-			break;
-		case 0x03:	/* AP->AP */
-			goto drop;
-		}
+		memmove(e->d, dstaddr(&h), Eaddrlen);
+		memmove(e->s, srcaddr(&h), Eaddrlen);
 		memmove(e->type, s.type, 2);
 		etheriq(wifi->ether, b, 1);
 		return;
@@ -111,10 +141,13 @@
 	w->seq[0] = seq;
 	w->seq[1] = seq>>8;
 
-	(*wifi->transmit)(wifi, wn, b);
+	if((w->fc[0] & 0x0c) != 0x00)
+		b = wifiencrypt(wifi, wn, b);
+
+	if(b != nil)
+		(*wifi->transmit)(wifi, wn, b);
 }
 
-
 static Wnode*
 nodelookup(Wifi *wifi, uchar *bssid, int new)
 {
@@ -142,12 +175,8 @@
 	}
 	if(!new)
 		return nil;
+	memset(nn, 0, sizeof(Wnode));
 	memmove(nn->bssid, bssid, Eaddrlen);
-	nn->ssid[0] = 0;
-	nn->ival = 0;
-	nn->cap = 0;
-	nn->aid = 0;
-	nn->channel = 0;
 	nn->lastseen = MACHP(0)->ticks;
 	return nn;
 }
@@ -184,8 +213,9 @@
 	Wifipkt *w;
 	Block *b;
 	uchar *p;
+	int n;
 
-	b = allocb(WIFIHDRSIZE + 128);
+	b = allocb(WIFIHDRSIZE + 512);
 	w = (Wifipkt*)b->wp;
 	w->fc[0] = 0x00;	/* assoc request */
 	w->fc[1] = 0x00;	/* STA->STA */
@@ -198,10 +228,12 @@
 	*p++ = 0;
 	*p++ = 16;	/* interval */
 	*p++ = 16>>8;
+
 	*p++ = 0;	/* SSID */
 	*p = strlen(bss->ssid);
 	memmove(p+1, bss->ssid, *p);
 	p += 1+*p;
+
 	*p++ = 1;	/* RATES (BUG: these are all lies!) */
 	*p++ = 4;
 	*p++ = 0x82;
@@ -208,6 +240,13 @@
 	*p++ = 0x84;
 	*p++ = 0x8b;
 	*p++ = 0x96;
+
+	n = bss->rsnelen;
+	if(n > 0){
+		memmove(p, bss->rsne, n);
+		p += n;
+	}
+
 	b->wp = p;
 	wifitx(wifi, bss, b);
 }
@@ -226,7 +265,10 @@
 	switch(s){
 	case 0x00:
 		wn->aid = d[0] | d[1]<<8;
-		wifi->status = Sassoc;
+		if(wn->rsnelen > 0)
+			wifi->status = Sblocked;
+		else
+			wifi->status = Sassoc;
 		break;
 	default:
 		wn->aid = 0;
@@ -264,7 +306,7 @@
 		switch(t){
 		case 0:	/* SSID */
 			len = 0;
-			while(len < 32 && d+len < x && d[len] != 0)
+			while(len < Essidlen && d+len < x && d[len] != 0)
 				len++;
 			if(len == 0)
 				continue;
@@ -297,12 +339,28 @@
 		if((b = qbread(wifi->iq, 100000)) == nil)
 			break;
 		w = (Wifipkt*)b->rp;
+		if(w->fc[1] & 0x40){
+			/* encrypted */
+			if((wn = nodelookup(wifi, w->a2, 1)) == nil)
+				continue;
+			if((b = wifidecrypt(wifi, wn, b)) != nil){
+				w = (Wifipkt*)b->rp;
+				if(w->fc[1] & 0x40)
+					continue;
+				wifiiq(wifi, b);
+				b = nil;
+			}
+			continue;
+		}
+		/* management */
+		if((w->fc[0] & 0x0c) != 0x00)
+			continue;
 		switch(w->fc[0] & 0xf0){
 		case 0x50:	/* probe response */
 		case 0x80:	/* beacon */
 			if((wn = nodelookup(wifi, w->a3, 1)) == nil)
 				continue;
-			b->rp += WIFIHDRSIZE;
+			b->rp += wifihdrlen(w);
 			recvbeacon(wifi, wn, b->rp, BLEN(b));
 			if(wifi->bss == nil && wifi->essid[0] != 0 && strcmp(wifi->essid, wn->ssid) == 0){
 				wifi->bss = wn;
@@ -320,7 +378,7 @@
 		switch(w->fc[0] & 0xf0){
 		case 0x10:	/* assoc response */
 		case 0x30:	/* reassoc response */
-			b->rp += WIFIHDRSIZE;
+			b->rp += wifihdrlen(w);
 			recvassoc(wifi, wn, b->rp, BLEN(b));
 			break;
 		case 0xb0:	/* auth */
@@ -328,8 +386,10 @@
 			sendassoc(wifi, wn);
 			break;
 		case 0xc0:	/* deauth */
-			wn->aid = 0;
 			wifi->status = Sunauth;
+			memset(wn->rxkey, 0, sizeof(wn->rxkey));
+			memset(wn->txkey, 0, sizeof(wn->txkey));
+			wn->aid = 0;
 			sendauth(wifi, wn);
 			break;
 		}
@@ -342,23 +402,30 @@
 {
 	Etherpkt e;
 	Wifipkt *w;
-	Wnode *bss;
+	Wnode *wn;
 	SNAP *s;
 
-	bss = wifi->bss;
-	if(bss == nil || BLEN(b) < ETHERHDRSIZE){
-		freeb(b);
-		return;
-	}
-	memmove(&e, b->rp, ETHERHDRSIZE);
+	if(BLEN(b) < ETHERHDRSIZE)
+		goto drop;
+	if((wn = wifi->bss) == nil)
+		goto drop;
 
+	memmove(&e, b->rp, ETHERHDRSIZE);
 	b->rp += ETHERHDRSIZE;
+
+	if(wifi->status == Sblocked){
+		/* only pass EAPOL frames when port is blocked */
+		if((e.type[0]<<8 | e.type[1]) != 0x888e)
+			goto drop;
+	} else if(wifi->status != Sassoc)
+		goto drop;
+
 	b = padblock(b, WIFIHDRSIZE + SNAPHDRSIZE);
 
 	w = (Wifipkt*)b->rp;
 	w->fc[0] = 0x08;	/* data */
 	w->fc[1] = 0x01;	/* STA->AP */
-	memmove(w->a1, bss->bssid, Eaddrlen);
+	memmove(w->a1, wn->bssid, Eaddrlen);
 	memmove(w->a2, e.s, Eaddrlen);
 	memmove(w->a3, e.d, Eaddrlen);
 
@@ -370,7 +437,10 @@
 	s->orgcode[2] = 0;
 	memmove(s->type, e.type, 2);
 
-	wifitx(wifi, bss, b);
+	wifitx(wifi, wn, b);
+	return;
+drop:
+	freeb(b);
 }
 
 static void
@@ -407,11 +477,98 @@
 	return wifi;
 }
 
+static int
+hextob(char *s, char **sp, uchar *b, int n)
+{
+	int r;
+
+	n <<= 1;
+	for(r = 0; r < n && *s; s++){
+		*b <<= 4;
+		if(*s >= '0' && *s <= '9')
+			*b |= (*s - '0');
+		else if(*s >= 'a' && *s <= 'f')
+			*b |= 10+(*s - 'a');
+		else if(*s >= 'A' && *s <= 'F')
+			*b |= 10+(*s - 'A');
+		else break;
+		if((++r & 1) == 0)
+			b++;
+	}
+	if(sp != nil)
+		*sp = s;
+	return r >> 1;
+}
+
+static char *ciphers[] = {
+	[0]	"clear",
+	[TKIP]	"tkip",
+};
+
+static int
+parsekey(Wkey *k, char *s)
+{
+	char buf[256], *p;
+	int i;
+
+	strncpy(buf, s, sizeof(buf)-1);
+	buf[sizeof(buf)-1] = 0;
+	if((p = strchr(buf, ':')) != nil)
+		*p++ = 0;
+	else
+		p = buf;
+	for(i=0; i<nelem(ciphers); i++){
+		if(ciphers[i] == nil)
+			continue;
+		if(strcmp(ciphers[i], buf) == 0)
+			break;
+	}
+	if(i >= nelem(ciphers))
+		return -1;
+	memset(k, 0, sizeof(Wkey));
+	k->len = hextob(p, &p, k->key, sizeof(k->key));
+	if(*p == '@')
+		k->tsc = strtoull(++p, nil, 16);
+	k->cipher = i;
+	return 0;
+}
+
+enum {
+	CMessid,
+	CMauth,
+	CMunblock,
+
+	CMrxkey0,
+	CMrxkey1,
+	CMrxkey2,
+	CMrxkey3,
+	CMrxkey4,
+	CMtxkey0,
+};
+
+static Cmdtab wifictlmsg[] =
+{
+	CMessid,	"essid",	0,
+	CMauth,		"auth",		0,
+
+	CMrxkey0,	"rxkey0",	0,	/* group keys */
+	CMrxkey1,	"rxkey1",	0,
+	CMrxkey2,	"rxkey2",	0,
+	CMrxkey3,	"rxkey3",	0,
+
+	CMrxkey4,	"rxkey",	0,	/* peerwise keys */
+	CMtxkey0,	"txkey",	0,
+
+	CMtxkey0,	"txkey0",	0,
+};
+
 long
 wifictl(Wifi *wifi, void *buf, long n)
 {
 	Cmdbuf *cb;
+	Cmdtab *ct;
 	Wnode *wn;
+	Wkey *k;
 
 	cb = nil;
 	if(waserror()){
@@ -418,15 +575,30 @@
 		free(cb);
 		nexterror();
 	}
+	wn = wifi->bss;
 	cb = parsecmd(buf, n);
-	if(cb->f[0] && strcmp(cb->f[0], "essid") == 0){
+	ct = lookupcmd(cb, wifictlmsg, nelem(wifictlmsg));
+	if(ct->index != CMessid){
+		if(ct->index >= CMrxkey0 && cb->nf > 1){
+			uchar addr[Eaddrlen];
+
+			if(parseether(addr, cb->f[1]) == 0){
+				cb->f++;
+				cb->nf--;
+				wn = nodelookup(wifi, addr, 0);
+			}
+		}
+		if(wn == nil)
+			error("missing node");
+	}
+	switch(ct->index){
+	case CMessid:
 		if(cb->f[1] == nil){
 			wifi->essid[0] = 0;
 			wifi->bss = nil;
 			wifi->status = Snone;
 		} else {
-			strncpy(wifi->essid, cb->f[1], 32);
-			wifi->essid[32] = 0;
+			strncpy(wifi->essid, cb->f[1], Essidlen);
 			for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++)
 				if(strcmp(wifi->essid, wn->ssid) == 0){
 					wifi->bss = wn;
@@ -435,6 +607,28 @@
 					break;
 				}
 		}
+		break;
+	case CMauth:
+		wifi->status = Sauth;
+		memset(wn->rxkey, 0, sizeof(wn->rxkey));
+		memset(wn->txkey, 0, sizeof(wn->txkey));
+		if(cb->f[1] == nil)
+			wn->rsnelen = 0;
+		else
+			wn->rsnelen = hextob(cb->f[1], nil, wn->rsne, sizeof(wn->rsne));
+		sendassoc(wifi, wn);
+		break;
+	case CMrxkey0: case CMrxkey1: case CMrxkey2: case CMrxkey3: case CMrxkey4:
+	case CMtxkey0:
+		if(ct->index < CMtxkey0)
+			k = &wn->rxkey[ct->index - CMrxkey0];
+		else
+			k = &wn->txkey[ct->index - CMtxkey0];
+		if(cb->f[1] == nil || parsekey(k, cb->f[1]) != 0)
+			error("bad key");
+		if(ct->index >= CMtxkey0 && wifi->status == Sblocked && wifi->bss == wn)
+			wifi->status = Sassoc;
+		break;
 	}
 	poperror();
 	free(cb);
@@ -461,10 +655,401 @@
 	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
 		if(wn->lastseen == 0)
 			continue;
-		p = seprint(p, e, "node: %E %.4x %d %ld %d %s\n",
-			wn->bssid, wn->cap, wn->ival, TK2MS(now - wn->lastseen), wn->channel, wn->ssid);
+		p = seprint(p, e, "node: %E %.4x %-11ld %.2d %s\n",
+			wn->bssid, wn->cap, TK2MS(now - wn->lastseen), wn->channel, wn->ssid);
 	}
 	n = readstr(off, buf, n, s);
 	free(s);
 	return n;
+}
+
+static void tkipk2tk(uchar key[16], u16int tk[8]);
+static void tkipphase1(u32int tscu, uchar ta[Eaddrlen], u16int tk[8], u16int p1k[5]);
+static void tkipphase2(u16int tscl, u16int p1k[5], u16int tk[8], uchar rc4key[16]);
+
+typedef struct MICstate MICstate;
+struct MICstate
+{
+	u32int	l;
+	u32int	r;
+	u32int	m;
+	u32int	n;
+};
+
+static void micsetup(MICstate *s, uchar key[8]);
+static void micupdate(MICstate *s, uchar *data, ulong len);
+static void micfinish(MICstate *s, uchar mic[8]);
+
+static uchar pad4[4] = { 0x00, 0x00, 0x00, 0x00, };
+
+static Block*
+wifiencrypt(Wifi *, Wnode *wn, Block *b)
+{
+	u16int tk[8], p1k[5];
+	uchar seed[16];
+	uvlong tsc;
+	ulong crc;
+	RC4state rs;
+	MICstate ms;
+	Wifipkt *w;
+	int n, kid;
+	Wkey *k;
+
+	kid = 0;
+	k = &wn->txkey[kid];
+	if(k->cipher == 0)
+		goto pass;
+	if(k->cipher != TKIP || k->len != 32)
+		goto drop;
+
+	n = wifihdrlen((Wifipkt*)b->rp);
+
+	b = padblock(b, 8);
+	b = padblock(b, -(8+4));
+
+	w = (Wifipkt*)b->rp;
+	memmove(w, b->rp+8, n);
+	b->rp += n;
+
+	tsc = ++k->tsc;
+	b->rp[0] = tsc>>8;
+	b->rp[1] = (b->rp[0] | 0x20) & 0x7f;
+	b->rp[2] = tsc;
+	b->rp[3] = kid<<6 | 0x20;
+	b->rp[4] = tsc>>16;
+	b->rp[5] = tsc>>24;
+	b->rp[6] = tsc>>32;
+	b->rp[7] = tsc>>40;
+	b->rp += 8;
+
+	micsetup(&ms, k->key+24);
+	micupdate(&ms, dstaddr(w), Eaddrlen);
+	micupdate(&ms, srcaddr(w), Eaddrlen);
+	micupdate(&ms, pad4, 4);
+	micupdate(&ms, b->rp, BLEN(b));
+	micfinish(&ms, b->wp);
+	b->wp += 8;
+
+	crc = ethercrc(b->rp, BLEN(b));
+	crc = ~crc;
+	b->wp[0] = crc;
+	b->wp[1] = crc>>8;
+	b->wp[2] = crc>>16;
+	b->wp[3] = crc>>24;
+	b->wp += 4;
+
+	tkipk2tk(k->key, tk);
+	tkipphase1(tsc >> 16, w->a2, tk, p1k);
+	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
+	setupRC4state(&rs, seed, sizeof(seed));
+	rc4(&rs, b->rp, BLEN(b));
+
+	b->rp = (uchar*)w;
+	w->fc[1] |= 0x40;
+
+pass:
+	return b;
+drop:
+	free(b);
+	return nil;
+}
+
+static Block*
+wifidecrypt(Wifi *, Wnode *wn, Block *b)
+{
+	uchar seed[16], mic[8];
+	u16int tk[8], p1k[5];
+	RC4state rs;
+	MICstate ms;
+	uvlong tsc;
+	ulong crc;
+	Wifipkt *w;
+	int n, kid;
+	Wkey *k;
+
+	w = (Wifipkt*)b->rp;
+	if(BLEN(b) < WIFIHDRSIZE)
+		goto drop;
+
+	n = wifihdrlen(w);
+	b->rp += n;
+
+	if(BLEN(b) < 8+8+4)
+		goto drop;
+
+	kid = b->rp[3]>>6;
+	if((b->rp[3] & 0x20) == 0)
+		goto drop;
+	if((dstaddr(w)[0] & 1) == 0)
+		kid = 4;	/* use peerwise key for non-unicast */
+
+	k = &wn->rxkey[kid];
+	if(k->cipher != TKIP || k->len != 32)
+		goto drop;
+
+	tsc =	(uvlong)b->rp[7]<<40 |
+		(uvlong)b->rp[6]<<32 |
+		(uvlong)b->rp[5]<<24 |
+		(uvlong)b->rp[4]<<16 |
+		(uvlong)b->rp[0]<<8 |
+		(uvlong)b->rp[2];
+	b->rp += 8;
+
+	if(tsc <= k->tsc)
+		goto drop;
+
+	tkipk2tk(k->key, tk);
+	tkipphase1(tsc >> 16, w->a2, tk, p1k);
+	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
+	setupRC4state(&rs, seed, sizeof(seed));
+	rc4(&rs, b->rp, BLEN(b));
+
+	b->wp -= 4;
+	crc =	(ulong)b->wp[0] |
+		(ulong)b->wp[1]<<8 |
+		(ulong)b->wp[2]<<16 |
+		(ulong)b->wp[3]<<24;
+	crc = ~crc;
+	if(ethercrc(b->rp, BLEN(b)) != crc)
+		goto drop;
+
+	b->wp -= 8;
+	micsetup(&ms, k->key+16);
+	micupdate(&ms, dstaddr(w), Eaddrlen);
+	micupdate(&ms, srcaddr(w), Eaddrlen);
+	micupdate(&ms, pad4, 4);
+	micupdate(&ms, b->rp, BLEN(b));
+	micfinish(&ms, mic);
+	if(memcmp(b->wp, mic, 8) != 0)
+		goto drop;
+
+	k->tsc = tsc;
+	b->rp -= n;
+	memmove(b->rp, w, n);
+	w = (Wifipkt*)b->rp;
+	w->fc[1] &= ~0x40;
+	return b;
+drop:
+	freeb(b);
+	return nil;
+}
+
+static u16int Sbox[256] = {
+	0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154,
+	0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A,
+	0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B,
+	0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B,
+	0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F,
+	0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F,
+	0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5,
+	0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F,
+	0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB,
+	0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397,
+	0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED,
+	0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A,
+	0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194,
+	0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3,
+	0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104,
+	0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D,
+	0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39,
+	0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695,
+	0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83,
+	0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76,
+	0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4,
+	0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B,
+	0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0,
+	0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018,
+	0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751,
+	0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85,
+	0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12,
+	0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9,
+	0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7,
+	0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A,
+	0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8,
+	0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A
+};
+
+static void
+tkipk2tk(uchar key[16], u16int tk[8])
+{
+	tk[0] = (u16int)key[1]<<8 | key[0];
+	tk[1] = (u16int)key[3]<<8 | key[2];
+	tk[2] = (u16int)key[5]<<8 | key[4];
+	tk[3] = (u16int)key[7]<<8 | key[6];
+	tk[4] = (u16int)key[9]<<8 | key[8];
+	tk[5] = (u16int)key[11]<<8 | key[10];
+	tk[6] = (u16int)key[13]<<8 | key[12];
+	tk[7] = (u16int)key[15]<<8 | key[14];
+}
+
+static void
+tkipphase1(u32int tscu, uchar ta[Eaddrlen], u16int tk[8], u16int p1k[5])
+{
+	u16int *k, i, x0, x1, x2;
+
+	p1k[0] = tscu;
+	p1k[1] = tscu>>16;
+	p1k[2] = (u16int)ta[1]<<8 | ta[0];
+	p1k[3] = (u16int)ta[3]<<8 | ta[2];
+	p1k[4] = (u16int)ta[5]<<8 | ta[4];
+
+	for(i=0; i<8; i++){
+		k = &tk[i & 1];
+
+		x0 = p1k[4] ^ k[0];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[0] += x2;
+		x0 = p1k[0] ^ k[2];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[1] += x2;
+		x0 = p1k[1] ^ k[4];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[2] += x2;
+		x0 = p1k[2] ^ k[6];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[3] += x2;
+		x0 = p1k[3] ^ k[0];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[4] += x2;
+
+		p1k[4] += i;
+	}
+}
+
+static void
+tkipphase2(u16int tscl, u16int p1k[5], u16int tk[8], uchar rc4key[16])
+{
+	u16int ppk[6], x0, x1, x2;
+
+	ppk[0] = p1k[0];
+	ppk[1] = p1k[1];
+	ppk[2] = p1k[2];
+	ppk[3] = p1k[3];
+	ppk[4] = p1k[4];
+	ppk[5] = p1k[4] + tscl;
+
+	x0 = ppk[5] ^ tk[0];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[0] += x2;
+	x0 = ppk[0] ^ tk[1];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[1] += x2;
+	x0 = ppk[1] ^ tk[2];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[2] += x2;
+	x0 = ppk[2] ^ tk[3];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[3] += x2;
+	x0 = ppk[3] ^ tk[4];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[4] += x2;
+	x0 = ppk[4] ^ tk[5];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[5] += x2;
+
+	x2 = ppk[5] ^ tk[6];
+	ppk[0] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[0] ^ tk[7];
+	ppk[1] += (x2 >> 1) | (x2 << 15);
+
+	x2 = ppk[1];
+	ppk[2] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[2];
+	ppk[3] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[3];
+	ppk[4] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[4];
+	ppk[5] += (x2 >> 1) | (x2 << 15);
+
+	rc4key[0] = tscl >> 8;
+	rc4key[1] = (rc4key[0] | 0x20) & 0x7F;
+	rc4key[2] = tscl;
+	rc4key[3] = (ppk[5] ^ tk[0]) >> 1;
+	rc4key[4]  = ppk[0];
+	rc4key[5]  = ppk[0] >> 8;
+	rc4key[6]  = ppk[1];
+	rc4key[7]  = ppk[1] >> 8;
+	rc4key[8]  = ppk[2];
+	rc4key[9]  = ppk[2] >> 8;
+	rc4key[10] = ppk[3];
+	rc4key[11] = ppk[3] >> 8;
+	rc4key[12] = ppk[4];
+	rc4key[13] = ppk[4] >> 8;
+	rc4key[14] = ppk[5];
+	rc4key[15] = ppk[5] >> 8;
+}
+
+
+static void
+micsetup(MICstate *s, uchar key[8])
+{
+	s->l =	(u32int)key[0] |
+		(u32int)key[1]<<8 |
+		(u32int)key[2]<<16 |
+		(u32int)key[3]<<24;
+	s->r =	(u32int)key[4] |
+		(u32int)key[5]<<8 |
+		(u32int)key[6]<<16 |
+		(u32int)key[7]<<24;
+	s->m = 0;
+	s->n = 0;
+}
+
+static void
+micupdate(MICstate *s, uchar *data, ulong len)
+{
+	u32int l, r, m, n, e;
+
+	l = s->l;
+	r = s->r;
+	m = s->m;
+	n = s->n;
+	e = n + len;
+	while(n != e){
+		m >>= 8;
+		m |= (u32int)*data++ << 24;
+		if(++n & 3)
+			continue;
+		l ^= m;
+		r ^= (l << 17) | (l >> 15);
+		l += r;
+		r ^= ((l & 0x00FF00FFUL)<<8) | ((l & 0xFF00FF00UL)>>8);
+		l += r;
+		r ^= (l << 3) | (l >> 29);
+		l += r;
+		r ^= (l >> 2) | (l << 30);
+		l += r;
+	}
+	s->l = l;
+	s->r = r;
+	s->m = m;
+	s->n = n;
+}
+
+static void
+micfinish(MICstate *s, uchar mic[8])
+{
+	static uchar pad[8] = { 0x5a, 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, };
+
+	micupdate(s, pad, sizeof(pad));
+
+	mic[0] = s->l;
+	mic[1] = s->l>>8;
+	mic[2] = s->l>>16;
+	mic[3] = s->l>>24;
+	mic[4] = s->r;
+	mic[5] = s->r>>8;
+	mic[6] = s->r>>16;
+	mic[7] = s->r>>24;
 }
--- a/sys/src/9/pc/wifi.h
+++ b/sys/src/9/pc/wifi.h
@@ -1,26 +1,35 @@
+typedef struct Wkey Wkey;
 typedef struct Wnode Wnode;
 typedef struct Wifi Wifi;
-
 typedef struct Wifipkt Wifipkt;
 
-struct Wifipkt
-{
-	uchar	fc[2];
-	uchar	dur[2];
-	uchar	a1[Eaddrlen];
-	uchar	a2[Eaddrlen];
-	uchar	a3[Eaddrlen];
-	uchar	seq[2];
+enum {
+	Essidlen = 32,
 };
 
+/* cipher */
 enum {
-	WIFIHDRSIZE = 2+2+3*6+2,
+	TKIP	= 1,
 };
 
+struct Wkey
+{
+	int	cipher;
+	int	len;
+	uchar	key[32];
+	uvlong	tsc;
+};
+
 struct Wnode
 {
 	uchar	bssid[Eaddrlen];
-	char	ssid[32+2];
+	char	ssid[Essidlen+2];
+
+	int	rsnelen;
+	uchar	rsne[256];
+	Wkey	txkey[1];
+	Wkey	rxkey[5];
+
 	int	ival;
 	int	cap;
 	int	aid;
@@ -37,15 +46,24 @@
 	Ref	txseq;
 	void	(*transmit)(Wifi*, Wnode*, Block*);
 
-	char	essid[32+2];
+	char	essid[Essidlen+2];
 	Wnode	*bss;
 
 	Wnode	node[32];
 };
 
+struct Wifipkt
+{
+	uchar	fc[2];
+	uchar	dur[2];
+	uchar	a1[Eaddrlen];
+	uchar	a2[Eaddrlen];
+	uchar	a3[Eaddrlen];
+	uchar	seq[2];
+};
+
 Wifi *wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*));
 void wifiiq(Wifi*, Block*);
 
 long wifistat(Wifi*, void*, long, ulong);
 long wifictl(Wifi*, void*, long);
-
--