diff --git a/package/key.jsm b/package/key.jsm index d07b1e4b..edea6b14 100644 --- a/package/key.jsm +++ b/package/key.jsm @@ -1,420 +1,256 @@ /*global Components: false, Math: false */ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailKey"]; const Cu = Components.utils; const KEY_BLOCK_UNKNOWN = 0; const KEY_BLOCK_KEY = 1; const KEY_BLOCK_REVOCATION = 2; +const SIG_TYPE_REVOCATION = 0x20; Cu.import("resource://enigmail/log.jsm"); /*global EnigmailLog: false */ Cu.import("resource://enigmail/armor.jsm"); /*global EnigmailArmor: false */ Cu.import("resource://enigmail/locale.jsm"); /*global EnigmailLocale: false */ Cu.import("resource://enigmail/files.jsm"); /*global EnigmailFiles: false */ Cu.import("resource://enigmail/gpg.jsm"); /*global EnigmailGpg: false */ Cu.import("resource://enigmail/execution.jsm"); /*global EnigmailExecution: false */ +Cu.import("resource://enigmail/openpgp.jsm"); /*global EnigmailOpenPGP: false */ Cu.import("resource://enigmail/lazy.jsm"); /*global EnigmailLazy: false */ const getKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing"); const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog"); -function KeyEntry(key) { - if (!(this instanceof KeyEntry)) { - return new KeyEntry(key); - } - // same data as in packetlist but in structured form - this.primaryKey = null; - this.revocationSignature = null; - this.directSignatures = null; - this.users = null; - this.subKeys = null; - this.packetlist2structure(this.parsePackets(key)); - if (!this.primaryKey || !this.users) { - throw new Error('Invalid key: need at least key and user ID packet'); - } - return this; -} - -KeyEntry.prototype = { - parsePackets: function(key) { - const packetHeaders = [":public key packet:", - ":user ID packet:", - ":public sub key packet:", - ":secret sub key packet:", - ":signature packet:", - ":secret key packet:" - ]; - var _packets = []; - - function extractPackets(line) { - var is_packet_hr = false; - packetHeaders.forEach( - function(packet) { - if (line.search(packet) > -1) { - is_packet_hr = true; - var obj = { - tag: packet, - value: "" - }; - _packets.push(obj); - } - }); - if (!is_packet_hr) { - var obj = _packets.pop(); - obj.value += line + "\n"; - _packets.push(obj); - } - } - var lines = key.split("\n"); - for (var i in lines) { - if (!lines[i].startsWith("gpg:")) extractPackets(lines[i]); - } - return _packets; - }, - - packetlist2structure: function(packetlist) { - for (var i = 0; i < packetlist.length; i++) { - var user, subKey; - - switch (packetlist[i].tag) { - case ":secret key packet:": - this.primaryKey = packetlist[i]; - break; - case ":user ID packet:": - if (!this.users) this.users = []; - user = packetlist[i]; - this.users.push(user); - break; - case ":public sub key packet:": - case ":secret sub key packet:": - user = null; - if (!this.subKeys) this.subKeys = []; - subKey = packetlist[i]; - this.subKeys.push(subKey); - break; - case ":signature packet:": - break; - } - } - } -}; - var EnigmailKey = { - Entry: KeyEntry, - /** * Format a key fingerprint * @fingerprint |string| - unformated OpenPGP fingerprint * * @return |string| - formatted string */ formatFpr: function(fingerprint) { //EnigmailLog.DEBUG("key.jsm: EnigmailKey.formatFpr(" + fingerprint + ")\n"); // format key fingerprint let r = ""; const fpr = fingerprint.match(/(....)(....)(....)(....)(....)(....)(....)(....)(....)?(....)?/); if (fpr && fpr.length > 2) { fpr.shift(); r = fpr.join(" "); } return r; }, // Extract public key from Status Message extractPubkey: function(statusMsg) { const matchb = statusMsg.match(/(^|\n)NO_PUBKEY (\w{8})(\w{8})/); if (matchb && (matchb.length > 3)) { EnigmailLog.DEBUG("enigmailCommon.jsm:: Enigmail.extractPubkey: NO_PUBKEY 0x" + matchb[3] + "\n"); return matchb[2] + matchb[3]; } else { return null; } }, /** * import a revocation certificate form a given keyblock string. * Ask the user before importing the cert, and display an error * message in case of failures. */ - importRevocationCert: function(keyBlockStr, packetStr) { - let keyId; - let m = packetStr.match(/(:signature packet: algo [0-9]+, keyid )([0-9A-Z]+)/i); - if (m && m.length > 2) { - keyId = m[2]; - - let key = getKeyRing().getKeyById(keyId); - - if (key) { - if (key.keyTrust === "r") { - // Key has already been revoked - getDialog().alert(null, EnigmailLocale.getString("revokeKeyAlreadyRevoked", keyId)); - } - else { - - let userId = key.userId + " - 0x" + key.keyId; - if (!getDialog().confirmDlg(null, - EnigmailLocale.getString("revokeKeyQuestion", userId), - EnigmailLocale.getString("keyMan.button.revokeKey"))) { - return; - } - - let errorMsgObj = {}; - if (getKeyRing().importKey(null, false, keyBlockStr, keyId, errorMsgObj) > 0) { - getDialog().alert(null, errorMsgObj.value); - } - } + importRevocationCert: function(keyId, keyBlockStr) { + + let key = getKeyRing().getKeyById(keyId); + + if (key) { + if (key.keyTrust === "r") { + // Key has already been revoked + getDialog().info(null, EnigmailLocale.getString("revokeKeyAlreadyRevoked", keyId)); } else { - // Suitable key for revocation certificate is not present in keyring - getDialog().alert(null, EnigmailLocale.getString("revokeKeyNotPresent", keyId)); + + let userId = key.userId + " - 0x" + key.keyId; + if (!getDialog().confirmDlg(null, + EnigmailLocale.getString("revokeKeyQuestion", userId), + EnigmailLocale.getString("keyMan.button.revokeKey"))) { + return; + } + + let errorMsgObj = {}; + if (getKeyRing().importKey(null, false, keyBlockStr, keyId, errorMsgObj) > 0) { + getDialog().alert(null, errorMsgObj.value); + } } } + else { + // Suitable key for revocation certificate is not present in keyring + getDialog().alert(null, EnigmailLocale.getString("revokeKeyNotPresent", keyId)); + } }, - /** - * determine the type of the contents in a given string - * @param keyBlockStr: String - input string - * - * @return: Object: - * - keyType - Number: - * 0 - no key data - * 1 - public and/or secret key(s) - * 2 - revocation certificate - * - packetStr - String: the packet list as received from GnuPG + * Split armored blocks into an array of strings */ - getKeyFileType: function(keyBlockStr) { - let args = EnigmailGpg.getStandardArgs(true).concat(["--no-verbose", - "--list-packets" - ]); - const exitCodeObj = {}; - const statusMsgObj = {}; - const errorMsgObj = {}; - - let packetStr = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, keyBlockStr, exitCodeObj, {}, statusMsgObj, errorMsgObj); - - if (packetStr.search(/^:(public|secret) key packet:/m) >= 0) { - return { - keyType: KEY_BLOCK_KEY, - packetStr: packetStr - }; + splitArmoredBlocks: function(keyBlockStr) { + let myRe = /-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----/g; + let myArray; + let retArr = []; + let startIndex = -1; + while ((myArray = myRe.exec(keyBlockStr)) !== null) { + if (startIndex >= 0) { + let s = keyBlockStr.substring(startIndex, myArray.index); + retArr.push(s); + } + startIndex = myArray.index; } - // simple detection of revocation certificate - // TODO: improve algorithm - let i = packetStr.search(/^:signature packet:/m); - if (i >= 0) { - if (packetStr.search(/sigclass 0x20/) > i) - return { - keyType: KEY_BLOCK_REVOCATION, - packetStr: packetStr - }; - } + retArr.push(keyBlockStr.substring(startIndex)); - return { - keyType: KEY_BLOCK_UNKNOWN, - packetStr: packetStr - }; + return retArr; }, /** * Get details (key ID, UID) of the data contained in a OpenPGP key block * * @param keyBlockStr String: the contents of one or more public keys * @param errorMsgObj Object: obj.value will contain an error message in case of failures * * @return Array of objects with the following structure: * - id (key ID) * - name (the UID of the key) - * - state (one of "old" [existing key], "new" [new key], "invalid" [key could not be imported]) + * - state (one of "old" [existing key], "new" [new key], "invalid" [key cannot not be imported]) */ getKeyListFromKeyBlock: function(keyBlockStr, errorMsgObj) { EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock\n"); - var ret = []; - let keyTypeObj = this.getKeyFileType(keyBlockStr); + let blocks; + let isBinary = false; - if (keyTypeObj.keyType === KEY_BLOCK_UNKNOWN) { - errorMsgObj.value = EnigmailLocale.getString("notFirstBlock"); - return ret; - } + errorMsgObj.value = ""; - if (keyTypeObj.keyType === KEY_BLOCK_REVOCATION) { - this.importRevocationCert(keyBlockStr, keyTypeObj.packetStr); - errorMsgObj.value = ""; - return ret; + if (keyBlockStr.search(/-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----/) >= 0) { + blocks = this.splitArmoredBlocks(keyBlockStr); + } + else { + isBinary = true; + blocks = [EnigmailOpenPGP.enigmailFuncs.bytesToArmor(EnigmailOpenPGP.enums.armor.public_key, keyBlockStr)]; } - const tempDir = EnigmailFiles.createTempSubDir("enigmail_import", true); - const tempPath = EnigmailFiles.getFilePath(tempDir); - const args = EnigmailGpg.getStandardArgs(true).concat([ - "--import", - "--trustdb", tempPath + "/trustdb", - "--no-default-keyring", "--keyring", tempPath + "/keyring" - ]); - - const exitCodeObj = {}; - const statusMsgObj = {}; - - EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, keyBlockStr, exitCodeObj, {}, statusMsgObj, errorMsgObj); - - const statusMsg = statusMsgObj.value; - - tempDir.remove(true); - - var state = "newOrResult"; - var lines = statusMsg.split("\n"); - var idx = 0; - var cur = {}; - - while (state != "end") { - - // Ignore all irrelevant lines - while (lines[idx].search(/^(IMPORTED|IMPORT_OK|IMPORT_RES|IMPORT_PROBLEM) /) < 0 && - idx < lines.length) { - EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock: Ignoring line: '" + lines[idx] + "'\n"); - ++idx; - } - - if (idx >= lines.length) { - errorMsgObj.value = EnigmailLocale.getString("cantImport"); - return []; - } + let keyList = []; + let key = {}; + for (let b of blocks) { + let m = EnigmailOpenPGP.message.readArmored(b); + + for (let i = 0; i < m.packets.length; i++) { + let packetType = EnigmailOpenPGP.enums.read(EnigmailOpenPGP.enums.packet, m.packets[i].tag); + switch (packetType) { + case "publicKey": + case "secretKey": + key = { + id: m.packets[i].getKeyId().toHex().toUpperCase(), + fpr: m.packets[i].getFingerprint().toUpperCase(), + name: null, + isSecret: false + }; - EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock: state: '" + state + "', line: '" + lines[idx] + "'\n"); - - switch (state) { - case "newOrResult": - { - const imported = lines[idx].match(/^IMPORTED (\w+) (.+)/); - if (imported && (imported.length > 2)) { - EnigmailLog.DEBUG("new imported: " + imported[1] + " (" + imported[2] + ")\n"); - state = "summary"; - cur.id = imported[1]; - cur.name = imported[2]; - cur.state = "new"; - idx += 1; - break; + if (!(key.id in keyList)) { + keyList[key.id] = key; } - const import_res = lines[idx].match(/^IMPORT_RES ([0-9 ]+)/); - if (import_res && (import_res.length > 1)) { - EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock: import result: " + import_res[1] + "\n"); - state = "end"; + if (packetType === "secretKey") { + keyList[key.id].isSecret = true; } - else { - state = "summary"; + break; + case "userid": + if (!key.name) { + key.name = m.packets[i].userid; } - break; - } - - case "summary": - { - const import_ok = lines[idx].match(/^IMPORT_OK (\d+) (\w+)/); - if (import_ok && (import_ok.length > 2)) { - EnigmailLog.DEBUG("import ok: " + import_ok[1] + " (" + import_ok[2] + ")\n"); - - state = "newOrResult"; - if (!(import_ok[1] === "16" || import_ok[1] === "0")) { // skip unchanged and private keys - cur.fingerprint = import_ok[2]; - - if (cur.state === undefined) { - cur.state = "old"; - } - - ret.push(cur); - cur = {}; + case "signature": + if (m.packets[i].signatureType === SIG_TYPE_REVOCATION) { + let keyId = m.packets[i].issuerKeyId.toHex().toUpperCase(); + if (keyId in keyList) { + keyList[keyId].revoke = true; } - idx += 1; - break; - } - - const import_err = lines[idx].match(/^IMPORT_PROBLEM (\d+) (\w+)/); - if (import_err && (import_err.length > 2)) { - EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock: import err: " + import_err[1] + " (" + import_err[2] + ")\n"); - state = "newOrResult"; - cur.fingerprint = import_err[2]; - - if (cur.state === undefined) { - cur.state = "invalid"; + else { + keyList[keyId] = { + revoke: true, + id: keyId + }; } - - ret.push(cur); - cur = {}; - idx += 1; - break; } + break; + } + } + } - errorMsgObj.value = EnigmailLocale.getString("cantImport"); - return []; - } - default: - { - EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock: skip line '" + lines[idx] + "'\n"); - idx += 1; - break; - } + let retArr = []; + for (let k in keyList) { + retArr.push(keyList[k]); + } + + if (retArr.length === 1) { + key = retArr[0]; + if (("revoke" in key) && (!("name" in key))) { + this.importRevocationCert(key.id, blocks.join("\n")); + errorMsgObj.value = ""; + return []; } } - errorMsgObj.value = ""; - return ret; + return retArr; }, /** * Get details of a key block to import. Works identically as getKeyListFromKeyBlock(); * except that the input is a file instead of a string * * @param file nsIFile object - file to read * @param errorMsgObj Object - obj.value will contain error message * * @return Array (same as for getKeyListFromKeyBlock()) */ getKeyListFromKeyFile: function(path, errorMsgObj) { var contents = EnigmailFiles.readFile(path); return this.getKeyListFromKeyBlock(contents, errorMsgObj); }, /** * Compare 2 KeyIds of possible different length (short, long, FPR-length, with or without prefixed * 0x are accepted) * * @param keyId1 string * @param keyId2 string * * @return true or false, given the comparison of the last minimum-length characters. */ compareKeyIds: function(keyId1, keyId2) { var keyId1Raw = keyId1.replace(/^0x/, "").toUpperCase(); var keyId2Raw = keyId2.replace(/^0x/, "").toUpperCase(); var minlength = Math.min(keyId1Raw.length, keyId2Raw.length); if (minlength < keyId1Raw.length) { // Limit keyId1 to minlength keyId1Raw = keyId1Raw.substr(-minlength, minlength); } if (minlength < keyId2Raw.length) { // Limit keyId2 to minlength keyId2Raw = keyId2Raw.substr(-minlength, minlength); } return (keyId1Raw === keyId2Raw); } }; diff --git a/package/keyEditor.jsm b/package/keyEditor.jsm index 107bcef1..d476956d 100644 --- a/package/keyEditor.jsm +++ b/package/keyEditor.jsm @@ -1,1388 +1,1382 @@ /*global Components: false */ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailKeyEditor"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://enigmail/core.jsm"); /*global EnigmailCore: false */ Cu.import("resource://enigmail/key.jsm"); /*global EnigmailKey: false */ Cu.import("resource://enigmail/log.jsm"); /*global EnigmailLog: false */ Cu.import("resource://enigmail/os.jsm"); /*global EnigmailOS: false */ Cu.import("resource://enigmail/files.jsm"); /*global EnigmailFiles: false */ Cu.import("resource://enigmail/locale.jsm"); /*global EnigmailLocale: false */ Cu.import("resource://enigmail/data.jsm"); /*global EnigmailData: false */ Cu.import("resource://enigmail/execution.jsm"); /*global EnigmailExecution: false */ Cu.import("resource://enigmail/gpgAgent.jsm"); /*global EnigmailGpgAgent: false */ Cu.import("resource://enigmail/gpg.jsm"); /*global EnigmailGpg: false */ Cu.import("resource://enigmail/keyRing.jsm"); /*global EnigmailKeyRing: false */ Cu.import("resource://enigmail/errorHandling.jsm"); /*global EnigmailErrorHandling: false */ const GET_BOOL = "GET_BOOL"; const GET_LINE = "GET_LINE"; const GET_HIDDEN = "GET_HIDDEN"; const NS_PROMPTSERVICE_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1"; function GpgEditorInterface(reqObserver, callbackFunc, inputData) { this._reqObserver = reqObserver; this._callbackFunc = callbackFunc; this._inputData = inputData; if (this._inputData && this._inputData.cardAdmin) { this._saveCmd = "quit"; } else this._saveCmd = "save"; } GpgEditorInterface.prototype = { _stdin: null, _data: "", _txt: "", _exitCode: 0, errorMsg: "", setStdin: function(pipe) { this._stdin = pipe; if (this._data.length > 0) this.processData(); }, gotData: function(data) { //EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.gotData: '"+data+"'\n"); this._data += data.replace(/\r\n/g, "\n"); this.processData(); }, processData: function() { //EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.processData\n"); var txt = ""; while (this._data.length > 0 && this._stdin) { var index = this._data.indexOf("\n"); if (index < 0) { txt = this._data; this._data = ""; } else { txt = this._data.substr(0, index); this._data = this._data.substr(index + 1); } this.nextLine(txt); } }, closeStdin: function() { EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.closeStdin:\n"); if (this._stdin) { this._stdin.close(); this._stdin = null; } }, done: function(parentCallback, exitCode) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.done: exitCode=" + exitCode + "\n"); if (exitCode === 0) exitCode = this._exitCode; EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.done: returning exitCode " + exitCode + "\n"); parentCallback(exitCode, this.errorMsg); }, writeLine: function(inputData) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.writeLine: '" + inputData + "'\n"); this._stdin.write(inputData + "\n"); }, nextLine: function(txt) { if (txt.indexOf("[GNUPG:]") >= 0) { if (this._reqObserver) { var newTxt = this._reqObserver.onDataAvailable(txt); if (newTxt.length > 0) { txt = newTxt; } } this._txt = txt; this.processLine(txt); } }, doCheck: function(inputType, promptVal) { var a = this._txt.split(/ /); return ((a[1] == inputType) && (a[2] == promptVal)); }, getText: function() { return this._txt; }, handleGpgError: function(lineTxt) { let retStatusObj = {}; EnigmailErrorHandling.parseErrorOutput(lineTxt, retStatusObj); return retStatusObj; }, processLine: function(txt) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: '" + txt + "'\n"); var r = { quitNow: false, exitCode: -1 }; try { if (txt.indexOf("[GNUPG:] BAD_PASSPHRASE") >= 0 || txt.indexOf("[GNUPG:] SC_OP_FAILURE 2") >= 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected bad passphrase\n"); r.exitCode = -2; r.quitNow = true; this.errorMsg = EnigmailLocale.getString("badPhrase"); } else if (txt.indexOf("[GNUPG:] ERROR ") >= 0 || txt.indexOf("[GNUPG:] FAILURE ") >= 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected GnuPG ERROR message\n"); let statusObj = this.handleGpgError(txt); if (statusObj.statusFlags & Ci.nsIEnigmail.DISPLAY_MESSAGE) { this.errorMsg = statusObj.statusMsg; r.exitCode = -3; r.quitNow = true; } } else if (txt.indexOf("[GNUPG:] NO_CARD_AVAILABLE") >= 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected missing card\n"); this.errorMsg = EnigmailLocale.getString("sc.noCardAvailable"); r.exitCode = -3; r.quitNow = true; } else if (txt.indexOf("[GNUPG:] ENIGMAIL_FAILURE") === 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected general failure\n"); r.exitCode = -3; r.quitNow = true; this.errorMsg = txt.substr(26); } else if (txt.indexOf("[GNUPG:] ALREADY_SIGNED") >= 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected key already signed\n"); this.errorMsg = EnigmailLocale.getString("keyAlreadySigned"); r.exitCode = -1; r.quitNow = true; } else if (txt.indexOf("[GNUPG:] MISSING_PASSPHRASE") >= 0) { EnigmailLog.DEBUG("keyManagmenent.jsm: GpgEditorInterface.processLine: detected missing passphrase\n"); this.errorMsg = EnigmailLocale.getString("noPassphrase"); r.exitCode = -2; this._exitCode = -2; r.quitNow = true; } else if (txt.indexOf("[GNUPG:] GET_") < 0) { // return if no "GET" statement return; } } catch (ex) { txt = ""; r.quitNow = true; } if (!r.quitNow) { if (txt.indexOf("[GNUPG:] GOT_IT") < 0) { if (this._callbackFunc) { this._callbackFunc(this._inputData, this, r); if (r.exitCode === 0) { this.writeLine(r.writeTxt); } else { if (r.errorMsg && r.errorMsg.length > 0) this.errorMsg = r.errorMsg; } } else { r.quitNow = true; r.exitCode = 0; } } else { r.exitCode = 0; } } if (r.quitNow) { try { this.writeLine(this._saveCmd); this.closeStdin(); } catch (ex) { EnigmailLog.DEBUG("no more data\n"); } } if (r.exitCode !== null) this._exitCode = r.exitCode; }, QueryInterface: function(iid) { if (!iid.equals(Ci.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; function editKey(parent, needPassphrase, userId, keyId, editCmd, inputData, callbackFunc, requestObserver, parentCallback) { EnigmailLog.DEBUG("keyManagmenent.jsm: editKey: parent=" + parent + ", editCmd=" + editCmd + "\n"); if (!EnigmailCore.getService(parent)) { EnigmailLog.ERROR("keyManagmenent.jsm: Enigmail.editKey: not yet initialized\n"); parentCallback(-1, EnigmailLocale.getString("notInit")); return -1; } var keyIdList = keyId.split(" "); var args = EnigmailGpg.getStandardArgs(false); var statusFlags = {}; args = args.concat(["--no-tty", "--status-fd", "1", "--logger-fd", "1", "--command-fd", "0"]); if (userId) args = args.concat(["-u", userId]); var editCmdArr; if (typeof(editCmd) == "string") { editCmdArr = [editCmd]; } else { editCmdArr = editCmd; } if (editCmdArr[0] == "revoke") { // escape backslashes and ' characters args = args.concat(["-a", "-o"]); args.push(EnigmailFiles.getEscapedFilename(inputData.outFile.path)); args.push("--gen-revoke"); args = args.concat(keyIdList); } else if (editCmdArr[0].indexOf("--") === 0) { args = args.concat(editCmd); args = args.concat(keyIdList); } else { args = args.concat(["--ask-cert-level", "--edit-key", keyId]); args = args.concat(editCmd); } var command = EnigmailGpgAgent.agentPath; EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n"); var keyEdit = new GpgEditorInterface(requestObserver, callbackFunc, inputData); try { EnigmailExecution.execCmd2(command, args, keyEdit.setStdin.bind(keyEdit), keyEdit.gotData.bind(keyEdit), function(result) { keyEdit.done(parentCallback, result.exitCode); } ); } catch (ex) { EnigmailLog.ERROR("keyEditor.jsm: editKey: " + command.path + " failed\n"); parentCallback(-1, ""); } return null; } /* * NOTE: the callbackFunc used in every call to the key editor needs to be implemented like this: * callbackFunc(returnCode, errorMsg) * returnCode = 0 in case of success * returnCode != 0 and errorMsg set in case of failure */ const EnigmailKeyEditor = { setKeyTrust: function(parent, keyId, trustLevel, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.setKeyTrust: trustLevel=" + trustLevel + ", keyId=" + keyId + "\n"); return editKey(parent, false, null, keyId, "trust", { trustLevel: trustLevel }, keyTrustCallback, null, callbackFunc); }, /** * Call editKey() to set the expiration date of the chosen key and subkeys * * @param Object parent * @param String keyId e.g. 8D18EB22FDF633A2 * @param Array subKeys List of Integer values, e.g. [0,1,3] * "0" should allways be set because it's the main key. * @param Integer expiryLength A number between 1 and 100 * @param Integer timeScale 1 or 30 or 365 meaning days, months, years * @param Boolean noExpiry True: Expire never. False: Use expiryLength. * @param Function callbackFunc will be executed by editKey() * @return Integer * returnCode = 0 in case of success * returnCode != 0 and errorMsg set in case of failure */ setKeyExpiration: function(parent, keyId, subKeys, expiryLength, timeScale, noExpiry, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.setKeyExpiry: keyId=" + keyId + "\n"); expiryLength = String(expiryLength); if (noExpiry === true) { expiryLength = "0"; } else { switch (parseInt(timeScale, 10)) { case 365: expiryLength += "y"; break; case 30: expiryLength += "m"; break; case 7: expiryLength += "w"; break; } } return editKey(parent, true, null, keyId, "", /* "expire", */ { expiryLength: expiryLength, subKeys: subKeys, currentSubKey: false }, keyExpiryCallback, /* contains the gpg communication logic */ null, callbackFunc); }, signKey: function(parent, userId, keyId, signLocally, trustLevel, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.signKey: trustLevel=" + trustLevel + ", userId=" + userId + ", keyId=" + keyId + "\n"); return editKey(parent, true, userId, keyId, (signLocally ? "lsign" : "sign"), { trustLevel: trustLevel, usePassphrase: true }, signKeyCallback, null, callbackFunc); }, genRevokeCert: function(parent, keyId, outFile, reasonCode, reasonText, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.genRevokeCert: keyId=" + keyId + "\n"); /** * GnuPG < 2.1 does not properly report failures; * therefore we check if the revokation certificate was really generated */ function checkGeneratedCert(exitCode, errorMsg) { if (!outFile.exists()) { exitCode = 1; errorMsg = ""; } callbackFunc(exitCode, errorMsg); } return editKey(parent, true, null, keyId, "revoke", { outFile: outFile, reasonCode: reasonCode, reasonText: EnigmailData.convertFromUnicode(reasonText), usePassphrase: true }, revokeCertCallback, null, checkGeneratedCert); }, addUid: function(parent, keyId, name, email, comment, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.addUid: keyId=" + keyId + ", name=" + name + ", email=" + email + "\n"); return editKey(parent, true, null, keyId, "adduid", { email: email, name: name, comment: comment, nameAsked: 0, emailAsked: 0, usePassphrase: true }, addUidCallback, null, callbackFunc); }, deleteKey: function(parent, keyId, deleteSecretKey, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.addUid: keyId=" + keyId + ", deleteSecretKey=" + deleteSecretKey + "\n"); var cmd = ["--yes", (deleteSecretKey ? "--delete-secret-and-public-key" : "--delete-key")]; return editKey(parent, false, null, keyId, cmd, { usePassphrase: true }, deleteKeyCallback, null, callbackFunc); }, changePassphrase: function(parent, keyId, oldPw, newPw, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.changePassphrase: keyId=" + keyId + "\n"); var pwdObserver = new ChangePasswdObserver(); return editKey(parent, false, null, keyId, "passwd", { oldPw: oldPw, newPw: newPw, step: 0, observer: pwdObserver, usePassphrase: true }, changePassphraseCallback, pwdObserver, callbackFunc); }, enableDisableKey: function(parent, keyId, disableKey, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.enableDisableKey: keyId=" + keyId + ", disableKey=" + disableKey + "\n"); var cmd = (disableKey ? "disable" : "enable"); return editKey(parent, false, null, keyId, cmd, { usePassphrase: true }, null, null, callbackFunc); }, setPrimaryUid: function(parent, keyId, idNumber, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.setPrimaryUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n"); return editKey(parent, true, null, keyId, "", { idNumber: idNumber, step: 0, usePassphrase: true }, setPrimaryUidCallback, null, callbackFunc); }, deleteUid: function(parent, keyId, idNumber, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.deleteUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n"); return editKey(parent, true, null, keyId, "", { idNumber: idNumber, step: 0, usePassphrase: true }, deleteUidCallback, null, callbackFunc); }, revokeUid: function(parent, keyId, idNumber, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.revokeUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n"); return editKey(parent, true, null, keyId, "", { idNumber: idNumber, step: 0, usePassphrase: true }, revokeUidCallback, null, callbackFunc); }, addPhoto: function(parent, keyId, photoFile, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.addPhoto: keyId=" + keyId + "\n"); var photoFileName = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(photoFile.QueryInterface(Ci.nsIFile))); return editKey(parent, true, null, keyId, "addphoto", { file: photoFileName, step: 0, usePassphrase: true }, addPhotoCallback, null, callbackFunc); }, genCardKey: function(parent, name, email, comment, expiry, backupPasswd, requestObserver, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.genCardKey: \n"); var generateObserver = new EnigCardAdminObserver(requestObserver, EnigmailOS.isDosLike); return editKey(parent, false, null, "", ["--with-colons", "--card-edit"], { step: 0, name: EnigmailData.convertFromUnicode(name), email: email, comment: EnigmailData.convertFromUnicode(comment), expiry: expiry, backupPasswd: backupPasswd, cardAdmin: true, backupKey: (backupPasswd.length > 0 ? "Y" : "N"), parent: parent }, genCardKeyCallback, generateObserver, callbackFunc); }, cardAdminData: function(parent, name, firstname, lang, sex, url, login, forcepin, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.cardAdminData: parent=" + parent + ", name=" + name + ", firstname=" + firstname + ", lang=" + lang + ", sex=" + sex + ", url=" + url + ", login=" + login + ", forcepin=" + forcepin + "\n"); var adminObserver = new EnigCardAdminObserver(null, EnigmailOS.isDosLike); return editKey(parent, false, null, "", ["--with-colons", "--card-edit"], { step: 0, name: name, firstname: firstname, lang: lang, sex: sex, url: url, login: login, cardAdmin: true, forcepin: forcepin }, cardAdminDataCallback, adminObserver, callbackFunc); }, cardChangePin: function(parent, action, oldPin, newPin, adminPin, pinObserver, callbackFunc) { EnigmailLog.DEBUG("keyManagmenent.jsm: Enigmail.cardChangePin: parent=" + parent + ", action=" + action + "\n"); var adminObserver = new EnigCardAdminObserver(pinObserver, EnigmailOS.isDosLike); return editKey(parent, true, null, "", ["--with-colons", "--card-edit"], { step: 0, pinStep: 0, cardAdmin: true, action: action, oldPin: oldPin, newPin: newPin, adminPin: adminPin }, cardChangePinCallback, adminObserver, callbackFunc); } }; // EnigmailKeyEditor -// TODO: function probably not used ?! -function keyReadCallback(outputData, ret) { - - outputData.keyObj = new EnigmailKey.Entry(outputData.key); - ret.exitCode = 0; -} function signKeyCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_BOOL, "sign_uid.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_BOOL, "keyedit.sign_all.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_LINE, "sign_uid.expire")) { ret.exitCode = 0; ret.writeTxt = "0"; } else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_value")) { ret.exitCode = 0; ret.writeTxt = "0"; } else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_depth")) { ret.exitCode = 0; ret.writeTxt = ""; } else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_regexp")) { ret.exitCode = 0; ret.writeTxt = "0"; } else if (keyEdit.doCheck(GET_LINE, "siggen.valid")) { ret.exitCode = 0; ret.writeTxt = "0"; } else if (keyEdit.doCheck(GET_BOOL, "sign_uid.local_promote_okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; - } + } else if (keyEdit.doCheck(GET_BOOL, "sign_uid.replace_expired_okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_LINE, "sign_uid.class")) { ret.exitCode = 0; ret.writeTxt = String(inputData.trustLevel); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.quitNow = true; } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function keyTrustCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "edit_ownertrust.value")) { ret.exitCode = 0; ret.writeTxt = String(inputData.trustLevel); } else if (keyEdit.doCheck(GET_BOOL, "edit_ownertrust.set_ultimate.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.quitNow = true; EnigmailKeyRing.clearCache(); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } /** * * @param Array inputData Has the keys ... * expiryLength (String): e.g. 8m = 8 month, 5 = 5 days, 3y = 3 years, 0 = never * subKeys (array): list of still unprocessed subkeys * currentSubKey (Integer or false): current subkey in progress * @param Object keyEdit Readonly messages from GPG. * @param Object ret */ function keyExpiryCallback(inputData, keyEdit, ret) { EnigmailLog.DEBUG("keyManagmenent.jsm: keyExpiryCallback()\n"); ret.writeTxt = ""; ret.errorMsg = ""; if (inputData.subKeys.length === 0) { // zero keys are submitted to edit: this must be a mistake. ret.exitCode = -1; ret.quitNow = true; } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { if (inputData.currentSubKey === false) { // currently no subkey is selected. Chose the first subkey. inputData.currentSubKey = inputData.subKeys[0]; ret.exitCode = 0; ret.writeTxt = "key " + inputData.currentSubKey; } else if (inputData.currentSubKey === inputData.subKeys[0]) { // a subkey is selected. execute command "expire" ret.exitCode = 0; ret.writeTxt = "expire"; } else { // if (inputData.currentSubKey === inputData.subKeys[0]) // unselect the previous used subkey ret.exitCode = 0; ret.writeTxt = "key " + inputData.currentSubKey; inputData.currentSubKey = false; } } else if (keyEdit.doCheck(GET_LINE, "keygen.valid")) { // submit the expiry length. ret.exitCode = 0; ret.writeTxt = inputData.expiryLength; // processing of the current subkey is through. // remove current subkey from list of "to be processed keys". inputData.subKeys.splice(0, 1); // if the list of "to be processed keys" is empty, then quit. if (inputData.subKeys.length === 0) { ret.quitNow = true; } } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function addUidCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "keygen.name")) { ++inputData.nameAsked; if (inputData.nameAsked == 1) { ret.exitCode = 0; ret.writeTxt = inputData.name; } else { ret.exitCode = -1; ret.quitNow = true; ret.errorMsg = "Invalid name (too short)"; } } else if (keyEdit.doCheck(GET_LINE, "keygen.email")) { ++inputData.emailAsked; if (inputData.emailAsked == 1) { ret.exitCode = 0; ret.writeTxt = inputData.email; } else { ret.exitCode = -1; ret.quitNow = true; ret.errorMsg = "Invalid email"; } } else if (keyEdit.doCheck(GET_LINE, "keygen.comment")) { ret.exitCode = 0; if (inputData.comment) { ret.writeTxt = inputData.comment; } else { ret.writeTxt = ""; } } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.quitNow = true; EnigmailKeyRing.clearCache(); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function revokeCertCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.code")) { ret.exitCode = 0; ret.writeTxt = String(inputData.reasonCode); } else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.text")) { ret.exitCode = 0; ret.writeTxt = ""; } else if (keyEdit.doCheck(GET_BOOL, "gen_revoke.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_BOOL, "ask_revocation_reason.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_BOOL, "openfile.overwrite.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.quitNow = true; } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function setPrimaryUidCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ++inputData.step; switch (inputData.step) { case 1: ret.exitCode = 0; ret.writeTxt = "uid " + inputData.idNumber; break; case 2: ret.exitCode = 0; ret.writeTxt = "primary"; break; case 3: ret.exitCode = 0; ret.quitNow = true; EnigmailKeyRing.clearCache(); break; default: ret.exitCode = -1; ret.quitNow = true; } } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function changePassphraseCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_HIDDEN, "passphrase.enter")) { switch (inputData.observer.passphraseStatus) { case 0: ret.writeTxt = inputData.oldPw; ret.exitCode = 0; break; case 1: ret.writeTxt = inputData.newPw; ret.exitCode = 0; break; case -1: ret.exitCode = -2; ret.quitNow = true; break; } } else if (keyEdit.doCheck(GET_BOOL, "change_passwd.empty.okay")) { ret.writeTxt = "Y"; ret.exitCode = 0; } else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.quitNow = true; } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function deleteUidCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ++inputData.step; switch (inputData.step) { case 1: ret.exitCode = 0; ret.writeTxt = "uid " + inputData.idNumber; break; case 2: ret.exitCode = 0; ret.writeTxt = "deluid"; break; case 4: ret.exitCode = 0; ret.quitNow = true; EnigmailKeyRing.clearCache(); break; default: ret.exitCode = -1; ret.quitNow = true; } } else if (keyEdit.doCheck(GET_BOOL, "keyedit.remove.uid.okay")) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function revokeUidCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ++inputData.step; switch (inputData.step) { case 1: ret.exitCode = 0; ret.writeTxt = "uid " + inputData.idNumber; break; case 2: ret.exitCode = 0; ret.writeTxt = "revuid"; break; case 7: ret.exitCode = 0; ret.quitNow = true; EnigmailKeyRing.clearCache(); break; default: ret.exitCode = -1; ret.quitNow = true; } } else if (keyEdit.doCheck(GET_BOOL, "keyedit.revoke.uid.okay")) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.code")) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = "0"; // no reason specified } else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.text")) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = ""; } else if (keyEdit.doCheck(GET_BOOL, "ask_revocation_reason.okay")) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function deleteKeyCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_BOOL, "delete_key.secret.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_BOOL, "keyedit.remove.subkey.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_BOOL, "delete_key.okay")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function getPin(domWindow, promptMsg, ret) { EnigmailLog.DEBUG("keyManagmenent.jsm: getPin: \n"); var passwdObj = { value: "" }; var dummyObj = {}; var success = false; var promptService = Cc[NS_PROMPTSERVICE_CONTRACTID].getService(Ci.nsIPromptService); success = promptService.promptPassword(domWindow, EnigmailLocale.getString("Enigmail"), promptMsg, passwdObj, null, dummyObj); if (!success) { ret.errorMsg = EnigmailLocale.getString("noPassphrase"); ret.quitNow = true; return false; } EnigmailLog.DEBUG("keyManagmenent.jsm: getPin: got pin\n"); ret.writeTxt = passwdObj.value; return true; } function genCardKeyCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; var pinObj = {}; if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) { if (inputData.step === 0) { ret.exitCode = 0; ret.writeTxt = "admin"; } else if (inputData.step == 1) { ret.exitCode = 0; ret.writeTxt = "generate"; } else { ret.exitCode = 0; ret.quitNow = true; ret.writeTxt = "quit"; } ++inputData.step; } else if (keyEdit.doCheck(GET_LINE, "cardedit.genkeys.backup_enc") || keyEdit.doCheck(GET_BOOL, "cardedit.genkeys.backup_enc")) { ret.exitCode = 0; ret.writeTxt = String(inputData.backupKey); } else if (keyEdit.doCheck(GET_BOOL, "cardedit.genkeys.replace_keys")) { ret.exitCode = 0; ret.writeTxt = "Y"; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.enter")) { ret.exitCode = 0; ret.writeTxt = inputData.backupPasswd; } else if (keyEdit.doCheck(GET_LINE, "keygen.valid")) { ret.exitCode = 0; ret.writeTxt = String(inputData.expiry); } else if (keyEdit.doCheck(GET_LINE, "cardedit.genkeys.size")) { ret.exitCode = 0; ret.writeTxt = "2048"; } else if (keyEdit.doCheck(GET_LINE, "keygen.name")) { ret.exitCode = 0; ret.writeTxt = inputData.name; } else if (keyEdit.doCheck(GET_LINE, "keygen.email")) { ret.exitCode = 0; ret.writeTxt = inputData.email; } else if (keyEdit.doCheck(GET_LINE, "keygen.comment")) { ret.exitCode = 0; if (inputData.comment) { ret.writeTxt = inputData.comment; } else { ret.writeTxt = ""; } } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function cardAdminDataCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; var pinObj = {}; if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) { ++inputData.step; ret.exitCode = 0; switch (inputData.step) { case 1: ret.writeTxt = "admin"; break; case 2: ret.writeTxt = "name"; break; case 3: ret.writeTxt = "lang"; break; case 4: ret.writeTxt = "sex"; break; case 5: ret.writeTxt = "url"; break; case 6: ret.writeTxt = "login"; break; case 7: if (inputData.forcepin !== 0) { ret.writeTxt = "forcesig"; } else { ret.writeTxt = "quit"; ret.exitCode = 0; ret.quitNow = true; } break; default: ret.writeTxt = "quit"; ret.exitCode = 0; ret.quitNow = true; break; } } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else if (keyEdit.doCheck(GET_LINE, "keygen.smartcard.surname")) { ret.exitCode = 0; ret.writeTxt = inputData.name.replace(/^$/, "-"); } else if (keyEdit.doCheck(GET_LINE, "keygen.smartcard.givenname")) { ret.exitCode = 0; ret.writeTxt = inputData.firstname.replace(/^$/, "-"); } else if (keyEdit.doCheck(GET_LINE, "cardedit.change_sex")) { ret.exitCode = 0; ret.writeTxt = inputData.sex; } else if (keyEdit.doCheck(GET_LINE, "cardedit.change_lang")) { ret.exitCode = 0; ret.writeTxt = inputData.lang.replace(/^$/, "-"); } else if (keyEdit.doCheck(GET_LINE, "cardedit.change_url")) { ret.exitCode = 0; ret.writeTxt = inputData.url.replace(/^$/, "-"); } else if (keyEdit.doCheck(GET_LINE, "cardedit.change_login")) { ret.exitCode = 0; ret.writeTxt = inputData.login.replace(/^$/, "-"); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function cardChangePinCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) { ++inputData.step; ret.exitCode = 0; switch (inputData.step) { case 1: ret.writeTxt = "admin"; break; case 2: ret.writeTxt = "passwd"; break; default: ret.writeTxt = "quit"; ret.exitCode = 0; ret.quitNow = true; break; } } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { ret.exitCode = 0; ret.writeTxt = inputData.adminPin; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { ret.exitCode = 0; ret.writeTxt = inputData.oldPin; } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.new.ask") || keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.repeat") || keyEdit.doCheck(GET_HIDDEN, "passphrase.ask") || keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.new.ask")) { ret.exitCode = 0; ret.writeTxt = inputData.newPin; } else if (keyEdit.doCheck(GET_LINE, "cardutil.change_pin.menu")) { ret.exitCode = 0; ++inputData.pinStep; if (inputData.pinStep == 1) { ret.writeTxt = inputData.action.toString(); } else { ret.writeTxt = "Q"; } } else { ret.exitCode = -1; ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); } } function addPhotoCallback(inputData, keyEdit, ret) { ret.writeTxt = ""; ret.errorMsg = ""; if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) { ret.exitCode = 0; ret.writeTxt = "save"; ret.quitNow = true; } else if (keyEdit.doCheck(GET_LINE, "photoid.jpeg.add")) { if (inputData.step === 0) { ++inputData.step; ret.exitCode = 0; ret.writeTxt = inputData.file; } else { ret.exitCode = -1; ret.quitNow = true; } } else if (keyEdit.doCheck(GET_BOOL, "photoid.jpeg.size")) { ret.exitCode = 0; ret.writeTxt = "Y"; // add large file } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret); } else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) { getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret); } else { ret.quitNow = true; EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n"); ret.exitCode = -1; } } function EnigCardAdminObserver(guiObserver, isDosLike) { this._guiObserver = guiObserver; this._isDosLike = isDosLike; } EnigCardAdminObserver.prototype = { _guiObserver: null, _failureCode: 0, QueryInterface: function(iid) { if (iid.equals(Ci.nsIEnigMimeReadCallback) || iid.equals(Ci.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; }, onDataAvailable: function(data) { var ret = ""; EnigmailLog.DEBUG("keyManagmenent.jsm: enigCardAdminObserver.onDataAvailable: data=" + data + "\n"); if (this._isDosLike && data.indexOf("[GNUPG:] BACKUP_KEY_CREATED") === 0) { data = data.replace(/\//g, "\\"); } if (data.indexOf("[GNUPG:] SC_OP_FAILURE") >= 0) { data = data.substr(23); if (data == "2") { data = "[GNUPG:] BAD_PASSPHRASE 0"; this._failureCode = 2; } else this._failureCode = 1; } if (this._failureCode == 1) { ret = "[GNUPG:] ENIGMAIL_FAILURE " + data; } if (this._guiObserver) { this._guiObserver.onDataAvailable(data); } return ret; } }; function ChangePasswdObserver() {} ChangePasswdObserver.prototype = { _failureCode: 0, passphraseStatus: 0, QueryInterface: function(iid) { if (iid.equals(Ci.nsIEnigMimeReadCallback) || iid.equals(Ci.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; }, onDataAvailable: function(data) { var ret = ""; EnigmailLog.DEBUG("keyManagmenent.jsm: ChangePasswdObserver.onDataAvailable: data=" + data + "\n"); if (this._failureCode) { ret = "[GNUPG:] ENIGMAIL_FAILURE " + data; } if (data.indexOf("[GNUPG:] GOOD_PASSPHRASE") >= 0) { this.passphraseStatus = 1; } else if (data.indexOf("[GNUPG:] BAD_PASSPHRASE") >= 0) { this.passphraseStatus = -1; } return ret; } }; diff --git a/package/openpgp.jsm b/package/openpgp.jsm index c63c08a4..4e3d3def 100644 --- a/package/openpgp.jsm +++ b/package/openpgp.jsm @@ -1,35 +1,146 @@ /* * This Source Code Form is licensed under the GNU LGPL 3.0 license. * */ "use strict"; /** * This code is taken from openpgp.js * * Do OpenPGP packet parsing */ /* global Components: false */ /* eslint no-invalid-this: 0 */ var EXPORTED_SYMBOLS = ["EnigmailOpenPGP"]; const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; var appShellSvc = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService); var window = appShellSvc.hiddenDOMWindow; var document = window.document; Components.utils.import("resource://gre/modules/Services.jsm"); /* global Services: false */ Services.scriptloader.loadSubScript("resource://enigmail/stdlib/openpgp-lib.js", this, "UTF-8"); /* global openpgp: false */ +var crc_table = [0x00000000, 0x00864cfb, 0x018ad50d, 0x010c99f6, 0x0393e6e1, 0x0315aa1a, 0x021933ec, 0x029f7f17, 0x07a18139, 0x0727cdc2, 0x062b5434, 0x06ad18cf, 0x043267d8, 0x04b42b23, + 0x05b8b2d5, 0x053efe2e, 0x0fc54e89, 0x0f430272, 0x0e4f9b84, 0x0ec9d77f, 0x0c56a868, 0x0cd0e493, 0x0ddc7d65, 0x0d5a319e, 0x0864cfb0, 0x08e2834b, 0x09ee1abd, 0x09685646, 0x0bf72951, + 0x0b7165aa, 0x0a7dfc5c, 0x0afbb0a7, 0x1f0cd1e9, 0x1f8a9d12, 0x1e8604e4, 0x1e00481f, 0x1c9f3708, 0x1c197bf3, 0x1d15e205, 0x1d93aefe, 0x18ad50d0, 0x182b1c2b, 0x192785dd, 0x19a1c926, + 0x1b3eb631, 0x1bb8faca, 0x1ab4633c, 0x1a322fc7, 0x10c99f60, 0x104fd39b, 0x11434a6d, 0x11c50696, 0x135a7981, 0x13dc357a, 0x12d0ac8c, 0x1256e077, 0x17681e59, 0x17ee52a2, 0x16e2cb54, + 0x166487af, 0x14fbf8b8, 0x147db443, 0x15712db5, 0x15f7614e, 0x3e19a3d2, 0x3e9fef29, 0x3f9376df, 0x3f153a24, 0x3d8a4533, 0x3d0c09c8, 0x3c00903e, 0x3c86dcc5, 0x39b822eb, 0x393e6e10, + 0x3832f7e6, 0x38b4bb1d, 0x3a2bc40a, 0x3aad88f1, 0x3ba11107, 0x3b275dfc, 0x31dced5b, 0x315aa1a0, 0x30563856, 0x30d074ad, 0x324f0bba, 0x32c94741, 0x33c5deb7, 0x3343924c, 0x367d6c62, + 0x36fb2099, 0x37f7b96f, 0x3771f594, 0x35ee8a83, 0x3568c678, 0x34645f8e, 0x34e21375, 0x2115723b, 0x21933ec0, 0x209fa736, 0x2019ebcd, 0x228694da, 0x2200d821, 0x230c41d7, 0x238a0d2c, + 0x26b4f302, 0x2632bff9, 0x273e260f, 0x27b86af4, 0x252715e3, 0x25a15918, 0x24adc0ee, 0x242b8c15, 0x2ed03cb2, 0x2e567049, 0x2f5ae9bf, 0x2fdca544, 0x2d43da53, 0x2dc596a8, 0x2cc90f5e, + 0x2c4f43a5, 0x2971bd8b, 0x29f7f170, 0x28fb6886, 0x287d247d, 0x2ae25b6a, 0x2a641791, 0x2b688e67, 0x2beec29c, 0x7c3347a4, 0x7cb50b5f, 0x7db992a9, 0x7d3fde52, 0x7fa0a145, 0x7f26edbe, + 0x7e2a7448, 0x7eac38b3, 0x7b92c69d, 0x7b148a66, 0x7a181390, 0x7a9e5f6b, 0x7801207c, 0x78876c87, 0x798bf571, 0x790db98a, 0x73f6092d, 0x737045d6, 0x727cdc20, 0x72fa90db, 0x7065efcc, + 0x70e3a337, 0x71ef3ac1, 0x7169763a, 0x74578814, 0x74d1c4ef, 0x75dd5d19, 0x755b11e2, 0x77c46ef5, 0x7742220e, 0x764ebbf8, 0x76c8f703, 0x633f964d, 0x63b9dab6, 0x62b54340, 0x62330fbb, + 0x60ac70ac, 0x602a3c57, 0x6126a5a1, 0x61a0e95a, 0x649e1774, 0x64185b8f, 0x6514c279, 0x65928e82, 0x670df195, 0x678bbd6e, 0x66872498, 0x66016863, 0x6cfad8c4, 0x6c7c943f, 0x6d700dc9, + 0x6df64132, 0x6f693e25, 0x6fef72de, 0x6ee3eb28, 0x6e65a7d3, 0x6b5b59fd, 0x6bdd1506, 0x6ad18cf0, 0x6a57c00b, 0x68c8bf1c, 0x684ef3e7, 0x69426a11, 0x69c426ea, 0x422ae476, 0x42aca88d, + 0x43a0317b, 0x43267d80, 0x41b90297, 0x413f4e6c, 0x4033d79a, 0x40b59b61, 0x458b654f, 0x450d29b4, 0x4401b042, 0x4487fcb9, 0x461883ae, 0x469ecf55, 0x479256a3, 0x47141a58, 0x4defaaff, + 0x4d69e604, 0x4c657ff2, 0x4ce33309, 0x4e7c4c1e, 0x4efa00e5, 0x4ff69913, 0x4f70d5e8, 0x4a4e2bc6, 0x4ac8673d, 0x4bc4fecb, 0x4b42b230, 0x49ddcd27, 0x495b81dc, 0x4857182a, 0x48d154d1, + 0x5d26359f, 0x5da07964, 0x5cace092, 0x5c2aac69, 0x5eb5d37e, 0x5e339f85, 0x5f3f0673, 0x5fb94a88, 0x5a87b4a6, 0x5a01f85d, 0x5b0d61ab, 0x5b8b2d50, 0x59145247, 0x59921ebc, 0x589e874a, + 0x5818cbb1, 0x52e37b16, 0x526537ed, 0x5369ae1b, 0x53efe2e0, 0x51709df7, 0x51f6d10c, 0x50fa48fa, 0x507c0401, 0x5542fa2f, 0x55c4b6d4, 0x54c82f22, 0x544e63d9, 0x56d11cce, 0x56575035, + 0x575bc9c3, 0x57dd8538 +]; var EnigmailOpenPGP = window.openpgp; + +EnigmailOpenPGP.enigmailFuncs = { + + /** + * Convert a string to an Uint8Array + * + * @param str: String with binary data + * @return Uint8Array + */ + str2Uint8Array: function(str) { + var buf = new ArrayBuffer(str.length); + var bufView = new Uint8Array(buf); + for (var i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i); + } + return bufView; + }, + + /** + * Create CRC24 checksum + * + * @param input: Uint8Array of input data + * + * @return Number + */ + createcrc24: function(input) { + var crc = 0xB704CE; + var index = 0; + + while (input.length - index > 16) { + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 1]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 2]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 3]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 4]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 5]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 6]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 7]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 8]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 9]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 10]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 11]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 12]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 13]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 14]) & 0xff]; + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 15]) & 0xff]; + index += 16; + } + + for (var j = index; j < input.length; j++) { + crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index++]) & 0xff]; + } + return crc & 0xffffff; + }, + + /** + * Create an ASCII armored string from binary data + * + */ + bytesToArmor: function(msgType, str) { + + const ARMOR_TYPE = EnigmailOpenPGP.enums.armor; + + let hdr = ""; + switch (msgType) { + case ARMOR_TYPE.signed: + case ARMOR_TYPE.message: + hdr = "MESSAGE"; + break; + case ARMOR_TYPE.public_key: + hdr = "PUBLIC KEY BLOCK"; + break; + case ARMOR_TYPE.private_key: + hdr = "PRIVATE KEY BLOCK"; + break; + case ARMOR_TYPE.signature: + hdr = "SIGNATURE"; + break; + } + + let crc = EnigmailOpenPGP.enigmailFuncs.createcrc24(EnigmailOpenPGP.enigmailFuncs.str2Uint8Array(str)); + let crcAsc = String.fromCharCode(crc >> 16) + String.fromCharCode(crc >> 8 & 0xFF) + String.fromCharCode(crc & 0xFF); + + let s = "-----BEGIN PGP " + hdr + "-----\n\n" + + btoa(str) + "\n" + + "=" + btoa(crcAsc) + "\n" + + "-----END PGP " + hdr + "-----\n"; + + return s; + } + +}; diff --git a/package/tests/key-test.js b/package/tests/key-test.js new file mode 100644 index 00000000..83109c18 --- /dev/null +++ b/package/tests/key-test.js @@ -0,0 +1,257 @@ +/*global do_load_module: false, do_get_file: false, do_get_cwd: false, testing: false, test: false, Assert: false, */ +/*global Components: false, resetting: false, JSUnit: false, do_test_pending: false, do_test_finished: false, component: false, Cc: false, Ci: false */ +/*jshint -W097 */ +/* + * 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/. + */ + +/* eslint no-useless-concat: 0*/ +"use strict"; + +/*global EnigmailFiles: false */ +do_load_module("file://" + do_get_cwd().path + "/testHelper.js"); /*global withEnigmail: false, withTestGpgHome: false, getKeyListEntryOfKey: false, gKeyListObj: true */ + +testing("key.jsm"); /*global EnigmailKey: false */ +component("enigmail/files.jsm"); /*global EnigmailFiles: false */ + +test(function shouldGetKeyDetails() { + const publicKey = do_get_file("resources/dev-strike.asc", false); + const secretKey = do_get_file("resources/dev-strike.sec", false); + + const pubKeyData = EnigmailFiles.readFile(publicKey); + let keyList = EnigmailKey.getKeyListFromKeyBlock(pubKeyData, {}); + + Assert.ok(keyList.length === 1); + let k = keyList[0]; + + Assert.equal(k.id, "781617319CE311C4"); + Assert.equal(k.name, "anonymous strike "); + Assert.equal(k.fpr, "65537E212DC19025AD38EDB2781617319CE311C4"); + Assert.equal(k.isSecret, false); + + const secKeyData = EnigmailFiles.readFile(secretKey); + keyList = EnigmailKey.getKeyListFromKeyBlock(secKeyData, {}); + + Assert.ok(keyList.length === 1); + k = keyList[0]; + + Assert.equal(k.name, "anonymous strike "); + Assert.equal(k.fpr, "65537E212DC19025AD38EDB2781617319CE311C4"); + Assert.equal(k.isSecret, true); + +}); + +test(function shouldSplitKeys() { + const publicKey1 = do_get_file("resources/dev-strike.asc", false); + const publicKey2 = do_get_file("resources/dev-tiger.asc", false); + + const pubKeyData = EnigmailFiles.readFile(publicKey1) + "\r\n" + EnigmailFiles.readFile(publicKey2); + + + let keyBlocks = EnigmailKey.splitArmoredBlocks(pubKeyData); + + Assert.equal(keyBlocks.length, 2); + + let keyList = EnigmailKey.getKeyListFromKeyBlock(pubKeyData, {}); + + Assert.equal(keyList.length, 2); + let k = keyList[1]; + + Assert.equal(k.name, "dev-tiger "); + Assert.equal(k.fpr, "8C140834F2D683E9A016D3098439E17046977C46"); + Assert.equal(k.isSecret, false); + +}); + + +test(function shouldMergePubAndSec() { + const keyList = EnigmailKey.getKeyListFromKeyBlock( + "-----BEGIN PGP PUBLIC KEY BLOCK-----" + + "\n" + "Comment: GPGTools - https://gpgtools.org" + + "\n" + + "\n" + "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" + + "\n" + "LBn4tb400iHp7byO85tF/H0OOI1K" + + "\n" + "=CVNK" + + "\n" + "-----END PGP PUBLIC KEY BLOCK-----" + + "\n" + "-----BEGIN PGP PRIVATE KEY BLOCK-----" + + "\n" + "Comment: GPGTools - https://gpgtools.org" + + "\n" + + "\n" + "lQc+BFVHm5sBEACs94Ln+RMdeyBpWQtTZ/NZnwntsB10Wd3HTgo5sdA/OOFOJrWe" + + "\n" + "tJfAZ/HRxiSu1bwRaFVC8p061ftTbxf8bsdfsykYJQQqPODfcO0/oY2n/Z93ya8K" + + "\n" + "TzjXR3qBQ1P7f5x71yeuo7Zrj7B0G44Xjfy+1L0eka9paBqmm3U5cUew5wSr772L" + + "\n" + "cflipWfncWXD2rBqgRfR339lRHd3Vwo7V8jje8rlP9msOuTMWCvQuQvpEkfIioXA" + + "\n" + "7QipP2f0aPzsavNjFnAfC9rm2FDs6lX4syTMVUWy8IblRYo6MjhNaJFlBJkTCl0b" + + "\n" + "ugT9Ge0ZUifuAI0ihVGBpMSh4GF2B3ZPidwGSjgx1sojNHzU/3vBa9DuOmW95qrD" + + "\n" + "Notvz61xYueTpOYK6ZeT880QMDvxXG9S5/H1KJxuOF1jx1DibAn9sfP4gtiQFI3F" + + "\n" + "WMV9w3YrrqidoWSZBqyBO0Toqt5fNdRyH4ET6HlJAQmFQUbqqnZrc07s/aITZN36" + + "\n" + "d9eupCZQfW6e80UkXRPCU53vhh0GQey9reDyVCsV7xi6oXk1fqlpDYigQwEr4+yJ" + + "\n" + "+1qAjtSVHJhFE0inQWkUwc2nxef6n7v/M9HszhP/aABadVE49oDaRm54PtA1l0mC" + + "\n" + "T8IHcVR4ZDkaNwrHJtidEQcQ/+YVV3g7UJI9+g2nPvgMhk86AzBIlGpG+wARAQAB" + + "\n" + "/gMDAtfSz5hVoDvp4Vugj4T3VQk8mJ3uYDZmPbNL8SK18VTIVpd3xgrjTP+JEtB+" + + "\n" + "aw1WQK4Qik0BdKAu9Lv6wz4u/QNC8q4x3lBcoYleD6iXRL2Tpnh7RcEakIoxIeFH" + + "\n" + "joBTZOI+v0HUlyVvSkIaEhE60UvdX+If9p9sx5+uHdYRRfOkM3SZMxLxCUVHMp1e" + + "\n" + "ZBcmW+x9UiyA07wXyvAhlS2/iTijDtQFRqK8xs9w7zn0A12afksGtPEL7J5MRqQs" + + "\n" + "BuxUrWSKVQ3DkgWXd56qEtbKuklKXe9t93eMPvcFQ2ZVhgic436dygtpNgkGliVq" + + "\n" + "Di83wUjorTZFMeC0uhvQ2akfQxvj5TgYoI0rFABvn/6He1LBSWyiu6ZK1nC1PKUc" + + "\n" + "KLGQGfq+kbHwJg3q0wIJ5+e1v6hZ9HClhaRsR4ADnTDnp3mGqPxDWvQox1S2+ESx" + + "\n" + "8N6AcZ+q47D78IE4EzF4LyQ0g9FdDiNsPwqN4oS2/ZkXb/IbFoVoottU7915KqZO" + + "\n" + "6kiJvpMcZrs4TJ4zR++CGBEvJDfUE4RoQHQe/XLA1RJXIwXr3kWPvB2Tc16vdhkh" + + "\n" + "LZ9z/HOrPW6SI/UwVYFHpmJIYj3nHdjGcyWwz0KmQ3H5+AYe36afwJws6TFx/QLi" + + "\n" + "fqlOkcaBaiQwpcpuSX2y4rTgcjDEaVdPGmvs2m5vKv66a8yybIl2P0veVTutGtC8" + + "\n" + "DfQaggqZWQYHmXXvGUnBM+H9YSBJ2g3W3w51fKcN2FtX42OsVxXqZkqlGR2zBE00" + + "\n" + "ilVZmiv6ajV9mmO7W8EV9TPqjrYuEDf2M57LllQ7OB1p1v6CtqIyVSL/Jak6ckMT" + + "\n" + "5VdqMoup6ib5j4CR+C4i7Btu+gkXhW775l/jbFlUXKE5Vn+LAAIOpxiVZ2Z7azL6" + + "\n" + "sNwxtfmpaTAgIvHGSysgPeXeEN3fgTsfZ0PYaqlEHggsYDDU4XvXIOKcUmrr6zEI" + + "\n" + "KXeeS0+V3nxSIb9kQHYZyUFvNv98gCCj0wgNl+LoVJ9NvMkaOrCS0jkRaxJicQfa" + + "\n" + "bu4XL9XbUBESuHvG6jiK6DNlhT1j3qFFcRBO7COI3OQ0JD7Y1XPYYR48EP69Fwe0" + + "\n" + "82LZH5dq9kslpn8VsuygTum9jYFnE5UVLfmjbroFu9YlLE54T0CdZ4UQEWTrZiuz" + + "\n" + "TXYf13FaVEgfAim+hjdUUVSCptsX2crC7Vrsk/xMjT2ETU1w/yZb5BVoCvbK/eaf" + + "\n" + "sqQAPGElSp0YlI/mgpbc5rRQzcSXghenjOol/gJM0MbFJuyQ93sLW0Gi7zEeBxQi" + + "\n" + "aO/Ua4F4VhPilPf+T66fNMM0bG29X5j41eRrN0m1ly4M+jOOIyocLcUamgFsRDTe" + + "\n" + "XG9kHZUylAJqNMwQvDzbVSTbHKjhOTa3PyinrTwauYiQP6fIbd4JWkIW88cBynbR" + + "\n" + "IHHCYYGxZoDUDd366QyNHKTd5wxw1MicK54tUDcUVDq8NKC+yGuGi6WLYt4WdNEg" + + "\n" + "pYb/MzxGRzbhVEHNbfFEr5e706VcQlglpPcMTUctzRVF18wWHzPVbHdZiTBXdr0t" + + "\n" + "hJkRNaAvnmQMvP0bXk+QDGW24Z66Yz0X2YzFo4Rdp/MAm/1KwagIu0hIGbwk8egq" + + "\n" + "tq6Q5zyyiSp7dVvcNAPaEzEKZXRSrSjyNwQw0CHI940SRgK5JDkAMHZWK8vg8Ih4" + + "\n" + "DR7m69XmYXwvTScrQqkFa+8XIb5QqeH7W3Qe4aKiC6QOJav/ptYLZ+s1TTzeIOA8" + + "\n" + "5zxhWPj81YgifDtWPc4MG+Y0QuSzMdMue+/oJUt6lyQmtCthbm9ueW1vdXMgc3Ry" + + "\n" + "aWtlIDxzdHJpa2UuZGV2dGVzdEBnbWFpbC5jb20+iQI9BBMBCgAnBQJVR5ubAhsD" + + "\n" + "BQkHhh+ABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEHgWFzGc4xHEt/4P/1zf" + + "\n" + "/2VsEwpJVlqwoLiJGQbViCRW34W8rTyL45GjRYAgDXrWLDPqxSbotXTXi72Dwug6" + + "\n" + "a/Pn1VI1R2ZaBsWXH8qUYtSV/0b/2HfqUyDhaiuASywMdSfTAXa+popNccD5yPCJ" + + "\n" + "VBD0xmPCAmrOciYePMMNBk4SCDV5DJcCyGhEAkSeGsXy+m2bXb1pTbg6OpqDIPCq" + + "\n" + "lmNQ8ZyAZNzWIyRWcqUY+B6xcZk+n50wG9A0TCOvVjsZ+E8Khyha2tfz1WFPmoy0" + + "\n" + "rMD4g2ggvII3v4elosBQW0pxYdkwBAwk6g3DMyUzR6GcNcZnuvnZVBbjCpqXtDJ7" + + "\n" + "UcjjcP8zvzDYlXAY74gM8Nu7/89Pw676rVUXtS7c/LUB8Z75FACi7d65Kp8Q6sNY" + + "\n" + "Vfi/mTggNwEAuAkjp9acEGvk67q2we+lEoeAwCyfBiBu5TmYriLyAvfyoyeMhRjV" + + "\n" + "0FdBaRh+4CkVgSG4/eTLFcnHVB2ZzhX7Uw0qoxM8R+caP75XoVUyXmIpC/UZTrF4" + + "\n" + "IGDUc4jbIqSGU2/Kln4Z8vQpuCw0vavbT93jSCyqaIbKqemK8D0rbaoxJq5RLkwU" + + "\n" + "6gJ3dOpQdDRuqUAkfbEZd8yVRSmfugRhCTbdAA5/1kdgoWv9xZU7asdsm5cpHpy7" + + "\n" + "lM7ponHsA+B+ykApDqDzPIHDZBoeqKl6qNe2BYOYnQc+BFVHm5sBEADBX28bR5Qx" + + "\n" + "brGNVPT3jM60IT+m/GTLH6lm4OcZomAej/XrBOcX/0BYtOqqP7Dur8k0A8fcLkZC" + + "\n" + "SBse1m6fvfACo8Vbeunv4IrST5FgXh7bYPZseIy5U7Xn0dLqpVXJRqMt3ULS/Rwy" + + "\n" + "18Xx8j9sXJJDAKIqZ4MHwgBknPeeBnD4aG6bJAuBEI6RW5lhbG8WFJfCniFuRnim" + + "\n" + "+VD6ucf93x3NkL0TWY0l0PbUdW92sLfiKp1nmz+1dRoBckT701sMs2pk48O5Y/vP" + + "\n" + "6OEDzFzjGdA1r9YkblXjN9VxhSN00Wlmcq1DqEU36+Mqi4YIQsuF3NfS13+U2lhj" + + "\n" + "lR5HpRxdDMfHjFYlk5hlOtuvopseYTlMykFl8D7y0qSFIAiqVl6pdlSBU84bOLHo" + + "\n" + "CUGens+Ul7m0UShwZdVmMifFw/fJwISZI8D5vGkM3rE7TxrHAQ/O1fJnGZNBRgn8" + + "\n" + "LjnZjRGA/u1fweptFY0NyzO5lOzTWI6HBJl1hMave2l0vtwBPLrRbbRhy6Z77BNf" + + "\n" + "E9a2w7Y4aFeshjEpWxE8bQIyMrBGaRaiQ2lpXmA6XYZxQ8xOUfstsAR1TM+JboXJ" + + "\n" + "DuTw+YhaVa2W7Z/RzdtNnahWCCjptFq60DuggLwAGnjr5HctpLgwvLVKCeDfU8nc" + + "\n" + "hzCkL7Hikh2LC7ySUR/VzORag/TkjxYRRwARAQAB/gMDAtfSz5hVoDvp4ZpoCdrR" + + "\n" + "S4An9JABiMWCTG4IUYuShVQKJJR3KtZ0C5D4gH4BUlEGDsUtY3/6deakvzedbVxv" + + "\n" + "mb59QoU8GuHZ/iWAlsY+37YIBu9kbywIFDDGJeD9th9cXPpuQ31kEvwE37gsNn5p" + + "\n" + "IB38oB3mgWoLi2nH4AAVNZXPNBTJ7rS1pi69v4BepUTbglb805ypmWJllzhyRUvm" + + "\n" + "DAU/8cu0cPWaaBU4s8Mi7SLv2s+i9EPYNzDkBEy7RYvZApP7G8x447iYPRvmaFnB" + + "\n" + "Fd3Ctpd3xkZhZatDV6MJCEfssIdy5yARV4zwCcZ5JDGXGlxoiPH6A3b11SwPOEMv" + + "\n" + "QJ0PRZ334XLK93hwzxjYKBJ8hBrR2oPvRUOAVs2/J8JSASYrufyqkXnYJ1EBnP3H" + + "\n" + "5TwbjRQ9Qmg1ScFCzTfYgu5emiIF5LFAfTasZGSJvjrXFVeocCswHUvHztzJmpbt" + + "\n" + "BAov3Lw6lBkxdvhZomyx74CGOnyz/eFD/khvIV/oms5lR8NNrkpkRGZ/xCN8Kmau" + + "\n" + "KhRBebGVEITzOWJQHz0QMhuTloVvtDbDDgqW8gH8eVQJkQCDw8Wd79uj0kw1Xaln" + + "\n" + "nseFPLCRNRN0js7cCGW95fVtawRNBCNYambNCLthkBfzT0+1/ULRC2JrP1rutr6D" + + "\n" + "sye0S44kNyXKhM72Hamu4XygYlzRsYQflv2PgypjVmkdcZ2rwomMVdqQll3Q7jde" + + "\n" + "kWKDpKdx7lR2pnDoXmn43VR1X4uD1PHab56tYE0JUrKDgqZJqzCwJXc3FcPV7LgD" + + "\n" + "8pISoMZJ5+84UiRBvNN7N24kpLd9k97Iz29qY6u86Uy/f7/X77qur58r6K04vTfH" + + "\n" + "8nA/ybc/9Ube6oyQ44A2NEwBrP3NUA6lHNPeaYBO2RGxTJJU2Edxuy3bJMpEkBhU" + + "\n" + "CeWWIZnuxojF+pGjiPLArVZg6Mahc0GlYoiA66cxTuoGHM/wO5Xl0f33L0jny3Kv" + + "\n" + "I9OWvUJbKs+J8G6q0zorl5nNmPpTwGYLJkUKhLtMhjS+jf5XA7b5jN1079/oToXu" + + "\n" + "Xsfl6J7vnwJt7gglLRpK8iw3xlF4X4AWodBLj36HEyOgTimJXLt2fdpIrkiutUFF" + + "\n" + "qdWdZeK+sII8ZAyrfgvwxvmairFK3ylbPwA340jsRQeA8boOOSiDV0cPOszQi1Rt" + + "\n" + "Ygnae9or6faA3lqe+fRQi5JsmnJ1jLMe0mxw1mMOR4YLCMjgBc0CTMkY5kmCykA7" + + "\n" + "NCQBec2Fueu9cxsu7LJO4+OAUF+i+G/BWPzvWqyJrLk52tME2TxyVL5PRZvqAcrK" + + "\n" + "CV+d9IKxH4Ng40qPXL1gM27wWrrFa6RGq12oGvTqkVcomImzqK1ASSPWU3bSYiOy" + + "\n" + "2JOQvjLxjQw6emNYG09SlKrzNmXlbrZ4BfolL4eI8H2+3+UG4l/cXxPnLEeQzkvu" + + "\n" + "XuW5yajWoNBocEICcopmv8QgpwgiTUstmOTMFXD1EbVasonaH5R+wxBMB4Y1K+ot" + + "\n" + "eRawIyFA75FO8HCPoTBe5+Qb6G8+5i7nsgDtHG337G8JFz3hE3U++90zbYxxtjYx" + + "\n" + "Y2PYHfOwsDE8IDu1ZqzuB7lgrNADzOzelhSrcaW/jNHPGlxcsPTXl7S2QazgIPqk" + + "\n" + "kZ9g4ceXSsZOV9Yl4Bu2ODeUiVeYGGEXwJ7WAKNvaR3bMbhl+iwIQFy3A12/fz0w" + + "\n" + "B16C9qp7P9+5FEFWjlqi/28dSfECiDD4X4iyEe+sWTS86Cv0VsL300dIUQPIs65d" + + "\n" + "ddkrIkcpM4jyabKTZcltiQIlBBgBCgAPBQJVR5ubAhsMBQkHhh+AAAoJEHgWFzGc" + + "\n" + "4xHEo+UP/02AIUR0349zGk9a4D5Jv007y+d0tWKPL0V2btaq9xQzoM51CtuT0Zoq" + + "\n" + "TO8A0uFmEhCkw++reQcWOz1Nn+MarPjjJwMjhTPS/H1qiwTXmuwx92xLL0pajloq" + + "\n" + "7oWYwlxsgVGCMDYE0TOMN8p/Vc+eoJaWZh8yO1xJGDP98RHbZQWwYN6qLzE4y/EC" + + "\n" + "THxqi9UKc68qHNVH9ZgtTXnmgLAkEvXzRV1UOEEttJ6rrgPrTubjsIG+ckZK5mli" + + "\n" + "vy+UW6XN0WBE0oetKjT8/Cb1dQYiX/8MJkGcIUFRurU7gtGW3ncSTdr6WRXaQtfn" + + "\n" + "Rn9JG1aSXNYB/xZWzCBdykZp+tLuu4A3LVeOzn064hqf3rz2N7b8dWMk5WL5LIUh" + + "\n" + "XYoYc7232RkNSiiKndeJNryvTowFt9anuMj4pFgGveClQc9+QGyMVdTe6G5kOJkK" + + "\n" + "G8ydHKFEFObtsTLaut4lHTtxn+06QO/LKtQTXqNEyOyfYhbyX7xDbCbu4/MA23Mz" + + "\n" + "Ts1hhwgIy4+UejU/Yeny6VkBodA3bFyEYKWPoMDDgfdlZbzjv3qAN4Qq+ollo8K3" + + "\n" + "gJgH0QONrUaRY84/hil05T4EnUZiXdzPWvhMv5lEK+pTMlO8FbOG31+aB8rxCg+w" + + "\n" + "p1ovyC/fp5XjZaLHcyPAWAXKLBn4tb400iHp7byO85tF/H0OOI1K" + + "\n" + "=h0dN" + + "\n" + "-----END PGP PRIVATE KEY BLOCK-----", {}); + + Assert.equal(keyList.length, 1); + + let k = keyList[0]; + Assert.ok(k.isSecret); + Assert.equal(k.name, "anonymous strike "); + +}); + +test(function testBinary() { + let data = atob( + "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"); + + let keyList = EnigmailKey.getKeyListFromKeyBlock(data, {}); + + Assert.equal(keyList.length, 1); + + let k = keyList[0]; + Assert.equal(k.id, "781617319CE311C4"); + Assert.ok(!k.isSecret); + Assert.equal(k.name, "anonymous strike "); +}); diff --git a/package/tests/main.js b/package/tests/main.js index 6ab449e3..607586af 100644 --- a/package/tests/main.js +++ b/package/tests/main.js @@ -1,55 +1,56 @@ /*global do_subtest: false, Components: false */ /* * 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/. */ "use strict"; function execTest(filename) { const Cc = Components.classes; const Ci = Components.interfaces; let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); let testcases = env.get("JS_TEST"); if (testcases && testcases.length > 0) { if (testcases.search(filename) >= 0) do_subtest(filename); } else do_subtest(filename); } execTest("armor-test.js"); execTest("data-test.js"); execTest("system-test.js"); execTest("decryption-test.js"); execTest("decryptPermanently-test.js"); execTest("errorHandling-test.js"); execTest("encryption-test.js"); execTest("gpgAgent-test.js"); execTest("enigmail-test.js"); execTest("files-test.js"); execTest("streams-test.js"); +execTest("key-test.js"); execTest("keyRing-test.js"); execTest("keyEditor-test.js"); execTest("keyserver-test.js"); execTest("keyserverUris-test.js"); execTest("locale-test.js"); execTest("log-test.js"); execTest("mime-test.js"); execTest("os-test.js"); execTest("prefs-test.js"); execTest("rules-test.js"); execTest("funcs-test.js"); execTest("mimeDecrypt-test.js"); execTest("expiry-test.js"); execTest("installGnuPG-test.js"); execTest("keyRefreshService-test.js"); execTest("tor-test.js"); execTest("versioning-test.js"); execTest("rng-test.js"); execTest("filters-test.js"); // execTest("pep-test.js"); // not yet enabled