Finding 5 — the MIME parser runs on attacker-controlled input: the inner content recovered after decrypt/verify is whatever the sender put there. Upstream had no depth limit on nested multiparts and no size cap anywhere. Verified against the unpatched upstream parser with the same input: UPSTREAM CRASHED: RangeError - Maximum call stack size exceeded UPSTREAM: 65MB accepted (no size cap) So this was a live decrypt-time DoS reachable by anyone who can send mail. Caps added: depth 20, parts 500, bytes 64 MB — generous enough that no legitimate message comes close (real mail nests 3-4 levels). Past a limit a subtree degrades to a leaf rather than throwing, so one pathological branch doesn't discard the legitimate parts above it. Oversize input is refused outright rather than truncated: half a MIME tree parses into misleading nonsense, and showing part of a message is worse than saying no. Both bodyStructure walkers in smime-detect.js are capped too — those run on server-supplied structure BEFORE any decrypt/verify gate. Finding 4 — hardened, not eliminated, per the agreed scope. Unlocked CryptoKeys still live in durable IndexedDB rather than memory; moving them would mean refactoring how the plugin shares state across iframes and risking the unlock->decrypt path just verified. What changed instead: - Removed the lockOnLogout opt-out from the logout/account-switch wipes. A non-extractable key cannot be exported but can still be USED, so a handle outliving the session lets anyone with the browser profile decrypt mail without knowing the passphrase. That is not a preference to toggle off. - Added a best-effort wipe on pagehide and beforeunload to narrow the window in which a usable handle exists on disk. Best-effort by nature: an IndexedDB write may not complete during teardown and neither event fires on a crash — which is precisely why the boot wipe in activate() remains the load-bearing control. - Deliberately NOT wiping on visibilitychange: tabbing away would drop the unlock and force a passphrase re-entry every time, which trains users into turning S/MIME off entirely. - Dropped the now-dead lockOnLogout setting from the manifest. A toggle that silently does nothing is worse than no toggle. Tests: 49 unit assertions + 28 round trip. The round trip now feeds genuinely hostile MIME through the real parser (5000-level nesting, 5000 siblings, 65 MB) and still confirms a normal multipart/alternative parses correctly. Full crypto round trip unchanged and passing, so neither fix broke S/MIME. Findings 6, 7, 8 and 9 remain open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.8 KiB
JSON
67 lines
2.8 KiB
JSON
{
|
|
"id": "smime",
|
|
"name": "S/MIME",
|
|
"version": "1.0.2",
|
|
"author": "Bulwark Mail Community",
|
|
"description": "End-to-end S/MIME for webmail: sign and encrypt outgoing messages, and automatically verify signatures and decrypt incoming CMS (PKCS#7) mail. Private keys are imported from a PKCS#12 (.p12/.pfx) file, encrypted at rest with a passphrase, and unlocked into non-extractable WebCrypto keys that never leave your browser. Runs in the privileged (same-origin) plugin tier so all cryptography happens locally with bundled pkijs/asn1js.",
|
|
"type": "ui-extension",
|
|
"tier": "privileged",
|
|
"permissions": [
|
|
"crypto:full",
|
|
"email:blob-read",
|
|
"email:raw-send",
|
|
"email:render-takeover",
|
|
"email:read",
|
|
"email:send",
|
|
"smime:read",
|
|
"auth:observe",
|
|
"ui:composer-toolbar",
|
|
"ui:email-banner",
|
|
"ui:settings-section",
|
|
"app:lifecycle"
|
|
],
|
|
"entrypoint": "index.js",
|
|
"minAppVersion": "1.7.6",
|
|
"icon": "media/icon.svg",
|
|
"banner": "media/banner.svg",
|
|
"settingsSchema": {
|
|
"encryptionStrength": {
|
|
"type": "select",
|
|
"label": "Content encryption algorithm",
|
|
"description": "Symmetric cipher used to encrypt the message body. AES-256-GCM is recommended; AES-128-GCM is slightly smaller and still strong.",
|
|
"default": "aes-256",
|
|
"options": ["aes-256", "aes-128"]
|
|
},
|
|
"autoImportSignerCerts": {
|
|
"type": "boolean",
|
|
"label": "Auto-save signer certificates",
|
|
"description": "When a validly signed message is opened, remember the signer's certificate so you can later send them encrypted mail without importing it manually.",
|
|
"default": true
|
|
},
|
|
"renderUnauthenticatedHtml": {
|
|
"type": "boolean",
|
|
"label": "Render HTML in legacy-encrypted mail",
|
|
"description": "Messages encrypted with AES-CBC carry no integrity protection, so their contents can be tampered with in transit. By default such mail is shown as plain text, which prevents a known attack that can leak the decrypted message. Turn this on only if you need HTML rendering for older encrypted mail and accept that risk. Mail encrypted with AES-GCM is unaffected and always renders fully.",
|
|
"default": false
|
|
},
|
|
"warnOnSelfSigned": {
|
|
"type": "boolean",
|
|
"label": "Warn on self-signed signer",
|
|
"description": "Show a caution banner when an incoming signature validates against a self-signed certificate (not chained to a trusted CA).",
|
|
"default": true
|
|
}
|
|
},
|
|
"locales": {
|
|
"en": {
|
|
"banner.signed_valid": "Signature valid",
|
|
"banner.signed_invalid": "Signature invalid",
|
|
"banner.encrypted": "Encrypted message",
|
|
"banner.decrypted": "Decrypted",
|
|
"banner.locked": "Encrypted — unlock your key to read",
|
|
"toolbar.sign": "Sign",
|
|
"toolbar.encrypt": "Encrypt",
|
|
"settings.title": "S/MIME keys & certificates"
|
|
}
|
|
}
|
|
}
|