diff --git a/demo/__init__.py b/demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demo/httpsig.py b/demo/httpsig.py new file mode 100644 index 0000000..979c235 --- /dev/null +++ b/demo/httpsig.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Verify and build HTTP signatures +""" +import base64 + +from Crypto.Hash import SHA256 +from Crypto.Signature import PKCS1_v1_5 +from Crypto.PublicKey import RSA + + +class HttpSignature: + """ + calculation and verification of HTTP signatures + """ + @classmethod + def calculation_digest( + cls, + body : bytes, + algorithm : str ="sha-256" + ) -> str: + """ + Calculates the digest header value for a given HTTP body + """ + if "sha-256" == algorithm: + body_digest = SHA256.new() + body_digest.update(body) + return "SHA-256=" + \ + base64.b64encode(body_digest.digest() + ).decode("utf-8") + + raise ValueError(f"No support algorithm {algorithm}") + + @classmethod + def verify_signature( + cls, + signature_string : str, + signature : bytes, + pubkey, + ) -> bool: + """ + Verify signatur that returns bool + """ + pubkey = RSA.importKey(pubkey) + signer = PKCS1_v1_5.new(pubkey) + digest = SHA256.new() + digest.update(signature_string.encode("utf-8")) + return signer.verify(digest, signature) + + + @classmethod + def parse_signature( + cls, + signature : str + ) -> dict: + """ + Parse signature string in headers + """ + detail = {} + for item in signature.split(','): + name, value = item.split('=', 1) + value = value.strip('"') + detail[name.lower()] = value + signature_details = { + "headers": detail["headers"].split(), + "signature": base64.b64decode(detail["signature"]), + "algorithm": detail["algorithm"], + "keyid": detail["keyid"], + } + return signature_details + + @classmethod + def build_signature_string( + cls, + method : str, + path : str, + signed_headers : list, + body_digest : str | None, + headers, + ) -> str: + """ + Build signature string + """ + signed_string = [] + for signed_header in signed_headers: + if signed_header == "(request-target)": + signed_string.append("(request-target): " + + method.lower() + ' ' + path) + elif signed_header == "digest" and body_digest: + signed_string.append("digest: " + body_digest) + else: + signed_string.append(signed_header + ": " + + headers[signed_header]) + + return "\n".join(signed_string)