Skip to content

Security

Two independent layers: the vault encrypts everything at rest, and OpenPGP signs and encrypts individual messages. Both drive tools you already run (hdiutil, cryptsetup, gocryptfs, gpg-agent); Antiphon never handles secret key material itself.

The vault

The store at $XDG_DATA_HOME/antiphon/store holds your Maildir, notmuch index, OAuth tokens and account state. With a vault, it is ciphertext on disk when locked and decrypted in place only while the daemon runs. A backup tool copying a locked store copies ciphertext.

antiphon doctor   # says whether the store is a vault or plain

Choose a backend

[vault]
backend = "auto"   # auto picks the right one per platform
Backend Platform Notes
apfs macOS Encrypted APFS sparse image via hdiutil, AES-256, no admin rights
luks2 Linux LUKS2 via cryptsetup; open/mount use a narrow sudo allowance
gocryptfs any FUSE, rootless, the portable fallback

Set it up

# set backend and passphrase_cmd in [vault] first, then:
antiphon vault create          # create and mount the vault
antiphon doctor --init-store   # lay out the store inside it
antiphond &                    # unlocks on start, seals on stop
antiphon

antiphon vault create will not shadow an existing plain store. To migrate one, move it aside, create the vault, then copy the mail back in while the vault is mounted.

Unlock with a passphrase

Every vault has a passphrase, and it always works. The daemon reads it from passphrase_cmd, a command that prints the secret; the passphrase itself is never stored in config.

[vault]
backend = "auto"
passphrase_cmd = "pass show antiphon/vault"
idle_lock_minutes = 30   # 0 = stay open until the daemon stops

Any secret-printing command works:

pass show antiphon/vault
secret-tool lookup service antiphon-vault
security find-generic-password -w -s antiphon-vault   # macOS

antiphon setup creates that macOS Keychain item for you. With idle_lock_minutes above zero the vault seals after that many minutes without a connected client, and reopens when a client next connects.

Unlock with a hardware token

Beyond the passphrase, a hardware token can open the vault. On macOS, antiphon vault touchid-enrol stores the passphrase behind a Touch ID Keychain item. On any platform, antiphon vault yubikey-enrol seals it under a YubiKey's FIDO2 hmac-secret: enrol a primary and a backup key, and whichever is present opens the vault with a single touch.

antiphon vault touchid-enrol    # macOS Touch ID
antiphon vault yubikey-enrol    # FIDO2 YubiKey (run once per key)

List the methods to try, in order, and give the YubiKey's PIN its own command:

[vault]
unlock = ["touchid", "yubikey", "passphrase"]
yubikey_pin_cmd = "pass show antiphon/yubikey-pin"

A hardware method that is cancelled, absent or wrong falls through to the next, and finally to the passphrase, never past the vault.

OpenPGP

Verification runs in the client (pure Rust). Signing and decryption are handed to your running gpg-agent, so your keyring, smartcard and pinentry behave exactly as elsewhere.

Verify signatures

Antiphon trusts only the certificates you place in $XDG_CONFIG_HOME/antiphon/pgp/. Add a correspondent's key:

gpg --armor --export alice@example.com \
    > ~/.config/antiphon/pgp/alice.asc

The pager then shows Good signature from … for a message that verifies against a cert there, Unknown signature for a signer you have not added, and BAD signature when a trusted key matches but the content does not. PGP/MIME and inline signatures are both handled. There is no web-of-trust or TOFU: a cert is trusted because you put it there.

Sign and encrypt

Turn signing on per identity in the account file:

[[identity]]
address = "you@example.com"
pgp_sign = true
pgp_key = "0xFINGERPRINT"   # optional; pins a specific key

Override the default for the next message only:

:sign   :nosign   :encrypt   :noencrypt

The compose status line shows the live plan. Sealing happens through gpg-agent when the editor closes, so pinentry or a smartcard touch appears as usual. Encryption needs a cert for every To and Cc address; if one is missing or refused the message aborts to a draft naming the problem rather than sending plaintext. Received encrypted mail decrypts through the agent when opened.

antiphon doctor   # keyring size, gpg-agent reachability, key count

Long-term archives

The vault protects the store in place. Mail you deliberately keep as a long-term archive is protected by age instead: antiphon export writes an age-encrypted archive and antiphon view reads it back without the account. See export and archives.