Beispiel #1
0
    def handle_pair_setup(self, request: http.HttpRequest):
        """Handle incoming /pair-setup request."""
        if request.headers.get("X-Apple-HKP") != "4":
            return http.HttpResponse(
                "RTSP",
                "1.0",
                501,
                "Not implemented",
                {"CSeq": request.headers["CSeq"]},
                b"",
            )

        body = (
            request.body
            if isinstance(request.body, bytes)
            else request.body.encode("utf-8")
        )
        pairing_data = read_tlv(body)
        _LOGGER.debug("Transient pair-setup message received: %s", pairing_data)

        seqno = int.from_bytes(pairing_data[TlvValue.SeqNo], byteorder="little")
        tlv = getattr(self, f"_m{seqno}_setup".format(seqno))(pairing_data)
        return http.HttpResponse(
            "RTSP",
            "1.0",
            200,
            "OK",
            {"CSeq": request.headers["CSeq"]},
            tlv,
        )
Beispiel #2
0
    async def verify_credentials(self) -> bool:
        """Verify if device is allowed to use AirPlau."""
        self.srp.initialize()

        await self.http.post("/pair-pin-start", headers=_AIRPLAY_HEADERS)

        data = {
            hap_tlv8.TlvValue.Method: b"\x00",
            hap_tlv8.TlvValue.SeqNo: b"\x01"
        }
        resp = await self.http.post("/pair-setup",
                                    body=hap_tlv8.write_tlv(data),
                                    headers=_AIRPLAY_HEADERS)
        pairing_data = hap_tlv8.read_tlv(resp.body)

        atv_salt = pairing_data[hap_tlv8.TlvValue.Salt]
        atv_pub_key = pairing_data[hap_tlv8.TlvValue.PublicKey]

        self.srp.step1(_TRANSIENT_PIN)

        pub_key, proof = self.srp.step2(atv_pub_key, atv_salt)
        data = {
            hap_tlv8.TlvValue.SeqNo: b"\x03",
            hap_tlv8.TlvValue.PublicKey: pub_key,
            hap_tlv8.TlvValue.Proof: proof,
        }
        await self.http.post("/pair-setup",
                             body=hap_tlv8.write_tlv(data),
                             headers=_AIRPLAY_HEADERS)

        return True
Beispiel #3
0
    async def finish_pairing(self, username: str, pin_code: int) -> bool:
        """Finish authentication process.

        A username (generated by new_credentials) and the PIN code shown on
        screen must be provided.
        """
        # Step 1
        self.srp.step1(pin_code)

        pub_key, proof = self.srp.step2(self._atv_pub_key, self._atv_salt)
        data = {
            hap_tlv8.TlvValue.SeqNo: b"\x03",
            hap_tlv8.TlvValue.PublicKey: pub_key,
            hap_tlv8.TlvValue.Proof: proof,
        }
        await self.http.post("/pair-setup",
                             body=hap_tlv8.write_tlv(data),
                             headers=_AIRPLAY_HEADERS)

        data = {
            hap_tlv8.TlvValue.SeqNo: b"\x05",
            hap_tlv8.TlvValue.EncryptedData: self.srp.step3(),
        }
        resp = await self.http.post("/pair-setup",
                                    body=hap_tlv8.write_tlv(data),
                                    headers=_AIRPLAY_HEADERS)
        pairing_data = hap_tlv8.read_tlv(resp.body)

        encrypted_data = pairing_data[hap_tlv8.TlvValue.EncryptedData]
        return self.srp.step4(encrypted_data)
Beispiel #4
0
    async def verify_credentials(self) -> bool:
        """Verify if device is allowed to use AirPlau."""

        _, public_key = self.srp.initialize()

        resp = await self._send({
            hap_tlv8.TlvValue.SeqNo: b"\x01",
            hap_tlv8.TlvValue.PublicKey: public_key,
        })

        pairing_data = hap_tlv8.read_tlv(resp.body)
        session_pub_key = pairing_data[hap_tlv8.TlvValue.PublicKey]
        encrypted = pairing_data[hap_tlv8.TlvValue.EncryptedData]
        log_binary(_LOGGER,
                   "Device",
                   Public=self.credentials.ltpk,
                   Encrypted=encrypted)

        encrypted_data = self.srp.verify1(self.credentials, session_pub_key,
                                          encrypted)
        await self._send({
            hap_tlv8.TlvValue.SeqNo: b"\x03",
            hap_tlv8.TlvValue.EncryptedData: encrypted_data,
        })

        # TODO: check status code

        return True
Beispiel #5
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 exceptions.AuthenticationError("data decrypt failed")

        decrypted_tlv = 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)
Beispiel #6
0
    def handle_auth_frame(self, frame_type, data):
        """Handle incoming auth message."""
        _LOGGER.debug("Received auth frame: type=%s, data=%s", frame_type,
                      data)
        pairing_data = read_tlv(data["_pd"])
        seqno = int.from_bytes(pairing_data[TlvValue.SeqNo],
                               byteorder="little")

        suffix = ("verify" if frame_type
                  in [FrameType.PV_Start, FrameType.PV_Next] else "setup")
        getattr(self, f"_m{seqno}_{suffix}")(pairing_data)
Beispiel #7
0
    def _m5_setup(self, pairing_data):
        session_key = hkdf_expand(
            "Pair-Setup-Encrypt-Salt",
            "Pair-Setup-Encrypt-Info",
            binascii.unhexlify(self.session.key),
        )

        acc_device_x = hkdf_expand(
            "Pair-Setup-Accessory-Sign-Salt",
            "Pair-Setup-Accessory-Sign-Info",
            binascii.unhexlify(self.session.key),
        )

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        decrypted_tlv_bytes = chacha.decrypt(
            pairing_data[TlvValue.EncryptedData], nounce="PS-Msg05".encode())

        _LOGGER.debug("MSG5 EncryptedData=%s", read_tlv(decrypted_tlv_bytes))

        other = {
            "altIRK": b"-\x54\xe0\x7a\x88*en\x11\xab\x82v-'%\xc5",
            "accountID": "DC6A7CB6-CA1A-4BF4-880D-A61B717814DB",
            "model": "AppleTV6,2",
            "wifiMAC": b"@\xff\xa1\x8f\xa1\xb9",
            "name": "Living Room",
            "mac": b"@\xc4\xff\x8f\xb1\x99",
        }

        device_info = acc_device_x + self.unique_id + self.keys.auth_pub
        signature = self.keys.sign.sign(device_info)

        tlv = {
            TlvValue.Identifier: self.unique_id,
            TlvValue.PublicKey: self.keys.auth_pub,
            TlvValue.Signature: signature,
            17: opack.pack(other),
        }

        tlv = write_tlv(tlv)

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        encrypted = chacha.encrypt(tlv, nounce="PS-Msg06".encode())

        tlv = write_tlv({
            TlvValue.SeqNo: b"\x06",
            TlvValue.EncryptedData: encrypted
        })

        self.send_to_client(FrameType.PS_Next, {"_pd": tlv})
        self.has_paired()
Beispiel #8
0
def _get_pairing_data(message: Dict[str, object]):
    pairing_data = message.get(PAIRING_DATA_KEY)
    if not pairing_data:
        raise exceptions.AuthenticationError("no pairing data in message")

    if not isinstance(pairing_data, bytes):
        raise exceptions.ProtocolError(
            f"Pairing data has unexpected type: {type(pairing_data)}"
        )

    tlv = read_tlv(pairing_data)
    if TlvValue.Error in tlv:
        raise exceptions.AuthenticationError(stringify(tlv))

    return tlv
Beispiel #9
0
    def handle_crypto_pairing(self, message, inner):
        """Handle incoming crypto pairing message."""
        _LOGGER.debug("Received crypto pairing message")
        pairing_data = read_tlv(inner.pairingData)
        seqno = pairing_data[TlvValue.SeqNo][0]

        # Work-around for now to support "tries" to auth before pairing
        if seqno == 1:
            if TlvValue.PublicKey in pairing_data:
                self.has_paired = True
            elif TlvValue.Method in pairing_data:
                self.has_paired = False

        suffix = "verify" if self.has_paired else "setup"
        getattr(self, f"_m{seqno}_{suffix}")(pairing_data)
Beispiel #10
0
    async def start_pairing(self) -> None:
        """Start the authentication process.

        This method will show the expected PIN on screen.
        """
        self.srp.initialize()

        await self.http.post("/pair-pin-start", headers=_AIRPLAY_HEADERS)

        data = {
            hap_tlv8.TlvValue.Method: b"\x00",
            hap_tlv8.TlvValue.SeqNo: b"\x01"
        }
        resp = await self.http.post("/pair-setup",
                                    body=hap_tlv8.write_tlv(data),
                                    headers=_AIRPLAY_HEADERS)
        pairing_data = hap_tlv8.read_tlv(resp.body)

        self._atv_salt = pairing_data[hap_tlv8.TlvValue.Salt]
        self._atv_pub_key = pairing_data[hap_tlv8.TlvValue.PublicKey]
Beispiel #11
0
    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 = 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 = write_tlv({
            TlvValue.Identifier: credentials.client_id,
            TlvValue.Signature: device_signature,
        })

        return chacha.encrypt(tlv, nounce="PV-Msg03".encode())
Beispiel #12
0
def test_read_key_larger_than_255_bytes():
    assert read_tlv(LARGE_KEY_OUT) == LARGE_KEY_IN
Beispiel #13
0
def test_read_two_keys():
    assert read_tlv(DOUBLE_KEY_OUT) == DOUBLE_KEY_IN
Beispiel #14
0
def test_read_single_key():
    assert read_tlv(SINGLE_KEY_OUT) == SINGLE_KEY_IN
Beispiel #15
0
def _get_pairing_data(resp):
    tlv = read_tlv(resp.inner().pairingData)
    if TlvValue.Error in tlv:
        raise exceptions.AuthenticationError(stringify(tlv))
    return tlv