From b846c397ab94179cec1ac25de7ef3c479e1376e9 Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Mon, 27 Jun 2022 20:56:13 +0200 Subject: [PATCH] Add LD sig from little-boxes --- app/ldsig.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/ldsig.py diff --git a/app/ldsig.py b/app/ldsig.py new file mode 100644 index 0000000..8dbed39 --- /dev/null +++ b/app/ldsig.py @@ -0,0 +1,77 @@ +import base64 +import hashlib +import typing +from datetime import datetime +from functools import lru_cache +from typing import Any + +from Crypto.Hash import SHA256 +from Crypto.Signature import PKCS1_v1_5 +from pyld import jsonld # type: ignore + +if typing.TYPE_CHECKING: + from app.key import Key + + +_LOADER = jsonld.requests_document_loader() + + +@lru_cache(256) +def _caching_document_loader(url: str) -> Any: + return _LOADER(url) + + +jsonld.set_document_loader(_caching_document_loader) + + +def _options_hash(doc): + doc = dict(doc["signature"]) + for k in ["type", "id", "signatureValue"]: + if k in doc: + del doc[k] + doc["@context"] = "https://w3id.org/identity/v1" + normalized = jsonld.normalize( + doc, {"algorithm": "URDNA2015", "format": "application/nquads"} + ) + h = hashlib.new("sha256") + h.update(normalized.encode("utf-8")) + return h.hexdigest() + + +def _doc_hash(doc): + doc = dict(doc) + if "signature" in doc: + del doc["signature"] + normalized = jsonld.normalize( + doc, {"algorithm": "URDNA2015", "format": "application/nquads"} + ) + h = hashlib.new("sha256") + h.update(normalized.encode("utf-8")) + return h.hexdigest() + + +def verify_signature(doc, key: "Key"): + to_be_signed = _options_hash(doc) + _doc_hash(doc) + signature = doc["signature"]["signatureValue"] + signer = PKCS1_v1_5.new(key.pubkey or key.privkey) # type: ignore + digest = SHA256.new() + digest.update(to_be_signed.encode("utf-8")) + return signer.verify(digest, base64.b64decode(signature)) # type: ignore + + +def generate_signature(doc, key: "Key"): + options = { + "type": "RsaSignature2017", + "creator": doc["actor"] + "#main-key", + "created": datetime.utcnow().replace(microsecond=0).isoformat() + "Z", + } + doc["signature"] = options + to_be_signed = _options_hash(doc) + _doc_hash(doc) + if not key.privkey: + raise ValueError(f"missing privkey on key {key!r}") + + signer = PKCS1_v1_5.new(key.privkey) + digest = SHA256.new() + digest.update(to_be_signed.encode("utf-8")) + sig = base64.b64encode(signer.sign(digest)) # type: ignore + options["signatureValue"] = sig.decode("utf-8")