1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5const EXPORTED_SYMBOLS = ["GPGMELibLoader"];
6
7const { XPCOMUtils } = ChromeUtils.import(
8  "resource://gre/modules/XPCOMUtils.jsm"
9);
10
11XPCOMUtils.defineLazyModuleGetters(this, {
12  ctypes: "resource://gre/modules/ctypes.jsm",
13  Services: "resource://gre/modules/Services.jsm",
14});
15
16var systemOS = Services.appinfo.OS.toLowerCase();
17var abi = ctypes.default_abi;
18
19// Default libary paths to look for on macOS
20const ADDITIONAL_LIB_PATHS = [
21  "/usr/local/lib",
22  "/opt/local/lib",
23  "/opt/homebrew/lib",
24];
25
26// Open libgpgme. Determine the path to the chrome directory and look for it
27// there first. If not, fallback to searching the standard locations.
28var libgpgme, libgpgmePath;
29
30function tryLoadGPGME(name, suffix) {
31  let filename = ctypes.libraryName(name) + suffix;
32  let binPath = Services.dirsvc.get("XpcomLib", Ci.nsIFile).path;
33  let binDir = PathUtils.parent(binPath);
34  libgpgmePath = PathUtils.join(binDir, filename);
35
36  let loadFromInfo;
37
38  try {
39    loadFromInfo = libgpgmePath;
40    libgpgme = ctypes.open(libgpgmePath);
41  } catch (e) {}
42
43  if (!libgpgme) {
44    try {
45      loadFromInfo = "system's standard library locations";
46      // look in standard locations
47      libgpgmePath = filename;
48      libgpgme = ctypes.open(libgpgmePath);
49    } catch (e) {}
50  }
51
52  if (!libgpgme && systemOS !== "winnt") {
53    // try specific additional directories
54
55    for (let tryPath of ADDITIONAL_LIB_PATHS) {
56      try {
57        loadFromInfo = "additional standard locations";
58        libgpgmePath = tryPath + "/" + filename;
59        libgpgme = ctypes.open(libgpgmePath);
60
61        if (libgpgme) {
62          break;
63        }
64      } catch (e) {}
65    }
66  }
67
68  if (libgpgme) {
69    console.debug(
70      "Successfully loaded optional OpenPGP library " +
71        filename +
72        " from " +
73        loadFromInfo
74    );
75  }
76}
77
78function loadExternalGPGMELib() {
79  if (!libgpgme) {
80    if (systemOS === "winnt") {
81      tryLoadGPGME("libgpgme-11", "");
82
83      if (!libgpgme) {
84        tryLoadGPGME("gpgme-11", "");
85      }
86    }
87
88    if (!libgpgme) {
89      tryLoadGPGME("gpgme", "");
90    }
91
92    if (!libgpgme) {
93      tryLoadGPGME("gpgme", ".11");
94    }
95
96    if (!libgpgme) {
97      tryLoadGPGME("gpgme.11");
98    }
99  }
100
101  return !!libgpgme;
102}
103
104var GPGMELibLoader = {
105  init() {
106    if (!loadExternalGPGMELib()) {
107      return null;
108    }
109    if (libgpgme) {
110      enableGPGMELibJS();
111    }
112    return GPGMELib;
113  },
114};
115
116const gpgme_error_t = ctypes.unsigned_int;
117const gpgme_ctx_t = ctypes.void_t.ptr;
118const gpgme_data_t = ctypes.void_t.ptr;
119const gpgme_validity_t = ctypes.int;
120const gpgme_keylist_mode_t = ctypes.unsigned_int;
121const gpgme_protocol_t = ctypes.int;
122const gpgme_pubkey_algo_t = ctypes.int;
123const gpgme_sig_notation_flags_t = ctypes.unsigned_int;
124const gpgme_export_mode_t = ctypes.unsigned_int;
125const gpgme_decrypt_flags_t = ctypes.unsigned_int;
126const gpgme_data_encoding_t = ctypes.unsigned_int;
127const gpgme_sig_mode_t = ctypes.int; // it's an enum, risk of wrong type.
128
129let _gpgme_subkey = ctypes.StructType("_gpgme_subkey");
130_gpgme_subkey.define([
131  { next: _gpgme_subkey.ptr },
132  { bitfield: ctypes.unsigned_int },
133  { pubkey_algo: gpgme_pubkey_algo_t },
134  { length: ctypes.unsigned_int },
135  { keyid: ctypes.char.ptr },
136  { _keyid: ctypes.char.array(17) },
137  { fpr: ctypes.char.ptr },
138  { timestamp: ctypes.long },
139  { expires: ctypes.long },
140  { card_number: ctypes.char.ptr },
141  { curve: ctypes.char.ptr },
142  { keygrip: ctypes.char.ptr },
143]);
144let gpgme_subkey_t = _gpgme_subkey.ptr;
145
146let _gpgme_sig_notation = ctypes.StructType("_gpgme_sig_notation");
147_gpgme_sig_notation.define([
148  { next: _gpgme_sig_notation.ptr },
149  { name: ctypes.char.ptr },
150  { value: ctypes.char.ptr },
151  { name_len: ctypes.int },
152  { value_len: ctypes.int },
153  { flags: gpgme_sig_notation_flags_t },
154  { bitfield: ctypes.unsigned_int },
155]);
156let gpgme_sig_notation_t = _gpgme_sig_notation.ptr;
157
158let _gpgme_key_sig = ctypes.StructType("_gpgme_key_sig");
159_gpgme_key_sig.define([
160  { next: _gpgme_key_sig.ptr },
161  { bitfield: ctypes.unsigned_int },
162  { pubkey_algo: gpgme_pubkey_algo_t },
163  { keyid: ctypes.char.ptr },
164  { _keyid: ctypes.char.array(17) },
165  { timestamp: ctypes.long },
166  { expires: ctypes.long },
167  { status: gpgme_error_t },
168  { class_: ctypes.unsigned_int },
169  { uid: ctypes.char.ptr },
170  { name: ctypes.char.ptr },
171  { email: ctypes.char.ptr },
172  { comment: ctypes.char.ptr },
173  { sig_class: ctypes.unsigned_int },
174  { notations: gpgme_sig_notation_t },
175  { last_notation: gpgme_sig_notation_t },
176]);
177let gpgme_key_sig_t = _gpgme_key_sig.ptr;
178
179let _gpgme_tofu_info = ctypes.StructType("_gpgme_tofu_info");
180_gpgme_tofu_info.define([
181  { next: _gpgme_tofu_info.ptr },
182  { bitfield: ctypes.unsigned_int },
183  { signcount: ctypes.unsigned_short },
184  { encrcount: ctypes.unsigned_short },
185  { signfirst: ctypes.unsigned_short },
186  { signlast: ctypes.unsigned_short },
187  { encrfirst: ctypes.unsigned_short },
188  { encrlast: ctypes.unsigned_short },
189  { description: ctypes.char.ptr },
190]);
191let gpgme_tofu_info_t = _gpgme_tofu_info.ptr;
192
193let _gpgme_user_id = ctypes.StructType("_gpgme_user_id");
194_gpgme_user_id.define([
195  { next: _gpgme_user_id.ptr },
196  { bitfield: ctypes.unsigned_int },
197  { validity: gpgme_validity_t },
198  { uid: ctypes.char.ptr },
199  { name: ctypes.char.ptr },
200  { email: ctypes.char.ptr },
201  { comment: ctypes.char.ptr },
202  { signatures: gpgme_key_sig_t },
203  { _last_keysig: gpgme_key_sig_t },
204  { address: ctypes.char.ptr },
205  { tofu: gpgme_tofu_info_t },
206  { last_update: ctypes.unsigned_long },
207]);
208let gpgme_user_id_t = _gpgme_user_id.ptr;
209
210let _gpgme_key = ctypes.StructType("gpgme_key_t", [
211  { _refs: ctypes.unsigned_int },
212  { bitfield: ctypes.unsigned_int },
213  { protocol: gpgme_protocol_t },
214  { issuer_serial: ctypes.char.ptr },
215  { issuer_name: ctypes.char.ptr },
216  { chain_id: ctypes.char.ptr },
217  { owner_trust: gpgme_validity_t },
218  { subkeys: gpgme_subkey_t },
219  { uids: gpgme_user_id_t },
220  { _last_subkey: gpgme_subkey_t },
221  { _last_uid: gpgme_user_id_t },
222  { keylist_mode: gpgme_keylist_mode_t },
223  { fpr: ctypes.char.ptr },
224  { last_update: ctypes.unsigned_long },
225]);
226let gpgme_key_t = _gpgme_key.ptr;
227
228var GPGMELib;
229
230function enableGPGMELibJS() {
231  // this must be delayed until after "libgpgme" is initialized
232
233  GPGMELib = {
234    path: libgpgmePath,
235
236    init() {
237      // GPGME 1.9.0 released 2017-03-28 is the first version that
238      // supports GPGME_DECRYPT_UNWRAP, requiring >= gpg 2.1.12
239      let versionPtr = this.gpgme_check_version("1.9.0");
240      let version = versionPtr.readString();
241      console.debug("gpgme version: " + version);
242
243      let gpgExe = Services.prefs.getStringPref(
244        "mail.openpgp.alternative_gpg_path"
245      );
246      if (!gpgExe) {
247        return true;
248      }
249
250      let extResult = this.gpgme_set_engine_info(
251        this.GPGME_PROTOCOL_OpenPGP,
252        gpgExe,
253        null
254      );
255      let success = extResult === this.GPG_ERR_NO_ERROR;
256      let info = success ? "success" : "failure: " + extResult;
257      console.debug(
258        "configuring GPGME to use an external OpenPGP engine " +
259          gpgExe +
260          " - " +
261          info
262      );
263      return success;
264    },
265
266    exportKeys(pattern, secret = false) {
267      let resultArray = [];
268      let allFingerprints = [];
269
270      let c1 = new gpgme_ctx_t();
271      if (this.gpgme_new(c1.address())) {
272        throw new Error("gpgme_new failed");
273      }
274
275      if (this.gpgme_op_keylist_start(c1, pattern, secret ? 1 : 0)) {
276        throw new Error("gpgme_op_keylist_start failed");
277      }
278
279      do {
280        let key = new gpgme_key_t();
281        let rv = this.gpgme_op_keylist_next(c1, key.address());
282        if (rv & GPGMELib.GPG_ERR_EOF) {
283          break;
284        } else if (rv) {
285          throw new Error("gpgme_op_keylist_next failed: " + rv);
286        }
287
288        if (key.contents.protocol == GPGMELib.GPGME_PROTOCOL_OpenPGP) {
289          let fpr = key.contents.fpr.readString();
290          allFingerprints.push(fpr);
291        }
292        this.gpgme_key_release(key);
293      } while (true);
294
295      if (this.gpgme_op_keylist_end(c1)) {
296        throw new Error("gpgme_op_keylist_end failed");
297      }
298
299      this.gpgme_release(c1);
300
301      for (let aFpr of allFingerprints) {
302        let c2 = new gpgme_ctx_t();
303        if (this.gpgme_new(c2.address())) {
304          throw new Error("gpgme_new failed");
305        }
306
307        this.gpgme_set_armor(c2, 1);
308
309        let data = new gpgme_data_t();
310        let rv = this.gpgme_data_new(data.address());
311        if (rv) {
312          throw new Error("gpgme_op_keylist_next gpgme_data_new: " + rv);
313        }
314
315        rv = this.gpgme_op_export(
316          c2,
317          aFpr,
318          secret ? GPGMELib.GPGME_EXPORT_MODE_SECRET : 0,
319          data
320        );
321        if (rv) {
322          throw new Error("gpgme_op_export gpgme_data_new: " + rv);
323        }
324
325        let result_len = new ctypes.size_t();
326        let result_buf = this.gpgme_data_release_and_get_mem(
327          data,
328          result_len.address()
329        );
330
331        let keyData = ctypes.cast(
332          result_buf,
333          ctypes.char.array(result_len.value).ptr
334        ).contents;
335
336        resultArray.push(keyData.readString());
337
338        this.gpgme_free(result_buf);
339        this.gpgme_release(c2);
340      }
341      return resultArray;
342    },
343
344    gpgme_check_version: libgpgme.declare(
345      "gpgme_check_version",
346      abi,
347      ctypes.char.ptr,
348      ctypes.char.ptr
349    ),
350
351    gpgme_set_engine_info: libgpgme.declare(
352      "gpgme_set_engine_info",
353      abi,
354      gpgme_error_t,
355      gpgme_protocol_t,
356      ctypes.char.ptr,
357      ctypes.char.ptr
358    ),
359
360    gpgme_new: libgpgme.declare("gpgme_new", abi, gpgme_error_t, gpgme_ctx_t),
361
362    gpgme_release: libgpgme.declare(
363      "gpgme_release",
364      abi,
365      ctypes.void_t,
366      gpgme_ctx_t
367    ),
368
369    gpgme_key_release: libgpgme.declare(
370      "gpgme_key_release",
371      abi,
372      ctypes.void_t,
373      gpgme_key_t
374    ),
375
376    gpgme_op_keylist_start: libgpgme.declare(
377      "gpgme_op_keylist_start",
378      abi,
379      gpgme_error_t,
380      gpgme_ctx_t,
381      ctypes.char.ptr,
382      ctypes.int
383    ),
384
385    gpgme_op_keylist_next: libgpgme.declare(
386      "gpgme_op_keylist_next",
387      abi,
388      gpgme_error_t,
389      gpgme_ctx_t,
390      gpgme_key_t.ptr
391    ),
392
393    gpgme_op_keylist_end: libgpgme.declare(
394      "gpgme_op_keylist_end",
395      abi,
396      gpgme_error_t,
397      gpgme_ctx_t
398    ),
399
400    gpgme_op_export: libgpgme.declare(
401      "gpgme_op_export",
402      abi,
403      gpgme_error_t,
404      gpgme_ctx_t,
405      ctypes.char.ptr,
406      gpgme_export_mode_t,
407      gpgme_data_t
408    ),
409
410    gpgme_set_armor: libgpgme.declare(
411      "gpgme_set_armor",
412      abi,
413      ctypes.void_t,
414      gpgme_ctx_t,
415      ctypes.int
416    ),
417
418    gpgme_data_new: libgpgme.declare(
419      "gpgme_data_new",
420      abi,
421      gpgme_error_t,
422      gpgme_data_t.ptr
423    ),
424
425    gpgme_data_release: libgpgme.declare(
426      "gpgme_data_release",
427      abi,
428      ctypes.void_t,
429      gpgme_data_t
430    ),
431
432    gpgme_data_release_and_get_mem: libgpgme.declare(
433      "gpgme_data_release_and_get_mem",
434      abi,
435      ctypes.char.ptr,
436      gpgme_data_t,
437      ctypes.size_t.ptr
438    ),
439
440    gpgme_free: libgpgme.declare(
441      "gpgme_free",
442      abi,
443      ctypes.void_t,
444      ctypes.void_t.ptr
445    ),
446
447    gpgme_op_decrypt_ext: libgpgme.declare(
448      "gpgme_op_decrypt_ext",
449      abi,
450      gpgme_error_t,
451      gpgme_ctx_t,
452      gpgme_decrypt_flags_t,
453      gpgme_data_t,
454      gpgme_data_t
455    ),
456
457    gpgme_data_new_from_mem: libgpgme.declare(
458      "gpgme_data_new_from_mem",
459      abi,
460      gpgme_error_t,
461      gpgme_data_t.ptr,
462      ctypes.char.ptr,
463      ctypes.size_t,
464      ctypes.int
465    ),
466
467    gpgme_data_read: libgpgme.declare(
468      "gpgme_data_read",
469      abi,
470      ctypes.ssize_t,
471      gpgme_data_t,
472      ctypes.void_t.ptr,
473      ctypes.size_t
474    ),
475
476    gpgme_data_rewind: libgpgme.declare(
477      "gpgme_data_rewind",
478      abi,
479      gpgme_error_t,
480      gpgme_data_t
481    ),
482
483    gpgme_data_get_encoding: libgpgme.declare(
484      "gpgme_data_get_encoding",
485      abi,
486      gpgme_data_encoding_t,
487      gpgme_data_t
488    ),
489
490    gpgme_data_set_encoding: libgpgme.declare(
491      "gpgme_data_set_encoding",
492      abi,
493      gpgme_error_t,
494      gpgme_data_t,
495      gpgme_data_encoding_t
496    ),
497
498    gpgme_op_sign: libgpgme.declare(
499      "gpgme_op_sign",
500      abi,
501      gpgme_error_t,
502      gpgme_ctx_t,
503      gpgme_data_t,
504      gpgme_data_t,
505      gpgme_sig_mode_t
506    ),
507
508    gpgme_signers_add: libgpgme.declare(
509      "gpgme_signers_add",
510      abi,
511      gpgme_error_t,
512      gpgme_ctx_t,
513      gpgme_key_t
514    ),
515
516    gpgme_get_key: libgpgme.declare(
517      "gpgme_get_key",
518      abi,
519      gpgme_error_t,
520      gpgme_ctx_t,
521      ctypes.char.ptr,
522      gpgme_key_t.ptr,
523      ctypes.int
524    ),
525
526    gpgme_set_textmode: libgpgme.declare(
527      "gpgme_set_textmode",
528      abi,
529      ctypes.void_t,
530      gpgme_ctx_t,
531      ctypes.int
532    ),
533
534    gpgme_error_t,
535    gpgme_ctx_t,
536    gpgme_data_t,
537    gpgme_validity_t,
538    gpgme_keylist_mode_t,
539    gpgme_pubkey_algo_t,
540    gpgme_sig_notation_flags_t,
541    gpgme_export_mode_t,
542    gpgme_decrypt_flags_t,
543    gpgme_data_encoding_t,
544
545    gpgme_protocol_t,
546    gpgme_subkey_t,
547    gpgme_sig_notation_t,
548    gpgme_key_sig_t,
549    gpgme_tofu_info_t,
550    gpgme_user_id_t,
551    gpgme_key_t,
552
553    GPG_ERR_NO_ERROR: 0x00000000,
554    GPG_ERR_EOF: 16383,
555    GPGME_PROTOCOL_OpenPGP: 0,
556    GPGME_EXPORT_MODE_SECRET: 16,
557    GPGME_DECRYPT_UNWRAP: 128,
558    GPGME_DATA_ENCODING_ARMOR: 3,
559    GPGME_SIG_MODE_DETACH: 1,
560  };
561}
562