diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8a965479..923698a4 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,320 +1,342 @@ /* 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 */ /* global chrome */ import { permittedOperations } from './permittedOperations'; import { gpgme_error } from './Errors'; import { GPGME_Message, createMessage } from './Message'; import { decode, atobArray, Utf8ArrayToStr } from './Helpers'; /** * A Connection handles the nativeMessaging interaction via a port. As the * protocol only allows up to 1MB of message sent from the nativeApp to the * browser, the connection will stay open until all parts of a communication * are finished. For a new request, a new port will open, to avoid mixing * contexts. * @class * @private */ export class Connection{ constructor (){ + this._connectionError = null; this._connection = chrome.runtime.connectNative('gpgmejson'); + this._connection.onDisconnect.addListener(() => { + if (chrome.runtime.lastError) { + this._connectionError = chrome.runtime.lastError.message; + } else { + this._connectionError = 'Disconnected without error message'; + } + }); } /** * Immediately closes an open port. */ disconnect () { if (this._connection){ this._connection.disconnect(); this._connection = null; + this._connectionError = 'Disconnect requested by gpgmejs'; } } + /** + * Checks if the connection terminated with an error state + */ + get isDisconnected (){ + return this._connectionError !== null; + } /** * @typedef {Object} backEndDetails * @property {String} gpgme Version number of gpgme * @property {Array} info Further information about the backend * and the used applications (Example: *
     * {
     *          "protocol":     "OpenPGP",
     *          "fname":        "/usr/bin/gpg",
     *          "version":      "2.2.6",
     *          "req_version":  "1.4.0",
     *          "homedir":      "default"
     * }
     * 
*/ /** * Retrieves the information about the backend. * @param {Boolean} details (optional) If set to false, the promise will * just return if a connection was successful. * @param {Number} timeout (optional) * @returns {Promise|Promise} Details from the * backend * @async */ checkConnection (details = true, timeout = 1000){ if (typeof timeout !== 'number' && timeout <= 0) { timeout = 1000; } const msg = createMessage('version'); if (details === true) { return this.post(msg); } else { let me = this; return new Promise(function (resolve) { Promise.race([ me.post(msg), new Promise(function (resolve, reject){ setTimeout(function (){ reject(gpgme_error('CONN_TIMEOUT')); }, timeout); }) ]).then(function (){ // success resolve(true); }, function (){ // failure resolve(false); }); }); } } /** * Sends a {@link GPGME_Message} via the nativeMessaging port. It * resolves with the completed answer after all parts have been * received and reassembled, or rejects with an {@link GPGME_Error}. * * @param {GPGME_Message} message * @returns {Promise<*>} The collected answer, depending on the messages' * operation * @private * @async */ post (message){ if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); return Promise.reject(gpgme_error( 'PARAM_WRONG', 'Connection.post')); } if (message.isComplete() !== true){ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } + if (this.isDisconnected) { + if ( this.isNativeHostUnknown === true) { + return Promise.reject(gpgme_error('CONN_NO_CONFIG')); + } else { + return Promise.reject(gpgme_error( + 'CONN_NO_CONNECT', this._connectionError)); + } + } let chunksize = message.chunksize; const me = this; - return new Promise(function (resolve, reject){ + const nativeCommunication = new Promise(function (resolve, reject){ let answer = new Answer(message); let listener = function (msg) { if (!msg){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else { let answer_result = answer.collect(msg); if (answer_result !== true){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(answer_result); } else { if (msg.more === true){ me._connection.postMessage({ 'op': 'getmore', 'chunksize': chunksize }); } else { me._connection.onMessage.removeListener(listener); me._connection.disconnect(); const message = answer.getMessage(); if (message instanceof Error){ reject(message); } else { resolve(message); } } } } }; me._connection.onMessage.addListener(listener); - if (permittedOperations[message.operation].pinentry){ - return me._connection.postMessage(message.message); - } else { - return Promise.race([ - me._connection.postMessage(message.message), - function (resolve, reject){ - setTimeout(function (){ - me._connection.disconnect(); - reject(gpgme_error('CONN_TIMEOUT')); - }, 5000); - } - ]).then(function (result){ - return result; - }, function (reject){ - if (!(reject instanceof Error)) { - me._connection.disconnect(); - return gpgme_error('GNUPG_ERROR', reject); - } else { - return reject; - } - }); - } + me._connection.postMessage(message.message); }); + if (permittedOperations[message.operation].pinentry === true) { + return nativeCommunication; + } else { + return Promise.race([ + nativeCommunication, + new Promise(function (resolve, reject){ + setTimeout(function (){ + me._connection.disconnect(); + reject(gpgme_error('CONN_TIMEOUT')); + }, 5000); + }) + ]); + } } } /** * A class for answer objects, checking and processing the return messages of * the nativeMessaging communication. * @private */ class Answer{ /** * @param {GPGME_Message} message */ constructor (message){ this._operation = message.operation; this._expected = message.expected; this._response_b64 = null; } get operation (){ return this._operation; } get expected (){ return this._expected; } + /** + * Checks if an error matching browsers 'host not known' messages occurred + */ + get isNativeHostUnknown () { + return this._connectionError === 'Specified native messaging host not found.'; + } + /** * Adds incoming base64 encoded data to the existing response * @param {*} msg base64 encoded data. * @returns {Boolean} * * @private */ collect (msg){ if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (!this._response_b64){ this._response_b64 = msg.response; return true; } else { this._response_b64 += msg.response; return true; } } /** * Decodes and verifies the base64 encoded answer data. Verified against * {@link permittedOperations}. * @returns {Object} The readable gpnupg answer */ getMessage (){ if (this._response_b64 === null){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } let _decodedResponse = JSON.parse(atob(this._response_b64)); let _response = { format: 'ascii' }; let messageKeys = Object.keys(_decodedResponse); let poa = permittedOperations[this.operation].answer; if (messageKeys.length === 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } for (let i= 0; i < messageKeys.length; i++){ let key = messageKeys[i]; switch (key) { case 'type': { if (_decodedResponse.type === 'error'){ return (gpgme_error('GNUPG_ERROR', decode(_decodedResponse.msg))); } else if (poa.type.indexOf(_decodedResponse.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; } case 'base64': { break; } case 'msg': { if (_decodedResponse.type === 'error'){ return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); } break; } default: { let answerType = null; if (poa.payload && poa.payload.hasOwnProperty(key)){ answerType = 'p'; } else if (poa.info && poa.info.hasOwnProperty(key)){ answerType = 'i'; } if (answerType !== 'p' && answerType !== 'i'){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (answerType === 'i') { if ( typeof (_decodedResponse[key]) !== poa.info[key] ){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } _response[key] = decode(_decodedResponse[key]); } else if (answerType === 'p') { if (_decodedResponse.base64 === true && poa.payload[key] === 'string' ) { if (this.expected === 'uint8'){ _response[key] = atobArray(_decodedResponse[key]); _response.format = 'uint8'; } else if (this.expected === 'base64'){ _response[key] = _decodedResponse[key]; _response.format = 'base64'; } else { // no 'expected' _response[key] = Utf8ArrayToStr( atobArray(_decodedResponse[key])); _response.format = 'string'; } } else if (poa.payload[key] === 'string') { _response[key] = _decodedResponse[key]; } else { // fallthrough, should not be reached // (payload is always string) return gpgme_error('CONN_UNEXPECTED_ANSWER'); } } break; } } } return _response; } } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index bf52cce7..6189414f 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,177 +1,185 @@ /* 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}. */ 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_NO_CONFIG':{ + msg: 'The browser does not recognize the nativeMessaging host.', + type: 'error' + }, + 'CONN_NATIVEMESSAGE':{ + msg: 'The native messaging was not successful.', + 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' }, 'DECODE_FAIL': { msg: 'Decoding failed due to unexpected data', 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 {String} code Error code as defined in {@link err_list}. * @param {String} info Possible additional error message to pass through. * Currently used for errors sent as answer by gnupg via a native Message port * @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 * It is created by {@link gpgme_error}, and its' codes are defined in * {@link err_list}. * * @property {String} code Short description of origin and type of the error * @property {String} msg Additional info * @protected * @class * @extends Error */ class GPGME_Error extends Error{ constructor (code = 'GENERIC_ERROR', msg=''){ - - if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ + const verboseErrors = ['GNUPG_ERROR', 'CONN_NATIVEMESSAGE']; + if (verboseErrors.includes(code) && 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/index.js b/lang/js/src/index.js index 106086fb..3c31a047 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -1,58 +1,68 @@ /* 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 } from './gpgmejs'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; /** * Main entry point for gpgme.js. It initializes by testing the nativeMessaging * connection once, and then offers the available functions as method of the * response object. * An unsuccessful attempt will reject as a GPGME_Error. * @param {Object} config (optional) configuration options * @param {Number} config.timeout set the timeout for the initial connection - * check. On some machines and operating systems a default timeout of 500 ms is + * check. On some machines and operating systems a default timeout of 1000 ms is * too low, so a higher number might be attempted. * @returns {Promise} * @async */ function init ({ timeout = 1000 } = {}){ return new Promise(function (resolve, reject){ const connection = new Connection; connection.checkConnection(false, timeout).then( function (result){ if (result === true) { resolve(new GpgME()); } else { - reject(gpgme_error('CONN_NO_CONNECT')); + if (connection._connectionError) { + if (connection.isNativeHostUnknown){ + reject(gpgme_error('CONN_NO_CONFIG')); + } else { + reject(gpgme_error('CONN_NATIVEMESSAGE', + connection._connectionError) + ); + } + } else { + reject(gpgme_error('CONN_TIMEOUT')); + } } }, function (){ // unspecific connection error. Should not happen reject(gpgme_error('CONN_NO_CONNECT')); }); }); } const exportvalue = { init:init }; export default exportvalue; \ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 414d18d1..45e2b93c 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,386 +1,418 @@ /* 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; const connectionTimeout = 2000; function unittests (){ describe('Connection testing', function (){ it('Connecting', function (done) { let conn0 = new Connection; conn0.checkConnection(true, connectionTimeout).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'); + expect(conn0.isDisconnected).to.be.false; done(); }); }); + it('Simple connection check', function (done) { + let conn0 = new Connection; + conn0.checkConnection(false, connectionTimeout).then( + function (answer) { + expect(answer).to.be.true; + expect(conn0.isDisconnected).to.be.false; + done(); + }); + }); + + it('Connection check with backend information', function (done) { + let conn0 = new Connection; + conn0.checkConnection(true, connectionTimeout).then( + function (answer) { + expect(answer).to.be.an('Object'); + expect(answer.gpgme).to.be.a('String'); + expect(answer.info).to.be.an('Array'); + expect(answer.info.length).to.be.above(0); + for (const item of answer.info) { + expect(item).to.have.property('protocol'); + expect(item).to.have.property('fname'); + expect(item).to.have.property('version'); + expect(item).to.have.property('req_version'); + expect(item).to.have.property('homedir'); + } + expect(conn0.isDisconnected).to.be.false; + done(); + }); + }); + it('Disconnecting', function (done) { let conn0 = new Connection; conn0.checkConnection(false, connectionTimeout).then( function (answer) { expect(answer).to.be.true; conn0.disconnect(); conn0.checkConnection(false, connectionTimeout).then( function (result) { expect(result).to.be.false; + expect(conn0.isDisconnected).to.be.true; 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({ 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({ 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({ pattern: kp.invalidKeyFingerprint, prepare_sync: 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