mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
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.
159 lines
8.6 KiB
TypeScript
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;
|
|
}
|