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_identityThe 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_keyRemote 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_trustThe 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
bothrow — 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
digitalSignatureand one withkeyEncipherment/keyAgreement;with
[pepsi] CRYPTO_SMIME_SHARED_KEY = yesit is instead onebothcertificate 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
signrow’s private half is destroyed, and only the public half is kept so old signatures can still be verified;an
encryptrow’s private half is retained;a
bothrow 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 thecrypto_identity.private_wrappedcolumn. Every other role — including thepepsiaccount 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
bothrow using[pepsi] CRYPTO_OPENPGP_ALGORITHM(ed25519, the default, orrsaatCRYPTO_GENERATE_RSA_BITS, which must be 2048, 3072 or 4096). S/MIME produces the signing/encryption pair usingCRYPTO_SMIME_ALGORITHM(rsa,p256orp384), 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_KEYsays. Refused with an elliptic-curveCRYPTO_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,encryptorbothand 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
purposeimplies. 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
revokedand stops being primary; the public half is kept so signatures made before the revocation can still be checked. Asignrow’s private half is destroyed here and now; anencryptorbothrow 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
signidentity 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
activeidentity 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
SMIMEArecord 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 = yesworks 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 mostVKS_BATCHper run, and abandoned afterVKS_MAX_ATTEMPTSwith 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_atbecomesexpired, private signing material is destroyed and stamped, and decryption material (including a sharedbothrow’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
*@domainrow is consulted only when there is no exact one (it is flagged as a domain-wide fallback in the output). PROTOCOL defaults toopenpgp.- peer import ADDRESS –protocol P –file FILE [–pin]
Store a correspondent’s key by hand (
-reads standard input). The row is recorded with sourcemanual, which outranks every discovery mechanism, and is the only source permitted to carry a*@domainaddress — 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
manualkey 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] SOURCESat 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 byALLOW_DOMAINS/DENY_DOMAINSis 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_afterhas passed, or its own expiry falls within –expiring-within-days (default7) — is put through the same fan-out, up to –limit keys (default100). Pinned and hand-entered (manual/api) keys are skipped, and so are*@domainrows: 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 thepepsi.key_requestside 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 withoutbasicConstraints CA:TRUEis 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 asKEY_WRAP_SECRETand keep the old one beside it asKEY_WRAP_SECRET_<OLD-ID>, pointKEY_WRAP_KEY_IDat 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
OPENPGPKEYrecord (RFC 7929) for OpenPGP material and anSMIMEArecord (RFC 8162, in the TLSA3 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(defaultno)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_SERVERSentry)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(default6 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 hparses,1 ddoes 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,debugortrace(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.secretThe 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 mode0640and hands it to thepepsi-cryptoaccount 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.