Skip to content

Commit

Permalink
Backported simple support for BIMI indicators
Browse files Browse the repository at this point in the history
  • Loading branch information
dodmi committed Nov 23, 2023
1 parent 5fc0ab0 commit 7d61dac
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 10 deletions.
31 changes: 21 additions & 10 deletions modules/AuthVerifier.jsm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

// options for ESLint
/* global Components, Services, MailServices */
/* global Logging, ARHParser */
/* global PREF, dkimStrings, domainIsInDomain, getDomainFromAddr, tryGetFormattedString, DKIM_InternalError */
/* global Logging, ARHParser, BIMI */
/* global PREF, dkimStrings, domainIsInDomain, getDomainFromAddr, tryGetFormattedString, DKIM_InternalError, addrIsInDomain */
/* exported EXPORTED_SYMBOLS, AuthVerifier */

"use strict";
Expand All @@ -43,6 +43,7 @@ Cu.import("resource://dkim_verifier/logging.jsm.js");
Cu.import("resource://dkim_verifier/helper.jsm.js");
Cu.import("resource://dkim_verifier/MsgReader.jsm.js");
Cu.import("resource://dkim_verifier/ARHParser.jsm.js");
Cu.import("resource://dkim_verifier/bimi.jsm.js");
// @ts-ignore
let DKIM = {};
Cu.import("resource://dkim_verifier/dkimPolicy.jsm.js", DKIM);
Expand Down Expand Up @@ -70,12 +71,13 @@ let prefs = Services.prefs.getBranch(PREF_BRANCH);
/**
* @typedef {Object} SavedAuthResult|SavedAuthResultV3
* @property {String} version
* result version ("3.0")
* result version ("3.1")
* @property {dkimSigResultV2[]} dkim
* @property {ARHResinfo[]} [spf]
* @property {ARHResinfo[]} [dmarc]
* @property {Object} [arh]
* @property {dkimSigResultV2[]} [arh.dkim]
* @property {string|undefined} [bimiIndicator] Since version 3.1
*/

/**
Expand Down Expand Up @@ -149,13 +151,14 @@ var AuthVerifier = {
savedAuthResult = arhResult;
} else {
savedAuthResult = {
version: "3.0",
version: "3.1",
dkim: [],
spf: arhResult.spf,
dmarc: arhResult.dmarc,
arh: {
dkim: arhResult.dkim
},
bimiIndicator: arhResult.bimiIndicator,
};
}
} else {
Expand Down Expand Up @@ -260,6 +263,7 @@ function getARHResult(msgHdr, msg) {
let arhDKIM = [];
let arhSPF = [];
let arhDMARC = [];
let arhBIMI = [];
for (let i = 0; i < msg.headerFields.get("authentication-results").length; i++) {
let arh;
try {
Expand All @@ -286,6 +290,7 @@ function getARHResult(msgHdr, msg) {
arhDKIM = arhDKIM.concat(arh.resinfo.filter(e => e.method === "dkim"));
arhSPF = arhSPF.concat(arh.resinfo.filter(e => e.method === "spf"));
arhDMARC = arhDMARC.concat(arh.resinfo.filter(e => e.method === "dmarc"));
arhBIMI = arhBIMI.concat(arh.resinfo.filter(e => e.method === "bimi"));
}

// convert DKIM results
Expand Down Expand Up @@ -345,12 +350,13 @@ function getARHResult(msgHdr, msg) {

// sort signatures
DKIM.Verifier.sortSignatures(msg, dkimSigResults);

let savedAuthResult = {
version: "3.0",
version: "3.1",
dkim: dkimSigResults,
spf: arhSPF,
dmarc: arhDMARC,
bimiIndicator: BIMI.getBimiIndicator(msg.headerFields, arhBIMI) || undefined,
};
log.debug("ARH result:", savedAuthResult);
return savedAuthResult;
Expand Down Expand Up @@ -701,7 +707,7 @@ async function SavedAuthResult_to_AuthResult(savedAuthResult, from) { // eslint-
authResult.arh.dkim = authResult.arh.dkim.map(
dkimSigResultV2_to_AuthResultDKIM);
}
return addFavicons(authResult, from);
return addFavicons(authResult, from, savedAuthResult.bimiIndicator);
}

/**
Expand All @@ -724,16 +730,21 @@ function AuthResultDKIMV2_to_dkimSigResultV2(authResultDKIM) {
*
* @param {AuthResult} authResult
* @param {String|undefined} from
* @param {String|undefined} bimiIndicator
* @return {Promise<AuthResult>} authResult
*/
async function addFavicons(authResult, from) {
async function addFavicons(authResult, from, bimiIndicator) {
if (!prefs.getBoolPref("display.favicon.show")) {
return authResult;
}
for (let i = 0; i < authResult.dkim.length; i++) {
if (authResult.dkim[i].sdid) {
authResult.dkim[i].favicon =
await DKIM.Policy.getFavicon(authResult.dkim[i].sdid, authResult.dkim[i].auid, from);
if (bimiIndicator && from && addrIsInDomain(from, authResult.dkim[i].sdid)) {
authResult.dkim[i].favicon = `data:image/svg+xml;base64,${bimiIndicator}`;
} else {
authResult.dkim[i].favicon =
await DKIM.Policy.getFavicon(authResult.dkim[i].sdid, authResult.dkim[i].auid, from);
}
}
}
return authResult;
Expand Down
96 changes: 96 additions & 0 deletions modules/bimi.jsm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Brand Indicators for Message Identification (BIMI).
* https://datatracker.ietf.org/doc/draft-brand-indicators-for-message-identification/04/
*
* BIMI implementation for a Mail User Agent (MUA).
* Gets the BIMI Indicator based on the information the receiving
* Mail Transfer Agent (MTA) writes into the headers of the message.
*
* This is not a complete implementation of BIMI.
*
* Copyright (c) 2023 Philippe Lieser
*
* This software is licensed under the terms of the MIT License.
*
* The above copyright and license notice shall be
* included in all copies or substantial portions of the Software.
*/

// options for ESLint
/* global Components */
/* global Logging */
/* exported EXPORTED_SYMBOLS, BIMI */

"use strict";

var EXPORTED_SYMBOLS = [
"BIMI"
];

// @ts-ignore
const Cu = Components.utils;

Cu.import("resource://dkim_verifier/logging.jsm.js");
Cu.import("resource://dkim_verifier/ARHParser.jsm.js");

let BIMI = (function() {

const log = Logging.getLogger("BIMI");

// WSP as specified in Appendix B.1 of RFC 5234
const WSP_p = "[ \t]";
// obs-FWS as specified in Section 4.2 of RFC 5322
const obs_FWS_p = `(?:${WSP_p}+(?:\r\n${WSP_p}+)*)`;
// FWS as specified in Section 3.2.2 of RFC 5322
const FWS_p = `(?:(?:(?:${WSP_p}*\r\n)?${WSP_p}+)|${obs_FWS_p})`;

let that = {
/**
* Try to get the BIMI Indicator if available.
*
* @param {Map<string, string[]>} headers
* @param {ARHResinfo[]} arhBIMI - Trusted ARHs containing a BIMI result.
* @returns {string|null}
*/
getBimiIndicator: function(headers, arhBIMI) {
// Assuming:
// 1. We only get ARHs that can be trusted (i.e. from the receiving MTA).
// 2. If the receiving MTA does not supports BIMI,
// we will not see an ARH with a BIMI result (because of 1)
// 3. If the receiving MTA supports BIMI,
// it will make sure we only see his BIMI-Indicator headers (as required by the RFC).
//
// Given the above, it should be safe to trust the BIMI indicator from the BIMI-Indicator header
// if we have a passing BIMI result there the MTA claims to have checked the Authority Evidence.
const hasAuthorityPassBIMI = arhBIMI.some(
arh => arh.method === "bimi" &&
arh.result === "pass" &&
arh.propertys.policy.authority === "pass"
);
if (!hasAuthorityPassBIMI) {
return null;
}

const bimiIndicators = headers.get("bimi-indicator") || [];
if (bimiIndicators.length > 1) {
log.warn("Message contains more than one BIMI-Indicator header");
return null;
}
let bimiIndicator = bimiIndicators[0];
if (!bimiIndicator) {
log.warn("Message contains an ARH with passing BIMI but does not have a BIMI-Indicator header");
return null;
}

// TODO: If in the future we support ARC we might want to check the policy.indicator-hash

// Remove header name and new line at end
bimiIndicator = bimiIndicator.slice("bimi-indicator:".length, -"\r\n".length);
// Remove all whitespace
bimiIndicator = bimiIndicator.replace(new RegExp(`${FWS_p}`, "g"), "");

return bimiIndicator;
}
};
return that;
}());

0 comments on commit 7d61dac

Please sign in to comment.