Give the seated user the TPM, so nobody has to join the tss group
ci / build and test (push) Successful in 3m0s
ci / android head (push) Successful in 3m24s
ci / desktop nightly (push) Successful in 1m4s
ci / api image (push) Successful in 24s

ADR 0007 decision 4 refused this a commit ago, on the grounds that widening
access to the machine's TPM is a system-wide decision an application package
should not make for an administrator. That refusal is overturned here, and the
reasoning it is replaced with is narrower rather than louder — because the
refusal had treated "widen access" as one thing when it is two.

Adding a person to `tss` is what the refusal was really about, and it stays the
wrong answer: permanent, effective over SSH, applying whether or not they are at
the keyboard, and needing an administrator and a fresh login before the feature
appears at all.

Tagging /dev/tpmrm0 with `uaccess` is a different thing. It is systemd's own
mechanism for this exact question — the one /dev/dri, webcams, scanners and FIDO
keys already use — and logind grants the ACL to the user of the *active local
session*, then takes it back when that session ends. Not over SSH, not on an
inactive VT, not to daemons. The rule changes neither the mode nor the group, so
everything that worked before still works and the ACL is layered on top.

tpmrm and never tpm, which is what makes it defensible at all: /dev/tpm0 is the
raw device and is exclusive, where /dev/tpmrm0 is the kernel's resource manager,
which virtualises handles and isolates callers precisely so that more than one
unprivileged program can use the chip.

◆ THE COST IS A DENIAL OF SERVICE AND NOT A DISCLOSURE, AND IT IS WRITTEN DOWN.

TPM objects are protected by their own auth values, so this lets nobody read
anybody else's sealed data. What it allows is *failed* attempts — and a TPM
answers enough of those by locking out everyone, which is the same anti-hammering
this project relies on to make a four-digit PIN safe. On a machine that unlocks
its disk with the TPM at boot, somebody sitting at it can trip that and stop the
next boot from unlocking until it recovers.

An administrator who does not accept that masks the file:

    sudo ln -s /dev/null /etc/udev/rules.d/71-dodossh-tpm.rules

which is the standard override, survives upgrades, and puts DodoSSH back to
behaving as though the machine had no TPM. That is in the rule's own comments,
because the person who needs it is reading the file rather than the ADR.

Numbered 71 because 60-tpm-udev.rules is what creates the node's ownership and
73-seat-late.rules is what turns the tag into an ACL; verified in that order on
Fedora 44, along with `udevadm verify` accepting the file and the mask being
reported as "masked by previous entry". %post reloads and replays it against the
tpmrm subsystem, because a rule that waits for a reboot is a feature that does
not work on the day it is installed.

Both channels install the same path deliberately: this is about the machine's
TPM rather than about either build, the file is byte-identical from both, and
giving each its own copy would mean removing one package silently revoked the
other's access.

Phase 19 grew a check that the ACL exists for the local session and does *not*
exist for an SSH one, and another that masking the rule really does put the
machine back — then tells you to undo it before the checks that follow.

Also recorded, after it cost five confusing failures: swtpm serves exactly one
client, so a shell readiness probe sitting on the port makes the whole device-key
suite fail with "The TPM closed the connection" and nothing pointing at why.
This commit is contained in:
jaap-jan committed 2026-08-22 20:38:22 +02:00
1 parent 4552538533
commit c2c860b4f2
10 files changed
+220 -48

No files matched your search

+33 -7
View File
@@ -139,13 +139,39 @@ not claimed anywhere in the interface**, which is why `DesktopDeviceKeyStores` d
why the prompt is deliberately plain rather than dressed to look like a system dialog — teaching people
that a convincing DodoSSH PIN box is a system dialog is the one thing this design must not do.
**Availability is narrower than "the machine has a TPM", and that is a deployment fact rather than a bug.**
On Fedora `/dev/tpmrm0` is `root:tss` mode 0660 and desktop users are not in `tss`, so the ordinary case is
a working TPM this process may not open. That reports unavailable and the passphrase path continues,
exactly as a machine with no TPM does. **Shipping a udev rule to widen it is refused**: granting every local
desktop user access to the machine's TPM is a system-wide security decision, some distributions restrict it
deliberately, and an application package has no business making it on the administrator's behalf. One line
of `usermod` is the answer, and the README says so.
**Reaching the TPM at all is the other half, and the package now arranges it.** On Fedora `/dev/tpmrm0` is
`root:tss` mode 0660 and desktop users are not in `tss`, so without help the ordinary case is a working TPM
this process may not open — which reports unavailable and keeps asking for the passphrase, exactly as a
machine with no TPM does.
◆ **This originally said shipping a udev rule to widen that was refused, on the grounds that it is a
system-wide decision an application package should not make. That refusal was overturned deliberately, and
the reasoning it was replaced with is narrower rather than louder.** The refusal treated "widen access" as
one thing. It is two, and they are not close:
- *Adding a person to `tss`* is what the refusal was really about, and it remains the wrong answer. It is
permanent, it applies whether or not they are at the machine, it applies over SSH, and it needs an
administrator and a fresh login before the feature appears at all.
- *Tagging `/dev/tpmrm0` with `uaccess`* is the mechanism systemd already uses for `/dev/dri`, webcams,
scanners and FIDO keys: logind grants a POSIX ACL to the user of the **active local session** and removes
it when that session ends. Not over SSH, not on an inactive VT, not to daemons. `build/linux/71-dodossh-tpm.rules`
is that one line, and it changes neither the mode nor the group, so anything that worked before still does.
**`tpmrm` and never `tpm`, which is what makes it defensible at all.** `/dev/tpm0` is the raw device and is
exclusive; a process holding it can leave the chip in a state that breaks every other user. `/dev/tpmrm0` is
the kernel's resource manager, which virtualises handles and isolates callers — it exists *because* the TPM
is meant to be usable by more than one unprivileged program. Granting a seat's user the manager is the use
it was designed for.
**The cost is a denial of service and not a disclosure, and it is stated because it is easy to miss.** TPM
objects are protected by their own auth values, so this lets nobody read anybody else's sealed data. What it
does allow is *failed* authorisation attempts — and the chip answers those by counting them and eventually
refusing everyone, which is the same lockout decision 4 relies on. On a machine whose disk is unlocked by
the TPM at boot (`systemd-cryptenroll`, Clevis), somebody sitting at it can trip that lockout and stop the
next boot from unlocking until it recovers. An administrator who does not accept that masks the file with
`ln -s /dev/null /etc/udev/rules.d/71-dodossh-tpm.rules`, which is the standard override, survives upgrades,
and puts the behaviour back to the passphrase-only path. The rule says so in its own comments, because the
person who needs to know is reading the file rather than this document.
**What was rejected, and why each would have failed the amendment above.** The Secret Service API —
gnome-keyring or KWallet — is out of process and OS-managed and has *no per-use consent*: once the session
@@ -398,8 +398,9 @@ is what `ClientPaths` reads, and the packaging must not answer that question a s
platform had none and that the passphrase was asked for at every unlock. It has one:
[ADR 0007](0007-device-key-protection.md) decision 4 adds a TPM-sealed key released against a PIN the TPM
itself checks and counts failures against. It is weaker than Hello and Touch ID in a way that ADR names
precisely, and it is available on fewer machines than have a TPM, because the ordinary Fedora desktop user
may not open `/dev/tpmrm0`.
precisely. Reaching the chip needs no group membership, because this package ships the udev rule that gives
the seated user an ACL on `/dev/tpmrm0` — a decision that ADR overturned from its own first answer, and
whose cost it states.
**What this platform does not have is a signature on the package**, which is not decision 8's "unsigned for
now": nothing on this platform
+53 -26
View File
@@ -2849,11 +2849,12 @@ because nothing on this platform refuses an unsigned package, which is a distinc
is fine"; `build/linux/dodossh.spec` says what stands in for a signature, which is that the package comes
from the project's own forge over TLS and from nowhere else.
Everything else is here, and two things are here that neither other phase has. **The update does not
install itself**, so 19.7 is about watching a polkit prompt appear and saying no to it, where phases 16 and
18 watch an application replace itself. And **the device key's gate is a PIN rather than a gesture**, with
the TPM's own lockout behind it — so 19.6 is the one check in this document that deliberately breaks
something and then tells you how to put it back.
Everything else is here, and three things are here that neither other phase has. **The update does not
install itself**, so 19.8 is about watching a polkit prompt appear and saying no to it, where phases 16 and
18 watch an application replace itself. **The device key's gate is a PIN rather than a gesture**, with the
TPM's own lockout behind it — so 19.7 deliberately breaks something and then tells you how to put it back.
And **this package changes a permission on the machine**: 19.4 and 19.5 are about the udev rule that gives
the seated user the TPM, checking both that it works and that masking it undoes it completely.
What this phase is for is the set nobody else can answer. **CI builds this package and installs nothing**,
and every check in `scripts/ci-linux-rpm.sh` is a check on what the package *says* — its dependencies, its
@@ -2909,32 +2910,58 @@ are the package's binary name, so they can only disagree if one was edited. A bl
48-pixel file missing and the 256 being scaled down; `dodossh-icon.ps1` draws four sizes and the spec
installs four.
### 19.4 With no access to the TPM, no device key is offered at all
### 19.4 The udev rule gives *this* session the TPM, and gives nobody else it · **needs a real TPM**
On a stock Fedora install your account is not in the `tss` group, so this is the state most people are in.
Confirm it — `id -nG | tr ' ' '
' | grep -x tss` prints nothing — then open the security settings page.
Installing the package should be the whole setup — no group, no administrator, no fresh login. Check it
from a graphical session on the machine itself:
**Pass:** registering a device key is **not** offered, and unlocking asks for the passphrase every time.
```bash
getfacl /dev/tpmrm0 | grep "^user:"
id -nG | tr ' ' '
' | grep -x tss # expect nothing: you are not in the group
```
**Failure means:** being offered it is `LinuxDeviceKeyStore.IsSupported` having answered from the presence
of the device node rather than from opening it. Accepting the offer would put a wrap on the server that
nothing can ever open — and the failure would arrive at the next unlock rather than now.
**Pass:** `getfacl` shows a `user:<you>:rw-` entry that is **not** there for any other account, and you are
not in `tss`. Now `ssh` into the same machine from elsewhere and run the same `getfacl`: the entry for you
is still the *local* session's, and a shell that is not on the seat cannot open the device — `head -c1
/dev/tpmrm0` answers "Permission denied".
### 19.5 With access to the TPM, the PIN unlocks and the TPM is what checks it
**Failure means:** no ACL at all is the rule not having been applied. `udevadm test --action=change
/sys/class/tpmrm/tpmrm0 | grep uaccess` says whether the tag is being set, and the load order matters —
71 has to fall between `60-tpm-udev.rules` and `73-seat-late.rules`, which is what turns the tag into an
ACL. If the file is present and untagged, check nothing in `/etc/udev/rules.d` is masking it. **An ACL
granted to a remote session would be the serious one**, and would mean the tag had been put on `tpm` rather
than `tpmrm` or the mode widened instead — neither of which this rule does.
`sudo usermod -aG tss $USER`, log out and back in, then register a device key from the security settings
page and choose a PIN. Lock the vault and unlock it again.
### 19.5 Masking the rule puts the machine back the way it was
```bash
sudo ln -s /dev/null /etc/udev/rules.d/71-dodossh-tpm.rules
sudo udevadm control --reload && sudo udevadm trigger --subsystem-match=tpmrm --action=change
```
Log out and back in, then open the security settings page.
**Pass:** registering a device key is **not** offered, and unlocking asks for the passphrase every time —
the same behaviour as a machine with no TPM. `udevadm test` reports the file as "masked by previous entry".
**Failure means:** still being offered it means `LinuxDeviceKeyStore.IsSupported` answered from the presence
of the device node rather than from opening it. Accepting that offer would put a wrap on the server that
nothing can ever open, and the failure would arrive at the next unlock rather than now.
**Then undo it** — `sudo rm /etc/udev/rules.d/71-dodossh-tpm.rules` and reload — before 19.6.
### 19.6 The PIN unlocks, and the TPM is what checks it
Register a device key from the security settings page and choose a PIN. Lock the vault and unlock it again.
**Pass:** registering asks for a PIN twice and shows no other prompt. Unlocking asks for it once and opens
the vault. Cancelling that prompt lands on the passphrase screen, which works.
**Failure means:** no offer after joining the group is `/dev/tpmrm0` still unreadable — check `ls -l` on it
and that the login really was restarted, because group membership is fixed at session start. A PIN prompt
that opens the vault whatever you type would mean the unseal is not reaching the TPM at all, which is the
one failure that would make this whole feature decorative.
**Failure means:** a PIN prompt that opens the vault whatever you type would mean the unseal is not reaching
the TPM at all, which is the one failure that would make this whole feature decorative.
### 19.6 A wrong PIN gets nothing, and the TPM stops answering
### 19.7 A wrong PIN gets nothing, and the TPM stops answering
With a device key registered, lock the vault and enter a wrong PIN. Do it several times.
@@ -2949,7 +2976,7 @@ assertion against real firmware, where the lockout is real and recovery takes ho
**Then put it back:** `sudo tpm2_dictionarylockout --clear-lockout`, or wait out the recovery time. Note
which, because a user who trips this will ask — and the honest answer is that it needs an administrator.
### 19.7 An update is noticed, fetched, and installed by PackageKit and not by DodoSSH
### 19.8 An update is noticed, fetched, and installed by PackageKit and not by DodoSSH
With the nightly installed, wait for a newer one to be published — or press **CHECK NOW** on
PREFERENCES → UPDATES once there is one.
@@ -2967,7 +2994,7 @@ covers the algorithm. A message naming PackageKit means it is not installed; it
minimal install legitimately lacks it. **The vault locking or shells closing when you decline is the
serious one** — it means `ApplyingEndsTheProcess` has been set true, which punishes somebody for saying no.
### 19.8 A tampered package is refused before PackageKit ever sees it
### 19.9 A tampered package is refused before PackageKit ever sees it
Not reachable without editing the feed, so this is the one to reason about rather than perform unless you
are changing that code: the manifest's `sha256` is checked after the download and the file is deleted if it
@@ -2978,7 +3005,7 @@ does not match.
**Failure means:** a leftover `.rpm` there after a failed update is a file somebody could reasonably install
by hand, named as though the application had endorsed it.
### 19.9 The nightly installs beside the release rather than over it
### 19.10 The nightly installs beside the release rather than over it
With the release package installed, install the nightly one too.
@@ -2991,7 +3018,7 @@ nightly that opens the release's vault already unlocked is the profile separatio
`DodoChannel=nightly` reaching `ClientPaths`, not from the packaging, so check
`~/.local/share/dodossh.nightly` exists rather than looking at the spec.
### 19.10 Nothing in the installed tree is writable by anyone but root
### 19.11 Nothing in the installed tree is writable by anyone but root
```bash
find /opt/DodoSSH ! -user root -o -perm -o+w
@@ -3005,7 +3032,7 @@ model walked around rather than attacked. `ci-linux-rpm.sh` refuses to produce s
sets every mode absolutely, so a hit here means one of those two was changed — it cannot arrive by accident
from a publish directory.
### 19.11 Removing it leaves the vault, and leaves the other channel alone
### 19.12 Removing it leaves the vault, and leaves the other channel alone
Register a device, sync something, then `sudo dnf remove dodossh`.