.. This file is part of PEPSI. Copyright (C) 2026 GNUnet e.V. PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. ======================= pepsi-stage-vks-confirm ======================= *Follow a key server's address-verification link, and nothing else.* Role ==== A key uploaded to a verifying key server is stored but **not findable by address** until somebody follows a link in a mail the server sends to that address. ``pepsi-stage-vks-confirm`` does that, on the inbound path, so key publication completes without a human reading every user's mailbox. It is a separate program because it fetches URLs found in inbound mail. That is a dangerous primitive, and keeping it in its own small, unprivileged stage keeps it out of the programs that hold key material or parse hostile ciphertext, and makes its position in the pipeline visible in the configuration. Reference: :manpage:`pepsi-stage-vks-confirm(1)`. Everything it does not act on — nearly all mail — is advanced to ``NEXT_STAGE`` untouched, so placement is forgiving. What it refuses =============== Every one of these must hold before a byte is fetched. Failing any of them means the message is delivered instead, and the user can click the link themselves — which is what lets the conditions be this strict. * **The envelope sender's domain is exactly** ``VKS_HOST``, not a subdomain, not a suffix match, and not the ``From:`` header. * **SPF or DMARC passed** (unless ``REQUIRE_AUTHENTICATION = no``). A ``MAIL FROM`` is a claim; this is what turns it into evidence. ``dkim=pass`` deliberately does not count — it says a signature verified, not whose. * **Every link is** ``https`` **to that same host, on the default port.** A message naming any other host is refused entirely rather than having the foreign link skipped: a genuine verification mail links to the key server and nowhere else. The host is read after any userinfo, because ``https://keys.example.org@evil.test/`` is a request to ``evil.test``. * **A recipient is an address we are actually waiting on** — published, uploaded, not yet verified. An unsolicited verification mail is delivered, not clicked, which is also what stops a stranger making Pepsi issue requests by mailing a served address. * **At most** ``MAX_LINKS`` **per message**, fetched through the same hardened client key discovery uses: HTTPS only, certificates verified, the resolved address vetted against the SSRF guard, the body capped while streaming. Confirmation is verified, not assumed ===================================== A ``200`` from the link is the server's answer to a request, not a statement that the address is now published. The stage therefore asks the key server whether it serves *our* key for the address, and only a fingerprint match records the identity as verified — marking it on the ``200`` would stop :doc:`pepsi-keys`'s retry job from ever trying again. The same check decides the mail's fate: it is discarded (``DISCARD_CONFIRMED``) only when at least one identity was verified, and delivered otherwise, so a confirmation that did not take effect can still be finished by hand. Every decision and every fetch is logged at ``info`` on the ``vks-confirm`` target, which is the audit trail. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-vks-confirm``, ``NEXT_STAGE`` (**required** — see above), ``VKS_HOST`` (defaulting to the host of ``[pepsi-keys] VKS_SERVER``; ``pepsi-setup`` refuses a value naming any other host, since the stage would then ignore every confirmation), ``MAX_LINKS`` (3), ``DISCARD_CONFIRMED`` (``yes``: a confirmation mail that verified an identity is machine mail about an action Pepsi took) and ``REQUIRE_AUTHENTICATION`` (``yes``). See :manpage:`pepsi-stage-vks-confirm(1)`. State ===== * **Inputs:** ``state.auth.spf`` / ``state.auth.dmarc``, the envelope sender and the message body. * **Outputs:** none. The stage never writes to ``state`` and never rewrites the message; what it changes is the key store's publication bookkeeping. See also ======== :doc:`../key-management`, :doc:`pepsi-keys`, :doc:`pepsi-httpd`, :doc:`pepsi-keydisc`, :manpage:`pepsi-stage-vks-confirm(1)`.