diff --git a/fedireads/api.py b/fedireads/api.py index 0aae9db5..4c342e74 100644 --- a/fedireads/api.py +++ b/fedireads/api.py @@ -79,9 +79,12 @@ def sign_and_send(sender, action, destination): ''' crpyto whatever and http junk ''' inbox_fragment = sender.inbox.replace('https://%s' % DOMAIN, '') now = datetime.utcnow().isoformat() - message_to_sign = '''(request-target): post %s -host: https://%s -date: %s''' % (inbox_fragment, DOMAIN, now) + signature_headers = [ + '(request-target): post %s' % inbox_fragment, + 'host: https://%s' % DOMAIN, + 'date: %s' % now + ] + message_to_sign = '\n'.join(signature_headers) signer = pkcs1_15.new(RSA.import_key(sender.private_key)) signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8'))) @@ -89,7 +92,7 @@ date: %s''' % (inbox_fragment, DOMAIN, now) 'keyId': '%s#main-key' % sender.actor, 'algorithm': 'rsa-sha256', 'headers': '(request-target) host date', - 'signature': b64encode(signed_message), + 'signature': b64encode(signed_message).decode('utf8'), } signature = ','.join('%s="%s"' % (k, v) for (k, v) in signature.items()) @@ -99,7 +102,7 @@ date: %s''' % (inbox_fragment, DOMAIN, now) headers={ 'Date': now, 'Signature': signature, - 'Host': DOMAIN, + 'Host': 'https://%s' % DOMAIN, }, ) if not response.ok: diff --git a/fedireads/incoming.py b/fedireads/incoming.py index f9052d07..402cea04 100644 --- a/fedireads/incoming.py +++ b/fedireads/incoming.py @@ -1,8 +1,13 @@ ''' handles all of the activity coming in to the server ''' +from base64 import b64decode +from Crypto.PublicKey import RSA +from Crypto.Signature import pkcs1_15 +from Crypto.Hash import SHA256 from django.http import HttpResponse, HttpResponseBadRequest, \ HttpResponseNotFound, JsonResponse from django.views.decorators.csrf import csrf_exempt import json +import requests from uuid import uuid4 from fedireads import models @@ -32,26 +37,56 @@ def webfinger(request): }) -''' -def host_meta(request): - import pdb;pdb.set_trace() -''' - - @csrf_exempt def shared_inbox(request): ''' incoming activitypub events ''' - # TODO: this is just a dupe of inbox but there's gotta be a reason?? + # TODO: should this be functionally different from the non-shared inbox?? if request.method == 'GET': return HttpResponseNotFound() - # TODO: RSA key verification - try: activity = json.loads(request.body) except json.decoder.JSONDecodeError: return HttpResponseBadRequest + # verify rsa signature + signature_header = request.headers['Signature'].split(',') + signature_dict = {} + for pair in signature_header: + k, v = pair.split('=', 1) + v = v.replace('"', '') + signature_dict[k] = v + + key_id = signature_dict['keyId'] + headers = signature_dict['headers'] + signature = b64decode(signature_dict['signature']) + + response = requests.get( + key_id, + headers={'Accept': 'application/activity+json'} + ) + if not response.ok: + response.raise_for_status() + + actor = response.json() + key = RSA.import_key(actor['publicKey']['publicKeyPem']) + + comparison_string = [] + for signed_header_name in headers.split(' '): + if signed_header_name == '(request-target)': + comparison_string.append('(request-target): post %s' % request.path) + else: + comparison_string.append('%s: %s' % ( + signed_header_name, + request.headers[signed_header_name] + )) + comparison_string = '\n'.join(comparison_string) + + signer = pkcs1_15.new(key) + digest = SHA256.new() + digest.update(comparison_string.encode()) + signer.verify(digest, signature) + if activity['type'] == 'Add': return handle_incoming_shelve(activity) @@ -61,37 +96,18 @@ def shared_inbox(request): if activity['type'] == 'Create': return handle_incoming_create(activity) - return HttpResponse() + return HttpResponseNotFound() @csrf_exempt def inbox(request, username): ''' incoming activitypub events ''' - if request.method == 'GET': - return HttpResponseNotFound() - - # TODO: RSA key verification - - try: - activity = json.loads(request.body) - except json.decoder.JSONDecodeError: - return HttpResponseBadRequest - # TODO: should do some kind of checking if the user accepts - # this action from the sender + # this action from the sender probably? idk # but this will just throw an error if the user doesn't exist I guess models.User.objects.get(localname=username) - if activity['type'] == 'Add': - return handle_incoming_shelve(activity) - - if activity['type'] == 'Follow': - return handle_incoming_follow(activity) - - if activity['type'] == 'Create': - return handle_incoming_create(activity) - - return HttpResponse() + return shared_inbox(request) @csrf_exempt diff --git a/fedireads/migrations/0001_initial.py b/fedireads/migrations/0001_initial.py index 6402ab2d..7e45870b 100644 --- a/fedireads/migrations/0001_initial.py +++ b/fedireads/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.2 on 2020-01-29 19:58 +# Generated by Django 3.0.2 on 2020-01-30 02:26 from django.conf import settings import django.contrib.auth.models