summaryrefslogtreecommitdiffstats
path: root/comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm')
-rw-r--r--comm/mail/extensions/openpgp/content/modules/GPGMELib.jsm584
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,
+ };
+}