Security keys and smart cards
A security key, such as a YubiKey, and a smart card reader, such as the one
a Slovak eID card goes into, stay plugged into the host. The machines use
them through it. A person signs in to the host with them, so the login
screen must always reach them, and pulling one out must take it from every
machine at once. losos-tokens, built from src/losos-tokens, does the
relaying. nixos/modules/tokens.nix runs its host half and
nixos/guest/tokens.nix runs its machine half in every
machine template.
Security keys
A machine gets a copy of each FIDO key the host has. losos-tokens guest
makes the copy with uhid, the kernel's way to create a HID device from a
program. A browser, libfido2 or ssh in the machine sees a key on USB with
the real key's name, vendor and product. Everything a program writes to the
copy goes to the host over vsock port 7370, and losos-tokens host writes
it to the real key's hidraw device. The key's answers come back the same
way. The person touches the key and types its PIN as they would on the
host.
The host half watches /sys/class/hidraw for devices whose report
descriptor declares the FIDO usage page, 0xF1D0, and tells every connected
machine when one appears or goes. It skips the copies, which name
losos-tokens as their physical path, so a host that is itself a machine
never hands a key back.
Several machines and the host can use a key at once. Each program opens a channel on the key with CTAPHID's INIT command and a nonce. The key answers on the broadcast channel with the same nonce and the new channel's number. The relay reads those two reports and nothing else, and remembers which machine opened which channel. A machine then gets only the answers on its own channels, and may write only on those or send INIT. The host's own programs, such as derisk's login, open their channels through hidraw beside the relay, so no machine sees them. The host's journal says which machine started using which key.
In a machine nobody logs in, so systemd's uaccess rule gives its user no
access to the copy. A udev rule gives the copies to the users group
instead, which the machine's user is in. On the host, a udev rule gives FIDO
keys to the relay's own account, losos-tokens, and the person at the seat
keeps the access uaccess gives them.
Smart cards
The host runs pcscd with the CCID driver, which covers every USB smart card
reader and the card side of keys such as a YubiKey. A machine runs no pcscd
of its own. losos-tokens guest listens at /run/pcscd/pcscd.comm instead,
where libpcsclite looks for it, and passes each connection over vsock port
7371 to the host half, which passes it on to the host's pcscd. pcscd asks
polkit about each client and allows only the person at the seat, so a
polkit rule lets the relay's account in too. The relay decides which machine
gets the readers.
One machine holds the readers at a time. A card keeps what a PIN unlocked until it is reset or pulled out. If two machines shared a reader, the second could sign with a card the first had unlocked. The first machine to open a connection holds the readers until its last connection closes. Until then the host refuses every other machine's connections, and its journal says which machine holds them. The host's own programs are not limited this way.
Hand-offs between apps
Some sites hand work to an app on the person's own computer through
localhost. slovensko.sk signs a person in with their eID card this way.
The page sends the browser to http://127.0.0.1:24727/, where an eID
client listens (BSI TR-03124), and the eID client reads the card and sends
the browser back. With the browser on the host and the eID klient in a
machine, the two localhosts are different places.
losos-tokens guest looks at what listens in its machine every two
seconds, on the ports it serves (24727 in the image), and tells the host
when something starts or stops listening on one. losos-tokens host
listens on the same ports of the host's localhost and passes each
connection to the machine that serves the port, over vsock port 7372. If
several machines serve it, the one that started last gets it, since that
is the one the person just opened the app in. If no machine serves it, the
host closes the connection, as a computer with nothing listening would. A
machine reaches only the ports it serves, and only from the host.
The hand-off works the other way without help. Danube in a machine reaches the eID klient on that machine's own localhost.
The Slovak eID
The Ministry of the Interior's eID klient signs in to slovensko.sk and
other Slovak public services with the eID card. Every x86_64 machine has
it as an app, "eID klient". Its licence lets nobody pass it on, so the
image carries only losos-eid, from nixos/guest/eid.nix. The first time
the app is started, losos-eid downloads the eID klient from
eidas.minv.sk into ~/.local/share/losos-eid in the machine. Each later
start asks the server for the file's size and downloads it again only when
the size has changed, because the server gives no date or version for the
file. Without the network, the copy already there runs.
The download is an AppImage. losos-eid unpacks it without running
anything from it, and starts the eID klient in an FHS environment with the
few libraries it does not carry. The eID klient is built only for X11, so
every machine runs xwayland-satellite, an X server that turns each X11
window into a Wayland one for waypipe to carry. The card goes through the
host's reader as described in Smart cards, and the eID
klient asks for its PIN in a window of its own. The host forwards
slovensko.sk's hand-off to it while it is open, as
Hand-offs between apps describes.
Open the eID klient in a machine before signing in on the host. The host reaches only an eID klient that is running.
Signing in with the eID
A person can sign in to the computer itself with their eID card, as they can with a security key. homed does the work, as it does for a key. It encrypts a secret for the person's account to a certificate on a PKCS#11 token, and at login the token decrypts it once the person types the token's PIN. The eID card's certificate for encryption is the one used, because only its key may decrypt.
The card speaks PKCS#11 only through the Ministry's closed module, which
comes in the eID klient download. So the module runs in a machine of its
own, the eID machine (nixos/guest/eid-machine.nix), and no closed code
runs on the host. It is one of the machines the OS runs parts of itself
in (nixos/modules/service-machines.nix). It boots from the host's own
store, the Machines app does not show it, and it has no network card. nixos/modules/eid-signin.nix is the host's side:
- homed loads a PKCS#11 module called
losos-eid. It is p11-kit's remote mode, not a library: every call goes as p11-kit's RPC to/run/losos-eid/pkcs11.sock. Only homed, its helper and homectl load it. Danube and the machines still use the readers through pcscd. - systemd hands each connection on that socket to
losos-tokens pkcs11-relay. The relay lets in root, which is homed, and the person at the seat, and refuses everyone else. It then connects to the eID machine's vsock port 7373 from a port below 1024, which only a program withCAP_NET_BIND_SERVICEcan bind. - In the eID machine, systemd hands each connection to
losos-tokens pkcs11-serve. It checks that the connection comes from the host and from a port below 1024, and then runsp11-kit remotewith the module on it. Nothing in the machine or on the host can skip the relay. - The module reads the card through the host's pcscd, as any machine does. Each connection loads the module for one conversation and exits with it, so the eID machine holds the readers only while the host is reading the card. A machine running the eID klient for slovensko.sk is refused the readers for those few seconds.
The image carries no part of the eID klient. losos-eid-signin turns
eID sign-in on for the person who runs it, with the card in a reader. The
first time, it starts losos-eid-fetch.service, and polkit asks an
administrator to allow it. That service downloads and unpacks the eID
klient as nobody in particular, the way losos-eid does in a machine, and
shares the copy with the eID machine read-only. A path unit starts the eID
machine from then on, at every boot, and a timer looks for a new eID klient
every week. Then losos-eid-signin finds the card's certificates with
p11tool, through the same relay. It picks the one whose key may encipher
and is not an authority's, and enrols it by its ID with
homectl update --pkcs11-token-uri. homed decrypts with the private key
that shares that ID. homectl asks for the card's PIN, to
find the key beside the certificate, and for the person's password. If the card has more than one such
certificate, it lists them and takes the one given on its command line.
At the login screen, pam_systemd_home asks for the token's PIN, and derisk
shows the prompt.
Nobody has signed in with a real eID card yet. If the Ministry's module insists on its own PIN window rather than taking the PIN homed passes it, that window opens in the eID machine, which has no screen, and signing in fails. The password keeps working either way.
Which machine is asking
The host knows which machine a connection comes from by the vsock address the kernel stamps on it. It never trusts anything the machine says about itself. It asks libvirt's read-only connection which running machine has that address, and refuses an address no running machine has.
Tests
nixos/tests/tokens.nix runs both halves on one machine over vsock's
loopback. The host half there takes connections from that kernel as coming
from a machine called work. A Python program makes a FIDO2 key with uhid
that answers INIT and CTAP2's getInfo. The machine half makes its copy,
fido2-token -I reads the key's AAGUID through it, and stopping the key
takes the copy away. vsmartcard's virtual reader in the host's pcscd is
listed through the machine's socket, and so is the virtual card put into it.
A web server on the machine's localhost stands in for the eID klient. The
host's localhost reaches it while it listens, and nothing does once it
stops. The machine's localhost is 127.0.0.2 in that test, because the host
half listens on 127.0.0.1 of the same kernel.
nixos/tests/eid-signin.nix runs the host's side of eID sign-in and the
eID machine's half on one machine, with SoftHSM serving a token in place of
the Ministry's module. The token holds an encryption certificate and a
signing one. losos-eid-signin --print picks the encryption one, homed
enrols it for a person, and the person logs in on the console with the
token's PIN and no password. A program run by nobody gets no token from
the relay. A connection to the eID machine's port from an ordinary port is
refused.
Nothing tests the eID klient itself, which needs a real card and the Ministry's servers.