diff options
Diffstat (limited to 'comm/mail/extensions/openpgp/content/modules/encryption.jsm')
-rw-r--r-- | comm/mail/extensions/openpgp/content/modules/encryption.jsm | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/content/modules/encryption.jsm b/comm/mail/extensions/openpgp/content/modules/encryption.jsm new file mode 100644 index 0000000000..b02336bb91 --- /dev/null +++ b/comm/mail/extensions/openpgp/content/modules/encryption.jsm @@ -0,0 +1,564 @@ +/* + * 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 = ["EnigmailEncryption"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +XPCOMUtils.defineLazyModuleGetters(lazy, { + EnigmailConstants: "chrome://openpgp/content/modules/constants.jsm", + EnigmailCryptoAPI: "chrome://openpgp/content/modules/cryptoAPI.jsm", + EnigmailCore: "chrome://openpgp/content/modules/core.jsm", + EnigmailData: "chrome://openpgp/content/modules/data.jsm", + EnigmailDialog: "chrome://openpgp/content/modules/dialog.jsm", + EnigmailFuncs: "chrome://openpgp/content/modules/funcs.jsm", + EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.jsm", + EnigmailLog: "chrome://openpgp/content/modules/log.jsm", + PgpSqliteDb2: "chrome://openpgp/content/modules/sqliteDb.jsm", +}); + +XPCOMUtils.defineLazyGetter(lazy, "l10n", () => { + return new Localization(["messenger/openpgp/openpgp.ftl"], true); +}); + +const gMimeHashAlgorithms = [ + null, + "sha1", + "ripemd160", + "sha256", + "sha384", + "sha512", + "sha224", + "md5", +]; + +const ENC_TYPE_MSG = 0; +const ENC_TYPE_ATTACH_BINARY = 1; + +var EnigmailEncryption = { + // return object on success, null on failure + getCryptParams( + fromMailAddr, + toMailAddr, + bccMailAddr, + hashAlgorithm, + sendFlags, + isAscii, + errorMsgObj, + logFileObj + ) { + let result = {}; + result.sender = ""; + result.sign = false; + result.signatureHash = ""; + result.sigTypeClear = false; + result.sigTypeDetached = false; + result.encrypt = false; + result.encryptToSender = false; + result.armor = false; + result.senderKeyIsExternal = false; + + lazy.EnigmailLog.DEBUG( + "encryption.jsm: getCryptParams: hashAlgorithm=" + hashAlgorithm + "\n" + ); + + try { + fromMailAddr = lazy.EnigmailFuncs.stripEmail(fromMailAddr); + toMailAddr = lazy.EnigmailFuncs.stripEmail(toMailAddr); + bccMailAddr = lazy.EnigmailFuncs.stripEmail(bccMailAddr); + } catch (ex) { + errorMsgObj.value = lazy.l10n.formatValueSync("invalid-email"); + return null; + } + + var signMsg = sendFlags & lazy.EnigmailConstants.SEND_SIGNED; + var encryptMsg = sendFlags & lazy.EnigmailConstants.SEND_ENCRYPTED; + var usePgpMime = sendFlags & lazy.EnigmailConstants.SEND_PGP_MIME; + + if (sendFlags & lazy.EnigmailConstants.SEND_SENDER_KEY_EXTERNAL) { + result.senderKeyIsExternal = true; + } + + // Some day we might need to look at flag SEND_TWO_MIME_LAYERS here, + // to decide which detached signature flag needs to be passed on + // to the RNP or GPGME layers. However, today those layers can + // derive their necessary behavior from being asked to do combined + // or single encryption/signing. This is because today we always + // create signed messages using the detached signature, and we never + // need the OpenPGP signature encoding that includes the message + // except when combining GPG signing with RNP encryption. + + var detachedSig = + (usePgpMime || sendFlags & lazy.EnigmailConstants.SEND_ATTACHMENT) && + signMsg && + !encryptMsg; + + 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 + } + + if (result.bcc.length == 1 && result.bcc[0].length == 0) { + result.bcc.splice(0, 1); // remove the single empty entry + } + + if (/^0x[0-9a-f]+$/i.test(fromMailAddr)) { + result.sender = fromMailAddr; + } else { + result.sender = "<" + fromMailAddr + ">"; + } + result.sender = result.sender.replace(/(["'`])/g, "\\$1"); + + if (signMsg && hashAlgorithm) { + result.signatureHash = hashAlgorithm; + } + + if (encryptMsg) { + if (isAscii != ENC_TYPE_ATTACH_BINARY) { + result.armor = true; + } + result.encrypt = true; + + if (signMsg) { + result.sign = true; + } + + if ( + sendFlags & lazy.EnigmailConstants.SEND_ENCRYPT_TO_SELF && + fromMailAddr + ) { + result.encryptToSender = true; + } + + 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; + } + email = email.toLowerCase(); + 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 + ">"; + } + + let aliasKeyList = lazy.EnigmailKeyRing.getAliasKeyList(email); + if (aliasKeyList) { + // We have an alias definition. + + let aliasKeys = lazy.EnigmailKeyRing.getAliasKeys(aliasKeyList); + if (!aliasKeys.length) { + // An empty result means there was a failure obtaining the + // defined keys, this happens if at least one key is missing + // or unusable. + // We don't allow composing an email that involves a + // bad alias definition, return null to signal that + // sending should be aborted. + errorMsgObj.value = "bad alias definition for " + email; + return null; + } + + result.aliasKeys.set(email, aliasKeys); + } + } + } + } else if (detachedSig) { + result.sigTypeDetached = true; + result.sign = true; + + if (isAscii != ENC_TYPE_ATTACH_BINARY) { + result.armor = true; + } + } else if (signMsg) { + result.sigTypeClear = true; + result.sign = true; + } + + return result; + }, + + /** + * Determine why a given key cannot be used for signing. + * + * @param {string} keyId - key ID + * + * @returns {string} The reason(s) as message to display to the user, or + * an empty string in case the key is valid. + */ + determineInvSignReason(keyId) { + lazy.EnigmailLog.DEBUG( + "errorHandling.jsm: determineInvSignReason: keyId: " + keyId + "\n" + ); + + let key = lazy.EnigmailKeyRing.getKeyById(keyId); + if (!key) { + return lazy.l10n.formatValueSync("key-error-key-id-not-found", { + keySpec: keyId, + }); + } + let r = key.getSigningValidity(); + if (!r.keyValid) { + return r.reason; + } + + return ""; + }, + + /** + * Determine why a given key cannot be used for encryption. + * + * @param {string} keyId - key ID + * + * @returns {string} The reason(s) as message to display to the user, or + * an empty string in case the key is valid. + */ + determineInvRcptReason(keyId) { + lazy.EnigmailLog.DEBUG( + "errorHandling.jsm: determineInvRcptReason: keyId: " + keyId + "\n" + ); + + let key = lazy.EnigmailKeyRing.getKeyById(keyId); + if (!key) { + return lazy.l10n.formatValueSync("key-error-key-id-not-found", { + keySpec: keyId, + }); + } + let r = key.getEncryptionValidity(false); + if (!r.keyValid) { + return r.reason; + } + + return ""; + }, + + /** + * Determine if the sender key ID or user ID can be used for signing and/or + * encryption + * + * @param {integer} sendFlags - The send Flags; need to contain SEND_SIGNED and/or SEND_ENCRYPTED + * @param {string} fromKeyId - The sender key ID + * + * @returns {object} object + * - keyId: String - the found key ID, or null if fromMailAddr is not valid + * - errorMsg: String - the error message if key not valid, or null if key is valid + */ + async determineOwnKeyUsability(sendFlags, fromKeyId, isExternalGnuPG) { + lazy.EnigmailLog.DEBUG( + "encryption.jsm: determineOwnKeyUsability: sendFlags=" + + sendFlags + + ", sender=" + + fromKeyId + + "\n" + ); + + let foundKey = null; + let ret = { + errorMsg: null, + }; + + if (!fromKeyId) { + return ret; + } + + let sign = !!(sendFlags & lazy.EnigmailConstants.SEND_SIGNED); + let encrypt = !!(sendFlags & lazy.EnigmailConstants.SEND_ENCRYPTED); + + if (/^(0x)?[0-9a-f]+$/i.test(fromKeyId)) { + // key ID specified + foundKey = lazy.EnigmailKeyRing.getKeyById(fromKeyId); + } + + // even for isExternalGnuPG we require that the public key is available + if (!foundKey) { + ret.errorMsg = this.determineInvSignReason(fromKeyId); + return ret; + } + + if (!isExternalGnuPG && foundKey.secretAvailable) { + let isPersonal = await lazy.PgpSqliteDb2.isAcceptedAsPersonalKey( + foundKey.fpr + ); + if (!isPersonal) { + ret.errorMsg = lazy.l10n.formatValueSync( + "key-error-not-accepted-as-personal", + { + keySpec: fromKeyId, + } + ); + return ret; + } + } + + let canSign = false; + let canEncrypt = false; + + if (isExternalGnuPG) { + canSign = true; + } else if (sign && foundKey) { + let v = foundKey.getSigningValidity(); + if (v.keyValid) { + canSign = true; + } else { + // If we already have a reason for the key not being valid, + // use that as error message. + ret.errorMsg = v.reason; + } + } + + if (encrypt && foundKey) { + let v; + if (lazy.EnigmailKeyRing.isSubkeyId(fromKeyId)) { + // If the configured own key ID points to a subkey, check + // specifically that this subkey is a valid encryption key. + + let id = fromKeyId.replace(/^0x/, ""); + v = foundKey.getEncryptionValidity(false, null, id); + } else { + // Use parameter "false", because for isExternalGnuPG we cannot + // confirm that the user has the secret key. + // And for users of internal encryption code, we don't need to + // check that here either, public key is sufficient for encryption. + v = foundKey.getEncryptionValidity(false); + } + + if (v.keyValid) { + canEncrypt = true; + } else { + // If we already have a reason for the key not being valid, + // use that as error message. + ret.errorMsg = v.reason; + } + } + + if (sign && !canSign) { + if (!ret.errorMsg) { + // Only if we don't have an error message yet. + ret.errorMsg = this.determineInvSignReason(fromKeyId); + } + } else if (encrypt && !canEncrypt) { + if (!ret.errorMsg) { + // Only if we don't have an error message yet. + ret.errorMsg = this.determineInvRcptReason(fromKeyId); + } + } + + return ret; + }, + + // return 0 on success, non-zero on failure + encryptMessageStart( + win, + uiFlags, + fromMailAddr, + toMailAddr, + bccMailAddr, + hashAlgorithm, + sendFlags, + listener, + statusFlagsObj, + errorMsgObj + ) { + lazy.EnigmailLog.DEBUG( + "encryption.jsm: encryptMessageStart: uiFlags=" + + uiFlags + + ", from " + + fromMailAddr + + " to " + + toMailAddr + + ", hashAlgorithm=" + + hashAlgorithm + + " (" + + lazy.EnigmailData.bytesToHex(lazy.EnigmailData.pack(sendFlags, 4)) + + ")\n" + ); + + // This code used to call determineOwnKeyUsability, and return on + // failure. But now determineOwnKeyUsability is an async function, + // and calling it from here with await results in a deadlock. + // Instead we perform this check in Enigmail.msg.prepareSendMsg. + + var hashAlgo = + gMimeHashAlgorithms[ + Services.prefs.getIntPref("temp.openpgp.mimeHashAlgorithm") + ]; + + if (hashAlgorithm) { + hashAlgo = hashAlgorithm; + } + + errorMsgObj.value = ""; + + if (!sendFlags) { + lazy.EnigmailLog.DEBUG( + "encryption.jsm: encryptMessageStart: NO ENCRYPTION!\n" + ); + errorMsgObj.value = lazy.l10n.formatValueSync("not-required"); + return 0; + } + + if (!lazy.EnigmailCore.getService(win)) { + throw new Error( + "encryption.jsm: encryptMessageStart: not yet initialized" + ); + } + + let logFileObj = {}; + + let encryptArgs = EnigmailEncryption.getCryptParams( + fromMailAddr, + toMailAddr, + bccMailAddr, + hashAlgo, + sendFlags, + ENC_TYPE_MSG, + errorMsgObj, + logFileObj + ); + + if (!encryptArgs) { + return -1; + } + + if (!listener) { + throw new Error("unexpected no listener"); + } + + let resultStatus = {}; + const cApi = lazy.EnigmailCryptoAPI(); + let encrypted = cApi.sync( + cApi.encryptAndOrSign( + listener.getInputForCrypto(), + encryptArgs, + resultStatus + ) + ); + + if (resultStatus.exitCode) { + if (resultStatus.errorMsg.length) { + lazy.EnigmailDialog.alert(win, resultStatus.errorMsg); + } + } else if (encrypted) { + listener.addCryptoOutput(encrypted); + } + + if (resultStatus.exitCode === 0 && !listener.getCryptoOutputLength()) { + resultStatus.exitCode = -1; + } + return resultStatus.exitCode; + }, + + encryptMessage( + parent, + uiFlags, + plainText, + fromMailAddr, + toMailAddr, + bccMailAddr, + sendFlags, + exitCodeObj, + statusFlagsObj, + errorMsgObj + ) { + lazy.EnigmailLog.DEBUG( + "enigmail.js: Enigmail.encryptMessage: " + + plainText.length + + " bytes from " + + fromMailAddr + + " to " + + toMailAddr + + " (" + + sendFlags + + ")\n" + ); + throw new Error("Not implemented"); + + /* + exitCodeObj.value = -1; + statusFlagsObj.value = 0; + errorMsgObj.value = ""; + + if (!plainText) { + EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: NO ENCRYPTION!\n"); + exitCodeObj.value = 0; + EnigmailLog.DEBUG(" <=== encryptMessage()\n"); + return plainText; + } + + var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT; + var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED; + var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED; + + if (encryptMsg) { + // First convert all linebreaks to newlines + plainText = plainText.replace(/\r\n/g, "\n"); + plainText = plainText.replace(/\r/g, "\n"); + + // we need all data in CRLF according to RFC 4880 + plainText = plainText.replace(/\n/g, "\r\n"); + } + + var listener = EnigmailExecution.newSimpleListener( + function _stdin(pipe) { + pipe.write(plainText); + pipe.close(); + }, + function _done(exitCode) {}); + + + var proc = EnigmailEncryption.encryptMessageStart(parent, uiFlags, + fromMailAddr, toMailAddr, bccMailAddr, + null, sendFlags, + listener, statusFlagsObj, errorMsgObj); + if (!proc) { + exitCodeObj.value = -1; + EnigmailLog.DEBUG(" <=== encryptMessage()\n"); + return ""; + } + + // Wait for child pipes to close + proc.wait(); + + var retStatusObj = {}; + exitCodeObj.value = EnigmailEncryption.encryptMessageEnd(fromMailAddr, EnigmailData.getUnicodeData(listener.stderrData), listener.exitCode, + uiFlags, sendFlags, + listener.stdoutData.length, + retStatusObj); + + statusFlagsObj.value = retStatusObj.statusFlags; + statusFlagsObj.statusMsg = retStatusObj.statusMsg; + errorMsgObj.value = retStatusObj.errorMsg; + + + if ((exitCodeObj.value === 0) && listener.stdoutData.length === 0) + exitCodeObj.value = -1; + + if (exitCodeObj.value === 0) { + // Normal return + EnigmailLog.DEBUG(" <=== encryptMessage()\n"); + return EnigmailData.getUnicodeData(listener.stdoutData); + } + + // Error processing + EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: command execution exit code: " + exitCodeObj.value + "\n"); + return ""; + */ + }, +}; |