import hmac import logging import os import pickle from binascii import hexlify, unhexlify from hashlib import sha256 from struct import pack, unpack from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from .usb import usb from .util import unhex password_hardcoded = unhexlify('717cd72d0962bc4a2846138dbb2c24192512a76407065f383846139d4bec2033') gwk_sign_hardcoded = unhexlify('3a4c76b76a97981d1274247e166610e77f4d9c9d07d3c728e532916bdd28b454') crt_hardcoded = unhex(''' 170000000001000001000000fcffffffffffffffffffffff00000000000000000000000001000000fffff fff0000000000000000000000000000000000000000000000000000000000000000000000004b60d2273e 3cce3bf6b053ccb0061d65bc86987655bdebb3e7933aaad835c65a0000000000000000000000000000000 0000000000000000000000000000000000000000096c298d84539a1f4a033eb2d817d0377f240a463e5e6 bcf847422ce1f2d1176b00000000000000000000000000000000000000000000000000000000000000000 0000000f551bf376840b6cbce5e316b5733ce2b169e0f7c4aebe78e9b7f1afee242e34f00000000000000 0000000000000000000000000000000000000000000000000000000000512563fcc2cab9f3849e17a7adf ae6bcffffffffffffffff00000000ffffffff000000000000000000000000000000000000000000000000 000000000000000000000000ffffffffffffffffffffffff00000000000000000000000001000000fffff fff000000000000000000000000000000000000000000000000000000000000000000000000 ''') crypto_backend = default_backend() def prf(secret, seed, length): n = (length + 0x20 - 1) // 0x20 res = b'' a = hmac.new(secret, seed, sha256).digest() while n > 0: res += hmac.new(secret, a + seed, sha256).digest() a = hmac.new(secret, a, sha256).digest() n -= 1 return res[:length] def hs_key(): key = password_hardcoded[:0x10] seed = password_hardcoded[0x10:] + b'\xaa' * 2 hs_key = prf(key, b'HS_KEY_PAIR_GEN' + seed, 0x20) return int(hs_key[::-1].hex(), 16) def with_2bytes_size(chunk): return pack('>H', len(chunk)) + chunk def with_3bytes_size(chunk): return pack('>BH', len(chunk) >> 16, len(chunk)) + chunk def with_1byte_size(chunk): return pack('>B', len(chunk)) + chunk def to_bytes(n): b = b'' while n: b += (n & 0xff).to_bytes(1, 'big') n >>= 8 return b def pad(b): l = 16 - (len(b) % 16) return b + bytes([l - 1]) * l def unpad(b): return b[:-1 - b[-1]] # TODO assert the right state transitions class Tls(): def __init__(self, usb): self.usb = usb self.reset() try: with open('/sys/class/dmi/id/product_name', 'r') as node: product_name = node.read().strip() with open('/sys/class/dmi/id/product_serial', 'r') as node: product_serial = node.read().strip() except: product_name = 'VirtualBox' product_serial = '0' self.set_hwkey(product_name=product_name, serial_number=product_serial) def reset(self): self.trace_enabled = False self.secure_rx = False self.secure_tx = False # Info about the host computer def set_hwkey(self, product_name, serial_number): hw_key = bytes(product_name, 'ascii') + b'\0' + \ bytes(serial_number, 'ascii') + b'\0' # pre-TLS keys self.psk_encryption_key = prf(password_hardcoded, b'GWK' + hw_key, 0x20) self.psk_validation_key = prf(self.psk_encryption_key, b'GWK_SIGN' + gwk_sign_hardcoded, 0x20) def cmd(self, cmd): if self.secure_rx and self.secure_tx: rsp = self.app(cmd) else: rsp = self.usb.cmd(cmd) return rsp def open(self): self.secure_rx = False self.secure_tx = False self.handshake_hash = sha256() rsp = self.usb.cmd(unhexlify('44000000') + self.make_handshake(self.make_client_hello())) self.parse_tls_response(rsp) self.make_keys() rsp = self.usb.cmd( unhexlify('44000000') + self.make_handshake(self.make_certs() + self.make_client_kex() + self.make_cert_verify()) + self.make_change_cipher_spec() + self.make_handshake(self.make_finish())) self.parse_tls_response(rsp) def trace(self, s): if self.trace_enabled: logging.debug(s) def app(self, b): b = b() if callable(b) else b return self.parse_tls_response(self.usb.cmd(self.make_app_data(b))) def update_neg(self, b): self.handshake_hash.update(b) def make_keys(self): skey = ec.generate_private_key(ec.SECP256R1(), crypto_backend) self.session_public = skey.private_numbers().public_numbers pre_master_secret = skey.exchange(ec.ECDH(), self.ecdh_q) seed = self.client_random + self.server_random self.master_secret = prf(pre_master_secret, b'master secret' + seed, 0x30) key_block = prf(self.master_secret, b'key expansion' + seed, 0x120) self.sign_key = key_block[0x00:0x20] self.validation_key = key_block[0x20:0x20 + 0x20] self.encryption_key = key_block[0x40:0x40 + 0x20] self.decryption_key = key_block[0x60:0x60 + 0x20] def save(self): with open('tls.dict', 'wb') as f: pickle.dump( { 'sign_key': self.sign_key, 'validation_key': self.validation_key, 'encryption_key': self.encryption_key, 'decryption_key': self.decryption_key, 'secure_rx': self.secure_rx, 'secure_tx': self.secure_tx }, f) def load(self): with open('tls.dict', 'rb') as f: d = pickle.load(f) self.sign_key = d['sign_key'] self.validation_key = d['validation_key'] self.encryption_key = d['encryption_key'] self.decryption_key = d['decryption_key'] self.secure_rx = d['secure_rx'] self.secure_tx = d['secure_tx'] def decrypt(self, c): iv, c = c[:0x10], c[0x10:] cipher = Cipher(algorithms.AES(self.decryption_key), modes.CBC(iv), backend=crypto_backend) decryptor = cipher.decryptor() m = decryptor.update(c) + decryptor.finalize() m = unpad(m) return m def encrypt(self, b): #iv = unhexlify('454849acdd075174d6b9e713a957c2e7') iv = os.urandom(0x10) cipher = Cipher(algorithms.AES(self.encryption_key), modes.CBC(iv), backend=crypto_backend) encryptor = cipher.encryptor() b = pad(b) c = encryptor.update(b) + encryptor.finalize() return iv + c def validate(self, t, b): b, hs = b[:-0x20], b[-0x20:] hdr = pack('>BBBH', t, 3, 3, len(b)) sig = hmac.new(self.validation_key, hdr + b, sha256).digest() if sig != hs: raise Exception('Packet signature validation check failed') self.trace('tls> %02x: %s' % (t, hexlify(b).decode())) hdr = pack('>BBBH', t, 3, 3, len(b)) sig = hmac.new(self.sign_key, hdr + b, sha256).digest() return b + sig def make_finish(self): self.secure_tx = True hs_hash = self.handshake_hash.copy().digest() verify_data = prf(self.master_secret, b'client finished' + hs_hash, 0xc) return b'\x14' + with_3bytes_size(verify_data) def make_change_cipher_spec(self): return unhexlify('140303000101') def make_certs(self): cert = self.tls_cert cert = unhexlify('ac16') + cert # what's this? cert = pack('>BH', 0, len( self.tls_cert)) + cert # this seems to violate the standard (should be len(cert)) cert = pack('>BH', 0, len(self.tls_cert)) + cert # same return self.with_neg_hdr(0x0b, cert) def with_neg_hdr(self, t, b): b = pack('>B', t) + with_3bytes_size(b) self.update_neg(b) return b def make_client_kex(self): b = b'\x04' + to_bytes(self.session_public.x)[::-1] + to_bytes(self.session_public.y)[::-1] return self.with_neg_hdr(0x10, b) def make_cert_verify(self): buf = self.handshake_hash.copy().digest() b = self.priv_key.sign(buf, ec.ECDSA(Prehashed(hashes.SHA256()))) return self.with_neg_hdr(0x0f, b) def handle_server_hello(self, p): if p[:2] != unhexlify('0303'): raise Exception('unexpected TLS version %s' % hexlify(p[:2]).decode()) p = p[2:] self.server_random, p = p[:0x20], p[0x20:] l = p[0] self.server_sessid, p = p[1:1 + l], p[1 + l:] (suite, ), p = unpack('>H', p[:2]), p[2:] if suite != 0xc005: raise Exception('Server accepted unsupported cipher suite %04x' % suite) if p[0] != 0: raise Exception('Server selected to enable compression, which we don' 't support %02x' % p[0]) p = p[1:] if p != b'': raise Exception('Not expecting any more data') def handle_cert_req(self, p): (sign_and_hash_algo, ), p = unpack('>H', p[:2]), p[2:] if sign_and_hash_algo != 0x140: raise Exception( 'Server requested a cert with an unsupported sign and hash algo combination %04x' % sign_and_hash_algo) (l, ), p = unpack('>H', p[:2]), p[2:] if l != 0: raise Exception('Server requested a cert with non-empty list of CAs') if p != b'': raise Exception('Not expecting any more data') def handle_server_hello_done(self, p): if p != b'': raise Exception('Not expecting any body for "server hello done" pkt: %s' % hexlify(p).decode()) def handle_finish(self, b): hs_hash = self.handshake_hash.copy().digest() verify_data = prf(self.master_secret, b'server finished' + hs_hash, 0xc) if verify_data != b: raise Exception('Final handshake check failed') def handle_app_data(self, b): if not self.secure_rx: raise Exception('App payload before secure connection established') return self.validate(0x17, self.decrypt(b)) def handle_handshake(self, handshake): if self.secure_rx: handshake = self.validate(0x16, self.decrypt(handshake)) while len(handshake) > 0: while len(handshake) < 4: handshake += b'\0' hdr, handshake = handshake[:4], handshake[4:] t, l12, l3 = unpack('>BHB', hdr) l = (l12 << 8) | l3 p, handshake = handshake[:l], handshake[l:] if t == 2: self.handle_server_hello(p) elif t == 0xd: self.handle_cert_req(p) elif t == 0xe: self.handle_server_hello_done(p) elif t == 0x14: self.handle_finish(p) else: raise Exception('Unknown handshake packet %02x' % t) self.update_neg(hdr + p) def parse_tls_response(self, rsp): app_data = b'' while len(rsp) > 0: while len(rsp) < 5: rsp += b'\0' hdr, rsp = rsp[:5], rsp[5:] t, mj, mn, sz = unpack('>BBBH', hdr) pkt, rsp = rsp[:sz], rsp[sz:] if mj != 3 or mn != 3: raise Exception('Unexpected TLS version %d %d' % (mj, mn)) if t == 0x16: self.handle_handshake(pkt) elif t == 0x14: if pkt != unhexlify('01'): raise Exception('Unexpected ChangeCipherSpec payload') self.secure_rx = True elif t == 0x17: app_data += self.handle_app_data(pkt) else: raise Exception('Dont know how to handle message type %02x' % t) return app_data def make_app_data(self, b): if not self.secure_tx: raise Exception('App payload before secure connection established') b = self.encrypt(self.sign(0x17, b)) return unhexlify('170303') + with_2bytes_size(b) def make_handshake(self, b): if self.secure_tx: b = self.encrypt(self.sign(0x16, b)) return unhexlify('160303') + with_2bytes_size(b) def make_client_hello(self): h = unhexlify('0303') # TLS 1.2 #self.client_random = unhexlify('bc349559ac16c8f8362191395b4d04a435d870315f519eed8777488bc2b9600c') self.client_random = os.urandom(0x20) h += self.client_random # client's random h += with_1byte_size(unhexlify('00000000000000')) # session ID suits = b'' suits += pack('>H', 0xc005) # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA suits += pack('>H', 0x003d) # TLS_RSA_WITH_AES_256_CBC_SHA256 suits += pack('>H', 0x008d) # TLS_RSA_WITH_AES_256_CBC_SHA256 h += with_2bytes_size(suits) h += with_1byte_size(b'') # no compression options exts = b'' exts += self.make_ext(0x004, pack('>H', 0x0017)) # truncated_hmac = 0x17 exts += self.make_ext(0x00b, with_1byte_size(unhexlify('00'))) # EC points format = uncompressed # h += with_2bytes_size(exts) h += pack('>H', len(exts) - 2) + exts # -2? WHY?!... return self.with_neg_hdr(0x01, h) def make_ext(self, id, b): return pack('>H', id) + with_2bytes_size(b) def parseTlsFlash(self, reply): while len(reply) > 0: hdr, reply = reply[:4], reply[4:] hs, reply = reply[:0x20], reply[0x20:] id, sz = unpack('