mirror of
https://git.exozy.me/a/fuwuqi.git
synced 2024-11-25 13:01:11 +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.
|
||||
|
||||
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!!! 🎉😎🚀🙃🥳
|
||||
|
||||
|
|
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",
|
||||
"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",
|
||||
|
|
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