85.1.27. pepsi-stage-reencrypt¶
seal decrypted inbound mail to the recipient’s own key before filing it
- Manual section:
1
85.1.27.1.1. Name¶
pepsi-stage-reencrypt - the storage re-encryption stage of the Pepsi pipeline.
85.1.27.1.2. Synopsis¶
pepsi-stage-reencrypt [GLOBAL-OPTIONS] worker
85.1.27.1.3. Description¶
pepsi-stage-reencrypt is a stage program run by pepsi-dispatch(1)
as a persistent worker reading message ids on standard input. It loads that
pepsi.workqueue row (refusing to act unless its status is running) and
reads its [stage-<stage>] section.
pepsi-stage-decrypt(1) opens mail encrypted to a user’s gateway (MTA) key,
so that the language, spam, whitelist and key-learning stages can read it. The
plaintext is then what would be filed in the mailbox. This stage runs after all
of those readers and puts the message back under encryption — to the key the
user registered for their own mail client (a custody = client row in
pepsi.crypto_identity, the MUA key, registered with pepsi-keys(1)
identity import, the e-mail key commands, or learnt from the Autocrypt:
header of the user’s own submissions). What is filed is then ciphertext that
only that client can open.
It acts only on a message the decrypt stage opened: state.crypto.in
must say it arrived encrypted and was decrypted. Mail that arrived in
plaintext is never touched (sealing it would claim a confidentiality it never
had on the wire), and mail decrypt left unopened because it was already
encrypted to the MUA key (for_client) needs nothing. Only recipients this
host serves are considered; a recipient an alias expanded to another provider is
left alone.
For each local recipient the newest active MUA key able to receive
encryption is used, in whichever protocol it is (OpenPGP or S/MIME). A message
with several recipients whose treatment differs is first split into one row per
recipient (state.dsn.rcpt sliced in lockstep), since each copy is sealed to
one person’s key.
85.1.27.1.4. Where to put it¶
Immediately before local delivery (pepsi-stage-relay-to-maildir(1), pepsi-stage-relay-to-lmtp(1)), and therefore:
after pepsi-stage-decrypt(1) — before it nothing has been opened, so the stage only passes mail through;
after every stage that reads the content: language detection and blocking, the whitelist and pay-to-send gates, pepsi-stage-autocrypt-learn(1) (which reads gossip from the plaintext), pepsi-stage-vacation(1);
after pepsi-stage-aliases(1) and pepsi-stage-dot-forward(1) — a copy forwarded elsewhere must not leave sealed to a local user’s key, which its new recipient cannot open.
pepsi-setup(1) warns when no decrypt stage leads to it, and when it leads to any of the stages above. Note that an LMTP server’s Sieve script sees the sealed message: rules on the body (or, with PROTECT_HEADERS, the subject) no longer match.
85.1.27.1.5. What it writes¶
The envelope split is the one pepsi-stage-encrypt(1) uses. The
Content-* fields and Autocrypt-Gossip: (Autocrypt Level 1 §5.3) go
inside the ciphertext; everything else stays on the outer block —
Received:, From:/To:/Date:/Message-ID:, the sender’s own
Autocrypt: header (a client must read it before it can decrypt anything),
the DKIM/ARC fields, and X-Pepsi-Crypto, the gateway’s verdict on how the
message arrived. With PROTECT_HEADERS the RFC 5322 fields are also copied
inside and the outer Subject becomes ....
No signature is added. The verdict on the sender’s signature was reached by
the decrypt stage and is recorded in state.crypto.in and in
X-Pepsi-Crypto; a gateway signature would only say “the gateway sealed this”,
which a mail client cannot tell from authorship. A sender signature that survived
decryption as a MIME layer (S/MIME, or PGP/MIME multipart/signed inside the
ciphertext) is inside the plaintext and so is sealed with it and still verifies
in the client; an OpenPGP signature packed into the same stream as the
encryption was consumed by decryption and cannot be reconstructed, so the client
then shows an unsigned message and the gateway’s verdict header.
The container follows [pepsi] CRYPTO_ALLOW_DOWNGRADE exactly as for
outbound encryption (see pepsi.conf(5)).
85.1.27.1.6. With no key¶
ON_NO_CLIENT_KEY decides what happens to a local recipient with no usable MUA key — none registered, or one that cannot be sealed to (an expired certificate, a container the policy refuses; that case is also logged as a warning):
plaintext(default)File the plaintext, exactly as without this stage.
bounceRefuse the message: route it to BOUNCE_STAGE with enhanced status
5.7.5(RFC 3463, cryptographic failure). The returned DSN carries neither the body nor any header the sender could have protected — the header block is cut down toFrom,Sender,To,Cc,DateandMessage-ID, the body is emptied andRET=HDRSis forced whatever the sender asked — because the sender encrypted the message and a DSN travels in clear. Without a BOUNCE_STAGE the row is failed instead (and pepsi-setup(1) refuses that combination).
A refusal bounces rather than defers on purpose: a deferral would keep the plaintext in the queue for the message’s whole lifetime — a longer exposure, not a shorter one — and nothing would wake it when a key is registered.
The option is read through the per-address override layer, so the site default in the section can be changed for one recipient with pepsi-settings(1):
pepsi-settings set bob@example.org reencrypt ON_NO_CLIENT_KEY bounce
85.1.27.1.7. Configuration¶
Options live in the stage’s own [stage-<name>] section (see
pepsi.conf(5)):
- NEXT_STAGE (mandatory)
Local delivery.
- BOUNCE_STAGE
Where a refusal goes; required when ON_NO_CLIENT_KEY is
bouncein the section, and advisable whenever a per-address override might say so.- ON_NO_CLIENT_KEY =
plaintext|bounce See With no key. Default
plaintext.- PROTECT_HEADERS = yes | no
Copy the RFC 5322 fields inside the ciphertext and replace the outer
Subjectwith...(default no, like pepsi-stage-encrypt(1): client support is uneven, and a client that cannot look inside shows every subject as...).- ENABLED = yes | no
Off, every message is passed through unchanged. Default yes.
- LOCAL_DOMAINS, TARGETS, RECIPIENT_DELIMITER, REQUIRE_ACCOUNT
Which recipients this host serves, with the meaning and defaults of pepsi-stage-decrypt(1) (LOCAL_DOMAINS falls back to
[pepsi-ingress] ACCEPTED_DOMAINS).
The stage is unprivileged and folded into the multi-call pepsi binary:
sealing to a public key needs no private material, and it reads only the
public columns of pepsi.crypto_identity, which the ordinary pepsi
pipeline role may read.
85.1.27.1.8. State¶
Inputs: state.crypto.in.encrypted, state.crypto.in.decrypted and
state.crypto.in.for_client, written by pepsi-stage-decrypt(1).
Outputs: state.crypto.store, whose presence also keeps the stage from
ever sealing a row twice:
outcomereencrypted,plaintextorbounced.protocol,fingerprint,container,downgradedFor
reencrypted: which MUA key was used and the container emitted.reasonFor
plaintext/bounced:no-client-key,not-localor the sealing error.
The state layout is described in pepsi.state(7).
Transitions: advances to NEXT_STAGE; reroutes to BOUNCE_STAGE (or
fails without one) under ON_NO_CLIENT_KEY = bounce; hands a multi-recipient
row back to the queue after splitting it.
85.1.27.1.9. Commands¶
- worker
Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.
85.1.27.1.10. Global Options¶
- -c FILE, –config FILE
Read the configuration from FILE instead of searching the default locations.
- -L LOGLEVEL, –log LOGLEVEL
Set the logging verbosity (default
info).- -v, –verbose
Show log messages from all sources.
- -h, –help; -V, –version
Print a usage summary / the version and exit.
85.1.27.1.11. Exit Status¶
- 0
The message was processed and handed on.
- 1
An error occurred (message not found or not
running, a misconfigured stage, a database error). The reason is written to the log.- 78
The worker refused to start: its section or the
[pepsi]crypto policy does not parse. The dispatcher requeues what it handed over and retries the stage later. A database error while a message is processed is retried the same way as any other fault of this host (see pepsi-dispatch(1)).
85.1.27.1.12. Examples¶
Re-encrypt message 42 (it must be running):
echo 42 | pepsi-stage-reencrypt -c /etc/pepsi/pepsi.conf worker
The tail of an inbound pipeline:
[stage-dot-forward]
PROGRAM = pepsi-stage-dot-forward
NEXT_STAGE = reencrypt
[stage-reencrypt]
PROGRAM = pepsi-stage-reencrypt
NEXT_STAGE = local
BOUNCE_STAGE = bounce
Never file readable mail for anybody, and hide subjects too:
[stage-reencrypt]
PROGRAM = pepsi-stage-reencrypt
NEXT_STAGE = local
BOUNCE_STAGE = bounce
ON_NO_CLIENT_KEY = bounce
PROTECT_HEADERS = yes
85.1.27.1.13. See Also¶
pepsi-stage-decrypt(1), pepsi-stage-encrypt(1), pepsi-keys(1), pepsi-settings(1), pepsi-stage-relay-to-maildir(1), pepsi-stage-relay-to-lmtp(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.27.1.14. Bugs¶
Report bugs to the Pepsi issue tracker.