__filename__ = "linked_data_sig.py"
__author__ = "Bob Mottram"
__credits__ = ['Based on ' +
               'https://github.com/tsileo/little-boxes']
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"

import base64
import hashlib
from datetime import datetime

try:
    from Cryptodome.PublicKey import RSA
    from Cryptodome.Hash import SHA256
    from Cryptodome.Signature import pkcs1_5 as PKCS1_v1_5
except ImportError:
    from Crypto.PublicKey import RSA
    from Crypto.Hash import SHA256
    from Crypto.Signature import PKCS1_v1_5

from pyjsonld import normalize
from context import hasValidContext


def _options_hash(doc: {}) -> str:
    """Returns a hash of the signature, with a few fields removed
    """
    docSig = dict(doc["signature"])

    # remove fields from signature
    for k in ["type", "id", "signatureValue"]:
        if k in docSig:
            del docSig[k]

    docSig["@context"] = "https://w3id.org/identity/v1"
    options = {
        "algorithm": "URDNA2015",
        "format": "application/nquads"
    }

    normalized = normalize(docSig, options)
    h = hashlib.new("sha256")
    h.update(normalized.encode("utf-8"))
    return h.hexdigest()


def _doc_hash(doc: {}) -> str:
    """Returns a hash of the ActivityPub post
    """
    doc = dict(doc)

    # remove the signature
    if "signature" in doc:
        del doc["signature"]

    options = {
        "algorithm": "URDNA2015",
        "format": "application/nquads"
    }

    normalized = normalize(doc, options)
    h = hashlib.new("sha256")
    h.update(normalized.encode("utf-8"))
    return h.hexdigest()


def verifyJsonSignature(doc: {}, publicKeyPem: str) -> bool:
    """Returns True if the given ActivityPub post was sent
    by an actor having the given public key
    """
    if not hasValidContext(doc):
        return False
    key = RSA.importKey(publicKeyPem)
    to_be_signed = _options_hash(doc) + _doc_hash(doc)
    signature = doc["signature"]["signatureValue"]
    signer = PKCS1_v1_5.new(key)  # type: ignore
    digest = SHA256.new()
    digest.update(to_be_signed.encode("utf-8"))
    base64sig = base64.b64decode(signature)
    return signer.verify(digest, base64sig)  # type: ignore


def generateJsonSignature(doc: {}, privateKeyPem: str) -> None:
    """Adds a json signature to the given ActivityPub post
    """
    if not doc.get('actor'):
        return
    if not hasValidContext(doc):
        return
    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)

    key = RSA.importKey(privateKeyPem)
    signer = PKCS1_v1_5.new(key)
    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")