def get_session_data(ctx, username, password, salt, server_public, private, preset): """Print out client session data.""" session = SRPClientSession(SRPContext(username, password, prime=preset[0], generator=preset[1]), private=private) session.process(server_public, salt, base64=True) click.secho('Client session key: %s' % session.key_b64) click.secho('Client session key proof: %s' % session.key_proof_b64) click.secho('Client session key hash: %s' % session.key_proof_hash_b64)
class SRPAuthHandler: """Handle SRP data and crypto routines for auth and verification.""" def __init__(self): """Initialize a new SRPAuthHandler.""" self.seed = None self.session = None self.client_session_key = None # TODO: can this be removed? self._auth_private = None self._auth_public = None self._verify_private = None self._verify_public = None def initialize(self, seed=None): """Initialize handler operation. This method will generate new encryption keys and must be called prior to doing authentication or verification. """ self.seed = seed or os.urandom(32) # Generate new seed if not provided signing_key = SigningKey(self.seed) verifying_key = signing_key.get_verifying_key() self._auth_private = signing_key.to_seed() self._auth_public = verifying_key.to_bytes() log_binary(_LOGGER, 'Authentication keys', Private=self._auth_private, Public=self._auth_public) def verify1(self): """First device verification step.""" self._check_initialized() self._verify_private = curve25519.Private(secret=self.seed) self._verify_public = self._verify_private.get_public() log_binary(_LOGGER, 'Verification keys', Private=self._verify_private.serialize(), Public=self._verify_public.serialize()) verify_public = self._verify_public.serialize() return b'\x01\x00\x00\x00' + verify_public + self._auth_public def verify2(self, atv_public_key, data): """Last device verification step.""" self._check_initialized() log_binary(_LOGGER, 'Verify', PublicSecret=atv_public_key, Data=data) # Generate a shared secret key public = curve25519.Public(atv_public_key) shared = self._verify_private.get_shared_key( public, hashfunc=lambda x: x) # No additional hashing used log_binary(_LOGGER, 'Shared secret', Secret=shared) # Derive new AES key and IV from shared key aes_key = hash_sha512('Pair-Verify-AES-Key', shared)[0:16] aes_iv = hash_sha512('Pair-Verify-AES-IV', shared)[0:16] log_binary(_LOGGER, 'Pair-Verify-AES', Key=aes_key, IV=aes_iv) # Sign public keys and encrypt with AES signer = SigningKey(self._auth_private) signed = signer.sign(self._verify_public.serialize() + atv_public_key) signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed) log_binary(_LOGGER, 'Signature', Signature=signature) # Signature is prepended with 0x00000000 (alignment?) return b'\x00\x00\x00\x00' + signature def step1(self, username, password): """First authentication step.""" self._check_initialized() context = AtvSRPContext( str(username), str(password), prime=constants.PRIME_2048, generator=constants.PRIME_2048_GEN) self.session = SRPClientSession( context, binascii.hexlify(self._auth_private).decode()) def step2(self, pub_key, salt): """Second authentication step.""" self._check_initialized() pk_str = binascii.hexlify(pub_key).decode() salt = binascii.hexlify(salt).decode() self.client_session_key, _, _ = self.session.process(pk_str, salt) _LOGGER.debug('Client session key: %s', self.client_session_key) # Generate client public and session key proof. client_public = self.session.public client_session_key_proof = self.session.key_proof _LOGGER.debug('Client public: %s, proof: %s', client_public, client_session_key_proof) if not self.session.verify_proof(self.session.key_proof_hash): raise AuthenticationError('proofs do not match (mitm?)') return client_public, client_session_key_proof def step3(self): """Last authentication step.""" self._check_initialized() # TODO: verify: self.client_session_key same as self.session.key_b64()? session_key = binascii.unhexlify(self.client_session_key) aes_key = hash_sha512('Pair-Setup-AES-Key', session_key)[0:16] tmp = bytearray(hash_sha512('Pair-Setup-AES-IV', session_key)[0:16]) tmp[-1] = tmp[-1] + 1 # Last byte must be increased by 1 aes_iv = bytes(tmp) log_binary(_LOGGER, 'Pair-Setup-AES', Key=aes_key, IV=aes_iv) epk, tag = aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public) log_binary(_LOGGER, 'Pair-Setup EPK+Tag', EPK=epk, Tag=tag) return epk, tag def _check_initialized(self): if not self.seed: raise NoCredentialsError()
client_public_A = client_session.public # the client has received B # the server has received A ##### # HERE WE ARE IN THE SERVER server_session.process(client_public_A, salt) # generating the common secret server_key_proof = server_session.key_proof server_key_proof_hash = server_session.key_proof_hash print(server_key_proof) print(server_key_proof_hash) ##### # HERE WE ARE IN THE CLIENT client_session.process(server_public_B, salt) #generating the common secret client_key_proof = client_session.key_proof client_key_proof_hash = client_session.key_proof_hash print(client_key_proof) print(client_key_proof_hash) ##### # HERE WE ARE IN THE SERVER assert server_session.verify_proof(client_key_proof) ##### # HERE WE ARE IN THE CLIENT assert client_session.verify_proof(server_key_proof_hash)
print("K:", ensure_hash_size(server_session.key)) if args.command == "client": client_session = SRPClientSession(context, private=args.private) print("a:", client_session.private) # Client => Server: username, A print("A:", client_session.public) # Server => Client: s, B sys.stdout.write("s: ") sys.stdout.flush() s = input() sys.stdout.write("B: ") sys.stdout.flush() B = input() client_session.process(B, s) # Client => Server: M print("M:", ensure_hash_size(client_session.key_proof)) # Server => Client: HAMK sys.stdout.write("HAMK: ") sys.stdout.flush() HAMK = input() assert client_session.verify_proof(HAMK) print("OK") # Always keep the key secret! It is printed to validate the implementation. print("K:", ensure_hash_size(client_session.key))
# domain parameters received_data = server.recv(DATA_SIZE).decode() server_data = json.loads(received_data) print(server_data['prime']) # prime print(server_data['generator']) # generator print(server_data['B']) # server public key B print(server_data['server_salt']) # salt # receive server verifier and salt and all the public parameters client_session = SRPClientSession( SRPContext(USERNAME, PASSWORD, prime=server_data['prime'], generator=server_data['generator'])) client_session.process(server_data['B'], server_data['server_salt']) client_public_A = client_session.public print(client_public_A) # client public key A client_session_key_proof = client_session.key_proof.hex() print(client_session_key_proof) client_parameters = json.dumps({ 'A': client_public_A, 'client_session_key_proof': client_session_key_proof }) print(client_parameters) # send to server
def pairing(socket, config): msg = ProtocolMessage_pb2.ProtocolMessage() msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0 msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv_build( { kTLVType_Method: b'\x00', kTLVType_State: b'\x01' }) msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].state = 2 send(msg, socket) msg = receive(socket) parsed = tlv_parse(msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData) appletv_public = parsed[kTLVType_PublicKey] salt = parsed[kTLVType_Salt] code = input("Enter code displayed by Apple TV: ") session = SRPClientSession( SRPContext("Pair-Setup", code, PRIME_3072, PRIME_3072_GEN, hashlib.sha512, bits_random=256, bits_salt=128)) session.process(binascii.hexlify(appletv_public), binascii.hexlify(salt)) our_public = binascii.unhexlify(session.public) key_proof = binascii.unhexlify(session.key_proof) msg = ProtocolMessage_pb2.ProtocolMessage() msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0 tlv = tlv_build({ kTLVType_State: b'\x03', kTLVType_PublicKey: our_public, kTLVType_Proof: key_proof }) msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv send(msg, socket) msg = receive(socket) parsed = tlv_parse(msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData) proof = parsed[kTLVType_Proof] if not session.verify_proof(binascii.hexlify(proof)): print("proof does not match!") exit(1) ltsk, ltpk = ed25519.create_keypair() hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Controller-Sign-Salt", b"Pair-Setup-Controller-Sign-Info", default_backend()) x = hkdf.derive(binascii.unhexlify(session.key)) device_id = bytes(config['device_id'], 'utf-8') info = x + device_id + ltpk.to_bytes() subtlv = tlv_build({ kTLVType_Identifier: device_id, kTLVType_PublicKey: ltpk.to_bytes(), kTLVType_Signature: ltsk.sign(info) }) hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Encrypt-Salt", b"Pair-Setup-Encrypt-Info", default_backend()) x = hkdf.derive(binascii.unhexlify(session.key)) cha_cha_poly = ChaCha20Poly1305(x) encrypted = cha_cha_poly.encrypt(b"\0\0\0\0PS-Msg05", subtlv, None) msg = ProtocolMessage_pb2.ProtocolMessage() msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0 msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv_build( { kTLVType_State: b'\x05', kTLVType_EncryptedData: encrypted }) send(msg, socket) msg = receive(socket) parsed = tlv_parse(msg.Extensions[ CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData) encrypted = parsed[kTLVType_EncryptedData] subtlv = tlv_parse( cha_cha_poly.decrypt(b"\0\0\0\0PS-Msg06", encrypted, None)) hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Accessory-Sign-Salt", b"Pair-Setup-Accessory-Sign-Info", default_backend()) x = hkdf.derive(binascii.unhexlify(session.key)) info = x + subtlv[kTLVType_Identifier] + subtlv[kTLVType_PublicKey] ed25519.VerifyingKey(subtlv[kTLVType_PublicKey]).verify( subtlv[kTLVType_Signature], info) pickle.dump( { "seed": ltsk.to_seed(), "peer_id": subtlv[kTLVType_Identifier], "peer_public_key": subtlv[kTLVType_PublicKey] }, open("data/pairing.state", "wb"))
class SRPAuthHandler: """Handle SRP crypto routines for auth and key derivation.""" def __init__(self): """Initialize a new SRPAuthHandler.""" self.pairing_id = str(uuid.uuid4()).encode() self._signing_key = None self._auth_private = None self._auth_public = None self._verify_private = None self._verify_public = None self._public_bytes = None self._session = None self._shared = None self._session_key = None def initialize(self): """Initialize operation by generating new keys.""" self._signing_key = Ed25519PrivateKey.from_private_bytes(os.urandom(32)) self._auth_private = self._signing_key.private_bytes( encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption(), ) self._auth_public = self._signing_key.public_key().public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw ) self._verify_private = X25519PrivateKey.from_private_bytes(os.urandom(32)) self._verify_public = self._verify_private.public_key() self._public_bytes = self._verify_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw ) return self._auth_public, self._public_bytes def verify1(self, credentials, session_pub_key, encrypted): """First verification step.""" self._shared = self._verify_private.exchange( X25519PublicKey.from_public_bytes(session_pub_key) ) session_key = hkdf_expand( "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", self._shared ) chacha = chacha20.Chacha20Cipher(session_key, session_key) decrypted_tlv = hap_tlv8.read_tlv( chacha.decrypt(encrypted, nounce="PV-Msg02".encode()) ) identifier = decrypted_tlv[TlvValue.Identifier] signature = decrypted_tlv[TlvValue.Signature] if identifier != credentials.atv_id: raise exceptions.AuthenticationError("incorrect device response") info = session_pub_key + bytes(identifier) + self._public_bytes ltpk = Ed25519PublicKey.from_public_bytes(bytes(credentials.ltpk)) try: ltpk.verify(bytes(signature), bytes(info)) except InvalidSignature as ex: raise exceptions.AuthenticationError("signature error") from ex device_info = self._public_bytes + credentials.client_id + session_pub_key device_signature = Ed25519PrivateKey.from_private_bytes(credentials.ltsk).sign( device_info ) tlv = hap_tlv8.write_tlv( { TlvValue.Identifier: credentials.client_id, TlvValue.Signature: device_signature, } ) return chacha.encrypt(tlv, nounce="PV-Msg03".encode()) def verify2(self, salt, output_info, input_info): """Last verification step. The derived keys (output, input) are returned here. """ output_key = hkdf_expand(salt, output_info, self._shared) input_key = hkdf_expand(salt, input_info, self._shared) log_binary(_LOGGER, "Keys", Output=output_key, Input=input_key) return output_key, input_key def step1(self, pin): """First pairing step.""" context = SRPContext( "Pair-Setup", str(pin), prime=constants.PRIME_3072, generator=constants.PRIME_3072_GEN, hash_func=hashlib.sha512, ) self._session = SRPClientSession( context, binascii.hexlify(self._auth_private).decode() ) def step2(self, atv_pub_key, atv_salt): """Second pairing step.""" pk_str = binascii.hexlify(atv_pub_key).decode() salt = binascii.hexlify(atv_salt).decode() self._session.process(pk_str, salt) if not self._session.verify_proof(self._session.key_proof_hash): raise exceptions.AuthenticationError("proofs do not match") pub_key = binascii.unhexlify(self._session.public) proof = binascii.unhexlify(self._session.key_proof) log_binary(_LOGGER, "Client", Public=pub_key, Proof=proof) return pub_key, proof def step3(self, additional_data=None): """Third pairing step.""" ios_device_x = hkdf_expand( "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", binascii.unhexlify(self._session.key), ) self._session_key = hkdf_expand( "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", binascii.unhexlify(self._session.key), ) device_info = ios_device_x + self.pairing_id + self._auth_public device_signature = self._signing_key.sign(device_info) tlv = { TlvValue.Identifier: self.pairing_id, TlvValue.PublicKey: self._auth_public, TlvValue.Signature: device_signature, } if additional_data: tlv.update(additional_data) chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key) encrypted_data = chacha.encrypt( hap_tlv8.write_tlv(tlv), nounce="PS-Msg05".encode() ) log_binary(_LOGGER, "Data", Encrypted=encrypted_data) return encrypted_data def step4(self, encrypted_data): """Last pairing step.""" chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key) decrypted_tlv_bytes = chacha.decrypt(encrypted_data, nounce="PS-Msg06".encode()) if not decrypted_tlv_bytes: raise exceptions.AuthenticationError("data decrypt failed") decrypted_tlv = hap_tlv8.read_tlv(decrypted_tlv_bytes) _LOGGER.debug("PS-Msg06: %s", decrypted_tlv) atv_identifier = decrypted_tlv[TlvValue.Identifier] atv_signature = decrypted_tlv[TlvValue.Signature] atv_pub_key = decrypted_tlv[TlvValue.PublicKey] log_binary( _LOGGER, "Device", Identifier=atv_identifier, Signature=atv_signature, Public=atv_pub_key, ) # TODO: verify signature here return HapCredentials( atv_pub_key, self._auth_private, atv_identifier, self.pairing_id )
server = remote(HOST, PORT) auth_data = json.dumps({'username': username, 'prime': prime, 'generator': gen, 'password_verifier': password_verifier, 'salt': client_salt}) print(auth_data) server.send(auth_data.encode()) #receive server verifier and salt received_data = server.recv(DATA_SIZE).decode() server_data = json.loads(received_data) print(server_data) print("server public= "+str(server_data['server_verifier'])) # print("server salt = "+str(server_data['salt'])) #compute client parameters client_session = SRPClientSession(context) client_session.process(server_data['server_verifier'], client_salt) # Generate client public and session key proof. client_public = client_session.public print("client public = "+str(client_public)) client_session_key_proof = client_session.key_proof.hex() print("session key proof = " + str(client_session_key_proof)) print("session key proofB= " + str(client_session.key_proof)) client_parameters = json.dumps({'client_parameter': client_public, 'session_key_proof': client_session_key_proof}) server.send(client_parameters.encode()) print(client_session.key) # Generate session key proof hash # server_session_key_proof_hash = client_session.key_proof_hash
class SRPAuthHandler: """Handle SRP data and crypto routines for auth and verification.""" def __init__(self): """Initialize a new SRPAuthHandler.""" self.seed = None self.session = None self._public_bytes = None self._auth_private = None self._auth_public = None self._verify_private = None self._verify_public = None def initialize(self, seed=None): """Initialize handler operation. This method will generate new encryption keys and must be called prior to doing authentication or verification. """ self.seed = seed or os.urandom(32) # Generate new seed if not provided signing_key = Ed25519PrivateKey.from_private_bytes(self.seed) verifying_key = signing_key.public_key() self._auth_private = signing_key.private_bytes( encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption(), ) self._auth_public = verifying_key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) log_binary( _LOGGER, "Authentication keys", Private=self._auth_private, Public=self._auth_public, ) def verify1(self): """First device verification step.""" self._check_initialized() self._verify_private = X25519PrivateKey.from_private_bytes(self.seed) self._verify_public = self._verify_private.public_key() verify_private_bytes = self._verify_private.private_bytes( encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption(), ) self._public_bytes = self._verify_public.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) log_binary( _LOGGER, "Verification keys", Private=verify_private_bytes, Public=self._public_bytes, ) return b"\x01\x00\x00\x00" + self._public_bytes + self._auth_public def verify2(self, atv_public_key, data): """Last device verification step.""" self._check_initialized() log_binary(_LOGGER, "Verify", PublicSecret=atv_public_key, Data=data) # Generate a shared secret key shared = self._verify_private.exchange( X25519PublicKey.from_public_bytes(atv_public_key)) log_binary(_LOGGER, "Shared secret", Secret=shared) # Derive new AES key and IV from shared key aes_key = hash_sha512("Pair-Verify-AES-Key", shared)[0:16] aes_iv = hash_sha512("Pair-Verify-AES-IV", shared)[0:16] log_binary(_LOGGER, "Pair-Verify-AES", Key=aes_key, IV=aes_iv) # Sign public keys and encrypt with AES signer = Ed25519PrivateKey.from_private_bytes(self._auth_private) signed = signer.sign(self._public_bytes + atv_public_key) signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed) log_binary(_LOGGER, "Signature", Signature=signature) # Signature is prepended with 0x00000000 (alignment?) return b"\x00\x00\x00\x00" + signature def step1(self, username, password): """First authentication step.""" self._check_initialized() context = AtvSRPContext( str(username), str(password), prime=constants.PRIME_2048, generator=constants.PRIME_2048_GEN, ) self.session = SRPClientSession( context, binascii.hexlify(self._auth_private).decode()) def step2(self, pub_key, salt): """Second authentication step.""" self._check_initialized() pk_str = binascii.hexlify(pub_key).decode() salt = binascii.hexlify(salt).decode() client_session_key, _, _ = self.session.process(pk_str, salt) _LOGGER.debug("Client session key: %s", client_session_key) # Generate client public and session key proof. client_public = self.session.public client_session_key_proof = self.session.key_proof _LOGGER.debug("Client public: %s, proof: %s", client_public, client_session_key_proof) if not self.session.verify_proof(self.session.key_proof_hash): raise AuthenticationError("proofs do not match (mitm?)") return client_public, client_session_key_proof def step3(self): """Last authentication step.""" self._check_initialized() session_key = binascii.unhexlify(self.session.key) aes_key = hash_sha512("Pair-Setup-AES-Key", session_key)[0:16] tmp = bytearray(hash_sha512("Pair-Setup-AES-IV", session_key)[0:16]) _LOGGER.debug("Increase last byte from %d to %s", tmp[-1], tmp[-1] + 1) tmp[-1] = tmp[-1] + 1 # Last byte must be increased by 1 aes_iv = bytes(tmp) log_binary(_LOGGER, "Pair-Setup-AES", Key=aes_key, IV=aes_iv) epk, tag = aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public) log_binary(_LOGGER, "Pair-Setup EPK+Tag", EPK=epk, Tag=tag) return epk, tag def _check_initialized(self): if not self.seed: raise NoCredentialsError("no credentials available")
class SRPAuthenticationHandler(object): """ Handle SRP (Secure Remote Password) data and crypto routines for auth and verification. """ def __init__(self): self.seed = None self.session = None self._auth_private = None self._auth_public = None self._verify_private = None self._verify_public = None self.client_session_key = None def initialize(self, seed): """ This method will generate new encryption keys and must be called prior to doing authentication or verification. :param seed: seed used for generation or key """ self.seed = binascii.unhexlify(seed) signing_key = SigningKey(self.seed) verifying_key = signing_key.get_verifying_key() self._auth_private = signing_key.to_seed() self._auth_public = verifying_key.to_bytes() # region pairing @requires_credentials def step1(self, username, password): """ First authentication step. :param username: username :param password: password """ context = AtvSRPContext(str(username), str(password), prime=constants.PRIME_2048, generator=constants.PRIME_2048_GEN) self.session = SRPClientSession( context, binascii.hexlify(self._auth_private).decode()) @requires_credentials def step2(self, pub_key, salt): """ Second authentication step. (Run SRP) :param pub_key: Apple TVs public key :param salt: Apple TVs salt """ pk_str = binascii.hexlify(pub_key).decode() salt = binascii.hexlify(salt).decode() self.client_session_key, _, _ = self.session.process(pk_str, salt) # Generate client public and session key proof. client_public = self.session.public client_session_key_proof = self.session.key_proof if not self.session.verify_proof(self.session.key_proof_hash): raise DeviceAuthenticationError('proofs do not match (mitm?)') return binascii.unhexlify(client_public), binascii.unhexlify( client_session_key_proof) @requires_credentials def step3(self): """ Last authentication step. (Run AES) :return epk, tag """ session_key = binascii.unhexlify(self.client_session_key) aes_key = hash_sha512('Pair-Setup-AES-Key', session_key)[0:16] tmp = bytearray(hash_sha512('Pair-Setup-AES-IV', session_key)[0:16]) tmp[-1] += +1 # Last byte must be increased by 1 aes_iv = bytes(tmp) return aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public) # endregion # region verification steps @requires_credentials def verify1(self): """ First device verification step. """ self._verify_private = curve25519.Private(secret=self.seed) self._verify_public = self._verify_private.get_public() verify_public = self._verify_public.serialize() return b'\x01\x00\x00\x00' + verify_public + self._auth_public @requires_credentials def verify2(self, atv_public_key, data): """ Last device verification step. """ # Generate a shared secret key public = curve25519.Public(atv_public_key) shared = self._verify_private.get_shared_key( public, hashfunc=lambda x: x) # No additional hashing used # Derive new AES key and IV from shared key aes_key = hash_sha512('Pair-Verify-AES-Key', shared)[0:16] aes_iv = hash_sha512('Pair-Verify-AES-IV', shared)[0:16] # Sign public keys and encrypt with AES signer = SigningKey(self._auth_private) signed = signer.sign(self._verify_public.serialize() + atv_public_key) signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed) # Signature is prepended with 0x00000000 (alignment?) return b'\x00\x00\x00\x00' + signature
class SRPAuthHandler: """Handle SRP crypto routines for auth and key derivation.""" def __init__(self): """Initialize a new SRPAuthHandler.""" self.pairing_id = str(uuid.uuid4()).encode() self._signing_key = None self._auth_private = None self._auth_public = None self._verify_private = None self._verify_public = None self._session = None self._shared = None self._session_key = None self._client_session_key = None # TODO: can remove? def initialize(self): """Initialize operation by generating new keys.""" self._signing_key = SigningKey(os.urandom(32)) self._auth_private = self._signing_key.to_seed() self._auth_public = self._signing_key.get_verifying_key().to_bytes() self._verify_private = curve25519.Private(secret=os.urandom(32)) self._verify_public = self._verify_private.get_public() return self._auth_public, self._verify_public.serialize() def verify1(self, credentials, session_pub_key, encrypted): """First verification step.""" # No additional hashing used self._shared = self._verify_private.get_shared_key( curve25519.Public(session_pub_key), hashfunc=lambda x: x) session_key = hkdf_expand('Pair-Verify-Encrypt-Salt', 'Pair-Verify-Encrypt-Info', self._shared) chacha = chacha20.Chacha20Cipher(session_key, session_key) decrypted_tlv = tlv8.read_tlv( chacha.decrypt(encrypted, nounce='PV-Msg02'.encode())) identifier = decrypted_tlv[tlv8.TLV_IDENTIFIER] signature = decrypted_tlv[tlv8.TLV_SIGNATURE] if identifier != credentials.atv_id: raise Exception('incorrect device response') # TODO: new exception info = session_pub_key + \ bytes(identifier) + self._verify_public.serialize() ltpk = VerifyingKey(bytes(credentials.ltpk)) ltpk.verify(bytes(signature), bytes(info)) # throws if no match device_info = self._verify_public.serialize() + \ credentials.client_id + session_pub_key device_signature = SigningKey(credentials.ltsk).sign(device_info) tlv = tlv8.write_tlv({tlv8.TLV_IDENTIFIER: credentials.client_id, tlv8.TLV_SIGNATURE: device_signature}) return chacha.encrypt(tlv, nounce='PV-Msg03'.encode()) def verify2(self): """Last verification step. The derived keys (output, input) are returned here. """ output_key = hkdf_expand('MediaRemote-Salt', 'MediaRemote-Write-Encryption-Key', self._shared) input_key = hkdf_expand('MediaRemote-Salt', 'MediaRemote-Read-Encryption-Key', self._shared) log_binary(_LOGGER, 'Keys', Output=output_key, Input=input_key) return output_key, input_key def step1(self, pin): """First pairing step.""" context = SRPContext( 'Pair-Setup', str(pin), prime=constants.PRIME_3072, generator=constants.PRIME_3072_GEN, hash_func=hashlib.sha512) self._session = SRPClientSession( context, binascii.hexlify(self._auth_private).decode()) def step2(self, atv_pub_key, atv_salt): """Second pairing step.""" pk_str = binascii.hexlify(atv_pub_key).decode() salt = binascii.hexlify(atv_salt).decode() self._client_session_key, _, _ = self._session.process(pk_str, salt) if not self._session.verify_proof(self._session.key_proof_hash): raise exceptions.AuthenticationError('proofs do not match (mitm?)') pub_key = binascii.unhexlify(self._session.public) proof = binascii.unhexlify(self._session.key_proof) log_binary(_LOGGER, 'Client', Public=pub_key, Proof=proof) return pub_key, proof def step3(self): """Third pairing step.""" ios_device_x = hkdf_expand( 'Pair-Setup-Controller-Sign-Salt', 'Pair-Setup-Controller-Sign-Info', binascii.unhexlify(self._client_session_key)) self._session_key = hkdf_expand( 'Pair-Setup-Encrypt-Salt', 'Pair-Setup-Encrypt-Info', binascii.unhexlify(self._client_session_key)) device_info = ios_device_x + self.pairing_id + self._auth_public device_signature = self._signing_key.sign(device_info) tlv = tlv8.write_tlv({tlv8.TLV_IDENTIFIER: self.pairing_id, tlv8.TLV_PUBLIC_KEY: self._auth_public, tlv8.TLV_SIGNATURE: device_signature}) chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key) encrypted_data = chacha.encrypt(tlv, nounce='PS-Msg05'.encode()) log_binary(_LOGGER, 'Data', Encrypted=encrypted_data) return encrypted_data def step4(self, encrypted_data): """Last pairing step.""" chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key) decrypted_tlv_bytes = chacha.decrypt( encrypted_data, nounce='PS-Msg06'.encode()) if not decrypted_tlv_bytes: raise Exception('data decrypt failed') # TODO: new exception decrypted_tlv = tlv8.read_tlv(decrypted_tlv_bytes) _LOGGER.debug('PS-Msg06: %s', decrypted_tlv) atv_identifier = decrypted_tlv[tlv8.TLV_IDENTIFIER] atv_signature = decrypted_tlv[tlv8.TLV_SIGNATURE] atv_pub_key = decrypted_tlv[tlv8.TLV_PUBLIC_KEY] log_binary(_LOGGER, 'Device', Identifier=atv_identifier, Signature=atv_signature, Public=atv_pub_key) # TODO: verify signature here return Credentials(atv_pub_key, self._signing_key.to_seed(), atv_identifier, self.pairing_id)