71. RFC Index¶
Pepsi aims to be a standards-compliant e-mail forwarder and a standards-compliant end-to-end crypto gateway. This chapter is the single place that maps every RFC Pepsi implements (or deliberately does not yet) to where in the system it is realised, so a reviewer can jump from a standard to its code and configuration.
The summary table lists every applicable RFC at a glance; the per-RFC notes below give the detail. RFCs that are planned but not yet implemented are collected under Applicable RFCs not yet implemented.
Not everything indexed here is an RFC, and that is deliberate. The OpenPGP Web Key Directory is an Internet-Draft; Autocrypt and the VKS key-server protocol are de-facto standards; NTLM is a Microsoft Open Specification; XOAUTH2 has no specification body at all; SRS and Maildir are published schemes that were never put through the IETF; FIPS 186-4 is a NIST publication. Each is what the deployed world actually speaks, so leaving them out would make this chapter describe a system nobody runs. They are listed after the RFCs, italicised, with their status named.
Implementing a standard is not the same as interoperating with the clients that claim to implement it. Client interoperability records which of these have been measured against real software, which are one-off observations, and which have never been run at all.
A note on what “implemented in” means: it names the module or program where the
standard is realised, not every caller. Where a standard is only partly
implemented — BINARYMIME of RFC 3030, Autocrypt gossip without its
inbound-gossip reading half — the cell says so, and the boundary is stated
rather than left for the reader to discover. Standards Pepsi deliberately does
not implement are under Applicable RFCs not yet implemented with the reason.
71.1. Summary table¶
RFC |
Title (subject) |
Implemented in |
|---|---|---|
RFC 1035 |
Domain names — implementation and specification (DNS) |
All DNS lookups; domain/label syntax in |
RFC 1870 |
SMTP service extension for message size declaration ( |
Advertised and enforced by pepsi-ingress; the outbound client
honours the next hop’s announced limit, |
RFC 1918 |
Address allocation for private internets |
Together with RFC 6598 and RFC 2544, the address ranges an outbound fetch
refuses so a key-discovery URL cannot reach inside the deployment
( |
RFC 1951 |
|
The bound on decompressing fetched key material — a compression bomb is
refused rather than expanded ( |
RFC 2033 |
Local Mail Transfer Protocol (LMTP) |
Local delivery to an MDA (Dovecot), pepsi-stage-relay-to-lmtp |
RFC 2034 |
SMTP service extension for returning enhanced error codes |
|
RFC 2045 |
MIME Part One: format of message bodies (transfer-encodings) |
8-bit→7-bit body downgrade, |
RFC 2046 |
MIME Part Two: media types ( |
MIME tree parsing and boundary validation, |
RFC 2047 |
MIME Part Three: message header extensions (encoded-words) |
UTF-8 header downgrade, |
RFC 2104 |
HMAC: keyed-hashing for message authentication |
The SRS envelope-sender MAC ( |
RFC 2195 |
IMAP/POP |
Outbound SMTP |
RFC 2231 |
MIME parameter value and encoded word extensions |
Continued and character-set-tagged header parameters, |
RFC 2392 |
Content-ID and Message-ID uniform resource locators |
The |
RFC 2831 |
Using digest authentication as a SASL mechanism ( |
Outbound SMTP |
RFC 2920 |
SMTP service extension for command pipelining |
|
RFC 2986 |
PKCS #10: certification request syntax |
Certificate-signing requests for externally-issued S/MIME certificates,
|
RFC 3030 |
SMTP service extensions for large and binary messages ( |
pepsi-ingress ( |
RFC 3156 |
MIME security with OpenPGP (PGP/MIME) |
|
RFC 3207 |
SMTP service extension for secure SMTP over TLS ( |
pepsi-ingress listeners; outbound SMTP client |
RFC 3339 |
Date and time on the Internet: timestamps |
Report generation timestamps, |
RFC 3370 |
Cryptographic Message Syntax (CMS) algorithms |
S/MIME digest and signature algorithm identifiers, |
RFC 3394 |
Advanced Encryption Standard (AES) key wrap algorithm |
Wrapping the content-encryption key in an ECDH |
RFC 3461 |
SMTP service extension for Delivery Status Notifications ( |
|
RFC 3463 |
Enhanced mail system status codes |
DSN status codes, |
RFC 3464 |
An extensible message format for Delivery Status Notifications |
|
RFC 3492 |
Punycode |
IDNA label encoding for internationalized domains, |
RFC 3565 |
Use of AES encryption in CMS |
AES-256-CBC |
RFC 3676 |
The text/plain format=flowed media type |
Body-text extraction and the |
RFC 3834 |
Recommendations for automatic responses to electronic mail |
|
RFC 3848 |
ESMTP and LMTP transmission types ( |
|
RFC 3864 |
Registration procedures for message header fields |
The procedure Pepsi’s two private header fields (SMTP Protocol Extensions) are not registered under; both are unregistered by design |
RFC 4013 |
SASLprep: stringprep profile for user names and passwords |
SCRAM username/password preparation, |
RFC 4055 |
Additional algorithms and identifiers for RSA in PKIX |
RSAES-OAEP and RSASSA-PSS parameter encoding, |
RFC 4121 |
The Kerberos 5 GSS-API mechanism, v2 |
Outbound smarthost |
RFC 4155 |
The application/mbox media type |
|
RFC 4422 |
Simple Authentication and Security Layer (SASL) — |
Outbound SMTP |
RFC 4511 |
LDAP: the protocol |
LDAP key discovery, |
RFC 4514 |
LDAP: string representation of distinguished names |
Certificate subject/issuer DN rendering, |
RFC 4515 |
LDAP: string representation of search filters |
Filter escaping for the LDAP discovery query — the injection guard,
|
RFC 4616 |
The |
Inbound (ingress) and outbound (smarthost) |
RFC 4648 |
The Base16, Base32, and Base64 data encodings |
SRS hash encoding ( |
RFC 4752 |
The Kerberos V5 ( |
Outbound smarthost |
RFC 4954 |
SMTP service extension for authentication ( |
Inbound |
RFC 5083 |
CMS |
The default S/MIME encryption container (AES-256-GCM),
|
RFC 5084 |
Using AES-CCM and AES-GCM in CMS |
AES-256-GCM |
RFC 5228 |
Sieve: an email filtering language |
Run by the MDA (Dovecot) on LMTP delivery; Pepsi implements no Sieve itself |
RFC 5280 |
Internet X.509 PKI certificate and CRL profile |
Certificate parsing, extension handling and path validation,
|
RFC 5321 |
Simple Mail Transfer Protocol |
Core of ingress and both relay stages; envelope handling, MX, null sender |
RFC 5322 |
Internet Message Format |
Header/body model ( |
RFC 5480 |
Elliptic curve cryptography subject public key information |
EC public-key encoding in certificates, |
RFC 5652 |
Cryptographic Message Syntax (CMS) |
|
RFC 5753 |
Use of ECC algorithms in CMS |
ECDH key-agreement |
RFC 5758 |
Additional algorithms and identifiers for DSA and ECDSA in PKIX |
|
RFC 5802 |
Salted Challenge Response Authentication Mechanism ( |
Outbound |
RFC 5890 |
Internationalized Domain Names for Applications (IDNA2008): definitions |
Domain handling throughout, via the |
RFC 5915 |
Elliptic curve private key structure |
EC private-key encoding, |
RFC 5929 |
Channel bindings for TLS ( |
SCRAM |
RFC 5958 |
Asymmetric key packages (PKCS #8) |
Private-key encoding at rest, |
RFC 5987 |
Character set and language encoding for HTTP header field parameters |
|
RFC 6125 |
Service identity in TLS (certificate name checking) |
MTA-STS certificate validation, |
RFC 6152 |
SMTP service extension for 8-bit MIME transport ( |
Advertised by ingress; per-hop re-advertise/downgrade in the relay stages |
RFC 6265 |
HTTP state management mechanism (cookies) |
The administrative session cookie and the secure-link portal cookie, both
|
RFC 6331 |
Moving |
The reason |
RFC 6376 |
DomainKeys Identified Mail (DKIM) signatures |
Verification (ingress) and signing ( |
RFC 6409 |
Message submission for mail |
Submission listeners ( |
RFC 6531 |
SMTP extension for internationalized email ( |
Advertised by ingress; per-hop re-advertise/downgrade in the relay stages |
RFC 6598 |
IANA-reserved IPv4 prefix for shared address space |
Part of the outbound-fetch address guard; see RFC 1918 |
RFC 6648 |
Deprecating the |
Why |
RFC 6698 |
DNS-based authentication of named entities (DANE): TLSA |
|
RFC 6749 |
The OAuth 2.0 authorization framework |
Smarthost token acquisition and refresh, |
RFC 6750 |
The OAuth 2.0 authorization framework: bearer token usage |
The |
RFC 6761 |
Special-use domain names |
Names that must not be resolved on the public DNS, short-circuited in
|
RFC 6840 |
Clarifications and implementation notes for DNSSEC |
The |
RFC 6891 |
Extension mechanisms for DNS (EDNS(0)) |
UDP payload size on outgoing queries, |
RFC 6979 |
Deterministic usage of DSA and ECDSA |
Nonce generation for ECDSA signatures, |
RFC 7208 |
Sender Policy Framework (SPF) for authorizing use of domains |
Inbound SPF verification (ingress, via |
RFC 7235 |
HTTP/1.1: authentication |
The |
RFC 7489 |
Domain-based Message Authentication, Reporting, and Conformance (DMARC) |
Inbound DMARC evaluation and optional enforcement (ingress) |
RFC 7505 |
A “Null MX” no-service resource record |
MX classification, |
RFC 7595 |
Guidelines and registration procedures for URI schemes |
The procedure the |
RFC 7628 |
A set of SASL mechanisms for OAuth ( |
Outbound smarthost |
RFC 7672 |
SMTP security via opportunistic DANE TLS ( |
|
RFC 7677 |
|
Outbound |
RFC 7748 |
Elliptic curves for security (X25519) |
Curve25519 key agreement in OpenPGP and TLS |
RFC 7929 |
DNS-based authentication of named entities (DANE) bindings for OpenPGP |
|
RFC 8017 |
PKCS #1 v2.2: RSA cryptography specification |
RSA signature and key-transport primitives (via the |
RFC 8018 |
PKCS #5: password-based cryptography (PBKDF2) |
|
RFC 8162 |
Using secure DNS to associate certificates with domain names for S/MIME |
|
RFC 8314 |
Cleartext considered obsolete: use of TLS for submission and access |
Implicit-TLS submission listener ( |
RFC 8446 |
The Transport Layer Security (TLS) protocol version 1.3 |
Every TLS listener and client, over |
RFC 8460 |
SMTP TLS Reporting (TLSRPT) |
Outbound TLS-session recording (relay stages) + daily reports ( |
RFC 8461 |
SMTP MTA Strict Transport Security (MTA-STS) |
Policy enforcement ( |
RFC 8463 |
A new cryptographic signature method for DKIM (Ed25519) |
Ed25519 DKIM keys/signatures, |
RFC 8550 |
S/MIME 4.0 certificate handling |
Path building, |
RFC 8551 |
S/MIME 4.0 message specification |
|
RFC 8601 |
Message header field for indicating message authentication status |
|
RFC 8617 |
The Authenticated Received Chain (ARC) protocol |
ARC verify/sign/seal, pepsi-stage-arc |
RFC 8905 |
The |
Wire-transfer targets in the wallet self-service reply,
|
RFC 8959 |
The |
The merchant access token’s form, |
RFC 9051 |
Internet Message Access Protocol (IMAP) version 4rev2 |
Read-only mailbox scanning for whitelist import ( |
RFC 9106 |
Argon2 memory-hard function for password hashing and PoW |
The secure-link content key derived from the recipient’s PIN
( |
RFC 9110 |
HTTP semantics |
Request/response handling in pepsi-httpd |
RFC 9112 |
HTTP/1.1 |
The wire protocol served by pepsi-httpd, over |
RFC 9580 |
OpenPGP (the crypto refresh; successor to RFC 4880) |
|
Autocrypt Level 1 |
The |
Harvested from inbound mail at the lowest trust rank,
|
FIPS 186-4 |
Digital Signature Standard (a NIST publication) |
The |
draft-ietf-mailmaint-autoconfig |
Mail account autoconfiguration (an Internet-Draft of the IETF |
The |
MS-NLMP |
The NT LAN Manager authentication protocol (a Microsoft Open Specification, not an RFC) |
Outbound smarthost |
Maildir |
D. J. Bernstein’s mailbox format (a specification, not an RFC —
|
pepsi-stage-relay-to-maildir via the setuid helper; Maildir++ recursion when scanning for whitelist import |
SRS |
The Sender Rewriting Scheme (a published scheme with no RFC; the
|
|
VKS ( |
The verifying key server’s |
Lookup by |
XOAUTH2 |
Google’s and Microsoft’s pre-standard SASL bearer-token mechanism (no
specification body; superseded by RFC 7628 |
Outbound smarthost |
draft-koch-openpgp-webkey-service |
OpenPGP Web Key Directory (an Internet-Draft, not an RFC) |
Client side: |
71.2. Per-RFC notes¶
71.2.1. RFC 1035 — Domain Names (DNS)¶
All DNS resolution (MX, A/AAAA, TXT for SPF/DKIM/DMARC/MTA-STS,
and reverse PTR) follows RFC 1035. Domain-name and label syntax limits are
enforced in pepsi-common::domain. DNS is used by ingress (authentication),
the ARC stage (re-verification) and pepsi-stage-relay-to-internet (MX
discovery and address caching in the pepsi.dns_address table).
71.2.2. RFC 2045 / RFC 2047 — MIME bodies and header encoded-words¶
When a next hop does not advertise 8BITMIME (RFC 6152), pepsi-common::mime
walks the MIME tree and re-encodes each 8-bit leaf part with a 7-bit
content-transfer-encoding (RFC 2045: quoted-printable for text/*, base64
otherwise). When a hop does not advertise SMTPUTF8 (RFC 6531), UTF-8 header
fields are rewritten as RFC 2047 encoded-words. See
pepsi-stage-relay-to-internet.
71.2.3. RFC 3030 — CHUNKING / BDAT¶
Ingress advertises CHUNKING and accepts message bodies framed with BDAT
chunks (in addition to DATA). The BODY=BINARYMIME body value of the same
RFC is not advertised or accepted. See pepsi-ingress.
71.2.4. RFC 3207 — STARTTLS¶
Ingress listeners in starttls mode advertise the STARTTLS verb and
upgrade a cleartext session in place. The outbound SMTP client uses STARTTLS
opportunistically (or as required by MTA-STS). Implicit TLS is governed by
RFC 8314 instead.
71.2.5. RFC 3461 / RFC 3463 / RFC 3464 — Delivery Status Notifications¶
Ingress advertises the DSN extension and validates/stores
RET/ENVID/NOTIFY/ORCPT under state.dsn
(pepsi-common::dsn). The relay stages propagate the parameters to a next hop
that also advertises DSN (RFC 3461 §5–6). pepsi-stage-bounce emits the
notification as an RFC 3464 multipart/report with RFC 3463 enhanced status
codes, honouring NOTIFY (failure by default; success/delay only on explicit
request). See Supported Features and pepsi-stage-bounce.
71.2.6. RFC 3834 — Automatic responses¶
The auto-replies Pepsi originates — the pepsi-stage-anti-spam payment-request
message and the pepsi-stage-edit-settings confirmation/error replies — set
Auto-Submitted: auto-replied per RFC 3834 and are sent with the null sender.
A message that is itself an automatic response (null sender / Auto-Submitted)
is never gated, bounced or auto-replied to, so the stages cannot form a reply
loop. See pepsi-stage-anti-spam.
71.2.7. RFC 3848 — ESMTP transmission types¶
The Received: trace header ingress prepends records the transport in the
with clause as SMTP, ESMTP, ESMTPS or — when the session
authenticated over TLS with SMTP AUTH — ESMTPSA, per RFC 3848.
71.2.8. RFC 4648 — Base16/Base32/Base64¶
SRS addresses encode their truncated HMAC in base32 (RFC 4648) so the token is case-insensitive and address-safe; key material uses base64. See pepsi-stage-srs.
71.2.9. RFC 4954 — SMTP AUTH¶
Both directions are supported. Inbound: ingress implements the AUTH
verb with the PLAIN (RFC 4616) and LOGIN mechanisms (SASL PLAIN
initial-response and the prompted LOGIN exchange). It is advertised and
accepted only on an encrypted session (implicit TLS or after STARTTLS);
an AUTH attempt in cleartext is refused with 538. Credentials are
verified against a configured SASL backend (Dovecot’s auth-client socket,
SASL_TYPE/SASL_PATH); a successful AUTH marks the session locally
originated, lets it relay to any domain, and stamps ESMTPSA in the
Received: with clause.
Outbound: the SMTP client authenticates to a smarthost via the AUTH
option of an […-mta-*] section. The full mechanism suite (all in
pepsi-common::smtp::sasl, hand-rolled on the RustCrypto primitives already
in-tree — no SASL/SCRAM crate) is:
plain—PLAIN(RFC 4616) andlogin—LOGIN(cleartext password inside the TLS tunnel).cram-md5—CRAM-MD5(RFC 2195), HMAC-MD5 challenge-response.scram-sha-1/scram-sha-256and their-plusvariants —SCRAM(RFC 5802) andSCRAM-SHA-256(RFC 7677); the-PLUSforms addtls-server-end-pointchannel binding (RFC 5929). The server signature is verified for mutual authentication; usernames/passwords are SASLprep’d (RFC 4013).digest-md5—DIGEST-MD5(RFC 2831), deprecated (RFC 6331 moved it to Historic): supported for legacy smarthosts but excluded fromautoand warned about both atpepsi-setupvalidation and at runtime.oauth—OAUTHBEARER(RFC 7628), falling back to the de-factoXOAUTH2; the bearer token is read fromTOKEN_FILE(see pepsi-helper-token-refresh).external— SASLEXTERNAL(RFC 4422), authenticated by the configured TLS client certificate rather than a password.ntlm— the de-facto MicrosoftNTLMmechanism (no RFC; NTLMv2 only). Run only under TLS, so it always carries Extended Protection for Authentication: thetls-server-end-point(RFC 5929) channel-binding andsmtp/<host>SPN AV pairs plus the MIC. Weak and without server authentication — flagged bypepsi-setup.gssapi— SASLGSSAPI/ Kerberos (RFC 4752, over the RFC 4121 Kerberos 5 GSS mechanism), via the system Kerberos library (pepsi-common::smtp::gssapi). The ticket comes from a credential cache kept fresh out of band; the SASL security layer is negotiated to no security layer (TLS already protects the session).auto— negotiate the strongest mechanism the smarthost’s EHLO offers (SCRAM-SHA-256-PLUS>SCRAM-SHA-256>SCRAM-SHA-1-PLUS>SCRAM-SHA-1>CRAM-MD5>LOGIN>PLAIN;DIGEST-MD5,NTLMandGSSAPIare never auto-selected).
As inbound, the outbound client refuses to send credentials over an unencrypted
connection. A 5xx rejection of a password mechanism is treated as a permanent
failure (bounce). See pepsi-ingress and
pepsi-stage-relay-to-smarthost.
GSSAPI/Kerberos is the one mechanism that is not hand-rolled: it links the
system Kerberos GSS implementation (MIT krb5 / Heimdal). The Debian package
therefore build-depends on libkrb5-dev and depends on libgssapi-krb5-2.
71.2.10. RFC 2033 / RFC 5228 — LMTP local delivery and Sieve¶
pepsi-stage-relay-to-lmtp delivers a message to a local Mail
Delivery Agent — typically Dovecot — over LMTP (RFC 2033): it offers
every recipient in a single LMTP transaction (LHLO, optional STARTTLS and
SASL, multi-reply DATA) and routes each recipient onward by the MDA’s
per-recipient reply (delivered, or classified by its RFC 3463 status). The MDA,
not Pepsi, runs each recipient’s Sieve (RFC 5228) filter as it files the mail;
Pepsi implements no Sieve itself. The shared LMTP client is
pepsi-common::smtp::deliver_lmtp. Because the MDA drops privilege, this stage
needs no setuid/setgid helper (unlike pepsi-stage-relay-to-maildir).
See Supported Features and pepsi-stage-relay-to-lmtp.
71.2.11. RFC 5321 — SMTP¶
RFC 5321 is the backbone: the ingress command grammar and replies; the envelope
(MAIL FROM/RCPT TO, the null sender <>); the domainless
<Postmaster> mailbox (§4.5.1); the Received: trace header (§4.4); implicit
MX via address records (§5.1); and the “never bounce a bounce” rule (§6.1). It
governs ingress and both relay stages.
71.2.12. RFC 5322 — Internet Message Format¶
Messages are stored split into a header block and a body at the first blank line
(pepsi-common::message); header selection for signing and the parsed
From:/Subject: follow RFC 5322. DKIM/ARC h= selection is bottom-up so
a prepended header never disturbs an existing signature.
71.2.13. RFC 3156 / RFC 9580 — OpenPGP and PGP/MIME¶
RFC 9580 is the 2024 OpenPGP specification (the “crypto refresh”, succeeding
RFC 4880); RFC 3156 is how an OpenPGP message is carried in MIME. Pepsi
implements both over the sequoia-openpgp library, in pepsi-crypto::pgp
(cryptography) and pepsi-crypto::mime (containers).
The MIME half (RFC 3156). Encryption emits multipart/encrypted;
protocol="application/pgp-encrypted" with the Version: 1 control part and
an application/octet-stream ciphertext part (§4). Detached signing emits
multipart/signed; protocol="application/pgp-signature" with the micalg
parameter computed from the hash actually used (§5) — pgp-sha256 for the
default. Both forms are recognised on the way in, as is non-MIME inline
armor, which [pepsi] CRYPTO_INLINE_PGP may refuse.
The container question (RFC 9580). The choice between the RFC 9580 SEIPDv2
container (AEAD) and the RFC 4880 SEIPDv1 container (an MDC) is made
explicitly from the recipient keys’ declared capabilities rather than left to
the library, so a downgrade is a decision that gets recorded rather than a silent
one; [pepsi] CRYPTO_ALLOW_DOWNGRADE governs whether it is permitted at all.
Reading a SEIPDv1+MDC message is never gated — most of the installed base still
produces them — but the pre-MDC SED packet is refused outright.
See Key management, pepsi-stage-encrypt and pepsi-stage-decrypt; Client interoperability records what has been measured against gpg.
71.2.14. RFC 5280 — X.509 certificates and path validation¶
Every S/MIME decision rests on this one. pepsi-crypto::trust parses
certificates, applies the extensions that matter for mail
(basicConstraints, keyUsage, the emailProtection extended key usage
of §4.2.1.12, and the rfc822Name subject alternative name that binds a
certificate to an address) and builds a path to a configured anchor.
Two boundaries are worth stating because the code is deliberate about them:
The trust store is searched before the message’s own certificates. A CMS
SignerIdentifiernames a certificate by issuer and serial number, so a message can carry a forged certificate bearing a pinned one’s issuer and serial with a different key. A verifier that looked at the message first would accept it.Revocation is not checked. Neither the CRL profile of §5 nor OCSP (RFC 6960) is consulted — see Applicable RFCs not yet implemented. The
Revocationvalue on a validated path is alwaysNotChecked, which is reported honestly rather than rendered as “not revoked”.
A certificate that chains to no configured anchor is untrusted, not invalid
(Verdict::Untrusted): most of the world’s signed mail chains to a CA nobody
handed us an anchor for, and reporting that as a failure would make the
indicator useless.
71.2.15. RFC 5652 / 5083 / 5753 / 8550 / 8551 — CMS and S/MIME¶
S/MIME is Pepsi’s second end-to-end protocol, and its four standards divide the work cleanly.
RFC 5652 (CMS) is the container syntax. pepsi-crypto::smime builds and
opens SignedData (detached and opaque), EnvelopedData, and the
certs-only form used to publish a certificate. The cms crate supplies the
ASN.1 type definitions only; the builders and openers are Pepsi’s own, so that
what is emitted and what is accepted are decided here rather than inherited.
RFC 5083 (``AuthEnvelopedData``) is the authenticated-encryption container,
and it is what Pepsi emits by default (AES-256-GCM). The RFC 5652
EnvelopedData with AES-CBC remains as the downgrade path for a recipient that
cannot do better — again gated by [pepsi] CRYPTO_ALLOW_DOWNGRADE. Which
clients can actually read AuthEnvelopedData is the largest open
interoperability question in the tree; see
The AuthEnvelopedData question.
RFC 5753 covers the elliptic-curve RecipientInfo: ECDH key agreement over
P-256/P-384 with the X9.63 KDF and AES key wrap, alongside the RSA forms
(RSAES-OAEP-SHA-256, and PKCS#1 v1.5 for a recipient that needs it).
RFC 8550 (certificate handling) is pepsi-crypto::trust: path building to a
configured anchor, validity dates, basicConstraints, keyUsage against the
operation being performed, the id-kp-emailProtection extended key usage, and
the rfc822Name subject-alternative-name that binds a certificate to the
From: address. Revocation is tri-state — “not checked” is never rendered
as “not revoked” — and the module opens no sockets: there is no OCSP and no CRL
fetching, only CRLs supplied to it.
RFC 8551 (message specification) is the MIME surface:
application/pkcs7-mime with the smime-type parameter
(signed-data/enveloped-data/authEnveloped-data/certs-only) and
application/pkcs7-signature inside multipart/signed, with the
conventional smime.p7m/smime.p7s/smime.p7c file names. The legacy
application/x-pkcs7-* spellings are accepted on the key-harvesting path only.
Certificates for this deployment’s own users are issued by
pepsi-crypto::keygen through the local CA in pepsi-keys. See
Key management; Client interoperability records what has been measured
against OpenSSL.
71.2.16. RFC 6125 — TLS service identity¶
When MTA-STS requires authenticated TLS, the recipient MX certificate is checked to validate for the expected host name per RFC 6125. See pepsi-stage-relay-to-internet.
71.2.17. RFC 6152 — 8BITMIME¶
Ingress advertises 8BITMIME and records the BODY= value under
state.origin. Each relay stage re-advertises BODY=8BITMIME to a hop that
supports it and otherwise downgrades the body (RFC 2045). See Supported Features.
71.2.18. RFC 6376 / RFC 8463 — DKIM (RSA and Ed25519)¶
Ingress verifies inbound DKIM signatures; pepsi-stage-dkim-sign signs
outbound mail. Each domain has both an RSA-2048 key (RFC 6376) and an Ed25519 key
(RFC 8463), provisioned by pepsi-setup; both signatures are added. The l=
body-length tag is optional (COVER_BODY). See
pepsi-stage-dkim-sign.
71.2.19. RFC 6409 — Message submission¶
A listener marked SUBMISSION = yes acts as a Message Submission Agent. It
requires AUTH as RFC 6409 expects (an unauthenticated MAIL FROM is
refused with 530; see RFC 4954 — SMTP AUTH) and applies the submission
fixups to every accepted message: a missing Date: is added (§8.2) and a
missing Message-ID: is generated (§8.3). Submission-identity enforcement
(§6) and the Sender: fixup (§8.1) are driven by the USERNAME_MAP mapping
of the authenticated SASL user to its allowed addresses: an envelope MAIL
FROM or a From: the user is not permitted to use is refused with 550,
and an allowed From: that differs from the user’s canonical identity gets a
Sender: header naming that identity. See pepsi-ingress.
71.2.20. RFC 6531 — SMTPUTF8¶
Ingress advertises SMTPUTF8 and accepts UTF-8 in headers and the envelope.
Each relay stage re-advertises it to a supporting hop and otherwise downgrades
UTF-8 headers (RFC 2047); a non-ASCII address that cannot be represented to a
non-SMTPUTF8 hop is a permanent failure (bounce). See Supported Features.
71.2.21. RFC 7208 — SPF¶
Ingress verifies SPF on the MAIL FROM identity using the connecting client’s
IP (via the mail_auth library) and records the verdict in
Authentication-Results and under state.auth.spf.
71.2.22. RFC 7489 — DMARC¶
Ingress evaluates DMARC and records the verdict. With DMARC_ENFORCE set, a
definite DMARC failure under a quarantine/reject policy is rejected at
SMTP time (550); otherwise the failing verdict is recorded and the mail is
accepted (fail-open, suited to a forwarder).
The organizational domain that relaxed alignment compares against is found by
the RFC 9989 (DMARCbis) tree walk, which mail_auth performs internally. Note
this changed with mail-auth 0.11: the older interface asked the caller for an
organizational-domain function and defaulted it to the identity, so before that
upgrade relaxed alignment compared the two domains as given and a subdomain never
aligned with its parent.
71.2.23. RFC 7505 — Null MX¶
pepsi-stage-relay-to-internet classifies a 0 . exchange as a permanent
failure (an immediate bounce, never a retry).
71.2.24. RFC 7672 — DANE / TLSA for SMTP¶
Both relay stages support opportunistic DANE: a DANE = off|warn|strict
option (default warn) looks up the next hop’s TLSA records — per-MX for
pepsi-stage-relay-to-internet, per-[…-mta-*] for
pepsi-stage-relay-to-smarthost — and matches them against the presented
certificate chain. The verifier and TLSA types live in
pepsi-common::smtp::dane; the AD-bit-aware TLSA/MX lookups in
pepsi-common::dns (Pepsi trusts a validating resolver and reads the response
AD bit rather than validating DNSSEC itself). DANE takes precedence over MTA-STS
and TLS_VERIFY; a usable-record mismatch defers in strict and only warns
in warn.
71.2.25. RFC 7929 / RFC 8162 — OPENPGPKEY and SMIMEA key records¶
Pepsi implements both halves: publishing our own addresses’ key records, and looking a correspondent’s up.
Publishing. pepsi-keys dns <address> prints the DNS records that publish
an address’s stored public key material: an OPENPGPKEY record (RFC 7929)
carrying an OpenPGP transferable public key, and an SMIMEA record (RFC 8162)
carrying an S/MIME certificate in the TLSA (usage, selector, matching-type)
shape — 3 0 0 (domain-issued certificate, full certificate, exact match), the
form a self-signed or directly pinned certificate takes. Only published, active
identities are emitted. pepsi-setup run prints the same records for every
published identity alongside the DKIM, SPF and MTA-STS ones.
Looking up. The pepsi-keydisc@dane discovery instance queries the same
two record types for a correspondent’s address, OPENPGPKEY first and
SMIMEA if the domain publishes no OpenPGP record. It ranks second on the
trust ladder — below only material an operator entered by hand — because DNSSEC
is the one source with cryptographic provenance.
Both directions use the same hashed owner name — the hex of the first 28 octets
of the SHA-256 of the local part, followed by ._openpgpkey. or
._smimecert. — so a zone does not enumerate the addresses it serves. That
hash is case-sensitive (RFC 7929 §3), unlike the Web Key Directory hash
below.
Both standards presuppose a DNSSEC-signed zone, and Pepsi treats that as
binding in both directions. pepsi-keys dns says so in its own output, and a
lookup is AD-gated: an answer the resolver did not validate is refused
outright rather than accepted at a lower rank, because an unsigned key record is
a key from whoever can answer for the zone. Pepsi trusts a validating resolver
rather than validating DNSSEC itself, so a deployment without one gets no DANE
results at all — pepsi-setup probes for this and reports it. See
pepsi-keys, pepsi-keydisc and
Key management.
71.2.26. OpenPGP Web Key Directory (draft-koch-openpgp-webkey-service)¶
The Web Key Directory is the HTTPS half of the same job: a correspondent’s key is
fetched from the address’s own domain under a hash of its local part. Pepsi
computes that hash in pepsi_common::keystore::wkd_local_part_hash —
z-base-32 of the SHA-1 of the lower-cased local part — and stores it on each
identity, indexed, so a request is answered with one lookup instead of by hashing
every candidate row. (The SHA-1 there is a naming function the specification
fixes, not a security one, so it is deliberately not gated by
CRYPTO_ALLOW_WEAK_DIGESTS.)
The client side is implemented: pepsi-keydisc@wkd-advanced queries
openpgpkey.<domain> and pepsi-keydisc@wkd-direct the domain itself. They
are separate ranks and therefore separate concurrent lookups rather than a
fallback chain, since running direct only “if advanced fails” would accept the
weaker answer whenever the stronger one was merely slow. The fetch is hardened
beyond the draft’s own requirements: HTTPS only with certificate validation and
no insecure mode, at most one HTTPS-to-HTTPS redirect to the same host, refusal to
connect to loopback/private/link-local addresses, a body cap enforced while
streaming, a deadline on every step, and IDNA A-label conversion. A returned key
whose User ID does not name the queried address is discarded — without that, a
domain could answer every request with one key of its choosing — and the policy
file’s mailbox-only and dane-only directives are honoured.
The serving side is implemented too, by pepsi-httpd: the
direct and advanced forms of both the key and the policy path, answered by an
indexed (domain, local-part hash) lookup over identities this deployment
holds and has marked published. The response is the binary transferable key
with Access-Control-Allow-Origin: *; the policy file is a zero-length 200,
which has to exist because GnuPG reads its absence as “no Web Key Directory here”;
an unknown hash is an empty-bodied 404. The ?l= parameter is read and
ignored — honouring it would turn the endpoint into a lookup by caller-supplied
address rather than by a hash the caller had to know.
Two properties of the serving side are deliberate and worth stating. It never serves a cached correspondent key, only our own identities: publishing material we did not verify, under our own domain name, is what a key server does and we are not one. And it applies no rate limiting, because a Web Key Directory is a public oracle by construction — see Key management.
The specification is an Internet-Draft rather than an RFC.
71.2.27. Autocrypt — the Autocrypt: header¶
Autocrypt is not an RFC but a de-facto standard, and it is how most PGP-using
mail clients advertise a key today, which is reason enough to read it. Pepsi
harvests the header — together with application/pgp-keys parts and the signer
certificates inside an S/MIME SignedData — from mail that has already
arrived.
Harvesting is deliberately not a discovery service: it costs no network I/O
because the material is already in front of it, so it runs inline rather than as
a pepsi-keydisc instance, and inbound is rejected in
[pepsi-keydiscovery] SOURCES. It does write through the same path, so a
harvested key also releases any message parked waiting on that address.
A harvested key sits at the bottom of the trust ladder — trust on first use — and the server-side conflict rule stops it displacing anything better attested. It is nevertheless usable by default, because the alternative to encrypting to a TOFU key is sending cleartext. See Key management.
Pepsi writes the header as well. Unless [stage-encrypt] AUTOCRYPT is
turned off, pepsi-stage-encrypt advertises the From: author’s own
OpenPGP key on every locally submitted message — including plain cleartext mail,
which is the case that matters, since a correspondent has to learn the key
before there is anything to encrypt with. What is advertised is the minimal
five-packet transferable public key of Level 1 §2.1.1, only for an identity that
still holds its private material, and never on a message that already carries an
Autocrypt: field from the sender’s own client. Because the stage runs
immediately before DKIM signing, the header is covered by our own signature.
prefer-encrypt=mutual is claimed only when the effective ENCRYPT policy
is required. Level 1 §2.4 has a correspondent’s client encrypt by default
only when both ends say mutual, so claiming it under an opportunistic policy
— one whose ON_NO_KEY = cleartext says unencrypted mail is acceptable —
would announce a preference the deployment does not enforce.
Level 1 §5.3 key gossip — the mechanism by which one message advertises the keys
of the other recipients, so that two correspondents who have never written to
each other can nevertheless answer encrypted — is implemented too, under
[stage-encrypt] AUTOCRYPT_GOSSIP. It is off by default: unlike the
Autocrypt: header, which publishes a key its owner asked this deployment to
hold, gossip redistributes third parties’ cached key material to correspondents
who did not ask for it, and that is an operator’s decision to take deliberately.
Two properties are worth stating, because both are what makes the feature safe
to turn on. The fields ride inside the ciphertext and only there — an outer
one would publish the recipient set of an encrypted message to every relay on
the path — so there is no gossip on cleartext, on signed-only mail, or in an
S/MIME container, which Autocrypt does not define. And the addresses gossiped
are read from the message’s To: and Cc: fields alone: the envelope
recipient list, which carries the Bcc recipients, is consulted only to
remove an address from the set. A blind recipient can therefore never appear in
any copy, which is exactly the rule Level 1 §5.3 also imposes on a receiver.
The receiving half is implemented as well, in
pepsi-stage-autocrypt-learn under LEARN_GOSSIP (default on) — a stage of
its own precisely so that it can run after the spam checks, which §5.3 also asks
for and which pepsi-stage-decrypt, first on the inbound path by design, could
never have honoured. The interesting part of it is not the parser. §5.3 requires a gossiped key to be kept in a peer state separate from
one learnt from an Autocrypt: header, so that a third party’s introduction
can never displace what an address’s owner said about themselves; Pepsi realises
that as a rung of its own at the bottom of the discovery trust ladder
(gossip, rank 8, below harvested), which the database’s conflict rule
already knows how to enforce. A field counts only when it really was inside the
ciphertext — an outer one could have been written by anything on the path — only
when its addr is in the message’s own To:/Cc:, never for the sender’s
own address, and never for an address this host serves. It never carries
prefer-encrypt, and a signature checked against a gossiped key comes back
valid-untrusted, so an introduction opens no gate.
71.2.28. VKS — the verifying key server protocol¶
keys.openpgp.org and its work-alikes speak a small HTTP protocol under
/vks/v1, and it is deliberately not HKP: a key is only served for an
address once the address holder has answered a confirmation mail, which is what
makes the answer worth more than “somebody uploaded this”.
Pepsi speaks both halves. pepsi-keydisc@vks looks a correspondent up by
address, discarding a key whose User ID does not name the address queried and
skipping a revoked one; it ranks below DANE, both Web Key Directory methods
and LDAP, because the confirmation proves control of the mailbox and nothing
more. pepsi-keys uploads our own identities and requests
verification — off by default ([pepsi-keys] VKS_PUBLISH), because publishing
a user’s address to a third party is an operator’s decision and, unlike the Web
Key Directory, it cannot be undone. The server’s confirmation mail then has to be
answered, which is the whole job of
pepsi-stage-vks-confirm — a stage that follows a link only after
six independent checks, including that the envelope sender’s host is exactly the
configured key server and that every link in the body points there too.
71.2.29. RFC 8446 — TLS 1.3¶
Every TLS-protected connection in Pepsi — inbound STARTTLS and implicit-TLS
listeners, the outbound relay client, LMTP, the HTTP server and the LDAP
discovery client — runs over rustls, which speaks TLS 1.3 and 1.2 and
nothing older. There is no configuration knob to re-enable SSLv3 or TLS 1.0/1.1,
because rustls does not implement them.
What varies between those connections is not the protocol but what
authentication is required of the peer, and that is where the mail-specific
standards take over: RFC 6125 for identity matching, RFC 7672 for DANE, RFC 8461
for MTA-STS, and the TLS_VERIFY option for the case where an operator
knowingly relaxes it. Opportunistic STARTTLS to an arbitrary MX authenticates
nothing by design — that is RFC 3207’s model, not a weakness here — which is
precisely why DANE and MTA-STS exist and why the outcome of every attempt is
recorded for RFC 8460 reporting.
71.2.30. RFC 8314 — Implicit TLS¶
A listener with MODE = tls performs the TLS handshake before the SMTP
greeting (implicit TLS, e.g. the submissions port 465), as recommended by
RFC 8314.
Following RFC 8314 §4.3, ingress also records the negotiated TLS version and
cipher in the Received: trace header it prepends — as a
(version=… cipher=…) comment after the ESMTPS keyword — since the bare
ESMTPS transmission type does not convey which parameters were used.
71.2.31. RFC 8460 — SMTP TLS Reporting (TLSRPT)¶
When [pepsi-tlsrpt] SEND_REPORTS is on, the relay stages record the outcome
of each outbound TLS session in the pepsi.tls_session aggregate-counter
table. pepsi-tlsrpt(1), run daily from cron, groups a day’s rows into one
RFC 8460 JSON report per policy domain, gzips it, looks up the domain’s
_smtp._tls rua and ships it by mailto: (injected back into the
pipeline so it is signed and relayed) or HTTPS POST. The advertising half is
pepsi-setup printing our own _smtp._tls TXT record from
[pepsi-tlsrpt] RUA. The shared types, failure classifier and report
builders live in pepsi-common::tlsrpt. See pepsi-tlsrpt.
71.2.32. RFC 8461 — MTA-STS¶
pepsi-stage-relay-to-internet discovers and enforces recipient-domain MTA-STS
policies: an enforce policy restricts delivery to the listed MX hosts over
authenticated STARTTLS. pepsi-setup publishes our own _mta-sts TXT
record and prints the policy file. See pepsi-setup.
71.2.33. RFC 8601 — Authentication-Results¶
Ingress prepends a standalone Authentication-Results header carrying the SPF,
DKIM, DMARC and iprev (FCrDNS) verdicts, with the ingress hostname as the
authserv-id. The ARC stage reproduces the same assessment in its AAR.
71.2.34. RFC 8617 — ARC¶
pepsi-stage-arc verifies any inbound ARC chain and seals the
message as our ADMD by prepending an AAR/AMS/AS set, signed with the single
ARC_ALGORITHM. This lets the final receiver trust the boundary verdict even
after Pepsi’s forwarding breaks SPF/DKIM alignment. The ARC-Message-Signature’s
RFC 6376 parameters — canonicalization (c=), covered headers (h=), the
l= body-length tag and an x= expiration — are configurable on the stage
(as they are on pepsi-stage-dkim-sign).
71.2.35. RFC 9051 / RFC 4155 — reading a mailbox (IMAP and mbox)¶
Pepsi is not a mail store and serves neither protocol. It reads mailboxes in
one place only: pepsi-helper-mailbox-scan, which seeds a whitelist from the
mail a user has already sent (see pepsi-whitelist).
The IMAP client is read-only in the strict sense — it issues EXAMINE rather
than SELECT and fetches BODY.PEEK[HEADER], so no message is marked
\Seen and no mailbox state changes. Only header blocks are read; every
reader stops at the blank line, so message bodies never enter the process. The
same helper reads local mbox (RFC 4155) and Maildir, and can consume a
doveadm -f json stream.
LMTP (RFC 2033) is the delivery counterpart and has no fetch verb at all, which is why a mailbox on an LMTP-only MDA cannot be scanned; IMAP is the network option.
71.2.36. RFC 9106 — Argon2¶
Argon2id appears twice, for two different threats.
In the secure-link portal (The secure-link fallback portal) it derives the key that encrypts a stored message from the recipient’s PIN plus a per-message salt and a server-side pepper held outside the database. The cost parameters are chosen so that a PIN short enough to read over the telephone is still expensive to grind, and the pepper is what makes a stolen database insufficient on its own — which is why disclosing it is treated as seriously as disclosing the messages.
In the administrative API it hashes account passwords. That instance is also why the portal’s rate limiter matters: at 19 MiB per derivation, an unauthenticated attacker who could retry freely would consume memory as fast as they consume guesses.
71.3. Applicable RFCs not yet implemented¶
These standards are in scope for a forwarder or a crypto gateway and are tracked
on the roadmap (docs/ROADMAP.md) but are not implemented today. Several
are partial in a way worth stating precisely: a status object exists and is
reported, which is easy to mistake for the check having been performed.
RFC |
Title |
Status in Pepsi |
|---|---|---|
RFC 3030 |
|
|
RFC 6960 |
Online Certificate Status Protocol (OCSP) |
Not implemented, and this is the one most likely to be misread.
|
RFC 2634 |
Enhanced Security Services for S/MIME (triple wrapping) |
Not implemented, by decision (plan 01 D6). Pepsi signs then encrypts, or encrypts then signs, but never sign-encrypt-sign. Signed receipts and security labels are likewise absent. |
RFC 3161 |
Time-Stamp Protocol (TSP) |
Not implemented. The S/MIME |
RFC 6541 |
DKIM Authorized Third-Party Signatures (ATPS) |
Not implemented. The |
RFC 6651 |
Extensions to DKIM for failure reporting |
Not implemented. The |
RFC 5321 |
|
Not implemented and not planned. Pepsi’s queue is a database table drained by the dispatcher; there is no per-domain queue to release. |
Autocrypt Level 1 |
Gossip peer-state promotion (§5.3) |
Both halves of gossip are implemented, but one nuance of the peer-state
model is not: Level 1 lets a client promote a gossiped key when the
address’s owner later confirms it, and Pepsi has no such transition. A
better-sourced answer replaces the gossiped row outright (which is the
safe direction), and until one arrives the key stays on the |