diff --git a/api/funkwhale_api/federation/exceptions.py b/api/funkwhale_api/federation/exceptions.py new file mode 100644 index 000000000..96fd24a7e --- /dev/null +++ b/api/funkwhale_api/federation/exceptions.py @@ -0,0 +1,4 @@ + + +class MalformedPayload(ValueError): + pass diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py index 29aed4baf..f5d612b0d 100644 --- a/api/funkwhale_api/federation/factories.py +++ b/api/funkwhale_api/federation/factories.py @@ -4,16 +4,16 @@ import requests_http_signature from funkwhale_api.factories import registry -from . import signing +from . import keys -registry.register(signing.get_key_pair, name='federation.KeyPair') +registry.register(keys.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 = factory.LazyFunction(lambda: keys.get_key_pair()[0]) key_id = factory.Faker('url') class Meta: diff --git a/api/funkwhale_api/federation/keys.py b/api/funkwhale_api/federation/keys.py new file mode 100644 index 000000000..432560ef7 --- /dev/null +++ b/api/funkwhale_api/federation/keys.py @@ -0,0 +1,43 @@ +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 + +import requests + +from . import exceptions + + +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 + + +def get_public_key(actor_url): + """ + Given an actor_url, request it and extract publicKey data from + the response payload. + """ + response = requests.get(actor_url) + response.raise_for_status() + payload = response.json() + try: + return { + 'public_key_pem': payload['publicKey']['publicKeyPem'], + 'id': payload['publicKey']['id'], + 'owner': payload['publicKey']['owner'], + } + except KeyError: + raise exceptions.MalformedPayload(str(payload)) diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py index e8d79097c..87ac82bac 100644 --- a/api/funkwhale_api/federation/signing.py +++ b/api/funkwhale_api/federation/signing.py @@ -1,28 +1,6 @@ import requests import requests_http_signature -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 - def verify(request, public_key): return requests_http_signature.HTTPSignatureAuth.verify( diff --git a/api/tests/federation/test_keys.py b/api/tests/federation/test_keys.py new file mode 100644 index 000000000..1c30c30b1 --- /dev/null +++ b/api/tests/federation/test_keys.py @@ -0,0 +1,16 @@ +from funkwhale_api.federation import keys + + +def test_public_key_fetching(r_mock): + payload = { + 'id': 'https://actor.mock/users/actor#main-key', + 'owner': 'test', + 'publicKeyPem': 'test_pem', + } + actor = 'https://actor.mock/' + r_mock.get(actor, json={'publicKey': payload}) + r = keys.get_public_key(actor) + + assert r['id'] == payload['id'] + assert r['owner'] == payload['owner'] + assert r['public_key_pem'] == payload['publicKeyPem'] diff --git a/api/tests/federation/test_signing.py b/api/tests/federation/test_signing.py index 5187faa52..dc678f749 100644 --- a/api/tests/federation/test_signing.py +++ b/api/tests/federation/test_signing.py @@ -4,6 +4,7 @@ import pytest import requests_http_signature from funkwhale_api.federation import signing +from funkwhale_api.federation import keys def test_can_sign_and_verify_request(factories): @@ -45,7 +46,7 @@ def test_verify_fails_with_wrong_key(factories): def test_can_verify_django_request(factories, api_request): - private_key, public_key = signing.get_key_pair() + private_key, public_key = keys.get_key_pair() signed_request = factories['federation.SignedRequest']( auth__key=private_key ) @@ -61,7 +62,7 @@ def test_can_verify_django_request(factories, api_request): def test_can_verify_django_request_digest(factories, api_request): - private_key, public_key = signing.get_key_pair() + private_key, public_key = keys.get_key_pair() signed_request = factories['federation.SignedRequest']( auth__key=private_key, method='post', @@ -81,7 +82,7 @@ def test_can_verify_django_request_digest(factories, api_request): def test_can_verify_django_request_digest_failure(factories, api_request): - private_key, public_key = signing.get_key_pair() + private_key, public_key = keys.get_key_pair() signed_request = factories['federation.SignedRequest']( auth__key=private_key, method='post', @@ -102,7 +103,7 @@ def test_can_verify_django_request_digest_failure(factories, api_request): def test_can_verify_django_request_failure(factories, api_request): - private_key, public_key = signing.get_key_pair() + private_key, public_key = keys.get_key_pair() signed_request = factories['federation.SignedRequest']( auth__key=private_key )