Definitively switch PIV->FIDO2 for SSH

And move Security section from home/home
This commit is contained in:
2026-04-29 17:14:24 +02:00
parent 090889f324
commit 757602449b
3 changed files with 151 additions and 4 deletions

134
README.md
View File

@@ -1,10 +1,13 @@
# User and machine configs
## Installation
Step 1.
```
cd
git clone git@github.com:dotdoom/dotfiles.git
cd dotfiles
git submodule update --init
```
Step 2 - stow.
@@ -24,3 +27,132 @@ nix run \
--extra-experimental-features 'nix-command flakes' \
--flake .#artem@deimos
```
## Security
Risks taken (disclaimer):
- hardware attestation (this documentation and some code lists almost precisely
the hardware I use, and the way I use it)
- privacy (committing public keys technically allows verifying which servers I
have access to).
### SSH client
- `.ssh/id_XXXXX`
Static keys (stored in bitwarden). These have to be passphrase-protected when
stored on a disk; use `ssh-keygen -p -f .ssh/id_XXXXX`.
The use of these keys is expected to be low, but SSH may always fall back to
it, in which case you have to remember and type the passphrase. Use
`ssh-add -D` to remove unencrypted identity from memory afterwards. Saving a
passphrase in keychain is possible, but using Security Enclave is recommended
instead.
- Apple Security Enclave
This is the most used but also most ephemeral key, because it's bound to a
machine. It's provided by Secretive app acting as an SSH agent and the private
key is stored in Apple Security Enclave on a MacBook, requiring a fingerprint
touch on SSH.
- Yubikey
Yubikey PIV can be used as Smart Card, requiring a daemon and PGP
infrastructure. It's useful when a root entity is signing certificates from
multiple Yubikey owners, i.e. in large enterprise.
Another obstacle is that we already use Secretive as our SSH agent, and it
doesn't mux well with Yubikey agent, see `mac-portable.nix` for details.
Instead of PIV, we use FIDO2 slots on Yubikey to generate resident (i.e.
stored solely on Yubikey itself) SSH keys using modern OpenSSH client built-in
FIDO2 support. This doesn't need an agent or a background daemon.
To generate a new key:
```
ssh-keygen -t ed25519-sk -O resident -O verify-required
# Omit "-O verify-required" to skip PIN; only if key is physically safe.
# Add "-O no-touch-required" to skip touch.
```
To restore "private key" files (remove `_rk` and drop them into `.ssh`):
```
ssh-keygen -K
```
To list or delete FIDO2 (and WebAuthn) credentials:
```
ykman fido credentials list
ykman fido credentials delete abcdef123
```
### SSH from VM
For trusted VMs, ssh-agent forwarding is configured in `.ssh/config.d/local`:
```
Host deimos
ForwardAgent yes
```
### Commit signing
Use SSH keys (from Apple SE and Yubikey) to sign commits. Make sure to generate
a different set of keys for signing than the one you use for authentication.
### AGE encryption
All files are encrypted using some sort of hardware. Using an on-disk key alone
is not sufficient.
Each of the AGE plugins generates and subsequently during decryption uses a
(usually not sensitive) identity, which contains metadata about how to access
the specific cryptography on underlying hardware. Use the plugin directly to
initialize the hardware:
- `age-plugin-se keygen --access-control any-biometry-or-passcode`
- `age-plugin-yubikey --generate`
The identities which can be used to decrypt the secrets for editing (i.e.
Yubikey PIV, Apple SE) are concatenated into a single file:
- MacOS: `~/Library/Application Support/sops/age/keys.txt`
- Linux: `~/.config/sops/age/keys.txt`
which is not secret. You can only decrypt the data on the device where Yubikey
is plugged into, or one that has Apple SE or a TPM.
For Yubikey, you can also retrieve the identity using `age-plugin-yubikey -i`,
feeding the output directly into the identities file. To manage a Yubikey:
```
# Disable unused features
$ ykman config nfc --disable OTP
$ ykman config usb --disable OTP
# Check what's already there; slot 9A can be used by `O=yubikey-agent`; we don't
# rely on that key at the moment, see SSH above
$ ykman piv info
# To delete a key (age-plugin-yubikey uses 82 for slot 1, 83 for slot 2 etc).
$ ykman piv certificates delete 82
# Fully reset (initialize).
$ ykman piv reset
# See Bitwarden for keys
$ ykman piv access change-pin
$ ykman piv access change-puk
```
### Remote decryption
There's an ephemeral SSH server configured in `.ssh/ephemeral_sshd` which will
listen on localhost. If you port-forward it to remote machine, it can be
configured to run certain binaries (age plugins) through a reverse SSH
connection, which enables the use of local hardware to decrypt remote secrets.

View File

@@ -11,6 +11,7 @@
wget
gemini-cli
silver-searcher
yubikey-manager
];
home.activation.stowLegacy = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if [ -d "$HOME/dotfiles" ]; then

View File

@@ -12,6 +12,10 @@
# Faster and more feature-rich than Terminal.
iterm2
# Newer OpenSSH client to support FIDO2 keys.
openssh
libfido2
];
targets.darwin.defaults."com.googlecode.iterm2" = {
@@ -45,11 +49,21 @@
};
programs.zsh.envExtra = ''
# If Secretive doesn't recognize your Yubikey PIV, it's possible you
# generated it using yubikey-agent and that did not update CHUID. Simply
# running 'ykman piv objects generate chuid' should be sufficient.
# Can't use ssh-agent-mux to mux Secretive and yubikey-agent:
# https://github.com/overhacked/ssh-agent-mux/issues/56
# export SSH_AUTH_SOCK=~/.ssh/ssh-agent-mux.sock
# Can't use Secretive to SSH using PIV from Yubikey:
# https://github.com/maxgoedjen/secretive/issues/330
#
# If PIV entry was generated by yubikey-agent, Secretive may not see it at
# all. Running 'ykman piv objects generate chuid' should fix that.
# https://github.com/maxgoedjen/secretive/issues/333
# See README.md "Security" section to learn how we create keys.
# Setting IdentityAgent in SSH config achieves a similar result, but doesn't
# work with commit signing.
export SSH_AUTH_SOCK=~/Library/Containers/com.maxgoedjen.Secretive.SecretAgent/Data/socket.ssh
'';