Encryption Encryption Settings Encryption Settings Admin path: Encryption > Encryption Settings ( view_encryption_settings.cfm, inc/edit_encryption_settings.sh). This is the global Ciphermail policy page — a thin CFML wrapper over a fixed set of CipherMail "global" properties that govern subject-based encryption triggering, the PDF reply-sender identity, and three internal shared secrets used by the Secure Email Portal back-channel. Per-recipient policy lives on External Recipients; CA / S/MIME issuance lives on Internal CA. This page is the small set of gateway-wide toggles that affect every encrypted send. Important: not a full encryption-mode picker. The page does NOT pick "always encrypt vs opportunistic vs off" at the system level — CipherMail does that per-recipient via the user's user.encryptMode property (set when the admin creates the recipient on External Recipients). The only system-wide opt-in/opt-out exposed here is the Subject Trigger mechanism: whether [encrypt] (or whatever keyword is configured) in a message subject promotes that one message to an encryption attempt. What the page persists Every setting on the page is stored twice: once in the Hermes encryption_settings table (so the UI can re-render the current state on next load) and once in CipherMail's own global property store via the CLITool --set-property ... --global invocation. The two are kept in sync by re-running the full apply script on every save. Field encryption_settings.property CipherMail property Notes Trigger Encryption by Subject (Enabled / Disabled) user.subjectTriggerEnabled user.subjectTriggerEnabled true / false string Subject Trigger Keyword user.subjectTrigger user.subjectTrigger Free text, e.g. [encrypt] Remove Trigger After Encryption (Yes / No) user.subjectTriggerRemovePattern user.subjectTriggerRemovePattern When true, the keyword is stripped before the recipient sees the message PDF Reply Sender Email user.pdf.replySender user.pdf.replySender Email validated as IsValid("email", ...) before save Portal URL (read-only, derived) user.portal.baseURL user.portal.baseURL Built at save time as https:///web/portal — NOT directly editable on this page; change Console Host on System Settings Server Secret Keyword user.serverSecret user.serverSecret (encrypted) 64-char auto-generated, masked in UI Client Secret Keyword user.clientSecret user.clientSecret (encrypted) 64-char auto-generated, masked in UI Mail Secret Keyword user.systemMailSecret user.systemMailSecret (encrypted) 64-char auto-generated, masked in UI Additionally, the script always sets user.otpEnabled = true --global on every save — a fixed override that ensures CipherMail's one-time password feature is on globally regardless of any prior state. Subject Trigger: how it actually works When Trigger Encryption by Subject is enabled, CipherMail inspects each outbound message's Subject: header during processing: +------------------+ +-------------------+ +-----------------+ | Outbound message |----->| CipherMail |----->| Encryption | | Subject: | | subject-trigger | yes | policy for | | "[encrypt] Q4" | | match? |----->| this recipient | +------------------+ +-------------------+ +-----------------+ | no v +-----------------+ | Recipient's | | user.encryptMode| | decides | +-----------------+ Setting combination Behavior Trigger ENABLED + Keyword present + Recipient user.encryptMode = allow Message encrypted using whichever protocol the recipient has enabled (S/MIME / PGP / PDF). If none, CipherMail falls back to its protocol-selection rules. Trigger ENABLED + Keyword present + Recipient user.encryptMode = mandatory Already always-encrypted; the keyword is redundant. If Remove Trigger is on, the keyword is still stripped from the visible subject. Trigger ENABLED + Keyword NOT present + Recipient user.encryptMode = allow Message sent plaintext (the recipient is configured "by subject" and the sender did not opt in). Trigger ENABLED + Keyword NOT present + Recipient user.encryptMode = mandatory Encrypted regardless (recipient policy overrides). Trigger DISABLED Subject line is never inspected; recipient user.encryptMode is the sole authority. Senders cannot opt-in per message. Recipient user.encryptMode is set when the admin picks a mode (e.g. "PDF Mandatory" vs "PDF By Subject") on Encryption > External Recipients > Create. See External Recipients — Encryption modes. PDF Reply Sender When a recipient receives a PDF-encrypted message and clicks the reply link in the encrypted PDF, the response comes back to Hermes via the Secure Email Portal. The PDF Reply Sender Email is the From: address CipherMail uses when delivering that reply back to the original internal sender (and on system notifications about PDF reply activity). Operators typically set this to a monitored address like postmaster@yourdomain.tld or a dedicated secure-reply@... mailbox. The field is validated: empty or non-email values trigger alerts m=3 and m=2 respectively and abort the save. The three secret keywords CipherMail uses three independent shared secrets to authenticate the back-channel between the encryption engine and the Secure Email Portal ( /web/portal/). They are stored AES-encrypted in encryption_settings.value (using /opt/hermes/keys/hermes.key as the key) and pushed into CipherMail with the --encrypt flag so CipherMail encrypts them again with its own key. Secret Used by Generated by Server Secret ( user.serverSecret) CipherMail server-side validation of portal session tokens Click the sync icon on the field; never user-entered Client Secret ( user.clientSecret) Portal client-side validation handshake Click the sync icon Mail Secret ( user.systemMailSecret) Signing of system-generated email notifications (password delivery, portal invitations, etc.) Click the sync icon The UI masks the values to ******************** — full plaintext is never re-displayed after generation. To replace a secret, click the sync ( fa-sync-alt) button on its row; a confirmation modal fires; on confirm Hermes: Generates 64 lowercase hex-ish characters by concatenating 8 rounds of the standard customtrans3 token generator and truncating. AES-encrypts that with /opt/hermes/keys/hermes.key and UPDATEs encryption_settings.value for the corresponding property. Runs the full edit_encryption_settings.sh apply script (see below) to push all three secrets — plus the subject-trigger / PDF reply / portal URL settings — into CipherMail in one shot. Rotating any one secret therefore re-applies the other two as a side-effect; in practice the values are stable across rotations because the script reads each from its already-decrypted form before writing. Operational consequence: rotating a secret invalidates any in-flight portal sessions for that secret's role. Recipients with an active portal session may need to log in again; system notifications in transit may fail signature verification and be re-queued. The apply pipeline Both Save Settings and Generate Secret funnel through the same temp-script pattern documented across the Hermes admin: +--------------------+ +-----------------------------+ +-------------------+ | CFML page UPDATEs |----->| Read /opt/hermes/scripts/ |----->| REReplace 9 | | encryption_settings| | edit_encryption_settings.sh | | placeholders | +--------------------+ +-----------------------------+ +-------------------+ | v +---------------------+ | Write to | | /opt/hermes/tmp/ | | _edit_...sh | +---------------------+ | v +---------------------+ | chmod +x and execute| | (240s timeout) then | | delete the temp file| +---------------------+ | v +---------------------+ | 9 sequential | | docker exec | | hermes_ciphermail | | CLITool --global | +---------------------+ Placeholders substituted in the template: Placeholder Replaced with PDFREPLY-SENDER user.pdf.replySender value PORTAL-URL Derived https:///web/portal SUBJECT-TRIGGER user.subjectTrigger value SUBJECT-ENABLE true / false TRIGGER-REMOVE true / false SERVER-SECRET Decrypted server secret (pushed with --encrypt so CipherMail re-encrypts) CLIENT-SECRET Decrypted client secret MAIL-SECRET Decrypted mail secret On a CLITool execution failure the page sets session.m_enc = 11 and surfaces "Settings saved to database but failed to apply to Ciphermail. Please check the logs." — the DB write succeeds first, so the UI state matches what the operator entered even when the CipherMail-side push fails. Re-save (with no edits) re-runs the apply script. What's NOT on this page Several things an operator might reasonably expect from a global "Encryption Settings" page that live elsewhere: Expectation Where it actually lives Per-recipient "always encrypt vs by subject vs never" External Recipients ( user.encryptMode per CipherMail user) Default cipher / algorithm selection (AES-128 vs AES-256, RSA key sizes) CipherMail Advanced Settings ( /ciphermail/, external link in sidebar) Per-mailbox sign / encrypt action defaults Email Server > Mailboxes (per-mailbox encryption action editor, inc/edit_mailbox_encryption_action.cfm) TLS opportunistic vs DANE policy on outbound delivery Email Relay > Relay Hosts and TLS Settings; this page is about message-content encryption only Subject keyword for DLP-driven (content-based) encryption triggers Not implemented in Hermes; CipherMail Advanced Settings can express custom DLP rules Portal URL customization Derived automatically from System > Console Settings ( parameters2.console.host); editing console host updates this on next save S/MIME signing of every outbound (gateway sign-and-forward) CipherMail Advanced Settings; not surfaced here Password complexity rules for the auto-generated portal / PDF passwords Hardcoded in the modal JS on External Recipients (16-char mixed alphanumeric) Body-modification interaction The CipherMail encryption / signing pass runs after the hermes_body_milter disclaimer / signature / banner pipeline. That means PDF, S/MIME, and PGP envelopes always wrap the final body the recipient sees — including any appended disclaimer (see Disclaimers — Behavior with S/MIME, PGP, and DKIM-signed mail). The same milter-ordering rationale applies to ARC inbound sealing (see ARC Settings — Container and milter placement): the cryptographic envelope is the last thing applied so it always matches what the recipient downloads. Container and database touch-points Component Container / path Role Page config/hermes/var/www/html/admin/2/view_encryption_settings.cfm ( hermes_commandbox) CRUD UI + apply orchestration Template script /opt/hermes/scripts/edit_encryption_settings.sh ( hermes_commandbox bind mount) 9-line shell with 9 placeholders Temp scripts /opt/hermes/tmp/_edit_encryption_settings.sh Substituted copy, executed once, deleted Settings store (Hermes side) encryption_settings in hermes DB ( hermes_db_server) One row per property; secrets stored AES-encrypted in value Settings store (CipherMail side) cm_properties in djigzo DB ( hermes_db_server) — set indirectly via CLITool --global CipherMail's authoritative global property store Encryption engine hermes_ciphermail (Java; CipherMail Community 5.x branded djigzo) Performs S/MIME / PGP / PDF encryption at send time Encryption key /opt/hermes/keys/hermes.key ( hermes_commandbox bind mount) AES key used for CFML-side encrypt() / decrypt() of the three secrets Console host source parameters2.console.host in hermes DB Drives the auto-derived user.portal.baseURL Related External Recipients — per-recipient encryption modes; the page where user.encryptMode = mandatory vs allow is actually chosen Internal CA — where the S/MIME root CAs that mint per-recipient certs live; cross-referenced by recipient PDF / S/MIME / PGP rows on External Recipients PGP Key Servers — outbound key publishing list (note: publish-only, not lookup) Disclaimers — body-mod ordering against the CipherMail encryption pass ARC Settings — same milter-ordering pattern applied to inbound chain sealing DMARC Settings — cross-references the body-mod pipeline that also feeds DKIM signing Advanced Settings (sidebar link to /ciphermail/) — CipherMail's own admin UI; everything not surfaced on this page (per-protocol cipher selection, custom DLP, gateway-wide always-sign) lives there External Recipients External Recipients Admin path: Encryption > External Recipients ( view_ext_rec_encryption.cfm, view_create_ext_recipient.cfm, view_ext_smime_certificates.cfm, view_ext_pgp_keyrings.cfm, view_ext_add_smime_cert.cfm, view_ext_add_pgp_keyring.cfm, inc/create_ext_recipient.cfm, inc/delete_ext_recipient.cfm, inc/reset_pdf_password.cfm, inc/reset_portal_password.cfm). This is the per-counterparty encryption policy and key store for external (non-managed) email addresses. Each row binds a single external email to one of three protocols (PDF / S/MIME / PGP) and to one of two trigger modes (Mandatory / By Subject). It is the page where the policy referenced by Encryption Settings actually takes effect — the global page chooses the mechanism (subject trigger keyword, shared secrets, PDF reply sender); this page chooses the policy for every external recipient the gateway encrypts to. The DataTable is the master view across both Hermes-side metadata ( external_recipients in the hermes DB) and CipherMail's own user table ( cm_users in the djigzo DB), joined on email address. Rows are tagged Admin-Configured (explicitly created on this page, with a matching external_recipients row) or Auto-Discovered (materialized by CipherMail during message processing, no external_recipients row). Schema: two tables, one view +--------------------------+ +--------------------------+ | hermes.external_recipients | djigzo.cm_users | | (admin metadata) | | (CipherMail user store) | +--------------------------+ +--------------------------+ | email | ---- | cm_email | | encryption_mode | | cm_id --> cm_properties| | pdf, smime, pgp (flags) | | (per-user | | pdf_mode | | policy) | | pdf_password (AES-enc.) | +--------------------------+ +--------------------------+ | v Page renders Admin badge | +--------------------------+ | If NO matching row, | | recipient is "Auto" with | | inferred policy from | | cm_certificates_email / | | cm_keyring_email | +--------------------------+ The page never N+1's against CipherMail — three batch queries build struct lookups ( adminLookup, smimeLookup, pgpLookup) and the row loop reads from those instead of per-row queries. That matters at any scale beyond a few hundred recipients. external_recipients columns: Column Purpose id PK email External email address (joined to cm_users.cm_email) encryption_mode pdf_mandatory / pdf_by_subject / smime_mandatory / smime_by_subject / pgp_mandatory / pgp_by_subject pdf / smime / pgp Flag (1 / NULL) indicating which protocol is the active one for this recipient pdf_mode For PDF only: static / random / backtosender pdf_password AES-encrypted (with /opt/hermes/keys/hermes.key) copy of the static PDF password — for admin re-display only; CipherMail holds its own copy smime_mode / pgp_mode Reserved for parity; populated identically to encryption_mode for the matching protocol Encryption modes The 6 encryption modes map cleanly onto two axes (protocol × trigger): Mode CipherMail user.encryptMode CipherMail user.pdf.encryptionAllowed CipherMail user.sMIMEEnabled CipherMail user.pgp.enabled pdf_mandatory mandatory true false false pdf_by_subject allow true false false smime_mandatory mandatory false true false smime_by_subject allow false true false pgp_mandatory mandatory false false true pgp_by_subject allow false false true "By Subject" requires Encryption Settings > Trigger Encryption by Subject = Enabled plus the configured keyword (default [encrypt]) in the message subject. See Encryption Settings — Subject Trigger for the decision tree. PDF mode: three sub-policies PDF encryption is the lowest-friction protocol (recipient needs only a PDF reader and a password — no certs, no keys, no portal account required up front), so it ships with three independent password-distribution sub-modes: pdf_mode How the password reaches the recipient When to use random CipherMail auto-generates a one-time password per message and pushes it through the Secure Email Portal ( https:///web/portal); recipient self-registers on first use Default. Best for ad-hoc / first-time external recipients static Admin sets a fixed password once (minimum 12 chars); recipient must already know it via out-of-band channel Long-term partners who have agreed on a shared secret backtosender CipherMail generates a per-message password and emails it back to the original internal sender for them to relay to the recipient Compliance scenarios where the sender must explicitly hand the password to the recipient (auditable trail) For backtosender, two extra fields are configurable per recipient: Field Range Purpose Password Age (minutes) 15-240 How long the random password is valid Password Length 16-bit / 20-bit Bit-strength of the generated random password Bulk vs single create The Create External Recipient page ( view_create_ext_recipient.cfm) exposes a Single / Bulk toggle: Mode Protocol options Use case Single PDF, S/MIME, PGP (all three modes available) One-off precise configuration including S/MIME / PGP recipients that need a cert/key uploaded afterward Bulk PDF only (Mandatory or By Subject) Mass-onboard a list of external addresses, one per line; the UI auto-hides S/MIME and PGP because those protocols need per-recipient cert/key material that has no bulk equivalent The bulk path validates and skips per-row (invalid format / internal domain / already-exists rows are reported but do not abort the batch); session variables bulk_created, bulk_skipped, bulk_failed feed a partial-success alert on return. Both paths refuse internal domains. The check is a COUNT(*) FROM domains WHERE domain = — if Hermes is the authoritative MX for that domain, the recipient is a local mailbox or relay recipient, not an external recipient, and per-mailbox encryption policy belongs on Email Server > Mailboxes instead. Auto-Discovered recipients When CipherMail processes mail to an address it has never seen, it materializes a cm_users row with the global defaults. These recipients show up here with Source = Auto and no external_recipients row backing them. They: Use the global Subject Trigger policy (from Encryption Settings) Have no per-recipient password mode (PDF random is the CipherMail default) Display only the cert / keyring counts CipherMail actually holds Cannot be edited from this page (no Admin badge, no action buttons for cert / PGP / password reset) — managing them means either promoting them to Admin-Configured by creating an explicit row, or dropping into CipherMail's own admin UI at /ciphermail/ The Source dropdown defaults to Admin-Configured on page load — operators most often want to see what they explicitly configured, not the long tail of mail CipherMail has touched. Per-row actions The action column varies by what the recipient is configured for: Action Icon Visible when What it does S/MIME Certificates fa-certificate (green) Admin row, smime = 1 Links to view_ext_smime_certificates.cfm?email=... for cert add / delete / send PGP Keyrings fa-key (blue) Admin row, pgp = 1 Links to view_ext_pgp_keyrings.cfm?email=... for keyring add / delete / publish Reset PDF Password fa-file-pdf (yellow) Admin row, pdf = 1 AND pdf_mode = static Opens modal; auto-generates a 16-char mixed-case-alphanumeric password client-side via generatePassword(16); submits to inc/reset_pdf_password.cfm Reset Portal Password fa-lock (grey) Admin row, pdf = 1 AND pdf_mode = random Opens modal; same 16-char generator; submits to inc/reset_portal_password.cfm (two-step: encode via --encode-password, then set user.portal.password) Delete Recipient fa-trash-alt (red) Every row Confirms, then submits to delete_recipient handler The Cert Expiry column derives from a batch join of cm_certificates_email + cm_certificates, picking the earliest cm_not_after across all certs for that recipient. Color coding: red bold (already expired), yellow bold (within 30 days), grey muted (more than 30 days). Delete cascade Deleting an external recipient is a multi-table operation handled by inc/delete_ext_recipient.cfm: +---------------------------+ | For each row in | | recipient_certificates | | where user_id = recipient | +---------------------------+ | v +---------------------------+ +---------------------------+ | inc/delete_smime_ |----->| Removes from | | certificate.cfm | | cm_certificates_email, | | | | CipherMail user store, | | | | on-disk PFX | +---------------------------+ +---------------------------+ | v +---------------------------+ | For each master keyring | | in recipient_keystores | +---------------------------+ | v +---------------------------+ | inc/delete_pgp_keyring. | | cfm | +---------------------------+ | v +---------------------------+ | DELETE FROM | | external_recipients | | WHERE id = ... | +---------------------------+ | v +----------------------------------------+ | docker exec hermes_ciphermail CLITool | | --delete-user | | (cascades all cm_properties, cm_users) | +----------------------------------------+ On success the page surfaces a callout reminding the operator that any Sender Checks Bypass mapping tied to this recipient must be re-created — that relationship is not auto-cascaded. Password reset specifics PDF static password reset ( inc/reset_pdf_password.cfm): Writes a one-liner CLITool --set-property user.password --value --encrypt --email to /opt/hermes/tmp/_reset_pdf_password.sh. chmod +x, executes (240s timeout), deletes. AES-encrypts the new password with /opt/hermes/keys/hermes.key and UPDATEs external_recipients.pdf_password so the admin re-display path still works. Portal password reset ( inc/reset_portal_password.cfm) is two-step because CipherMail's portal password is stored as an encoded value, not the raw string: Step 1 — encode: runs CLITool --encode-password , captures stdout to /opt/hermes/tmp/_portal_password, reads that file back into CFML, deletes the temp file. Step 2 — set: runs CLITool --set-property user.portal.password --encrypt --email --value to push the encoded value into CipherMail. Both modals auto-generate a 16-character mixed-case-alphanumeric password client-side and pre-populate the hidden confirm field; the operator can regenerate or type-in their own. Min length 12 is enforced server-side; the regenerator produces 16. The modal text explicitly notes that unencrypted voice calls and texts are NOT considered secure for relaying the password to the recipient — operators are expected to use Signal, an in-person exchange, or a separately-encrypted channel. CipherMail integration: every action is docker exec Every CipherMail-side mutation on this page uses the same pattern documented across the Hermes admin: +----------------------+ +----------------------+ +-------------------+ | CFML builds shell |----->| Write to |----->| chmod +x | | string with N | | /opt/hermes/tmp/ | | | | docker exec CLITool | | _.sh | | | | lines | | | | | +----------------------+ +----------------------+ +-------------------+ | v +--------------------+ | cfexecute (240s), | | then delete the | | temp file | +--------------------+ | v +-------------------------------+ | docker exec hermes_ciphermail | | /usr/bin/java -cp '/.../lib/*'| | mitm.application.djigzo.tools | | .CLITool | +-------------------------------+ The Hermes app container ( hermes_commandbox) holds no JVM and no CipherMail libraries; everything reaches into hermes_ciphermail over the docker socket via CLITool. The temp-script pattern (write + chmod + execute + delete) survives the Lucee cfexecute quirks around stderr and quoting that would otherwise make a direct inline invocation unreliable. What's NOT on this page Expectation Where it actually lives Per-recipient cipher / algorithm selection (AES-128 vs AES-256, RSA / EC) CipherMail Advanced Settings ( /ciphermail/); per-recipient overrides live in cm_properties directly Auto-lookup of recipient PGP keys from a keyserver at send time Not implemented; see PGP Key Servers — that page is publish-only. Keys must be uploaded manually on the PGP Keyrings sub-page Auto-lookup of recipient S/MIME certs via LDAP / public directory Not implemented; certs must be uploaded manually on the S/MIME Certificates sub-page, OR minted from an Internal CA row and sent to the recipient Per-recipient subject-trigger keyword override Not implemented; the keyword is global (one row in encryption_settings) Recipient-side enrollment / self-service for their own keys The Secure Email Portal handles recipient password registration for PDF-random mode; there is no self-service cert / PGP upload UI Bulk import from CSV with mixed protocols Bulk path is PDF-only by design (S/MIME / PGP need per-recipient material that doesn't bulk-import cleanly) Sender-side "force encrypt for this thread" UI Senders use the subject trigger; there is no per-mailbox sender UI Container and database touch-points Component Container / path Role Page config/hermes/var/www/html/admin/2/view_ext_rec_encryption.cfm ( hermes_commandbox) List, filter, password resets, delete Create page view_create_ext_recipient.cfm + sub-pages for cert / keyring management Single + bulk insertion Action includes inc/create_ext_recipient.cfm, inc/delete_ext_recipient.cfm, inc/reset_pdf_password.cfm, inc/reset_portal_password.cfm One-liner CLITool dispatchers via temp script Admin metadata external_recipients in hermes DB ( hermes_db_server) Per-recipient policy choices + AES-encrypted static PDF password copy CipherMail user store cm_users, cm_properties in djigzo DB Authoritative per-recipient state CipherMail cert / key index cm_certificates_email, cm_certificates, cm_keyring_email in djigzo DB Joined batch into smimeLookup / pgpLookup for column rendering Encryption engine hermes_ciphermail (Java; CipherMail Community 5.x branded djigzo) Actual S/MIME / PGP / PDF encryption + portal back-channel AES key /opt/hermes/keys/hermes.key ( hermes_commandbox bind mount) Encrypts pdf_password for re-display Secure Email Portal https:///web/portal/ (served by hermes_ciphermail) Recipient-facing landing page for PDF random + portal account flows Related Encryption Settings — global Subject Trigger, PDF reply sender, three shared secrets; the policy mechanism this page applies per recipient Internal CA — where the private CAs that can mint S/MIME certs for these recipients live (operator-issued S/MIME chain delivered out-of-band) PGP Key Servers — the publish list for the Publish action on the PGP Keyrings sub-page (note: publish-only, not lookup) System Certificates — distinct TLS cert store; not related to message-content S/MIME Disclaimers — body-mod ordering vs the CipherMail encryption pass (disclaimer is appended before encryption wraps the message) Organizational Signatures — same milter ordering applies to signature injection ARC Settings — same milter-ordering pattern applied to inbound chain sealing Advanced Settings (sidebar link to /ciphermail/) — CipherMail's own admin UI for everything not surfaced here (per-recipient cipher tuning, custom DLP, direct cm_properties editing) Internal CA Internal CA Admin path: Encryption > Internal CA ( view_internal_ca.cfm, inc/download_ca_file.cfm, inc/create_certificate.cfm, inc/send_smime_certificate.cfm, inc/delete_smime_certificate.cfm). This is the gateway's built-in Certificate Authority for issuing S/MIME certificates to local users and relay recipients. Each CA row here corresponds to a private CA cert + key on disk under /opt/hermes/CA//root_ca/ and a matching roots-store entry in the CipherMail ( djigzo) trust list. Per-recipient S/MIME certs minted from a CA on this page are stored in recipient_certificates and listed on Email Server > Relay Recipients (and Email Server > Mailboxes when S/MIME is enabled on a mailbox). This page is distinct from System Certificates: System Certificates Internal CA What it stores Operator-uploaded TLS leaf certs (nginx, Postfix, Dovecot) Private CAs that mint S/MIME end-user certs Trust direction Hermes presents these to clients Hermes issues certs that recipients present Backing store system_certificates table + /opt/hermes/ssl/ or /etc/letsencrypt/ ca_settings table + /opt/hermes/CA// + CipherMail cm_certificates ( roots store) + cm_ctl trust list Typical lifetime 90 d (ACME) or 1-3 yr (commercial) 5 yr root (recommended), extendable in place Lifecycle owner nginx / Postfix / Dovecot via TLS handshake CipherMail S/MIME signer / encryptor for outbound; per-recipient cert issuance for inbound encrypt The two ingest paths The page exposes two collapsing cards (Create Internal CA, Import External CA) plus a DataTable of existing CAs. Both paths land a row in ca_settings and register the cert in CipherMail's cm_certificates table as a root ( cm_store_name = 'roots') plus an entry in cm_ctl (Certificate Trust List) flagged whitelisted. 1. Create Internal CA Operator fills the DN fields, picks a key size (2048 / 4096) and a validity (1-5 years; 5 years recommended). Hermes: Validates inputs (regex-restricted character set per field, 2-char ISO country code, uniqueness against ca_settings.ca_commonname). Materializes a per-CA on-disk skeleton at /opt/hermes/CA//root_ca/ with the standard OpenSSL layout ( certs/, crl/, newcerts/, private/, requests/, PFX/, serial, index.txt, crlnumber). Materializes an openssl.cnf from /opt/hermes/templates/rootca_openssl.cnf with the directory placeholder substituted. Snapshots cm_certificates into cm_certificates_tmp, runs the OpenSSL root-CA generation script as a one-shot temp script ( /opt/hermes/scripts/_create_ca.sh), then diffs to find the new cert. Marks the new CipherMail row cm_store_name = 'roots', inserts a cm_ctl row with status whitelisted and allowExpired = false, and back-fills ca_settings.ca_djigzo_id + ca_djigzo_subject. 2. Import External CA For organizations that already have a private CA (commercial issuer, internal PKI, prior Hermes install). Operator uploads the CA cert (PEM) and the CA private key (PEM, unencrypted). Hermes: Lands the files at /opt/hermes/CA//root_ca/certs/cacert.pem and .../private/cakey.pem. Runs an OpenSSL validation script that checks: Cert parses as X.509 ( openssl x509 -modulus) Key parses as RSA ( openssl rsa -modulus) Cert and key moduli match (private key matches public key) Cert has CA:TRUE basic constraint On any check failure the upload directory is removed and the operator gets a specific error alert (m=48 / 49 / 50 / 51). Generates openssl.cnf from the template + cachain.pem = copy of the cert (needed for later PFX export of per-user certs). Pipes the cert into CipherMail via docker exec -i hermes_ciphermail /usr/bin/java -cp '/usr/share/djigzo/lib/*' mitm.application.djigzo.tools.CertStore --import-certificates and back-fills the ca_djigzo_id exactly as the Create path does. The Import path is the only way to migrate a CA that already has issued certs in the wild — re-creating a CA from scratch with the same DN does NOT reproduce the original key material, so previously issued certs would not chain to it. Default CA flag ( default2) Exactly one row in ca_settings has default2 = '1'; all others have '2'. The default CA is the one Hermes mints from when an admin clicks Create Certificate for a recipient on Email Server > Relay Recipients (or the mailbox equivalent) without explicitly choosing a CA. The page enforces single-default by: Setting all rows to '2' before flipping the new row to '1' Forcing the first-ever CA to default regardless of the checkbox Disabling the Make Default checkbox on the Create card when no default exists (forced default is implicit) The DataTable Default column renders a green YES badge for the default row and a one-click set default button for the others. CA lifecycle workflow +----------------+ +----------------+ +----------------+ | Admin creates |----->| CipherMail |----->| Recipient | | Internal CA | | trusts root | | cert minted | +----------------+ +----------------+ +----------------+ | v +----------------+ | Outbound mail | | signed by | | recipient cert | +----------------+ Stage Where the data lives Trigger CA root created ca_settings + /opt/hermes/CA// + cm_certificates ( roots) + cm_ctl ( whitelisted) Create / Import buttons on this page Per-recipient cert minted recipient_certificates (or external_recipient_certificates) + CipherMail user store Create Certificate button on a recipient page; uses default2 = '1' CA unless overridden Cert self-introduction Bundled into the first signed outbound message the recipient sends Automatic on first S/MIME-signed send Cert revocation delete_smime_certificate.cfm removes the row + CipherMail entry; CRL is maintained by CipherMail's own scheduled job Delete button on the recipient cert row CA renewal Re-sign the existing cert + key with openssl x509 -days and re-import into CipherMail; ca_settings.expires updated Renew button (sync icon) on the CA row CA deletion Refused if any recipient_certificates.ca_id row references it; otherwise removes DB row + CipherMail cm_certificates / cm_ctl + on-disk tree Delete button (only enabled when zero issued certs) CA Renewal: 5-year extension in place Clicking the Renew (sync) icon does NOT generate a new key pair — it re-signs the existing CA cert against its own key with an extended notAfter. The math: new_expires = current_expires + 5 years days_param = max(1825, days_from_now_to_new_expires) openssl x509 -in cacert.pem -days -out cacert.pem.new -signkey cakey.pem mv cacert.pem.new cacert.pem cp cacert.pem cachain.pem cat cacert.pem | docker exec -i hermes_ciphermail \ /usr/bin/java -cp '/usr/share/djigzo/lib/*' \ mitm.application.djigzo.tools.CertStore --import-certificates Because the key stays the same, every previously issued recipient cert still chains to a valid CA cert — there is no need to re-mint recipient certs after a CA renewal. This is the operator-friendly path: recipients on the outside who already trust the CA root continue to trust it transparently. The old CipherMail row is deleted and the renewed cert re-imported so the cm_certificates/ cm_ctl rows reflect the new validity window (otherwise CipherMail would keep enforcing the old expiry). Trust distribution to external recipients A Hermes-issued S/MIME cert is signed by a private CA that no operating system or mail client trusts by default. External recipients see Hermes-signed mail as "signed by an unknown CA" until they explicitly install the Internal CA root in their trust store. Two practical paths: Path Effort Reach Operator distributes the CA root out-of-band (download from this page, email or publish on a portal, recipient installs in Outlook / macOS Keychain / iOS Profile / Thunderbird) Manual per recipient Small fixed counterparty set (B2B, partner orgs) Issue recipient certs from a publicly-rooted CA (commercial S/MIME issuer signs your CA, or you buy per-user S/MIME certs from a public issuer) One-time cross-sign or per-user cost Every MUA on the planet trusts the chain For most Hermes deployments the Internal CA is the right answer (per-user public S/MIME costs $20-$80/yr/user); for high-volume B2C senders the publicly-rooted route is sometimes worth the cost. Hermes does not generate a CRL distribution URL on this page; CipherMail maintains the revocation list internally and applies it when verifying inbound S/MIME from local recipients. External recipients have no automatic way to consume the CRL — revocation is effectively local-only unless the operator publishes the CRL manually. CA file downloads (gated) Each row's action column exposes a Download Certificate and Download Private Key button. These are disabled by default — downloading a CA private key off a web console is a high-risk operation. To enable, set ALLOW_CA_DOWNLOAD=yes in /opt/hermes/config/security.conf on the host filesystem. This is the same toggle pattern used by System Certificates ( ALLOW_CERT_DOWNLOAD) — read on every page load, surfaced as a disabled-button + tooltip when off. When enabled, downloads stream via a hidden iframe (