#!/usr/bin/env bash
# Real demos for P022 "Hashing vs Encryption in Cybersecurity: When Each One Wins" (Cybersecurity for Beginners).
# One Ubuntu 24.04 container stands in for the server of Ana's small online shop (hostname "shop", user ana).
# No network is needed and nothing here attacks anything: we hash, encrypt, decrypt and check files we made.
# Tools: coreutils (sha256sum, base64), OpenSSL (AES-256), Python with argon2-cffi 21.1 (Argon2id, its defaults: m=100 MiB, t=2, p=8).
# Commands are typed into one real interactive bash (a pty); commands + real output go to demo_output.json.
set -euo pipefail
cd "$(dirname "$0")"
P=ce-p022
cleanup() { docker rm -f $P-shop >/dev/null 2>&1 || true; }
cleanup
trap cleanup EXIT

W=/tmp/ce-p022
rm -rf $W && mkdir -p $W/img
cat > $W/img/Dockerfile <<'DOCKER'
FROM ubuntu:24.04
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
      openssl python3 python3-argon2 procps >/dev/null \
 && rm -rf /var/lib/apt/lists/* \
 && userdel -r ubuntu 2>/dev/null; useradd -m -s /bin/bash ana
DOCKER
docker build -q ${DOCKER_BUILD_ARGS:-} -t $P-img $W/img >/dev/null
docker run -d --name $P-shop --hostname shop --network none $P-img sleep infinity >/dev/null
for f in lab/*.py; do docker cp "$f" $P-shop:/home/ana/; done
docker exec $P-shop sh -c "printf '12 Elm Street, Springfield\n' > /home/ana/address.txt && \
  mkdir -p /tmp/app && cp /home/ana/shop.py /tmp/app/ && tar -C /tmp/app -czf /home/ana/shop-app.tar.gz . && \
  chown -R ana:ana /home/ana"

python3 - "$P" <<'PY'
import fcntl, json, os, pty, re, select, struct, subprocess, sys, termios, time
P = sys.argv[1]
steps = {}
READY = re.compile(rb"__READY__:(.*?):__ ")

def sh(cmd):
    return subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True).stdout

def clean(text):
    text = re.sub(r"\x1b\[[0-9;?]*[A-Za-z]|\x1b[()][A-Z0-9]", "", text)
    lines = [l.rstrip("\r").split("\r")[-1] for l in text.split("\n")]
    return "\n".join(l.rstrip() for l in lines).strip("\n")

class Shell:
    """One interactive bash in a real terminal (80 columns), like a terminal window."""
    def __init__(self, box, user):
        self.host, self.user, self.where = box, user, "~"
        pid, fd = pty.fork()
        if pid == 0:
            fcntl.ioctl(0, termios.TIOCSWINSZ, struct.pack("HHHH", 40, 80, 0, 0))
            os.execvp("docker", ["docker", "exec", "-it", "-u", user, "-w", f"/home/{user}", "-e", f"HOME=/home/{user}",
                                 "-e", "TERM=xterm", f"{P}-{box}", "bash", "-i"])
        self.fd = fd
        os.write(fd, b"stty -echo; PS1='__REA''DY__:\\w:__ '\n")
        self.read()

    def read(self, timeout=120):
        buf, end = b"", time.time() + timeout
        while time.time() < end:
            if select.select([self.fd], [], [], 0.3)[0]:
                buf += os.read(self.fd, 65536)
                m = None
                for m in READY.finditer(buf):
                    pass
                if m:
                    self.where = m.group(1).decode()
                    return clean(buf[:m.start()].decode("utf-8", "replace"))
        raise SystemExit(f"{self.host}: timed out; last output: {buf[-400:]!r}")

    def run(self, key, cmd):
        shown = f"{self.user}@{self.host}:{self.where}$ " + cmd
        os.write(self.fd, cmd.encode() + b"\n")
        out = self.read()
        steps[key] = {"cmd": shown, "out": out}
        print(f"== {key}\n{shown}\n{out}\n", flush=True)

ana = Shell("shop", "ana")
# ---------------------------------------------------------------- 1. hashing: a fingerprint
ana.run("h_hello", "echo -n hello | sha256sum")
ana.run("h_Hello", "echo -n Hello | sha256sum")
ana.run("h_size", "ls -lh /usr/bin/bash")
ana.run("h_big", "sha256sum /usr/bin/bash")
# ---------------------------------------------------------------- 2. encryption: a locked box
ana.run("e_plain", "cat address.txt")
ana.run("e_key", "openssl rand -base64 32 > shop.key")
ana.run("e_lock1", "openssl enc -aes-256-cbc -pbkdf2 -a -pass file:shop.key -in address.txt")
ana.run("e_lock2", "openssl enc -aes-256-cbc -pbkdf2 -a -pass file:shop.key -in address.txt -out address.enc")
ana.run("e_show", "cat address.enc")
ana.run("e_unlock", "openssl enc -d -aes-256-cbc -pbkdf2 -a -pass file:shop.key -in address.enc")
ana.run("e_wrongkey", "openssl rand -base64 32 > wrong.key")
ana.run("e_wrong", "openssl enc -d -aes-256-cbc -pbkdf2 -a -pass file:wrong.key -in address.enc -out /dev/null")
# ---------------------------------------------------------------- 3. passwords: the old, fast way
ana.run("o_ben", "python3 old_shop.py signup ben sunshine1")
ana.run("o_cara", "python3 old_shop.py signup cara sunshine1")
ana.run("o_show", "cat old_accounts.txt")
# ---------------------------------------------------------------- 4. passwords: salted, slow Argon2id
ana.run("p_ben", "python3 shop.py signup ben sunshine1")
ana.run("p_cara", "python3 shop.py signup cara sunshine1")
ana.run("p_show", "cat accounts.txt")
ana.run("p_login_ok", "python3 shop.py login ben sunshine1")
ana.run("p_login_bad", "python3 shop.py login ben sunshine2")
ana.run("p_speed", "python3 speed.py")
# ---------------------------------------------------------------- 5. files: is it the file I meant to get?
ana.run("f_sum", "sha256sum shop-app.tar.gz > shop-app.sha256")
ana.run("f_show", "cat shop-app.sha256")
ana.run("f_ok", "sha256sum -c shop-app.sha256")
ana.run("f_change", "echo x >> shop-app.tar.gz")
ana.run("f_bad", "sha256sum -c shop-app.sha256")
# ---------------------------------------------------------------- 6. look-alikes: encoding is not encryption
ana.run("b_encode", "echo -n '12 Elm Street' | base64")
b64 = steps["b_encode"]["out"].strip()
ana.run("b_decode", f"echo {b64} | base64 -d; echo")

steps["version"] = {"cmd": "versions", "out": sh(
    f"docker exec {P}-shop sh -c '. /etc/os-release; echo $PRETTY_NAME; openssl version; sha256sum --version | head -1; "
    f"python3 --version; python3 -c \"import importlib.metadata as m; print(\\\"argon2-cffi\\\", m.version(\\\"argon2-cffi\\\"))\"; "
    f"nproc'; date -u +%Y-%m-%dT%H:%MZ").strip()}
json.dump(steps, open("demo_output.json", "w"), indent=1)
print("saved demo_output.json")
PY
