From fda7b13f1b673962ce34b6f429158a7eb9cef47b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Apr 2018 20:03:09 +0200 Subject: [PATCH] js: more testing -- * Tests: initialization of the two modes, encryption * gpgme.js: reintroduced message check before calling Connection.post() * gpgmejs_openpgp.js: Fixed openpgp mode not passing keys * index.js: fixed some confusion in parseconfig() * Inserted some TODO stubs for missing error handling --- .../BrowserTestExtension/tests/encryptTest.js | 71 ++++++++++++++ .../BrowserTestExtension/tests/inputvalues.js | 6 +- lang/js/BrowserTestExtension/tests/startup.js | 69 ++++++++----- lang/js/build_extensions.sh | 1 + lang/js/src/Connection.js | 6 +- lang/js/src/Key.js | 23 +++-- lang/js/src/Keyring.js | 4 + lang/js/src/Message.js | 66 +++++++++++-- lang/js/src/gpgmejs.js | 56 ++++++----- lang/js/src/gpgmejs_openpgpjs.js | 7 +- lang/js/src/index.js | 23 +++-- lang/js/src/permittedOperations.js | 98 ++++++++++++++----- lang/js/test/Helpers.js | 2 +- lang/js/test/Message.js | 62 ++++++++++-- lang/js/test/index.js | 28 ++++++ lang/js/test/inputvalues.js | 10 ++ 16 files changed, 417 insertions(+), 115 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/encryptTest.js create mode 100644 lang/js/test/index.js diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js new file mode 100644 index 00000000..e6000003 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -0,0 +1,71 @@ +/* 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+ + */ +describe('Encryption', function(){ + + it('Successfull encrypt', function(done){ + let prm = Gpgmejs.init(); + prm.then(function(context){ + context.encrypt( + inputvalues.encrypt.good.data, + inputvalues.encrypt.good.fingerprint).then(function(answer){ + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a("string"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + done(); + }, function(err){ + expect(err).to.be.undefined; + done(); + }); + }); + }); + + it('Sending encryption without keys fails', function(){ + let prm = Gpgmejs.init(); + prm.then(function(context){ + context.encrypt( + inputvalues.encrypt.good.data, + null).then(function(answer){ + expect(answer).to.be.undefined; + done(); + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done() + }); + }); + }); + + it('Sending encryption without data fails', function(){ + let prm = Gpgmejs.init(); + prm.then(function(context){ + context.encrypt( + null,inputvalues.encrypt.good.keyid).then(function(answer){ + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); + }); + }); + }); + + // TODO check different valid parameter +}); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 47600c84..1761c82f 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -22,7 +22,11 @@ var inputvalues = { encrypt: { good:{ data : 'Hello World.', - keyid : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' + fingerprint : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' } + }, + init: { + invalid_startups: [{all_passwords: true}, 'openpgpmode', {api_style:"frankenstein"}] } + }; diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 14d12c0a..a5614a83 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -20,32 +20,51 @@ describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ - Gpgmejs.init().then( + let prm = Gpgmejs.init(); + prm.then( function(context){ - expect(context.connection).to.not.be.undefined; - expect(context).to.be.an('object'); - expect(context.connection).to.be.an('object'); - expect(context.Keyring).to.be.undefined; - expect(context.encrypt).to.be.a('function'); - expect(context.decrypt).to.be.a('function'); - done(); - }, function(err){ - done(err); - }); - }); - it('Starting an openpgp mode GPGME instance', function(done){ - Gpgmejs.init({api_style:"gpgme_openpgpjs"}).then( - function(context){ - console.log(context); + expect(context.connection).to.not.be.undefined; + expect(context).to.be.an('object'); + expect(context.connection).to.be.an('object'); + expect(context.Keyring).to.be.undefined; + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(errorr){ + expect(error).to.be.undefined; done(); - // expect(context).to.be.an('object'); - // expect(context.connection).to.be.undefined; - // expect(context.Keyring).to.be.an('object'); - // expect(context.encrypt).to.be.a('function'); - // expect(context.decrypt).to.be.a('function'); - // done(); - }, function(err){ - done(err); }); }); - }); +}); +describe('openpgp mode', function(){ + it('startup of openpgp mode returns the correct parameters', function(done){ + let prm = Gpgmejs.init({api_style:"gpgme_openpgpjs"}); + prm.then(function(context){ + expect(context).to.be.an('object'); + expect(context.connection).to.be.undefined; + expect(context.Keyring).to.be.an('object'); + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(error){ + expect(error).to.be.undefined; + done(); + }); + }); +}); + +describe('GPGME does not start with invalid parameters', function(){ + for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ + it('Parameter '+ i, function(done){ + let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); + prm.then(function(context){ + expect(context).to.be.undefined; + done(); + }, function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('PARAM_WRONG'); + done(); + }); + }) + } +}); \ No newline at end of file diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh index be7b0584..b99a362c 100755 --- a/lang/js/build_extensions.sh +++ b/lang/js/build_extensions.sh @@ -6,6 +6,7 @@ cp node_modules/chai/chai.js \ node_modules/mocha/mocha.css \ node_modules/mocha/mocha.js \ build/gpgmejs.bundle.js BrowserTestExtension/libs +rm -rf build/extensions mkdir -p build/extensions zip -r build/extensions/browsertest.zip BrowserTestExtension diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index a10f9d9a..a198bdc6 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -100,9 +100,7 @@ export class Connection{ reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener) - reject( - {code: 'GNUPG_ERROR', - msg: msg.msg} ); + reject(gpgme_error('GNUPG_ERROR', msg.msg)); } else { let answer_result = answer.add(msg); if (answer_result !== true){ @@ -129,6 +127,8 @@ export class Connection{ }, 5000); }]).then(function(result){ return result; + }, function(error){ + return error; }); } }); diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 0b44b245..30449d63 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -51,10 +51,7 @@ export class GPGME_Key { * hasSecret returns true if a secret subkey is included in this Key */ get hasSecret(){ - checkKey(this._fingerprint, 'secret').then( function(result){ - return Promise.resolve(result); - }); - + return checkKey(this._fingerprint, 'secret'); } get isRevoked(){ @@ -130,6 +127,8 @@ export class GPGME_Key { } } return Promise.resolve(resultset); + }, function(error){ + //TODO checkKey fails }); } @@ -175,8 +174,7 @@ export class GPGME_Key { */ function checkKey(fingerprint, property){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - if (!property || - permittedOperations[keyinfo].indexOf(property) < 0){ + if (!property || !permittedOperations[keyinfo].hasOwnProperty(property)){ return Promise.reject(gpgme_error('PARAM_WRONG')); } return new Promise(function(resolve, reject){ @@ -188,19 +186,20 @@ function checkKey(fingerprint, property){ reject(gpgme_error('PARAM_WRONG')); } msg.setParameter('fingerprint', this.fingerprint); - return (this.connection.post(msg)).then(function(result){ - if (result.hasOwnProperty(property)){ + return (this.connection.post(msg)).then(function(result, error){ + if (error){ + reject(gpgme_error('GNUPG_ERROR',error.msg)); + } else if (result.hasOwnProperty(property)){ resolve(result[property]); } else if (property == 'secret'){ - // TBD property undefined means "not true" in case of secret? - resolve(false); + // TBD property undefined means "not true" in case of secret? + resolve(false); } else { reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } }, function(error){ - reject({code: 'GNUPG_ERROR', - msg: error.msg}); + //TODO error handling }); }); }; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 364bfb46..d1f4122e 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -78,6 +78,8 @@ export class GPGME_Keyring { } } return Promise.resolve(resultset); + }, function(error){ + //TODO error handling }); } @@ -151,6 +153,8 @@ export class GPGME_Keyring { } } return Promise.resolve(resultset); + }, function(error){ + //TODO error handling }); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 95d043ba..c42480f2 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -73,11 +73,60 @@ export class GPGME_Message { if (!po){ return gpgme_error('MSG_WRONG_OP'); } - if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ - this._msg[param] = value; - return true; + 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'); } - return gpgme_error('PARAM_WRONG'); + let checktype = function(val){ + switch(typeof(val)){ + case 'string': + case 'number': + case 'boolean': + if (poparam.allowed.indexOf(typeof(val)) >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + break; + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); + } + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + return true; + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + } else { + return gpgme_error('PARAM_WRONG'); + } + break; + default: + return 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; } /** @@ -89,11 +138,12 @@ export class GPGME_Message { if (!this._msg.op){ return false; } - let reqParams = permittedOperations[this._msg.op].required; + 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 (!this._msg.hasOwnProperty(reqParams[i])){ - console.log(reqParams[i] + 'missing'); + if (msg_params.indexOf(reqParams[i]) < 0){ + console.log(reqParams[i] + ' missing'); return false; } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 2ddf2964..9475b2b0 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -33,25 +33,24 @@ export class GpgME { this.connection = connection; } - set connection(connection){ + set connection(conn){ if (this._connection instanceof Connection){ gpgme_error('CONN_ALREADY_CONNECTED'); - } - if (connection instanceof Connection){ - this._connection = connection; + } else if (conn instanceof Connection){ + this._connection = conn; } else { gpgme_error('PARAM_WRONG'); } } get connection(){ - if (this._connection instanceof Connection){ - if (this._connection.isConnected){ + if (this._connection){ + if (this._connection.isConnected === true){ return this._connection; } - return undefined; //TODO: connection was lost! + return undefined; } - return undefined; //TODO: no connection there + return undefined; } set Keyring(keyring){ @@ -85,8 +84,11 @@ export class GpgME { putData(msg, data); if (wildcard === true){msg.setParameter('throw-keyids', true); }; - - return (this.connection.post(msg)); + if (msg.isComplete === true){ + return this.connection.post(msg); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } } /** @@ -133,20 +135,24 @@ export class GpgME { msg.setParameter('delete_force', true); // TBD } - this.connection.post(msg).then(function(success){ - // TODO: it seems that there is always errors coming back: - }, function(error){ - switch (error.msg){ - case 'ERR_NO_ERROR': - return Promise.resolve('okay'); //TBD - default: - return Promise.reject(gpgme_error('TODO') ); // - // INV_VALUE, - // GPG_ERR_NO_PUBKEY, - // GPG_ERR_AMBIGUOUS_NAME, - // GPG_ERR_CONFLICT - } - }); + if (msg.isComplete === true){ + this.connection.post(msg).then(function(success){ + // TODO: it seems that there is always errors coming back: + }, function(error){ + switch (error.msg){ + case 'ERR_NO_ERROR': + return Promise.resolve('okay'); //TBD + default: + return Promise.reject(gpgme_error('TODO') ); // + // INV_VALUE, + // GPG_ERR_NO_PUBKEY, + // GPG_ERR_AMBIGUOUS_NAME, + // GPG_ERR_CONFLICT + } + }); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } } } @@ -162,7 +168,7 @@ function putData(message, data){ return gpgme_error('PARAM_WRONG'); } if (!data){ - message.setParameter('data', ''); + return gpgme_error('PARAM_WRONG'); } else if (data instanceof Uint8Array){ let decoder = new TextDecoder('utf8'); message.setParameter('base64', true); diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index cc2afde1..c80d5a86 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -109,7 +109,7 @@ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } - return this._GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); + return this._GpgME.encrypt(data, translateKeys(publicKeys), wildcard); } /** Decrypt Message @@ -201,6 +201,8 @@ class GPGME_Keyring_openpgpmode { // TODO: Can there be several default keys? return gpgme_error('TODO'); } + }, function(error){ + //TODO }); } @@ -264,6 +266,9 @@ class GPGME_Key_openpgpmode { * creates GPGME_Key_openpgpmode from GPGME_Keys */ function translateKeys(input){ + if (!input){ + return null; + } if (!Array.isArray(input)){ input = [input]; } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 4de98457..90fe99e3 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -53,18 +53,17 @@ function init(config){ }); } -function parseconfiguration(config){ - if (!config){ - return defaultConf; - } - if ( typeof(config) !== 'object'){ +function parseconfiguration(rawconfig = {}){ + if ( typeof(rawconfig) !== 'object'){ return gpgme_error('PARAM_WRONG'); }; - let result_config = defaultConf; - let conf_keys = Object.keys(config); - for (let i=0; i < conf_keys; i++){ + let result_config = {}; + let conf_keys = Object.keys(rawconfig); + + for (let i=0; i < conf_keys.length; i++){ + if (availableConf.hasOwnProperty(conf_keys[i])){ - let value = config[conf_keys[i]]; + let value = rawconfig[conf_keys[i]]; if (availableConf[conf_keys[i]].indexOf(value) < 0){ return gpgme_error('PARAM_WRONG'); } else { @@ -75,6 +74,12 @@ function parseconfiguration(config){ return gpgme_error('PARAM_WRONG'); } } + let default_keys = Object.keys(defaultConf); + for (let j=0; j < default_keys.length; j++){ + if (!result_config.hasOwnProperty(default_keys[j])){ + result_config[default_keys[j]] = defaultConf[default_keys[j]]; + } + } return result_config; }; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 79e74223..274e037e 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -21,9 +21,16 @@ /** * Definition of the possible interactions with gpgme-json. * operation: - required: Array - optional: Array - pinentry: Boolean If a pinentry dialog is expected, and a timeout of + required: Array + name The name of the property + allowed: Array of allowed types. Currently accepted values: + ['number', 'string', 'boolean', 'Uint8Array'] + array_allowed: Boolean. If the value can be an array of the above + allowed_data: If present, restricts to the given value + optional: Array + see 'required', with these parameters not being mandatory for a + complete message + pinentry: boolean If a pinentry dialog is expected, and a timeout of 5000 ms would be too short answer: type: . + * SPDX-License-Identifier: LGPL-2.1+ + */ +import { Helpertest } from "./Helpers"; +import { Messagetest } from "./Message"; + +/** + * Unit tests. + */ + +Helpertest(); +Messagetest(); diff --git a/lang/js/test/inputvalues.js b/lang/js/test/inputvalues.js index a50c8162..f6cd75aa 100644 --- a/lang/js/test/inputvalues.js +++ b/lang/js/test/inputvalues.js @@ -8,6 +8,8 @@ export const helper_params = { 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], invalidLongId: '9A9A7A7A8A9A9A7A7A8A', invalidFingerprint: [{hello:'World'}], invalidKeyArray: {curiosity:'uncat'}, @@ -21,8 +23,16 @@ export const helper_params = { export const message_params = { invalid_op_action : 'dance', invalid_op_type : [234, 34, '<>'], + valid_encrypt_data: "مرحبا بالعالم", + invalid_param_test: { + valid_op: 'encrypt', + invalid_param_names: [22,'dance', {}], + validparam_name_0: 'mime', + invalid_values_0: [2134, 'All your passwords', new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] + } } + export default { helper_params: helper_params, message_params: message_params