Files
Linus Rath cf0eb03c4a feat: show the sender's BIMI logo as the sender icon when the message passes DMARC
Stalwart does no BIMI, so sender icons were only ever favicons, which
anyone controls for any domain. The webmail server now does the
receiver side itself at /api/bimi, and avatars ask it only for
messages whose own Authentication-Results say dmarc=pass for the exact
From domain.

- `lib/bimi/`: DMARC policy check (quarantine or reject, pct=100, the
  parent domain's sp and p for subdomains), the assertion record with
  fallback to the parent domain, mark certificate validation (chain to
  pinned DigiCert, GlobalSign and SSL.com VMC roots, BIMI key purpose,
  validity, SAN, SCTs present, CRL revocation, logo from the logotype
  extension), SVG Tiny PS checks and rendering to PNG, caching
- logos backed only by the DNS record show by default; the admin
  setting "Require a mark certificate" (`BIMI_REQUIRE_CERTIFICATE`)
  limits them to VMC and CMC backed ones
- a check mark beside the sender's name for certificate-backed logos
- avatars try contact photo, BIMI logo, plugin avatar, favicon; the
  sender warning shield still wins, and logos follow the sender icons
  setting and the no-third-party-traffic mode
- the two-label public suffix list moved to `lib/root-domain.ts`

SCT signatures are not verified and only the default selector is used.
2026-10-30 00:12:24 +01:00

159 lines
8.6 KiB
TypeScript

import { X509Certificate } from 'node:crypto';
/**
* Root CAs of the Mark Verifying Authorities whose VMCs and CMCs we accept.
* Each was downloaded from the CA itself (the URL above it) and pinned here;
* the root a sender's PEM carries is never trusted on its own. SSL.com's
* roots also anchor the mark certificates Sectigo issues.
*
* To add a root, take it from the CA's repository, not from a sender's PEM,
* and list its SHA-256 fingerprint here as well.
*/
const ROOT_PEMS: readonly { name: string; sha256: string; pem: string }[] = [
// From cacerts.digicert.com/DigiCertVerifiedMarkRootCA.crt
{
name: 'DigiCert Verified Mark Root CA',
sha256: '50:43:86:C9:EE:89:32:FE:CC:95:FA:DE:42:7F:69:C3:E2:53:4B:73:10:48:9E:30:0F:EE:44:8E:33:C4:6B:42',
pem: `-----BEGIN CERTIFICATE-----
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
0wberd/tg3N0dJsSSznZjwYB
-----END CERTIFICATE-----`,
},
// From secure.globalsign.com/cacert/gsverifiedmarkrootr42.crt
{
name: 'GlobalSign Verified Mark Root R42',
sha256: 'CD:12:2C:B8:77:C6:92:8B:90:17:B0:F0:B8:0D:BD:50:81:96:30:0B:BD:03:CD:73:56:C3:BE:EF:52:4E:7E:0B',
pem: `-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQf+UwA4GYp199F8APJCyr8zANBgkqhkiG9w0BAQwFADBU
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEqMCgGA1UE
AxMhR2xvYmFsU2lnbiBWZXJpZmllZCBNYXJrIFJvb3QgUjQyMB4XDTIzMTExNTAz
NDA1MloXDTQyMTExNTAwMDAwMFowVDELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEds
b2JhbFNpZ24gbnYtc2ExKjAoBgNVBAMTIUdsb2JhbFNpZ24gVmVyaWZpZWQgTWFy
ayBSb290IFI0MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANzqTcvu
bRbqX0CzP7sZbHfd1usqWOPrHzoLvbZTnJqt9WgBwQwcGEFl8rmIcOss7XmewAqj
oCx7PQp1FJ+/o/yGezMbnPCglAVwWptUOMIa7UJ/8qrV50/qQSRN8s8MAcnhj3Ab
Ja/mVjlMHzjYCo0O+jc24uHlEHcIo3vJPsP2Sy9HwcMH4wB2rQ4xDN7kAn5NmqXI
xAvSnJ2KGrZsoNx8MGAfX5dl0ACOpaaqbonBGtawvRGAE6f747E+pUamjGjisQ8y
ZC0X5Ht1s53sliy9AHDpOUWVcCLOoQCP/Y8yDjkjE1VWiL1Y7LlCnYfAVfY87DRD
81toIOLjFNACxeG02Qvgzg0JMKh81seiZfSLbq8DgepDABUTs8WYpN/u52/kdM+/
UeaQEn+q2HDDI8OMmBqwVo98k+dCQ0QgKLkHaLxpm5EjacSkNZFeqp1ttDZsGKQt
9REPJxpY6OO9KSYuFNFpnmLfaDNA/VngLw+Z4hYH59RnE2p3s95CfUa+CT4BUvDy
Wefc9RNiJVppbQNiZ4DKKpixVfXptgukbnh5l7vyDVonKGdgNEZeN5YjopM5V2+J
eIrsC3u0OmDV41cSFfe/1dmoUhy5EfTgWXqLOOgoUxqy62Yl7DDwoyXerEzDzZAq
nSBE/HAra3/d5pdj8/5WXoybjbXYM/bsKALLAgMBAAGjQjBAMA4GA1UdDwEB/wQE
AwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ9s+P1IK6506ybFLxwJe4P
Cn6VyTANBgkqhkiG9w0BAQwFAAOCAgEA24nHkncSyqeuk1lkpRq/MLVHdVONJnZ6
t8OZEu3nr6DHAuImAonIqO8HcBcgwydDIAeD7GOafOZ47mRkYOFIa+tkJ21pfmJv
KUt3ASW1uLAHCJNYHVLXs7txnq1mRKuwyuaeb6JdTazzjXgNDLZQgvOfM7mkDf6f
S2Z+dbKOZBYwo6zspqoQc1s6k0Xg9h7oEiJCC+3FUfJQSYyi2bynZSEn2c67OSFT
fqXVr9nhlaawLGEKUaFlYeQspUO5MUFYmGWqnf38M8ZM0rPx5R00jZ/E5sKcSqFH
CaVghDPiVv9Jzs8gNllwDbi7rXwMstaKATJJXiWgSdsxrfQr7+FRyXQJmRVXKxZ6
6uNe1ZgAzgNbjCvGmwF1StBtr1DrdOSk7sOMBMuJftB2g9/K67VUfz+jkylIbv5y
bvnCqBoAvq2DSQ76O5WyWiTBkPYp2Fh/WMHkXVIQpT6Q7FMTiGxtAxv69sq1CeJ9
i9g/iAJBdToQwJwH80Ah3hSPg1DQ8LTIjTtOxxkJ6Aeh/XU1/w9lW91XGLgXye1U
fjUIWKsoe1rE2RK+RSP6BZWcRNhJOyqi40gdTRLJkbc0Itq/62NFV4mIWcWKafhb
4ZOU1rvk1woeteJJZHd7cLakW6ONJAEtWGe43ZVx/lqFXQH4kOMPuR/7h2G1Aie6
Fk6hsIjUv20=
-----END CERTIFICATE-----`,
},
// From repo-certs.ssl.com/repo/certs/SSL.com-VMC-Root-2024-RSA.pem
{
name: 'SSL.com VMC RSA Root CA 2024',
sha256: '8F:9D:1B:76:98:88:67:82:A5:99:B4:85:10:65:1C:66:A1:AA:0C:5C:A3:19:20:97:BD:C6:85:34:15:4B:D3:0D',
pem: `-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQcJRvrr6zzODYb7h2d4BhyzANBgkqhkiG9w0BAQsFADBO
MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD
DBxTU0wuY29tIFZNQyBSU0EgUm9vdCBDQSAyMDI0MB4XDTI0MDIxOTE2MjU1MVoX
DTQ4MDIxMzE2MjU1MFowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw
b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBWTUMgUlNBIFJvb3QgQ0EgMjAyNDCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIyEglUdOfMiwwS13+Q9dho5
swa3HbdPZByWNPwgD1tDdPYyCdox2F0bJxfrBnUFKCf46SZV7DESkn/YTKj7tMSk
Vr4c9GzNbr7WWSMprVEsgDrwDw/RM9YcwsFNlx1PFt+gDQhitA/ezIRO0uextBp4
0/HP7HQ7ZeOKVB0W71WoBty6TBCN+l5LVQGGDSvRGmQndf9zsmITQpddp1xAbUMA
uxFnkS22vYCHdEWzSVuEWLakVh53qcYF7krDZY82kIurOC1wEwGNkdsfQ0HLpf8Y
VhHMOm/JoCOXcA79nvjehykZb+7c1zgPgUjRd4d/UbwoDBy0Y4GrGw4Q+c0bscz/
xlrxf+SYIozASUEFjNcfvOkPCLlTHj7V8yhQRm58Q36/GdnahwLgdRwhk4WP5p7W
tkxJrHIvdsQJgIH9OVL089MHfOHQHuAcxV54+/YKr/AT6m3pgr+Zs067rQYMMc/k
TCGial39KMnkHydy8AduW+17U1auIpqt7XOtroC46IMbL2zVQDY1lyvUfvwc+5Kt
fjYVpnsle7g+fkEupWvbQHCWZR4iE9jFNR6ETF2damAMx+yuFP+dfyc4CAZUoGwz
0+ZhUIYuTeEMCPSuZAvHy0ugTPEuSc5LKnJ5JK+bOeqtbRkQVmFC74fkAqhkCfzt
vkckHbTCI5ukoeKueCRJAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
BBgwFoAUwTWi9FnZB3aX/croa3fWJq56WvswHQYDVR0OBBYEFME1ovRZ2Qd2l/3K
6Gt31iauelr7MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIet3
+K2+sef6SZBE+5cFsnKYtagi50I1iECJQziusyw1wK9Keman4TZshwio4Z/nvaq2
ed3s3YYFL6BGksJTJPImrEaQI3aqHa16AEzw6F2k3gz78wbcpjygGJMekVrhyF5l
E+ErRcY9JGo1IGscBYlZcXkHJThNO9gEyoH+os73KIsY4120CBe3jbfOhRpDZioF
wUxepdzEzx8ZRXGsz58m+0NS0cBcvkqpIpnI2TFDT/lK0s2ys65h2YLhDsUnhtJ2
Kd8boP+ImazgpfpjgVEeCCpoPzWmmlMzxr083f0Mya4uSPidk/FkVYo3l+NAUcfH
WcfH6ca7Xrm5DXsOaP0cDWaYphbEKVVi164V03fNClW3p7xzLCDUfzdAaQs1JY1G
h0GgH4dNdp+BgGjjUPZ86IZhCuOq0rm3czfxb25l6WuAf7A0gq4oVgMNbbIepap8
YRbbU3nGl4A+s6qjtIhy6QGVWYqwQVElP7ba1sbhqLu5Uu/QyI/B5uEBO3oTvNgc
pld8jFKTqIO9yFgzvIO6XhfwW5VvLHozlzb7fM8hPKnrdd5HT3XnFTkKl/y4zLrw
OnvNhh5QMQe+HEnpScufX/cFPh568bVicV061IvLoGr84rlgxO+RjWuILOiXCVi0
7JsQLmpgD+ebpTit7HrVpCJa1SZXeHlzkc/yI74=
-----END CERTIFICATE-----`,
},
// From repo-certs.ssl.com/repo/certs/SSL.com-VMC-Root-2024-ECC.pem
{
name: 'SSL.com VMC ECC Root CA 2024',
sha256: '1B:82:E7:F4:91:0B:51:E3:E8:02:A4:93:AC:DC:17:FF:58:EA:C8:B9:EB:7C:09:B5:2A:C6:CD:2E:FB:83:59:8C',
pem: `-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQEUfBai0/SvdnXWXlwaytjjAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT
U0wuY29tIFZNQyBFQ0MgUm9vdCBDQSAyMDI0MB4XDTI0MDIxOTE2MjUzMFoXDTQ4
MDIxMzE2MjUyOVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh
dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBWTUMgRUNDIFJvb3QgQ0EgMjAyNDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABAlYNKIok2H2MoERpO66/tfLns/aRbbVN8HhNGbe
znXOJhQozKiHoCOCN7bCCuMdpAXVosUptXs+8Sksd8Hluh91J1W1QQVi9KyFSwgM
VTAynuA64X9MbrQNwkZaCQXPtKNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
GDAWgBQl8MYq5N4ipgAt8VK4J42gUeKgpTAdBgNVHQ4EFgQUJfDGKuTeIqYALfFS
uCeNoFHioKUwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMHkYwAy0
rQ/BXQCanvUeTylOOCbaIO+rbFxOY8KF+AHmv0MJI7t++BnWUE1XwYABXAIxAKbi
1Mpz6UpoC5yWsed9Z1rhT+pMwzPwNrdsRMLAesSq2Jbv0UGQ/1uJcM/6OtDFgQ==
-----END CERTIFICATE-----`,
},
];
let parsed: X509Certificate[] | null = null;
export function bimiRoots(): X509Certificate[] {
if (!parsed) {
parsed = ROOT_PEMS.map(({ name, sha256, pem }) => {
const cert = new X509Certificate(pem);
if (cert.fingerprint256 !== sha256) throw new Error(`BIMI root ${name} does not match its pinned fingerprint`);
return cert;
});
}
return parsed;
}