#!/usr/bin/env python3
"""Read an Evergist gist without any Evergist code.

An independent implementation of the v1 scheme, for checking that the server
only ever holds ciphertext. Needs: pip install cryptography

    python3 evergist_read.py "https://evergist.com/g/<id>#<key>" [password]
"""
import base64, hashlib, json, sys, unicodedata, urllib.error, urllib.request
from urllib.parse import urlsplit
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF


def b64url_decode(s: str) -> bytes:
    return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))


def b64url_encode(b: bytes) -> str:
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode()


def hkdf(ikm: bytes, info: str, length: int) -> bytes:
    return HKDF(algorithm=hashes.SHA256(), length=length, salt=None, info=info.encode()).derive(ikm)


def get(url: str, token: str) -> dict:
    req = urllib.request.Request(url, headers={"authorization": f"Bearer {token}", "user-agent": "evergist-python/1"})
    with urllib.request.urlopen(req) as res:
        return json.load(res)


def main() -> None:
    link = sys.argv[1]
    password = sys.argv[2] if len(sys.argv) > 2 else None
    parts = urlsplit(link)
    gist_id = parts.path.rstrip("/").split("/")[-1]
    key = b64url_decode(parts.fragment)          # K: never sent anywhere
    api = f"{parts.scheme}://{parts.netloc}/api/v1/gists/{gist_id}"

    access = b64url_encode(hkdf(key, "evergist/v1/access", 32))
    status = get(f"{api}/status", access)       # does not use a view

    ikm = key
    token = access
    if status["password"]:
        if not password:
            sys.exit("This gist needs a password.")
        salt = hkdf(key, "evergist/v1/pbkdf2-salt", 16)
        pw_key = hashlib.pbkdf2_hmac("sha256", unicodedata.normalize("NFC", password).encode(), salt, status["iterations"], 32)
        ikm = key + pw_key
        token = f"{access}.{b64url_encode(hkdf(ikm, 'evergist/v1/password', 32))}"

    enc_key = hkdf(ikm, "evergist/v1/enc", 32)
    sealed = get(api, token)                     # uses one view
    plaintext = AESGCM(enc_key).decrypt(b64url_decode(sealed["iv"]), b64url_decode(sealed["ciphertext"]), b"evergist/v1")
    sys.stdout.write(plaintext.decode("utf-8"))


if __name__ == "__main__":
    main()
