Bug 1644085 - Support recipient aliases for OpenPGP encryption. r=mkmelin

Differential Revision: https://phabricator.services.mozilla.com/D97973

--HG--
extra : rebase_source : ecb59884c807ba4f3e7a0c422889b31e7a316b98
This commit is contained in:
Kai Engert 2021-02-11 18:30:30 +01:00
Родитель 35969d5191
Коммит ad40ff5527
22 изменённых файлов: 800 добавлений и 149 удалений

Просмотреть файл

@ -36,6 +36,12 @@ pref("mail.identity.default.is_gnupg_key_id", false);
// The hexadecimal OpenPGP key ID externally configured by GnuPG used for an identity.
pref("mail.identity.default.last_entered_external_gnupg_key_id", "");
// Load a JSON file that contains recipient key alias rules. See bug 1644085.
// Suggested filename: openpgp-alias-rules.json
// Simple filenames (without path) are loaded from the profile directory.
// If you need to specify a path, use a file:// URL
pref("mail.openpgp.alias_rules_file", "");
// When sending, encrypt to this additional key. Not available in release channel builds.
pref("mail.openpgp.debug.extra_encryption_key", "");

Просмотреть файл

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const EXPORTED_SYMBOLS = ["OpenPGPAlias"];
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
Cu.importGlobalProperties(["fetch"]);
var OpenPGPAlias = {
_aliasDomains: null,
_aliasEmails: null,
_loaded() {
return this._aliasDomains && this._aliasEmails;
},
async load() {
let path = Services.prefs.getStringPref(
"mail.openpgp.alias_rules_file",
""
);
if (!path) {
this._clear();
return;
}
await this._loadFromFile(path);
},
_clear() {
this._aliasDomains = new Map();
this._aliasEmails = new Map();
},
async _loadFromFile(src) {
this._clear();
let aliasRules;
let jsonData;
if (src.startsWith("file://")) {
let response = await fetch(src);
jsonData = await response.json();
} else if (src.includes("/") || src.includes("\\")) {
throw new Error(`Invalid alias rules src: ${src}`);
} else {
let spec = OS.Path.join(OS.Constants.Path.profileDir, src);
let response = await fetch(OS.Path.toFileURI(spec));
jsonData = await response.json();
}
if (!("rules" in jsonData)) {
throw new Error(
"alias file contains invalid JSON data, no rules element found"
);
}
aliasRules = jsonData.rules;
for (let entry of aliasRules) {
if (!("keys" in entry)) {
continue;
}
// Ignore duplicate rules, only use first rule per key.
// Require email address contains @, and domain doesn't contain @.
if ("email" in entry) {
if (!entry.email.includes("@")) {
console.log("Ignoring invalid email alias rule: " + entry.email);
continue;
}
if (this._aliasEmails.get(entry.email)) {
console.log("Ignoring duplicate email alias rule: " + entry.email);
} else {
this._aliasEmails.set(entry.email, entry.keys);
}
} else if ("domain" in entry) {
if (entry.domain.includes("@")) {
console.log("Ignoring invalid domain alias rule: " + entry.domain);
continue;
}
if (this._aliasDomains.get(entry.domain)) {
console.log("Ignoring duplicate domain alias rule: " + entry.domain);
} else {
this._aliasDomains.set(entry.domain, entry.keys);
}
}
}
},
getDomainAliasKeyList(email) {
if (!this._loaded()) {
return null;
}
let lastAt = email.lastIndexOf("@");
if (lastAt == -1) {
return null;
}
let domain = email.substr(lastAt + 1);
if (!domain) {
return null;
}
return this._aliasDomains.get(domain);
},
getEmailAliasKeyList(email) {
if (!this._loaded()) {
return null;
}
return this._aliasEmails.get(email);
},
};

Просмотреть файл

@ -2190,6 +2190,38 @@ var RNP = {
}
},
addAliasKeys(aliasKeys, op) {
for (let ak of aliasKeys) {
let key = this.getKeyHandleByKeyIdOrFingerprint(RNPLib.ffi, "0x" + ak);
if (!key || key.isNull()) {
console.debug(
"addAliasKeys: cannot find key used by alias rule: " + ak
);
return false;
}
this.addSuitableEncryptKey(key, op);
RNPLib.rnp_key_handle_destroy(key);
}
return true;
},
async addEncryptionKeyForEmail(email, op) {
let key = await this.findKeyByEmail(email, true);
if (!key || key.isNull()) {
return false;
}
this.addSuitableEncryptKey(key, op);
RNPLib.rnp_key_handle_destroy(key);
return true;
},
getEmailWithoutBrackets(email) {
if (email.startsWith("<") && email.endsWith(">")) {
return email.substring(1, email.length - 1);
}
return email;
},
async encryptAndOrSign(plaintext, args, resultStatus) {
if (args.sign && args.senderKeyIsExternal) {
if (!GPGME.allDependenciesLoaded()) {
@ -2339,26 +2371,41 @@ var RNP = {
}
if (args.encrypt) {
// If we have an alias definition, it will be used, and the usual
// lookup by email address will be skipped. Earlier code should
// have already checked that alias keys are available and usable
// for encryption, so we fail if a problem is found.
for (let id in args.to) {
let toEmail = args.to[id].toLowerCase();
let toKey = await this.findKeyByEmail(toEmail, true);
if (!toKey || toKey.isNull()) {
let aliasKeys = args.aliasKeys.get(
this.getEmailWithoutBrackets(toEmail)
);
if (aliasKeys) {
if (!this.addAliasKeys(aliasKeys, op)) {
resultStatus.statusFlags |= EnigmailConstants.INVALID_RECIPIENT;
return null;
}
} else if (!(await this.addEncryptionKeyForEmail(toEmail, op))) {
resultStatus.statusFlags |= EnigmailConstants.INVALID_RECIPIENT;
return null;
}
this.addSuitableEncryptKey(toKey, op);
RNPLib.rnp_key_handle_destroy(toKey);
}
for (let id in args.bcc) {
let bccEmail = args.bcc[id].toLowerCase();
let bccKey = await this.findKeyByEmail(bccEmail, true);
if (bccKey.isNull()) {
let aliasKeys = args.aliasKeys.get(
this.getEmailWithoutBrackets(bccEmail)
);
if (aliasKeys) {
if (!this.addAliasKeys(aliasKeys, op)) {
resultStatus.statusFlags |= EnigmailConstants.INVALID_RECIPIENT;
return null;
}
} else if (!(await this.addEncryptionKeyForEmail(bccEmail, op))) {
resultStatus.statusFlags |= EnigmailConstants.INVALID_RECIPIENT;
return null;
}
this.addSuitableEncryptKey(bccKey, op);
RNPLib.rnp_key_handle_destroy(bccKey);
}
if (AppConstants.MOZ_UPDATE_CHANNEL != "release") {
@ -2477,7 +2524,7 @@ var RNP = {
async findKeyByEmail(id, onlyIfAcceptableAsRecipientKey = false) {
if (!id.startsWith("<") || !id.endsWith(">") || id.includes(" ")) {
throw new Error("invalid parameter given to findKeyByEmail");
throw new Error(`Invalid argument; id=${id}`);
}
let emailWithoutBrackets = id.substring(1, id.length - 1);

Просмотреть файл

@ -45,6 +45,7 @@ const ENC_TYPE_MSG = 0;
const ENC_TYPE_ATTACH_BINARY = 1;
var EnigmailEncryption = {
// return object on success, null on failure
getCryptParams(
fromMailAddr,
toMailAddr,
@ -100,6 +101,7 @@ var EnigmailEncryption = {
result.to = toMailAddr.split(/\s*,\s*/);
result.bcc = bccMailAddr.split(/\s*,\s*/);
result.aliasKeys = new Map();
if (result.to.length == 1 && result.to[0].length == 0) {
result.to.splice(0, 1); // remove the single empty entry
@ -111,7 +113,7 @@ var EnigmailEncryption = {
console.debug(`getCryptParams, got: to=${result.to}, bcc=${result.bcc}`);
if (fromMailAddr.search(/^0x/) === 0) {
if (/^0x[0-9a-f]+$/i.test(fromMailAddr)) {
result.sender = fromMailAddr;
} else {
result.sender = "<" + fromMailAddr + ">";
@ -136,18 +138,36 @@ var EnigmailEncryption = {
result.encryptToSender = true;
}
var k;
for (k = 0; k < result.to.length; k++) {
//result.to[k] = result.to[k].replace(/'/g, "\\'");
if (result.to[k].length > 0 && result.to[k].search(/^0x/) !== 0) {
result.to[k] = "<" + result.to[k] + ">";
let recipArrays = ["to", "bcc"];
for (let recipArray of recipArrays) {
let kMax = recipArray == "to" ? result.to.length : result.bcc.length;
for (let k = 0; k < kMax; k++) {
let email = recipArray == "to" ? result.to[k] : result.bcc[k];
if (!email) {
continue;
}
if (/^0x[0-9a-f]+$/i.test(email)) {
throw new Error(`Recipient should not be a key ID: ${email}`);
}
if (recipArray == "to") {
result.to[k] = "<" + email + ">";
} else {
result.bcc[k] = "<" + email + ">";
}
for (k = 0; k < result.bcc.length; k++) {
//result.bcc[k] = result.bcc[k].replace(/'/g, "\\'");
if (result.bcc[k].length > 0 && result.bcc[k].search(/^0x/) !== 0) {
result.bcc[k] = "<" + result.bcc[k] + ">";
let aliasKeyList = EnigmailKeyRing.getAliasKeyList(email);
if (aliasKeyList) {
let aliasKeys = EnigmailKeyRing.getAliasKeys(email, aliasKeyList);
if (!aliasKeys.length) {
errorMsgObj.value = "bad alias definition for " + email;
return null;
}
// We insert the definition even if aliasKeys is empty,
// because having an alias means we want to skip the usual
// lookup - and having the entry tells RNP to skip.
result.aliasKeys.set(email, aliasKeys);
}
}
}
} else if (detachedSig) {
@ -198,7 +218,7 @@ var EnigmailEncryption = {
let sign = !!(sendFlags & EnigmailConstants.SEND_SIGNED);
let encrypt = !!(sendFlags & EnigmailConstants.SEND_ENCRYPTED);
if (fromKeyId.search(/^(0x)?[A-Z0-9]+$/) === 0) {
if (/^(0x)?[0-9a-f]+$/i.test(fromKeyId)) {
// key ID specified
foundKey = EnigmailKeyRing.getKeyById(fromKeyId);
}
@ -248,6 +268,7 @@ var EnigmailEncryption = {
return ret;
},
// return 0 on success, non-zero on failure
encryptMessageStart(
win,
uiFlags,
@ -316,7 +337,7 @@ var EnigmailEncryption = {
);
if (!encryptArgs) {
return 0;
return -1;
}
if (!listener) {

Просмотреть файл

@ -14,6 +14,7 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
OpenPGPAlias: "chrome://openpgp/content/modules/OpenPGPAlias.jsm",
EnigmailArmor: "chrome://openpgp/content/modules/armor.jsm",
EnigmailCryptoAPI: "chrome://openpgp/content/modules/cryptoAPI.jsm",
EnigmailFiles: "chrome://openpgp/content/modules/files.jsm",
@ -979,15 +980,43 @@ var EnigmailKeyRing = {
throw new Error("Not implemented");
},
isValidForEncryption(keyObj) {
return this._getValidityLevelIgnoringAcceptance(keyObj, null) == 0;
},
// returns an acceptanceLevel from -1 to 3,
// or -2 for "doesn't match email" or "not usable"
async isValidKeyForRecipient(keyObj, emailAddr) {
if (!emailAddr) {
return -2;
}
let level = this._getValidityLevelIgnoringAcceptance(keyObj, emailAddr);
if (level < 0) {
return level;
}
return this._getAcceptanceLevelForEmail(keyObj, emailAddr);
},
/**
* This function checks that given key is not expired, not revoked,
* and that a (related) encryption (sub-)key is available.
* If an email address is provided by the caller, the function
* also requires that a matching user id is available.
*
* @param {Object} keyObj - the key to check
* @param {String} [emailAddr] - optional email address
* @return {Integer} - validity level, negative for invalid,
* 0 if no problem were found (neutral)
*/
_getValidityLevelIgnoringAcceptance(keyObj, emailAddr) {
switch (keyObj.keyTrust) {
case "e":
case "r":
return -2;
}
if (emailAddr) {
let uidMatch = false;
for (let uid of keyObj.userIds) {
if (uid.type !== "uid") {
@ -1005,6 +1034,8 @@ var EnigmailKeyRing = {
if (!uidMatch) {
return -2;
}
}
// key valid for encryption?
if (!keyObj.keyUseFor.includes("E")) {
return -2;
@ -1033,6 +1064,10 @@ var EnigmailKeyRing = {
return -2;
}
return 0; // no problem found
},
async _getAcceptanceLevelForEmail(keyObj, emailAddr) {
let acceptanceLevel;
if (keyObj.secretAvailable) {
let isPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr);
@ -1138,6 +1173,12 @@ var EnigmailKeyRing = {
},
async getKeyAcceptanceLevelForEmail(keyObj, email) {
if (keyObj.secretAvailable) {
throw new Error(
`Unexpected private key parameter; keyObj.fpr=${keyObj.fpr}`
);
}
let acceptanceLevel = 0;
let acceptanceResult = {};
@ -1200,12 +1241,10 @@ var EnigmailKeyRing = {
* * addr {String}: email addresses
* * msg {String}: related error
* }
* - keyMap {Object<String>}: map of email addr -> keyID
* @param {Array<String>} resultingArray: list of found key IDs
*
* @return {Boolean}: true if at least one key missing; false otherwise
*/
async getValidKeysForAllRecipients(addresses, details, resultingArray) {
async getValidKeysForAllRecipients(addresses, details) {
if (!addresses) {
return null;
}
@ -1213,7 +1252,6 @@ var EnigmailKeyRing = {
let keyMissing = false;
if (details) {
details.errArray = [];
details.keyMap = {};
}
for (let i = 0; i < addresses.length; i++) {
let addr = addresses[i];
@ -1221,7 +1259,6 @@ var EnigmailKeyRing = {
continue;
}
// try to find current address in key list:
let keyId = null;
var errMsg = null;
if (!addr.includes("@")) {
throw new Error(
@ -1230,19 +1267,62 @@ var EnigmailKeyRing = {
);
}
let aliasKeyList = this.getAliasKeyList(addr);
if (aliasKeyList) {
for (let entry of aliasKeyList) {
let foundError = true;
let key;
if ("fingerprint" in entry) {
key = this.getKeyById(entry.fingerprint);
} else if ("id" in entry) {
key = this.getKeyById(entry.id);
}
if (key && this.isValidForEncryption(key)) {
let acceptanceResult = {};
await PgpSqliteDb2.getFingerprintAcceptance(
null,
key.fpr,
acceptanceResult
);
// If we don't have acceptance info for the key yet,
// or, we have it and it isn't rejected,
// then we accept the key for using it in alias definitions.
if (
!("fingerprintAcceptance" in acceptanceResult) ||
acceptanceResult.fingerprintAcceptance != "rejected"
) {
foundError = false;
}
}
if (foundError) {
keyMissing = true;
if (details) {
let detEl = {};
detEl.addr = addr;
detEl.msg = "alias problem";
details.errArray.push(detEl);
}
console.debug(
'keyRing.jsm: getValidKeysForAllRecipients(): alias key list for="' +
addr +
' refers to missing or unusable key"\n'
);
}
}
// skip the lookup for direct matching keys by email
continue;
}
// try email match:
var addrErrDetails = {};
let foundKeyId = await this.getValidKeyForRecipient(addr, addrErrDetails);
if (details && addrErrDetails.msg) {
errMsg = addrErrDetails.msg;
}
if (foundKeyId) {
keyId = "0x" + foundKeyId.toUpperCase();
resultingArray.push(keyId);
if (details) {
details.keyMap[addr.toLowerCase()] = keyId;
}
} else {
if (!foundKeyId) {
// no key for this address found
keyMissing = true;
if (details) {
@ -1255,9 +1335,9 @@ var EnigmailKeyRing = {
details.errArray.push(detailsElem);
}
EnigmailLog.DEBUG(
'keyRing.jsm: getValidKeysForAllRecipients(): return null (no single valid key found for="' +
'keyRing.jsm: getValidKeysForAllRecipients(): no single valid key found for="' +
addr +
'")\n'
'"\n'
);
}
}
@ -1299,35 +1379,59 @@ var EnigmailKeyRing = {
},
/**
* Determine the key ID for a set of given addresses
* If the given email address has an alias definition, return its
* list of key identifiers.
*
* @param {string[]} addresses - Email addresses to get key id for.
* The function will prefer a match to an exact email alias.
* If no email alias could be found, the function will search for
* an alias rule that matches the domain.
*
* @return {Map<string,keyObj[]>}: map of email addr -> keyObj[]
* @param {string} email - The email address to look up
* @return {[]} - An array with alias key identifiers found for the
* input, or null if no alias matches the address.
*/
async getMultValidKeysForMultRecipients(addresses) {
if (!addresses) {
return null;
}
let allKeysMap = new Map();
for (let i = 0; i < addresses.length; i++) {
let addr = addresses[i].toLowerCase();
if (!addr) {
continue;
getAliasKeyList(email) {
let ekl = OpenPGPAlias.getEmailAliasKeyList(email);
if (ekl) {
return ekl;
}
if (!addr.includes("@")) {
throw new Error(
"getAllRecipientKeys unexpected lookup for non-email addr: " + addr
return OpenPGPAlias.getDomainAliasKeyList(email);
},
/**
* Return the fingerprint of each usable alias key for the given
* email address.
*
* @param {string} email - The email address to look up.
* @param {String[]} keyList - Array of key identifiers
* @return {String[]} An array with fingerprints of each usable alias key.
*/
getAliasKeys(email, keyList) {
let keys = [];
for (let entry of keyList) {
let key;
let lookupId;
if ("fingerprint" in entry) {
lookupId = entry.fingerprint;
key = this.getKeyById(entry.fingerprint);
} else if ("id" in entry) {
lookupId = entry.id;
key = this.getKeyById(entry.id);
}
if (key && this.isValidForEncryption(key)) {
keys.push(key.fpr);
} else {
let reason = key ? "not usable" : "missing";
console.debug(
"getAliasKeys: key for identifier: " + lookupId + " is " + reason
);
return [];
}
}
let found = await this.getMultValidKeysForOneRecipient(addr);
if (found) {
allKeysMap.set(addr, found);
}
}
return allKeysMap;
return keys;
},
/**

Просмотреть файл

@ -65,6 +65,13 @@ var PgpSqliteDb2 = {
accCacheEmails: null,
async getFingerprintAcceptance(conn, fingerprint, rv) {
// 40 is for modern fingerprints, 32 for older fingerprints.
if (fingerprint.length != 40 && fingerprint.length != 32) {
throw new Error(
"internal error, invalid fingerprint value: " + fingerprint
);
}
fingerprint = fingerprint.toLowerCase();
if (fingerprint == this.accCacheFingerprint) {
rv.fingerprintAcceptance = this.accCacheValue;

Просмотреть файл

@ -15,15 +15,24 @@ var { EnigmailWindows } = ChromeUtils.import(
var { EnigmailKey } = ChromeUtils.import(
"chrome://openpgp/content/modules/key.jsm"
);
const { OpenPGPAlias } = ChromeUtils.import(
"chrome://openpgp/content/modules/OpenPGPAlias.jsm"
);
const { PgpSqliteDb2 } = ChromeUtils.import(
"chrome://openpgp/content/modules/sqliteDb.jsm"
);
var gListBox;
var gViewButton;
var gLdapBundle;
var gEmailAddresses = [];
var gRowToEmail = [];
// One boolean entry per row. True means it is an alias row.
// This allows us to use different dialog behavior for alias entries.
var gAliasRows = [];
var gMapAddressToKeyObjs = null;
function addRecipients(toAddrList, recList) {
@ -38,37 +47,53 @@ function addRecipients(toAddrList, recList) {
}
async function setListEntries() {
gMapAddressToKeyObjs = await EnigmailKeyRing.getMultValidKeysForMultRecipients(
gEmailAddresses
);
if (!gMapAddressToKeyObjs) {
throw new Error("getMultValidKeysForMultRecipients failed");
}
gMapAddressToKeyObjs = new Map();
for (let addr of gEmailAddresses) {
let emailStatus = null;
addr = addr.toLowerCase();
let foundKeys = gMapAddressToKeyObjs.get(addr);
if (!foundKeys || !foundKeys.length) {
emailStatus = "openpgp-recip-missing";
let statusStringID = null;
let statusStringDirect = "";
let aliasKeyList = EnigmailKeyRing.getAliasKeyList(addr);
let isAlias = !!aliasKeyList;
if (isAlias) {
let aliasKeys = EnigmailKeyRing.getAliasKeys(addr, aliasKeyList);
if (!aliasKeys.length) {
// failure, at least one alias key is unusable/unavailable
statusStringDirect = gLdapBundle.getString("33");
} else {
// use a better string after 78, bug 1679301
statusStringDirect = "a -> b";
}
} else {
let foundKeys = await EnigmailKeyRing.getMultValidKeysForOneRecipient(
addr
);
if (!foundKeys || !foundKeys.length) {
statusStringID = "openpgp-recip-missing";
} else {
gMapAddressToKeyObjs.set(addr, foundKeys);
for (let keyObj of foundKeys) {
let goodPersonal = false;
if (keyObj.secretAvailable) {
goodPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr);
goodPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(
keyObj.fpr
);
}
if (
goodPersonal ||
keyObj.acceptance == "verified" ||
keyObj.acceptance == "unverified"
) {
emailStatus = "openpgp-recip-good";
statusStringID = "openpgp-recip-good";
break;
}
}
if (!emailStatus) {
emailStatus = "openpgp-recip-none-accepted";
if (!statusStringID) {
statusStringID = "openpgp-recip-none-accepted";
}
}
}
@ -81,7 +106,13 @@ async function setListEntries() {
listitem.appendChild(emailItem);
let status = document.createXULElement("label");
document.l10n.setAttributes(status, emailStatus);
if (statusStringID) {
document.l10n.setAttributes(status, statusStringID);
} else {
status.setAttribute("value", statusStringDirect);
}
status.setAttribute("crop", "end");
status.setAttribute("style", "width: var(--statusWidth)");
listitem.appendChild(status);
@ -89,6 +120,7 @@ async function setListEntries() {
gListBox.appendChild(listitem);
gRowToEmail.push(addr);
gAliasRows.push(isAlias);
}
}
@ -98,9 +130,14 @@ async function onLoad() {
return;
}
await OpenPGPAlias.load();
gListBox = document.getElementById("infolist");
gViewButton = document.getElementById("detailsButton");
// Fix as part of bug 1679301
gLdapBundle = document.getElementById("bundle_ldap");
var arrLen = {};
var recList;
@ -147,7 +184,10 @@ async function reloadAndReselect(selIndex = -1) {
}
function onSelectionChange(event) {
gViewButton.disabled = !gListBox.selectedItems.length;
// We don't offer detail management/discovery for email addresses
// that match an alias rule.
gViewButton.disabled =
!gListBox.selectedItems.length || gAliasRows[gListBox.selectedIndex];
}
function viewSelectedEmail() {

Просмотреть файл

@ -29,6 +29,9 @@
]]></script>
<script src="chrome://messenger/content/dialogShadowDom.js"/>
<!-- TODO: Remove after 78 is done, bug 1679301 -->
<stringbundle id="bundle_ldap" src="chrome://mozldap/locale/ldap.properties"/>
<linkset>
<html:link rel="localization" href="messenger/openpgp/composeKeyStatus.ftl"/>
</linkset>

Просмотреть файл

@ -46,43 +46,15 @@ if (!Enigmail) {
}
Enigmail.hlp = {
/* try to find valid key to passed email addresses (or keys)
* @return: list of all found key (with leading "0x") or null
* details in details parameter
/**
* Check availability of valid keys for passed email addresses (or keys).
* @param {String} emailsOrKeys - comma separated list
* @param {Object} details - holds details for invalid keys, see
* EnigmailKeyRing.getValidKeysForAllRecipients
* @return {Boolean} - false on failure
*/
async validKeysForAllRecipients(emailsOrKeys, details) {
EnigmailLog.DEBUG("=====> validKeysForAllRecipients()\n");
EnigmailLog.DEBUG(
"enigmailMsgComposeHelper.js: validKeysForAllRecipients(): emailsOrKeys='" +
emailsOrKeys +
"'\n"
);
// use helper to see when we enter and leave this function
let resultingArray = await this.doValidKeysForAllRecipients(
emailsOrKeys,
details
);
EnigmailLog.DEBUG(
"enigmailMsgComposeHelper.js: validKeysForAllRecipients(): return '" +
resultingArray +
"'\n"
);
EnigmailLog.DEBUG(" <=== validKeysForAllRecipients()\n");
return resultingArray;
},
// helper for validKeysForAllRecipients()
async doValidKeysForAllRecipients(emailsOrKeys, details) {
EnigmailLog.DEBUG(
"enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): emailsOrKeys='" +
emailsOrKeys +
"'\n"
);
let keyMissing;
let resultingArray = []; // resulting key list (if all valid)
let keyMissing = true;
try {
// create array of address elements (email or key)
let addresses = [];
@ -93,30 +65,22 @@ Enigmail.hlp = {
// resolve all the email addresses if possible:
keyMissing = await EnigmailKeyRing.getValidKeysForAllRecipients(
addresses,
details,
resultingArray
details
);
} catch (ex) {
EnigmailLog.DEBUG(
"enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): return null (exception: " +
"enigmailMsgComposeHelper.js: validKeysForAllRecipients(): return null (exception: " +
ex.message +
"\n" +
ex.stack +
")\n"
);
return null;
}
if (keyMissing) {
EnigmailLog.DEBUG(
"enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): return null (key missing)\n"
"enigmailMsgComposeHelper.js: validKeysForAllRecipients(): return null (key missing)\n"
);
return null;
}
EnigmailLog.DEBUG(
'enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): return "' +
resultingArray +
'"\n'
);
return resultingArray;
return !keyMissing;
},
};

Просмотреть файл

@ -96,6 +96,9 @@ var EnigmailMimeEncrypt = ChromeUtils.import(
const { EnigmailCryptoAPI } = ChromeUtils.import(
"chrome://openpgp/content/modules/cryptoAPI.jsm"
);
const { OpenPGPAlias } = ChromeUtils.import(
"chrome://openpgp/content/modules/OpenPGPAlias.jsm"
);
var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
var l10nOpenPGP = new Localization(["messenger/openpgp/openpgp.ftl"]);
@ -1240,6 +1243,8 @@ Enigmail.msg = {
// gMsgCompose.expandMailingLists();
if (Enigmail.msg.isEnigmailEnabledForIdentity()) {
await OpenPGPAlias.load();
var toAddrList = [];
var arrLen = {};
var recList;

Просмотреть файл

@ -54,7 +54,7 @@ async function setListEntries(keys = null) {
} else {
if (!("acceptance" in keyObj)) {
throw new Error(
"expected getMultValidKeysForMultRecipients to set acceptance"
"expected getMultValidKeysForOneRecipient to set acceptance"
);
}
switch (keyObj.acceptance) {

Просмотреть файл

@ -0,0 +1,239 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Tests for OpenPGP encryption alias rules.
*/
"use strict";
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { RNP } = ChromeUtils.import("chrome://openpgp/content/modules/RNP.jsm");
const { EnigmailConstants } = ChromeUtils.import(
"chrome://openpgp/content/modules/constants.jsm"
);
const { EnigmailKeyRing } = ChromeUtils.import(
"chrome://openpgp/content/modules/keyRing.jsm"
);
const { EnigmailEncryption } = ChromeUtils.import(
"chrome://openpgp/content/modules/encryption.jsm"
);
const { OpenPGPAlias } = ChromeUtils.import(
"chrome://openpgp/content/modules/OpenPGPAlias.jsm"
);
const { OpenPGPTestUtils } = ChromeUtils.import(
"resource://testing-common/mozmill/OpenPGPTestUtils.jsm"
);
const keyDir = "../../../../../test/browser/openpgp/data/keys";
const mailNewsDir = "../../../../../../mailnews/test/data";
// Alice's key: EB85BB5FA33A75E15E944E63F231550C4F47E38E
// Bob's key: D1A66E1A23B182C9980F788CFBFCC82A015E7330
// Carol's key: B8F2F6F4BD3AD3F82DC446833099FF1238852B9F
const tests = [
{
info: "Should find Alice's key directly",
filename: undefined,
to: "alice@openpgp.example",
expectedMissing: false,
expectedAliasKeys: null,
},
{
info: "Key absent, no alias defined for address",
filename: `${mailNewsDir}/alias-1.json`,
to: "nobody@openpgp.example",
expectedMissing: true,
expectedAliasKeys: null,
},
{
info:
"File maps Alice's address to Bob's (id) and Carol's (fingerprint) keys",
filename: `${mailNewsDir}/alias-1.json`,
to: "alice@openpgp.example",
expectedMissing: false,
expectedAliasKeys: [
"D1A66E1A23B182C9980F788CFBFCC82A015E7330",
"B8F2F6F4BD3AD3F82DC446833099FF1238852B9F",
],
},
{
info: "File maps Alice's address to an absent key",
filename: `${mailNewsDir}/alias-2.json`,
to: "alice@openpgp.example",
expectedMissing: true,
expectedAliasKeys: null,
},
{
info: "File maps Alice's address to Alice's key (unnecessary alias)",
filename: `${mailNewsDir}/alias-3.json`,
to: "alice@openpgp.example",
expectedMissing: false,
expectedAliasKeys: ["EB85BB5FA33A75E15E944E63F231550C4F47E38E"],
},
{
info: "File maps an address to several keys, all available",
filename: `${mailNewsDir}/alias-4.json`,
to: "nobody@example.com",
expectedMissing: false,
expectedAliasKeys: [
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
"D1A66E1A23B182C9980F788CFBFCC82A015E7330",
"B8F2F6F4BD3AD3F82DC446833099FF1238852B9F",
],
},
{
info: "File maps an address to several keys, one not available",
filename: `${mailNewsDir}/alias-5.json`,
to: "nobody@example.com",
expectedMissing: true,
expectedAliasKeys: null,
},
{
info: "File maps the domain to Carol's key",
filename: `${mailNewsDir}/alias-6.json`,
to: "someone@example.com",
expectedMissing: false,
expectedAliasKeys: ["B8F2F6F4BD3AD3F82DC446833099FF1238852B9F"],
},
{
info: "Multiple rules, should match domain1 rule",
filename: `${mailNewsDir}/alias-7.json`,
to: "someone@domain1.example.com",
expectedMissing: false,
expectedAliasKeys: ["EB85BB5FA33A75E15E944E63F231550C4F47E38E"],
},
{
info: "Multiple rules, should match domain2 rule",
filename: `${mailNewsDir}/alias-7.json`,
to: "contact@domain2.example.com",
expectedMissing: false,
expectedAliasKeys: ["D1A66E1A23B182C9980F788CFBFCC82A015E7330"],
},
{
info: "Multiple rules, should match email contact@domain1 rule",
filename: `${mailNewsDir}/alias-7.json`,
to: "contact@domain1.example.com",
expectedMissing: false,
expectedAliasKeys: [
"D1A66E1A23B182C9980F788CFBFCC82A015E7330",
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
],
},
{
info: "Multiple rules, shouldn't match",
filename: `${mailNewsDir}/alias-7.json`,
to: "contact@domain2.example",
expectedMissing: true,
expectedAliasKeys: null,
},
];
/**
* Initialize OpenPGP add testing keys.
*/
add_task(async function setUp() {
do_get_profile();
await OpenPGPTestUtils.initOpenPGP();
await OpenPGPTestUtils.importPublicKey(
null,
do_get_file(`${keyDir}/alice@openpgp.example-0xf231550c4f47e38e-pub.asc`)
);
await OpenPGPTestUtils.importPublicKey(
null,
do_get_file(`${keyDir}/bob@openpgp.example-0xfbfcc82a015e7330-pub.asc`)
);
await OpenPGPTestUtils.importPublicKey(
null,
do_get_file(`${keyDir}/carol@example.com-0x3099ff1238852b9f-pub.asc`)
);
});
add_task(async function testAlias() {
let aliasFilename = "openpgp-alias-rules.json";
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
for (let test of tests) {
if (test.filename) {
info(`Running alias test with rules from: ${test.filename}`);
// Copy test file to profile directory (which is a relative path),
// because load function only works with simple filenames
// or absolute file URLs.
let inFile = do_get_file(test.filename);
inFile.copyTo(profileDir, aliasFilename);
await OpenPGPAlias._loadFromFile(aliasFilename);
} else {
info(`Running alias test without rules`);
OpenPGPAlias._clear();
}
info(test.info);
let addresses = [test.to];
let resultDetails = {};
let isMissing = await EnigmailKeyRing.getValidKeysForAllRecipients(
addresses,
resultDetails
);
Assert.ok(
(isMissing && test.expectedMissing) ||
(!isMissing && !test.expectedMissing),
"Should have the expected result from getValidKeysForAllRecipients"
);
if (isMissing || test.expectedMissing) {
continue;
}
let errorMsgObj = { value: "" };
let logFileObj = {};
let encryptArgs = EnigmailEncryption.getCryptParams(
"",
test.to,
"",
"SHA256",
EnigmailConstants.SEND_ENCRYPTED,
0,
errorMsgObj,
logFileObj
);
let foundAliasKeys = encryptArgs.aliasKeys.get(test.to);
if (!test.expectedAliasKeys) {
Assert.ok(!foundAliasKeys, "foundAliasKeys should be empty");
} else {
Assert.equal(foundAliasKeys.length, test.expectedAliasKeys.length);
test.expectedAliasKeys.forEach((val, i) => {
Assert.ok(foundAliasKeys.includes(val));
});
let encryptResult = {};
let encrypted = await RNP.encryptAndOrSign(
"plaintext",
encryptArgs,
encryptResult
);
Assert.ok(
!encryptResult.exitCode,
"RNP.encryptAndOrSign() should exit ok"
);
Assert.ok(encrypted.includes("END PGP MESSAGE"));
}
}
});

Просмотреть файл

@ -200,6 +200,7 @@ add_task(async function testEncryptAndOrSignResults() {
let encryptResult = {};
let encryptArgs = {
aliasKeys: new Map(),
armor: true,
bcc: [],
encrypt: true,

Просмотреть файл

@ -8,3 +8,4 @@ support-files =
[test_encryptAndOrSign.js]
[test_secretKeys.js]
[test_alias.js]

Просмотреть файл

@ -19,6 +19,7 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
OpenPGPAlias: "chrome://openpgp/content/modules/OpenPGPAlias.jsm",
EnigmailCore: "chrome://openpgp/content/modules/core.jsm",
EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.jsm",
EnigmailFiles: "chrome://openpgp/content/modules/files.jsm",
@ -69,6 +70,7 @@ const OpenPGPTestUtils = {
Assert.ok(await RNP.init(), "librnp did load");
Assert.ok(await EnigmailCore.getService({}), "EnigmailCore did load");
EnigmailKeyRing.init();
await OpenPGPAlias.load();
},
/**

Просмотреть файл

@ -0,0 +1,12 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"email": "alice@openpgp.example",
"keys": [
{ "id": "FBFCC82A015E7330" },
{ "fingerprint": "B8F2F6F4BD3AD3F82DC446833099FF1238852B9F" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"email": "alice@openpgp.example",
"keys": [
{ "fingerprint": "0123456789012345678901234567890123456789" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"email": "alice@openpgp.example",
"keys": [
{ "fingerprint": "EB85BB5FA33A75E15E944E63F231550C4F47E38E" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,13 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"email": "nobody@example.com",
"keys": [
{ "fingerprint": "EB85BB5FA33A75E15E944E63F231550C4F47E38E" },
{ "fingerprint": "D1A66E1A23B182C9980F788CFBFCC82A015E7330" },
{ "fingerprint": "B8F2F6F4BD3AD3F82DC446833099FF1238852B9F" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"email": "nobody@example.com",
"keys": [
{ "fingerprint": "EB85BB5FA33A75E15E944E63F231550C4F47E38E" },
{ "fingerprint": "D1A66E1A23B182C9980F788CFBFCC82A015E7330" },
{ "fingerprint": "B8F2F6F4BD3AD3F82DC446833099FF1238852B9F" },
{ "fingerprint": "0123456789012345678901234567890123456789" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"domain": "example.com",
"keys": [
{ "fingerprint": "B8F2F6F4BD3AD3F82DC446833099FF1238852B9F" }
]
}
]
}

Просмотреть файл

@ -0,0 +1,24 @@
{
"description": "OpenPGP alias rules",
"rules": [
{
"domain": "domain1.example.com",
"keys": [
{ "fingerprint": "EB85BB5FA33A75E15E944E63F231550C4F47E38E" }
]
},
{
"domain": "domain2.example.com",
"keys": [
{ "fingerprint": "D1A66E1A23B182C9980F788CFBFCC82A015E7330" }
]
},
{
"email": "contact@domain1.example.com",
"keys": [
{ "fingerprint": "D1A66E1A23B182C9980F788CFBFCC82A015E7330" },
{ "id": "F231550C4F47E38E" }
]
}
]
}