diff options
Diffstat (limited to 'comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm')
-rw-r--r-- | comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm b/comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm new file mode 100644 index 0000000000..c58181da37 --- /dev/null +++ b/comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm @@ -0,0 +1,584 @@ +/* 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 = ["GPGMELibLoader"]; + +const { ctypes } = ChromeUtils.importESModule( + "resource://gre/modules/ctypes.sys.mjs" +); + +var systemOS = Services.appinfo.OS.toLowerCase(); +var abi = ctypes.default_abi; + +// Default library paths to look for on macOS +const ADDITIONAL_LIB_PATHS = [ + "/usr/local/lib", + "/opt/local/lib", + "/opt/homebrew/lib", +]; + +// Open libgpgme. Determine the path to the chrome directory and look for it +// there first. If not, fallback to searching the standard locations. +var libgpgme, libgpgmePath; + +function tryLoadGPGME(name, suffix) { + let filename = ctypes.libraryName(name) + suffix; + let binPath = Services.dirsvc.get("XpcomLib", Ci.nsIFile).path; + let binDir = PathUtils.parent(binPath); + libgpgmePath = PathUtils.join(binDir, filename); + + let loadFromInfo; + + try { + loadFromInfo = libgpgmePath; + libgpgme = ctypes.open(libgpgmePath); + } catch (e) {} + + if (!libgpgme) { + try { + loadFromInfo = "system's standard library locations"; + // look in standard locations + libgpgmePath = filename; + libgpgme = ctypes.open(libgpgmePath); + } catch (e) {} + } + + if (!libgpgme && systemOS !== "winnt") { + // try specific additional directories + + for (let tryPath of ADDITIONAL_LIB_PATHS) { + try { + loadFromInfo = "additional standard locations"; + libgpgmePath = tryPath + "/" + filename; + libgpgme = ctypes.open(libgpgmePath); + + if (libgpgme) { + break; + } + } catch (e) {} + } + } + + if (libgpgme) { + console.debug( + "Successfully loaded optional OpenPGP library " + + filename + + " from " + + loadFromInfo + ); + } +} + +function loadExternalGPGMELib() { + if (!libgpgme) { + if (systemOS === "winnt") { + tryLoadGPGME("libgpgme6-11", ""); + + if (!libgpgme) { + tryLoadGPGME("libgpgme-11", ""); + } + + if (!libgpgme) { + tryLoadGPGME("gpgme-11", ""); + } + } + + if (!libgpgme) { + tryLoadGPGME("gpgme", ""); + } + + if (!libgpgme) { + tryLoadGPGME("gpgme", ".11"); + } + + if (!libgpgme) { + tryLoadGPGME("gpgme.11"); + } + } + + return !!libgpgme; +} + +var GPGMELibLoader = { + init() { + if (!loadExternalGPGMELib()) { + return null; + } + if (libgpgme) { + enableGPGMELibJS(); + } + return GPGMELib; + }, +}; + +const gpgme_error_t = ctypes.unsigned_int; +const gpgme_ctx_t = ctypes.void_t.ptr; +const gpgme_data_t = ctypes.void_t.ptr; +const gpgme_validity_t = ctypes.int; +const gpgme_keylist_mode_t = ctypes.unsigned_int; +const gpgme_protocol_t = ctypes.int; +const gpgme_pubkey_algo_t = ctypes.int; +const gpgme_sig_notation_flags_t = ctypes.unsigned_int; +const gpgme_export_mode_t = ctypes.unsigned_int; +const gpgme_decrypt_flags_t = ctypes.unsigned_int; +const gpgme_data_encoding_t = ctypes.unsigned_int; +const gpgme_sig_mode_t = ctypes.int; // it's an enum, risk of wrong type. + +let _gpgme_subkey = ctypes.StructType("_gpgme_subkey"); +_gpgme_subkey.define([ + { next: _gpgme_subkey.ptr }, + { bitfield: ctypes.unsigned_int }, + { pubkey_algo: gpgme_pubkey_algo_t }, + { length: ctypes.unsigned_int }, + { keyid: ctypes.char.ptr }, + { _keyid: ctypes.char.array(17) }, + { fpr: ctypes.char.ptr }, + { timestamp: ctypes.long }, + { expires: ctypes.long }, + { card_number: ctypes.char.ptr }, + { curve: ctypes.char.ptr }, + { keygrip: ctypes.char.ptr }, +]); +let gpgme_subkey_t = _gpgme_subkey.ptr; + +let _gpgme_sig_notation = ctypes.StructType("_gpgme_sig_notation"); +_gpgme_sig_notation.define([ + { next: _gpgme_sig_notation.ptr }, + { name: ctypes.char.ptr }, + { value: ctypes.char.ptr }, + { name_len: ctypes.int }, + { value_len: ctypes.int }, + { flags: gpgme_sig_notation_flags_t }, + { bitfield: ctypes.unsigned_int }, +]); +let gpgme_sig_notation_t = _gpgme_sig_notation.ptr; + +let _gpgme_key_sig = ctypes.StructType("_gpgme_key_sig"); +_gpgme_key_sig.define([ + { next: _gpgme_key_sig.ptr }, + { bitfield: ctypes.unsigned_int }, + { pubkey_algo: gpgme_pubkey_algo_t }, + { keyid: ctypes.char.ptr }, + { _keyid: ctypes.char.array(17) }, + { timestamp: ctypes.long }, + { expires: ctypes.long }, + { status: gpgme_error_t }, + { class_: ctypes.unsigned_int }, + { uid: ctypes.char.ptr }, + { name: ctypes.char.ptr }, + { email: ctypes.char.ptr }, + { comment: ctypes.char.ptr }, + { sig_class: ctypes.unsigned_int }, + { notations: gpgme_sig_notation_t }, + { last_notation: gpgme_sig_notation_t }, +]); +let gpgme_key_sig_t = _gpgme_key_sig.ptr; + +let _gpgme_tofu_info = ctypes.StructType("_gpgme_tofu_info"); +_gpgme_tofu_info.define([ + { next: _gpgme_tofu_info.ptr }, + { bitfield: ctypes.unsigned_int }, + { signcount: ctypes.unsigned_short }, + { encrcount: ctypes.unsigned_short }, + { signfirst: ctypes.unsigned_short }, + { signlast: ctypes.unsigned_short }, + { encrfirst: ctypes.unsigned_short }, + { encrlast: ctypes.unsigned_short }, + { description: ctypes.char.ptr }, +]); +let gpgme_tofu_info_t = _gpgme_tofu_info.ptr; + +let _gpgme_user_id = ctypes.StructType("_gpgme_user_id"); +_gpgme_user_id.define([ + { next: _gpgme_user_id.ptr }, + { bitfield: ctypes.unsigned_int }, + { validity: gpgme_validity_t }, + { uid: ctypes.char.ptr }, + { name: ctypes.char.ptr }, + { email: ctypes.char.ptr }, + { comment: ctypes.char.ptr }, + { signatures: gpgme_key_sig_t }, + { _last_keysig: gpgme_key_sig_t }, + { address: ctypes.char.ptr }, + { tofu: gpgme_tofu_info_t }, + { last_update: ctypes.unsigned_long }, +]); +let gpgme_user_id_t = _gpgme_user_id.ptr; + +let _gpgme_key = ctypes.StructType("gpgme_key_t", [ + { _refs: ctypes.unsigned_int }, + { bitfield: ctypes.unsigned_int }, + { protocol: gpgme_protocol_t }, + { issuer_serial: ctypes.char.ptr }, + { issuer_name: ctypes.char.ptr }, + { chain_id: ctypes.char.ptr }, + { owner_trust: gpgme_validity_t }, + { subkeys: gpgme_subkey_t }, + { uids: gpgme_user_id_t }, + { _last_subkey: gpgme_subkey_t }, + { _last_uid: gpgme_user_id_t }, + { keylist_mode: gpgme_keylist_mode_t }, + { fpr: ctypes.char.ptr }, + { last_update: ctypes.unsigned_long }, +]); +let gpgme_key_t = _gpgme_key.ptr; + +var GPGMELib; + +function enableGPGMELibJS() { + // this must be delayed until after "libgpgme" is initialized + + GPGMELib = { + path: libgpgmePath, + + init() { + // GPGME 1.9.0 released 2017-03-28 is the first version that + // supports GPGME_DECRYPT_UNWRAP, requiring >= gpg 2.1.12 + let versionPtr = this.gpgme_check_version("1.9.0"); + let version = versionPtr.readString(); + console.debug("gpgme version: " + version); + + let gpgExe = Services.prefs.getStringPref( + "mail.openpgp.alternative_gpg_path" + ); + if (!gpgExe) { + return true; + } + + let extResult = this.gpgme_set_engine_info( + this.GPGME_PROTOCOL_OpenPGP, + gpgExe, + null + ); + let success = extResult === this.GPG_ERR_NO_ERROR; + let info = success ? "success" : "failure: " + extResult; + console.debug( + "configuring GPGME to use an external OpenPGP engine " + + gpgExe + + " - " + + info + ); + return success; + }, + + /** + * Export key blocks from GnuPG that match the given paramters. + * + * @param {string} pattern - A pattern given to GnuPG for listing keys. + * @param {boolean} secret - If true, retrieve secret keys. + * If false, retrieve public keys. + * @param {function} keyFilterFunction - An optional test function that + * will be called for each candidate key that GnuPG lists for the + * given pattern. Allows the caller to decide whether a candidate + * key is wanted or not. Function will be called with a + * {gpgme_key_t} parameter, the candidate key returned by GPGME. + * + * @returns {Map} - a Map that contains ASCII armored key blocks + * indexed by fingerprint. + */ + exportKeys(pattern, secret = false, keyFilterFunction = undefined) { + let resultMap = new Map(); + let allFingerprints = []; + + let c1 = new gpgme_ctx_t(); + if (this.gpgme_new(c1.address())) { + throw new Error("gpgme_new failed"); + } + + if (this.gpgme_op_keylist_start(c1, pattern, secret ? 1 : 0)) { + throw new Error("gpgme_op_keylist_start failed"); + } + + do { + let key = new gpgme_key_t(); + let rv = this.gpgme_op_keylist_next(c1, key.address()); + if (rv & GPGMELib.GPG_ERR_EOF) { + break; + } else if (rv) { + throw new Error("gpgme_op_keylist_next failed: " + rv); + } + + if (key.contents.protocol == GPGMELib.GPGME_PROTOCOL_OpenPGP) { + if (!keyFilterFunction || keyFilterFunction(key)) { + let fpr = key.contents.fpr.readString(); + allFingerprints.push(fpr); + } + } + this.gpgme_key_release(key); + } while (true); + + if (this.gpgme_op_keylist_end(c1)) { + throw new Error("gpgme_op_keylist_end failed"); + } + + this.gpgme_release(c1); + + for (let aFpr of allFingerprints) { + let c2 = new gpgme_ctx_t(); + if (this.gpgme_new(c2.address())) { + throw new Error("gpgme_new failed"); + } + + this.gpgme_set_armor(c2, 1); + + let data = new gpgme_data_t(); + let rv = this.gpgme_data_new(data.address()); + if (rv) { + throw new Error("gpgme_op_keylist_next gpgme_data_new: " + rv); + } + + rv = this.gpgme_op_export( + c2, + aFpr, + secret ? GPGMELib.GPGME_EXPORT_MODE_SECRET : 0, + data + ); + if (rv) { + throw new Error("gpgme_op_export gpgme_data_new: " + rv); + } + + let result_len = new ctypes.size_t(); + let result_buf = this.gpgme_data_release_and_get_mem( + data, + result_len.address() + ); + + let keyData = ctypes.cast( + result_buf, + ctypes.char.array(result_len.value).ptr + ).contents; + + resultMap.set(aFpr, keyData.readString()); + + this.gpgme_free(result_buf); + this.gpgme_release(c2); + } + return resultMap; + }, + + gpgme_check_version: libgpgme.declare( + "gpgme_check_version", + abi, + ctypes.char.ptr, + ctypes.char.ptr + ), + + gpgme_set_engine_info: libgpgme.declare( + "gpgme_set_engine_info", + abi, + gpgme_error_t, + gpgme_protocol_t, + ctypes.char.ptr, + ctypes.char.ptr + ), + + gpgme_new: libgpgme.declare("gpgme_new", abi, gpgme_error_t, gpgme_ctx_t), + + gpgme_release: libgpgme.declare( + "gpgme_release", + abi, + ctypes.void_t, + gpgme_ctx_t + ), + + gpgme_key_release: libgpgme.declare( + "gpgme_key_release", + abi, + ctypes.void_t, + gpgme_key_t + ), + + gpgme_op_keylist_start: libgpgme.declare( + "gpgme_op_keylist_start", + abi, + gpgme_error_t, + gpgme_ctx_t, + ctypes.char.ptr, + ctypes.int + ), + + gpgme_op_keylist_next: libgpgme.declare( + "gpgme_op_keylist_next", + abi, + gpgme_error_t, + gpgme_ctx_t, + gpgme_key_t.ptr + ), + + gpgme_op_keylist_end: libgpgme.declare( + "gpgme_op_keylist_end", + abi, + gpgme_error_t, + gpgme_ctx_t + ), + + gpgme_op_export: libgpgme.declare( + "gpgme_op_export", + abi, + gpgme_error_t, + gpgme_ctx_t, + ctypes.char.ptr, + gpgme_export_mode_t, + gpgme_data_t + ), + + gpgme_set_armor: libgpgme.declare( + "gpgme_set_armor", + abi, + ctypes.void_t, + gpgme_ctx_t, + ctypes.int + ), + + gpgme_data_new: libgpgme.declare( + "gpgme_data_new", + abi, + gpgme_error_t, + gpgme_data_t.ptr + ), + + gpgme_data_release: libgpgme.declare( + "gpgme_data_release", + abi, + ctypes.void_t, + gpgme_data_t + ), + + gpgme_data_release_and_get_mem: libgpgme.declare( + "gpgme_data_release_and_get_mem", + abi, + ctypes.char.ptr, + gpgme_data_t, + ctypes.size_t.ptr + ), + + gpgme_free: libgpgme.declare( + "gpgme_free", + abi, + ctypes.void_t, + ctypes.void_t.ptr + ), + + gpgme_op_decrypt_ext: libgpgme.declare( + "gpgme_op_decrypt_ext", + abi, + gpgme_error_t, + gpgme_ctx_t, + gpgme_decrypt_flags_t, + gpgme_data_t, + gpgme_data_t + ), + + gpgme_data_new_from_mem: libgpgme.declare( + "gpgme_data_new_from_mem", + abi, + gpgme_error_t, + gpgme_data_t.ptr, + ctypes.char.ptr, + ctypes.size_t, + ctypes.int + ), + + gpgme_data_read: libgpgme.declare( + "gpgme_data_read", + abi, + ctypes.ssize_t, + gpgme_data_t, + ctypes.void_t.ptr, + ctypes.size_t + ), + + gpgme_data_rewind: libgpgme.declare( + "gpgme_data_rewind", + abi, + gpgme_error_t, + gpgme_data_t + ), + + gpgme_data_get_encoding: libgpgme.declare( + "gpgme_data_get_encoding", + abi, + gpgme_data_encoding_t, + gpgme_data_t + ), + + gpgme_data_set_encoding: libgpgme.declare( + "gpgme_data_set_encoding", + abi, + gpgme_error_t, + gpgme_data_t, + gpgme_data_encoding_t + ), + + gpgme_op_sign: libgpgme.declare( + "gpgme_op_sign", + abi, + gpgme_error_t, + gpgme_ctx_t, + gpgme_data_t, + gpgme_data_t, + gpgme_sig_mode_t + ), + + gpgme_signers_add: libgpgme.declare( + "gpgme_signers_add", + abi, + gpgme_error_t, + gpgme_ctx_t, + gpgme_key_t + ), + + gpgme_get_key: libgpgme.declare( + "gpgme_get_key", + abi, + gpgme_error_t, + gpgme_ctx_t, + ctypes.char.ptr, + gpgme_key_t.ptr, + ctypes.int + ), + + gpgme_set_textmode: libgpgme.declare( + "gpgme_set_textmode", + abi, + ctypes.void_t, + gpgme_ctx_t, + ctypes.int + ), + + gpgme_error_t, + gpgme_ctx_t, + gpgme_data_t, + gpgme_validity_t, + gpgme_keylist_mode_t, + gpgme_pubkey_algo_t, + gpgme_sig_notation_flags_t, + gpgme_export_mode_t, + gpgme_decrypt_flags_t, + gpgme_data_encoding_t, + + gpgme_protocol_t, + gpgme_subkey_t, + gpgme_sig_notation_t, + gpgme_key_sig_t, + gpgme_tofu_info_t, + gpgme_user_id_t, + gpgme_key_t, + + GPG_ERR_NO_ERROR: 0x00000000, + GPG_ERR_EOF: 16383, + GPGME_PROTOCOL_OpenPGP: 0, + GPGME_EXPORT_MODE_SECRET: 16, + GPGME_DECRYPT_UNWRAP: 128, + GPGME_DATA_ENCODING_ARMOR: 3, + GPGME_SIG_MODE_DETACH: 1, + GPGME_SIG_MODE_NORMAL: 0, + + gpgme_key_t_revoked: 1, + gpgme_key_t_expired: 2, + gpgme_key_t_disabled: 4, + gpgme_key_t_invalid: 8, + gpgme_key_t_can_encrypt: 16, + }; +} |