A Verified Mark Certificate cannot be made here: an authority issues it after checking the organisation. What the service can do is hold the file the authority returns, serve it where the DNS record points, and refuse one that would make receivers ignore BIMI without a word. - migration 0015: domain_bimi keeps the PEM chain next to the logo - bimi.InspectCertificate reads a chain and reports every reason it would not work: private key blocks refused outright, the brand-indicator usage, the validity period, the domain (exact, wildcard or organisational), and that the logo embedded in the certificate (gzipped data URI in the logotype extension) is byte for byte the logo being published. Tested against two real public VMCs (eBay via DigiCert, CNN via Valimail) plus mutations - GET /.well-known/bimi/vmc.pem (public, conditional, cacheable for an hour), and PUT/DELETE /api/v1/admin/bimi/certificate; the GET of the logo now reports the certificate as it stands against the logo being served, and replacing a logo under a held certificate says it no longer matches - GET /api/v1/admin/bimi/check looks at what a receiver would: the logo, DMARC enforcement, the BIMI record, the public logo URL, the certificate and its public URL. Its HTTP client is HTTPS only and refuses non-public addresses after DNS, since the URLs come from DNS and an operator field - the DNS record points a= at the held certificate when there is one Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
177 lines
6.4 KiB
Go
177 lines
6.4 KiB
Go
package bimi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type fakeDNS map[string][]string
|
|
|
|
func (f fakeDNS) LookupTXT(_ context.Context, name string) ([]string, error) {
|
|
records, ok := f[name]
|
|
if !ok {
|
|
return nil, errors.New("no such host")
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func fakeFetch(files map[string]Response) Fetcher {
|
|
return func(_ context.Context, url string) (Response, error) {
|
|
resp, ok := files[url]
|
|
if !ok {
|
|
return Response{}, errors.New("connection refused")
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
|
|
func byID(checks []Check) map[string]Check {
|
|
out := map[string]Check{}
|
|
for _, c := range checks {
|
|
out[c.ID] = c
|
|
}
|
|
return out
|
|
}
|
|
|
|
const (
|
|
logoURL = "https://mail.ebay.com/.well-known/bimi/default.svg"
|
|
vmcURL = "https://mail.ebay.com/.well-known/bimi/vmc.pem"
|
|
)
|
|
|
|
// goodSVG is a logo the validator accepts.
|
|
const goodSVG = `<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny-ps" viewBox="0 0 100 100"><title>Example</title><rect width="100" height="100" fill="#fff"/></svg>`
|
|
|
|
func TestRunChecksAllGoodWithTheRealCertificate(t *testing.T) {
|
|
svg := read(t, "ebay.svg")
|
|
pemData := read(t, "ebay.pem")
|
|
in := Inputs{Domain: "ebay.com", LogoURL: logoURL, CertificateURL: vmcURL, SVG: svg, CertificatePEM: pemData, Now: fixtureNow}
|
|
dns := fakeDNS{
|
|
"_dmarc.ebay.com": {"v=DMARC1; p=reject; pct=100; rua=mailto:x@ebay.com"},
|
|
"default._bimi.ebay.com": {"v=BIMI1; l=" + logoURL + "; a=" + vmcURL + ";"},
|
|
}
|
|
fetch := fakeFetch(map[string]Response{
|
|
logoURL: {Status: 200, ContentType: "image/svg+xml", Body: svg},
|
|
vmcURL: {Status: 200, ContentType: "application/x-pem-file", Body: pemData},
|
|
})
|
|
|
|
got := byID(RunChecks(context.Background(), in, dns, fetch))
|
|
for _, id := range []string{"logo", "dmarc", "record", "logoUrl", "certificate", "certificateUrl"} {
|
|
if got[id].Status != StatusOK {
|
|
t.Errorf("%s = %s (%s)", id, got[id].Status, got[id].Detail)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunChecksDMARC(t *testing.T) {
|
|
cases := []struct {
|
|
record string
|
|
want Status
|
|
}{
|
|
{"v=DMARC1; p=none", StatusFail},
|
|
{"v=DMARC1; p=quarantine", StatusOK},
|
|
{"v=DMARC1; p=reject; pct=100", StatusOK},
|
|
{"v=DMARC1; p=reject; pct=50", StatusWarn},
|
|
{"v=spf1 -all", StatusFail},
|
|
}
|
|
for _, c := range cases {
|
|
got := checkDMARC(context.Background(), "example.test", fakeDNS{"_dmarc.example.test": {c.record}})
|
|
if got.Status != c.want {
|
|
t.Errorf("%q: %s (%s), want %s", c.record, got.Status, got.Detail, c.want)
|
|
}
|
|
}
|
|
if got := checkDMARC(context.Background(), "example.test", fakeDNS{}); got.Status != StatusFail {
|
|
t.Errorf("missing record: %s", got.Status)
|
|
}
|
|
}
|
|
|
|
func TestRunChecksRecordMismatchesAreSpelledOut(t *testing.T) {
|
|
in := Inputs{Domain: "example.test", LogoURL: "https://mail.example.test/.well-known/bimi/default.svg", CertificateURL: "https://mail.example.test/.well-known/bimi/vmc.pem"}
|
|
|
|
missing := checkRecord(context.Background(), in, fakeDNS{})
|
|
if missing.Status != StatusFail || !strings.Contains(missing.Detail, "default._bimi.example.test") {
|
|
t.Errorf("missing: %+v", missing)
|
|
}
|
|
|
|
wrong := checkRecord(context.Background(), in, fakeDNS{"default._bimi.example.test": {"v=BIMI1; l=https://elsewhere.test/logo.svg;"}})
|
|
if wrong.Status != StatusFail || !strings.Contains(wrong.Detail, "l=") || !strings.Contains(wrong.Detail, "a=") {
|
|
t.Errorf("wrong: %+v", wrong)
|
|
}
|
|
|
|
// Without a certificate here, an a= in DNS is a fault too: it points nowhere
|
|
noCert := Inputs{Domain: "example.test", LogoURL: in.LogoURL}
|
|
stray := checkRecord(context.Background(), noCert, fakeDNS{"default._bimi.example.test": {"v=BIMI1; l=" + in.LogoURL + "; a=https://old.test/x.pem"}})
|
|
if stray.Status != StatusFail {
|
|
t.Errorf("stray a=: %+v", stray)
|
|
}
|
|
}
|
|
|
|
func TestRunChecksLogoURL(t *testing.T) {
|
|
in := Inputs{Domain: "example.test", LogoURL: logoURL, SVG: []byte(goodSVG)}
|
|
cases := map[string]struct {
|
|
resp Response
|
|
err bool
|
|
want Status
|
|
}{
|
|
"ok": {Response{Status: 200, ContentType: "image/svg+xml; charset=utf-8", Body: []byte(goodSVG)}, false, StatusOK},
|
|
"not found": {Response{Status: 404}, false, StatusFail},
|
|
"wrong type": {Response{Status: 200, ContentType: "text/html", Body: []byte(goodSVG)}, false, StatusFail},
|
|
"different bytes": {Response{Status: 200, ContentType: "image/svg+xml", Body: []byte("<svg/>")}, false, StatusFail},
|
|
"unreachable": {Response{}, true, StatusFail},
|
|
}
|
|
for name, c := range cases {
|
|
files := map[string]Response{}
|
|
if !c.err {
|
|
files[logoURL] = c.resp
|
|
}
|
|
if got := checkLogoURL(context.Background(), in, fakeFetch(files)); got.Status != c.want {
|
|
t.Errorf("%s: %s (%s), want %s", name, got.Status, got.Detail, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunChecksCertificate(t *testing.T) {
|
|
svg := read(t, "ebay.svg")
|
|
pemData := read(t, "ebay.pem")
|
|
base := Inputs{Domain: "ebay.com", LogoURL: logoURL, SVG: svg, Now: fixtureNow}
|
|
|
|
t.Run("none is a warning, with the reason", func(t *testing.T) {
|
|
got := byID(checkCertificate(context.Background(), base, fakeFetch(nil)))["certificate"]
|
|
if got.Status != StatusWarn || !strings.Contains(got.Detail, "Gmail") {
|
|
t.Errorf("%+v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("hosted but the public URL serves something else", func(t *testing.T) {
|
|
in := base
|
|
in.CertificatePEM, in.CertificateURL = pemData, vmcURL
|
|
got := byID(checkCertificate(context.Background(), in, fakeFetch(map[string]Response{vmcURL: {Status: 200, Body: []byte("other")}})))
|
|
if got["certificate"].Status != StatusOK || got["certificateUrl"].Status != StatusFail {
|
|
t.Errorf("%+v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("hosted for a logo that changed since", func(t *testing.T) {
|
|
in := base
|
|
in.CertificatePEM, in.CertificateURL, in.SVG = pemData, vmcURL, []byte(goodSVG)
|
|
got := byID(checkCertificate(context.Background(), in, fakeFetch(map[string]Response{vmcURL: {Status: 200, Body: pemData}})))
|
|
if got["certificate"].Status != StatusFail || !strings.Contains(got["certificate"].Detail, "logoMismatch") {
|
|
t.Errorf("%+v", got["certificate"])
|
|
}
|
|
})
|
|
|
|
t.Run("kept elsewhere, fetched and inspected", func(t *testing.T) {
|
|
in := base
|
|
in.ExternalCertificateURL = "https://vmc.example.test/x.pem"
|
|
got := byID(checkCertificate(context.Background(), in, fakeFetch(map[string]Response{in.ExternalCertificateURL: {Status: 200, Body: pemData}})))
|
|
if got["certificate"].Status != StatusOK {
|
|
t.Errorf("%+v", got["certificate"])
|
|
}
|
|
bad := byID(checkCertificate(context.Background(), in, fakeFetch(nil)))
|
|
if bad["certificate"].Status != StatusFail {
|
|
t.Errorf("unreachable external certificate: %+v", bad["certificate"])
|
|
}
|
|
})
|
|
}
|