paws/paws/signature.py
2020-01-13 08:10:48 -05:00

158 lines
3.6 KiB
Python

import aiohttp
import aiohttp.web
import binascii
import base64
import json
import logging
from aiohttp.http_exceptions import *
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA, SHA256, SHA512
from Crypto.Signature import PKCS1_v1_5
from .config import MASTOCONFIG, VERSION
class cache:
from .cache import LRUCache, TTLCache
messages = LRUCache()
actors = TTLCache()
keys = LRUCache()
sigstrings = LRUCache()
def pass_hash():
password_hash = SHA512.new(NET['api_pass'].encode('UTF-8'))
return password_hash.hexdigest()
HASHES = {
'sha1': SHA,
'sha256': SHA256,
'sha512': SHA512
}
def split_signature(sig):
default = {"headers": "date"}
sig = sig.strip().split(',')
for chunk in sig:
k, _, v = chunk.partition('=')
v = v.strip('\"')
default[k] = v
default['headers'] = default['headers'].split()
return default
def build_signing_string(headers, used_headers):
return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers))
def sign_signing_string(sigstring, key):
if sigstring not in cache.sigstrings.items:
pkcs = PKCS1_v1_5.new(key)
h = SHA256.new()
h.update(sigstring.encode('ascii'))
sigdata = pkcs.sign(h)
sigdata = base64.b64encode(sigdata)
cache.sigstrings.store(sigstring, sigdata.decode('ascii'))
return cache.sigstrings.fetch(sigstring)
def sign_headers(headers, key, key_id):
headers = {x.lower(): y for x, y in headers.items()}
used_headers = headers.keys()
sig = {
'keyId': key_id,
'algorithm': 'rsa-sha256',
'headers': ' '.join(used_headers)
}
sigstring = build_signing_string(headers, used_headers)
sig['signature'] = sign_signing_string(sigstring, key)
chunks = ['{}="{}"'.format(k, v) for k, v in sig.items()]
return ','.join(chunks)
async def fetch_actor(uri, force=False):
domain = MASTOCONFIG["domain"]
try:
headers = {
'Accept': 'application/activity+json',
'User-Agent': f'MAW/{VERSION}; https://{domain}'
}
async with aiohttp.ClientSession() as session:
async with session.get(uri, headers=headers) as resp:
if resp.status != 200:
print(await resp.read())
return
data = await resp.json(encoding='utf-8')
return data
except Exception as e:
logging.info('Caught %r while fetching actor %r.', e, uri)
return None
async def fetch_actor_key(actor):
if actor not in cache.keys.items:
actor_data = await fetch_actor(actor)
if not actor_data:
logging.debug('Failed to fetch actor')
return None
if 'publicKey' not in actor_data:
logging.debug('publicKey not in actor')
return None
if 'publicKeyPem' not in actor_data['publicKey']:
logging.debug('Missing pubkey in actor')
return None
cache.keys.store(actor, actor_data['publicKey']['publicKeyPem'])
return RSA.importKey(cache.keys.fetch(actor))
async def validate(actor, request):
pubkey = await fetch_actor_key(actor)
if not pubkey:
logging.debug(f'Failed to fetch pubkey for actor: {actor}')
return False
logging.debug(f'actor key: {pubkey}')
headers = request.headers.copy()
headers['(request-target)'] = ' '.join([request.method.lower(), request.path])
sig = split_signature(headers['signature'])
logging.debug(f'sigdata: {sig}')
sigstring = build_signing_string(headers, sig['headers'])
logging.debug(f'sigstring: {sigstring}')
sign_alg, _, hash_alg = sig['algorithm'].partition('-')
logging.debug(f'sign alg: {sign_alg}, hash alg: {hash_alg}')
sigdata = base64.b64decode(sig['signature'])
pkcs = PKCS1_v1_5.new(pubkey)
h = HASHES[hash_alg].new()
h.update(sigstring.encode('ascii'))
result = pkcs.verify(h, sigdata)
request['validated'] = result
logging.debug(f'validates? {result}')
return result