From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../openpgp/content/modules/keyLookupHelper.jsm | 380 +++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 comm/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm (limited to 'comm/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm') diff --git a/comm/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm b/comm/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm new file mode 100644 index 0000000000..621b61b2ae --- /dev/null +++ b/comm/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm @@ -0,0 +1,380 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +"use strict"; + +const EXPORTED_SYMBOLS = ["KeyLookupHelper"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +XPCOMUtils.defineLazyModuleGetters(lazy, { + CollectedKeysDB: "chrome://openpgp/content/modules/CollectedKeysDB.jsm", + EnigmailDialog: "chrome://openpgp/content/modules/dialog.jsm", + EnigmailKey: "chrome://openpgp/content/modules/key.jsm", + EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.jsm", + EnigmailKeyServer: "chrome://openpgp/content/modules/keyserver.jsm", + EnigmailKeyserverURIs: "chrome://openpgp/content/modules/keyserverUris.jsm", + EnigmailWkdLookup: "chrome://openpgp/content/modules/wkdLookup.jsm", +}); + +XPCOMUtils.defineLazyGetter(lazy, "l10n", () => { + return new Localization(["messenger/openpgp/openpgp.ftl"], true); +}); + +var KeyLookupHelper = { + /** + * Internal helper function, search for keys by either keyID + * or email address on a keyserver. + * Returns additional flags regarding lookup and import. + * Will never show feedback prompts. + * + * @param {string} mode - "interactive-import" or "silent-collection" + * In interactive-import mode, the user will be asked to confirm + * import of keys into the permanent keyring. + * In silent-collection mode, only updates to existing keys will + * be imported. New keys will only be added to CollectedKeysDB. + * @param {nsIWindow} window - parent window + * @param {string} identifier - search value, either key ID or fingerprint or email address. + * @returns {object} flags + * @returns {boolean} flags.keyImported - At least one key was imported. + * @returns {boolean} flags.foundUpdated - At least one update for a local existing key was found and imported. + * @returns {boolean} flags.foundUnchanged - All found keys are identical to already existing local keys. + * @returns {boolean} flags.collectedForLater - At least one key was added to CollectedKeysDB. + */ + + isExpiredOrRevoked(keyTrust) { + return keyTrust.match(/e/i) || keyTrust.match(/r/i); + }, + + async _lookupAndImportOnKeyserver(mode, window, identifier) { + let keyImported = false; + let foundUpdated = false; + let foundUnchanged = false; + let collectedForLater = false; + + let ksArray = lazy.EnigmailKeyserverURIs.getKeyServers(); + if (!ksArray.length) { + return false; + } + + let continueSearching = true; + for (let ks of ksArray) { + let foundKey; + if (ks.startsWith("vks://")) { + foundKey = await lazy.EnigmailKeyServer.downloadNoImport( + identifier, + ks + ); + } else if (ks.startsWith("hkp://") || ks.startsWith("hkps://")) { + foundKey = + await lazy.EnigmailKeyServer.searchAndDownloadSingleResultNoImport( + identifier, + ks + ); + } + if (foundKey && "keyData" in foundKey) { + let errorInfo = {}; + let keyList = await lazy.EnigmailKey.getKeyListFromKeyBlock( + foundKey.keyData, + errorInfo, + false, + true, + false + ); + // We might get a zero length keyList, if we refuse to use the key + // that we received because of its properties. + if (keyList && keyList.length == 1) { + let oldKey = lazy.EnigmailKeyRing.getKeyById(keyList[0].fpr); + if (oldKey) { + await lazy.EnigmailKeyRing.importKeyDataSilent( + window, + foundKey.keyData, + true, + "0x" + keyList[0].fpr + ); + + let updatedKey = lazy.EnigmailKeyRing.getKeyById(keyList[0].fpr); + // If new imported/merged key is equal to old key, + // don't notify about new keys details. + if (JSON.stringify(oldKey) !== JSON.stringify(updatedKey)) { + foundUpdated = true; + keyImported = true; + if (mode == "interactive-import") { + lazy.EnigmailDialog.keyImportDlg( + window, + keyList.map(a => a.id) + ); + } + } else { + foundUnchanged = true; + } + } else { + keyList = keyList.filter(k => k.userIds.length); + keyList = keyList.filter(k => !this.isExpiredOrRevoked(k.keyTrust)); + if (keyList.length && mode == "interactive-import") { + keyImported = + await lazy.EnigmailKeyRing.importKeyDataWithConfirmation( + window, + keyList, + foundKey.keyData, + true + ); + if (keyImported) { + // In interactive mode, don't offer the user to import keys multiple times. + // When silently collecting keys, it's fine to discover everything we can. + continueSearching = false; + } + } + if (!keyImported) { + collectedForLater = true; + let db = await lazy.CollectedKeysDB.getInstance(); + for (let newKey of keyList) { + // If key is known in the db: merge + update. + let key = await db.mergeExisting(newKey, foundKey.keyData, { + uri: lazy.EnigmailKeyServer.serverReqURL( + `0x${newKey.fpr}`, + ks + ), + type: "keyserver", + }); + await db.storeKey(key); + } + } + } + } else { + if (keyList && keyList.length > 1) { + throw new Error("Unexpected multiple results from keyserver " + ks); + } + console.log( + "failed to process data retrieved from keyserver " + + ks + + ": " + + errorInfo.value + ); + } + } + if (!continueSearching) { + break; + } + } + + return { keyImported, foundUpdated, foundUnchanged, collectedForLater }; + }, + + /** + * Search online for keys by key ID on keyserver. + * + * @param {string} mode - "interactive-import" or "silent-collection" + * In interactive-import mode, the user will be asked to confirm + * import of keys into the permanent keyring. + * In silent-collection mode, only updates to existing keys will + * be imported. New keys will only be added to CollectedKeysDB. + * @param {nsIWindow} window - parent window + * @param {string} keyId - the key ID to search for. + * @param {boolean} giveFeedbackToUser - false to be silent, + * true to show feedback to user after search and import is complete. + * @returns {boolean} - true if at least one key was imported. + */ + async lookupAndImportByKeyID(mode, window, keyId, giveFeedbackToUser) { + if (!/^0x/i.test(keyId)) { + keyId = "0x" + keyId; + } + let importResult = await this._lookupAndImportOnKeyserver( + mode, + window, + keyId + ); + if ( + mode == "interactive-import" && + giveFeedbackToUser && + !importResult.keyImported + ) { + let msgId; + if (importResult.foundUnchanged) { + msgId = "no-update-found"; + } else { + msgId = "no-key-found2"; + } + let value = await lazy.l10n.formatValue(msgId); + lazy.EnigmailDialog.alert(window, value); + } + return importResult.keyImported; + }, + + /** + * Search online for keys by email address. + * Will search both WKD and keyserver. + * + * @param {string} mode - "interactive-import" or "silent-collection" + * In interactive-import mode, the user will be asked to confirm + * import of keys into the permanent keyring. + * In silent-collection mode, only updates to existing keys will + * be imported. New keys will only be added to CollectedKeysDB. + * @param {nsIWindow} window - parent window + * @param {string} email - the email address to search for. + * @param {boolean} giveFeedbackToUser - false to be silent, + * true to show feedback to user after search and import is complete. + * @returns {boolean} - true if at least one key was imported. + */ + async lookupAndImportByEmail(mode, window, email, giveFeedbackToUser) { + let resultKeyImported = false; + + let wkdKeyImported = false; + let wkdFoundUnchanged = false; + + let wkdResult; + let wkdUrl; + if (lazy.EnigmailWkdLookup.isWkdAvailable(email)) { + wkdUrl = await lazy.EnigmailWkdLookup.getDownloadUrlFromEmail( + email, + true + ); + wkdResult = await lazy.EnigmailWkdLookup.downloadKey(wkdUrl); + if (!wkdResult) { + wkdUrl = await lazy.EnigmailWkdLookup.getDownloadUrlFromEmail( + email, + false + ); + wkdResult = await lazy.EnigmailWkdLookup.downloadKey(wkdUrl); + } + } + + if (!wkdResult) { + console.debug("searchKeysOnInternet no wkd data for " + email); + } else { + let errorInfo = {}; + let keyList = await lazy.EnigmailKey.getKeyListFromKeyBlock( + wkdResult, + errorInfo, + false, + true, + false, + true + ); + if (!keyList) { + console.debug( + "failed to process data retrieved from WKD server: " + errorInfo.value + ); + } else { + let existingKeys = []; + let newKeys = []; + + for (let wkdKey of keyList) { + let oldKey = lazy.EnigmailKeyRing.getKeyById(wkdKey.fpr); + if (oldKey) { + await lazy.EnigmailKeyRing.importKeyDataSilent( + window, + wkdKey.pubKey, + true, + "0x" + wkdKey.fpr + ); + + let updatedKey = lazy.EnigmailKeyRing.getKeyById(wkdKey.fpr); + // If new imported/merged key is equal to old key, + // don't notify about new keys details. + if (JSON.stringify(oldKey) !== JSON.stringify(updatedKey)) { + // If a caller ever needs information what we found, + // this is the place to set: wkdFoundUpdated = true + existingKeys.push(wkdKey.id); + } else { + wkdFoundUnchanged = true; + } + } else if (wkdKey.userIds.length) { + newKeys.push(wkdKey); + } + } + + if (existingKeys.length) { + if (mode == "interactive-import") { + lazy.EnigmailDialog.keyImportDlg(window, existingKeys); + } + wkdKeyImported = true; + } + + newKeys = newKeys.filter(k => !this.isExpiredOrRevoked(k.keyTrust)); + if (newKeys.length && mode == "interactive-import") { + wkdKeyImported = + wkdKeyImported || + (await lazy.EnigmailKeyRing.importKeyArrayWithConfirmation( + window, + newKeys, + true + )); + } + if (!wkdKeyImported) { + // If a caller ever needs information what we found, + // this is the place to set: wkdCollectedForLater = true + let db = await lazy.CollectedKeysDB.getInstance(); + for (let newKey of newKeys) { + // If key is known in the db: merge + update. + let key = await db.mergeExisting(newKey, newKey.pubKey, { + uri: wkdUrl, + type: "wkd", + }); + await db.storeKey(key); + } + } + } + } + + let { keyImported, foundUnchanged } = + await this._lookupAndImportOnKeyserver(mode, window, email); + resultKeyImported = wkdKeyImported || keyImported; + + if ( + mode == "interactive-import" && + giveFeedbackToUser && + !resultKeyImported && + !keyImported + ) { + let msgId; + if (wkdFoundUnchanged || foundUnchanged) { + msgId = "no-update-found"; + } else { + msgId = "no-key-found2"; + } + let value = await lazy.l10n.formatValue(msgId); + lazy.EnigmailDialog.alert(window, value); + } + + return resultKeyImported; + }, + + /** + * This function will perform discovery of new or updated OpenPGP + * keys using various mechanisms. + * + * @param {string} mode - "interactive-import" or "silent-collection" + * @param {string} email - search for keys for this email address, + * (parameter allowed to be null or empty) + * @param {string[]} keyIds - KeyIDs that should be updated. + * (parameter allowed to be null or empty) + * + * @returns {boolean} - Returns true if at least one key was imported. + */ + async fullOnlineDiscovery(mode, window, email, keyIds) { + // Try to get updates for all existing keys from keyserver, + // by key ID, to get updated validy/revocation info. + // (A revoked key on the keyserver might have no user ID.) + let atLeastoneImport = false; + if (keyIds) { + for (let keyId of keyIds) { + // Ensure the function call goes first in the logic or expression, + // to ensure it's always called, even if atLeastoneImport is already true. + let rv = await this.lookupAndImportByKeyID(mode, window, keyId, false); + atLeastoneImport = rv || atLeastoneImport; + } + } + // Now check for updated or new keys by email address + let rv2 = await this.lookupAndImportByEmail(mode, window, email, false); + atLeastoneImport = rv2 || atLeastoneImport; + return atLeastoneImport; + }, +}; -- cgit v1.2.3