Self-sovereign infrastructure platform with secure, encrypted NixOS deployments
Version: 1.0
Date: 2025-11-03
Module: keystone.tpmEnrollment
This guide explains how to configure TPM-based automatic disk unlock for your Keystone installation. TPM (Trusted Platform Module) enrollment allows your system to unlock encrypted disks automatically during boot without manual password entry, while maintaining secure recovery credentials for emergency scenarios.
After a fresh Keystone installation, you’ll see a security warning on first login:
TPM ENROLLMENT NOT CONFIGURED
Choose one of these commands to get started:
# Recommended: Generate recovery key
$ sudo keystone-enroll-recovery
# Alternative: Set custom password
$ sudo keystone-enroll-password
Both commands will:
Before enrolling TPM, verify these requirements:
$ sudo bootctl status | grep "Secure Boot"
Secure Boot: enabled (user)
If output shows disabled or setup, complete Secure Boot enrollment first:
$ sudo sbctl status
$ ls /dev/tpm*
/dev/tpm0 /dev/tpmrm0
For VMs: Enable TPM 2.0 emulation in your hypervisor For bare metal: Enable TPM in BIOS/UEFI settings
$ ls /dev/zvol/rpool/credstore
/dev/zvol/rpool/credstore
This is automatically created during Keystone installation with keystone.disko.enable = true.
Recovery keys provide maximum security with minimal memorization burden.
Run:
$ sudo keystone-enroll-recovery
You will receive a recovery key like:
fda7-w4n8-km9p-3jc2-vx5h-7qte-2nuw-8rbg
CRITICAL: Save this key immediately in:
Advantages:
Disadvantages:
Custom passwords are familiar and don’t require external storage.
Run:
$ sudo keystone-enroll-password
Requirements:
Password Examples:
GOOD: "coffee-morning-laptop-window" (28 characters)
GOOD: "MyBlueServer2024Today" (20 characters)
GOOD: "xK9mP2vL4nQ8wR7tY3nB" (20 characters)
BAD: "Password1!" (11 characters - too short)
BAD: "keystone" (prohibited - publicly known)
Advantages:
Disadvantages:
Both enrollment methods follow this workflow:
After enrollment, the system will unlock automatically during normal boots.
You will need your recovery key or custom password in these situations:
If you intentionally changed firmware or Secure Boot configuration:
# 1. Boot with recovery key/custom password (system will prompt)
# 2. Verify Secure Boot is enabled
$ sudo bootctl status | grep "Secure Boot: enabled"
# 3. Remove old TPM keyslot (bound to old PCR values)
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
# 4. Enroll new TPM keyslot with updated PCR values
$ sudo keystone-enroll-tpm
# 5. Reboot to verify automatic unlock
$ sudo reboot
After reboot, the system should unlock automatically with the new PCR measurements.
IMPORTANT: Test your recovery key/password BEFORE you need it:
# 1. Temporarily disable TPM unlock
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
# 2. Reboot - system will prompt for password
$ sudo reboot
# Boot prompt will appear:
# Please enter passphrase for disk credstore: _
# 3. Enter your recovery key at the prompt
# 4. After successful login, re-enable TPM
$ sudo keystone-enroll-tpm
# 5. Reboot to verify automatic unlock works again
$ sudo reboot
Same procedure as recovery key testing above - enter your custom password at the boot prompt.
By default, TPM enrollment uses PCRs 1 and 7. You can customize this in your NixOS configuration:
# configuration.nix
{
keystone.tpmEnrollment = {
enable = true;
# Custom PCR list - choose based on your security vs update trade-off
tpmPCRs = [ 7 ]; # Secure Boot only (most update-resilient)
# tpmPCRs = [ 1 7 ]; # Default: Firmware config + Secure Boot
# tpmPCRs = [ 0 1 7 ]; # More restrictive: Firmware code + config + Secure Boot
};
}
PCR Selection Guide:
| PCR List | Security | Update Resilience | When to Use |
|---|---|---|---|
[7] |
Good | Excellent | Frequent firmware updates, prioritize convenience |
[1 7] |
Better | Good | Default balanced approach (recommended) |
[0 1 7] |
Best | Poor | Maximum security, rare firmware updates |
[7 11] |
Excellent | Poor | Requires signed PCR policies (future feature) |
Trade-offs:
If you modified the disko configuration to use a different credstore path:
{
keystone.tpmEnrollment = {
enable = true;
credstoreDevice = "/dev/mapper/my-custom-credstore";
};
}
Symptoms: Login banner shows “TPM ENROLLMENT NOT CONFIGURED” even after running enrollment
Diagnosis:
# Check if TPM keyslot exists
$ sudo cryptsetup luksDump /dev/zvol/rpool/credstore | grep systemd-tpm2
# Check marker file
$ cat /var/lib/keystone/tpm-enrollment-complete
Solutions:
Symptoms: After enrollment, system still asks for password at boot
Diagnosis:
# Check boot logs for TPM unlock attempts
$ sudo journalctl -b | grep -i "credstore\|tpm"
# Expected: "Failed to activate with TPM2" (indicates PCR mismatch)
Common Causes:
For Virtual Machines:
libvirt/virt-manager:
# Check if TPM is enabled in VM XML
$ virsh dumpxml your-vm-name | grep -A 3 "<tpm"
# If missing, add TPM device:
$ virsh edit your-vm-name
# Add this in <devices> section:
# <tpm model='tpm-crb'>
# <backend type='emulator' version='2.0'/>
# </tpm>
QEMU directly:
# Add TPM emulation to QEMU command:
qemu-system-x86_64 \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
-device tpm-crb,tpmdev=tpm0 \
...
bin/virtual-machine script:
The Keystone bin/virtual-machine script automatically includes TPM 2.0 emulation.
For Bare Metal:
ls /dev/tpm*Check current status:
$ sudo bootctl status
Secure Boot: disabled (setup)
Setup Mode: setup
If in Setup Mode:
If Secure Boot disabled in BIOS:
sudo bootctl statusThe default “keystone” password is:
Enrollment scripts remove this password after adding your secure credential.
TPM Automatic Unlock:
Recovery Credential (recovery key or custom password):
Both together provide defense-in-depth:
Default PCRs 1,7 Provide:
Default PCRs 1,7 Do NOT Protect Against:
For enhanced security, consider:
# configuration.nix
{
keystone.tpmEnrollment = {
# Enable the TPM enrollment module
enable = true;
# PCR list for TPM binding (default: [1 7])
tpmPCRs = [ 1 7 ];
# Credstore device path (default matches disko module)
credstoreDevice = "/dev/zvol/rpool/credstore";
};
}
After enabling the module, these commands become available:
| Command | Purpose |
|---|---|
keystone-enroll-recovery |
Generate recovery key + enroll TPM |
keystone-enroll-password |
Set custom password + enroll TPM |
keystone-enroll-tpm |
Enroll TPM only (advanced users) |
| File | Purpose |
|---|---|
/var/lib/keystone/tpm-enrollment-complete |
Enrollment status marker |
/dev/zvol/rpool/credstore |
LUKS-encrypted credstore volume |
/etc/profile.d/tpm-enrollment-warning.sh |
Login banner script |
Yes! LUKS supports up to 32 keyslots. You can run:
$ sudo keystone-enroll-recovery
# Save recovery key
$ sudo systemd-cryptenroll --password /dev/zvol/rpool/credstore
# Enter custom password
Both credentials will work for recovery scenarios.
# Add new password (will prompt for current password)
$ sudo systemd-cryptenroll --password /dev/zvol/rpool/credstore
# Remove old password (optional - you can keep both)
$ sudo systemd-cryptenroll --wipe-slot=1 /dev/zvol/rpool/credstore
$ sudo systemd-cryptenroll /dev/zvol/rpool/credstore
SLOT TYPE
1 recovery
2 tpm2
# Or detailed view:
$ sudo cryptsetup luksDump /dev/zvol/rpool/credstore
# Remove TPM keyslot
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
# System will now prompt for password on every boot
Prevention: Save recovery key in multiple secure locations immediately
If lost before TPM failure: Generate new recovery key while system is accessible:
$ sudo systemd-cryptenroll --recovery-key /dev/zvol/rpool/credstore
# Save new key immediately
If lost after TPM failure: DATA IS UNRECOVERABLE - no way to unlock disk
No - each system generates a unique recovery key during enrollment. This is a security feature - if one system’s key is compromised, other systems remain secure.
$ sudo cryptsetup luksDump /dev/zvol/rpool/credstore | grep -A 4 systemd-tpm2
0: systemd-tpm2
tpm2-hash-pcrs: 1+7
tpm2-pcr-bank: sha256
tpm2-pin: false
Currently not supported by the enrollment scripts, but can be done manually:
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
$ sudo systemd-cryptenroll \
--tpm2-device=auto \
--tpm2-pcrs=1,7 \
--tpm2-with-pin=yes \
/dev/zvol/rpool/credstore
This will prompt for a PIN during boot before TPM unlock.
Don’t wait for an emergency - test your recovery credential right after enrollment:
# Disable TPM temporarily
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
$ sudo reboot
# Use recovery credential at boot prompt
# Re-enroll TPM after successful login
$ sudo keystone-enroll-tpm
DO:
DON’T:
If you customize tpmPCRs, document the choice:
{
keystone.tpmEnrollment = {
enable = true;
# Using PCR 7 only for maximum update resilience
# Acceptable for home server with physical security
tpmPCRs = [ 7 ];
};
}
Test recovery quarterly:
If you manually enrolled TPM before this module existed:
The module will self-heal:
No action required - existing enrollment continues working.
To disable TPM enrollment features:
{
keystone.tpmEnrollment.enable = false;
}
This will:
To fully remove TPM unlock:
$ sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/zvol/rpool/credstore
After enrollment, typical keyslot configuration:
| Slot | Type | Credential |
|---|---|---|
| 0 | (removed) | Default “keystone” password (removed) |
| 1 | password/recovery | Your recovery key or custom password |
| 2 | tpm2 | TPM-sealed automatic unlock |
The TPM token stored in LUKS header contains:
1. UEFI firmware loads bootloader (lanzaboote)
2. TPM measures boot state into PCRs
3. systemd initrd starts
4. systemd-cryptsetup attempts unlock:
a. Try TPM unlock (if PCRs match policy)
b. If TPM fails, prompt for password/recovery key
5. Credstore unlocks, ZFS key loaded
6. System continues booting
# Check TPM device
$ ls -la /dev/tpm*
# Check TPM capabilities
$ sudo tpm2_getcap properties-variable
# Check PCR values
$ sudo tpm2_pcrread
# Check Secure Boot status
$ sudo bootctl status
# Check LUKS keyslots
$ sudo cryptsetup luksDump /dev/zvol/rpool/credstore
# Check enrollment marker
$ cat /var/lib/keystone/tpm-enrollment-complete
# Check boot logs
$ sudo journalctl -b | grep -i "credstore\|tpm"
If you encounter issues:
journalctl -b | grep tpmdocs/secure-boot.mdmodules/disko-single-disk-root/Version: 1.0 Last Updated: 2025-11-03 Maintainer: Keystone Project