From dbe57075d386d7474bafc208b654507d9a2d769e Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sun, 6 Nov 2022 13:48:04 -0700 Subject: Rework to a domains model for better vhosting --- core/context.py | 4 +++- core/ld.py | 11 +++++++++-- core/signatures.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 core/signatures.py (limited to 'core') diff --git a/core/context.py b/core/context.py index 38a268c..026ac11 100644 --- a/core/context.py +++ b/core/context.py @@ -2,4 +2,6 @@ from django.conf import settings def config_context(request): - return {"config": {"site_name": settings.SITE_NAME}} + return { + "config": {"site_name": settings.SITE_NAME}, + } diff --git a/core/ld.py b/core/ld.py index 38e436a..28ff65a 100644 --- a/core/ld.py +++ b/core/ld.py @@ -252,7 +252,7 @@ def builtin_document_loader(url: str, options={}): ) -def canonicalise(json_data): +def canonicalise(json_data, include_security=False): """ Given an ActivityPub JSON-LD document, round-trips it through the LD systems to end up in a canonicalised, compacted format. @@ -264,5 +264,12 @@ def canonicalise(json_data): raise ValueError("Pass decoded JSON data into LDDocument") return jsonld.compact( jsonld.expand(json_data), - ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"], + ( + [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ] + if include_security + else "https://www.w3.org/ns/activitystreams" + ), ) diff --git a/core/signatures.py b/core/signatures.py new file mode 100644 index 0000000..bcacb68 --- /dev/null +++ b/core/signatures.py @@ -0,0 +1,48 @@ +import base64 +from typing import Any, Dict, List + +from cryptography.hazmat.primitives import hashes +from django.http import HttpRequest + + +class HttpSignature: + """ + Allows for calculation and verification of HTTP signatures + """ + + @classmethod + def calculate_digest(cls, data, algorithm="sha-256") -> str: + """ + Calculates the digest header value for a given HTTP body + """ + if algorithm == "sha-256": + digest = hashes.Hash(hashes.SHA256()) + digest.update(data) + return "SHA-256=" + base64.b64encode(digest.finalize()).decode("ascii") + else: + raise ValueError(f"Unknown digest algorithm {algorithm}") + + @classmethod + def headers_from_request(cls, request: HttpRequest, header_names: List[str]) -> str: + """ + Creates the to-be-signed header payload from a Django request""" + headers = {} + for header_name in header_names: + if header_name == "(request-target)": + value = f"post {request.path}" + elif header_name == "content-type": + value = request.META["CONTENT_TYPE"] + else: + value = request.META[f"HTTP_{header_name.upper()}"] + headers[header_name] = value + return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items()) + + @classmethod + def parse_signature(cls, signature) -> Dict[str, Any]: + signature_details = {} + for item in signature.split(","): + name, value = item.split("=", 1) + value = value.strip('"') + signature_details[name.lower()] = value + signature_details["headers"] = signature_details["headers"].split() + return signature_details -- cgit v1.2.3