diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 53e7bcd7..73418028 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,169 +1,169 @@ /* 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 */ /** * Listing of all possible error codes and messages of a {@link GPGME_Error}. */ -const err_list = { +export const err_list = { // Connection 'CONN_NO_CONNECT': { msg:'Connection with the nativeMessaging host could not be' + ' established.', type: 'error' }, 'CONN_EMPTY_GPG_ANSWER':{ msg: 'The nativeMessaging answer was empty.', type: 'error' }, 'CONN_TIMEOUT': { msg: 'A connection timeout was exceeded.', type: 'error' }, 'CONN_UNEXPECTED_ANSWER': { msg: 'The answer from gnupg was not as expected.', type: 'error' }, 'CONN_ALREADY_CONNECTED':{ msg: 'A connection was already established.', type: 'warning' }, // Message/Data 'MSG_INCOMPLETE': { msg: 'The Message did not match the minimum requirements for' + ' the interaction.', type: 'error' }, 'MSG_EMPTY' : { msg: 'The Message is empty.', type: 'error' }, 'MSG_WRONG_OP': { msg: 'The operation requested could not be found', type: 'error' }, 'MSG_NO_KEYS' : { msg: 'There were no valid keys provided.', type: 'warning' }, 'MSG_NOT_A_FPR': { msg: 'The String is not an accepted fingerprint', type: 'warning' }, 'KEY_INVALID': { msg:'Key object is invalid', type: 'error' }, 'KEY_NOKEY': { msg:'This key does not exist in GPG', type: 'error' }, 'KEY_NO_INIT': { msg:'This property has not been retrieved yet from GPG', type: 'error' }, 'KEY_ASYNC_ONLY': { msg: 'This property cannot be used in synchronous calls', type: 'error' }, 'KEY_NO_DEFAULT': { msg:'A default key could not be established. Please check yout gpg ' + 'configuration', type: 'error' }, 'SIG_WRONG': { msg:'A malformed signature was created', type: 'error' }, 'SIG_NO_SIGS': { msg:'There were no signatures found', type: 'error' }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', type: 'error' }, 'PARAM_IGNORED': { msg: 'An parameter was set that has no effect in gpgmejs', type: 'warning' }, 'GENERIC_ERROR': { msg: 'Unspecified error', type: 'error' } }; /** * Checks the given error code and returns an {@link GPGME_Error} error object * with some information about meaning and origin * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR' * @param {*} info Error message passed through if code is 'GNUPG_ERROR' * @returns {GPGME_Error} */ export function gpgme_error (code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ // eslint-disable-next-line no-console // console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ return new GPGME_Error(code, info); } else { return new GPGME_Error('GENERIC_ERROR'); } } /** * An error class with additional info about the origin of the error, as string * @property {String} code Short description of origin and type of the error * @property {String} msg Additional info * @class * @protected * @extends Error */ class GPGME_Error extends Error{ constructor (code = 'GENERIC_ERROR', msg=''){ if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ super(err_list[code].msg + '--' + msg); } else { super(err_list[code].msg); } } else { super(err_list['GENERIC_ERROR'].msg); } this._code = code; } get code (){ return this._code; } } \ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 2800ae9a..d0f87eda 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,688 +1,688 @@ /* 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 { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; import { createMessage } from './Message'; /** * Validates the given fingerprint and creates a new {@link GPGME_Key} * @param {String} fingerprint * @param {Boolean} async If True, Key properties (except fingerprint) will be * queried from gnupg on each call, making the operation up-to-date, the * answers will be Promises, and the performance will likely suffer * @param {Object} data additional initial properties this Key will have. Needs * a full object as delivered by gpgme-json - * @returns {Object|GPGME_Error} The verified and updated data + * @returns {Object} The verified and updated data */ export function createKey (fingerprint, async = false, data){ if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } if (data !== undefined){ data = validateKeyData(fingerprint, data); } if (data instanceof Error){ - return gpgme_error('KEY_INVALID'); + throw gpgme_error('KEY_INVALID'); } else { return new GPGME_Key(fingerprint, async, data); } } /** * Represents the Keys as stored in the gnupg backend * It allows to query almost all information defined in gpgme Key Objects * Refer to {@link validKeyProperties} for available information, and the gpgme * documentation on their meaning * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) * * @class */ class GPGME_Key { constructor (fingerprint, async, data){ /** * @property {Boolean} If true, most answers will be asynchronous */ this._async = async; this._data = { fingerprint: fingerprint.toUpperCase() }; if (data !== undefined && data.fingerprint.toUpperCase() === this._data.fingerprint ) { this._data = data; } } /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @returns {Boolean| String | Date | Array | Object |GPGME_Error} + * @returns {Boolean| String | Date | Array | Object} * the value of the property. If the Key is set to Async, the value * will be fetched from gnupg and resolved as a Promise. If Key is not * async, the armored property is not available (it can still be * retrieved asynchronously by {@link Key.getArmor}) */ get (property) { if (this._async === true) { switch (property){ case 'armored': return this.getArmor(); case 'hasSecret': return this.getGnupgSecretState(); default: return getGnupgState(this.fingerprint, property); } } else { if (property === 'armored') { - return gpgme_error('KEY_ASYNC_ONLY'); + throw gpgme_error('KEY_ASYNC_ONLY'); } // eslint-disable-next-line no-use-before-define if (!validKeyProperties.hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } else { return (this._data[property]); } } } /** * Reloads the Key information from gnupg. This is only useful if you * use the GPGME_Keys cached. Note that this is a performance hungry * operation. If you desire more than a few refreshs, it may be * advisable to run {@link Keyring.getKeys} instead. * @returns {Promise} * @async */ refreshKey () { let me = this; return new Promise(function (resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', me._data.fingerprint); msg.post().then(function (result){ if (result.keys.length === 1){ const newdata = validateKeyData( me._data.fingerprint, result.keys[0]); if (newdata instanceof Error){ reject(gpgme_error('KEY_INVALID')); } else { me._data = newdata; me.getGnupgSecretState().then(function (){ me.getArmor().then(function (){ resolve(me); }, function (error){ reject(error); }); }, function (error){ reject(error); }); } } else { reject(gpgme_error('KEY_NOKEY')); } }, function (error) { reject(gpgme_error('GNUPG_ERROR'), error); }); }); } /** * Query the armored block of the Key directly from gnupg. Please note * that this will not get you any export of the secret/private parts of * a Key * @returns {Promise} * @async */ getArmor () { const me = this; return new Promise(function (resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('export'); msg.setParameter('armor', true); msg.setParameter('keys', me._data.fingerprint); msg.post().then(function (result){ resolve(result.data); }, function (error){ reject(error); }); }); } /** * Find out if the Key is part of a Key pair including public and * private key(s). If you want this information about more than a few * Keys in synchronous mode, it may be advisable to run * {@link Keyring.getKeys} instead, as it performs faster in bulk * querying this state. * @returns {Promise} True if a private Key is * available in the gnupg Keyring. * @async */ getGnupgSecretState (){ const me = this; return new Promise(function (resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } else { let msg = createMessage('keylist'); msg.setParameter('keys', me._data.fingerprint); msg.setParameter('secret', true); msg.post().then(function (result){ me._data.hasSecret = null; if ( result.keys && result.keys.length === 1 && result.keys[0].secret === true ) { me._data.hasSecret = true; resolve(true); } else { me._data.hasSecret = false; resolve(false); } }, function (error){ reject(error); }); } }); } /** * Deletes the (public) Key from the GPG Keyring. Note that a deletion * of a secret key is not supported by the native backend. * @returns {Promise} Success if key was deleted, * rejects with a GPG error otherwise. */ delete (){ const me = this; return new Promise(function (resolve, reject){ if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('delete'); msg.setParameter('key', me._data.fingerprint); msg.post().then(function (result){ resolve(result.success); }, function (error){ reject(error); }); }); } /** * @returns {String} The fingerprint defining this Key. Convenience getter */ get fingerprint (){ return this._data.fingerprint; } } /** * Representing a subkey of a Key. * @class * @protected */ class GPGME_Subkey { /** * Initializes with the json data sent by gpgme-json * @param {Object} data * @private */ constructor (data){ this._data = {}; let keys = Object.keys(data); const me = this; /** * Validates a subkey property against {@link validSubKeyProperties} and * sets it if validation is successful * @param {String} property * @param {*} value * @param private */ const setProperty = function (property, value){ // eslint-disable-next-line no-use-before-define if (validSubKeyProperties.hasOwnProperty(property)){ // eslint-disable-next-line no-use-before-define if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ me._data[property] = new Date(value * 1000); } else { me._data[property] = value; } } } }; for (let i=0; i< keys.length; i++) { setProperty(keys[i], data[keys[i]]); } } /** * Fetches any information about this subkey * @param {String} property Information to request * @returns {String | Number | Date} */ get (property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } } } /** * Representing user attributes associated with a Key or subkey * @class * @protected */ class GPGME_UserId { /** * Initializes with the json data sent by gpgme-json * @param {Object} data * @private */ constructor (data){ this._data = {}; const me = this; let keys = Object.keys(data); const setProperty = function (property, value){ // eslint-disable-next-line no-use-before-define if (validUserIdProperties.hasOwnProperty(property)){ // eslint-disable-next-line no-use-before-define if (validUserIdProperties[property](value) === true) { if (property === 'last_update'){ me._data[property] = new Date(value*1000); } else { me._data[property] = value; } } } }; for (let i=0; i< keys.length; i++) { setProperty(keys[i], data[keys[i]]); } } /** * Fetches information about the user * @param {String} property Information to request * @returns {String | Number} */ get (property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } } } /** * Validation definition for userIds. Each valid userId property is represented * as a key- Value pair, with their value being a validation function to check * against * @protected * @const */ const validUserIdProperties = { 'revoked': function (value){ return typeof (value) === 'boolean'; }, 'invalid': function (value){ return typeof (value) === 'boolean'; }, 'uid': function (value){ if (typeof (value) === 'string' || value === ''){ return true; } return false; }, 'validity': function (value){ if (typeof (value) === 'string'){ return true; } return false; }, 'name': function (value){ if (typeof (value) === 'string' || value === ''){ return true; } return false; }, 'email': function (value){ if (typeof (value) === 'string' || value === ''){ return true; } return false; }, 'address': function (value){ if (typeof (value) === 'string' || value === ''){ return true; } return false; }, 'comment': function (value){ if (typeof (value) === 'string' || value === ''){ return true; } return false; }, 'origin': function (value){ return Number.isInteger(value); }, 'last_update': function (value){ return Number.isInteger(value); } }; /** * Validation definition for subKeys. Each valid userId property is represented * as a key-value pair, with the value being a validation function * @protected * @const */ const validSubKeyProperties = { 'invalid': function (value){ return typeof (value) === 'boolean'; }, 'can_encrypt': function (value){ return typeof (value) === 'boolean'; }, 'can_sign': function (value){ return typeof (value) === 'boolean'; }, 'can_certify': function (value){ return typeof (value) === 'boolean'; }, 'can_authenticate': function (value){ return typeof (value) === 'boolean'; }, 'secret': function (value){ return typeof (value) === 'boolean'; }, 'is_qualified': function (value){ return typeof (value) === 'boolean'; }, 'is_cardkey': function (value){ return typeof (value) === 'boolean'; }, 'is_de_vs': function (value){ return typeof (value) === 'boolean'; }, 'pubkey_algo_name': function (value){ return typeof (value) === 'string'; // TODO: check against list of known?[''] }, 'pubkey_algo_string': function (value){ return typeof (value) === 'string'; // TODO: check against list of known?[''] }, 'keyid': function (value){ return isLongId(value); }, 'pubkey_algo': function (value) { return (Number.isInteger(value) && value >= 0); }, 'length': function (value){ return (Number.isInteger(value) && value > 0); }, 'timestamp': function (value){ return (Number.isInteger(value) && value > 0); }, 'expires': function (value){ return (Number.isInteger(value) && value > 0); } }; /** * Validation definition for Keys. Each valid Key property is represented * as a key-value pair, with their value being a validation function. For * details on the meanings, please refer to the gpgme documentation * https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#Key-objects * @param {String} fingerprint * @param {Boolean} revoked * @param {Boolean} expired * @param {Boolean} disabled * @param {Boolean} invalid * @param {Boolean} can_encrypt * @param {Boolean} can_sign * @param {Boolean} can_certify * @param {Boolean} can_authenticate * @param {Boolean} secret * @param {Boolean}is_qualified * @param {String} protocol * @param {String} issuer_serial * @param {String} issuer_name * @param {Boolean} chain_id * @param {String} owner_trust * @param {Date} last_update * @param {String} origin * @param {Array} subkeys * @param {Array} userids * @param {Array} tofu * @param {Boolean} hasSecret * @protected * @const */ const validKeyProperties = { 'fingerprint': function (value){ return isFingerprint(value); }, 'revoked': function (value){ return typeof (value) === 'boolean'; }, 'expired': function (value){ return typeof (value) === 'boolean'; }, 'disabled': function (value){ return typeof (value) === 'boolean'; }, 'invalid': function (value){ return typeof (value) === 'boolean'; }, 'can_encrypt': function (value){ return typeof (value) === 'boolean'; }, 'can_sign': function (value){ return typeof (value) === 'boolean'; }, 'can_certify': function (value){ return typeof (value) === 'boolean'; }, 'can_authenticate': function (value){ return typeof (value) === 'boolean'; }, 'secret': function (value){ return typeof (value) === 'boolean'; }, 'is_qualified': function (value){ return typeof (value) === 'boolean'; }, 'protocol': function (value){ return typeof (value) === 'string'; // TODO check for implemented ones }, 'issuer_serial': function (value){ return typeof (value) === 'string'; }, 'issuer_name': function (value){ return typeof (value) === 'string'; }, 'chain_id': function (value){ return typeof (value) === 'string'; }, 'owner_trust': function (value){ return typeof (value) === 'string'; }, 'last_update': function (value){ return (Number.isInteger(value)); // TODO undefined/null possible? }, 'origin': function (value){ return (Number.isInteger(value)); }, 'subkeys': function (value){ return (Array.isArray(value)); }, 'userids': function (value){ return (Array.isArray(value)); }, 'tofu': function (value){ return (Array.isArray(value)); }, 'hasSecret': function (value){ return typeof (value) === 'boolean'; } }; /** * sets the Key data in bulk. It can only be used from inside a Key, either * during construction or on a refresh callback. * @param {Object} key the original internal key data. * @param {Object} data Bulk set the data for this key, with an Object structure * as sent by gpgme-json. * @returns {Object|GPGME_Error} the changed data after values have been set, * an error if something went wrong. * @private */ function validateKeyData (fingerprint, data){ const key = {}; if (!fingerprint || typeof (data) !== 'object' || !data.fingerprint || fingerprint !== data.fingerprint.toUpperCase() ){ return gpgme_error('KEY_INVALID'); } let props = Object.keys(data); for (let i=0; i< props.length; i++){ if (!validKeyProperties.hasOwnProperty(props[i])){ return gpgme_error('KEY_INVALID'); } // running the defined validation function if (validKeyProperties[props[i]](data[props[i]]) !== true ){ return gpgme_error('KEY_INVALID'); } switch (props[i]){ case 'subkeys': key.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { key.subkeys.push( new GPGME_Subkey(data.subkeys[i])); } break; case 'userids': key.userids = []; for (let i=0; i< data.userids.length; i++) { key.userids.push( new GPGME_UserId(data.userids[i])); } break; case 'last_update': key[props[i]] = new Date( data[props[i]] * 1000 ); break; default: key[props[i]] = data[props[i]]; } } return key; } /** * Fetches and sets properties from gnupg * @param {String} fingerprint * @param {String} property to search for. * @private * @async */ function getGnupgState (fingerprint, property){ return new Promise(function (resolve, reject) { if (!isFingerprint(fingerprint)) { reject(gpgme_error('KEY_INVALID')); } else { let msg = createMessage('keylist'); msg.setParameter('keys', fingerprint); msg.post().then(function (res){ if (!res.keys || res.keys.length !== 1){ reject(gpgme_error('KEY_INVALID')); } else { const key = res.keys[0]; let result; switch (property){ case 'subkeys': result = []; if (key.subkeys.length){ for (let i=0; i < key.subkeys.length; i++) { result.push( new GPGME_Subkey(key.subkeys[i])); } } resolve(result); break; case 'userids': result = []; if (key.userids.length){ for (let i=0; i< key.userids.length; i++) { result.push( new GPGME_UserId(key.userids[i])); } } resolve(result); break; case 'last_update': if (key.last_update === undefined){ reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } else if (key.last_update !== null){ resolve(new Date( key.last_update * 1000)); } else { resolve(null); } break; default: if (!validKeyProperties.hasOwnProperty(property)){ reject(gpgme_error('PARAM_WRONG')); } else { if (key.hasOwnProperty(property)){ resolve(key[property]); } else { reject(gpgme_error( 'CONN_UNEXPECTED_ANSWER')); } } break; } } }, function (error){ reject(gpgme_error(error)); }); } }); } \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index ab0144ef..cb053ba1 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,433 +1,435 @@ /* 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 {String | Array} pattern (optional) A pattern to * search for in userIds or KeyIds. * @param {Boolean} 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} search (optional) retrieve Keys from external * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) * @returns {Promise>} * @static * @async */ getKeys (pattern, prepare_sync=false, search=false){ return new Promise(function (resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', 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 (prepare_sync === true) { secondrequest = function () { let msg2 = createMessage('keylist'); - msg2.setParameter('keys', 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 (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, !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 {String|Array} pattern (optional) The Pattern to * search for * @param {Boolean} with_secret_fpr (optional) 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 (pattern, with_secret_fpr) { return new Promise(function (resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); if (with_secret_fpr === true) { msg.setParameter('with-sec-fprs', true); } if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); } msg.post().then(function (answer){ const result = { armored: answer.data }; if (with_secret_fpr === true && answer.hasOwnProperty('sec-fprs') ) { result.secret_fprs = answer['sec-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) { 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 {String} userId The user Id, e.g. 'Foo Bar ' * @param {String} algo (optional) algorithm (and optionally key size) * to be used. See {@link supportedKeyAlgos} below for supported * values. If ommitted, 'default' is used. * @param {Number} expires (optional) Expiration time in seconds from now. * If not set or set to 0, expiration will be 'never' * @param {String} subkey_algo (optional) algorithm of the encryption * subkey. If ommited the same as algo is used. * * @return {Promise} * @async */ generateKey (userId, algo = 'default', expires, subkey_algo){ if ( typeof (userId) !== 'string' || // eslint-disable-next-line no-use-before-define supportedKeyAlgos.indexOf(algo) < 0 || (expires && !( Number.isInteger(expires) || expires < 0 )) ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } // eslint-disable-next-line no-use-before-define 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', userId); msg.setParameter('algo', algo ); if (subkey_algo) { msg.setParameter('subkey-algo', subkey_algo ); } if (expires){ msg.setParameter('expires', expires); } else { msg.setParameter('expires', 0); } 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/Message.js b/lang/js/src/Message.js index 1ba2b658..b83caf6d 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -1,239 +1,239 @@ /* 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 { permittedOperations } from './permittedOperations'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; /** * Initializes a message for gnupg, validating the message's purpose with * {@link permittedOperations} first * @param {String} operation - * @returns {GPGME_Message|GPGME_Error} The Message object + * @returns {GPGME_Message} The Message object */ export function createMessage (operation){ if (typeof (operation) !== 'string'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ return new GPGME_Message(operation); } else { - return gpgme_error('MSG_WRONG_OP'); + throw gpgme_error('MSG_WRONG_OP'); } } /** * A Message collects, validates and handles all information required to * successfully establish a meaningful communication with gpgme-json via * {@link Connection.post}. The definition on which communication is available * can be found in {@link permittedOperations}. * @class */ export class GPGME_Message { constructor (operation){ this._msg = { op: operation, chunksize: 1023* 1024 }; this._expected = null; } get operation (){ return this._msg.op; } set expected (value){ if (value === 'base64'){ this._expected = value; } } get expected () { return this._expected; } /** * The maximum size of responses from gpgme in bytes. As of July 2018, * most browsers will only accept answers up to 1 MB of size. * Everything above that threshold will not pass through * nativeMessaging; answers that are larger need to be sent in parts. * The lower limit is set to 10 KB. Messages smaller than the threshold * will not encounter problems, larger messages will be received in * chunks. If the value is not explicitly specified, 1023 KB is used. */ set chunksize (value){ if ( Number.isInteger(value) && value > 10 * 1024 && value <= 1024 * 1024 ){ this._msg.chunksize = value; } } get chunksize (){ return this._msg.chunksize; } /** * Returns the prepared message with parameters and completeness checked * @returns {Object|null} Object to be posted to gnupg, or null if * incomplete */ get message () { if (this.isComplete() === true){ return this._msg; } else { return null; } } /** * Sets a parameter for the message. It validates with * {@link permittedOperations} * @param {String} param Parameter to set * @param {any} value Value to set * @returns {Boolean} If the parameter was set successfully */ setParameter ( param,value ){ if (!param || typeof (param) !== 'string'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } let po = permittedOperations[this._msg.op]; if (!po){ - return gpgme_error('MSG_WRONG_OP'); + throw gpgme_error('MSG_WRONG_OP'); } let poparam = null; if (po.required.hasOwnProperty(param)){ poparam = po.required[param]; } else if (po.optional.hasOwnProperty(param)){ poparam = po.optional[param]; } else { - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } // check incoming value for correctness let checktype = function (val){ switch (typeof (val)){ case 'string': if (poparam.allowed.indexOf(typeof (val)) >= 0 && val.length > 0) { return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'number': if ( poparam.allowed.indexOf('number') >= 0 && isNaN(value) === false){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'boolean': if (poparam.allowed.indexOf('boolean') >= 0){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'object': if (Array.isArray(val)){ if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } for (let i=0; i < val.length; i++){ let res = checktype(val[i]); if (res !== true){ return res; } } if (val.length > 0) { return true; } } else if (val instanceof Uint8Array){ if (poparam.allowed.indexOf('Uint8Array') >= 0){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } else { - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } break; default: - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } }; let typechecked = checktype(value); if (typechecked !== true){ return typechecked; } if (poparam.hasOwnProperty('allowed_data')){ if (poparam.allowed_data.indexOf(value) < 0){ return gpgme_error('PARAM_WRONG'); } } this._msg[param] = value; return true; } /** * Check if the message has the minimum requirements to be sent, that is * all 'required' parameters according to {@link permittedOperations}. * @returns {Boolean} true if message is complete. */ isComplete (){ if (!this._msg.op){ return false; } let reqParams = Object.keys( permittedOperations[this._msg.op].required); let msg_params = Object.keys(this._msg); for (let i=0; i < reqParams.length; i++){ if (msg_params.indexOf(reqParams[i]) < 0){ return false; } } return true; } /** * Sends the Message via nativeMessaging and resolves with the answer. * @returns {Promise} * @async */ post (){ let me = this; return new Promise(function (resolve, reject) { if (me.isComplete() === true) { let conn = new Connection; conn.post(me).then(function (response) { resolve(response); }, function (reason) { reject(reason); }); } else { reject(gpgme_error('MSG_INCOMPLETE')); } }); } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 3be5cdd5..2886c6f6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,380 +1,389 @@ /* 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} data The decrypted data * @property {Boolean} base64 indicating whether data is base64 encoded. * @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 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 {String|Object} data text/data to be encrypted as String. Also * accepts Objects with a getText method * @param {inputKeys} publicKeys * Keys used to encrypt the message * @param {inputKeys} secretKeys (optional) Keys used to sign the * message. If Keys are present, the operation requested is assumed * to be 'encrypt and sign' * @param {Boolean} base64 (optional) The data will be interpreted as * base64 encoded data. * @param {Boolean} armor (optional) Request the output as armored * block. * @param {Boolean} 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 (data, publicKeys, secretKeys, base64=false, armor=true, wildcard=false, additional = {}){ let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg); } msg.setParameter('armor', armor); msg.setParameter('always-trust', true); if (base64 === true) { msg.setParameter('base64', true); } let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); let sigkeys = toKeyIdArray(secretKeys); if (sigkeys.length > 0) { msg.setParameter('signing_keys', sigkeys); } putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); } if (additional){ let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { - msg.setParameter(additional_Keys[k], - additional[additional_Keys[k]]); + try { + msg.setParameter(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 {String|Object} data text/data to be decrypted. Accepts * Strings and Objects with a getText method * @param {Boolean} base64 (optional) false if the data is an armored * block, true if it is base64 encoded binary data * @returns {Promise} Decrypted Message and information * @async */ decrypt (data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (msg instanceof Error){ return Promise.reject(msg); } if (base64 === true){ msg.setParameter('base64', true); } putData(msg, data); - if (base64 === true){ - msg.setParameter('base64', true); - } return new Promise(function (resolve, reject){ msg.post().then(function (result){ let _result = { data: result.data }; _result.base64 = result.base64 ? true: false; 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); } - resolve(_result); + if (_result.signatures instanceof Error){ + reject(_result.signatures); + } else { + resolve(_result); + } }, function (error){ reject(error); }); }); } /** * Sign a Message * @param {String|Object} data text/data to be signed. Accepts Strings * and Objects with a getText method. * @param {inputKeys} keys The key/keys to use for signing * @param {String} mode The signing mode. Currently supported: * 'clearsign':The Message is embedded into the signature; * 'detached': The signature is stored separately * @param {Boolean} base64 input is considered base64 * @returns {Promise} * @async */ sign (data, keys, mode='clearsign', base64=false) { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let key_arr = toKeyIdArray(keys); if (key_arr.length === 0){ return Promise.reject(gpgme_error('MSG_NO_KEYS')); } let msg = createMessage('sign'); msg.setParameter('keys', key_arr); if (base64 === true){ msg.setParameter('base64', true); } msg.setParameter('mode', mode); putData(msg, data); return new Promise(function (resolve,reject) { if (mode ==='detached'){ msg.expected ='base64'; } msg.post().then( function (message) { if (mode === 'clearsign'){ resolve({ data: message.data } ); } else if (mode === 'detached') { resolve({ data: data, signature: message.data }); } }, function (error){ reject(error); }); }); } /** * Verifies data. * @param {String|Object} data text/data to be verified. Accepts Strings * and Objects with a getText method * @param {String} (optional) A detached signature. If not present, * opaque mode is assumed * @param {Boolean} (optional) Data and signature are base64 encoded * @returns {Promise} *@async */ verify (data, signature, base64 = false){ let msg = createMessage('verify'); let dt = putData(msg, data); if (dt instanceof Error){ return Promise.reject(dt); } if (signature){ if (typeof (signature)!== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } else { msg.setParameter('signature', signature); } } 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 = collectSignatures( - message.info.signatures); - _result.is_mime = message.info.is_mime? true: false; - if (message.info.filename){ - _result.file_name = message.info.filename; + let _result = collectSignatures(message.info.signatures); + if (_result 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); } - _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(sigObj); + 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 0abc1061..212effd3 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,375 +1,379 @@ /* 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 } from './src/Errors'; +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++){ - let key0 = createKey(wp.four_invalid_params[i]); - expect(key0).to.be.an.instanceof(Error); - expect(key0.code).to.equal('PARAM_WRONG'); + 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){ 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( 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('data', ''); 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 (){ - let test0 = createMessage(mp.invalid_op_action); - - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('MSG_WRONG_OP'); + expect(function () { + createMessage(mp.invalid_op_action); + }).to.throw( + err_list.MSG_WRONG_OP.msg); }); it('Not accepting wrong parameter type', function (){ - let test0 = createMessage(mp.invalid_op_type); - - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('PARAM_WRONG'); + 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++){ - let ret = test0.setParameter( - mp.invalid_param_test.invalid_param_names[i], - 'Somevalue'); - - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + 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++){ - let ret = test0.setParameter( - mp.invalid_param_test.validparam_name_0, - mp.invalid_param_test.invalid_values_0[j]); - - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + 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