6 from Crypto.PublicKey import RSA
7 from Crypto.Hash import SHA, SHA256, SHA512
8 from Crypto.Signature import PKCS1_v1_5
10 from async_lru import alru_cache
12 from .remote_actor import fetch_actor
22 def split_signature(sig):
23 default = {"headers": "date"}
25 sig = sig.strip().split(',')
28 k, _, v = chunk.partition('=')
32 default['headers'] = default['headers'].split()
36 def build_signing_string(headers, used_headers):
37 return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers))
40 def sign_headers(headers, key, key_id):
41 headers = {x.lower(): y for x, y in headers.items()}
42 used_headers = headers.keys()
45 'algorithm': 'rsa-sha256',
46 'headers': ' '.join(used_headers)
48 sigstring = build_signing_string(headers, used_headers)
50 pkcs = PKCS1_v1_5.new(key)
52 h.update(sigstring.encode('ascii'))
53 sigdata = pkcs.sign(h)
55 sigdata = base64.b64encode(sigdata)
56 sig['signature'] = sigdata.decode('ascii')
58 chunks = ['{}="{}"'.format(k, v) for k, v in sig.items()]
59 return ','.join(chunks)
62 @alru_cache(maxsize=16384)
63 async def fetch_actor_key(actor):
64 actor_data = await fetch_actor(actor)
69 if 'publicKey' not in actor_data:
72 if 'publicKeyPem' not in actor_data['publicKey']:
75 return RSA.importKey(actor_data['publicKey']['publicKeyPem'])
78 async def validate(actor, request):
79 pubkey = await fetch_actor_key(actor)
83 logging.debug('actor key: %r', pubkey)
85 headers = request.headers.copy()
86 headers['(request-target)'] = ' '.join([request.method.lower(), request.path])
88 sig = split_signature(headers['signature'])
89 logging.debug('sigdata: %r', sig)
91 sigstring = build_signing_string(headers, sig['headers'])
92 logging.debug('sigstring: %r', sigstring)
94 sign_alg, _, hash_alg = sig['algorithm'].partition('-')
95 logging.debug('sign alg: %r, hash alg: %r', sign_alg, hash_alg)
97 sigdata = base64.b64decode(sig['signature'])
99 pkcs = PKCS1_v1_5.new(pubkey)
100 h = HASHES[hash_alg].new()
101 h.update(sigstring.encode('ascii'))
102 result = pkcs.verify(h, sigdata)
104 request['validated'] = result
106 logging.debug('validates? %r', result)
110 async def http_signatures_middleware(app, handler):
111 async def http_signatures_handler(request):
112 request['validated'] = False
114 if 'signature' in request.headers:
115 data = await request.json()
116 if 'actor' not in data:
117 raise aiohttp.web.HTTPUnauthorized(body='signature check failed, no actor in message')
119 actor = data["actor"]
120 if not (await validate(actor, request)):
121 logging.info('Signature validation failed for: %r', actor)
122 raise aiohttp.web.HTTPUnauthorized(body='signature check failed, signature did not match key')
124 return (await handler(request))
126 return (await handler(request))
128 return http_signatures_handler