#!/bin/bash
# Replace the login keyring with a fresh one whose password is the account
# password (so password logins unlock it via PAM), and store that password
# encrypted with the YubiKey's hmac-secret (so YubiKey logins can unlock it).
# The old keyring is kept as a backup. Re-run after changing your password.
set -euo pipefail
. "$HOME/.local/lib/yubikey-keyring.sh"   # DIR, RP, yk_device, yk_secret
KR="$HOME/.local/share/keyrings/v1"
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT

# 1. Account password, verified against the real account before anything changes.
if [[ -t 0 ]]; then
  read -rsp "Account password: " pw; echo
  printf '%s' "$pw" > "$tmp/pw"; unset pw
else
  zenity --password --title="Account password" --text="Enter your account (login) password" > "$tmp/pw"
  truncate -s -1 "$tmp/pw"   # drop zenity's trailing newline
fi
if [[ -z ${YKR_TEST:-} ]]; then
  # unix_chkpwd expects a NUL-terminated password on stdin
  { cat "$tmp/pw"; printf '\0'; } | /usr/sbin/unix_chkpwd "$USER" nullok \
    || { echo "account password incorrect, nothing changed"; exit 1; }
  echo "account password verified"
fi

# 2. Derive the key secret (PIN + touch) and encrypt the password with it.
dev=$(yk_device)
secret=$(yk_secret "$dev" pin)
[[ -n $secret ]] || { echo "YubiKey PIN/touch failed, nothing changed"; exit 1; }
openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt -in "$tmp/pw" -out "$DIR/password.enc.new" -pass fd:3 3< <(printf '%s' "$secret")
unset secret
echo "password encrypted with YubiKey"

# 3. Back up the old keyring and restart the daemon without it. oo7 then shows
#    a locked placeholder "login" collection; the first password used to unlock
#    it becomes the keyring password, and the file is written with the first item.
# (YKR_TEST=<pidfile>: drive a private test daemon instead of the systemd unit.)
stop_daemon() {
  if [[ -n ${YKR_TEST:-} ]]; then
    kill "$(cat "$YKR_TEST")"; while kill -0 "$(cat "$YKR_TEST")" 2>/dev/null; do sleep 0.2; done
  else
    systemctl --user stop oo7-daemon.service
  fi
}
start_daemon() {
  if [[ -n ${YKR_TEST:-} ]]; then
    setsid /usr/libexec/oo7-daemon >/dev/null 2>&1 & echo $! > "$YKR_TEST"
  else
    systemctl --user start oo7-daemon.service
  fi
  for _ in $(seq 25); do
    busctl --user status org.freedesktop.secrets >/dev/null 2>&1 && return 0
    sleep 0.2
  done
  echo "secret service did not come back"; return 1
}
backup="$KR/login.keyring.bak-$(date +%Y%m%d-%H%M%S)"
restore() {
  echo "FAILED — restoring the old keyring"
  stop_daemon
  rm -f "$KR/login.keyring"
  mv "$backup" "$KR/login.keyring"
  start_daemon
  rm -f "$DIR/password.enc.new"
  exit 1
}
stop_daemon
mv "$KR/login.keyring" "$backup"
echo "old keyring backed up to $backup"
start_daemon || restore

# 4. Unlock the placeholder with the password and store a marker item so the
#    keyring file is written now, encrypted with that password.
python3 -I - "$tmp/pw" <<'PY' || restore
import sys, gi
gi.require_version("Gio", "2.0")
from gi.repository import Gio, GLib
pw = open(sys.argv[1], "rb").read()
bus = Gio.bus_get_sync(Gio.BusType.SESSION)
S = "/org/freedesktop/secrets"
COLL = "/org/freedesktop/secrets/collection/login"
def call(path, iface, m, args, rt):
    return bus.call_sync("org.freedesktop.secrets", path, iface, m, args,
                         GLib.VariantType(rt) if rt else None, 0, -1, None).unpack()
def locked():
    return call(COLL, "org.freedesktop.DBus.Properties", "Get",
                GLib.Variant("(ss)", ("org.freedesktop.Secret.Collection", "Locked")), "(v)")[0]
_, sess = call(S, "org.freedesktop.Secret.Service", "OpenSession",
               GLib.Variant("(sv)", ("plain", GLib.Variant("s", ""))), "(vo)")
call(S, "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface", "UnlockWithMasterPassword",
     GLib.Variant("(o(oayays))", (COLL, (sess, b"", pw, "text/plain"))), "()")
if locked():
    sys.exit("placeholder did not unlock")
props = {"org.freedesktop.Secret.Item.Label": GLib.Variant("s", "yubikey-keyring marker"),
         "org.freedesktop.Secret.Item.Attributes": GLib.Variant("a{ss}", {"yubikey-keyring": "marker"})}
call(COLL, "org.freedesktop.Secret.Collection", "CreateItem",
     GLib.Variant("(a{sv}(oayays)b)", (props, (sess, b"", b"marker", "text/plain"), True)), "(oo)")
print("new keyring created")
PY
[[ -s $KR/login.keyring ]] || { echo "keyring file was not written"; restore; }

mv "$DIR/password.enc.new" "$DIR/password.enc"
chmod 600 "$DIR"/*
echo "done"
