예제 #1
0
    async def finish_pairing(self, pin):
        """Finish pairing process."""
        self.srp.step1(pin)

        pub_key, proof = self.srp.step2(self._atv_pub_key, self._atv_salt)

        msg = messages.crypto_pairing({
            tlv8.TLV_SEQ_NO: b'\x03',
            tlv8.TLV_PUBLIC_KEY: pub_key,
            tlv8.TLV_PROOF: proof
        })
        resp = await self.protocol.send_and_receive(msg,
                                                    generate_identifier=False)

        pairing_data = _get_pairing_data(resp)
        atv_proof = pairing_data[tlv8.TLV_PROOF]
        log_binary(_LOGGER, 'Device', Proof=atv_proof)

        encrypted_data = self.srp.step3()
        msg = messages.crypto_pairing({
            tlv8.TLV_SEQ_NO: b'\x05',
            tlv8.TLV_ENCRYPTED_DATA: encrypted_data
        })
        resp = await self.protocol.send_and_receive(msg,
                                                    generate_identifier=False)

        pairing_data = _get_pairing_data(resp)
        encrypted_data = pairing_data[tlv8.TLV_ENCRYPTED_DATA]

        return self.srp.step4(encrypted_data)
예제 #2
0
    def data_received(self, data):
        """Message received from iOS app/client."""
        self.buffer += data

        while self.buffer:
            length, raw = variant.read_variant(self.buffer)
            if len(raw) < length:
                break

            data = raw[:length]
            self.buffer = raw[length:]
            if self.chacha:
                log_binary(_LOGGER, 'ENC Phone->ATV', Encrypted=data)
                data = self.chacha.decrypt(data)

            parsed = protobuf.ProtocolMessage()
            parsed.ParseFromString(data)
            _LOGGER.info('(DEC Phone->ATV): %s', parsed)

            try:

                def unhandled_message(_, raw):
                    self.connection.send_raw(raw)

                self.mapping.get(parsed.type, unhandled_message)(parsed, data)
            except Exception:  # pylint: disable=broad-except
                _LOGGER.exception('Error while dispatching message')
예제 #3
0
    async def verify_credentials(self):
        """Verify credentials with device."""
        _, public_key = self.srp.initialize()

        msg = messages.crypto_pairing({
            tlv8.TLV_SEQ_NO: b'\x01',
            tlv8.TLV_PUBLIC_KEY: public_key
        })
        resp = await self.protocol.send_and_receive(msg,
                                                    generate_identifier=False)

        resp = _get_pairing_data(resp)
        session_pub_key = resp[tlv8.TLV_PUBLIC_KEY]
        encrypted = resp[tlv8.TLV_ENCRYPTED_DATA]
        log_binary(_LOGGER,
                   'Device',
                   Public=self.credentials.ltpk,
                   Encrypted=encrypted)

        encrypted_data = self.srp.verify1(self.credentials, session_pub_key,
                                          encrypted)
        msg = messages.crypto_pairing({
            tlv8.TLV_SEQ_NO: b'\x03',
            tlv8.TLV_ENCRYPTED_DATA: encrypted_data
        })
        resp = await self.protocol.send_and_receive(msg,
                                                    generate_identifier=False)

        # TODO: check status code

        self._output_key, self._input_key = self.srp.verify2()
예제 #4
0
    def send_raw(self, data):
        """Send message to device."""
        log_binary(_LOGGER, '>> Send raw', Data=data)
        if self._chacha:
            data = self._chacha.encrypt(data)
            log_binary(_LOGGER, '>> Send raw', Encrypted=data)

        data = write_variant(len(data)) + data
        self._transport.write(data)
예제 #5
0
    def _send(self, message):
        data = message.SerializeToString()
        _LOGGER.info('<<(DECRYPTED): %s', message)
        if self.chacha:
            data = self.chacha.encrypt(data)
            log_binary(_LOGGER, '<<(ENCRYPTED)', Message=message)

        length = variant.write_variant(len(data))
        self.transport.write(length + data)
예제 #6
0
    def _handle_message(self, data):
        if self._chacha:
            data = self._chacha.decrypt(data)
            log_binary(_LOGGER, '<< Receive', Decrypted=data)

        parsed = protobuf.ProtocolMessage()
        parsed.ParseFromString(data)
        _LOGGER.debug('<< Receive: Protobuf=%s', parsed)
        if self.listener:
            self.listener.message_received(parsed)
예제 #7
0
파일: srp.py 프로젝트: phanimohan/pyatv
 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
예제 #8
0
    def send(self, message):
        """Send message to device."""
        serialized = message.SerializeToString()

        log_binary(_LOGGER, '>> Send', Data=serialized)
        if self._chacha:
            serialized = self._chacha.encrypt(serialized)
            log_binary(_LOGGER, '>> Send', Encrypted=serialized)

        data = write_variant(len(serialized)) + serialized
        self._transport.write(data)
        _LOGGER.debug('>> Send: Protobuf=%s', message)
예제 #9
0
    def _seqno_1(self, pairing_data):
        if self.has_paired:
            server_pub_key = self._verify_public.serialize()
            client_pub_key = pairing_data[tlv8.TLV_PUBLIC_KEY]

            self._shared = self._verify_private.get_shared_key(
                curve25519.Public(client_pub_key), hashfunc=lambda x: x)

            session_key = hkdf_expand('Pair-Verify-Encrypt-Salt',
                                      'Pair-Verify-Encrypt-Info', self._shared)

            info = server_pub_key + self.atv_device_id + client_pub_key
            signature = SigningKey(self._signing_key.to_seed()).sign(info)

            tlv = tlv8.write_tlv({
                tlv8.TLV_IDENTIFIER: self.atv_device_id,
                tlv8.TLV_SIGNATURE: signature
            })

            chacha = chacha20.Chacha20Cipher(session_key, session_key)
            encrypted = chacha.encrypt(tlv, nounce='PV-Msg02'.encode())

            msg = messages.crypto_pairing({
                tlv8.TLV_SEQ_NO: b'\x02',
                tlv8.TLV_PUBLIC_KEY: server_pub_key,
                tlv8.TLV_ENCRYPTED_DATA: encrypted
            })

            self.output_key = hkdf_expand('MediaRemote-Salt',
                                          'MediaRemote-Write-Encryption-Key',
                                          self._shared)

            self.input_key = hkdf_expand('MediaRemote-Salt',
                                         'MediaRemote-Read-Encryption-Key',
                                         self._shared)

            log_binary(_LOGGER,
                       'Keys',
                       Output=self.output_key,
                       Input=self.input_key)

        else:
            msg = messages.crypto_pairing({
                tlv8.TLV_SALT:
                binascii.unhexlify(self.salt),
                tlv8.TLV_PUBLIC_KEY:
                binascii.unhexlify(self._session.public),
                tlv8.TLV_SEQ_NO:
                b'\x02'
            })

        self._send(msg)
예제 #10
0
    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
예제 #11
0
    def _send_raw(self, raw):
        parsed = protobuf.ProtocolMessage()
        parsed.ParseFromString(raw)

        log_binary(_LOGGER, 'ATV->APP', Raw=raw)
        _LOGGER.info('ATV->APP Parsed: %s', parsed)
        if self.chacha:
            raw = self.chacha.encrypt(raw)
            log_binary(_LOGGER, 'ATV->APP', Encrypted=raw)

        length = variant.write_variant(len(raw))
        try:
            self.transport.write(length + raw)
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception('Failed to send to app')
예제 #12
0
    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
예제 #13
0
파일: srp.py 프로젝트: phanimohan/pyatv
    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)
예제 #14
0
파일: srp.py 프로젝트: phanimohan/pyatv
    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
예제 #15
0
    def data_received(self, data):
        """Message was received from device."""
        # A message might be split over several reads, so we store a buffer and
        # try to decode messages from that buffer
        self._buffer += data
        log_binary(_LOGGER, '<< Receive', Data=data)

        while self._buffer:
            # The variant tells us how much data must follow
            length, raw = read_variant(self._buffer)
            if len(raw) < length:
                _LOGGER.debug('Require %d bytes but only %d in buffer', length,
                              len(raw))
                break

            data = raw[:length]  # Incoming message (might be encrypted)
            self._buffer = raw[length:]  # Buffer, might contain more messages

            try:
                self._handle_message(data)
            except Exception:  # pylint: disable=broad-except
                _LOGGER.error('Failed to handle message')
예제 #16
0
    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
예제 #17
0
    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)
예제 #18
0
파일: srp.py 프로젝트: phanimohan/pyatv
    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
예제 #19
0
 def test_log_single_arg_if_enabled(self):
     log.log_binary(self.mock_logger, 'abc', test=b'\x01\x02')
     self.assertEqual(self._debug_string(), 'abc (test=0102)')
예제 #20
0
 def test_no_log_if_not_debug(self):
     self.mock_logger.isEnabledFor.return_value = False
     log.log_binary(self.mock_logger, 'test')
     self.mock_logger.isEnabledFor.assert_called_with(logging.DEBUG)
예제 #21
0
 def test_log_no_args_if_enabled(self):
     log.log_binary(self.mock_logger, 'testing')
     self.assertEqual(self._debug_string(), 'testing ()')
예제 #22
0
 def test_log_multiple_args_if_enabled(self):
     log.log_binary(self.mock_logger, 'k', test=b'\x01\x02', dummy=b'\xfe')
     self.assertEqual(self._debug_string(), 'k (dummy=fe, test=0102)')