ci: Use black instead of flake8

It results in mostly the same style, plus it’s configurable via pyprojects.toml.
This commit is contained in:
Gergely Polonkai 2023-10-29 09:47:57 +01:00
parent bbfc137ca8
commit 93f2d329a4
No known key found for this signature in database
GPG Key ID: 2D2885533B869ED4
14 changed files with 221 additions and 117 deletions

View File

@ -1,2 +0,0 @@
[flake8]
max-line-length = 120

View File

@ -16,3 +16,11 @@ repos:
pass_filenames: false
language: system
stages: [push]
- id: black
name: black
description: "Black: The uncompromising Python code formatter"
entry: poetry run black
args: ["--diff"]
language: system
require_serial: true
types_or: [python, pyi]

View File

@ -7,13 +7,13 @@ from nacl.signing import SigningKey
from secret_handshake import SHSClient
with open(os.path.expanduser('~/.ssb/secret')) as f:
with open(os.path.expanduser("~/.ssb/secret")) as f:
config = yaml.safe_load(f)
async def main():
server_pub_key = b64decode(config['public'][:-8])
client = SHSClient('localhost', 8008, SigningKey.generate(), server_pub_key)
server_pub_key = b64decode(config["public"][:-8])
client = SHSClient("localhost", 8008, SigningKey.generate(), server_pub_key)
await client.open()
async for msg in client:

View File

@ -7,7 +7,7 @@ from nacl.signing import SigningKey
from secret_handshake import SHSServer
with open(os.path.expanduser('~/.ssb/secret')) as f:
with open(os.path.expanduser("~/.ssb/secret")) as f:
config = yaml.safe_load(f)
@ -17,11 +17,12 @@ async def _on_connect(conn):
async def main():
server_keypair = SigningKey(b64decode(config['private'][:-8])[:32])
server = SHSServer('localhost', 8008, server_keypair)
server_keypair = SigningKey(b64decode(config["private"][:-8])[:32])
server = SHSServer("localhost", 8008, server_keypair)
server.on_connect(_on_connect)
await server.listen()
loop = get_event_loop()
loop.run_until_complete(main())
loop.run_forever()

78
poetry.lock generated
View File

@ -53,6 +53,46 @@ setuptools = {version = "*", markers = "python_version >= \"3.12\""}
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
name = "black"
version = "23.10.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"},
{file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"},
{file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"},
{file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"},
{file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"},
{file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"},
{file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"},
{file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"},
{file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"},
{file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"},
{file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"},
{file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"},
{file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"},
{file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"},
{file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"},
{file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"},
{file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"},
{file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "build"
version = "1.0.3"
@ -278,6 +318,20 @@ setuptools = "*"
[package.extras]
test = ["mock (>=3.0.0)", "pytest"]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
@ -595,6 +649,17 @@ files = [
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "nodeenv"
version = "1.8.0"
@ -620,6 +685,17 @@ files = [
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
name = "pathspec"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "platformdirs"
version = "3.11.0"
@ -1184,4 +1260,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "ea2b635a53716be43ebfd83e9e810998913f10f5da1cd5bf778c57b92c6836bb"
content-hash = "4f9ec52a576209d59ea926bd2e332f5f06b4b2158c2ea348185704d21c63bd73"

View File

@ -23,10 +23,14 @@ pytest-asyncio = "^0.21.1"
pytest-mock = "^3.12.0"
pre-commit = "^3.5.0"
commitizen = "^3.12.0"
black = "^23.10.1"
[tool.poetry.group.docs.dependencies]
sphinx = "^7.2.6"
[tool.black]
line-length = 120
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"

View File

@ -21,4 +21,4 @@
from .network import SHSClient, SHSServer
__all__ = ('SHSClient', 'SHSServer')
__all__ = ("SHSClient", "SHSServer")

View File

@ -8,21 +8,21 @@ from .util import inc_nonce, split_chunks
HEADER_LENGTH = 2 + 16 + 16
MAX_SEGMENT_SIZE = 4 * 1024
TERMINATION_HEADER = (b'\x00' * 18)
TERMINATION_HEADER = b"\x00" * 18
def get_stream_pair(reader, writer, **kwargs):
"""Return a tuple with `(unbox_stream, box_stream)` (reader/writer).
:return: (:class:`secret_handshake.boxstream.UnboxStream`,
:class:`secret_handshake.boxstream.BoxStream`) """
:class:`secret_handshake.boxstream.BoxStream`)"""
box_args = {
'key': kwargs['encrypt_key'],
'nonce': kwargs['encrypt_nonce'],
"key": kwargs["encrypt_key"],
"nonce": kwargs["encrypt_nonce"],
}
unbox_args = {
'key': kwargs['decrypt_key'],
'nonce': kwargs['decrypt_nonce'],
"key": kwargs["decrypt_key"],
"nonce": kwargs["decrypt_nonce"],
}
return UnboxStream(reader, **unbox_args), BoxStream(writer, **box_args)
@ -49,7 +49,7 @@ class UnboxStream(object):
self.closed = True
return None
length = struct.unpack('>H', header[:2])[0]
length = struct.unpack(">H", header[:2])[0]
mac = header[2:]
data = await self.reader.readexactly(length)
@ -78,7 +78,7 @@ class BoxStream(object):
def write(self, data):
for chunk in split_chunks(data, MAX_SEGMENT_SIZE):
body = self.box.encrypt(chunk, inc_nonce(self.nonce))[24:]
header = struct.pack('>H', len(body) - 16) + body[:16]
header = struct.pack(">H", len(body) - 16) + body[:16]
hdrbox = self.box.encrypt(header, self.nonce)[24:]
self.writer.write(hdrbox)
@ -87,4 +87,4 @@ class BoxStream(object):
self.writer.write(body[16:])
def close(self):
self.writer.write(self.box.encrypt(b'\x00' * 18, self.nonce)[24:])
self.writer.write(self.box.encrypt(b"\x00" * 18, self.nonce)[24:])

View File

@ -23,17 +23,17 @@ import hashlib
import hmac
from base64 import b64decode
from nacl.bindings import (crypto_box_afternm, crypto_box_open_afternm,
crypto_scalarmult)
from nacl.bindings import crypto_box_afternm, crypto_box_open_afternm, crypto_scalarmult
from nacl.exceptions import CryptoError
from nacl.public import PrivateKey
from nacl.signing import VerifyKey
APPLICATION_KEY = b64decode('1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=')
APPLICATION_KEY = b64decode("1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=")
class SHSError(Exception):
"""A SHS exception."""
pass
@ -45,8 +45,9 @@ class SHSCryptoBase(object):
def _reset_keys(self, ephemeral_key):
self.local_ephemeral_key = ephemeral_key
self.local_app_hmac = (hmac.new(self.application_key, bytes(ephemeral_key.public_key), digestmod='sha512')
.digest()[:32])
self.local_app_hmac = hmac.new(
self.application_key, bytes(ephemeral_key.public_key), digestmod="sha512"
).digest()[:32]
def generate_challenge(self):
"""Generate and return a challenge to be sent to the server."""
@ -57,7 +58,7 @@ class SHSCryptoBase(object):
assert len(data) == 64
sent_hmac, remote_ephemeral_key = data[:32], data[32:]
h = hmac.new(self.application_key, remote_ephemeral_key, digestmod='sha512')
h = hmac.new(self.application_key, remote_ephemeral_key, digestmod="sha512")
self.remote_app_hmac = h.digest()[:32]
ok = self.remote_app_hmac == sent_hmac
@ -78,11 +79,11 @@ class SHSCryptoBase(object):
def get_box_keys(self):
shared_secret = hashlib.sha256(self.box_secret).digest()
return {
'shared_secret': shared_secret,
'encrypt_key': hashlib.sha256(shared_secret + bytes(self.remote_pub_key)).digest(),
'decrypt_key': hashlib.sha256(shared_secret + bytes(self.local_key.verify_key)).digest(),
'encrypt_nonce': self.remote_app_hmac[:24],
'decrypt_nonce': self.local_app_hmac[:24]
"shared_secret": shared_secret,
"encrypt_key": hashlib.sha256(shared_secret + bytes(self.remote_pub_key)).digest(),
"decrypt_key": hashlib.sha256(shared_secret + bytes(self.local_key.verify_key)).digest(),
"encrypt_nonce": self.remote_app_hmac[:24],
"decrypt_nonce": self.local_app_hmac[:24],
}
@ -91,7 +92,7 @@ class SHSServerCrypto(SHSCryptoBase):
assert len(data) == 112
a_bob = crypto_scalarmult(bytes(self.local_key.to_curve25519_private_key()), self.remote_ephemeral_key)
box_secret = hashlib.sha256(self.application_key + self.shared_secret + a_bob).digest()
self.hello = crypto_box_open_afternm(data, b'\x00' * 24, box_secret)
self.hello = crypto_box_open_afternm(data, b"\x00" * 24, box_secret)
signature, public_key = self.hello[:64], self.hello[64:]
signed = self.application_key + bytes(self.local_key.verify_key) + self.shared_hash
pkey = VerifyKey(public_key)
@ -99,14 +100,15 @@ class SHSServerCrypto(SHSCryptoBase):
# will raise an exception if verification fails
pkey.verify(signed, signature)
self.remote_pub_key = pkey
b_alice = crypto_scalarmult(bytes(self.local_ephemeral_key),
bytes(self.remote_pub_key.to_curve25519_public_key()))
b_alice = crypto_scalarmult(
bytes(self.local_ephemeral_key), bytes(self.remote_pub_key.to_curve25519_public_key())
)
self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + a_bob + b_alice).digest()[:32]
return True
def generate_accept(self):
okay = self.local_key.sign(self.application_key + self.hello + self.shared_hash).signature
d = crypto_box_afternm(okay, b'\x00' * 24, self.box_secret)
d = crypto_box_afternm(okay, b"\x00" * 24, self.box_secret)
return d
def clean(self, new_ephemeral_key=None):
@ -159,8 +161,7 @@ class SHSClientCrypto(SHSCryptoBase):
b_alice = crypto_scalarmult(bytes(curve_lkey), self.remote_ephemeral_key)
self.b_alice = b_alice
# this is hash(K | a * b | a * B | A * b)
self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + self.a_bob +
b_alice).digest()
self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + self.a_bob + b_alice).digest()
nonce = b"\x00" * 24
@ -168,7 +169,7 @@ class SHSClientCrypto(SHSCryptoBase):
# let's use the box secret to unbox our encrypted message
signature = crypto_box_open_afternm(data, nonce, self.box_secret)
except CryptoError:
raise SHSError('Error decrypting server acceptance message')
raise SHSError("Error decrypting server acceptance message")
# we should have received sign(B)[K | H | hash(a * b)]
# let's see if that signature can verify the reconstructed data on our side

View File

@ -77,13 +77,13 @@ class SHSServer(SHSEndpoint):
async def _handshake(self, reader, writer):
data = await reader.readexactly(64)
if not self.crypto.verify_challenge(data):
raise SHSClientException('Client challenge is not valid')
raise SHSClientException("Client challenge is not valid")
writer.write(self.crypto.generate_challenge())
data = await reader.readexactly(112)
if not self.crypto.verify_client_auth(data):
raise SHSClientException('Client auth is not valid')
raise SHSClientException("Client auth is not valid")
writer.write(self.crypto.generate_accept())
@ -125,21 +125,22 @@ class SHSClient(SHSDuplexStream, SHSEndpoint):
SHSEndpoint.__init__(self)
self.host = host
self.port = port
self.crypto = SHSClientCrypto(client_kp, server_pub_key, ephemeral_key=ephemeral_key,
application_key=application_key)
self.crypto = SHSClientCrypto(
client_kp, server_pub_key, ephemeral_key=ephemeral_key, application_key=application_key
)
async def _handshake(self, reader, writer):
writer.write(self.crypto.generate_challenge())
data = await reader.readexactly(64)
if not self.crypto.verify_server_challenge(data):
raise SHSClientException('Server challenge is not valid')
raise SHSClientException("Server challenge is not valid")
writer.write(self.crypto.generate_client_auth())
data = await reader.readexactly(80)
if not self.crypto.verify_server_accept(data):
raise SHSClientException('Server accept is not valid')
raise SHSClientException("Server accept is not valid")
async def open(self):
reader, writer = await asyncio.open_connection(self.host, self.port)

View File

@ -26,29 +26,31 @@ from secret_handshake.util import AsyncBuffer, async_comprehend
from .test_crypto import CLIENT_ENCRYPT_KEY, CLIENT_ENCRYPT_NONCE
MESSAGE_1 = (b'\xcev\xedE\x06l\x02\x13\xc8\x17V\xfa\x8bZ?\x88B%O\xb0L\x9f\x8e\x8c0y\x1dv\xc0\xc9\xf6\x9d\xc2\xdf\xdb'
b'\xee\x9d')
MESSAGE_1 = (
b"\xcev\xedE\x06l\x02\x13\xc8\x17V\xfa\x8bZ?\x88B%O\xb0L\x9f\x8e\x8c0y\x1dv\xc0\xc9\xf6\x9d\xc2\xdf\xdb" b"\xee\x9d"
)
MESSAGE_2 = b"\x141\xd63\x13d\xd1\xecZ\x9b\xd0\xd4\x03\xcdR?'\xaa.\x89I\x92I\xf9guL\xaa\x06?\xea\xca/}\x88*\xb2"
MESSAGE_3 = (b'\xcbYY\xf1\x0f\xa5O\x13r\xa6"\x15\xc5\x9d\r.*\x0b\x92\x10m\xa6(\x0c\x0c\xc61\x80j\x81)\x800\xed\xda'
b'\xad\xa1')
MESSAGE_CLOSED = b'\xb1\x14hU\'\xb5M\xa6"\x03\x9duy\xa1\xd4evW,\xdcE\x18\xe4+ C4\xe8h\x96\xed\xc5\x94\x80'
MESSAGE_3 = (
b'\xcbYY\xf1\x0f\xa5O\x13r\xa6"\x15\xc5\x9d\r.*\x0b\x92\x10m\xa6(\x0c\x0c\xc61\x80j\x81)\x800\xed\xda' b"\xad\xa1"
)
MESSAGE_CLOSED = b"\xb1\x14hU'\xb5M\xa6\"\x03\x9duy\xa1\xd4evW,\xdcE\x18\xe4+ C4\xe8h\x96\xed\xc5\x94\x80"
@pytest.mark.asyncio
async def test_boxstream():
buffer = AsyncBuffer()
box_stream = BoxStream(buffer, CLIENT_ENCRYPT_KEY, CLIENT_ENCRYPT_NONCE)
box_stream.write(b'foo')
box_stream.write(b"foo")
buffer.seek(0)
assert await buffer.read() == MESSAGE_1
pos = buffer.tell()
box_stream.write(b'foo')
box_stream.write(b"foo")
buffer.seek(pos)
assert await buffer.read() == MESSAGE_2
pos = buffer.tell()
box_stream.write(b'bar')
box_stream.write(b"bar")
buffer.seek(pos)
assert await buffer.read() == MESSAGE_3
@ -65,7 +67,7 @@ async def test_unboxstream():
unbox_stream = UnboxStream(buffer, CLIENT_ENCRYPT_KEY, CLIENT_ENCRYPT_NONCE)
assert not unbox_stream.closed
assert (await async_comprehend(unbox_stream)) == [b'foo', b'foo', b'bar']
assert (await async_comprehend(unbox_stream)) == [b"foo", b"foo", b"bar"]
assert unbox_stream.closed

View File

@ -27,11 +27,11 @@ from nacl.signing import SigningKey
from secret_handshake.crypto import SHSClientCrypto, SHSServerCrypto
APP_KEY = hashlib.sha256(b'app_key').digest()
SERVER_KEY_SEED = b'\xcaw\x01\xc2cQ\xfd\x94\x9f\x14\x84\x0c0<l\xd8\xe4\xf5>\x12\\\x96\xcd\x9b\x0c\x02z&\x96!\xe0\xa2'
CLIENT_KEY_SEED = b'\xbf\x02<\xd3e\x9d\xac-\xd1\x9e-{\xe5q\x90\x03\x11\xba\x8cSQ\xa0\xc3p~\x89\xe6\xeeb\xaa\x1c\x17'
APP_KEY = hashlib.sha256(b"app_key").digest()
SERVER_KEY_SEED = b"\xcaw\x01\xc2cQ\xfd\x94\x9f\x14\x84\x0c0<l\xd8\xe4\xf5>\x12\\\x96\xcd\x9b\x0c\x02z&\x96!\xe0\xa2"
CLIENT_KEY_SEED = b"\xbf\x02<\xd3e\x9d\xac-\xd1\x9e-{\xe5q\x90\x03\x11\xba\x8cSQ\xa0\xc3p~\x89\xe6\xeeb\xaa\x1c\x17"
SERVER_EPH_KEY_SEED = b"ed\x1c\x01\x03s\x04\xdc\x8e`\xd6Z\xd0u;\xcbX\x91\xd8ZO\xf8\xf0\xd6'\xd5\xb1Yy\x13yH"
CLIENT_EPH_KEY_SEED = b'u8\xd0\xe3\x85d_Pz\x0c\xf5\xfd\x15\xce2p#\xb0\xf0\x9f\xe6!\xe1\xcb\xf6\x93\t\xebr{1\x8b'
CLIENT_EPH_KEY_SEED = b"u8\xd0\xe3\x85d_Pz\x0c\xf5\xfd\x15\xce2p#\xb0\xf0\x9f\xe6!\xe1\xcb\xf6\x93\t\xebr{1\x8b"
@pytest.fixture()
@ -49,28 +49,38 @@ def client():
return SHSClientCrypto(client_key, bytes(server_key.verify_key), client_eph_key, application_key=APP_KEY)
CLIENT_CHALLENGE = (b'd\xe8\xccD\xec\xb9E\xbb\xaa\xa7\x7f\xe38\x15\x16\xef\xca\xd22u\x1d\xfe<\xe7j'
b'\xd7\xf0uc\xf0r\xf3\x7f\t\x18\xec\x8c\xf7\xff\x8e\xa9\xc83\x13\x18R\x16\x1d'
b'\xe5\xc6K\xae\x94\xdbVt\x84\xdc\x1c@+D\x1c%')
CLIENT_AUTH = (b'\xf2\xaf?z\x15\x10\xd0\xf0\xdf\xe3\x91\xfe\x14\x1c}z\xab\xeey\xf5\xef\xfc\xa1EdV\xf2T\x95s[!$z'
b'\xeb\x8f\x1b\x96JP\x17^\x92\xc8\x9e\xb4*5`\xf2\x8fI.\x93\xb9\x14:\xca@\x06\xff\xd1\xf1J\xc8t\xc4'
b'\xd8\xc3$[\xc5\x94je\x83\x00%\x99\x10\x16\xb1\xa2\xb2\xb7\xbf\xc9\x88\x14\xb9\xbb^\tzq\xa4\xef\xc5'
b'\xf5\x1f7#\xed\x92X\xb2\xe3\xe5\x8b[t3')
SERVER_CHALLENGE = (b'S\\\x06\x8d\xe5\xeb&*\xb8\x0bp\xb3Z\x8e\\\x85\x14\xaa\x1c\x8di\x9d\x7f\xa9\xeawl\xb9}\x85\xc3ik'
b'\x0c ($E\xb4\x8ax\xc4)t<\xd7\x8b\xd6\x07\xb7\xecw\x84\r\xe1-Iz`\xeb\x04\x89\xd6{')
SERVER_ACCEPT = (b'\xb4\xd0\xea\xfb\xfb\xf6s\xcc\x10\xc4\x99\x95"\x13 y\xa6\xea.G\xeed\x8d=t9\x88|\x94\xd1\xbcK\xd47'
b'\xd8\xbcG1h\xac\xd0\xeb*\x1f\x8d\xae\x0b\x91G\xa1\xe6\x96b\xf2\xda90u\xeb_\xab\xdb\xcb%d7}\xb5\xce'
b'(k\x15\xe3L\x9d)\xd5\xa1|:')
INTER_SHARED_SECRET = (b'vf\xd82\xaeU\xda]\x08\x9eZ\xd6\x06\xcc\xd3\x99\xfd\xce\xc5\x16e8n\x9a\x04\x04\x84\xc5\x1a'
b'\x8f\xf2M')
BOX_SECRET = b'\x03\xfe\xe3\x8c u\xbcl^\x17eD\x96\xa3\xa6\x880f\x11\x7f\x85\xf2:\xa3[`\x06[#l\xbcr'
CLIENT_CHALLENGE = (
b"d\xe8\xccD\xec\xb9E\xbb\xaa\xa7\x7f\xe38\x15\x16\xef\xca\xd22u\x1d\xfe<\xe7j"
b"\xd7\xf0uc\xf0r\xf3\x7f\t\x18\xec\x8c\xf7\xff\x8e\xa9\xc83\x13\x18R\x16\x1d"
b"\xe5\xc6K\xae\x94\xdbVt\x84\xdc\x1c@+D\x1c%"
)
CLIENT_AUTH = (
b"\xf2\xaf?z\x15\x10\xd0\xf0\xdf\xe3\x91\xfe\x14\x1c}z\xab\xeey\xf5\xef\xfc\xa1EdV\xf2T\x95s[!$z"
b"\xeb\x8f\x1b\x96JP\x17^\x92\xc8\x9e\xb4*5`\xf2\x8fI.\x93\xb9\x14:\xca@\x06\xff\xd1\xf1J\xc8t\xc4"
b"\xd8\xc3$[\xc5\x94je\x83\x00%\x99\x10\x16\xb1\xa2\xb2\xb7\xbf\xc9\x88\x14\xb9\xbb^\tzq\xa4\xef\xc5"
b"\xf5\x1f7#\xed\x92X\xb2\xe3\xe5\x8b[t3"
)
SERVER_CHALLENGE = (
b"S\\\x06\x8d\xe5\xeb&*\xb8\x0bp\xb3Z\x8e\\\x85\x14\xaa\x1c\x8di\x9d\x7f\xa9\xeawl\xb9}\x85\xc3ik"
b"\x0c ($E\xb4\x8ax\xc4)t<\xd7\x8b\xd6\x07\xb7\xecw\x84\r\xe1-Iz`\xeb\x04\x89\xd6{"
)
SERVER_ACCEPT = (
b'\xb4\xd0\xea\xfb\xfb\xf6s\xcc\x10\xc4\x99\x95"\x13 y\xa6\xea.G\xeed\x8d=t9\x88|\x94\xd1\xbcK\xd47'
b"\xd8\xbcG1h\xac\xd0\xeb*\x1f\x8d\xae\x0b\x91G\xa1\xe6\x96b\xf2\xda90u\xeb_\xab\xdb\xcb%d7}\xb5\xce"
b"(k\x15\xe3L\x9d)\xd5\xa1|:"
)
INTER_SHARED_SECRET = (
b"vf\xd82\xaeU\xda]\x08\x9eZ\xd6\x06\xcc\xd3\x99\xfd\xce\xc5\x16e8n\x9a\x04\x04\x84\xc5\x1a" b"\x8f\xf2M"
)
BOX_SECRET = b"\x03\xfe\xe3\x8c u\xbcl^\x17eD\x96\xa3\xa6\x880f\x11\x7f\x85\xf2:\xa3[`\x06[#l\xbcr"
SHARED_SECRET = b'UV\xad*\x8e\xce\x88\xf2\x87l\x13iZ\x12\xd7\xa6\xd1\x9c-\x9d\x07\xf5\xa96\x03w\x11\xe5\x96$m\x1d'
CLIENT_ENCRYPT_KEY = (b'\xec\x1f,\x82\x9f\xedA\xc0\xda\x87[\xf9u\xbf\xac\x9cI\xa5T\xd1\x91\xff\xa8.\xd0 \xfbU\xc7\x14'
b')\xc7')
CLIENT_DECRYPT_KEY = b'\xf9e\xa0As\xb2=\xb7P~\xf3\xf9(\xfd\x7f\xfe\xb7TZhn\xd7\x8c=\xea.o\x9e\x8c9)\x10'
CLIENT_ENCRYPT_NONCE = b'S\\\x06\x8d\xe5\xeb&*\xb8\x0bp\xb3Z\x8e\\\x85\x14\xaa\x1c\x8di\x9d\x7f\xa9'
CLIENT_DECRYPT_NONCE = b'd\xe8\xccD\xec\xb9E\xbb\xaa\xa7\x7f\xe38\x15\x16\xef\xca\xd22u\x1d\xfe<\xe7'
SHARED_SECRET = b"UV\xad*\x8e\xce\x88\xf2\x87l\x13iZ\x12\xd7\xa6\xd1\x9c-\x9d\x07\xf5\xa96\x03w\x11\xe5\x96$m\x1d"
CLIENT_ENCRYPT_KEY = (
b"\xec\x1f,\x82\x9f\xedA\xc0\xda\x87[\xf9u\xbf\xac\x9cI\xa5T\xd1\x91\xff\xa8.\xd0 \xfbU\xc7\x14" b")\xc7"
)
CLIENT_DECRYPT_KEY = b"\xf9e\xa0As\xb2=\xb7P~\xf3\xf9(\xfd\x7f\xfe\xb7TZhn\xd7\x8c=\xea.o\x9e\x8c9)\x10"
CLIENT_ENCRYPT_NONCE = b"S\\\x06\x8d\xe5\xeb&*\xb8\x0bp\xb3Z\x8e\\\x85\x14\xaa\x1c\x8di\x9d\x7f\xa9"
CLIENT_DECRYPT_NONCE = b"d\xe8\xccD\xec\xb9E\xbb\xaa\xa7\x7f\xe38\x15\x16\xef\xca\xd22u\x1d\xfe<\xe7"
def test_handshake(client, server):
@ -100,12 +110,12 @@ def test_handshake(client, server):
client_keys = client.get_box_keys()
server_keys = server.get_box_keys()
assert client_keys['shared_secret'] == SHARED_SECRET
assert client_keys['encrypt_key'] == CLIENT_ENCRYPT_KEY
assert client_keys['decrypt_key'] == CLIENT_DECRYPT_KEY
assert client_keys['encrypt_nonce'] == CLIENT_ENCRYPT_NONCE
assert client_keys['decrypt_nonce'] == CLIENT_DECRYPT_NONCE
assert client_keys["shared_secret"] == SHARED_SECRET
assert client_keys["encrypt_key"] == CLIENT_ENCRYPT_KEY
assert client_keys["decrypt_key"] == CLIENT_DECRYPT_KEY
assert client_keys["encrypt_nonce"] == CLIENT_ENCRYPT_NONCE
assert client_keys["decrypt_nonce"] == CLIENT_DECRYPT_NONCE
assert client_keys['shared_secret'] == server_keys['shared_secret']
assert client_keys['encrypt_key'] == server_keys['decrypt_key']
assert client_keys['encrypt_nonce'] == server_keys['decrypt_nonce']
assert client_keys["shared_secret"] == server_keys["shared_secret"]
assert client_keys["encrypt_key"] == server_keys["decrypt_key"]
assert client_keys["encrypt_nonce"] == server_keys["decrypt_nonce"]

View File

@ -29,6 +29,7 @@ from secret_handshake.util import AsyncBuffer
class DummyCrypto(object):
"""Dummy crypto module, pretends everything is fine."""
def verify_server_challenge(self, data):
return True
@ -39,23 +40,23 @@ class DummyCrypto(object):
return True
def generate_challenge(self):
return b'CHALLENGE'
return b"CHALLENGE"
def generate_client_auth(self):
return b'AUTH'
return b"AUTH"
def verify_client_auth(self, data):
return True
def generate_accept(self):
return b'ACCEPT'
return b"ACCEPT"
def get_box_keys(self):
return {
'encrypt_key': b'x' * 32,
'encrypt_nonce': b'x' * 32,
'decrypt_key': b'x' * 32,
'decrypt_nonce': b'x' * 32
"encrypt_key": b"x" * 32,
"encrypt_nonce": b"x" * 32,
"decrypt_key": b"x" * 32,
"decrypt_nonce": b"x" * 32,
}
def clean(self):
@ -68,8 +69,8 @@ def _dummy_boxstream(stream, **kwargs):
def _client_stream_mocker():
reader = AsyncBuffer(b'xxx')
writer = AsyncBuffer(b'xxx')
reader = AsyncBuffer(b"xxx")
writer = AsyncBuffer(b"xxx")
async def _create_mock_streams(host, port):
return reader, writer
@ -78,8 +79,8 @@ def _client_stream_mocker():
def _server_stream_mocker():
reader = AsyncBuffer(b'xxx')
writer = AsyncBuffer(b'xxx')
reader = AsyncBuffer(b"xxx")
writer = AsyncBuffer(b"xxx")
async def _create_mock_server(cb, host, port):
await cb(reader, writer)
@ -90,18 +91,18 @@ def _server_stream_mocker():
@pytest.mark.asyncio
async def test_client(mocker):
reader, writer, _create_mock_streams = _client_stream_mocker()
mocker.patch('asyncio.open_connection', new=_create_mock_streams)
mocker.patch('secret_handshake.boxstream.BoxStream', new=_dummy_boxstream)
mocker.patch('secret_handshake.boxstream.UnboxStream', new=_dummy_boxstream)
mocker.patch("asyncio.open_connection", new=_create_mock_streams)
mocker.patch("secret_handshake.boxstream.BoxStream", new=_dummy_boxstream)
mocker.patch("secret_handshake.boxstream.UnboxStream", new=_dummy_boxstream)
from secret_handshake import SHSClient
client = SHSClient('shop.local', 1111, SigningKey.generate(), os.urandom(32))
client = SHSClient("shop.local", 1111, SigningKey.generate(), os.urandom(32))
client.crypto = DummyCrypto()
await client.open()
reader.append(b'TEST')
assert (await client.read()) == b'TEST'
reader.append(b"TEST")
assert (await client.read()) == b"TEST"
client.disconnect()
@ -116,11 +117,11 @@ async def test_server(mocker):
resolve.set()
reader, writer, _create_mock_server = _server_stream_mocker()
mocker.patch('asyncio.start_server', new=_create_mock_server)
mocker.patch('secret_handshake.boxstream.BoxStream', new=_dummy_boxstream)
mocker.patch('secret_handshake.boxstream.UnboxStream', new=_dummy_boxstream)
mocker.patch("asyncio.start_server", new=_create_mock_server)
mocker.patch("secret_handshake.boxstream.BoxStream", new=_dummy_boxstream)
mocker.patch("secret_handshake.boxstream.UnboxStream", new=_dummy_boxstream)
server = SHSServer('shop.local', 1111, SigningKey.generate(), os.urandom(32))
server = SHSServer("shop.local", 1111, SigningKey.generate(), os.urandom(32))
server.crypto = DummyCrypto()
server.on_connect(_on_connect)

View File

@ -23,14 +23,16 @@ import struct
from io import BytesIO
NONCE_SIZE = 24
MAX_NONCE = (8 * NONCE_SIZE)
MAX_NONCE = 8 * NONCE_SIZE
class AsyncBuffer(BytesIO):
"""Just a BytesIO with an async read method."""
async def read(self, n=None):
v = super(AsyncBuffer, self).read(n)
return v
readexactly = read
def append(self, data):
@ -50,10 +52,10 @@ async def async_comprehend(generator):
def inc_nonce(nonce):
num = bytes_to_long(nonce) + 1
if num > 2 ** MAX_NONCE:
if num > 2**MAX_NONCE:
num = 0
bnum = long_to_bytes(num)
bnum = b'\x00' * (NONCE_SIZE - len(bnum)) + bnum
bnum = b"\x00" * (NONCE_SIZE - len(bnum)) + bnum
return bnum
@ -78,24 +80,24 @@ def long_to_bytes(n, blocksize=0):
blocksize.
"""
# after much testing, this algorithm was deemed to be the fastest
s = b('')
s = b("")
pack = struct.pack
while n > 0:
s = pack('>I', n & 0xffffffff) + s
s = pack(">I", n & 0xFFFFFFFF) + s
n = n >> 32
# strip off leading zeros
for i in range(len(s)):
if s[i] != b('\000')[0]:
if s[i] != b("\000")[0]:
break
else:
# only happens when n == 0
s = b('\000')
s = b("\000")
i = 0
s = s[i:]
# add back some pad bytes. this could be done more efficiently w.r.t. the
# de-padding being done above, but sigh...
if blocksize > 0 and len(s) % blocksize:
s = (blocksize - len(s) % blocksize) * b('\000') + s
s = (blocksize - len(s) % blocksize) * b("\000") + s
return s
@ -108,9 +110,9 @@ def bytes_to_long(s):
unpack = struct.unpack
length = len(s)
if length % 4:
extra = (4 - length % 4)
s = b('\000') * extra + s
extra = 4 - length % 4
s = b("\000") * extra + s
length = length + extra
for i in range(0, length, 4):
acc = (acc << 32) + unpack('>I', s[i:i+4])[0]
acc = (acc << 32) + unpack(">I", s[i : i + 4])[0]
return acc