End-to-end encryption: S/MIME authority, OpenPGP and WKD, zero access, sealed mail
-
Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes the anti-virus action that removes attachments (Remove all attachments and deliver the text in 6.3.7's Control Panel, Delete infected attachments only in 6.3.6's): an S/MIME message signed as one part, which is opaque but not encrypted, is now replaced by the action's note when the scanner finds something in it, where every earlier version delivered it as it arrived, and a message the server cannot read is deleted, its recipients told; an encrypted message is still delivered as it is (work item 92). See the 6.3.7 announcement. In hMailServer 6.3.8 (2 November 2026) the web services listener refuses an address an auto-ban or an IP range bans (work item 105), so an IP range of your own that allows some addresses none of SMTP, POP3 and IMAP now closes that listener to them, and with it the Web Key Directory: check that the clients that look keys up there do not come from such a range. See the 6.3.8 announcement.
This topic is about encrypting the message rather than the connection, so that the person it is for can read it and nobody on the way can, this server included. S/MIME in the webmail has been there since 6.3.2; everything else here is new in 6.3.4. The design rule running through all of it is that this server holds nothing that decrypts a person's mail: keys are made in the browser and kept wrapped under a passphrase or password the server never sees. The one thing to know before you allow any of it is the cost of that rule: nothing on this server can recover mail encrypted to a key that was lost. There is no escrow.
Which one you want
You want Use S/MIME inside the organisation without buying a certificate per person A domain's S/MIME authority Correspondents who use GnuPG, Thunderbird, Proton or Mailvelope OpenPGP in the webmail, and the Web Key Directory To write privately to somebody who has no key at all A password-protected message A mailbox the administrator cannot read Zero access Everything to one partner organisation encrypted, whatever the sender's client did Gateway encryption, on a remote domain policy A stolen disk or backup to hold nothing readable, while the mailbox works as before Mailbox encryption at rest, which the server can read and so is not in this topic All of the browser cryptography needs a page opened over HTTPS: a browser withholds
crypto.subtlefrom any other, and the page says so instead of offering Sign and Encrypt.S/MIME with your own authority
Since 6.3.4 (schema 6095). A domain may have its own S/MIME authority: a root certificate the domain owns, which certifies the keys its accounts make in the webmail, publishes them for colleagues to look up, and says what it has revoked in a signed CRL. Nothing is issued until an administrator creates one.
Who holds what. The person's private key never leaves the browser. The webmail makes the key pair, builds a PKCS#10 request, wraps the private key under the account password, and sends only the request. The authority's own key is on the server, because it signs there, and it is protected by the platform's secret store:
Where the authority's key opens Windows DPAPI with the machine's key. It opens on this machine and nowhere else, and on this machine to any process that asks Linux The stored-secret key file, <DataFolder>/.hmailserver-secret-key(AES-256-GCM, mode 0600). It opens wherever that file isIt never falls back to the legacy scheme. When the store is unavailable the authority is not created (
503).Creating it. In the Control Deck, open the domain and find S/MIME authority, or
POST /api/v1/domains/{domain}/smime-cawith{"key": "p256"}or"rsa3072". The root is self-signed, CA:TRUE with pathlen:0, valid ten years, with a critical name constraint permitting the domain's own addresses and nothing else. P-256 is the default. The Control Panel shows nothing of it, and there is no COM surface.Enrolment. A person finds Get a certificate from {domain} under Settings → Security → S/MIME.
- They pick the key type. RSA 2048 is the default and the one to choose. A P-256 certificate from this page signs, and neither this webmail nor a colleague's can encrypt to it.
- The page makes the key pair and the request, and asks for the account password that wraps the private key before anything is sent.
POST /api/v1/me/smime/enrollis made from the account's own sign-in. An app password and an administrator's support session are refused with403.- The server checks the request. It must name exactly one address, and that address must be the account's own or an active alias of its own domain that points at it.
- The certificate is issued pending. When it arrives in the account's own store it is confirmed, and only then is it answered to colleagues and the address's earlier certificates revoked as superseded.
The limits are five enrolments a day (
429) and ten valid certificates at once (409). A person with two devices ticks Keep my other certificates for this address on the second.What is issued. Valid one year. With
smime_authority_base_urlempty, which is the default, a certificate carries no CRL distribution point. What that costs: Outlook and Thunderbird cannot fetch the CRL from the certificate, so a revocation reaches them only when they have been given the CRL another way. This webmail does not use the CRL at all; it reads the authority's own record.Publication and trust. The root and the CRL are served over plain HTTP by the web services listener (
WebServicesHttpPort), at/smime-ca/{domain}.pem,.cerand.crl. A signature verifies in Outlook, Thunderbird, Apple Mail or a phone only once the root is installed on the clients, by group policy, a device-management profile or by hand. This webmail trusts the domain's authority by itself. A colleague's certificate is looked up withGET /api/v1/me/directory/{address}/smime, which the webmail asks on its own when Encrypt is ticked.Moving and restoring. The authority is in the configuration backup, but its key opens only where its envelope opens. Restored anywhere else, the Deck shows a warning, enrolment is refused with
503, its CRL is not served, and the authority must be rotated there. Rotation makes a new root and retires the old one without deleting it, and clients must install the new root beside the old.What it is not. Not Active Directory Certificate Services. No OCSP. No key escrow: a forgotten password that the key was wrapped under leaves the mail encrypted to that key unreadable, by anybody, for ever. No elliptic-curve encryption in the webmail. One address per certificate. No automatic renewal: a person enrols again before the year is out.
OpenPGP: the half in the directory and the half in the browser
The directory The page Where it runs On the server In the person's browser What it holds Public keys only Own keys, the secret half encrypted under a passphrase, and correspondents' public keys What cryptography it does None at all All of it: signing, encrypting, decrypting, verifying, in PGP/MIME Table and schema hm_pgpkeys, schema 6050hm_pgpring, schema 6080; the trust columns 6081State as shipped Three switches, all off portal_enabledonIn neither half does the server sign, verify, encrypt or decrypt a message. It makes an OpenPGP message itself in only two places further down, a delivery to a zero-access mailbox and gateway encryption to a partner's key; there it encrypts, never signs or decrypts, and verifies only the binding of the subkey it encrypts to.
The directory publishes keys at a URL derived from the address, so that anybody who has the address can find the key.
<hash>is the local part, lower-cased, SHA-1 hashed and z-base-32 encoded. Both methods are served:Method The key advanced https://openpgpkey.<domain>/.well-known/openpgpkey/<domain>/hu/<hash>direct https://<domain>/.well-known/openpgpkey/hu/<hash>Three switches have to be on before one key is served:
- The server:
OpenPGPWebKeyDirectoryEnabled, set withhmctl settings set openpgp --set web_key_directory_enabled=trueorPUT /api/v1/settings/openpgp. - The domain:
openpgp_publishon the domain. - The account:
openpgp_publishon the account. This one is the person's own, throughPUT /api/v1/me/openpgp/publish.
The web services listener has to exist (
WebServicesHttpsPort), and the advanced method, which clients try first, needs an address record foropenpgpkey.<domain>and a certificate valid for it. Keys are served over HTTPS only. Every refusal is the same404, whether the domain is unknown, a switch is off, or the account has no key, so that the directory cannot be used to ask whether somebody has an account.Put a key in with
hmctl openpgp store [email protected] ann.asc --publish, and check it as a correspondent's client will:gpg --locate-keys --auto-key-locate clear,wkd [email protected]An upload carrying a secret-key packet is refused; so are a version 3 key, a key with no user id or no self-signature, a key past
OpenPGPKeyMaximumBytes(65536), and a key whose user ids do not carry the account's own address. An account may holdOpenPGPKeysPerAccountkeys (8). A signature on an uploaded key is not verified cryptographically: what makes a key in this directory the right one is that its owner or an administrator put it there. The desktop Control Panel has nothing for the directory yet.The page is
/portal-openpgp.js, written on the Web Crypto API with no library vendored.- Keys. Curve25519 (the default) or RSA 3072 or 4096, made in the browser. The secret key is stored as OpenPGP writes one, under the passphrase, so the bytes the server holds are bytes it cannot open, and the same bytes import into GnuPG. A revocation certificate is made at the same moment and saved to the device.
- Autocrypt Level 1, both ways.
- Finding a key.
GET /api/v1/me/openpgp/lookup?address=asks the correspondent's own Web Key Directory (lookup_wkd, on) and then a keyserver (lookup_keyserver, off by default;keyserver_urlishttps://keys.openpgp.org). Nothing is stored by the lookup. - Trust made visible. A different key arriving for an address the ring already holds a key for is stored as a change. Until the person accepts it, the page does not encrypt to it. A key can be marked verified in person, and the mark is never inherited by a key that replaces it.
- Who may write. Every write to the ring is refused to an app password and to an administrator's support session.
Not implemented, and refused by name: version 5 and 6 keys and AEAD; DSA and ElGamal keys and the NIST and Brainpool curves; smartcards and hardware tokens; the web of trust; a draft or a scheduled send that is to be sealed. Curve25519 needs Chrome 137, Firefox 130 or Safari 17 or later. Both directions are proved against GnuPG 2.4 by
build/check-portal-openpgp.js, which GitLab CI runs on every push and every merge request.A password-protected message (schema 6082) is for the correspondent with no key. The page encrypts the text and attachments to a password as one OpenPGP message, and what goes out by SMTP is a short notice with a link to
/portal/protected/<token>, where the recipient's browser decrypts it. The link is the whole access control. It needsportal_enabledon and a public address for the links (theconfidential_base_urlchain the messages that expire use), and is offered only over HTTPS. A message livesprotected_message_days(30 by default). The ceilings are 25 MB a message and 250 MB an account. It does not promise that the recipient cannot copy what they read.Zero-access mailboxes
Since 6.3.4 (schema 6083). A mailbox this server holds and cannot read. An administrator allows it server-wide once (
zero_access_allowed, under/api/v1/settings/openpgp, off by default); the person then switches it on for their own account on the webmail's Security page. From then on every message delivered to the account is encrypted to the account's own OpenPGP key - after the spam and virus filters, the rules and the Sieve script have had the text - and stored as PGP/MIME with the headers in the clear. An administrator sees the switch read-only everywhere.What it costs. The webmail refuses to switch until the recovery key has been typed back and the sentence about a lost password has been ticked.
Cost What it means The administrator cannot read the mail The message source and the account export answer 403Neither the administrator nor the server can search it The index and IMAP SEARCH read the headers, never the text. A word in the subject is found; a word in the body is not Nobody can recover it A forgotten password without the recovery key is the mail lost, for ever IMAP and POP see ciphertext Valid PGP/MIME that a client holding the key opens. Until then the account is a webmail account The plaintext was on the disk From acceptance until the seal. The account's copy is overwritten with zeros; whether the bytes are gone is the filesystem's decision Only what is delivered is sealed, and the Sent copy A draft, and anything a client uploads with IMAP APPEND, is stored as it is A journal rule sees the plaintext Journaling runs at acceptance, before delivery The filters have read it A forward goes on in the clear. A rule that files into a public folder files the plaintext there. Quarantined mail is held in the clear Switching off decrypts nothing Mail that arrived while it was on stays encrypted to the key The headers the server kept.
From,To,Cc,Date,Subject,Message-ID, theReceivedtrace and the rest stay in the clear, so that the listing, the threading and the spam folder work without the key. The server addsX-hMailServer-Zero-Access: sealed on delivery.The key, in two sealed copies.
hm_zeroaccessholds two copies of the secret key, both sealed in the browser:- one under a passphrase the page derives from the account password with Argon2id, three passes over 64 MiB in four lanes. It was measured at 1.6 seconds in Node 24 on the development machine; a phone is expected to take several seconds, and it was not measured on one;
- one under the recovery key: 30 random octets shown once as eight groups of six base32 characters.
A key that carries an expiry is refused at the switch-on.
When a seal fails, the delivery is refused rather than stored in the clear: the sender is told with
4.3.0and the error log carriesHM7070.Passwords. A change through the webmail carries the key sealed again under the new password, and the server refuses a change without it (
409,zero_access_rewrap). A password an administrator sets cannot re-wrap; at the next sign-in the page asks for the previous password or the recovery key.It is not encryption at rest. Mailbox encryption at rest stores mail under a key the server holds while the person is signed in. The two are never on one mailbox: each switch is refused where the other is on.
Not built: a local bridge for IMAP or POP clients; search over the decrypted text in the browser; encryption of mail already in the mailbox when the switch is thrown.
What the server does with a sealed message
A sealed message is one signed or encrypted end to end, which must leave this server byte for byte as it arrived. An opaque one is a sealed message the server cannot read at all. A
multipart/signedmessage is sealed but not opaque: its text is in the clear.Opaque is decided from the body, never from the label, or every check that stands aside for an encrypted message would be one header away from being switched off by anyone sending mail. The body must be S/MIME whose one part begins a CMS object (enveloped-data, authenticated-enveloped-data or signed-data), or PGP/MIME of exactly two parts:
Version: 1, then one armoured OpenPGP message. None of what follows is a setting.Part of the server What it does with an opaque message Blocked-attachment list Never rewrites it. One application log line says the list was not applied. In 6.3.3 and earlier, blocking *.ascor*.p7mdestroyed the ciphertext without a log lineVirus scanner Scans it, and logs that its content COULD NOT BE EXAMINED for viruses. It is not treated as a scanner failure, soAVFailAction=1does not hold it. If mail the server cannot scan must not be delivered, use a rule or a Sieve script onbody :rawDelete attachments action (Remove all attachments and deliver the text since 6.3.7) Never strips an encrypted message. Since 6.3.7 an S/MIME message signed as one part - application/pkcs7-mimeholding signed-data, opaque but not encrypted - is replaced by the action's note when the scanner finds something in it, where it was delivered as it arrived (work item 92). Set the action to delete the message if such mail must not be deliveredRules A body criterion does not match, whichever way it is phrased, with one log line per rule per message: under AND the rule does not fire; under OR its other criteria still count. To catch encrypted mail, test Content-Typeformultipart/encryptedorapplication/pkcs7-mimewith a custom-header criterionSieve The evaluation is unchanged, as RFC 5173 requires; a log line is new. Use body :rawto recognise such a messageSearch and the index Only the header is indexed Content spam scoring Sees nothing in it, so encrypted spam is scored more leniently. SPF, DKIM, DMARC and DNSBL are unaffected A message only labelled encrypted, whose parts are readable, is blocked, stripped, filtered and searched like any other. In SealedMail.md's words: a message that arrives encrypted is not scanned, and your mail client, and you, are the last check.
Gateway encryption, briefly
Since 6.3.4 (schema 6094). On a remote domain policy, Encrypt mail to this domain at this server encrypts every message to one partner, here, to the partner's own gateway key. It encrypts at the server, as a delivery to a zero-access mailbox does, and the server holds no private key for it. The Subject is not hidden. When a message cannot be encrypted the default is to refuse it with
5.7.10. It does not sign, look keys up, or decrypt replies.Where to read more
- Operator documents at the v6.3.4 tag: SmimeAuthority.md, OpenPGP.md, ZeroAccess.md, SealedMail.md and RemoteDomains.md.
- Wiki: Encryption for People and Encryption and Certificates.
- Public documentation: 36.6 The portal: what a user gets.
- On this forum: The webmail portal at /portal, The Control Deck, Moving to new hardware before you carry an authority's key to another machine, and the rest of this category.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login