def main(): args = sys.argv[1:] for arg in args: print("// Path: m/44'/866'/0'") print("MnemonicToRistretto {") print(f" phrase: \"{arg}\",") print(" account_index: 0,") mnemo = mnemonic.Mnemonic("english") master_seed = mnemo.to_seed(arg, "") # manually build the path to m/usage/cointype/acctidx # for us, usage is BIP-44, cointype is MobileCoin # m k, c = seed2hdnode(master_seed, b"ed25519 seed", 'ed25519') # m/44 k, c = derive(k, c, 44 + privdev, 'ed25519') # m/44/866 k, c = derive(k, c, 866 + privdev, 'ed25519') # m/44/866/0 acct0, _c = derive(k, c, 0 + privdev, 'ed25519') kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-view", acct0, hashlib.sha512) key = kdf.expand(length=64) print(f" view_hex: \"{key.hex()}\",") kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-spend", acct0, hashlib.sha512) key = kdf.expand(length=64) print(f" spend_hex: \"{key.hex()}\",") print("},") print("// Path: m/44'/866'/1'") print("MnemonicToRistretto {") print(f" phrase: \"{arg}\",") print(" account_index: 1,") # m/44/866/1 acct1, _c = derive(k, c, 1 + privdev, 'ed25519') kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-view", acct1, hashlib.sha512) key = kdf.expand(length=64) print(f" view_hex: \"{key.hex()}\",") kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-spend", acct1, hashlib.sha512) key = kdf.expand(length=64) print(f" spend_hex: \"{key.hex()}\",") print("},")
def receiveM2(self, data): logger.info("Receive M2") pvDict = OPACK.decode(data) tlv = TLV8Box.decodeFromData(pvDict["pd"]).toDict() searching_device_pub_key = x25519.X25519PublicKey.from_public_bytes( bytes(tlv[0x03])) self.shared_secret = self.session_keys.private.exchange( searching_device_pub_key) hkdf_inst = hkdf.Hkdf("Pair-Verify-Encrypt-Salt".encode(), self.shared_secret, hash=hashlib.sha512) key = hkdf_inst.expand("Pair-Verify-Encrypt-Info".encode(), 32) encryptedData = bytes(tlv[0x05])[:-16] authKey = bytes(tlv[0x05])[-16:] nonce = "PV-Msg02".encode() cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) plainData = cipher.decrypt_and_verify(encryptedData, authKey) plainTLV = TLV8Box.decodeFromData(plainData).toDict() # plainTLV contains authentication data: # { # 9: Signature over public keys of grantor and requestor # 10: Apple ID certificate (NSDataCompressed) # 20: Apple ID validation record (NSDataCompressed) # } logger.debug(f"Identity TLV: {plainTLV}") self.sendM3()
def check_class_tv(tv): '''Test HKDF output via wrapper class''' kdf = hkdf.Hkdf(tv["salt"], tv["IKM"], tv["hash"]) test_okm = kdf.expand(tv["info"], tv["L"]) print "%s (via wrapper class)" % tv print "PRK: %s" % ("match" if kdf._prk == tv["PRK"] else "FAIL") print "OKM: %s" % ("match" if test_okm == tv["OKM"] else "FAIL") print assert_equals(kdf._prk, tv["PRK"]) assert_equals(test_okm, tv["OKM"])
def get_privkey(ln_dir, known_pubkey: str): hsm_secret = open(f"{ln_dir}/hsm_secret", "rb").read() logger.debug(f"hsm_secret: {hsm_secret}") # To generate the node private key, apply hkdf to string b"nodeid" salt = bytes([0]) or b"\x00" key = hkdf.Hkdf(salt, hsm_secret, hash=hashlib.sha256).expand(b"nodeid") # Small chance of the key produced not being valid to secp256k1 parameters, so test # and increase the salt until it is valid. i = 0 privkey = b"" while True and i < 1000: if i == 999: logger.error( "No valid secp256k1 key found for hsm secret after 1000 salts") return False try: privkey = secp256k1.PrivateKey(key) except Exception: # invalid key i += 1 salt = bytes([i]) key = hkdf.Hkdf(salt, hsm_secret, hash=hashlib.sha256).expand(b"nodeid") else: # Key valid under secp256k1 break # Check public key derived from the private key against node id if not privkey.pubkey.serialize().hex() == known_pubkey: logger.warning( f"Valid secp265k1 derived pubkey doesn't appear to match " f"lightning node id:") logger.warning(f"generated: {privkey.pubkey.serialize().hex()}") logger.warning(f"actual: {known_pubkey}") return key.hex()
def expand_argon2_secret(pkm: bytes, context: bytes, salt: Optional[bytes] = None) -> bytes: """ Expand ``pkm`` and ``context`` into a key of length ``bytes`` using HKDF's expand function based on HMAC SHA-512). See the HKDF draft RFC and paper for usage notes. :param pkm: :param context: :param salt: :return: """ kdf = hkdf.Hkdf(salt=salt, input_key_material=pkm, hash=hashlib.sha512) key = kdf.expand(info=context, length=32) return key
def sendM3(self): logger.info("Send M3") hkdf_inst = hkdf.Hkdf("Pair-Verify-Encrypt-Salt".encode(), self.shared_secret, hash=hashlib.sha512) key = hkdf_inst.expand("Pair-Verify-Encrypt-Info".encode(), 32) nonce = "PV-Msg03".encode() payload = bytes() cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) _ciphertext, authTag = cipher.encrypt_and_digest(payload) tlv8_bytes = TLV8Box([TLV8(0x05, authTag), TLV8(0x06, b"\x03")]).encode() opack_bytes = OPACK.encode({ "pd": tlv8_bytes, }) self.send(0x13, opack_bytes)
def encrypt(): req = request.json CaaS_creds = req['CaaS_creds'] UserList = req['UserList'] ciphertext = base64.b64decode(req['Ciphertext']) response.content_type = "application/json" rv = dict() # Abort if any of the people in the list don't have CaaS accounts for user in UserList: if not user_exists(user): rv['Status'] = "Unsuccessful" rv['Error'] = "User {0} not found. Aborted.".format(user) return rv # Add sender to user list UserList.append(CaaS_creds['username']) # Sort user list UserList = sorted(UserList) hashes = [''] # Calculate the iterative hashes for j in range(len(UserList)): s = hashlib.sha512() s.update(UserList[j] + hashes[j]) h = s.hexdigest() hashes.append(h) # Obtain key via HMAC-based key derivation function (hkdf) kdf = hkdf.Hkdf(binascii.unhexlify(hashes[-1]), Xp, hash=hashlib.sha512) key = kdf.expand(b"context1", 32) ctr = os.urandom(16) e = AES.new(key, AES.MODE_CTR, counter=lambda: ctr) CaaS_encrypted_text = e.encrypt(ciphertext) rv['ctr'] = base64.b64encode(str(ctr)) rv['enc_p'] = base64.b64encode(CaaS_encrypted_text) return rv
def sendPWS3(self): logger.info("Send PWS3") hkdf_inst = hkdf.Hkdf("WriteKeySalt".encode(), self.shared_secret, hash=hashlib.sha512) key = hkdf_inst.expand("WriteKeyInfo".encode(), 32) opack_bytes = OPACK.encode({ "op": 5, "nw": self.ssid, "psk": self.psk, }) aad = bytes(b"\x06\x07") nonce = bytes(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) cipher.update(aad) encryptedData, authTag = cipher.encrypt_and_digest(opack_bytes) encryptedData += authTag self.send(0x06, encryptedData)
def get_session_keys(pairing_data): """ HomeKit Controller state machine to perform a pair verify operation as described in chapter 4.8 page 47 ff. :param pairing_data: the paring data as returned by perform_pair_setup :return: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) :raises InvalidAuthTagError: if the auth tag could not be verified, :raises IncorrectPairingIdError: if the accessory's LTPK could not be found :raises InvalidSignatureError: if the accessory's signature could not be verified :raises AuthenticationError: if the secured session could not be established """ # # Step #1 ios --> accessory (send verify start Request) (page 47) # ios_key = x25519.X25519PrivateKey.generate() ios_key_pub = ios_key.public_key().public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) request_tlv = [(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_PublicKey, ios_key_pub)] step2_expectations = [ TLV.kTLVType_State, TLV.kTLVType_PublicKey, TLV.kTLVType_EncryptedData ] response_tlv = yield (request_tlv, step2_expectations) # # Step #3 ios --> accessory (send SRP verify request) (page 49) # response_tlv = TLV.reorder(response_tlv, step2_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M2, 'get_session_keys: not M2' assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'get_session_keys: no public key' assert response_tlv[2][ 0] == TLV.kTLVType_EncryptedData, 'get_session_keys: no encrypted data' # 1) generate shared secret accessorys_session_pub_key_bytes = bytes(response_tlv[1][1]) accessorys_session_pub_key = x25519.X25519PublicKey.from_public_bytes( accessorys_session_pub_key_bytes) shared_secret = ios_key.exchange(accessorys_session_pub_key) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify auth tag on encrypted data and 4) decrypt encrypted = response_tlv[2][1] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), encrypted) if type(decrypted) == bool and not decrypted: raise InvalidAuthTagError('step 3') d1 = TLV.decode_bytes(decrypted) d1 = TLV.reorder(d1, [TLV.kTLVType_Identifier, TLV.kTLVType_Signature]) assert d1[0][ 0] == TLV.kTLVType_Identifier, 'get_session_keys: no identifier' assert d1[1][0] == TLV.kTLVType_Signature, 'get_session_keys: no signature' # 5) look up pairing by accessory name accessory_name = d1[0][1].decode() if pairing_data['AccessoryPairingID'] != accessory_name: raise IncorrectPairingIdError('step 3') accessory_ltpk = ed25519.VerifyingKey( bytes.fromhex(pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[1][1] accessory_session_pub_key_bytes = response_tlv[1][1] accessory_info = accessory_session_pub_key_bytes + accessory_name.encode( ) + ios_key_pub try: accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)) except ed25519.BadSignatureError: raise InvalidSignatureError('step 3') # 7) create iOSDeviceInfo ios_device_info = ios_key_pub + pairing_data['iOSPairingId'].encode( ) + accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltpk_h = pairing_data['iOSDeviceLTPK'] ios_device_ltsk = ed25519.SigningKey( bytes.fromhex(ios_device_ltsk_h) + bytes.fromhex(ios_device_ltpk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv sub_tlv = TLV.encode_list([(TLV.kTLVType_Identifier, pairing_data['iOSPairingId'].encode()), (TLV.kTLVType_Signature, ios_device_signature)]) # 10) encrypt and sign encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), sub_tlv) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 11) create tlv request_tlv = [(TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, tmp)] step3_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error] response_tlv = yield (request_tlv, step3_expectations) # # Post Step #4 verification (page 51) # response_tlv = TLV.reorder(response_tlv, step3_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M4, 'get_session_keys: not M4' if len(response_tlv) == 2 and response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'verification') # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand( 'Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand( 'Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key
def perform_pair_setup_part2(pin, ios_pairing_id, write_fun, salt, server_public_key): """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :param pin: the setup code from the accessory :param ios_pairing_id: the id of the simulated ios device :param write_fun: a function that takes a bytes representation of a TLV, the expected keys as list and returns decoded TLV as list :return: a dict with the ios device's part of the pairing information :raises UnavailableError: if the device is already paired :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts :raises BusyError: if a parallel pairing is ongoing :raises AuthenticationError: if the verification of the device's SRP proof fails :raises MaxPeersError: if the device cannot accept an additional pairing :raises IllegalData: if the verification of the accessory's data fails """ srp_client = SrpClient('Pair-Setup', pin) srp_client.set_salt(salt) srp_client.set_server_public_key(server_public_key) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = TLV.encode_list([ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), ]) step4_expectations = [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_Proof ] response_tlv = write_fun(response_tlv, step4_expectations) # # Step #5 ios --> accessory (Exchange Request) (see page 43) # logging.debug('#5 ios -> accessory: send SRP exchange request') # M4 Verification (page 43) response_tlv = TLV.reorder(response_tlv, step4_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][1] == TLV.M4, \ 'perform_pair_setup: State not M4' if response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'step 5') assert response_tlv[1][ 0] == TLV.kTLVType_Proof, 'perform_pair_setup: Not a proof' if not srp_client.verify_servers_proof(response_tlv[1][1]): raise AuthenticationError('Step #5: wrong proof!') # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk, ios_device_ltpk = ed25519.create_keypair() # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_ltpk.to_bytes( ) ios_device_signature = ios_device_ltsk.sign(ios_device_info) sub_tlv = [(TLV.kTLVType_Identifier, ios_device_pairing_id), (TLV.kTLVType_PublicKey, ios_device_ltpk.to_bytes()), (TLV.kTLVType_Signature, ios_device_signature)] sub_tlv_b = TLV.encode_list(sub_tlv) # taking tge iOSDeviceX as key was reversed from # https://github.com/KhaosT/HAP-NodeJS/blob/2ea9d761d9bd7593dd1949fec621ab085af5e567/lib/HAPServer.js # function handlePairStepFive calling encryption.encryptAndSeal encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] response_tlv = [(TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, tmp)] body = TLV.encode_list(response_tlv) step6_expectations = [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_EncryptedData ] response_tlv = write_fun(body, step6_expectations) # # Step #7 ios (Verification) (page 47) # response_tlv = TLV.reorder(response_tlv, step6_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M6, 'perform_pair_setup: State not M6' if response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'step 7') assert response_tlv[1][ 0] == TLV.kTLVType_EncryptedData, 'perform_pair_setup: No encrypted data' decrypted_data = chacha20_aead_decrypt(bytes(), session_key, 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]), response_tlv[1][1]) if decrypted_data is False: raise homekit.exception.IllegalData('step 7') response_tlv = TLV.decode_bytearray(decrypted_data) response_tlv = TLV.reorder(response_tlv, [ TLV.kTLVType_Identifier, TLV.kTLVType_PublicKey, TLV.kTLVType_Signature ]) assert response_tlv[2][ 0] == TLV.kTLVType_Signature, 'perform_pair_setup: No signature' accessory_sig = response_tlv[2][1] assert response_tlv[0][ 0] == TLV.kTLVType_Identifier, 'perform_pair_setup: No identifier' accessory_pairing_id = response_tlv[0][1] assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'perform_pair_setup: No public key' accessory_ltpk = response_tlv[1][1] hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpClient.to_byte_array( srp_client.get_session_key()), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk e25519s = ed25519.VerifyingKey(bytes(response_tlv[1][1])) try: e25519s.verify(bytes(accessory_sig), bytes(accessory_info)) except AssertionError: raise InvalidSignatureError('step #7') return { 'AccessoryPairingID': response_tlv[0][1].decode(), 'AccessoryLTPK': hexlify(response_tlv[1][1]).decode(), 'iOSPairingId': ios_pairing_id, 'iOSDeviceLTSK': ios_device_ltsk.to_ascii(encoding='hex').decode()[:64], 'iOSDeviceLTPK': hexlify(ios_device_ltpk.to_bytes()).decode() }
def hkdf_sha256(key, salt, info, length): kdf = hkdf.Hkdf(salt, key, hash=hashlib.sha256) return kdf.expand(info, length)
def get_session_keys(conn, pairing_data): """ Perform a pair verify operation as described in chapter 4.8 page 47 ff. :param conn: the http connection to the target accessory :param pairing_data: the paring data as returned by perform_pair_setup :returns: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) """ headers = { 'Content-Type': 'application/pairing+tlv8' } # Step #1 ios --> accessory (send verify start request) (page 47) ios_key = PrivateKey.generate() request_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M1, TLV.kTLVType_PublicKey: bytes(ios_key.public_key), }) try: conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Step #3 ios --> accessory (send SRP verify request) (page 49) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M2: return None if TLV.kTLVType_PublicKey not in response_tlv: return None if TLV.kTLVType_EncryptedData not in response_tlv: return None # 1) generate shared secret accessorys_session_pub_key_bytes = \ bytes(response_tlv[TLV.kTLVType_PublicKey]) shared_secret = crypto_scalarmult( bytes(ios_key), bytes(PublicKey(accessorys_session_pub_key_bytes))) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify authtag on encrypted data and 4) decrypt encrypted = response_tlv[TLV.kTLVType_EncryptedData] decrypted = crypto_aead_chacha20poly1305_ietf_decrypt( bytes(encrypted), bytes(), bytes([0, 0, 0, 0]) + 'PV-Msg02'.encode(), session_key) if not decrypted: return None d1 = TLV.decode_bytes(decrypted) if TLV.kTLVType_Identifier not in d1: return None if TLV.kTLVType_Signature not in d1: return None # 5) look up pairing by accessory name accessory_name = d1[TLV.kTLVType_Identifier].decode() if pairing_data['AccessoryPairingID'] != accessory_name: return None accessory_ltpk = VerifyKey(bytes.fromhex(pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[TLV.kTLVType_Signature] accessory_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey] accessory_info = accessory_session_pub_key_bytes + \ accessory_name.encode() + bytes(ios_key.public_key) try: accessory_ltpk.verify(bytes(accessory_info), bytes(accessory_sig)) except BadSignatureError: return None # 7) create iOSDeviceInfo ios_device_info = bytes(ios_key.public_key) + \ pairing_data['iOSPairingID'].encode() + \ accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltsk = SigningKey(bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info).signature # 9) construct sub tlv sub_tlv = TLV.encode_dict({ TLV.kTLVType_Identifier: pairing_data['iOSPairingID'].encode(), TLV.kTLVType_Signature: ios_device_signature }) # 10) encrypt and sign ciphertext = crypto_aead_chacha20poly1305_ietf_encrypt( bytes(sub_tlv), bytes(), bytes([0, 0, 0, 0]) + 'PV-Msg03'.encode(), session_key) tmp = ciphertext # 11) create tlv request_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M3, TLV.kTLVType_EncryptedData: tmp }) # 12) send to accessory try: conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Post Step #4 verification (page 51) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M4: return None if TLV.kTLVType_Error in response_tlv: return None # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = \ hkdf_inst.expand('Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = \ hkdf_inst.expand('Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key
def perform_pair_setup(connection, pin, ios_pairing_id): """ Perform a pair setup operation as described in chapter 4.7 page 39 ff. :param connection: the http connection to the target accessory :param pin: the setup code from the accessory :param ios_pairing_id: the id of the simulated ios device :returns: a dict with the ios device's part of the pairing information """ headers = { 'Content-Type': 'application/pairing+tlv8' } # Step #1 ios --> accessory (send SRP start request) (see page 39) request_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M1, TLV.kTLVType_Method: TLV.PairSetup }) try: connection.request('POST', '/pair-setup', request_tlv, headers) resp = connection.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Step #3 ios --> accessory (send SRP verify request) (see page 41) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M2: return None if TLV.kTLVType_Error in response_tlv: return None srp_client = SrpClient('Pair-Setup', pin) srp_client.set_salt(response_tlv[TLV.kTLVType_Salt]) srp_client.set_server_public_key(response_tlv[TLV.kTLVType_PublicKey]) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M3, TLV.kTLVType_PublicKey: SrpClient.to_byte_array(client_pub_key), TLV.kTLVType_Proof: SrpClient.to_byte_array(client_proof), }) try: connection.request('POST', '/pair-setup', response_tlv, headers) resp = connection.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Step #5 ios --> accessory (exchange request) (see page 43) # M4 Verification (page 43) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M4: return None if TLV.kTLVType_Error in response_tlv: return None if TLV.kTLVType_Proof not in response_tlv: return None if not srp_client.verify_servers_proof(response_tlv[TLV.kTLVType_Proof]): return None # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk = SigningKey.generate() ios_device_ltpk = ios_device_ltsk.verify_key # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = \ ios_device_x + ios_device_pairing_id + bytes(ios_device_ltpk) ios_device_signature = ios_device_ltsk.sign(ios_device_info).signature sub_tlv = { TLV.kTLVType_Identifier: ios_device_pairing_id, TLV.kTLVType_PublicKey: bytes(ios_device_ltpk), TLV.kTLVType_Signature: ios_device_signature } sub_tlv_b = TLV.encode_dict(sub_tlv) # taking the iOSDeviceX as key was reversed from # https://github.com/KhaosT/HAP-NodeJS/blob/ # 2ea9d761d9bd7593dd1949fec621ab085af5e567/lib/HAPServer.js # function handlePairStepFive calling encryption.encryptAndSeal ciphertext = crypto_aead_chacha20poly1305_ietf_encrypt( bytes(sub_tlv_b), bytes(), bytes([0, 0, 0, 0]) + 'PS-Msg05'.encode(), session_key) tmp = ciphertext response_tlv = { TLV.kTLVType_State: TLV.M5, TLV.kTLVType_EncryptedData: tmp } body = TLV.encode_dict(response_tlv) try: connection.request('POST', '/pair-setup', body, headers) resp = connection.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Step #7 ios (verification) (page 47) if response_tlv[TLV.kTLVType_State] != TLV.M6: return None if TLV.kTLVType_Error in response_tlv: return None if TLV.kTLVType_EncryptedData not in response_tlv: return None decrypted_data = crypto_aead_chacha20poly1305_ietf_decrypt( bytes(response_tlv[TLV.kTLVType_EncryptedData]), bytes(), bytes([0, 0, 0, 0]) + 'PS-Msg06'.encode(), session_key) if not decrypted_data: return None decrypted_data = bytearray(decrypted_data) response_tlv = TLV.decode_bytearray(decrypted_data) if TLV.kTLVType_Signature not in response_tlv: return None accessory_sig = response_tlv[TLV.kTLVType_Signature] if TLV.kTLVType_Identifier not in response_tlv: return None accessory_pairing_id = response_tlv[TLV.kTLVType_Identifier] if TLV.kTLVType_PublicKey not in response_tlv: return None accessory_ltpk = response_tlv[TLV.kTLVType_PublicKey] hkdf_inst = hkdf.Hkdf( 'Pair-Setup-Accessory-Sign-Salt'.encode(), SrpClient.to_byte_array(srp_client.get_session_key()), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk e25519s = VerifyKey(bytes(response_tlv[TLV.kTLVType_PublicKey])) e25519s.verify(bytes(accessory_info), bytes(accessory_sig)) return { 'AccessoryPairingID': response_tlv[TLV.kTLVType_Identifier].decode(), 'AccessoryLTPK': hexlify(response_tlv[TLV.kTLVType_PublicKey]).decode(), 'iOSPairingID': ios_pairing_id, 'iOSDeviceLTSK': hexlify(bytes(ios_device_ltsk)).decode(), 'iOSDeviceLTPK': hexlify(bytes(ios_device_ltpk)).decode(), }
def request_pin_setup(connection, pin): """ Requests a Pin from device """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send SRP start Request) (see page 39) # request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_Method, TLV.PairSetup)]) # # Step #3 ios --> accessory (send SRP verify request) (see page 41) # connection.request('POST', '/pair-setup', request_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) response_tlv = TLV.reorder(response_tlv, [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_PublicKey, TLV.kTLVType_Salt ]) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M2, 'perform_pair_setup: State not M2' # the errors here can be: # * kTLVError_Unavailable: Device is paired # * kTLVError_MaxTries: More than 100 unsuccessfull attempts # * kTLVError_Busy: There is already a pairing going on # if response_tlv[1][0] == TLV.kTLVType_Error: # error_handler(response_tlv[1][1], 'step 3') assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'perform_pair_setup: Not a public key' assert response_tlv[2][ 0] == TLV.kTLVType_Salt, 'perform_pair_setup: Not a salt' srp_client = SrpClient('Pair-Setup', pin) srp_client.set_salt(response_tlv[2][1]) srp_client.set_server_public_key(response_tlv[1][1]) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = TLV.encode_list([ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), ]) connection.request('POST', '/pair-setup', response_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #5 ios --> accessory (Exchange Request) (see page 43) # # M4 Verification (page 43) # response_tlv = TLV.reorder(response_tlv, [TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_Proof]) # assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][1] == TLV.M4, \ # 'perform_pair_setup: State not M4' # if response_tlv[1][0] == TLV.kTLVType_Error: # error_handler(response_tlv[1][1], 'step 5') # assert response_tlv[1][0] == TLV.kTLVType_Proof, 'perform_pair_setup: Not a proof' # if not srp_client.verify_servers_proof(response_tlv[1][1]): # raise AuthenticationError('Step #5: wrong proof!') # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk, ios_device_ltpk = ed25519.create_keypair() # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) # ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = ios_device_x + ios_device_ltpk.to_bytes() ios_device_signature = ios_device_ltsk.sign(ios_device_info) sub_tlv = [ # (TLV.kTLVType_Identifier, ios_device_pairing_id), (TLV.kTLVType_PublicKey, ios_device_ltpk.to_bytes()), (TLV.kTLVType_Signature, ios_device_signature) ] sub_tlv_b = TLV.encode_list(sub_tlv) # taking tge iOSDeviceX as key was reversed from # https://github.com/KhaosT/HAP-NodeJS/blob/2ea9d761d9bd7593dd1949fec621ab085af5e567/lib/HAPServer.js # function handlePairStepFive calling encryption.encryptAndSeal encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] response_tlv = [(TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, tmp)] body = TLV.encode_list(response_tlv) connection.request('POST', '/pair-setup', body, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read())
def hash_hkdf(algo, ikm, length=0, info=b'', salt=b''): h = getattr(hashlib, algo) kdf = hkdf.Hkdf(salt, ikm, h) if length == 0: length = h().digest_size return kdf.expand(info, length)
def get_session_keys(conn, pairing_data): """ Performs a pair verify operation as described in chapter 4.8 page 47 ff. :param conn: the http connection to the target accessory :param pairing_data: the paring data as returned by perform_pair_setup :return: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send verify start Request) (page 47) # ios_key = py25519.Key25519() request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_PublicKey, ios_key.pubkey)]) conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #3 ios --> accessory (send SRP verify request) (page 49) # assert TLV.kTLVType_State in response_tlv, response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M2 assert TLV.kTLVType_PublicKey in response_tlv, response_tlv assert TLV.kTLVType_EncryptedData in response_tlv, response_tlv # 1) generate shared secret accessorys_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey] shared_secret = ios_key.get_ecdh_key( py25519.Key25519(pubkey=bytes(accessorys_session_pub_key_bytes), verifyingkey=bytes())) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify authtag on encrypted data and 4) decrypt encrypted = response_tlv[TLV.kTLVType_EncryptedData] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), encrypted) if decrypted == False: raise homekit.exception.InvalidAuth("step 3") d1 = TLV.decode_bytes(decrypted) assert TLV.kTLVType_Identifier in d1 assert TLV.kTLVType_Signature in d1 # 5) look up pairing by accessory name accessory_name = d1[TLV.kTLVType_Identifier].decode() if pairing_data['AccessoryPairingID'] != accessory_name: raise homekit.exception.IncorrectPairingID("step 3") accessory_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes.fromhex( pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[TLV.kTLVType_Signature] accessory_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey] accessory_info = accessory_session_pub_key_bytes + accessory_name.encode( ) + ios_key.pubkey if not accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)): raise homekit.exception.InvalidSignature("step 3") # 7) create iOSDeviceInfo ios_device_info = ios_key.pubkey + pairing_data['iOSPairingId'].encode( ) + accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltsk = py25519.Key25519( secretkey=bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv sub_tlv = TLV.encode_list([(TLV.kTLVType_Identifier, pairing_data['iOSPairingId'].encode()), (TLV.kTLVType_Signature, ios_device_signature)]) # 10) encrypt and sign encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), sub_tlv) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 11) create tlv request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, tmp)]) # 12) send to accessory conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Post Step #4 verification (page 51) # if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "verification") assert TLV.kTLVType_State in response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M4 # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand( 'Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand( 'Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key
def _derive_subkey(self, salt: bytes): return hkdf.Hkdf(salt, self.key, hashlib.sha1).expand(self.INFO, self.KEY_SIZE)
def perform_pair_setup(connection, pin, ios_pairing_id): """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :param connection: the http connection to the target accessory :param pin: the setup code from the accessory :param ios_pairing_id: the id of the simulated ios device :return: a dict with the ios device's part of the pairing information """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send SRP start Request) (see page 39) # request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_Method, TLV.PairSetup)]) connection.request('POST', '/pair-setup', request_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #3 ios --> accessory (send SRP verify request) (see page 41) # assert TLV.kTLVType_State in response_tlv, response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M2 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 3") srp_client = SrpClient('Pair-Setup', pin) srp_client.set_salt(response_tlv[TLV.kTLVType_Salt]) srp_client.set_server_public_key(response_tlv[TLV.kTLVType_PublicKey]) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = TLV.encode_list([ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), ]) connection.request('POST', '/pair-setup', response_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #5 ios --> accessory (Exchange Request) (see page 43) # # M4 Verification (page 43) assert TLV.kTLVType_State in response_tlv, response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M4 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 5") assert TLV.kTLVType_Proof in response_tlv if not srp_client.verify_servers_proof(response_tlv[TLV.kTLVType_Proof]): print('Step #5: wrong proof!') # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk, ios_device_ltpk = ed25519.create_keypair() # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_ltpk.to_bytes( ) ios_device_signature = ios_device_ltsk.sign(ios_device_info) sub_tlv = [(TLV.kTLVType_Identifier, ios_device_pairing_id), (TLV.kTLVType_PublicKey, ios_device_ltpk.to_bytes()), (TLV.kTLVType_Signature, ios_device_signature)] sub_tlv_b = TLV.encode_list(sub_tlv) # taking tge iOSDeviceX as key was reversed from # https://github.com/KhaosT/HAP-NodeJS/blob/2ea9d761d9bd7593dd1949fec621ab085af5e567/lib/HAPServer.js # function handlePairStepFive calling encryption.encryptAndSeal encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] response_tlv = [(TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, tmp)] body = TLV.encode_list(response_tlv) connection.request('POST', '/pair-setup', body, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #7 ios (Verification) (page 47) # assert response_tlv[TLV.kTLVType_State] == TLV.M6 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 7") assert TLV.kTLVType_EncryptedData in response_tlv decrypted_data = chacha20_aead_decrypt( bytes(), session_key, 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]), response_tlv[TLV.kTLVType_EncryptedData]) if decrypted_data == False: raise homekit.exception.IllegalData("step 7") response_tlv = TLV.decode_bytearray(decrypted_data) assert TLV.kTLVType_Signature in response_tlv accessory_sig = response_tlv[TLV.kTLVType_Signature] assert TLV.kTLVType_Identifier in response_tlv accessory_pairing_id = response_tlv[TLV.kTLVType_Identifier] assert TLV.kTLVType_PublicKey in response_tlv accessory_ltpk = response_tlv[TLV.kTLVType_PublicKey] hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpClient.to_byte_array( srp_client.get_session_key()), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk e25519s = ed25519.VerifyingKey(bytes(response_tlv[TLV.kTLVType_PublicKey])) e25519s.verify(bytes(accessory_sig), bytes(accessory_info)) return { 'AccessoryPairingID': response_tlv[TLV.kTLVType_Identifier].decode(), 'AccessoryLTPK': hexlify(response_tlv[TLV.kTLVType_PublicKey]).decode(), 'iOSPairingId': ios_pairing_id, 'iOSDeviceLTSK': ios_device_ltsk.to_ascii(encoding='hex').decode(), 'iOSDeviceLTPK': hexlify(ios_device_ltpk.to_bytes()).decode() }
print "uid:\t<{}>".format(hexlify(uid)) print "kB:\t<{}>".format(hexlify(kB)) # Validate OAuth client details, and fetch scoped-key metadata. scoped_key_identifier = 'app_key:{}'.format(urlquote(urlorigin(redirect_uri))) print "scoped_key_identifier:\t'{}'".format(scoped_key_identifier) print "key_rotation_secret:\t<{}>".format(hexlify(key_rotation_secret)) print "key_rotation_timestamp:\t{}".format(key_rotation_timestamp) # Calculate the scoped key and its fingerprint via HKDF. context = 'identity.mozilla.com/picl/v1/scoped_key\n' + scoped_key_identifier ks_bytes = hkdf.Hkdf(uid, kB + key_rotation_secret).expand(context, 16 + 32) kSfp = ks_bytes[:16] kS = ks_bytes[16:] assert len(kS) == 32 print "kSfp:\t<{}>".format(hexlify(kSfp)) print "kS:\t<{}>".format(hexlify(kS)) # Serialize into a JSON key bundle payload. keys_bundle = json_encode({ "app_key": { "kid": "{}-{}".format(key_rotation_timestamp, b64url_encode(kSfp)), "k": b64url_encode(kS),
def _derive_subkey(self, salt: bytes) -> bytes: return hkdf.Hkdf(salt, self.master_key, sha1).expand(self.info, self.KEY_SIZE)
def _post_pair_verify(self): d_req = TLV.decode_bytes(self.body) d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1: # step #2 Accessory -> iOS Device Verify Start Response if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('Step #2 /pair-verify') # 1) generate new curve25519 key pair accessory_session_key = py25519.Key25519() accessory_spk = accessory_session_key.public_key().pubkey self.server.sessions[self.session_id]['accessory_pub_key'] = accessory_spk # 2) generate shared secret ios_device_curve25519_pub_key_bytes = d_req[TLV.kTLVType_PublicKey] self.server.sessions[self.session_id]['ios_device_pub_key'] = ios_device_curve25519_pub_key_bytes ios_device_curve25519_pub_key = py25519.Key25519(pubkey=bytes(ios_device_curve25519_pub_key_bytes), verifyingkey=bytes()) shared_secret = accessory_session_key.get_ecdh_key(ios_device_curve25519_pub_key) self.server.sessions[self.session_id]['shared_secret'] = shared_secret # 3) generate accessory info accessory_info = accessory_spk + self.server.data.accessory_pairing_id_bytes + \ ios_device_curve25519_pub_key_bytes # 4) sign accessory info for accessory signature accessory_ltsk = py25519.Key25519(secretkey=self.server.data.accessory_ltsk) accessory_signature = accessory_ltsk.sign(accessory_info) # 5) sub tlv sub_tlv = { TLV.kTLVType_Identifier: self.server.data.accessory_pairing_id_bytes, TLV.kTLVType_Signature: accessory_signature } sub_tlv_b = TLV.encode_dict(sub_tlv) # 6) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) self.server.sessions[self.session_id]['session_key'] = session_key # 7) encrypt sub tlv encrypted_data_with_auth_tag = chacha20_aead_encrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 8) construct result tlv d_res[TLV.kTLVType_State] = TLV.M2 d_res[TLV.kTLVType_PublicKey] = accessory_spk d_res[TLV.kTLVType_EncryptedData] = tmp self._send_response_tlv(d_res) if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('after step #2\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M3: # step #4 Accessory -> iOS Device Verify Finish Response if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('Step #4 /pair-verify') session_key = self.server.sessions[self.session_id]['session_key'] # 1) verify ios' authtag # 2) decrypt encrypted = d_req[TLV.kTLVType_EncryptedData] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), encrypted) if decrypted == False: self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: authtag', d_res, self.server.sessions) return d1 = TLV.decode_bytes(decrypted) assert TLV.kTLVType_Identifier in d1 assert TLV.kTLVType_Signature in d1 # 3) get ios_device_ltpk ios_device_pairing_id = d1[TLV.kTLVType_Identifier] self.server.sessions[self.session_id]['ios_device_pairing_id'] = ios_device_pairing_id ios_device_ltpk_bytes = self.server.data.get_peer_key(ios_device_pairing_id) if ios_device_ltpk_bytes is None: self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: not paired', d_res, self.server.sessions) return ios_device_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=ios_device_ltpk_bytes) # 4) verify ios_device_info ios_device_sig = d1[TLV.kTLVType_Signature] ios_device_curve25519_pub_key_bytes = self.server.sessions[self.session_id]['ios_device_pub_key'] accessory_spk = self.server.sessions[self.session_id]['accessory_pub_key'] ios_device_info = ios_device_curve25519_pub_key_bytes + ios_device_pairing_id + accessory_spk if not ios_device_ltpk.verify(bytes(ios_device_sig), bytes(ios_device_info)): self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: signature', d_res, self.server.sessions) return # shared_secret = self.server.sessions[self.session_id]['shared_secret'] hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand('Control-Write-Encryption-Key'.encode(), 32) self.server.sessions[self.session_id]['controller_to_accessory_key'] = controller_to_accessory_key self.server.sessions[self.session_id]['controller_to_accessory_count'] = 0 hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand('Control-Read-Encryption-Key'.encode(), 32) self.server.sessions[self.session_id]['accessory_to_controller_key'] = accessory_to_controller_key self.server.sessions[self.session_id]['accessory_to_controller_count'] = 0 d_res[TLV.kTLVType_State] = TLV.M4 self._send_response_tlv(d_res) if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('after step #4\n%s', TLV.to_string(d_res)) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)
def _post_pair_setup(self): d_req = TLV.decode_bytes(self.body) self.log_message('POST /pair-setup request body:\n%s', TLV.to_string(d_req)) d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1: # step #2 Accessory -> iOS Device SRP Start Response self.log_message('Step #2 /pair-setup') # 1) Check if paired if self.server.data.is_paired: self.send_error_reply(TLV.M2, TLV.kTLVError_Unavailable) return # 2) Check if over 100 attempts if self.server.data.unsuccessful_tries > 100: self.log_error('to many failed attempts') self.send_error_reply(TLV.M2, TLV.kTLVError_MaxTries) return # 3) Check if already in pairing if False: self.send_error_reply(TLV.M2, TLV.kTLVError_Busy) return # 4) 5) 7) Create in SRP Session, set username and password server = SrpServer('Pair-Setup', self.server.data.setup_code) # 6) create salt salt = server.get_salt() # 8) show setup code to user sc = self.server.data.setup_code sc_str = 'Setup Code\n┌─' + '─' * len(sc) + '─┐\n│ ' + sc + ' │\n└─' + '─' * len(sc) + '─┘' self.log_message(sc_str) # 9) create public key public_key = server.get_public_key() # 10) create response tlv and send response d_res[TLV.kTLVType_State] = TLV.M2 d_res[TLV.kTLVType_PublicKey] = SrpServer.to_byte_array(public_key) d_res[TLV.kTLVType_Salt] = SrpServer.to_byte_array(salt) self._send_response_tlv(d_res) # store session self.server.sessions[self.session_id]['srp'] = server self.log_message('after step #2:\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M3: # step #4 Accessory -> iOS Device SRP Verify Response self.log_message('Step #4 /pair-setup') # 1) use ios pub key to compute shared secret key ios_pub_key = bytes_to_mpz(d_req[TLV.kTLVType_PublicKey]) server = self.server.sessions[self.session_id]['srp'] server.set_client_public_key(ios_pub_key) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpServer.to_byte_array(server.get_session_key()), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) self.server.sessions[self.session_id]['session_key'] = session_key # 2) verify ios proof ios_proof = bytes_to_mpz(d_req[TLV.kTLVType_Proof]) if not server.verify_clients_proof(ios_proof): d_res[TLV.kTLVType_State] = TLV.M4 d_res[TLV.kTLVType_Error] = TLV.kTLVError_Authentication self._send_response_tlv(d_res) print('error in step #4', d_res, self.server.sessions) return else: self.log_message('ios proof was verified') # 3) generate accessory proof accessory_proof = server.get_proof(ios_proof) # 4) create response tlv d_res[TLV.kTLVType_State] = TLV.M4 d_res[TLV.kTLVType_Proof] = SrpServer.to_byte_array(accessory_proof) # 5) send response tlv self._send_response_tlv(d_res) self.log_message('after step #4:\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M5: # step #6 Accessory -> iOS Device Exchange Response self.log_message('Step #6 /pair-setup') # 1) Verify the iOS device's authTag # done by chacha20_aead_decrypt # 2) decrypt and test encrypted_data = d_req[TLV.kTLVType_EncryptedData] decrypted_data = chacha20_aead_decrypt(bytes(), self.server.sessions[self.session_id]['session_key'], 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), encrypted_data) if decrypted_data == False: d_res[TLV.kTLVType_State] = TLV.M6 d_res[TLV.kTLVType_Error] = TLV.kTLVError_Authentication self.send_error_reply(TLV.M6, TLV.kTLVError_Authentication) print('error in step #6', d_res, self.server.sessions) return d_req_2 = TLV.decode_bytearray(decrypted_data) # 3) Derive ios_device_x shared_secret = self.server.sessions[self.session_id]['srp'].get_session_key() hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpServer.to_byte_array(shared_secret), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) # 4) construct ios_device_info ios_device_pairing_id = d_req_2[TLV.kTLVType_Identifier] ios_device_ltpk = d_req_2[TLV.kTLVType_PublicKey] ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_ltpk # 5) verify signature ios_device_sig = d_req_2[TLV.kTLVType_Signature] verify_key = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes(ios_device_ltpk)) if not verify_key.verify(bytes(ios_device_sig), bytes(ios_device_info)): self.send_error_reply(TLV.M6, TLV.kTLVError_Authentication) print('error in step #6', d_res, self.server.sessions) return # 6) save ios_device_pairing_id and ios_device_ltpk self.server.data.add_peer(ios_device_pairing_id, ios_device_ltpk) # Response Generation # 1) generate accessoryLTPK if not existing if self.server.data.accessory_ltsk is None or self.server.data.accessory_ltpk is None: accessory_ltsk = py25519.Key25519() accessory_ltpk = accessory_ltsk.verifyingkey self.server.data.set_accessory_keys(accessory_ltpk, accessory_ltsk.secretkey) else: accessory_ltsk = py25519.Key25519(self.server.data.accessory_ltsk) accessory_ltpk = accessory_ltsk.verifyingkey # 2) derive AccessoryX hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpServer.to_byte_array(shared_secret), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) # 3) accessory_info = accessory_x + self.server.data.accessory_pairing_id_bytes + accessory_ltpk # 4) generate signature accessory_signature = accessory_ltsk.sign(accessory_info) # 5) construct sub_tlv sub_tlv = { TLV.kTLVType_Identifier: self.server.data.accessory_pairing_id_bytes, TLV.kTLVType_PublicKey: accessory_ltpk, TLV.kTLVType_Signature: accessory_signature } sub_tlv_b = TLV.encode_dict(sub_tlv) # 6) encrypt sub_tlv encrypted_data_with_auth_tag = chacha20_aead_encrypt(bytes(), self.server.sessions[self.session_id]['session_key'], 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 7) send response self.server.publish_device() d_res = dict() d_res[TLV.kTLVType_State] = TLV.M6 d_res[TLV.kTLVType_EncryptedData] = tmp self._send_response_tlv(d_res) self.log_message('after step #6:\n%s', TLV.to_string(d_res)) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)
# # This takes a SLIP-0010 output (specifically, what is termed the "Ed25519 # private key" by the spec) and runs it through a pair of KDF instances to # create the view and spend outputs. # # This code should not be re-used in a production system. # import hkdf import hashlib import sys args = sys.argv[1:] print(args) for arg in args: print("SlipToRistretto {") print(f" slip10_hex: \"{arg}\",") slip10 = bytes.fromhex(arg) kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-view", slip10, hashlib.sha512) key = kdf.expand(length=64) print(f" view_hex: \"{key.hex()}\",") kdf = hkdf.Hkdf(b"mobilecoin-ristretto255-spend", slip10, hashlib.sha512) key = kdf.expand(length=64) print(f" spend_hex: \"{key.hex()}\",") print("},")
def hkdf(key, salt, info, output_length): f = hkdf_i.Hkdf(salt, key) return f.expand(info, output_length)
def get_session_keys(conn, pairing_data): """ HomeKit Controller side call to perform a pair verify operation as described in chapter 4.8 page 47 ff. :param conn: the http_impl connection to the target accessory :param pairing_data: the paring data as returned by perform_pair_setup :return: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) :raises InvalidAuthTagError: if the auth tag could not be verified, :raises IncorrectPairingIdError: if the accessory's LTPK could not be found :raises InvalidSignatureError: if the accessory's signature could not be verified :raises AuthenticationError: if the secured session could not be established """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send verify start Request) (page 47) # ios_key = py25519.Key25519() request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_PublicKey, ios_key.pubkey)]) conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #3 ios --> accessory (send SRP verify request) (page 49) # response_tlv = TLV.reorder(response_tlv, [ TLV.kTLVType_State, TLV.kTLVType_PublicKey, TLV.kTLVType_EncryptedData ]) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M2, 'get_session_keys: not M2' assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'get_session_keys: no public key' assert response_tlv[2][ 0] == TLV.kTLVType_EncryptedData, 'get_session_keys: no encrypted data' # 1) generate shared secret accessorys_session_pub_key_bytes = response_tlv[1][1] shared_secret = ios_key.get_ecdh_key( py25519.Key25519(pubkey=bytes(accessorys_session_pub_key_bytes), verifyingkey=bytes())) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify auth tag on encrypted data and 4) decrypt encrypted = response_tlv[2][1] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), encrypted) if type(decrypted) == bool and not decrypted: raise InvalidAuthTagError('step 3') d1 = TLV.decode_bytes(decrypted) d1 = TLV.reorder(d1, [TLV.kTLVType_Identifier, TLV.kTLVType_Signature]) assert d1[0][ 0] == TLV.kTLVType_Identifier, 'get_session_keys: no identifier' assert d1[1][0] == TLV.kTLVType_Signature, 'get_session_keys: no signature' # 5) look up pairing by accessory name accessory_name = d1[0][1].decode() if pairing_data['AccessoryPairingID'] != accessory_name: raise IncorrectPairingIdError('step 3') accessory_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes.fromhex( pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[1][1] accessory_session_pub_key_bytes = response_tlv[1][1] accessory_info = accessory_session_pub_key_bytes + accessory_name.encode( ) + ios_key.pubkey if not accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)): raise InvalidSignatureError('step 3') # 7) create iOSDeviceInfo ios_device_info = ios_key.pubkey + pairing_data['iOSPairingId'].encode( ) + accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltsk = py25519.Key25519( secretkey=bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv sub_tlv = TLV.encode_list([(TLV.kTLVType_Identifier, pairing_data['iOSPairingId'].encode()), (TLV.kTLVType_Signature, ios_device_signature)]) # 10) encrypt and sign encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), sub_tlv) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 11) create tlv request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, tmp)]) # 12) send to accessory conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Post Step #4 verification (page 51) # response_tlv = TLV.reorder(response_tlv, [TLV.kTLVType_State, TLV.kTLVType_Error]) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M4, 'get_session_keys: not M4' if len(response_tlv) == 2 and response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'verification') # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand( 'Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand( 'Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key