コード例 #1
0
 def test_long_values_2(self):
     val = [
         [TLV.kTLVType_State, TLV.M3],
         [TLV.kTLVType_Certificate, (150 * "a" + 150 * "b").encode()],
         [TLV.kTLVType_Identifier, "hello".encode()],
     ]
     res = TLV.decode_bytearray(TLV.encode_list(val))
     self.assertEqual(val, res)
コード例 #2
0
 def test_separator_list(self):
     val = [
         [TLV.kTLVType_State, TLV.M3],
         TLV.kTLVType_Separator_Pair,
         [TLV.kTLVType_State, TLV.M4],
     ]
     res = TLV.decode_bytearray(TLV.encode_list(val))
     self.assertEqual(val, res)
コード例 #3
0
 def test_to_string_for_dict(self):
     example = {1: "hello"}
     res = TLV.to_string(example)
     self.assertEqual(res, "{\n  1: (5 bytes/<class 'str'>) hello\n}\n")
     example = {1: "hello", 2: "world"}
     res = TLV.to_string(example)
     self.assertEqual(
         res,
         "{\n  1: (5 bytes/<class 'str'>) hello\n  2: (5 bytes/<class 'str'>) world\n}\n",
     )
コード例 #4
0
ファイル: connection.py プロジェクト: Jc2k/aiohomekit
 async def post_tlv(self, target, body, expected=None):
     try:
         response = await self.post(
             target,
             TLV.encode_list(body),
             content_type=HttpContentTypes.TLV,
         )
     except HttpErrorResponse as e:
         self.transport.close()
         response = e.response
     body = TLV.decode_bytes(response.body, expected=expected)
     return body
コード例 #5
0
ファイル: __init__.py プロジェクト: ekos2001/aiohomekit
 def write_http(request, expected):
     body = TLV.encode_list(request)
     logging.debug("write message: %s",
                   TLV.to_string(TLV.decode_bytes(body)))
     connection.putrequest("POST", "/pair-setup", skip_accept_encoding=True)
     connection.putheader("Content-Type", "application/pairing+tlv8")
     connection.putheader("Content-Length", len(body))
     connection.endheaders(body)
     resp = connection.getresponse()
     response_tlv = TLV.decode_bytes(resp.read(), expected)
     logging.debug("response: %s", TLV.to_string(response_tlv))
     return response_tlv
コード例 #6
0
    def test_filter(self):
        example = bytes(bytearray.fromhex("060103" + "010203"))
        expected = [
            [6, bytearray(b"\x03")],
        ]

        data = TLV.decode_bytes(example, expected=[6])
        self.assertListEqual(data, expected)
コード例 #7
0
ファイル: __init__.py プロジェクト: ekos2001/aiohomekit
def perform_pair_setup_part1(
    with_auth: bool = True,
) -> Generator[Tuple[List[Tuple[int, bytearray]], List[int]], None, Tuple[
        bytearray, bytearray]]:
    """
    Performs a pair setup operation as described in chapter 4.7 page 39 ff.

    :return: a tuple of salt and server's public key
    :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
    """

    #
    # Step #1 ios --> accessory (send SRP start Request) (see page 39)
    #
    logging.debug("#1 ios -> accessory: send SRP start request")
    request_tlv = [
        (TLV.kTLVType_State, TLV.M1),
        (TLV.kTLVType_Method,
         TLV.PairSetupWithAuth if with_auth else TLV.PairSetup),
    ]

    step2_expectations = [
        TLV.kTLVType_State,
        TLV.kTLVType_Error,
        TLV.kTLVType_PublicKey,
        TLV.kTLVType_Salt,
    ]
    response_tlv = yield (request_tlv, step2_expectations)

    #
    # Step #3 ios --> accessory (send SRP verify request) (see page 41)
    #
    logging.debug("#3 ios -> accessory: send SRP verify request")
    response_tlv = TLV.reorder(response_tlv, step2_expectations)
    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 unsuccessful 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"

    return response_tlv[2][1], response_tlv[1][1]
コード例 #8
0
 def test_reorder_2(self):
     val = [
         [TLV.kTLVType_State, TLV.M3],
         [TLV.kTLVType_Salt, (16 * "a").encode()],
         [TLV.kTLVType_PublicKey, (384 * "b").encode()],
     ]
     tmp = TLV.reorder(val, [TLV.kTLVType_State, TLV.kTLVType_Salt])
     self.assertEqual(tmp[0][0], TLV.kTLVType_State)
     self.assertEqual(tmp[0][1], TLV.M3)
     self.assertEqual(tmp[1][0], TLV.kTLVType_Salt)
     self.assertEqual(tmp[1][1], (16 * "a").encode())
コード例 #9
0
    def test_long_values_decode_bytearray_to_list(self):
        example = bytearray.fromhex("060103" + ("09FF" + 255 * "61" + "092D" +
                                                45 * "61") + "010568656c6c6f")
        expected = [
            [6, bytearray(b"\x03")],
            [9, bytearray(300 * b"a")],
            [1, bytearray(b"hello")],
        ]

        data = TLV.decode_bytearray(example)
        self.assertListEqual(data, expected)
コード例 #10
0
 def test_to_string_for_list(self):
     example = [
         (
             1,
             "hello",
         ),
     ]
     res = TLV.to_string(example)
     self.assertEqual(res, "[\n  1: (5 bytes/<class 'str'>) hello\n]\n")
     example = [
         (
             1,
             "hello",
         ),
         (
             2,
             "world",
         ),
     ]
     res = TLV.to_string(example)
     self.assertEqual(
         res,
         "[\n  1: (5 bytes/<class 'str'>) hello\n  2: (5 bytes/<class 'str'>) world\n]\n",
     )
コード例 #11
0
ファイル: __init__.py プロジェクト: Jc2k/aiohomekit
def validate_mfi(session_key, response_tlv):
    # If pairing method is PairSetupWithAuth there should be an EncryptedData TLV in M4
    # It should have a signature and a certificate from an Apple secure co-processor.
    decrypted = ChaCha20Poly1305Decryptor(session_key).decrypt(
        bytes(),
        b"PS-Msg04",
        bytes([0, 0, 0, 0]),
        response_tlv[TLV.kTLVType_EncryptedData],
    )

    if not decrypted:
        logger.debug(
            "Device returned kTLVType_EncryptedData during M4 but could not decrypt"
        )
        return

    sub_tlv = TLV.decode_bytes(decrypted)

    if TLV.kTLVType_Signature not in sub_tlv:
        logger.debug(
            "QUIRK: M4: Device returned kTLVType_EncryptedData, but did not contain kTLVType_Signature"
        )
        return

    if TLV.kTLVType_Certificate not in sub_tlv:
        logger.debug(
            "QUIRK: M4: Device returned kTLVType_Signature but not kTLVType_Certificate"
        )
        return

    # Certificate appears to be X509 in DER format but with some sort of PKCS7 pre-amble.
    # cryptography doesn't seem to support that yet.

    logger.debug(
        "Found seemingly valid MFI kTLVType_Signature; we don't validate this yet"
    )
コード例 #12
0
def check_convert_value(val: str, char: Characteristic) -> Any:
    """
    Checks if the given value is of the given type or is convertible into the type. If the value is not convertible, a
    HomeKitTypeException is thrown.

    :param val: the original value
    :param char: the characteristic
    :return: the converted value
    :raises FormatError: if the input value could not be converted to the target type
    """

    if char.format == CharacteristicFormats.bool:
        try:
            val = strtobool(str(val))
        except ValueError:
            raise FormatError('"{v}" is no valid "{t}"!'.format(v=val,
                                                                t=char.format))

        # We have seen iPhone's sending 1 and 0 for True and False
        # This is in spec
        # It is also *required* for Ecobee Switch+ devices (as at Mar 2020)
        return 1 if val else 0

    if char.format in NUMBER_TYPES:
        try:
            val = Decimal(val)
        except ValueError:
            raise FormatError('"{v}" is no valid "{t}"!'.format(v=val,
                                                                t=char.format))

        if char.minValue is not None:
            val = max(Decimal(char.minValue), val)

        if char.maxValue is not None:
            val = min(Decimal(char.maxValue), val)

        # Honeywell T6 Pro cannot handle arbritary precision, the values we send
        # *must* respect minStep
        # See https://github.com/home-assistant/core/issues/37083
        if char.minStep is not None:
            with localcontext() as ctx:
                ctx.prec = 6

                # Python3 uses bankers rounding by default, so 28.5 rounds to 28, not 29.
                # This is surprising for most people
                ctx.rounding = ROUND_HALF_UP

                val = Decimal(val)
                offset = Decimal(
                    char.minValue if char.minValue is not None else 0)
                min_step = Decimal(char.minStep)

                # We use to_integral_value() here rather than round as it respsects
                # ctx.rounding
                val = offset + ((
                    (val - offset) / min_step).to_integral_value() * min_step)

        if char.format in INTEGER_TYPES:
            val = int(val.to_integral_value())
        else:
            val = float(val)

    if char.format == CharacteristicFormats.data:
        try:
            base64.decodebytes(val.encode())
        except binascii.Error:
            raise FormatError('"{v}" is no valid "{t}"!'.format(v=val,
                                                                t=char.format))

    if char.format == CharacteristicFormats.tlv8:
        try:
            tmp_bytes = base64.decodebytes(val.encode())
            TLV.decode_bytes(tmp_bytes)
        except (binascii.Error, TlvParseException):
            raise FormatError('"{v}" is no valid "{t}"!'.format(v=val,
                                                                t=char.format))

    return val
コード例 #13
0
ファイル: __init__.py プロジェクト: Jc2k/aiohomekit
def get_session_keys(
    pairing_data: dict[str, str | int | list[Any]]
) -> Generator[
    (
        tuple[list[tuple[int, bytearray] | tuple[int, bytes]], list[int]]
        | tuple[list[tuple[int, bytearray]], list[int]]
    ),
    None,
    Callable[[str, str], bytes],
]:
    """
    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 = dict(response_tlv)
    handle_state_step(response_tlv, TLV.M2)

    if TLV.kTLVType_PublicKey not in response_tlv:
        raise InvalidError("M2: Missing public key")

    if TLV.kTLVType_EncryptedData not in response_tlv:
        raise InvalidError("M2: Missing encrypted data")

    # 1) generate shared secret
    accessorys_session_pub_key_bytes = bytes(response_tlv[TLV.kTLVType_PublicKey])
    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
    session_key = hkdf_derive(
        shared_secret, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info"
    )

    # 3) verify auth tag on encrypted data and 4) decrypt
    encrypted = response_tlv[TLV.kTLVType_EncryptedData]
    decrypted = ChaCha20Poly1305Decryptor(session_key).decrypt(
        bytes(), b"PV-Msg02", bytes([0, 0, 0, 0]), encrypted
    )
    if type(decrypted) == bool and not decrypted:
        raise InvalidAuthTagError("step 3")
    d1 = dict(TLV.decode_bytes(decrypted))

    if TLV.kTLVType_Identifier not in d1:
        raise InvalidError("M2: Encrypted data did not contain identifier")

    if TLV.kTLVType_Signature not in d1:
        raise InvalidError("M2: Encrypted data did not contain signature")

    # 5) look up pairing by accessory name
    accessory_name = d1[TLV.kTLVType_Identifier].decode()

    if pairing_data["AccessoryPairingID"] != accessory_name:
        raise IncorrectPairingIdError("step 3")

    accessory_ltpk = ed25519.Ed25519PublicKey.from_public_bytes(
        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_pub
    )
    try:
        accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info))
    except cryptography_exceptions.InvalidSignature:
        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.Ed25519PrivateKey.from_private_bytes(
        bytes.fromhex(ios_device_ltsk_h)
    )
    # 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 = ChaCha20Poly1305Encryptor(session_key).encrypt(
        bytes(), b"PV-Msg03", bytes([0, 0, 0, 0]), sub_tlv
    )

    # 11) create tlv
    request_tlv = [
        (TLV.kTLVType_State, TLV.M3),
        (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag),
    ]

    step3_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error]
    response_tlv = yield (request_tlv, step3_expectations)

    #
    #   Post Step #4 verification (page 51)
    #
    response_tlv = dict(response_tlv)
    handle_state_step(response_tlv, TLV.M4)

    # return function to calculate session keys
    def derive(salt, info):
        return hkdf_derive(shared_secret, salt, info)

    return derive
コード例 #14
0
ファイル: __init__.py プロジェクト: ekos2001/aiohomekit
def perform_pair_setup_part2(
    pin: str, ios_pairing_id: str, salt: bytearray,
    server_public_key: bytearray
) -> Generator[Tuple[List[Tuple[int, bytearray]], List[int]], None, Dict[str,
                                                                         str]]:
    """
    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
    :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.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 = yield (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 = ed25519.Ed25519PrivateKey.generate()
    ios_device_ltpk = ios_device_ltsk.public_key()
    ios_device_public_bytes = ios_device_ltpk.public_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PublicFormat.Raw)

    # reversed:
    #   Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt
    #   Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info
    ios_device_x = hkdf_derive(
        SrpClient.to_byte_array(session_key),
        "Pair-Setup-Controller-Sign-Salt",
        "Pair-Setup-Controller-Sign-Info",
    )

    session_key = hkdf_derive(
        SrpClient.to_byte_array(session_key),
        "Pair-Setup-Encrypt-Salt",
        "Pair-Setup-Encrypt-Info",
    )

    ios_device_pairing_id = ios_pairing_id.encode()
    ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_public_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_public_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)

    response_tlv = [
        (TLV.kTLVType_State, TLV.M5),
        (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag),
    ]

    step6_expectations = [
        TLV.kTLVType_State,
        TLV.kTLVType_Error,
        TLV.kTLVType_EncryptedData,
    ]
    response_tlv = yield (response_tlv, 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 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]

    accessory_x = hkdf_derive(
        SrpClient.to_byte_array(srp_client.get_session_key()),
        "Pair-Setup-Accessory-Sign-Salt",
        "Pair-Setup-Accessory-Sign-Info",
    )

    accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk

    e25519s = ed25519.Ed25519PublicKey.from_public_bytes(
        bytes(response_tlv[1][1]))
    try:
        e25519s.verify(bytes(accessory_sig), bytes(accessory_info))
    except cryptography_exceptions.InvalidSignature:
        raise InvalidSignatureError("step #7")

    ios_device_ltsk_private_bytes = ios_device_ltsk.private_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PrivateFormat.Raw,
        encryption_algorithm=serialization.NoEncryption(),
    )

    return {
        "AccessoryPairingID": response_tlv[0][1].decode(),
        "AccessoryLTPK": hexlify(response_tlv[1][1]).decode(),
        "iOSPairingId": ios_pairing_id,
        "iOSDeviceLTSK": ios_device_ltsk_private_bytes.hex(),
        "iOSDeviceLTPK": ios_device_public_bytes.hex(),
    }
コード例 #15
0
ファイル: __init__.py プロジェクト: ekos2001/aiohomekit
def get_session_keys(
    pairing_data: Dict[str, Union[str, int, List[Any]]]
) -> Generator[Union[Tuple[List[Union[Tuple[int, bytearray], Tuple[
        int, bytes]]], List[int]], Tuple[List[Tuple[
            int, bytearray]], List[int]], ], None, Tuple[bytes, bytes], ]:
    """
    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
    session_key = hkdf_derive(shared_secret, "Pair-Verify-Encrypt-Salt",
                              "Pair-Verify-Encrypt-Info")

    # 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.Ed25519PublicKey.from_public_bytes(
        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 cryptography_exceptions.InvalidSignature:
        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.Ed25519PrivateKey.from_private_bytes(
        bytes.fromhex(ios_device_ltsk_h))
    # 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)

    # 11) create tlv
    request_tlv = [
        (TLV.kTLVType_State, TLV.M3),
        (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag),
    ]

    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

    controller_to_accessory_key = hkdf_derive(shared_secret, "Control-Salt",
                                              "Control-Write-Encryption-Key")

    accessory_to_controller_key = hkdf_derive(shared_secret, "Control-Salt",
                                              "Control-Read-Encryption-Key")

    return controller_to_accessory_key, accessory_to_controller_key
コード例 #16
0
 def test_to_string_for_list_bytearray(self):
     example = [[1, bytearray([0x42, 0x23])]]
     res = TLV.to_string(example)
     self.assertEqual(res,
                      "[\n  1: (2 bytes/<class 'bytearray'>) 0x4223\n]\n")
コード例 #17
0
 def test_to_string_for_dict_bytearray(self):
     example = {1: bytearray([0x42, 0x23])}
     res = TLV.to_string(example)
     self.assertEqual(res,
                      "{\n  1: (2 bytes/<class 'bytearray'>) 0x4223\n}\n")