aboutsummaryrefslogtreecommitdiff
path: root/server.py
diff options
context:
space:
mode:
authorAnthony Wang2023-01-18 05:01:59 +0000
committerAnthony Wang2023-01-18 05:01:59 +0000
commit53f0c72117894c389e6e4c2990f802486eff330e (patch)
treee9247d13e9cd4f9fb0c9bbfeaa971a7edde5766c /server.py
parent80e5041182782ee9dea5e9aea063c846e168df01 (diff)
Implement (broken) HTTP signatures
Diffstat (limited to 'server.py')
-rw-r--r--server.py87
1 files changed, 87 insertions, 0 deletions
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..a68efc9
--- /dev/null
+++ b/server.py
@@ -0,0 +1,87 @@
+from base64 import b64decode
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from json import dump, load, loads
+from re import search
+from requests import get, post
+from os.path import isfile
+from urllib.parse import quote_plus
+
+
+def collection_append(file, item):
+ with open(file) as f:
+ collection = load(f)
+ collection['totalItems'] += 1
+ collection['orderedItems'].append(item)
+ with open(file, 'w') as f:
+ dump(collection, f)
+
+
+class fuwuqi(SimpleHTTPRequestHandler):
+ def do_POST(self):
+ body = self.rfile.read(int(self.headers['Content-Length']))
+ activity = loads(body)
+ print(activity)
+ print(self.headers)
+ print(self.path)
+
+ # Get actor public key
+ keyid = search('keyId="(.*?)"', self.headers['Signature']).group(1)
+ actorfile = f'users/{quote_plus(keyid)}'
+ if not isfile(actorfile):
+ with open(actorfile, 'w') as f:
+ f.write(get(keyid).text)
+ with open(actorfile) as f:
+ pubkeypem = load(f)['publicKey']['publicKeyPem'].encode('utf8')
+ pubkey = serialization.load_pem_public_key(pubkeypem, None)
+
+ # Assemble headers
+ headers = search('headers="(.*?)"', self.headers['Signature']).group(1)
+ message = ''
+ for header in headers.split():
+ headerval = self.headers[header]
+ message += f'{header}: {headerval}\n'
+
+ # Verify HTTP signature
+ signature = search('signature="(.*?)"', self.headers['Signature']).group(1)
+ pubkey.verify(
+ b64decode(signature),
+ message[:-1].encode('utf8'),
+ padding.PKCS1v15(),
+ hashes.SHA256()
+ )
+
+ # Make sure activity doer matches HTTP signature
+ actor = keyid.removesuffix('#main-key')
+ if 'actor' in activity and activity['actor'] != actor:
+ self.send_response(401)
+ return
+ if 'attributedTo' in activity and activity['attributedTo'] != actor:
+ self.send_response(401)
+ return
+
+ username = search('^/users/(.*)\.(in|out)box$', self.path).group(1)
+ if self.path.endswith('inbox'):
+ # S2S
+ id = activity['id'].split('/')[-1]
+ with open(f'users/{username}/{id}', 'w') as f:
+ dump(activity, f)
+ elif self.path.endswith('outbox'):
+ # C2S
+ collection_append(f'users/{username}.outbox', activity)
+
+ if activity['type'] == 'Create':
+ id = activity['id'].split('/')[-1]
+ with open(f'users/{username}.statuses/{id}', 'w') as f:
+ dump(activity['object'], f)
+ print(self.headers)
+ print(body)
+ resp = post('https://social.exozy.me/inbox', headers=self.headers, data=body)
+ print(resp)
+ print(resp.text)
+
+ self.send_response(200)
+
+
+ThreadingHTTPServer(('localhost', 4200), fuwuqi).serve_forever()