Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backported simple support for BIMI indicators #414

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 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 */
/* global Logging, ARHParser, BIMI */
/* global PREF, dkimStrings, domainIsInDomain, getDomainFromAddr, tryGetFormattedString, addrIsInDomain */
/* exported EXPORTED_SYMBOLS, AuthVerifier */

"use strict";
Expand All @@ -42,6 +42,7 @@ Cu.import("resource:///modules/iteratorUtils.jsm");
Cu.import("resource://dkim_verifier/logging.jsm.js");
Cu.import("resource://dkim_verifier/helper.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 @@ -69,12 +70,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 @@ -163,13 +165,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 @@ -275,6 +278,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 @@ -301,6 +305,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 @@ -362,10 +367,11 @@ function getARHResult(msgHdr, msg) {
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 @@ -716,7 +722,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 @@ -739,16 +745,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) {
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;
}());