diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d8fd8c81..59027088 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,469 +1,439 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ import { createMessage } from './Message'; import { createKey } from './Key'; import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; /** * This class offers access to the gnupg keyring */ export class GPGME_Keyring { /** * Queries Keys (all Keys or a subset) from gnupg. * - * @param {Object} options + * @param {Object} options: * @param {String | Array} options.pattern (optional) A pattern to * search for in userIds or KeyIds. * @param {Boolean} options.prepare_sync (optional) if set to true, most * data (with the exception of armored Key blocks) will be cached for the * Keys. This enables direct, synchronous use of these properties for * all keys. It does not check for changes on the backend. The cached * information can be updated with the {@link Key.refresh} method. * @param {Boolean} options.search (optional) retrieve Keys from external * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) * @returns {Promise>} * @static * @async */ - getKeys (options){ - if (options && typeof options !== 'object'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } + getKeys ({ pattern, prepare_sync = false, search = false }){ return new Promise(function (resolve, reject) { let msg = createMessage('keylist'); - if (options && options.pattern) { - if ( - typeof options.pattern === 'string' - || Array.isArray(options.pattern) - ){ - msg.setParameter('keys', options.pattern); - } else { - reject(gpgme_error('PARAM_WRONG')); - } + if (pattern) { + msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); - if (options && options.search === true){ + if (search === true){ msg.setParameter('locate', true); } msg.post().then(function (result){ let resultset = []; if (result.keys.length === 0){ resolve([]); } else { let secondrequest; - if (options && options.prepare_sync === true) { + if (prepare_sync === true) { secondrequest = function () { let msg2 = createMessage('keylist'); - if (options.pattern){ - msg2.setParameter('keys', options.pattern); + if (pattern){ + msg2.setParameter('keys', pattern); } msg2.setParameter('secret', true); return msg2.post(); }; } else { secondrequest = function () { return Promise.resolve(true); }; } secondrequest().then(function (answer) { for (let i=0; i < result.keys.length; i++){ - if (options.prepare_sync === true){ + if (prepare_sync === true){ if (answer && answer.keys) { for (let j=0; j < answer.keys.length; j++ ){ const a = answer.keys[j]; const b = result.keys[i]; if ( a.fingerprint === b.fingerprint ) { if (a.secret === true){ b.hasSecret = true; } else { b.hasSecret = false; } break; } } } } let k = createKey(result.keys[i].fingerprint, - !options.prepare_sync, result.keys[i]); + !prepare_sync, result.keys[i]); resultset.push(k); } resolve(resultset); }, function (error){ reject(error); }); } }); }); } /** * @typedef {Object} exportResult The result of a getKeysArmored * operation. * @property {String} armored The public Key(s) as armored block. Note * that the result is one armored block, and not a block per key. * @property {Array} secret_fprs (optional) list of * fingerprints for those Keys that also have a secret Key available in * gnupg. The secret key will not be exported, but the fingerprint can * be used in operations needing a secret key. */ /** * Fetches the armored public Key blocks for all Keys matching the * pattern (if no pattern is given, fetches all keys known to gnupg). * @param {Object} options (optional) * @param {String|Array} options.pattern The Pattern to * search for * @param {Boolean} options.with_secret_fpr also return a list of * fingerprints for the keys that have a secret key available * @returns {Promise} Object containing the * armored Key(s) and additional information. * @static * @async */ - getKeysArmored (options) { - if (options && typeof options !== 'object'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } + getKeysArmored ({ pattern, with_secret_fpr }) { return new Promise(function (resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); - if (options.with_secret_fpr === true) { + if (with_secret_fpr === true) { msg.setParameter('with-sec-fprs', true); } - if (options.pattern){ - if ( - typeof options.pattern === 'string' - || Array.isArray(options.pattern) - ){ - msg.setParameter('keys', options.pattern); - } + if (pattern){ + msg.setParameter('keys', pattern); } msg.post().then(function (answer){ const result = { armored: answer.data }; - if (options.with_secret_fpr === true){ + if (with_secret_fpr === true){ if (answer.hasOwnProperty('sec-fprs')){ result.secret_fprs = answer['sec-fprs']; } else { result.secret_fprs = []; } } resolve(result); }, function (error){ reject(error); }); }); } /** * Returns the Key used by default in gnupg. * (a.k.a. 'primary Key or 'main key'). * It looks up the gpg configuration if set, or the first key that * contains a secret key. * * @returns {Promise} * @async * @static */ getDefaultKey (prepare_sync = false) { let me = this; return new Promise(function (resolve, reject){ let msg = createMessage('config_opt'); msg.setParameter('component', 'gpg'); msg.setParameter('option', 'default-key'); msg.post().then(function (resp){ if (resp.option !== undefined && resp.option.hasOwnProperty('value') && resp.option.value.length === 1 && resp.option.value[0].hasOwnProperty('string') && typeof (resp.option.value[0].string) === 'string'){ me.getKeys(resp.option.value[0].string, true).then( function (keys){ if (keys.length === 1){ resolve(keys[0]); } else { reject(gpgme_error('KEY_NO_DEFAULT')); } }, function (error){ reject(error); }); } else { let msg = createMessage('keylist'); msg.setParameter('secret', true); msg.post().then(function (result){ if (result.keys.length === 0){ reject(gpgme_error('KEY_NO_DEFAULT')); } else { for (let i=0; i< result.keys.length; i++ ) { if ( result.keys[i].invalid === false && result.keys[i].expired === false && result.keys[i].revoked === false && result.keys[i].can_sign === true ) { let k = createKey( result.keys[i].fingerprint, !prepare_sync, result.keys[i]); resolve(k); break; } else if (i === result.keys.length - 1){ reject(gpgme_error('KEY_NO_DEFAULT')); } } } }, function (error){ reject(error); }); } }, function (error){ reject(error); }); }); } /** * @typedef {Object} importResult The result of a Key update * @property {Object} summary Numerical summary of the result. See the * feedbackValues variable for available Keys values and the gnupg * documentation. * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html * for details on their meaning. * @property {Array} Keys Array of Object containing * GPGME_Keys with additional import information * */ /** * @typedef {Object} importedKeyResult * @property {GPGME_Key} key The resulting key * @property {String} status: * 'nochange' if the Key was not changed, * 'newkey' if the Key was imported in gpg, and did not exist * previously, * 'change' if the key existed, but details were updated. For details, * Key.changes is available. * @property {Boolean} changes.userId Changes in userIds * @property {Boolean} changes.signature Changes in signatures * @property {Boolean} changes.subkey Changes in subkeys */ /** * Import an armored Key block into gnupg. Note that this currently * will not succeed on private Key blocks. * @param {String} armored Armored Key block of the Key(s) to be * imported into gnupg * @param {Boolean} prepare_sync prepare the keys for synched use * (see {@link getKeys}). * @returns {Promise} A summary and Keys considered. * @async * @static */ importKey (armored, prepare_sync) { let feedbackValues = ['considered', 'no_user_id', 'imported', 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', 'new_signatures', 'new_revocations', 'secret_read', 'secret_imported', 'secret_unchanged', 'skipped_new_keys', 'not_imported', 'skipped_v3_keys']; if (!armored || typeof (armored) !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let me = this; return new Promise(function (resolve, reject){ let msg = createMessage('import'); msg.setParameter('data', armored); msg.post().then(function (response){ let infos = {}; let fprs = []; let summary = {}; for (let i=0; i < feedbackValues.length; i++ ){ summary[feedbackValues[i]] = response.result[feedbackValues[i]]; } if (!response.result.hasOwnProperty('imports') || response.result.imports.length === 0 ){ resolve({ Keys:[],summary: summary }); return; } for (let res=0; res} * @async * @static */ deleteKey (fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); return key.delete(); } else { return Promise.reject(gpgme_error('KEY_INVALID')); } } /** * Generates a new Key pair directly in gpg, and returns a GPGME_Key * representing that Key. Please note that due to security concerns, * secret Keys can not be deleted or exported from inside gpgme.js. * @param {Object} options * @param {String} option.userId The user Id, e.g. 'Foo Bar ' * @param {String} option.algo (optional) algorithm (and optionally key * size) to be used. See {@link supportedKeyAlgos} below for supported * values. If ommitted, 'default' is used. * @param {Number} option.expires (optional) Expiration time in seconds * from now. If not set or set to 0, expiration will be 'never' * @param {String} options.subkey_algo (optional) algorithm of the * encryption subkey. If ommited the same as algo is used. * * @return {Promise} * @async */ - generateKey (options){ - if (!options - || typeof options !== 'object' - || typeof options.userId !== 'string' + generateKey ({ userId, algo = 'default', expires= 0, subkey_algo }){ + if (typeof userId !== 'string' // eslint-disable-next-line no-use-before-define - || ( options.algo && supportedKeyAlgos.indexOf(options.algo) < 0 ) - || ( options.expires && !( - Number.isInteger(options.expires) || options.expires < 0 ) ) + || (algo && supportedKeyAlgos.indexOf(algo) < 0 ) + || (!Number.isInteger(expires) || expires < 0 ) ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } // eslint-disable-next-line no-use-before-define - if (options.subkey_algo && supportedKeyAlgos.indexOf( - options.subkey_algo) < 0 - ){ + if (subkey_algo && supportedKeyAlgos.indexOf(subkey_algo) < 0){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let me = this; return new Promise(function (resolve, reject){ let msg = createMessage('createkey'); - msg.setParameter('userid', options.userId); - if (!options.algo){ - options.algo === 'default'; - } - msg.setParameter('algo', options.algo); - if (options.subkey_algo) { - msg.setParameter('subkey-algo', options.subkey_algo ); - } - if (options.expires){ - msg.setParameter('expires', options.expires); - } else { - msg.setParameter('expires', 0); + msg.setParameter('userid', userId); + msg.setParameter('algo', algo); + if (subkey_algo) { + msg.setParameter('subkey-algo',subkey_algo ); } + msg.setParameter('expires', expires); msg.post().then(function (response){ me.getKeys(response.fingerprint, true).then( // TODO prepare_sync? function (result){ resolve(result); }, function (error){ reject(error); }); }, function (error) { reject(error); }); }); } } /** * List of algorithms supported for key generation. Please refer to the gnupg * documentation for details */ const supportedKeyAlgos = [ 'default', 'rsa', 'rsa2048', 'rsa3072', 'rsa4096', 'dsa', 'dsa2048', 'dsa3072', 'dsa4096', 'elg', 'elg2048', 'elg3072', 'elg4096', 'ed25519', 'cv25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', 'NIST P-256', 'NIST P-384', 'NIST P-521' ]; \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index ac640308..ffee719c 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,427 +1,414 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ import { GPGME_Message, createMessage } from './Message'; import { toKeyIdArray } from './Helpers'; import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; import { createSignature } from './Signature'; /** * @typedef {Object} decrypt_result * @property {String|Uint8Array} data The decrypted data * @property {String} format Indicating how the data was converted after being * received from gpgme. * 'string': Data was decoded into an utf-8 string, * 'base64': Data was not processed and is a base64 string * 'uint8': data was turned into a Uint8Array * @property {Boolean} is_mime (optional) the data claims to be a MIME * object. * @property {String} file_name (optional) the original file name * @property {signatureDetails} signatures Verification details for * signatures */ /** * @typedef {Object} signatureDetails * @property {Boolean} all_valid Summary if all signatures are fully valid * @property {Number} count Number of signatures found * @property {Number} failures Number of invalid signatures * @property {Array} signatures.good All valid signatures * @property {Array} signatures.bad All invalid signatures */ /** * @typedef {Object} encrypt_result The result of an encrypt operation * @property {String} data The encrypted message * @property {Boolean} base64 Indicating whether returning payload data is * base64 encoded */ /** * @typedef { GPGME_Key | String | Object } inputKeys * Accepts different identifiers of a gnupg Key that can be parsed by * {@link toKeyIdArray}. Expected inputs are: One or an array of * GPGME_Keys; one or an array of fingerprint strings; one or an array of * openpgpjs Key objects. */ /** * @typedef {Object} signResult The result of a signing operation * @property {String} data The resulting data. Includes the signature in * clearsign mode * @property {String} signature The detached signature (if in detached mode) */ /** @typedef {Object} verifyResult The result of a verification * @property {Boolean} data: The verified data * @property {Boolean} is_mime (optional) the data claims to be a MIME * object. * @property {String} file_name (optional) the original file name * @property {signatureDetails} signatures Verification details for * signatures */ /** * The main entry point for gpgme.js. * @class */ export class GpgME { constructor (){ this._Keyring = null; } /** * setter for {@link setKeyring}. * @param {GPGME_Keyring} keyring A Keyring to use */ set Keyring (keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } /** * Accesses the {@link GPGME_Keyring}. */ get Keyring (){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; } return this._Keyring; } /** * Encrypt (and optionally sign) data * @param {Object} options * @param {String|Object} options.data text/data to be encrypted as String. * Also accepts Objects with a getText method * @param {inputKeys} options.publicKeys * Keys used to encrypt the message * @param {inputKeys} opions.secretKeys (optional) Keys used to sign the * message. If Keys are present, the operation requested is assumed * to be 'encrypt and sign' * @param {Boolean} options.base64 (optional) The data will be interpreted * as base64 encoded data. * @param {Boolean} options.armor (optional) Request the output as armored * block. * @param {Boolean} options.wildcard (optional) If true, recipient * information will not be added to the message. * @param {Object} additional use additional valid gpg options as * defined in {@link permittedOperations} * @returns {Promise} Object containing the encrypted * message and additional info. * @async */ - encrypt (options){ - if (!options || (typeof options !== 'object')){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - if (!options.hasOwnProperty('data') - || !options.hasOwnProperty('publicKeys') - ){ + encrypt ({ data, publicKeys, secretKeys, base64 = false, armor = true, + wildcard, additional = {} }){ + if (!data || !publicKeys){ return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg); } - if (!options.hasOwnProperty('armor')){ - options.armor = true; - } - msg.setParameter('armor', options.armor); + msg.setParameter('armor', armor); - if (options.base64 === true) { + if (base64 === true) { msg.setParameter('base64', true); } - let pubkeys = toKeyIdArray(options.publicKeys); + let pubkeys = toKeyIdArray(publicKeys); + if (!pubkeys.length) { + return Promise.reject(gpgme_error('MSG_NO_KEYS')); + } msg.setParameter('keys', pubkeys); - let sigkeys = toKeyIdArray(options.secretKeys); + let sigkeys = toKeyIdArray(secretKeys); if (sigkeys.length > 0) { msg.setParameter('signing_keys', sigkeys); } - putData(msg, options.data); - if (options.wildcard === true){ + putData(msg, data); + if (wildcard === true){ msg.setParameter('throw-keyids', true); } - if (options.additional){ - let additional_Keys = Object.keys(options.additional); + if (additional){ + let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { try { msg.setParameter(additional_Keys[k], - options.additional[additional_Keys[k]]); + additional[additional_Keys[k]]); } catch (error){ return Promise.reject(error); } } } if (msg.isComplete() === true){ return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } } /** * Decrypts a Message * @param {Object} options * @param {String|Object} options.data text/data to be decrypted. Accepts * Strings and Objects with a getText method * @param {Boolean} options.base64 (optional) false if the data is an * armored block, true if it is base64 encoded binary data * @param {String} options.expect (optional) can be set to 'uint8' or * 'base64'. Does no extra decoding on the data, and returns the decoded * data as either Uint8Array or unprocessed(base64 encoded) string. * @returns {Promise} Decrypted Message and information * @async */ - decrypt (options){ - if (!options || (typeof options !== 'object')){ - return Promise.reject('PARAM_WRONG'); - } - if (!options.data){ + decrypt ({ data, base64, expect }){ + if (!data){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (msg instanceof Error){ return Promise.reject(msg); } - if (options.base64 === true){ + if (base64 === true){ msg.setParameter('base64', true); } - if (options.expect === 'base64' || options.expect === 'uint8'){ - msg.expected = options.expect; + if (expect === 'base64' || expect === 'uint8'){ + msg.expected = expect; } - putData(msg, options.data); + putData(msg, data); return new Promise(function (resolve, reject){ msg.post().then(function (result){ let _result = { data: result.data }; _result.format = result.format ? result.format : null; if (result.hasOwnProperty('dec_info')){ _result.is_mime = result.dec_info.is_mime ? true: false; if (result.dec_info.file_name) { _result.file_name = result.dec_info.file_name; } } if (!result.file_name) { _result.file_name = null; } if (result.hasOwnProperty('info') && result.info.hasOwnProperty('signatures') && Array.isArray(result.info.signatures) ) { _result.signatures = collectSignatures( result.info.signatures); } if (_result.signatures instanceof Error){ reject(_result.signatures); } else { resolve(_result); } }, function (error){ reject(error); }); }); } /** * Sign a Message * @param {Object} options Signing options * @param {String|Object} options.data text/data to be signed. Accepts * Strings and Objects with a getText method. * @param {inputKeys} options.keys The key/keys to use for signing * @param {String} options.mode The signing mode. Currently supported: * 'clearsign':The Message is embedded into the signature; * 'detached': The signature is stored separately * @param {Boolean} options.base64 input is considered base64 * @returns {Promise} * @async */ - sign (options){ - if ( - !options || (typeof options !== 'object')){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - if (!options.data){ + sign ({ data, keys, mode = 'clearsign', base64 }){ + if (!data){ return Promise.reject(gpgme_error('MSG_EMPTY')); } - if (!options.mode) { - options.mode = 'clearsign'; - } - let key_arr = toKeyIdArray(options.keys); + let key_arr = toKeyIdArray(keys); if (key_arr.length === 0){ return Promise.reject(gpgme_error('MSG_NO_KEYS')); } - let msg = createMessage('sign'); + let msg = createMessage('sign'); msg.setParameter('keys', key_arr); - if (options.base64 === true){ + if (base64 === true){ msg.setParameter('base64', true); } - msg.setParameter('mode', options.mode); - putData(msg, options.data); + msg.setParameter('mode', mode); + putData(msg, data); + return new Promise(function (resolve,reject) { msg.post().then( function (message) { - if (options.mode === 'clearsign'){ + if (mode === 'clearsign'){ resolve({ data: message.data } ); - } else if (options.mode === 'detached') { + } else if (mode === 'detached') { resolve({ - data: options.data, + data: data, signature: message.data }); } }, function (error){ reject(error); }); }); } /** * Verifies data. * @param {Object} options * @param {String|Object} options.data text/data to be verified. Accepts * Strings and Objects with a getText method * @param {String} options.signature A detached signature. If not present, * opaque mode is assumed * @param {Boolean} options.base64 Indicating that data and signature are * base64 encoded * @returns {Promise} *@async */ - verify (options){ - if (!options || (typeof options !== 'object') || !options.data){ + verify ({ data, signature, base64 }){ + if (!data){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let msg = createMessage('verify'); - let dt = putData(msg, options.data); + let dt = putData(msg, data); if (dt instanceof Error){ return Promise.reject(dt); } - if (options.signature){ + if (signature){ if (typeof signature !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } else { msg.setParameter('signature', signature); } } - if (options.base64 === true){ + if (base64 === true){ msg.setParameter('base64', true); } return new Promise(function (resolve, reject){ msg.post().then(function (message){ if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { let _result = { signatures: collectSignatures(message.info.signatures) }; if (_result.signatures instanceof Error){ reject(_result.signatures); } else { _result.is_mime = message.info.is_mime? true: false; if (message.info.filename){ _result.file_name = message.info.filename; } _result.data = message.data; resolve(_result); } } }, function (error){ reject(error); }); }); } } /** * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set * @param { String| Object } data The data to enter. Expects either a string of * data, or an object with a getText method * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise * @private */ function putData (message, data){ if (!message || !(message instanceof GPGME_Message)) { return gpgme_error('PARAM_WRONG'); } if (!data){ return gpgme_error('PARAM_WRONG'); } else if (typeof data === 'string') { message.setParameter('data', data); } else if ( (typeof data === 'object') && (typeof data.getText === 'function') ){ let txt = data.getText(); if (typeof txt === 'string'){ message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } } else { return gpgme_error('PARAM_WRONG'); } } /** * Parses, validates and converts incoming objects into signatures. * @param {Array} sigs * @returns {signatureDetails} Details about the signatures */ function collectSignatures (sigs){ if (!Array.isArray(sigs)){ return gpgme_error('SIG_NO_SIGS'); } let summary = { all_valid: false, count: sigs.length, failures: 0, signatures: { good: [], bad: [], } }; for (let i=0; i< sigs.length; i++){ let sigObj = createSignature(sigs[i]); if (sigObj instanceof Error) { return gpgme_error('SIG_WRONG'); } if (sigObj.valid !== true){ summary.failures += 1; summary.signatures.bad.push(sigObj); } else { summary.signatures.good.push(sigObj); } } if (summary.failures === 0){ summary.all_valid = true; } return summary; } \ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 212effd3..8f1ffb66 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,379 +1,381 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ import './node_modules/mocha/mocha'; /* global mocha, it, describe*/ import './node_modules/chai/chai';/* global chai*/ import { helper_params as hp } from './unittest_inputvalues'; import { message_params as mp } from './unittest_inputvalues'; import { whatever_params as wp } from './unittest_inputvalues'; import { key_params as kp } from './unittest_inputvalues'; import { Connection } from './src/Connection'; import { gpgme_error, err_list } from './src/Errors'; import { toKeyIdArray , isFingerprint } from './src/Helpers'; import { createKey } from './src/Key'; import { GPGME_Keyring } from './src/Keyring'; import { GPGME_Message, createMessage } from './src/Message'; mocha.setup('bdd'); const expect = chai.expect; chai.config.includeStack = true; function unittests (){ describe('Connection testing', function (){ it('Connecting', function (done) { let conn0 = new Connection; conn0.checkConnection().then(function (answer) { expect(answer).to.not.be.empty; expect(answer.gpgme).to.not.be.undefined; expect(answer.gpgme).to.be.a('string'); expect(answer.info).to.be.an('Array'); expect(conn0.disconnect).to.be.a('function'); expect(conn0.post).to.be.a('function'); done(); }); }); it('Disconnecting', function (done) { let conn0 = new Connection; conn0.checkConnection(false).then(function (answer) { expect(answer).to.be.true; conn0.disconnect(); conn0.checkConnection(false).then(function (result) { expect(result).to.be.false; done(); }); }); }); }); describe('Error Object handling', function (){ // TODO: new GPGME_Error codes it('check the Timeout error', function (){ let test0 = gpgme_error('CONN_TIMEOUT'); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('CONN_TIMEOUT'); }); it('Error Object returns generic code if code is not listed', function (){ let test0 = gpgme_error(hp.invalidErrorCode); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('GENERIC_ERROR'); } ); it('Warnings like PARAM_IGNORED should not return errors', function (){ let test0 = gpgme_error('PARAM_IGNORED'); expect(test0).to.be.null; }); }); describe('Fingerprint checking', function (){ it('isFingerprint(): valid Fingerprint', function (){ let test0 = isFingerprint(hp.validFingerprint); expect(test0).to.be.true; }); it('isFingerprint(): invalid Fingerprints', function (){ for (let i=0; i < hp.invalidFingerprints.length; i++){ let test0 = isFingerprint(hp.invalidFingerprints[i]); expect(test0).to.be.false; } }); }); describe('toKeyIdArray() (converting input to fingerprint)', function (){ it('Correct fingerprint string', function (){ let test0 = toKeyIdArray(hp.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validFingerprint); }); it('openpgpjs-like object', function (){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); expect(test0).to.include( hp.valid_openpgplike.primaryKey.getFingerprint()); }); it('Array of valid inputs', function (){ let test0 = toKeyIdArray(hp.validKeys); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf(hp.validKeys.length); }); it('Incorrect inputs', function (){ it('valid Long ID', function (){ let test0 = toKeyIdArray(hp.validLongId); expect(test0).to.be.empty; }); it('invalidFingerprint', function (){ let test0 = toKeyIdArray(hp.invalidFingerprint); expect(test0).to.be.empty; }); it('invalidKeyArray', function (){ let test0 = toKeyIdArray(hp.invalidKeyArray); expect(test0).to.be.empty; }); it('Partially invalid array', function (){ let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf( hp.invalidKeyArray_OneBad.length - 1); }); }); }); describe('GPGME_Key', function (){ it('Key has data after a first refresh', function (done) { let key = createKey(kp.validKeyFingerprint); key.refreshKey().then(function (key2){ expect(key2.get).to.be.a('function'); for (let i=0; i < kp.validKeyProperties.length; i++) { let prop = key2.get(kp.validKeyProperties[i]); expect(prop).to.not.be.undefined; expect(prop).to.be.a('boolean'); } expect(isFingerprint(key2.get('fingerprint'))).to.be.true; expect( key2.get('fingerprint')).to.equal(kp.validKeyFingerprint); expect( key2.get('fingerprint')).to.equal(key.fingerprint); done(); }); }); it('Non-cached key async data retrieval', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('can_authenticate').then(function (result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async armored Key', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('armored').then(function (result){ expect(result).to.be.a('string'); expect(result).to.include('KEY BLOCK-----'); done(); }); }); it('Non-cached key async hasSecret', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('hasSecret').then(function (result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async hasSecret (no secret in Key)', function (done){ let key = createKey(kp.validFingerprintNoSecret, true); key.get('hasSecret').then(function (result){ expect(result).to.be.a('boolean'); expect(result).to.equal(false); done(); }); }); it('Querying non-existing Key returns an error', function (done) { let key = createKey(kp.invalidKeyFingerprint); key.refreshKey().then(function (){}, function (error){ expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('KEY_NOKEY'); done(); }); }); it('createKey returns error if parameters are wrong', function (){ for (let i=0; i< 4; i++){ expect(function (){ createKey(wp.four_invalid_params[i]); }).to.throw( err_list.PARAM_WRONG.msg ); } }); // it('Overwriting getFingerprint does not work', function(){ // const evilFunction = function(){ // return 'bad Data'; // }; // let key = createKey(kp.validKeyFingerprint, true); // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); // try { // key.getFingerprint = evilFunction; // } // catch(e) { // expect(e).to.be.an.instanceof(TypeError); // } // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); // expect(key.getFingerprint).to.not.equal(evilFunction); // }); }); describe('GPGME_Keyring', function (){ it('correct Keyring initialization', function (){ let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.getKeys).to.be.a('function'); }); it('Loading Keys from Keyring, to be used synchronously', function (done){ let keyring = new GPGME_Keyring; - keyring.getKeys(null, true).then(function (result){ + keyring.getKeys({ prepare_sync: true }).then(function (result){ expect(result).to.be.an('array'); expect(result[0].get('hasSecret')).to.be.a('boolean'); done(); }); } ); it('Loading specific Key from Keyring, to be used synchronously', function (done){ let keyring = new GPGME_Keyring; - keyring.getKeys(kp.validKeyFingerprint, true).then( + keyring.getKeys({ + pattern: kp.validKeyFingerprint, + prepare_sync: true }).then( function (result){ expect(result).to.be.an('array'); expect(result[0].get('hasSecret')).to.be.a('boolean'); done(); } ); } ); it('Querying non-existing Key from Keyring', function (done){ let keyring = new GPGME_Keyring; keyring.getKeys(kp.invalidKeyFingerprint, true).then( function (result){ expect(result).to.be.an('array'); expect(result.length).to.equal(0); done(); } ); }); }); describe('GPGME_Message', function (){ it('creating encrypt Message', function (){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); expect(test0.isComplete()).to.be.false; }); it('Message is complete after setting mandatory data', function (){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete()).to.be.true; }); it('Message is not complete after mandatory data is empty', function (){ let test0 = createMessage('encrypt'); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete()).to.be.false; expect(function (){ test0.setParameter('data', ''); }).to.throw( err_list.PARAM_WRONG.msg); }); it('Complete Message contains the data that was set', function (){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.message).to.not.be.null; expect(test0.message).to.have.keys('op', 'data', 'keys', 'chunksize'); expect(test0.message.op).to.equal('encrypt'); expect(test0.message.data).to.equal( mp.valid_encrypt_data); }); it ('Not accepting non-allowed operation', function (){ expect(function () { createMessage(mp.invalid_op_action); }).to.throw( err_list.MSG_WRONG_OP.msg); }); it('Not accepting wrong parameter type', function (){ expect(function () { createMessage(mp.invalid_op_type); }).to.throw( err_list.PARAM_WRONG.msg); }); it('Not accepting wrong parameter name', function (){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let i=0; i < mp.invalid_param_test.invalid_param_names.length; i++){ expect(function (){ test0.setParameter( mp.invalid_param_test.invalid_param_names[i], 'Somevalue');} ).to.throw(err_list.PARAM_WRONG.msg); } }); it('Not accepting wrong parameter value', function (){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let j=0; j < mp.invalid_param_test.invalid_values_0.length; j++){ expect(function (){ test0.setParameter( mp.invalid_param_test.validparam_name_0, mp.invalid_param_test.invalid_values_0[j]); }).to.throw(err_list.PARAM_WRONG.msg); } }); }); } export default { unittests }; \ No newline at end of file