git.lucas.co / cce-display-manager
login greeter
git clone https://git.lucas.co/cce-display-manager.git

scripts/cce-gnome-keyring-enroll (3.4K)

 1 #!/bin/sh
 2 # cce-gnome-keyring-enroll — one-time setup for the auto-unlocked login keyring.
 3 #
 4 # Seals a random keyring password to this machine's TPM using tpm2-tools
 5 # directly, and creates the gnome-keyring "login" keyring with it.
 6 #
 7 # Why tpm2-tools and NOT systemd-creds: `systemd-creds` running as a non-root
 8 # user does not touch the TPM itself — it delegates to a root varlink service
 9 # that is polkit-gated. That works in an interactive session and fails at
10 # login with io.systemd.InteractiveAuthenticationRequired, which is exactly
11 # how this broke on 2026-08-29. tpm2-tools talks to /dev/tpmrm0 directly via
12 # the `tss` group, so it needs no agent and no authorization prompt.
13 #
14 # The seal is bound to the TPM but deliberately NOT to PCRs: PCR policy would
15 # invalidate the blob on every kernel or firmware update, trading a disk-theft
16 # risk for routine breakage. The threat this defends against is a stolen disk,
17 # which TPM binding alone already covers.
18 #
19 # Safe to re-run: refuses to clobber an existing seal unless --force.
20 
21 set -eu
22 
23 DIR="$HOME/.config/cce"
24 PUB="$DIR/keyring-seal.pub"
25 PRIV="$DIR/keyring-seal.priv"
26 KEYRINGS="$HOME/.local/share/keyrings"
27 FORCE=0
28 [ "${1:-}" = "--force" ] && FORCE=1
29 
30 command -v tpm2_createprimary >/dev/null 2>&1 || {
31     echo "error: tpm2-tools not installed — run: sudo pacman -S tpm2-tools" >&2; exit 1; }
32 [ -r /dev/tpmrm0 ] || {
33     echo "error: cannot read /dev/tpmrm0 — this user must be in the 'tss' group" >&2; exit 1; }
34 if [ -e "$PRIV" ] && [ "$FORCE" -eq 0 ]; then
35     echo "error: $PRIV already exists; re-run with --force to replace it" >&2; exit 1
36 fi
37 
38 mkdir -p "$DIR" "$KEYRINGS"; chmod 700 "$DIR" "$KEYRINGS"
39 umask 077
40 TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT INT TERM
41 
42 # 32 bytes of kernel entropy, base64 — never typed, never shown.
43 PASSWORD=$(head -c 32 /dev/urandom | base64 -w0)
44 
45 # The owner-hierarchy primary key is derived deterministically from the TPM's
46 # seed, so the same template regenerates the identical key at every boot. That
47 # avoids persisting a handle (and the eviction bookkeeping that comes with it).
48 echo "creating TPM primary key..."
49 tpm2_createprimary -Q -C o -g sha256 -G ecc -c "$TMP/primary.ctx"
50 
51 echo "sealing keyring password to the TPM..."
52 printf '%s' "$PASSWORD" | tpm2_create -Q -C "$TMP/primary.ctx" \
53     -a "fixedtpm|fixedparent|userwithauth|noda" \
54     -i - -u "$PUB" -r "$PRIV"
55 chmod 600 "$PUB" "$PRIV"
56 
57 # Verify the unseal round trip BEFORE destroying the old keyring: if the TPM
58 # cannot give the password back, stopping here leaves everything as it was.
59 tpm2_load -Q -C "$TMP/primary.ctx" -u "$PUB" -r "$PRIV" -c "$TMP/seal.ctx"
60 BACK=$(tpm2_unseal -c "$TMP/seal.ctx")
61 [ "$BACK" = "$PASSWORD" ] || { echo "error: TPM round trip failed" >&2; exit 1; }
62 echo "verified TPM round trip"
63 
64 # Recreate the login keyring under the new password. The old one is kept
65 # aside, never deleted.
66 systemctl --user stop gnome-keyring-daemon.service 2>/dev/null || true
67 if [ -s "$KEYRINGS/login.keyring" ]; then
68     mv "$KEYRINGS/login.keyring" "$KEYRINGS/login.keyring.superseded-$(date +%Y%m%d%H%M%S)"
69     echo "set aside previous login.keyring"
70 fi
71 systemctl --user start gnome-keyring-daemon.service
72 sleep 3
73 systemctl --user is-active --quiet gnome-keyring-daemon.service \
74     || { echo "error: gnome-keyring-daemon did not stay running" >&2; exit 1; }
75 echo "created and unlocked the login keyring"
76 
77 printf 'login' > "$KEYRINGS/default"
78 echo "set 'login' as the default keyring"