#!/usr/bin/env python3 """Second-round supplicant tests: GTK rekey, message-3 retransmission, and RSN negotiation against realistic access-point information elements.""" import hashlib, hmac, os, subprocess, sys, struct from cryptography.hazmat.primitives.keywrap import aes_key_wrap HARNESS = sys.argv[1] fails = [] def prf(key, label, data, n): r=b''; i=0 while len(r)H',b,2,HDR+len(kd)-4); b[4]=2 struct.pack_into('>H',b,5,ki); struct.pack_into('>H',b,7,16) b[9:17]=replay; b[17:49]=nonce; b[65:73]=rsc struct.pack_into('>H',b,97,len(kd)); b[HDR:]=kd if ki & 0x0100: b[81:97]=mic(kck, bytes(b)) return bytes(b) def check_mic(f,kck): x=bytearray(f); x[81:97]=b'\x00'*16 return mic(kck,bytes(x))==f[81:97] def gtk_kde(gtk, idx): kd = bytes([0xdd, 6+len(gtk), 0x00,0x0f,0xac, 0x01, idx, 0x00]) + gtk if len(kd)%8: kd += b'\xdd' + b'\x00'*(7-len(kd)%8) return kd def start(p, ssid, passphrase, spa, aa): p.stdin.write(f"START {spa.hex()} {aa.hex()} {ssid} {passphrase} 2 4 4\n"); p.stdin.flush() p.stdout.readline(); p.stdout.readline() def rd(p): return p.stdout.readline().strip() # ---------------------------------------------------------------- handshake + print("=== GTK rekey and message-3 retransmission ===") aa=bytes.fromhex('001122334455'); spa=bytes.fromhex('aabbccddeeff') ssid="MontaukTest"; pw="supersecret123" p=subprocess.Popen([HARNESS],stdin=subprocess.PIPE,stdout=subprocess.PIPE,text=True,bufsize=1) start(p, ssid, pw, spa, aa) pmk=hashlib.pbkdf2_hmac('sha1',pw.encode(),ssid.encode(),4096,32) an=os.urandom(32) p.stdin.write("RX "+build(0x0002|0x0008|0x0080,(1).to_bytes(8,'big'),an,b'\x00'*8,b'').hex()+"\n"); p.stdin.flush() m2=bytes.fromhex(rd(p).split()[1]); rd(p) sn=m2[17:49] ptk=derive_ptk(pmk,aa,spa,an,sn); kck,kek,tk=ptk[:16],ptk[16:32],ptk[32:48] gtk1=os.urandom(16) m3=build(0x0002|0x0008|0x0040|0x0080|0x0100|0x0200|0x1000,(2).to_bytes(8,'big'),an, b'\x00'*8, aes_key_wrap(kek, gtk_kde(gtk1,1)), kck) p.stdin.write("RX "+m3.hex()+"\n"); p.stdin.flush() got={} while True: l=rd(p) if l.startswith('RX-OK'): break k,_,v=l.partition(' '); got[k]=v assert got['GTK']==gtk1.hex() and got['PTK']==tk.hex() print(f" initial handshake complete, state {l}") # message 3 retransmitted (AP missed message 4) m3b=build(0x0002|0x0008|0x0040|0x0080|0x0100|0x0200|0x1000,(3).to_bytes(8,'big'),an, b'\x00'*8, aes_key_wrap(kek, gtk_kde(gtk1,1)), kck) p.stdin.write("RX "+m3b.hex()+"\n"); p.stdin.flush() got2={} while True: l=rd(p) if l.startswith('RX-OK'): break k,_,v=l.partition(' '); got2[k]=v if 'TX' not in got2: fails.append("no message 4 in response to a retransmitted message 3") else: m4=bytes.fromhex(got2['TX']) ok = check_mic(m4,kck) and m4[9:17]==(3).to_bytes(8,'big') print(f" answered retransmitted message 3 with a fresh message 4: {ok}") if not ok: fails.append("message 4 replay answer malformed") # group key rekey (2-way, no pairwise bit) gtk2=os.urandom(16) gk=build(0x0002|0x0080|0x0100|0x0200|0x1000,(4).to_bytes(8,'big'),b'\x00'*32, bytes.fromhex('0a0b0c000000 0000'.replace(' ','')), aes_key_wrap(kek, gtk_kde(gtk2,2)), kck) p.stdin.write("RX "+gk.hex()+"\n"); p.stdin.flush() got3={} while True: l=rd(p) if l.startswith('RX-OK'): break k,_,v=l.partition(' '); got3[k]=v if got3.get('GTK')!=gtk2.hex(): fails.append(f"group rekey installed {got3.get('GTK')} instead of {gtk2.hex()}") else: print(f" group rekey installed the new GTK (index {got3['GTK-IDX']})") if 'TX' not in got3: fails.append("no acknowledgement to the group key message") else: m=bytes.fromhex(got3['TX']) ki=struct.unpack_from('>H',m,5)[0] ok = bool(check_mic(m, kck) and not (ki & 0x0008) and (ki & 0x0200)) print(f" group key acknowledged correctly (no pairwise bit, secure set): {ok}") if not ok: fails.append("group key acknowledgement malformed") p.stdin.close() # ---------------------------------------------------------------- RSN parsing print("\n=== RSN negotiation against real-world information elements ===") def suite(t): return bytes([0x00,0x0f,0xac,t]) def ie(group, pairwise, akms, caps=0): b = struct.pack(' accepted={ok} akm={akm} pairwise={pc}") if not good: fails.append(f"RSN parse: {name}") p.stdin.close() print() if fails: for f in fails: print("FAILURE:", f) sys.exit(1) print("All second-round supplicant cases passed.")