Check all signatures are signed by the right actor.

This commit is contained in:
Adam Kelly 2020-05-13 11:40:57 +01:00
parent 10efe4d1b4
commit 2db4da4061
2 changed files with 36 additions and 28 deletions

View File

@ -1,6 +1,8 @@
''' handles all of the activity coming in to the server ''' ''' handles all of the activity coming in to the server '''
import json import json
from base64 import b64decode from base64 import b64decode
from urllib.parse import urldefrag
from Crypto.Hash import SHA256 from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15 from Crypto.Signature import pkcs1_15
@ -46,7 +48,7 @@ def shared_inbox(request):
return HttpResponseBadRequest() return HttpResponseBadRequest()
try: try:
verify_signature(request) verify_signature(activity.get('actor'), request)
except ValueError: except ValueError:
return HttpResponse(status=401) return HttpResponse(status=401)
@ -82,24 +84,24 @@ def shared_inbox(request):
return HttpResponse() return HttpResponse()
def get_public_key(key_id): def get_public_key(key_actor):
# TODO Use the anchor - actors can have multiple keys?
key_actor = key_id.split('#', 1)[0]
try: try:
public_key = models.User.objects.get(actor=key_actor).public_key user = models.User.objects.get(actor=key_actor)
public_key = user.public_key
actor = user.actor
except models.User.DoesNotExist: except models.User.DoesNotExist:
response = requests.get( response = requests.get(
key_id, key_actor,
headers={'Accept': 'application/activity+json'} headers={'Accept': 'application/activity+json'}
) )
if not response.ok: if not response.ok:
raise ValueError('Could not load public key') raise ValueError('Could not load public key')
actor = response.json() user_data = response.json()
public_key = actor['publicKey']['publicKeyPem'] public_key = user_data['publicKey']['publicKeyPem']
return RSA.import_key(public_key) return RSA.import_key(public_key)
def verify_signature(request): def verify_signature(required_actor, request):
''' verify rsa signature ''' ''' verify rsa signature '''
signature_dict = {} signature_dict = {}
for pair in request.headers['Signature'].split(','): for pair in request.headers['Signature'].split(','):
@ -114,7 +116,13 @@ def verify_signature(request):
except KeyError: except KeyError:
raise ValueError('Invalid auth header') raise ValueError('Invalid auth header')
key = get_public_key(key_id) # TODO Use the fragment - actors can have multiple keys?
key_actor = urldefrag(key_id).url
if key_actor != required_actor:
raise ValueError("Wrong actor created signature.")
key = get_public_key(key_actor)
comparison_string = [] comparison_string = []
for signed_header_name in headers.split(' '): for signed_header_name in headers.split(' '):
@ -134,8 +142,6 @@ def verify_signature(request):
# raises a ValueError if it fails # raises a ValueError if it fails
signer.verify(digest, signature) signer.verify(digest, signature)
return True
@app.task @app.task
def handle_follow(activity): def handle_follow(activity):

View File

@ -14,23 +14,14 @@ class Signature(TestCase):
self.rat = User.objects.create_user('rat', 'rat@example.com', '') self.rat = User.objects.create_user('rat', 'rat@example.com', '')
self.cat = User.objects.create_user('cat', 'cat@example.com', '') self.cat = User.objects.create_user('cat', 'cat@example.com', '')
def test_wrong_signature(self): def send_follow(self, signature, now):
''' All messages must be signed by the right actor.
(cat cannot sign messages on behalf of mouse)
'''
activity = get_follow_request(
self.mouse,
self.rat,
)
now = http_date()
signature = make_signature(self.cat, self.rat.inbox, now)
c = Client() c = Client()
response = c.post( return c.post(
urlsplit(self.rat.inbox).path, urlsplit(self.rat.inbox).path,
data=activity, data=get_follow_request(
self.mouse,
self.rat,
),
content_type='application/json', content_type='application/json',
**{ **{
'HTTP_DATE': now, 'HTTP_DATE': now,
@ -40,4 +31,15 @@ class Signature(TestCase):
} }
) )
assert response.status_code == 401 def test_correct_signature(self):
now = http_date()
signature = make_signature(self.mouse, self.rat.inbox, now)
return self.send_follow(signature, now).status_code == 200
def test_wrong_signature(self):
''' Messages must be signed by the right actor.
(cat cannot sign messages on behalf of mouse)
'''
now = http_date()
signature = make_signature(self.cat, self.rat.inbox, now)
assert self.send_follow(signature, now).status_code == 401