mirror of
https://git.exozy.me/a/fuwuqi.git
synced 2025-02-16 18:15:18 +00:00
Implement (broken) HTTP signatures
This commit is contained in:
parent
80e5041182
commit
53f0c72117
16 changed files with 319 additions and 11 deletions
|
@ -24,7 +24,7 @@ That wasn't so bad, was it? (sobbing sounds)
|
||||||
|
|
||||||
Alright, time for the real deal. Start up `python main.py` on your server. If you want to customize the port or whatever, just change the source code.
|
Alright, time for the real deal. Start up `python main.py` on your server. If you want to customize the port or whatever, just change the source code.
|
||||||
|
|
||||||
Now on your client device, open up your favorite C2S ActivityPub client... oh wait... there aren't any! Welp, you'll just have to settle for reading the [AP spec](https://www.w3.org/TR/activitypub/) and `curl`ing some homemade JSON. That's rough, buddy.
|
Now on your client device, open up your favorite C2S ActivityPub client... oh wait... there aren't any! Welp, you'll just have to settle for reading the [AP spec](https://www.w3.org/TR/activitypub/), writing some homemade JSON, and figuring out how `python client.py` works.
|
||||||
|
|
||||||
Like and subscribe and enjoy your new "extremely hardcore" ActivityPub server!!! 🎉😎🚀🙃🥳
|
Like and subscribe and enjoy your new "extremely hardcore" ActivityPub server!!! 🎉😎🚀🙃🥳
|
||||||
|
|
||||||
|
|
14
activity.jsonld
Normal file
14
activity.jsonld
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id": "https://0.exozy.me/users/test.outbox/hello-world2",
|
||||||
|
"type": "Create",
|
||||||
|
"actor": "https://0.exozy.me/users/test.jsonld",
|
||||||
|
"object": {
|
||||||
|
"id": "https://0.exozy.me/users/test.statuses/hello-world2",
|
||||||
|
"type": "Note",
|
||||||
|
"attributedTo": "https://0.exozy.me/users/test.jsonld",
|
||||||
|
"inReplyTo": "https://social.exozy.me/@a/109707513227348721",
|
||||||
|
"content": "Hello from fuwuqi! 2",
|
||||||
|
"to": "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
|
}
|
35
client.py
Normal file
35
client.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from base64 import b64encode
|
||||||
|
from email.utils import formatdate
|
||||||
|
from requests import post
|
||||||
|
|
||||||
|
date = formatdate(usegmt=True)
|
||||||
|
|
||||||
|
with open('activity.jsonld', 'rb') as f:
|
||||||
|
activity = f.read()
|
||||||
|
|
||||||
|
digester = hashes.Hash(hashes.SHA256())
|
||||||
|
digester.update(activity)
|
||||||
|
digest = b64encode(digester.finalize()).decode()
|
||||||
|
message = f'(request-target): post /users/a/inbox\nhost: social.exozy.me\ndate: {date}\ndigest: SHA-256={digest}'
|
||||||
|
|
||||||
|
with open('private.pem', 'rb') as f:
|
||||||
|
privkey = serialization.load_pem_private_key(f.read(), None)
|
||||||
|
|
||||||
|
signature = b64encode(privkey.sign(
|
||||||
|
message.encode('utf8'),
|
||||||
|
padding.PKCS1v15(),
|
||||||
|
hashes.SHA256()
|
||||||
|
)).decode()
|
||||||
|
header = f'keyId="https://0.exozy.me/users/test.jsonld#main-key",headers="(request-target) host date digest",signature="{signature}"'
|
||||||
|
|
||||||
|
resp = post('http://localhost:4200/users/test.outbox', headers={
|
||||||
|
'(request-target)': 'post /users/a/inbox',
|
||||||
|
'Host': 'social.exozy.me',
|
||||||
|
'Date': date,
|
||||||
|
'Digest': f'SHA-256={digest}',
|
||||||
|
'Signature': header,
|
||||||
|
}, data=activity)
|
||||||
|
print(resp)
|
||||||
|
print(resp.text)
|
10
main.py
10
main.py
|
@ -1,10 +0,0 @@
|
||||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
class fuwuqi(SimpleHTTPRequestHandler):
|
|
||||||
def do_POST(self):
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text/html')
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write('hi!'.encode('utf-8'))
|
|
||||||
|
|
||||||
HTTPServer(('localhost', 4200), fuwuqi).serve_forever()
|
|
28
private.pem
Normal file
28
private.pem
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCc+fpJFDUlPCoU
|
||||||
|
wUmrsGDExb/hX8dhKMc9u0ov/ePBc5OY4tTPjCvghvUjCIqmyC7PS7JfUuNt7epD
|
||||||
|
OpoJm7mF2gaj64mxqfPrICgtjARN8gUiOpCvcWggZDFgkMQuSFI7yG1s4oqhGJUq
|
||||||
|
yMPnHhBwSRmQsBp5/xZWHTywSoTd8Li4uY6VJq/JxswuZqH1R2GWMVxpVJDuqjH0
|
||||||
|
cW8oXZ6LgGY3ZZ0wZnwk3bSdzQ7GhmkGkwCtGoXSSuMiSEDn/0K8hkRQs9AKFgvo
|
||||||
|
ByYtLSP/JPETzZ4UwEG/J6DwgIxHNxsKg36FWe/dxv/v4HA12hCNmq3REb+GFulz
|
||||||
|
xEq35g71AgMBAAECggEACqnGcS67ZMyfSn/4xP4QYhLRc89MSBLQ5KYeJirCd70l
|
||||||
|
97riMY5i2qKARhbJ8wYNJqK3OqzIQIrqoLzQguTo/NPbI0kYNjvbvbXrqhsP8yAv
|
||||||
|
bhb5W27s36ppWkHAjyj1gRRz2R4obu9bDqggT/Olso2cpsyiTSA2qwyFt1n7MueQ
|
||||||
|
ag/lSYAAnjRfnxnGiBwy+wURxZH+Y9uBhb9E8MZ+W+sUc7l8RDUfYPN9d7NmxfdV
|
||||||
|
x6x+jk+OLpYotj1v6aTfdwBCqpdt2ulMZexTCfjwAIU/XI2uhqaO1dzKI+Ssr/WY
|
||||||
|
j8R7LXV94pI1ZXdwAJ/8aWGllAhj1hpnhacjnr7JAQKBgQDZPj5zJVk7TBoRxQVw
|
||||||
|
VCfmGwPwAkfmGGqn9g41VlKtyG+JhWQ7aAs0gMFwq6QGDEBloAiXQw7z033ZSNTP
|
||||||
|
p61QCePq6fLGEgdsfgjGotEbZ2XDZdB6fdzK+sKJLQtXr8FVrywyNmlX0bk8IRSs
|
||||||
|
hnJXYco/VTLeDiQePO2bjm0UdQKBgQC4+0hR/se6AHgN9449ruHLSOCcC+Y6WQJ1
|
||||||
|
BM+ydk7OnnfDmxzXQMbzri5kwDTBw+Y01avbRkpMZbHtWip9QWL96Kf8D6OUoX6d
|
||||||
|
KJuheKsuJUKmXTPF8YyJiY+KXmx1yz7hu8X/SN955s4BAttIZGQvSIFpy+izzQWc
|
||||||
|
FghcplvAgQKBgQCIkuIN37AOYFSPUU6PBMkkl11NWRG8bSM4Pq9GBuPpjvXX/f06
|
||||||
|
f7lzo3J5E98FUlR1zzs3ZRgUX6RhorDvb1m81Mrtl3Bh51m1cjKwNhHB6aoHQo3j
|
||||||
|
RBc3oJgGR0Q3Ny4TYRIm6yAk7ptGWwG1SLy/hKHyWOymvzsjq2gxgEPBNQKBgEyM
|
||||||
|
6KvOBPdLVGNrS/jo01Yd7Z2GKxuAVEz61bzjys8ksylGmpPVob+cGGTnSa3aFP1O
|
||||||
|
Y1VV7E9bUluIEcdN9NpgmovsKOTMRCpjcKxM1II/NyrDrTZANMmCHN3FH5tLpdUi
|
||||||
|
sNhpXtoCksPGW9rEeNU8axnOIZmuwaCLWaCF07iBAoGAX494Slca2EnERTtcX9jq
|
||||||
|
SM/srMTz+cm5zNA3npMj1eZ7zNglD5tZapc3f5OErrDdZz5wlmIo/eheQxb8TMQW
|
||||||
|
F5goYHwpq6bghHlbAxK2I353s8Q8WriLTvAYouHfEqd14AS42OXD44CBwU0V5rRV
|
||||||
|
MmlfOVBGqPVHLQK/tOebZIw=
|
||||||
|
-----END PRIVATE KEY-----
|
9
public.pem
Normal file
9
public.pem
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnPn6SRQ1JTwqFMFJq7Bg
|
||||||
|
xMW/4V/HYSjHPbtKL/3jwXOTmOLUz4wr4Ib1IwiKpsguz0uyX1Ljbe3qQzqaCZu5
|
||||||
|
hdoGo+uJsanz6yAoLYwETfIFIjqQr3FoIGQxYJDELkhSO8htbOKKoRiVKsjD5x4Q
|
||||||
|
cEkZkLAaef8WVh08sEqE3fC4uLmOlSavycbMLmah9UdhljFcaVSQ7qox9HFvKF2e
|
||||||
|
i4BmN2WdMGZ8JN20nc0OxoZpBpMArRqF0krjIkhA5/9CvIZEULPQChYL6AcmLS0j
|
||||||
|
/yTxE82eFMBBvyeg8ICMRzcbCoN+hVnv3cb/7+BwNdoQjZqt0RG/hhbpc8RKt+YO
|
||||||
|
9QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
87
server.py
Normal file
87
server.py
Normal file
|
@ -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()
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1"
|
||||||
|
],
|
||||||
|
"id": "https://0.exozy.me/users/test.jsonld",
|
||||||
|
"type": "Person",
|
||||||
|
"preferredUsername": "test",
|
||||||
|
"name": "Billiam Wender",
|
||||||
|
"inbox": "https://0.exozy.me/users/test.inbox",
|
||||||
|
"outbox": "https://0.exozy.me/users/test.outbox",
|
||||||
|
"followers": "https://0.exozy.me/users/test.followers",
|
||||||
|
"following": "https://0.exozy.me/users/test.following",
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"url": "https://0.exozy.me/users/test.png"
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://0.exozy.me/users/test.jsonld#main-key",
|
||||||
|
"owner": "https://0.exozy.me/users/test.jsonld",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnPn6SRQ1JTwqFMFJq7Bg\nxMW/4V/HYSjHPbtKL/3jwXOTmOLUz4wr4Ib1IwiKpsguz0uyX1Ljbe3qQzqaCZu5\nhdoGo+uJsanz6yAoLYwETfIFIjqQr3FoIGQxYJDELkhSO8htbOKKoRiVKsjD5x4Q\ncEkZkLAaef8WVh08sEqE3fC4uLmOlSavycbMLmah9UdhljFcaVSQ7qox9HFvKF2e\ni4BmN2WdMGZ8JN20nc0OxoZpBpMArRqF0krjIkhA5/9CvIZEULPQChYL6AcmLS0j\n/yTxE82eFMBBvyeg8ICMRzcbCoN+hVnv3cb/7+BwNdoQjZqt0RG/hhbpc8RKt+YO\n9QIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
}
|
||||||
|
}
|
98
users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key
Normal file
98
users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key
Normal file
File diff suppressed because one or more lines are too long
6
users/test.followers
Normal file
6
users/test.followers
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type": "OrderedCollection",
|
||||||
|
"totalItems": 0,
|
||||||
|
"orderedItems": [],
|
||||||
|
}
|
6
users/test.following
Normal file
6
users/test.following
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type": "OrderedCollection",
|
||||||
|
"totalItems": 0,
|
||||||
|
"orderedItems": [],
|
||||||
|
}
|
6
users/test.inbox
Normal file
6
users/test.inbox
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type": "OrderedCollection",
|
||||||
|
"totalItems": 0,
|
||||||
|
"orderedItems": [],
|
||||||
|
}
|
|
@ -9,6 +9,8 @@
|
||||||
"name": "Billiam Wender",
|
"name": "Billiam Wender",
|
||||||
"inbox": "https://0.exozy.me/users/test.inbox",
|
"inbox": "https://0.exozy.me/users/test.inbox",
|
||||||
"outbox": "https://0.exozy.me/users/test.outbox",
|
"outbox": "https://0.exozy.me/users/test.outbox",
|
||||||
|
"followers": "https://0.exozy.me/users/test.followers",
|
||||||
|
"following": "https://0.exozy.me/users/test.following",
|
||||||
"icon": {
|
"icon": {
|
||||||
"type": "Image",
|
"type": "Image",
|
||||||
"mediaType": "image/png",
|
"mediaType": "image/png",
|
||||||
|
|
1
users/test.outbox
Normal file
1
users/test.outbox
Normal file
File diff suppressed because one or more lines are too long
1
users/test.statuses/hello-world
Normal file
1
users/test.statuses/hello-world
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}
|
1
users/test.statuses/hello-world2
Normal file
1
users/test.statuses/hello-world2
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"id": "https://0.exozy.me/users/test.statuses/hello-world2", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi! 2", "to": "https://www.w3.org/ns/activitystreams#Public"}
|
Loading…
Reference in a new issue