8000 Fix handling of multiple pairings (#456) · ikalchev/HAP-python@767d752 · GitHub
[go: up one dir, main page]

Skip to content

Commit 767d752

Browse files
authored
Fix handling of multiple pairings (#456)
1 parent 5f45a5e commit 767d752

File tree

7 files changed

+206
-27
lines changed

7 files changed

+206
-27
lines changed

pyhap/hap_handler.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import asyncio
66
from http import HTTPStatus
77
import logging
8-
from typing import TYPE_CHECKING, Dict, Optional
8+
from typing import TYPE_CHECKING, Dict, Optional, Any
99
from urllib.parse import ParseResult, parse_qs, urlparse
1010
import uuid
1111

@@ -88,6 +88,7 @@ class HAP_TLV_TAGS:
8888
ERROR_CODE = b"\x07"
8989
PROOF = b"\x0A"
9090
PERMISSIONS = b"\x0B"
91+
SEPARATOR = b"\xFF"
9192

9293

9394
class UnprivilegedRequestException(Exception):
@@ -148,7 +149,7 @@ def __init__(self, accessory_handler, client_address):
148149
"""
149150
self.accessory_handler: AccessoryDriver = accessory_handler
150151
self.state: State = self.accessory_handler.state
151-
self.enc_context = None
152+
self.enc_context: Optional[Dict[str, Any]] = None
152153
self.client_address = client_address
153154
self.is_encrypted = False
154155
self.client_uuid: Optional[uuid.UUID] = None
@@ -567,33 +568,33 @@ def _pair_verify_two(self, tlv_objects: Dict[bytes, bytes]) -> None:
567568

568569
dec_tlv_objects = tlv.decode(bytes(decrypted_data))
569570
client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME]
570-
material = (
571-
self.enc_context["client_public"]
572-
+ client_username
573-
+ self.enc_context["public_key"].public_bytes(
574-
encoding=serialization.Encoding.Raw,
575-
format=serialization.PublicFormat.Raw,
576-
)
571+
public_key: x25519.X25519PublicKey = self.enc_context["public_key"]
572+
raw_public_key = public_key.public_bytes(
573+
encoding=serialization.Encoding.Raw,
574+
format=serialization.PublicFormat.Raw,
577575
)
576+
material = self.enc_context["client_public"] + client_username + raw_public_key
578577

579578
client_uuid = uuid.UUID(str(client_username, "utf-8"))
580579
perm_client_public = self.state.paired_clients.get(client_uuid)
581580
if perm_client_public is None:
582581
logger.error(
583-
"%s: Client %s with uuid %s attempted pair verify without being paired first (paired clients=%s).",
582+
"%s: Client %s with uuid %s attempted pair verify "
583+
"without being paired first (public_key=%s, paired clients=%s).",
584+
self.accessory_handler.accessory.display_name,
584585
self.client_address,
585586
client_uuid,
586-
self.state.paired_clients,
587-
self.accessory_handler.accessory.display_name,
587+
raw_public_key.hex(),
588+
{uuid: key.hex() for uuid, key in self.state.paired_clients.items()},
588589
)
589590
self._send_authentication_error_tlv_response(HAP_TLV_STATES.M4)
590591
return
591592

592593
verifying_key = ed25519.Ed25519PublicKey.from_public_bytes(perm_client_public)
593594
try:
594595
verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material)
595-
except InvalidSignature:
596-
logger.error("%s: Bad signature, abort.", self.client_address)
596+
except (InvalidSignature, KeyError) as ex:
597+
logger.error("%s: %s, abort.", self.client_address, ex)
597598
self._send_authentication_error_tlv_response(HAP_TLV_STATES.M4)
598599
return
599600

@@ -781,9 +782,16 @@ def _handle_list_pairings(self) -> None:
781782
client_public,
782783
HAP_TLV_TAGS.PERMISSIONS,
783784
HAP_PERMISSIONS.ADMIN if admin else HAP_PERMISSIONS.USER,
785+
HAP_TLV_TAGS.SEPARATOR,
786+
b"",
784787
]
785788
)
786789

790+
if response[-2] == HAP_TLV_TAGS.SEPARATOR:
791+
# The last pairing should not have a separator
792+
response.pop()
793+
response.pop()
794+
787795
data = tlv.encode(*response)
788796
self._send_tlv_pairing_response(data)
789797

pyhap/params.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626

2727
def get_srp_context(ng_group_len, hashfunc, salt_len=16, secret_len=32):
28-
2928
group = _ng_const[ng_order.index(ng_group_len)]
3029

3130
ctx = {

pyhap/state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ def __init__(
4242
self.addresses = address
4343
else:
4444
self.addresses = [util.get_local_address()]
45-
self.mac = mac or util.generate_mac()
45+
self.mac: str = mac or util.generate_mac()
4646
self.pincode = pincode or util.generate_pincode()
4747
self.port = port or DEFAULT_PORT
4848
self.setup_id = util.generate_setup_id()
4949

5050
self.config_version = DEFAULT_CONFIG_VERSION
51-
self.paired_clients = {}
51+
self.paired_clients: Dict[UUID, bytes] = {}
5252
self.client_properties = {}
5353

5454
self.private_key = ed25519.Ed25519PrivateKey.generate()

pyhap/tlv.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Encodes and decodes Tag-Length-Value (tlv8) data."""
22
import struct
3+
from typing import Any, Dict
34

45
from pyhap import util
56

@@ -42,7 +43,7 @@ def encode(*args, to_base64=False):
4243
return util.to_base64_str(result) if to_base64 else result
4344

4445

45-
def decode(data, from_base64=False):
46+
def decode(data: bytes, from_base64: bool = False) -> Dict[bytes, Any]:
4647
"""Decode the given TLV-encoded ``data`` to a ``dict``.
4748
4849
:param from_base64: Whether the given ``data`` should be base64 decoded first.

tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def driver(async_zeroconf):
4444
yield AccessoryDriver(loop=loop)
4545

4646

47+
@pytest.fixture(autouse=True)
48+
def mock_local_address():
49+
with patch("pyhap.util.get_local_address", return_value="127.0.0.1"):
50+
yield
51+
52+
4753
class MockDriver:
4854
def __init__(self):
4955
self.loader = Loader()

tests/test_camera.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def test_setup_endpoints(mock_driver):
8686

8787

8888
def test_set_selected_stream_start_stop(mock_driver):
89-
"""Test starting a stream request"""
89+
"""Test starting a stream request."""
9090
# mocks for asyncio.Process
9191
async def communicate():
9292
return (None, "stderr")

0 commit comments

Comments
 (0)
0