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:
parent
bbfc137ca8
commit
93f2d329a4
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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
78
poetry.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -21,4 +21,4 @@
|
||||
|
||||
from .network import SHSClient, SHSServer
|
||||
|
||||
__all__ = ('SHSClient', 'SHSServer')
|
||||
__all__ = ("SHSClient", "SHSServer")
|
||||
|
@ -8,7 +8,7 @@ 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):
|
||||
@ -17,12 +17,12 @@ def get_stream_pair(reader, writer, **kwargs):
|
||||
:return: (:class:`secret_handshake.boxstream.UnboxStream`,
|
||||
: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:])
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
@ -53,7 +55,7 @@ def inc_nonce(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
|
||||
|
Loading…
Reference in New Issue
Block a user