Skip to content

Commit

Permalink
Merge pull request #357 from ikalchev/v3.5.2
Browse files Browse the repository at this point in the history
V3.5.2
  • Loading branch information
ikalchev committed Jul 22, 2021
2 parents 5178c0d + c4f1d32 commit 18767c2
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 84 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Sections
### Developers
-->

## [3.5.2] - 2021-07-22

- Switch from ed25519 to pynacl. [#355](https://github.com/ikalchev/HAP-python/pull/355)

## [3.5.1] - 2021-07-04

# Changed
Expand Down
4 changes: 2 additions & 2 deletions pyhap/const.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""This module contains constants used by other modules."""
MAJOR_VERSION = 3
MINOR_VERSION = 5
PATCH_VERSION = 1
PATCH_VERSION = 2
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5)
REQUIRED_PYTHON_VER = (3, 6)

BASE_UUID = "-0000-1000-8000-0026BB765291"

Expand Down
26 changes: 21 additions & 5 deletions pyhap/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import json
import uuid

import ed25519
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519


class AccessoryEncoder:
Expand Down Expand Up @@ -57,8 +58,19 @@ def persist(fp, state):
"mac": state.mac,
"config_version": state.config_version,
"paired_clients": paired_clients,
"private_key": bytes.hex(state.private_key.to_seed()),
"public_key": bytes.hex(state.public_key.to_bytes()),
"private_key": bytes.hex(
state.private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption(),
)
),
"public_key": bytes.hex(
state.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
),
}
json.dump(config_state, fp)

Expand All @@ -75,5 +87,9 @@ def load_into(fp, state):
uuid.UUID(client): bytes.fromhex(key)
for client, key in loaded["paired_clients"].items()
}
state.private_key = ed25519.SigningKey(bytes.fromhex(loaded["private_key"]))
state.public_key = ed25519.VerifyingKey(bytes.fromhex(loaded["public_key"]))
state.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(
bytes.fromhex(loaded["private_key"])
)
state.public_key = ed25519.Ed25519PublicKey.from_public_bytes(
bytes.fromhex(loaded["public_key"])
)
51 changes: 33 additions & 18 deletions pyhap/hap_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
from urllib.parse import parse_qs, urlparse
import uuid

from cryptography.exceptions import InvalidTag
from cryptography.exceptions import InvalidSignature, InvalidTag
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import curve25519
import ed25519

from pyhap import tlv
from pyhap.const import (
CATEGORY_BRIDGE,
HAP_REPR_CHARS,
HAP_REPR_STATUS,
HAP_SERVER_STATUS,
)
from pyhap import tlv
from pyhap.util import long_to_bytes

from .hap_crypto import hap_hkdf, pad_tls_nonce
Expand Down Expand Up @@ -368,11 +369,11 @@ def _pairing_four(self, client_username, client_ltpk, client_proof, encryption_k
)

data = output_key + client_username + client_ltpk
verifying_key = ed25519.VerifyingKey(client_ltpk)
verifying_key = ed25519.Ed25519PublicKey.from_public_bytes(client_ltpk)

try:
verifying_key.verify(client_proof, data)
except ed25519.BadSignatureError:
except InvalidSignature:
logger.error("Bad signature, abort.")
raise

Expand All @@ -390,7 +391,10 @@ def _pairing_five(self, client_username, client_ltpk, encryption_key):
long_to_bytes(session_key), self.PAIRING_5_SALT, self.PAIRING_5_INFO
)

server_public = self.state.public_key.to_bytes()
server_public = self.state.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
mac = self.state.mac.encode()

material = output_key + mac + server_public
Expand Down Expand Up @@ -457,16 +461,21 @@ def _pair_verify_one(self, tlv_objects):
logger.debug("%s: Pair verify [1/2].", self.client_address)
client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY]

private_key = curve25519.Private()
public_key = private_key.get_public()
shared_key = private_key.get_shared_key(
curve25519.Public(client_public),
# Key is hashed before being returned, we don't want it; This fixes that.
lambda x: x,
private_key = x25519.X25519PrivateKey.generate()
public_key = private_key.public_key()
shared_key = private_key.exchange(
x25519.X25519PublicKey.from_public_bytes(client_public)
)

mac = self.state.mac.encode()
material = public_key.serialize() + mac + client_public
material = (
public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
+ mac
+ client_public
)
server_proof = self.state.private_key.sign(material)

output_key = hap_hkdf(shared_key, self.PVERIFY_1_SALT, self.PVERIFY_1_INFO)
Expand All @@ -487,7 +496,10 @@ def _pair_verify_one(self, tlv_objects):
HAP_TLV_TAGS.ENCRYPTED_DATA,
aead_message,
HAP_TLV_TAGS.PUBLIC_KEY,
public_key.serialize(),
public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
),
)
self._send_tlv_pairing_response(data)

Expand All @@ -513,7 +525,10 @@ def _pair_verify_two(self, tlv_objects):
material = (
self.enc_context["client_public"]
+ client_username
+ self.enc_context["public_key"].serialize()
+ self.enc_context["public_key"].public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
)

client_uuid = uuid.UUID(str(client_username, "utf-8"))
Expand All @@ -528,10 +543,10 @@ def _pair_verify_two(self, tlv_objects):
self._send_authentication_error_tlv_response(HAP_TLV_STATES.M4)
return

verifying_key = ed25519.VerifyingKey(perm_client_public)
verifying_key = ed25519.Ed25519PublicKey.from_public_bytes(perm_client_public)
try:
verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material)
except ed25519.BadSignatureError:
except InvalidSignature:
logger.error("%s: Bad signature, abort.", self.client_address)
self._send_authentication_error_tlv_response(HAP_TLV_STATES.M4)
return
Expand Down
10 changes: 4 additions & 6 deletions pyhap/state.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Module for `State` class."""
import ed25519
from cryptography.hazmat.primitives.asymmetric import ed25519

from pyhap import util
from pyhap.const import DEFAULT_CONFIG_VERSION, DEFAULT_PORT
Expand All @@ -11,8 +11,7 @@ class State:
That includes all needed for setup of driver and pairing.
"""

def __init__(self, *, address=None, mac=None,
pincode=None, port=None):
def __init__(self, *, address=None, mac=None, pincode=None, port=None):
"""Initialize a new object. Create key pair.
Must be called with keyword arguments.
Expand All @@ -26,9 +25,8 @@ def __init__(self, *, address=None, mac=None,
self.config_version = DEFAULT_CONFIG_VERSION
self.paired_clients = {}

sk, vk = ed25519.create_keypair()
self.private_key = sk
self.public_key = vk
self.private_key = ed25519.Ed25519PrivateKey.generate()
self.public_key = self.private_key.public_key()

# ### Pairing ###
@property
Expand Down
2 changes: 0 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
h11
curve25519-donna
ed25519
cryptography
zeroconf
2 changes: 0 additions & 2 deletions requirements_all.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
base36
curve25519-donna
ed25519
cryptography
pyqrcode
h11
Expand Down
69 changes: 31 additions & 38 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,59 @@

import pyhap.const as pyhap_const


NAME = 'HAP-python'
DESCRIPTION = 'HomeKit Accessory Protocol implementation in python'
URL = 'https://github.com/ikalchev/{}'.format(NAME)
AUTHOR = 'Ivan Kalchev'
NAME = "HAP-python"
DESCRIPTION = "HomeKit Accessory Protocol implementation in python"
URL = "https://github.com/ikalchev/{}".format(NAME)
AUTHOR = "Ivan Kalchev"


PROJECT_URLS = {
'Bug Reports': '{}/issues'.format(URL),
'Documentation': 'http://hap-python.readthedocs.io/en/latest/',
'Source': '{}/tree/master'.format(URL),
"Bug Reports": "{}/issues".format(URL),
"Documentation": "http://hap-python.readthedocs.io/en/latest/",
"Source": "{}/tree/master".format(URL),
}


MIN_PY_VERSION = '.'.join(map(str, pyhap_const.REQUIRED_PYTHON_VER))
MIN_PY_VERSION = ".".join(map(str, pyhap_const.REQUIRED_PYTHON_VER))

with open('README.md', 'r', encoding='utf-8') as f:
with open("README.md", "r", encoding="utf-8") as f:
README = f.read()


REQUIRES = [
'curve25519-donna',
'ed25519',
'cryptography',
'zeroconf>=0.32.0',
'h11'
]
REQUIRES = ["cryptography", "zeroconf>=0.32.0", "h11"]


setup(
name=NAME,
version=pyhap_const.__version__,
description=DESCRIPTION,
long_description=README,
long_description_content_type='text/markdown',
long_description_content_type="text/markdown",
url=URL,
packages=['pyhap'],
packages=["pyhap"],
include_package_data=True,
project_urls=PROJECT_URLS,
python_requires='>={}'.format(MIN_PY_VERSION),
python_requires=">={}".format(MIN_PY_VERSION),
install_requires=REQUIRES,
license='Apache License 2.0',
license_file='LICENSE',
license="Apache License 2.0",
license_file="LICENSE",
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Topic :: Home Automation',
'Topic :: Software Development :: Libraries :: Python Modules',
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Topic :: Home Automation",
"Topic :: Software Development :: Libraries :: Python Modules",
],
extras_require={
'QRCode': ['base36', 'pyqrcode'],
}
"QRCode": ["base36", "pyqrcode"],
},
)
11 changes: 9 additions & 2 deletions tests/test_accessory_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import tempfile
from unittest.mock import MagicMock, patch
from uuid import uuid1
from zeroconf import InterfaceChoice

from cryptography.hazmat.primitives import serialization
import pytest
from zeroconf import InterfaceChoice

from pyhap import util
from pyhap.accessory import STANDALONE_AID, Accessory, Bridge
Expand Down Expand Up @@ -100,7 +101,13 @@ def test_persist_load(async_zeroconf):
# the new accessory.
driver = AccessoryDriver(port=51234, persist_file=file.name)
driver.load()
assert driver.state.public_key == pk
assert driver.state.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
) == pk.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)


def test_persist_cannot_write(async_zeroconf):
Expand Down
Loading

0 comments on commit 18767c2

Please sign in to comment.