From c08397fc0946bbdbef665944e7d7a64e342d847b Mon Sep 17 00:00:00 2001 From: John Carr Date: Fri, 18 Jan 2019 14:07:51 +0000 Subject: [PATCH 1/2] Replace py25519 with cryptography. See https://cryptography.io/en/stable/. The cryptography library has a large community with active team-based maintenance doing regular releases. Heavy lifting is done by openssl, and a lot of work has been put into packaging. On most python installs (e.g. ones with whl support) a compiler isn't needed. It has broad platform support. In comparison, py25519 has not had a release since 2016, does not work on macOS, and runs its tests every time you import its module. --- homekit/accessoryserver.py | 43 ++++++++++++++++++++---------------- homekit/protocol/__init__.py | 28 ++++++++++++++--------- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/homekit/accessoryserver.py b/homekit/accessoryserver.py index 3160cccb..f98e4795 100644 --- a/homekit/accessoryserver.py +++ b/homekit/accessoryserver.py @@ -25,11 +25,13 @@ from socketserver import ThreadingMixIn import hkdf -import py25519 from zeroconf import Zeroconf, ServiceInfo import socket import sys import logging +import ed25519 + +from cryptography.hazmat.primitives.asymmetric import x25519 from homekit.crypto.chacha20poly1305 import chacha20_aead_decrypt, chacha20_aead_encrypt from homekit.crypto.srp import SrpServer @@ -653,16 +655,16 @@ def _post_pair_verify(self): 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 + accessory_session_key = x25519.X25519PrivateKey.generate() + accessory_spk = accessory_session_key.public_key().public_bytes() self.server.sessions[self.session_id]['accessory_pub_key'] = accessory_spk # 2) generate shared secret - ios_device_curve25519_pub_key_bytes = d_req[1][1] + ios_device_curve25519_pub_key_bytes = bytes(d_req[1][1]) 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) + ios_device_curve25519_pub_key = x25519.X25519PublicKey.from_public_bytes(ios_device_curve25519_pub_key_bytes) + + shared_secret = accessory_session_key.exchange(ios_device_curve25519_pub_key) self.server.sessions[self.session_id]['shared_secret'] = shared_secret # 3) generate accessory info @@ -670,7 +672,7 @@ def _post_pair_verify(self): ios_device_curve25519_pub_key_bytes # 4) sign accessory info for accessory signature - accessory_ltsk = py25519.Key25519(secretkey=self.server.data.accessory_ltsk) + accessory_ltsk = ed25519.SigningKey(self.server.data.accessory_ltsk) accessory_signature = accessory_ltsk.sign(accessory_info) # 5) sub tlv @@ -732,14 +734,16 @@ def _post_pair_verify(self): self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) self.log_error('error in step #4: not paired %s %s', d_res, self.server.sessions) return - ios_device_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=ios_device_ltpk_bytes) + ios_device_lptk = ed25519.VerifyingKey(ios_device_ltpk_bytes) # 4) verify ios_device_info ios_device_sig = d1[1][1] 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)): + try: + ios_device_lptk.verify(bytes(ios_device_sig), bytes(ios_device_info)) + except ed25519.BadSignatureError: self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) self.log_error('error in step #4: signature %s %s', d_res, self.server.sessions) return @@ -1040,8 +1044,10 @@ def _post_pair_setup(self): # 5) verify signature ios_device_sig = d_req_2[2][1] # should be [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)): + verify_key = ed25519.VerifyingKey(bytes(ios_device_ltpk)) + try: + verify_key.verify(bytes(ios_device_sig), bytes(ios_device_info)) + except ed25519.BadSignatureError: self.send_error_reply(TLV.M6, TLV.kTLVError_Authentication) self.log_error('error in step #6 %s %s', d_res, self.server.sessions) return @@ -1052,12 +1058,11 @@ def _post_pair_setup(self): # 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) + accessory_ltsk, accessory_ltpk = ed25519.create_keypair() + self.server.data.set_accessory_keys(accessory_ltpk.to_bytes(), accessory_ltsk.to_bytes()) else: - accessory_ltsk = py25519.Key25519(self.server.data.accessory_ltsk) - accessory_ltpk = accessory_ltsk.verifyingkey + accessory_ltsk = ed25519.SigningKey(self.server.data.accessory_ltsk) + accessory_ltpk = ed25519.VerifyingKey(self.server.data.accessory_ltpk) # 2) derive AccessoryX hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpServer.to_byte_array(shared_secret), @@ -1065,7 +1070,7 @@ def _post_pair_setup(self): 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 + accessory_info = accessory_x + self.server.data.accessory_pairing_id_bytes + accessory_ltpk.to_bytes() # 4) generate signature accessory_signature = accessory_ltsk.sign(accessory_info) @@ -1073,7 +1078,7 @@ def _post_pair_setup(self): # 5) construct sub_tlv sub_tlv = [ (TLV.kTLVType_Identifier, self.server.data.accessory_pairing_id_bytes), - (TLV.kTLVType_PublicKey, accessory_ltpk), + (TLV.kTLVType_PublicKey, accessory_ltpk.to_bytes()), (TLV.kTLVType_Signature, accessory_signature) ] sub_tlv_b = TLV.encode_list(sub_tlv) diff --git a/homekit/protocol/__init__.py b/homekit/protocol/__init__.py index 0663f386..fba599e2 100644 --- a/homekit/protocol/__init__.py +++ b/homekit/protocol/__init__.py @@ -17,8 +17,9 @@ import hashlib import ed25519 import hkdf -import py25519 from binascii import hexlify +from cryptography.hazmat.primitives.asymmetric import x25519 + from homekit.protocol.tlv import TLV from homekit.exceptions import IncorrectPairingIdError, InvalidAuthTagError, InvalidSignatureError, UnavailableError, \ AuthenticationError, InvalidError, BusyError, MaxTriesError, MaxPeersError, BackoffError @@ -243,11 +244,12 @@ def get_session_keys(conn, pairing_data): # # Step #1 ios --> accessory (send verify start Request) (page 47) # - ios_key = py25519.Key25519() + ios_key = x25519.X25519PrivateKey.generate() + ios_key_pub = ios_key.public_key().public_bytes() request_tlv = TLV.encode_list([ (TLV.kTLVType_State, TLV.M1), - (TLV.kTLVType_PublicKey, ios_key.pubkey) + (TLV.kTLVType_PublicKey, ios_key_pub) ]) conn.request('POST', '/pair-verify', request_tlv, headers) @@ -263,9 +265,11 @@ def get_session_keys(conn, pairing_data): 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())) + 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) @@ -288,21 +292,23 @@ def get_session_keys(conn, pairing_data): if pairing_data['AccessoryPairingID'] != accessory_name: raise IncorrectPairingIdError('step 3') - accessory_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes.fromhex(pairing_data['AccessoryLTPK'])) + 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.pubkey - if not accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)): + 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.pubkey + pairing_data['iOSPairingId'].encode() + accessorys_session_pub_key_bytes + 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_ltsk = py25519.Key25519(secretkey=bytes.fromhex(ios_device_ltsk_h)) + ios_device_ltsk = ed25519.SigningKey(bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv diff --git a/requirements.txt b/requirements.txt index f103fbf1..1463fc90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ zeroconf gmpy2 -py25519 hkdf ed25519 +cryptography diff --git a/setup.py b/setup.py index c65766b1..6cd656c3 100644 --- a/setup.py +++ b/setup.py @@ -41,9 +41,9 @@ install_requires=[ 'zeroconf', 'gmpy2', - 'py25519', 'hkdf', 'ed25519', + 'cryptography', ], license='Apache License 2.0', long_description=long_description, From 19cc88f20fd049674f8214da514bd3254060b949 Mon Sep 17 00:00:00 2001 From: John Carr Date: Sun, 20 Jan 2019 10:36:16 +0000 Subject: [PATCH 2/2] Serialization compat between ed25519 and py25519 ed25519 serializes its private key with the public key appended. Apart from this the format is the same as py25119. So we trim the private key when serializing, and append the public key when restoring a SigningKey. This should mean the upgrade is transparent for existing pairing files and existing accessories. --- homekit/accessoryserver.py | 13 +++++++++---- homekit/protocol/__init__.py | 5 +++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/homekit/accessoryserver.py b/homekit/accessoryserver.py index f98e4795..c95de7b1 100644 --- a/homekit/accessoryserver.py +++ b/homekit/accessoryserver.py @@ -237,7 +237,7 @@ def accessory_ltpk(self) -> bytes: def set_accessory_keys(self, accessory_ltpk: bytes, accessory_ltsk: bytes): self.data['accessory_ltpk'] = binascii.hexlify(accessory_ltpk).decode() - self.data['accessory_ltsk'] = binascii.hexlify(accessory_ltsk).decode() + self.data['accessory_ltsk'] = binascii.hexlify(accessory_ltsk).decode()[:64] self._save_data() @property @@ -672,7 +672,7 @@ def _post_pair_verify(self): ios_device_curve25519_pub_key_bytes # 4) sign accessory info for accessory signature - accessory_ltsk = ed25519.SigningKey(self.server.data.accessory_ltsk) + accessory_ltsk = ed25519.SigningKey(self.server.data.accessory_ltsk + self.server.data.accessory_ltpk) accessory_signature = accessory_ltsk.sign(accessory_info) # 5) sub tlv @@ -1059,9 +1059,14 @@ def _post_pair_setup(self): # 1) generate accessoryLTPK if not existing if self.server.data.accessory_ltsk is None or self.server.data.accessory_ltpk is None: accessory_ltsk, accessory_ltpk = ed25519.create_keypair() - self.server.data.set_accessory_keys(accessory_ltpk.to_bytes(), accessory_ltsk.to_bytes()) + self.server.data.set_accessory_keys( + accessory_ltpk.to_bytes(), + accessory_ltsk.to_bytes(), + ) else: - accessory_ltsk = ed25519.SigningKey(self.server.data.accessory_ltsk) + accessory_ltsk = ed25519.SigningKey( + self.server.data.accessory_ltsk + self.server.data.accessory_ltpk + ) accessory_ltpk = ed25519.VerifyingKey(self.server.data.accessory_ltpk) # 2) derive AccessoryX diff --git a/homekit/protocol/__init__.py b/homekit/protocol/__init__.py index fba599e2..f6484188 100644 --- a/homekit/protocol/__init__.py +++ b/homekit/protocol/__init__.py @@ -220,7 +220,7 @@ def perform_pair_setup(connection, pin, ios_pairing_id): '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(), + 'iOSDeviceLTSK': ios_device_ltsk.to_ascii(encoding='hex').decode()[:64], 'iOSDeviceLTPK': hexlify(ios_device_ltpk.to_bytes()).decode() } @@ -308,7 +308,8 @@ def get_session_keys(conn, pairing_data): # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] - ios_device_ltsk = ed25519.SigningKey(bytes.fromhex(ios_device_ltsk_h)) + 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