From aa7365b71fa96da8808a462d9afdc664e8e2d41f Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 24 Mar 2018 15:20:15 +0100 Subject: [PATCH] Basic logic for signing/verifying requests --- api/config/settings/common.py | 1 + api/funkwhale_api/federation/__init__.py | 0 api/funkwhale_api/federation/factories.py | 30 ++++++++++++++++++++ api/funkwhale_api/federation/signing.py | 21 ++++++++++++++ api/requirements/base.txt | 2 ++ api/tests/conftest.py | 12 ++++++-- api/tests/federation/test_signing.py | 34 +++++++++++++++++++++++ 7 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 api/funkwhale_api/federation/__init__.py create mode 100644 api/funkwhale_api/federation/factories.py create mode 100644 api/funkwhale_api/federation/signing.py create mode 100644 api/tests/federation/test_signing.py diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 077566d1c..1144e8891 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -89,6 +89,7 @@ LOCAL_APPS = ( 'funkwhale_api.music', 'funkwhale_api.requests', 'funkwhale_api.favorites', + 'funkwhale_api.federation', 'funkwhale_api.radios', 'funkwhale_api.history', 'funkwhale_api.playlists', diff --git a/api/funkwhale_api/federation/__init__.py b/api/funkwhale_api/federation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py new file mode 100644 index 000000000..29aed4baf --- /dev/null +++ b/api/funkwhale_api/federation/factories.py @@ -0,0 +1,30 @@ +import factory +import requests +import requests_http_signature + +from funkwhale_api.factories import registry + +from . import signing + + +registry.register(signing.get_key_pair, name='federation.KeyPair') + + +@registry.register(name='federation.SignatureAuth') +class SignatureAuthFactory(factory.Factory): + algorithm = 'rsa-sha256' + key = factory.LazyFunction(lambda: signing.get_key_pair()[0]) + key_id = factory.Faker('url') + + class Meta: + model = requests_http_signature.HTTPSignatureAuth + + +@registry.register(name='federation.SignedRequest') +class SignedRequestFactory(factory.Factory): + url = factory.Faker('url') + method = 'get' + auth = factory.SubFactory(SignatureAuthFactory) + + class Meta: + model = requests.Request diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py new file mode 100644 index 000000000..626dfbe86 --- /dev/null +++ b/api/funkwhale_api/federation/signing.py @@ -0,0 +1,21 @@ +from cryptography.hazmat.primitives import serialization as crypto_serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.backends import default_backend as crypto_default_backend + + +def get_key_pair(size=2048): + key = rsa.generate_private_key( + backend=crypto_default_backend(), + public_exponent=65537, + key_size=size + ) + private_key = key.private_bytes( + crypto_serialization.Encoding.PEM, + crypto_serialization.PrivateFormat.PKCS8, + crypto_serialization.NoEncryption()) + public_key = key.public_key().public_bytes( + crypto_serialization.Encoding.PEM, + crypto_serialization.PublicFormat.PKCS1 + ) + + return private_key, public_key diff --git a/api/requirements/base.txt b/api/requirements/base.txt index efcc4eea4..d4408dbdc 100644 --- a/api/requirements/base.txt +++ b/api/requirements/base.txt @@ -60,3 +60,5 @@ channels_redis>=2.1,<2.2 django-cacheops>=4,<4.1 daphne==2.0.4 +cryptography>=2,<3 +requests-http-signature==0.0.3 diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 62bc5ada6..d5b3b24f4 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -31,7 +31,11 @@ def cache(): def factories(db): from funkwhale_api import factories for v in factories.registry.values(): - v._meta.strategy = factory.CREATE_STRATEGY + try: + v._meta.strategy = factory.CREATE_STRATEGY + except AttributeError: + # probably not a class based factory + pass yield factories.registry @@ -39,7 +43,11 @@ def factories(db): def nodb_factories(): from funkwhale_api import factories for v in factories.registry.values(): - v._meta.strategy = factory.BUILD_STRATEGY + try: + v._meta.strategy = factory.BUILD_STRATEGY + except AttributeError: + # probably not a class based factory + pass yield factories.registry diff --git a/api/tests/federation/test_signing.py b/api/tests/federation/test_signing.py new file mode 100644 index 000000000..712f2bd35 --- /dev/null +++ b/api/tests/federation/test_signing.py @@ -0,0 +1,34 @@ +import cryptography.exceptions +import io +import pytest +import requests_http_signature + +from funkwhale_api.federation import signing + + +def test_can_sign_and_verify_request(factories): + private, public = factories['federation.KeyPair']() + auth = factories['federation.SignatureAuth'](key=private) + request = factories['federation.SignedRequest']( + auth=auth + ) + prepared_request = request.prepare() + assert 'date' in prepared_request.headers + assert 'authorization' in prepared_request.headers + assert prepared_request.headers['authorization'].startswith('Signature') + assert requests_http_signature.HTTPSignatureAuth.verify( + prepared_request, + key_resolver=lambda **kwargs: public + ) is None + + +def test_verify_fails_with_wrong_key(factories): + wrong_private, wrong_public = factories['federation.KeyPair']() + request = factories['federation.SignedRequest']() + prepared_request = request.prepare() + + with pytest.raises(cryptography.exceptions.InvalidSignature): + requests_http_signature.HTTPSignatureAuth.verify( + prepared_request, + key_resolver=lambda **kwargs: wrong_public + )