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
This commit is contained in:
Maximilian Krambach 2018-04-27 20:03:09 +02:00
parent eb7129f319
commit fda7b13f1b
16 changed files with 417 additions and 115 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
* 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
});

View File

@ -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"}]
}
};

View File

@ -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();
});
})
}
});

View File

@ -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

View File

@ -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;
});
}
});

View File

@ -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
});
});
};

View File

@ -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
});
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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];
}

View File

@ -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;
};

View File

@ -21,9 +21,16 @@
/**
* Definition of the possible interactions with gpgme-json.
* operation: <Object>
required: Array<String>
optional: Array<String>
pinentry: Boolean If a pinentry dialog is expected, and a timeout of
required: Array<Object>
<String> 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: <Array> If present, restricts to the given value
optional: Array<Object>
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: <Object>
type: <String< The content type of answer expected
@ -38,20 +45,52 @@
export const permittedOperations = {
encrypt: {
required: ['keys', 'data'],
optional: [
'protocol',
'chunksize',
'base64',
'mime',
'armor',
'always-trust',
'no-encrypt-to',
'no-compress',
'throw-keyids',
'want-address',
'wrap'
],
required: {
'keys': {
allowed: ['string'],
array_allowed: true
},
'data': {
allowed: ['string', 'Uint8Array']
}
},
optional: {
'protocol': {
allowed: ['string'],
allowed_data: ['cms', 'openpgp']
},
'chunksize': {
allowed: ['number']
},
'base64': {
allowed: ['boolean']
},
'mime': {
allowed: ['boolean']
},
'armor': {
allowed: ['boolean']
},
'always-trust': {
allowed: ['boolean']
},
'no-encrypt-to': {
allowed: ['string'],
array_allowed: true
},
'no-compress': {
allowed: ['boolean']
},
'throw-keyids': {
allowed: ['boolean']
},
'want-address': {
allowed: ['boolean']
},
'wrap': {
allowed: ['boolean']
},
},
answer: {
type: ['ciphertext'],
data: ['data'],
@ -62,18 +101,29 @@ export const permittedOperations = {
decrypt: {
pinentry: true,
required: ['data'],
optional: [
'protocol',
'chunksize',
'base64'
],
required: {
'data': {
allowed: ['string', 'Uint8Array']
}
},
optional: {
'protocol': {
allowed: ['string'],
allowed_data: ['cms', 'openpgp']
},
'chunksize': {
allowed: ['number'],
},
'base64': {
allowed: ['boolean']
}
},
answer: {
type: ['plaintext'],
data: ['data'],
params: ['base64', 'mime'],
infos: [] // pending. Info about signatures and validity
//signature: [{Key Fingerprint, valid Boolean}]
//signature: [{Key Fingerprint, valid boolean}]
}
},
/**

View File

@ -24,7 +24,7 @@ import { GPGME_Key } from "../src/Key";
import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers"
import { helper_params } from "./inputvalues";
function Helpertest(){
export function Helpertest(){
describe('Error Object handling', function(){
it('check the Timeout error', function(){
let test0 = gpgme_error('CONN_TIMEOUT');

View File

@ -21,23 +21,73 @@
import { expect } from "../node_modules/chai/chai";
import { GPGME_Message, createMessage } from "../src/Message";
import { message_params } from "./inputvalues";
import { message_params as mp, helper_params as hp} from "./inputvalues";
function Messagetest(){
export function Messagetest(){
describe('Message Object', function(){
describe('incorrect initialization', function(){
describe('correct initialization of an encrypt Message', function(){
it('creating 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 mandatoy 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('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');
expect(test0.message.op).to.equal('encrypt');
expect(test0.message.data).to.equal(
mp.valid_encrypt_data);
});
});
describe('Incorrect initialization', function(){
it('non-allowed operation', function(){
let test0 = createMessage(message_params.invalid_op_action);
let test0 = createMessage(mp.invalid_op_action);
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('MSG_WRONG_OP');
});
it('wrong parameter type in constructor', function(){
let test0 = createMessage(message_params.invalid_op_type);
let test0 = createMessage(mp.invalid_op_type);
expect(test0).to.be.an.instanceof(Error);
expect(test0.code).to.equal('PARAM_WRONG');
});
});
describe('Setting wrong parameters', function(){
it('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');
}
});
it('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');
}
});
});
});
};
}
export default Messagetest;

28
lang/js/test/index.js Normal file
View File

@ -0,0 +1,28 @@
/* 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 <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+
*/
import { Helpertest } from "./Helpers";
import { Messagetest } from "./Message";
/**
* Unit tests.
*/
Helpertest();
Messagetest();

View File

@ -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