# TODO: For now only include the code we actually used for EAP-pwd # TODO: Program unit tests so we can easily keep our EAP-pwd code correct #!/usr/bin/env python3 from scapy.all import * from .wifi import * import sys, struct, math, random, select, time, binascii from Crypto.Hash import HMAC, SHA256 from Crypto.PublicKey import ECC from Crypto.Math.Numbers import Integer # Alternative is https://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python from sympy.ntheory.residue_ntheory import sqrt_mod_iter # ----------------------- Utility --------------------------------- def int_to_data(num): return binascii.unhexlify("%064x" % num) def zeropoint_to_data(): return int_to_data(0) + int_to_data(0) #TODO: Not sure if this actually works under python2... def str2bytes(password): if not isinstance(password, str): return password if sys.version_info < (3, 0): return bytes(password) else: return bytes(password, 'utf8') def getord(value): if isinstance(value, int): return value else: return ord(value) def HMAC256(pw, data): h = HMAC.new(pw, digestmod=SHA256) h.update(data) return h.digest() # ----------------------- Elliptic Curve Operations --------------------------------- # This is group 19. Support of it is required by WPA3. secp256r1_p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff secp256r1_r = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 def legendre_symbol(a, p): """Compute the Legendre symbol.""" if a % p == 0: return 0 ls = pow(a, (p - 1)//2, p) return -1 if ls == p - 1 else ls def point_on_curve(x, y, curve="p256"): try: point = ECC.EccPoint(x, y) except ValueError: return False return True def point_to_data(p): if p is None: return zeropoint_to_data() return int_to_data(p.x) + int_to_data(p.y) # ----------------------- WPA3 --------------------------------- def is_sae(p): if not Dot11Auth in p: return False return p[Dot11Auth].algo == 3 def is_sae_commit(p): return is_sae(p) and p[Dot11Auth].seqnum == 1 def is_sae_confirm(p): return is_sae(p) and p[Dot11Auth].seqnum == 2 def KDF_Length(data, label, context, length): iterations = int(math.ceil(length / 256.0)) result = b"" for i in range(1, iterations + 1): hash_data = struct.pack(" addr2 else addr2 + addr1 for counter in range(1, 100): hash_data = str2bytes(password) + struct.pack("= curve.p: continue x = Integer(pwd_value) y_sqr = (x**3 - x * 3 + curve.b) % curve.p if legendre_symbol(y_sqr, curve.p) != 1: continue y = y_sqr.sqrt(curve.p) y_bit = getord(pwd_seed[-1]) & 1 if y & 1 == y_bit: return ECC.EccPoint(x, y, curve_name) else: return ECC.EccPoint(x, curve.p - y, curve_name) # TODO: Use this somewhere??? def calc_k_kck_pmk(pwe, peer_element, peer_scalar, my_rand, my_scalar): k = ((pwe * peer_scalar + peer_element) * my_rand).x keyseed = HMAC256(b"\x00" * 32, int_to_data(k)) kck_and_pmk = KDF_Length(keyseed, "SAE KCK and PMK", int_to_data((my_scalar + peer_scalar) % secp256r1_r), 512) kck = kck_and_pmk[0:32] pmk = kck_and_pmk[32:] return k, kck, pmk def calculate_confirm_hash(kck, send_confirm, scalar, element, peer_scalar, peer_element): return HMAC256(kck, struct.pack(" 1 # COMMIT-ELEMENT = inverse(mask * PWE) temp = self.pwe * mask self.element = ECC.EccPoint(temp.x, Integer(secp256r1_p) - temp.y) auth = build_sae_commit(self.srcaddr, self.dstaddr, self.scalar, self.element) sendp(RadioTap()/auth) def process_commit(self, p): payload = str(p[Dot11Auth].payload) group_id = struct.unpack(" 1 else b"" hash_data += struct.pack(">H", i) + str2bytes(label) + struct.pack(">H", length) digest = HMAC256(data, hash_data) result += digest result = result[:num_bytes] if length % 8 != 0: num_clear = 8 - (length % 8) trailbyte = result[-1] >> num_clear << num_clear result = result[:-1] + struct.pack(">B", trailbyte) return result def derive_pwe_ecc_eappwd(password, peer_id, server_id, token, curve_name="p256", info=None): curve = ECC._curves[curve_name] bits = curve.modulus_bits hash_pw = struct.pack(">I", token) + str2bytes(peer_id + server_id + password) for counter in range(1, 100): hash_data = hash_pw + struct.pack("> (8 - (521 % 8)) if pwd_value >= curve.p: continue x = Integer(pwd_value) log(DEBUG, "X-candidate: %x" % x) y_sqr = (x**3 - x * 3 + curve.b) % curve.p if legendre_symbol(y_sqr, curve.p) != 1: continue y = y_sqr.sqrt(curve.p) y_bit = getord(pwd_seed[-1]) & 1 if y & 1 == y_bit: if not info is None: info["counter"] = counter return ECC.EccPoint(x, y, curve_name) else: if not info is None: info["counter"] = counter return ECC.EccPoint(x, curve.p - y, curve_name) def calculate_confirm_eappwd(k, element1, scalar1, element2, scalar2, group_num=19, rand_func=1, prf=1): hash_data = int_to_data(k) hash_data += point_to_data(element1) hash_data += int_to_data(scalar1) hash_data += point_to_data(element2) hash_data += int_to_data(scalar2) hash_data += struct.pack(">HBB", group_num, rand_func, prf) confirm = HMAC256(b"\x00" * 32, hash_data) return confirm # ----------------------- Fuzzing/Testing --------------------------------- def inject_sae_auth(srcaddr, bssid): p = Dot11(addr1=bssid, addr2=srcaddr, addr3=bssid) p = p/Dot11Auth(algo=3, seqnum=1, status=0) group_id = 19 scalar = 0 element_x = 0 element_y = 0 p = p/Raw(struct.pack("