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