Merge branch 'follows' into 'master'
[relay.git/.git] / relay / http_signatures.py
index 0965cfc..1093f5e 100644 (file)
@@ -7,6 +7,9 @@ from Crypto.PublicKey import RSA
 from Crypto.Hash import SHA, SHA256, SHA512
 from Crypto.Signature import PKCS1_v1_5
 
+from cachetools import LFUCache
+from async_lru import alru_cache
+
 from .remote_actor import fetch_actor
 
 
@@ -35,6 +38,34 @@ def build_signing_string(headers, used_headers):
     return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers))
 
 
+SIGSTRING_CACHE = LFUCache(1024)
+
+def sign_signing_string(sigstring, key):
+    if sigstring in SIGSTRING_CACHE:
+        return SIGSTRING_CACHE[sigstring]
+
+    pkcs = PKCS1_v1_5.new(key)
+    h = SHA256.new()
+    h.update(sigstring.encode('ascii'))
+    sigdata = pkcs.sign(h)
+
+    sigdata = base64.b64encode(sigdata)
+    SIGSTRING_CACHE[sigstring] = sigdata.decode('ascii')
+
+    return SIGSTRING_CACHE[sigstring]
+
+
+def generate_body_digest(body):
+    bodyhash = SIGSTRING_CACHE.get(body)
+
+    if not bodyhash:
+        h = SHA256.new(body.encode('utf-8'))
+        bodyhash = base64.b64encode(h.digest()).decode('utf-8')
+        SIGSTRING_CACHE[body] = bodyhash
+
+    return bodyhash
+
+
 def sign_headers(headers, key, key_id):
     headers = {x.lower(): y for x, y in headers.items()}
     used_headers = headers.keys()
@@ -44,19 +75,13 @@ def sign_headers(headers, key, key_id):
         'headers': ' '.join(used_headers)
     }
     sigstring = build_signing_string(headers, used_headers)
-
-    pkcs = PKCS1_v1_5.new(key)
-    h = SHA256.new()
-    h.update(sigstring.encode('ascii'))
-    sigdata = pkcs.sign(h)
-
-    sigdata = base64.b64encode(sigdata)
-    sig['signature'] = sigdata.decode('ascii')
+    sig['signature'] = sign_signing_string(sigstring, key)
 
     chunks = ['{}="{}"'.format(k, v) for k, v in sig.items()]
     return ','.join(chunks)
 
 
+@alru_cache(maxsize=16384)
 async def fetch_actor_key(actor):
     actor_data = await fetch_actor(actor)
 
@@ -108,7 +133,7 @@ async def http_signatures_middleware(app, handler):
     async def http_signatures_handler(request):
         request['validated'] = False
 
-        if 'signature' in request.headers:
+        if 'signature' in request.headers and request.method == 'POST':
             data = await request.json()
             if 'actor' not in data:
                 raise aiohttp.web.HTTPUnauthorized(body='signature check failed, no actor in message')