stab at wpa auth sourced from cinap Reference: /n/atom/patch/applied/wpa Date: Sat May 3 15:46:20 CES 2014 Signed-off-by: quanstro@quanstro.net --- /sys/src/cmd/aux/mkfile Sat May 3 15:46:15 2014 +++ /sys/src/cmd/aux/mkfile Sat May 3 15:46:16 2014 @@ -39,6 +39,7 @@ timesync\ trampoline\ usage\ + wpa\ write\ wrmsr\ xlisten\ --- /sys/src/cmd/aux/wpa.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/aux/wpa.c Sat May 3 15:46:17 2014 @@ -0,0 +1,869 @@ +#include +#include +#include +#include +#include +#include + +enum { + PTKlen = 512/8, + GTKlen = 256/8, + + MIClen = 16, + + Noncelen = 32, + Eaddrlen = 6, +}; + +enum { + Fptk = 1<<3, + Fins = 1<<6, + Fack = 1<<7, + Fmic = 1<<8, + Fsec = 1<<9, + Ferr = 1<<10, + Freq = 1<<11, + Fenc = 1<<12, + + Keydescrlen = 1+2+2+8+32+16+8+8+16+2, +}; + +typedef struct Keydescr Keydescr; +struct Keydescr +{ + uchar type[1]; + uchar flags[2]; + uchar keylen[2]; + uchar repc[8]; + uchar nonce[32]; + uchar eapoliv[16]; + uchar rsc[8]; + uchar id[8]; + uchar mic[16]; + uchar datalen[2]; + uchar data[]; +}; + +typedef struct Cipher Cipher; +struct Cipher +{ + char *name; + int keylen; +}; + +Cipher tkip = { "tkip", 32 }; +Cipher ccmp = { "ccmp", 16 }; + +Cipher *peercipher; +Cipher *groupcipher; + +int background; +int prompt; +int debug; +int fd, cfd; +char *dev; +char devdir[40]; +uchar ptk[PTKlen]; +char essid[32+1]; +uvlong lastrepc; + +uchar rsntkipoui[4] = {0x00, 0x0F, 0xAC, 0x02}; +uchar rsnccmpoui[4] = {0x00, 0x0F, 0xAC, 0x04}; +uchar rsnapskoui[4] = {0x00, 0x0F, 0xAC, 0x02}; + +uchar rsnie[] = { + 0x30, /* RSN */ + 0x14, /* length */ + 0x01, 0x00, /* version 1 */ + 0x00, 0x0F, 0xAC, 0x04, /* group cipher suite CCMP */ + 0x01, 0x00, /* pairwise cipher suite count 1 */ + 0x00, 0x0F, 0xAC, 0x04, /* pairwise cipher suite CCMP */ + 0x01, 0x00, /* authentication suite count 1 */ + 0x00, 0x0F, 0xAC, 0x02, /* authentication suite PSK */ + 0x00, 0x00, /* capabilities */ +}; + +uchar wpa1oui[4] = {0x00, 0x50, 0xF2, 0x01}; +uchar wpatkipoui[4] = {0x00, 0x50, 0xF2, 0x02}; +uchar wpaapskoui[4] = {0x00, 0x50, 0xF2, 0x02}; + +uchar wpaie[] = { + 0xdd, /* vendor specific */ + 0x16, /* length */ + 0x00, 0x50, 0xf2, 0x01, /* WPAIE type 1 */ + 0x01, 0x00, /* version 1 */ + 0x00, 0x50, 0xf2, 0x02, /* group cipher suite TKIP */ + 0x01, 0x00, /* pairwise cipher suite count 1 */ + 0x00, 0x50, 0xf2, 0x02, /* pairwise cipher suite TKIP */ + 0x01, 0x00, /* authentication suite count 1 */ + 0x00, 0x50, 0xf2, 0x02, /* authentication suite PSK */ +}; + +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; +} + +char* +getifstats(char *key, char *val, int nval) +{ + char buf[8*1024], *f[2], *p, *e; + int fd, n; + + snprint(buf, sizeof(buf), "%s/ifstats", devdir); + if((fd = open(buf, OREAD)) < 0) + return nil; + n = readn(fd, buf, sizeof(buf)-1); + close(fd); + if(n <= 0) + return nil; + buf[n] = 0; + for(p = buf; (e = strchr(p, '\n')) != nil; p = e){ + *e++ = 0; + if(tokenize(p, f, 2) != 2) + continue; + if(strcmp(f[0], key) != 0) + continue; + strncpy(val, f[1], nval); + val[nval-1] = 0; + return val; + } + return nil; +} + +char* +getessid(void) +{ + return getifstats("essid:", essid, sizeof(essid)); +} + +int +connected(void) +{ + char status[1024]; + + if(getifstats("status:", status, sizeof(status)) == nil) + return 0; + if(strcmp(status, "connecting") == 0) + return 0; + if(strcmp(status, "unauthenticated") == 0) + return 0; + if(debug) + fprint(2, "status: %s\n", status); + return 1; +} + +int +buildrsne(uchar rsne[258]) +{ + char buf[1024]; + uchar brsne[258]; + int brsnelen; + uchar *p, *w, *e; + int i, n; + + if(getifstats("brsne:", buf, sizeof(buf)) == nil) + return 0; /* not an error, might be old kernel */ + + brsnelen = hextob(buf, nil, brsne, sizeof(brsne)); + if(brsnelen <= 4){ +trunc: sysfatal("invalid or truncated RSNE; brsne: %s", buf); + return 0; + } + + w = rsne; + p = brsne; + e = p + brsnelen; + if(p[0] == 0x30){ + p += 2; + + /* RSN */ + *w++ = 0x30; + *w++ = 0; /* length */ + } else if(p[0] == 0xDD){ + p += 2; + if((e - p) < 4 || memcmp(p, wpa1oui, 4) != 0){ + sysfatal("unrecognized WPAIE type; brsne: %s", buf); + return 0; + } + + /* WPA */ + *w++ = 0xDD; + *w++ = 0; /* length */ + + memmove(w, wpa1oui, 4); + w += 4; + p += 4; + } else { + sysfatal("unrecognized RSNE type; brsne: %s", buf); + return 0; + } + + if((e - p) < 6) + goto trunc; + + *w++ = *p++; /* version */ + *w++ = *p++; + + if(rsne[0] == 0x30){ + if(memcmp(p, rsnccmpoui, 4) == 0) + groupcipher = &ccmp; + else if(memcmp(p, rsntkipoui, 4) == 0) + groupcipher = &tkip; + else { + sysfatal("unrecognized RSN group cipher; brsne: %s", buf); + return 0; + } + } else { + if(memcmp(p, wpatkipoui, 4) != 0){ + sysfatal("unrecognized WPA group cipher; brsne: %s", buf); + return 0; + } + groupcipher = &tkip; + } + + memmove(w, p, 4); /* group cipher */ + w += 4; + p += 4; + + if((e - p) < 6) + goto trunc; + + *w++ = 0x01; /* # of peer ciphers */ + *w++ = 0x00; + n = *p++; + n |= *p++ << 8; + + if(n <= 0) + goto trunc; + + peercipher = &tkip; + for(i=0; i= n){ + sysfatal("auth suite is not PSK; brsne: %s", buf); + return 0; + } + + memmove(w, p, 4); + w += 4; + + if(rsne[0] == 0x30){ + /* RSN caps */ + *w++ = 0x00; + *w++ = 0x00; + } + + rsne[1] = (w - rsne) - 2; + return w - rsne; +} + +int +getptk( uchar smac[Eaddrlen], uchar amac[Eaddrlen], + uchar snonce[Noncelen], uchar anonce[Noncelen], + uchar ptk[PTKlen]) +{ + uchar buf[2*Eaddrlen + 2*Noncelen], *p; + AuthRpc *rpc; + int afd, ret; + char *s; + + ret = -1; + s = nil; + rpc = nil; + if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0) + goto out; + if((rpc = auth_allocrpc(afd)) == nil) + goto out; + if((s = getessid()) == nil) + goto out; + if((s = smprint("proto=wpapsk role=client essid=%q", s)) == nil) + goto out; + if((ret = auth_rpc(rpc, "start", s, strlen(s))) != ARok) + goto out; + p = buf; + memmove(p, smac, Eaddrlen); p += Eaddrlen; + memmove(p, amac, Eaddrlen); p += Eaddrlen; + memmove(p, snonce, Noncelen); p += Noncelen; + memmove(p, anonce, Noncelen); p += Noncelen; + if((ret = auth_rpc(rpc, "write", buf, p - buf)) != ARok) + goto out; + if((ret = auth_rpc(rpc, "read", nil, 0)) != ARok) + goto out; + if(rpc->narg != PTKlen){ + ret = -1; + goto out; + } + memmove(ptk, rpc->arg, PTKlen); + ret = 0; +out: + free(s); + if(afd >= 0) close(afd); + if(rpc != nil) auth_freerpc(rpc); + return ret; +} + +int +Hfmt(Fmt *f) +{ + uchar *p, *e; + + p = va_arg(f->args, uchar*); + e = p; + if(f->prec >= 0) + e += f->prec; + for(; p != e; p++) + if(fmtprint(f, "%.2x", *p) < 0) + return -1; + return 0; +} + +void +dumpkeydescr(Keydescr *kd) +{ + static struct { + int flag; + char *name; + } flags[] = { + Fptk, "ptk", + Fins, "ins", + Fack, "ack", + Fmic, "mic", + Fsec, "sec", + Ferr, "err", + Freq, "req", + Fenc, "enc", + }; + int i, f; + + f = kd->flags[0]<<8 | kd->flags[1]; + fprint(2, "type=%.*H vers=%d flags=%.*H ( ", + sizeof(kd->type), kd->type, kd->flags[1] & 7, + sizeof(kd->flags), kd->flags); + for(i=0; ikeylen), kd->keylen, + sizeof(kd->repc), kd->repc, + sizeof(kd->nonce), kd->nonce, + sizeof(kd->eapoliv), kd->eapoliv, + sizeof(kd->rsc), kd->rsc, + sizeof(kd->id), kd->id, + sizeof(kd->mic), kd->mic); + i = kd->datalen[0]<<8 | kd->datalen[1]; + fprint(2, "data[%.4x]=%.*H\n", i, i, kd->data); +} + +int +rc4unwrap(uchar key[16], uchar iv[16], uchar *data, int len) +{ + uchar seed[32]; + RC4state rs; + + memmove(seed, iv, 16); + memmove(seed+16, key, 16); + setupRC4state(&rs, seed, sizeof(seed)); + rc4skip(&rs, 256); + rc4(&rs, data, len); + return len; +} + +int +aesunwrap(uchar *key, int nkey, uchar *data, int len) +{ + static uchar IV[8] = { 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, }; + uchar B[16], *R; + AESstate s; + uint t; + int n; + + len -= 8; + if(len < 16 || (len % 8) != 0) + return -1; + n = len/8; + t = n*6; + setupAESstate(&s, key, nkey, 0); + memmove(B, data, 8); + memmove(data, data+8, n*8); + do { + for(R = data + (n - 1)*8; R >= data; t--, R -= 8){ + memmove(B+8, R, 8); + B[7] ^= (t >> 0); + B[6] ^= (t >> 8); + B[5] ^= (t >> 16); + B[4] ^= (t >> 24); + aes_decrypt(s.dkey, s.rounds, B, B); + memmove(R, B+8, 8); + } + } while(t > 0); + if(memcmp(B, IV, 8) != 0) + return -1; + return n*8; +} + +int +calcmic(Keydescr *kd, uchar *msg, int msglen) +{ + int vers; + + vers = kd->flags[1] & 7; + memset(kd->mic, 0, MIClen); + if(vers == 1){ + uchar digest[MD5dlen]; + + hmac_md5(msg, msglen, ptk, 16, digest, nil); + memmove(kd->mic, digest, MIClen); + return 0; + } + if(vers == 2){ + uchar digest[SHA1dlen]; + + hmac_sha1(msg, msglen, ptk, 16, digest, nil); + memmove(kd->mic, digest, MIClen); + return 0; + } + return -1; +} + +int +checkmic(Keydescr *kd, uchar *msg, int msglen) +{ + uchar tmp[MIClen]; + + memmove(tmp, kd->mic, MIClen); + if(calcmic(kd, msg, msglen) != 0) + return -1; + return memcmp(tmp, kd->mic, MIClen) != 0; +} + +void +reply(int eapver, uchar smac[Eaddrlen], uchar amac[Eaddrlen], int flags, Keydescr *kd, uchar *data, int datalen) +{ + uchar buf[4096], *m, *p = buf; + + memmove(p, amac, Eaddrlen); p += Eaddrlen; + memmove(p, smac, Eaddrlen); p += Eaddrlen; + *p++ = 0x88; + *p++ = 0x8e; + + m = p; + *p++ = eapver; + *p++ = 0x03; + datalen += Keydescrlen; + *p++ = datalen >> 8; + *p++ = datalen; + datalen -= Keydescrlen; + + memmove(p, kd, Keydescrlen); + kd = (Keydescr*)p; + kd->flags[0] = flags >> 8; + kd->flags[1] = flags; + kd->datalen[0] = datalen >> 8; + kd->datalen[1] = datalen; + p = kd->data; + memmove(p, data, datalen); + p += datalen; + + memset(kd->mic, 0, MIClen); + if(flags & Fmic) + calcmic(kd, m, p - m); + if(debug != 0){ + fprint(2, "\nreply(v%d) %E -> %E: ", eapver, smac, amac); + dumpkeydescr(kd); + } + datalen = p - buf; + if(write(fd, buf, datalen) != datalen) + sysfatal("write: %r"); +} + +void +usage(void) +{ + fprint(2, "%s: [-dp12] [-s essid] dev\n", argv0); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + uchar mac[Eaddrlen], buf[1024]; + static uchar brsne[258]; + char addr[128]; + uchar *rsne; + int rsnelen; + int n, try; + + quotefmtinstall(); + fmtinstall('H', Hfmt); + fmtinstall('E', eipfmt); + + rsne = nil; + rsnelen = -1; + peercipher = nil; + groupcipher = nil; + + ARGBEGIN { + case 'd': + debug = 1; + break; + case 'p': + prompt = 1; + break; + case 's': + strncpy(essid, EARGF(usage()), 32); + break; + case '1': + rsne = wpaie; + rsnelen = sizeof(wpaie); + peercipher = &tkip; + groupcipher = &tkip; + break; + case '2': + rsne = rsnie; + rsnelen = sizeof(rsnie); + peercipher = &ccmp; + groupcipher = &ccmp; + break; + default: + usage(); + } ARGEND; + + if(*argv != nil) + dev = *argv++; + + if(*argv != nil || dev == nil) + usage(); + + if(myetheraddr(mac, dev) < 0) + sysfatal("can't get mac address: %r"); + + snprint(addr, sizeof(addr), "%s!0x888e", dev); + if((fd = dial(addr, nil, devdir, &cfd)) < 0) + sysfatal("dial: %r"); + + if(essid[0] != 0){ + if(fprint(cfd, "essid %s", essid) < 0) + sysfatal("write essid: %r"); + } else { + getessid(); + if(essid[0] == 0) + sysfatal("no essid set"); + } + + if(prompt){ + char *s; + + s = smprint("proto=wpapsk essid=%q !password?", essid); + auth_getkey(s); + free(s); + } + +Connect: + /* bss scan might not be complete yet, so check for 10 seconds. */ + for(try = 10; (background || try >= 0) && !connected(); try--) + sleep(1000); + + if(rsnelen <= 0 || rsne == brsne){ + rsne = brsne; + rsnelen = buildrsne(rsne); + } + + if(rsnelen <= 0){ + /* default is WPA */ + rsne = wpaie; + rsnelen = sizeof(wpaie); + peercipher = &tkip; + groupcipher = &tkip; + } + + if(debug) + fprint(2, "rsne: %.*H\n", rsnelen, rsne); + + /* + * we use write() instead of fprint so the message gets written + * at once and not chunked up on fprint buffer. + */ + n = sprint((char*)buf, "auth %.*H", rsnelen, rsne); + if(write(cfd, buf, n) != n) + sysfatal("write auth: %r"); + + if(!background){ + background = 1; + if(!debug){ + switch(rfork(RFFDG|RFREND|RFPROC|RFNOWAIT)){ + default: + exits(nil); + case -1: + sysfatal("fork: %r"); + return; + case 0: + break; + } + } + } + + lastrepc = 0ULL; + for(;;){ + uchar smac[Eaddrlen], amac[Eaddrlen], snonce[Noncelen], anonce[Noncelen], *p, *e, *m; + int proto, eapver, flags, vers, datalen; + uvlong repc, rsc, tsc; + Keydescr *kd; + + if((n = read(fd, buf, sizeof(buf))) < 0) + sysfatal("read: %r"); + + if(n == 0){ + if(debug != 0) + fprint(2, "got deassociation\n"); + goto Connect; + } + + p = buf; + e = buf+n; + if(n < 2*Eaddrlen + 2) + continue; + memmove(smac, p, Eaddrlen); p += Eaddrlen; + memmove(amac, p, Eaddrlen); p += Eaddrlen; + proto = p[0]<<8 | p[1]; p += 2; + if(proto != 0x888e || memcmp(smac, mac, Eaddrlen) != 0) + continue; + + m = p; + n = e - p; + if(n < 4) + continue; + eapver = p[0]; + if((eapver != 0x01 && eapver != 0x02) || p[1] != 0x03) + continue; + + if(debug != 0) + fprint(2, "\nrecv(v%d) %E <- %E: ", eapver, smac, amac); + + n = p[2]<<8 | p[3]; + p += 4; + if(n < Keydescrlen || p + n > e){ + if(debug != 0) + fprint(2, "bad kd size\n"); + continue; + } + e = p + n; + kd = (Keydescr*)p; + if(debug != 0) + dumpkeydescr(kd); + + if(kd->type[0] != 0xFE && kd->type[0] != 0x02) + continue; + + vers = kd->flags[1] & 7; + flags = kd->flags[0]<<8 | kd->flags[1]; + datalen = kd->datalen[0]<<8 | kd->datalen[1]; + if(kd->data + datalen > e) + continue; + + if((flags & Fmic) == 0){ + if((flags & (Fptk|Fack)) != (Fptk|Fack)) + continue; + + memmove(anonce, kd->nonce, sizeof(anonce)); + genrandom(snonce, sizeof(snonce)); + if(getptk(smac, amac, snonce, anonce, ptk) != 0){ + if(debug != 0) + fprint(2, "getptk: %r\n"); + continue; + } + + /* ack key exchange with mic */ + memset(kd->rsc, 0, sizeof(kd->rsc)); + memset(kd->eapoliv, 0, sizeof(kd->eapoliv)); + memmove(kd->nonce, snonce, sizeof(kd->nonce)); + reply(eapver, smac, amac, (flags & ~(Fack|Fins)) | Fmic, kd, rsne, rsnelen); + } else { + uchar gtk[GTKlen]; + int gtklen, gtkkid; + + if(checkmic(kd, m, e - m) != 0){ + if(debug != 0) + fprint(2, "bad mic\n"); + continue; + } + + repc = (uvlong)kd->repc[7] | + (uvlong)kd->repc[6]<<8 | + (uvlong)kd->repc[5]<<16 | + (uvlong)kd->repc[4]<<24 | + (uvlong)kd->repc[3]<<32 | + (uvlong)kd->repc[2]<<40 | + (uvlong)kd->repc[1]<<48 | + (uvlong)kd->repc[0]<<56; + if(repc <= lastrepc){ + if(debug != 0) + fprint(2, "bad repc: %llux <= %llux\n", repc, lastrepc); + continue; + } + lastrepc = repc; + + rsc = (uvlong)kd->rsc[0] | + (uvlong)kd->rsc[1]<<8 | + (uvlong)kd->rsc[2]<<16 | + (uvlong)kd->rsc[3]<<24 | + (uvlong)kd->rsc[4]<<32 | + (uvlong)kd->rsc[5]<<40; + + if(datalen > 0 && (flags & Fenc) != 0){ + if(vers == 1) + datalen = rc4unwrap(ptk+16, kd->eapoliv, kd->data, datalen); + else + datalen = aesunwrap(ptk+16, 16, kd->data, datalen); + if(datalen <= 0){ + if(debug != 0) + fprint(2, "bad keywrap\n"); + continue; + } + if(debug != 0) + fprint(2, "unwraped keydata[%.4x]=%.*H\n", datalen, datalen, kd->data); + } + + gtklen = 0; + gtkkid = -1; + + if(kd->type[0] != 0xFE || (flags & (Fptk|Fack)) == (Fptk|Fack)){ + uchar *p, *x, *e; + + p = kd->data; + e = p + datalen; + for(; p+2 <= e; p = x){ + if((x = p+2+p[1]) > e) + break; + if(debug != 0) + fprint(2, "ie=%.2x data[%.2x]=%.*H\n", p[0], p[1], p[1], p+2); + if(p[0] == 0x30){ /* RSN */ + } + if(p[0] == 0xDD){ /* WPA */ + static uchar oui[] = { 0x00, 0x0f, 0xac, 0x01, }; + + if(p+2+sizeof(oui) > x || memcmp(p+2, oui, sizeof(oui)) != 0) + continue; + if((flags & Fenc) == 0) + continue; /* ignore gorup key if unencrypted */ + gtklen = x - (p + 8); + if(gtklen <= 0) + continue; + if(gtklen > sizeof(gtk)) + gtklen = sizeof(gtk); + memmove(gtk, p + 8, gtklen); + gtkkid = p[6] & 3; + } + } + } + + if((flags & (Fptk|Fack)) == (Fptk|Fack)){ + if(vers != 1) /* in WPA2, RSC is for group key only */ + tsc = 0LL; + else { + tsc = rsc; + rsc = 0LL; + } + /* install pairwise receive key */ + if(fprint(cfd, "rxkey %.*H %s:%.*H@%llux", Eaddrlen, amac, + peercipher->name, peercipher->keylen, ptk+32, tsc) < 0) + sysfatal("write rxkey: %r"); + + tsc = 0LL; + memset(kd->rsc, 0, sizeof(kd->rsc)); + memset(kd->eapoliv, 0, sizeof(kd->eapoliv)); + memset(kd->nonce, 0, sizeof(kd->nonce)); + reply(eapver, smac, amac, flags & ~(Fack|Fenc|Fins), kd, nil, 0); + sleep(100); + + /* install pairwise transmit key */ + if(fprint(cfd, "txkey %.*H %s:%.*H@%llux", Eaddrlen, amac, + peercipher->name, peercipher->keylen, ptk+32, tsc) < 0) + sysfatal("write txkey: %r"); + } else + if((flags & (Fptk|Fsec|Fack)) == (Fsec|Fack)){ + if(kd->type[0] == 0xFE){ + /* WPA always RC4 encrypts the GTK, even tho the flag isnt set */ + if((flags & Fenc) == 0) + datalen = rc4unwrap(ptk+16, kd->eapoliv, kd->data, datalen); + gtklen = datalen; + if(gtklen > sizeof(gtk)) + gtklen = sizeof(gtk); + memmove(gtk, kd->data, gtklen); + gtkkid = (flags >> 4) & 3; + } + + memset(kd->rsc, 0, sizeof(kd->rsc)); + memset(kd->eapoliv, 0, sizeof(kd->eapoliv)); + memset(kd->nonce, 0, sizeof(kd->nonce)); + reply(eapver, smac, amac, flags & ~(Fenc|Fack), kd, nil, 0); + } else + continue; + + if(gtklen >= groupcipher->keylen && gtkkid != -1){ + /* install group key */ + if(fprint(cfd, "rxkey%d %.*H %s:%.*H@%llux", + gtkkid, Eaddrlen, amac, + groupcipher->name, groupcipher->keylen, gtk, rsc) < 0) + sysfatal("write rxkey%d: %r", gtkkid); + } + } + } +}