70.1.35. pepsi-keys

manage the end-to-end key store: identities, peer keys and anchors

Manual section:

1

70.1.35.1.1. Name

pepsi-keys - generate, import, publish and retire end-to-end key material.

70.1.35.1.2. Synopsis

pepsi-keys [GLOBAL-OPTIONS] identity list [–address ADDRESS] [–json]

pepsi-keys [GLOBAL-OPTIONS] identity show IDENTITY-ID [–json]

pepsi-keys [GLOBAL-OPTIONS] identity generate ADDRESS –protocol PROTOCOL [–name NAME] [–days N] [–no-publish] [–csr] [–shared | –split]

pepsi-keys [GLOBAL-OPTIONS] identity import ADDRESS –protocol PROTOCOL –purpose PURPOSE –public FILE [–private FILE] [–algorithm NAME] [–primary] [–no-publish]

pepsi-keys [GLOBAL-OPTIONS] identity export IDENTITY-ID [–private] [–pem]

pepsi-keys [GLOBAL-OPTIONS] identity csr IDENTITY-ID

pepsi-keys [GLOBAL-OPTIONS] identity revoke IDENTITY-ID [–reason TEXT]

pepsi-keys [GLOBAL-OPTIONS] identity forget-private IDENTITY-ID [-y]

pepsi-keys [GLOBAL-OPTIONS] identity set-primary IDENTITY-ID

pepsi-keys [GLOBAL-OPTIONS] identity publish IDENTITY-ID [–off]

pepsi-keys [GLOBAL-OPTIONS] identity delete IDENTITY-ID [-y]

pepsi-keys [GLOBAL-OPTIONS] identity retire

pepsi-keys [GLOBAL-OPTIONS] peer list [–address ADDRESS] [–json]

pepsi-keys [GLOBAL-OPTIONS] peer show ADDRESS [–protocol PROTOCOL] [–json]

pepsi-keys [GLOBAL-OPTIONS] peer import ADDRESS –protocol PROTOCOL –file FILE [–pin]

pepsi-keys [GLOBAL-OPTIONS] peer pin | unpin | remove PEER-KEY-ID

pepsi-keys [GLOBAL-OPTIONS] peer refresh [–address ADDRESS | –expire PEER-KEY-ID] [–expiring-within-days DAYS] [–limit N]

pepsi-keys [GLOBAL-OPTIONS] peer prune [–retain-days DAYS] [–dry-run]

pepsi-keys [GLOBAL-OPTIONS] ca list [–json]

pepsi-keys [GLOBAL-OPTIONS] ca add FILE [–label TEXT]

pepsi-keys [GLOBAL-OPTIONS] ca enable | disable | remove CA-ID

pepsi-keys [GLOBAL-OPTIONS] wrap rotate [–dry-run]

pepsi-keys [GLOBAL-OPTIONS] dns ADDRESS

70.1.35.1.3. Description

pepsi-keys is the operator’s tool for Pepsi’s end-to-end cryptography key store — the three tables that hold every key OpenPGP and S/MIME processing needs:

pepsi.crypto_identity

The key pairs of the addresses we serve, private half included and wrapped at rest. Used to sign outbound mail and to decrypt inbound mail.

pepsi.peer_key

Remote correspondents’ public keys and certificates, however they were obtained, each with the source it came from, a validity verdict and a cache deadline. Used to encrypt outbound mail and to verify inbound signatures.

pepsi.ca_trust

The CA certificates an inbound S/MIME chain must reach for its signature to count as trusted.

The tool is not a stage. It manages rows directly, in the shape of pepsi-whitelist(1) and pepsi-settings(1), connecting to the same database as the other components through the shared [pepsi-postgres] section and reading the [pepsi] CRYPTO_* options and the [pepsi-crypto] section described in pepsi.conf(5).

It is also the only program in the tree that generates key material, which is one of the two reasons it is a standalone binary rather than part of the unified pepsi executable; the other is that a site may install it setuid (see Privileges).

70.1.35.1.3.1. Capability, not shape

An address’s material is stored as one row per capability, never as “the identity”. Each row’s purpose is sign, encrypt or both:

  • an OpenPGP identity is one both row — a transferable key whose sign-capable primary carries an encryption subkey, so the split is intrinsic and costs nothing;

  • an S/MIME identity is normally two rows, one certificate with digitalSignature and one with keyEncipherment/keyAgreement;

  • with [pepsi] CRYPTO_SMIME_SHARED_KEY = yes it is instead one both certificate carrying both key usages (RSA only).

That option decides only what newly created identities look like. Both shapes are supported permanently and may coexist for one address — an old shared certificate still valid alongside a freshly issued split pair — so every lookup matches a capability (sign or both; encrypt or both) rather than counting rows. Listings and identity show print the purpose for exactly this reason.

70.1.35.1.3.2. Retirement is asymmetric

Pepsi delivers plaintext to the mailbox, so a private decryption key is needed only for mail still in flight and for whatever ciphertext an operator has archived — never to read one’s own mailbox. A private signing key, once retired, can do nothing legitimate at all: nothing ever re-signs old mail. So at expiry or revocation:

  • a sign row’s private half is destroyed, and only the public half is kept so old signatures can still be verified;

  • an encrypt row’s private half is retained;

  • a both row follows the decryption rule — destroying it would take the ability to decrypt with it — which is the price of shared-certificate mode: revoking a compromised signing key there also ends the ability to receive new encrypted mail.

private_purged_at records when material was dropped, so “deliberately destroyed” stays distinguishable from “never held”; both identity list and identity show report it.

70.1.35.1.3.3. Ownership

An identity is keyed on the lower-cased address. When pepsi-common’s locality rules ([pepsi-crypto] LOCAL_DOMAINS, RECIPIENT_DELIMITER and TARGETS, defaulting to [pepsi-ingress] ACCEPTED_DOMAINS and the /etc/login.defs uid range) resolve that address to a local account, the passwd login is recorded in crypto_identity.login; otherwise the column is NULL — a role address, a hosted domain, an Exchange-fronted deployment.

That column is the access rule. An unprivileged caller may see and change only identities whose login is their own, resolved from the real uid’s passwd entry — not $USER, not an argument, not the effective uid. An identity with no owning account belongs to nobody in particular and is therefore operator-only: treating “unowned” as “everybody’s” would make every role address writable by every local user.

root, and the pepsi-crypto, pepsi and pepsi-owner accounts, may manage every identity. Peer keys and trust anchors have no per-user namespace — they are somebody else’s key, or deployment-wide policy — so every peer and ca subcommand, list included, is operator-only, as are wrap rotate and identity retire.

70.1.35.1.3.4. Privileges

Reaching a private key takes two things, and they are guarded separately:

  • the database role pepsi-crypto, the only one granted the crypto_identity.private_wrapped column. Every other role — including the pepsi account every ordinary stage worker runs as — holds a column-level grant that omits it, so a compromise elsewhere in the pipeline yields the public half of every identity and nothing more (see pepsi-setup(1));

  • the key-encryption key, which lives only in the filesystem, in secrets.d/pepsi-crypto.secret.

The database alone therefore yields nothing but ciphertext, and the key-encryption key alone yields nothing at all.

pepsi-keys assumes the pepsi-crypto identity only for the duration of each database call. Run as root it becomes that account for the call (root has no database role of its own) and returns afterwards; run as pepsi-crypto or pepsi-owner it simply connects as itself; run from a setuid install it raises its effective ids to the binary’s owner. PostgreSQL peer authentication keys off the effective uid, not the gid, so a setgid bit would grant the group that guards the secret fragment but not the database identity — a program that must be this role has to be this account.

As shipped the binary carries no setuid bit (mode 0755): one binary able to open every private key in a deployment is a far larger prize than pepsi-whitelist(1)’s single table, so the default is that only the operator runs it. A site that wants ordinary users to manage their own identities makes it setuid pepsi-crypto; the ownership rule above is enforced either way, so that is a site decision rather than a code change. When it is setuid, the same three precautions pepsi-whitelist(1) takes apply: the effective ids are dropped to the invoking user before anything else happens, the environment variables that steer configuration discovery (HOME, XDG_CONFIG_HOME), ${DATADIR} expansion (PATH) and libpq (PG*) are removed, and -c/–config is refused for unprivileged callers — the configuration names the database and the key-encryption key.

70.1.35.1.4. Commands

70.1.35.1.4.1. Identity commands

identity list [–address ADDRESS] [–json]

List identities as a table: id, address, protocol, purpose, status, whether the row is the primary one for its capability, whether it is published, the state of its private half (yes/no/purged) and its expiry. An unprivileged caller sees only their own. –address restricts the listing to one address; –json emits the rows as JSON instead.

identity show IDENTITY-ID [–json]

Print one identity in full, including its fingerprint, algorithm, custody, wrap_key_id, timestamps, any revocation reason, and the Web Key Directory local-part hash the address is served under.

identity generate ADDRESS –protocol openpgp|smime [OPTIONS]

Create key material for ADDRESS and store it, private half wrapped. The new material becomes the primary for its capability; nothing existing is rewritten or replaced, so the previous material stays usable for decrypting what was already sent to it.

OpenPGP produces one both row using [pepsi] CRYPTO_OPENPGP_ALGORITHM (ed25519, the default, or rsa at CRYPTO_GENERATE_RSA_BITS, which must be 2048, 3072 or 4096). S/MIME produces the signing/encryption pair using CRYPTO_SMIME_ALGORITHM (rsa, p256 or p384), each with a self-signed certificate for immediate use.

–name NAME

Display name for the OpenPGP User ID (Name <address>) or the certificate subject. Without it the User ID is the bare address.

–days N

Lifetime of the generated material. Defaults to [pepsi-crypto] IDENTITY_VALIDITY_DAYS (730, two years).

–no-publish

Do not serve the public half over the Web Key Directory or upload it. By default a new identity is published — a key nobody can fetch cannot be encrypted to.

–csr

Also print a PKCS#10 certificate signing request over each generated key, so a real CA can issue a replacement certificate for the same key without re-keying. S/MIME only; OpenPGP has no such object.

–shared

Force one S/MIME certificate carrying both key usages, whatever CRYPTO_SMIME_SHARED_KEY says. Refused with an elliptic-curve CRYPTO_SMIME_ALGORITHM: one EC key doing both ECDSA and ECDH is cross-algorithm key reuse that several S/MIME clients reject.

–split

Force the separate signing and encryption certificates. Mutually exclusive with –shared; with neither, the configured default applies.

identity import ADDRESS –protocol P –purpose PURPOSE –public FILE [OPTIONS]

Store material generated elsewhere. PURPOSE is sign, encrypt or both and must describe what the material can actually do — it is what every later lookup matches on. The public half is an OpenPGP transferable key or an X.509 certificate (DER or PEM, detected); - reads standard input. The fingerprint is derived from the material itself: the OpenPGP fingerprint, or the SHA-256 of the certificate DER.

–private FILE

The private half — an OpenPGP transferable secret key, or a PKCS#8 key — which is wrapped before it is stored. Without it only the public half is kept, which is enough to publish but not to sign or decrypt.

–algorithm NAME

Algorithm name to record. Informational; defaults to imported.

–primary

Make this the material used for new messages of its capability.

–no-publish

Do not publish the public half.

identity export IDENTITY-ID [–private] [–pem]

Write the material to standard output in its own binary form — OpenPGP packets or X.509 DER — so it can be piped straight into another tool.

–private

Export the private half instead of the public one, unwrapping it first. Fails when the row holds none, naming which of “never held” and “purged” applies.

–pem

Wrap X.509 material in a PEM block. OpenPGP material has no PEM form and is refused with a pointer to gpg --enarmor.

identity csr IDENTITY-ID

Print a PKCS#10 certificate signing request over an identity’s existing private key, with the key usage its purpose implies. This is the path that matters once an identity is in service: the self-signed certificate got the organisation going, and the same key — already published, already used to receive encrypted mail — is later presented to a real CA without re-keying. S/MIME identities only, and only while the private half is still held.

identity revoke IDENTITY-ID [–reason TEXT] [–upload]

Withdraw an identity. The row becomes revoked and stops being primary; the public half is kept so signatures made before the revocation can still be checked. A sign row’s private half is destroyed here and now; an encrypt or both row keeps its private half, and the command says so. TEXT is recorded with the row.

–upload additionally builds a revocation certificate — signed by the key it retires, with TEXT as its reason — and publishes it to the configured key server. That is the only remedy for a key already uploaded: a key server can be told a key is revoked, never told to forget it.

The upload happens before the local revocation, and that ordering is not incidental: revoking a sign identity destroys the private key the revocation certificate is signed with. If the upload fails, nothing is revoked, so the key-server problem can be fixed and the command re-run. Revoking without –upload and then wanting a published revocation is not recoverable.

identity forget-private IDENTITY-ID [-y, –yes]

Destroy an identity’s private key permanently, leaving the public half. This is the only way a decryption key leaves the store, and it is irreversible: anything still queued in pepsi.ingress, anything in an encrypted archive and any message redelivered later from a backup becomes unreadable. Mail already delivered is unaffected. The command prints exactly that and asks for confirmation; –yes skips the question, and with no terminal and no –yes the answer is no, so an unattended run cannot destroy key material because nobody was there to object.

identity set-primary IDENTITY-ID

Make this the material used for new messages of its (address, protocol, purpose). Only an active identity may be made primary. The previous primary for the same triple is demoted.

identity publish IDENTITY-ID [–wkd] [–vks] [-y, –yes]

Serve the identity’s public half over the Web Key Directory. This is the reversible channel — our own server, our own domain — and it is what –wkd names; it is also the default, and the flag exists only so a command can say which channel it means. The material stays usable either way.

–vks additionally uploads the key to the configured key server and asks it to mail the confirmation link to the address. That is irreversible: a key server can later be told the key is revoked, but never told to forget it, and the address becomes a permanent public record. The command prints exactly what cannot be taken back and asks for confirmation; –yes skips the question, and with no terminal and no –yes the answer is no. OpenPGP only — there is no key server for S/MIME, whose publication channel is the SMIMEA record dns prints.

identity publish –retry [–dry-run]

Work through every published OpenPGP identity that is not verified on the key server yet, instead of one. This is the form to run from cron (hourly is ample), and the path [pepsi-keys] VKS_PUBLISH = yes works through; it does nothing at all while that switch is off.

It exists because publication is a two-step protocol: an upload returns a token, only the token authorises the confirmation mail, and only a followed link makes the server serve the key by address. A key uploaded but never confirmed is stored yet unfindable. Each round first asks the key server whether the address is already served — so a confirmation followed by pepsi-stage-vks-confirm(1), by a human, or on an earlier run whose database write did not land all end the loop — and otherwise uploads and requests verification. Identities are spaced by VKS_RETRY_INTERVAL, at most VKS_BATCH per run, and abandoned after VKS_MAX_ATTEMPTS with the reason left on the row for identity show. –dry-run lists what would be published and changes nothing. Operator-only.

identity unpublish IDENTITY-ID

Stop serving the identity over the Web Key Directory, and forget its key-server bookkeeping so the retry job leaves it alone and a later re-publication starts from a clean slate.

It does not remove anything from a key server, because nothing can. When the identity had been uploaded the command says so and points at identity revoke –upload, which is the only way to tell the world the key is dead.

identity delete IDENTITY-ID [-y, –yes]

Remove the row outright, public half included. Confirmed like forget-private. Prefer revoke, which keeps the public half so old signatures remain checkable; a deleted identity leaves nothing behind at all.

identity retire

Run the retirement sweep over every identity: everything past its expires_at becomes expired, private signing material is destroyed and stamped, and decryption material (including a shared both row’s) is left alone. Intended for a periodic timer as much as for the command line; identity revoke applies the same rule inline for one identity. Operator-only.

70.1.35.1.4.2. Peer commands

peer list [–address ADDRESS] [–json]

List cached peer keys: id, address, protocol, source, validity, whether the lookup that produced it was DNSSEC-validated, whether it is pinned, and the fingerprint.

peer show ADDRESS [–protocol PROTOCOL] [–json]

Show the keys that would actually be used for ADDRESS, after the matching rule: an exact address match always wins, and a *@domain row is consulted only when there is no exact one (it is flagged as a domain-wide fallback in the output). PROTOCOL defaults to openpgp.

peer import ADDRESS –protocol P –file FILE [–pin]

Store a correspondent’s key by hand (- reads standard input). The row is recorded with source manual, which outranks every discovery mechanism, and is the only source permitted to carry a *@domain address — a database constraint, not a convention, so no discovery answer for one address can ever be generalised to a whole domain.

A key that conflicts with one already stored replaces it only if it outranks everything there; a pinned row is never displaced, and the command says which of stored, refreshed, replaced and kept happened. (The single exception to “only if it outranks” is the Autocrypt tier’s newest-wins rule, which never involves a manual key in either direction — see pepsi-keydisc(1).)

–pin

Pin the imported key, so discovery never replaces it.

peer pin PEER-KEY-ID, peer unpin PEER-KEY-ID

Set or clear the pin on one stored key.

peer refresh [–address ADDRESS] [–expire PEER-KEY-ID] [–expiring-within-days DAYS] [–limit N]

Re-validate cached peer keys. It only ever refreshes keys that already exist: it never discovers a key for an address that has none. Pepsi looks an address up when it has mail for it, which is why discovery is driven by the pipeline (pepsi-keydisc(1)) rather than by this command.

Three shapes, selected by the flags:

–address ADDRESS

Run the whole discovery fan-out for one address in this process — every method in [pepsi-keydiscovery] SOURCES at once, with the rank-grace stop rule — and store what it finds with the right source and DNSSEC flag. One line per method is printed: the protocol and fingerprint (marked (dnssec) for an AD-validated DANE answer), no key, or the error. This also settles the address’s discovery request, so it releases any message parked on it. An address excluded by ALLOW_DOMAINS/DENY_DOMAINS is refused rather than queried.

–expire PEER-KEY-ID

Clear one row’s cache deadline so the next message for that address re-fetches. No network I/O at all, and mutually exclusive with –address.

(neither flag)

Sweep: every key that is due — its refresh_after has passed, or its own expiry falls within –expiring-within-days (default 7) — is put through the same fan-out, up to –limit keys (default 100). Pinned and hand-entered (manual/api) keys are skipped, and so are *@domain rows: an operator put those there, and no discovery answer outranks that, so fetching would be pure cost. The number re-validated is printed. This is the natural body of a periodic timer.

peer prune [–retain-days DAYS] [–dry-run]

Delete expired peer keys and aged-out discovery requests — two tables, one job. A peer key that expired more than DAYS (default 7) ago is removed unless it is pinned; on the pepsi.key_request side both a settled row whose negative entry has aged out past the same window and a pending row abandoned that long past its deadline have nothing left to say and go too. –dry-run reports how many keys carry an expiry and are unpinned, and removes nothing.

peer remove PEER-KEY-ID

Delete one stored key.

70.1.35.1.4.3. Trust-anchor commands

ca list [–json]

List the trust anchors with their id, whether they are enabled, their label and their subject.

ca add FILE [–label TEXT]

Add a CA certificate (DER or PEM; - reads standard input) as a trust anchor. A certificate without basicConstraints CA:TRUE is refused: no chain could ever end at it. Idempotent on the certificate’s SHA-256 — adding the same anchor twice only updates the label.

ca enable CA-ID, ca disable CA-ID

Hand an anchor to the verifier, or stop doing so while keeping the row for the record.

ca remove CA-ID

Delete an anchor.

Pepsi ships no default anchor set, deliberately. A signature that chains to a CA nobody chose is reported untrusted rather than bad, so an empty store degrades to “we cannot vouch for this” — and an operator who adds an anchor is making a decision rather than inheriting one.

70.1.35.1.4.4. Key-encryption-key commands

wrap rotate [–dry-run]

Re-wrap every stored private key under the current [pepsi-crypto] KEY_WRAP_KEY_ID. Rotation is incremental by construction, because each row records the key that sealed it: configure the new secret as KEY_WRAP_SECRET and keep the old one beside it as KEY_WRAP_SECRET_<OLD-ID>, point KEY_WRAP_KEY_ID at the new id, and run this. Between the configuration change and the sweep the deployment keeps working — new material is sealed under the new key, old material still opens under the retired one — and only afterwards may the retired secret be removed.

Rows whose key-encryption key has no configured secret are reported and left untouched rather than failing the run; so are rows re-wrapped concurrently (each update is guarded on the old key id, so a newer wrap is never written over).

–dry-run

List what would be re-wrapped, and change nothing.

70.1.35.1.4.5. DNS publication

dns ADDRESS

Print, in zone-file format, the DNS records that publish every published, active identity of ADDRESS: an OPENPGPKEY record (RFC 7929) for OpenPGP material and an SMIMEA record (RFC 8162, in the TLSA 3 0 0 — domain-issued certificate, full certificate, exact match — form) for S/MIME. Both are published under a hashed owner name, the hex of the first 28 octets of the SHA-256 of the local part, so the zone does not enumerate the addresses it serves.

Publish these only in a DNSSEC-signed zone, which the output repeats as a comment: an unsigned key record is a key from whoever can answer for the zone, which is no improvement on having no key at all.

The DNS owner-name hash is case-sensitive (RFC 7929 §3), unlike the Web Key Directory hash of the same local part, which lower-cases. The two are computed by different functions on purpose.

pepsi-setup(1) prints the same records for every published identity as part of its DNS output, capped at a readable number; this command is how to get one specific address in a large deployment.

70.1.35.1.5. Key-server configuration

Web Key Directory publication needs no configuration: it follows each identity’s published flag and is served by pepsi-httpd(1). Key-server publication does, because it cannot be undone, and its options live in [pepsi-keys]:

VKS_PUBLISH (default no)

Upload every published OpenPGP identity automatically, and keep retrying until the address is verified. Deliberately an operator-level switch rather than a per-identity one: an upload can be revoked but never withdrawn, so the choice belongs to whoever understands that — and a per-user opt-in is a step essentially nobody takes, so making it per-user would mean key-server discoverability never materialised at all. Leaving it off keeps identity publish –vks available as a confirmed one-off.

VKS_SERVER (default: the first [pepsi-keydiscovery] VKS_SERVERS entry)

Where uploads go; https:// only. One server, not a list: publishing the same key to several multiplies an irreversible act, and each then has to be kept up to date with revocations.

VKS_MAX_ATTEMPTS (default 5), VKS_RETRY_INTERVAL (default 6 h), VKS_BATCH (default 50)

How hard, how often and how many at a time identity publish –retry tries. Note that durations reject calendar units: 6 h parses, 1 d does not.

70.1.35.1.6. Global Options

These global options precede the subcommand (a trailing flag is rejected).

-c FILE, –config FILE

Read the configuration from FILE instead of searching the default locations. Rejected for unprivileged callers when this program is installed setuid: the configuration names the database connection, every ${…}-expanded path and the key-encryption key itself.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity. LOGLEVEL is one of error, warn, info, debug or trace (default: info).

-v, –verbose

Show log messages from all sources, including third-party libraries.

-h, –help

Print a usage summary and exit.

-V, –version

Print the version and exit.

70.1.35.1.7. Exit Status

0

Successful completion. A destructive command that was declined at its confirmation prompt also exits 0, having changed nothing.

1

An error occurred: a malformed configuration file, an inadmissible option combination, an address that resolves to another user’s account, key material that cannot be parsed, a key-encryption key that is missing or does not open a row, or a failed database connection or query. The reason is written to the log.

70.1.35.1.8. Files

When –config is not given, the first existing file from the following list is used:

  • $XDG_CONFIG_HOME/pepsi.conf

  • $HOME/.config/pepsi.conf

  • /etc/pepsi/pepsi.conf

secrets.d/pepsi-crypto.secret

The key-encryption key, beside the configuration file and pulled into the [pepsi-crypto] section with an @inline-secret@ directive. It is kept out of the world-readable configuration because it is the one secret that opens every stored private key; pepsi-setup(1) gives it mode 0640 and hands it to the pepsi-crypto account on every run. Back it up separately — see the Key management chapter of the manual for what its loss costs.

No other file is read or written: all key material lives in the database.

70.1.35.1.9. Examples

Generate an OpenPGP identity and check that GnuPG accepts the public half:

pepsi-keys -c /etc/pepsi/pepsi.conf identity generate alice@example.org \
    --protocol openpgp --name 'Alice Example'
pepsi-keys -c /etc/pepsi/pepsi.conf identity list --address alice@example.org
pepsi-keys -c /etc/pepsi/pepsi.conf identity export 1 | gpg --import

Generate the S/MIME pair, printing a certificate request over each key for a real CA:

pepsi-keys -c /etc/pepsi/pepsi.conf identity generate alice@example.org \
    --protocol smime --csr

Install the certificate that CA issued, over the key that is already in the store:

pepsi-keys -c /etc/pepsi/pepsi.conf identity import alice@example.org \
    --protocol smime --purpose sign --public /tmp/alice-sign.pem --primary

Publish the address in DNS (signed zone only):

pepsi-keys -c /etc/pepsi/pepsi.conf dns alice@example.org >> example.org.zone

Withdraw a compromised signing key, then confirm the private half is gone:

pepsi-keys -c /etc/pepsi/pepsi.conf identity revoke 1 --reason 'laptop stolen'
pepsi-keys -c /etc/pepsi/pepsi.conf identity show 1

Trust a correspondent’s key by hand and pin it against later discovery:

pepsi-keys -c /etc/pepsi/pepsi.conf peer import bob@example.com \
    --protocol openpgp --file bob.pgp --pin

Look one correspondent up with every enabled method and store the result:

pepsi-keys -c /etc/pepsi/pepsi.conf peer refresh --address bob@example.com

Keep the cache honest from a periodic timer: re-validate what is due, then drop what has aged out:

pepsi-keys -c /etc/pepsi/pepsi.conf peer refresh
pepsi-keys -c /etc/pepsi/pepsi.conf peer prune

Add an S/MIME trust anchor and see what is trusted:

pepsi-keys -c /etc/pepsi/pepsi.conf ca add /etc/ssl/certs/our-ca.pem --label 'Our CA'
pepsi-keys -c /etc/pepsi/pepsi.conf ca list

Rotate the key-encryption key, looking first:

pepsi-keys -c /etc/pepsi/pepsi.conf wrap rotate --dry-run
pepsi-keys -c /etc/pepsi/pepsi.conf wrap rotate

Expire everything that is past its validity (from a periodic timer):

pepsi-keys -c /etc/pepsi/pepsi.conf identity retire

Publish one key to the key server, then check it arrived:

pepsi-keys -c /etc/pepsi/pepsi.conf identity publish 1 --vks
pepsi-keys -c /etc/pepsi/pepsi.conf identity show 1

Drive every pending key-server verification to completion (from cron):

pepsi-keys -c /etc/pepsi/pepsi.conf identity publish --retry

Stop publishing a key, and tell the world the uploaded one is dead:

pepsi-keys -c /etc/pepsi/pepsi.conf identity unpublish 1
pepsi-keys -c /etc/pepsi/pepsi.conf identity revoke 1 --upload \
    --reason 'address closed'

70.1.35.1.10. See Also

pepsi-keydisc(1), pepsi-httpd(1), pepsi-stage-vks-confirm(1), pepsi-setup(1), pepsi-config(1), pepsi-whitelist(1), pepsi-settings(1), pepsi.conf(5), pepsi.state(7)

70.1.35.1.11. Bugs

Report bugs to the Pepsi issue tracker.