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 @@ -1,28 +1,32 @@ /* 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+ */ 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 @@ -1,51 +1,70 @@ /* 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('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); + 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(); }); }); - it('Starting an openpgp mode GPGME instance', function(done){ - Gpgmejs.init({api_style:"gpgme_openpgpjs"}).then( - function(context){ - console.log(context); - 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 @@ -1,14 +1,15 @@ #/!bin/bash npx webpack --config webpack.conf.js mkdir -p BrowserTestExtension/libs 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 mkdir -p DemoExtension/libs cp build/gpgmejs.bundle.js DemoExtension/libs zip -r build/extensions/demoextension.zip DemoExtension 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 @@ -1,214 +1,214 @@ /* 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+ */ /** * A connection port will be opened for each communication between gpgmejs and * gnupg. It should be alive as long as there are additional messages to be * expected. */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from "./Errors" import { GPGME_Message } from "./Message"; /** * A Connection handles the nativeMessaging interaction. */ export class Connection{ constructor(){ this.connect(); let me = this; } /** * (Simple) Connection check. * @returns {Boolean} true if the onDisconnect event has not been fired. * Please note that the event listener of the port takes some time * (5 ms seems enough) to react after the port is created. Then this will * return undefined */ get isConnected(){ return this._isConnected; } /** * Immediately closes the open port. */ disconnect() { if (this._connection){ this._connection.disconnect(); } } /** * Opens a nativeMessaging port. */ connect(){ if (this._isConnected === true){ gpgme_error('CONN_ALREADY_CONNECTED'); } else { this._isConnected = true; this._connection = chrome.runtime.connectNative('gpgmejson'); let me = this; this._connection.onDisconnect.addListener( function(){ me._isConnected = false; } ); } } /** * Sends a message and resolves with the answer. * @param {GPGME_Message} message * @returns {Promise} the gnupg answer, or rejection with error * information. */ post(message){ if (!this.isConnected){ return Promise.reject(gpgme_error('CONN_DISCONNECTED')); } if (!message || !message instanceof GPGME_Message){ return Promise.reject(gpgme_error('PARAM_WRONG'), message); } if (message.isComplete !== true){ return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; return new Promise(function(resolve, reject){ let answer = new Answer(message.operation); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) 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){ reject(answer_result); } if (msg.more === true){ me._connection.postMessage({'op': 'getmore'}); } else { me._connection.onMessage.removeListener(listener) resolve(answer.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(){ reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ return result; + }, function(error){ + return error; }); } }); } }; /** * A class for answer objects, checking and processing the return messages of * the nativeMessaging communication. * @param {String} operation The operation, to look up validity of returning messages */ class Answer{ constructor(operation){ this.operation = operation; } /** * Add the information to the answer * @param {Object} msg The message as received with nativeMessaging * returns true if successfull, gpgme_error otherwise */ add(msg){ if (this._response === undefined){ this._response = {}; } let messageKeys = Object.keys(msg); 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 ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; case 'more': break; default: //data should be concatenated if (poa.data.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[key] = ''; } this._response[key] = this._response[key].concat(msg[key]); } //params should not change through the message else if (poa.params.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[key] = msg[key]; } else if (this._response[key] !== msg[key]){ return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]); } } //infos may be json objects etc. Not yet defined. // Pushing them into arrays for now else if (poa.infos.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[key] = []; } this._response.push(msg[key]); } else { return gpgme_error('CONN_UNEXPECTED_ANSWER', key); } break; } } return true; } /** * @returns {Object} the assembled message. * TODO: does not care yet if completed. */ get message(){ return this._response; } } 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 @@ -1,206 +1,205 @@ /* 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+ */ /** * The key class allows to query the information defined in gpgme Key Objects * (see https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) * * This is a stub, as the gpgme-json side is not yet implemented * */ import { isFingerprint } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; export class GPGME_Key { constructor(fingerprint){ this.fingerprint = fingerprint; } set fingerprint(fpr){ if (isFingerprint(fpr) === true && !this._fingerprint){ this._fingerprint = fpr; } } get fingerprint(){ return this._fingerprint; } /** * 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(){ return checkKey(this._fingerprint, 'revoked'); } get isExpired(){ return checkKey(this._fingerprint, 'expired'); } get isDisabled(){ return checkKey(this._fingerprint, 'disabled'); } get isInvalid(){ return checkKey(this._fingerprint, 'invalid'); } get canEncrypt(){ return checkKey(this._fingerprint, 'can_encrypt'); } get canSign(){ return checkKey(this._fingerprint, 'can_sign'); } get canCertify(){ return checkKey(this._fingerprint, 'can_certify'); } get canAuthenticate(){ return checkKey(this._fingerprint, 'can_authenticate'); } get isQualified(){ return checkKey(this._fingerprint, 'is_qualified'); } get armored(){ let me = this; return new Promise(function(resolve, reject){ let conn = new Connection(); conn.setFlag('armor', true); conn.post('export',{'fpr': me._fingerprint}); }); // TODO return value not yet checked. Should result in an armored block // in correct encoding // TODO openpgpjs also returns secKey if private = true? } /** * TODO returns true if this is the default key used to sign */ get isDefault(){ throw('NOT_YET_IMPLEMENTED'); } /** * get the Key's subkeys as GPGME_Key objects * @returns {Array} */ get subkeys(){ return checkKey(this._fingerprint, 'subkeys').then(function(result){ // TBD expecting a list of fingerprints if (!Array.isArray(result)){ result = [result]; } let resultset = []; for (let i=0; i < result.length; i++){ let subkey = new GPGME_Key(result[i]); if (subkey instanceof GPGME_Key){ resultset.push(subkey); } } return Promise.resolve(resultset); + }, function(error){ + //TODO checkKey fails }); } /** * creation time stamp of the key * @returns {Date|null} TBD */ get timestamp(){ return checkKey(this._fingerprint, 'timestamp'); //TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available. } /** * The expiration timestamp of this key TBD * @returns {Date|null} TBD */ get expires(){ return checkKey(this._fingerprint, 'expires'); // TODO convert to Date; check for 0 } /** * getter name TBD * @returns {String|Array} The user ids associated with this key */ get userIds(){ return checkKey(this._fingerprint, 'uids'); } /** * @returns {String} The public key algorithm supported by this subkey */ get pubkey_algo(){ return checkKey(this._fingerprint, 'pubkey_algo'); } }; /** * generic function to query gnupg information on a key. * @param {*} fingerprint The identifier of the Keyring * @param {*} property The gpgme-json property to check * */ 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){ if (!isFingerprint(fingerprint)){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage ('keyinfo'); if (msg instanceof Error){ 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 @@ -1,157 +1,161 @@ /* 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 {createMessage} from './Message' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; export class GPGME_Keyring { constructor(connection){ this.connection = connection; } set connection(connection){ if (!this._connection && connection instanceof Connection){ this._connection = connection; } } get connection(){ if (this._connection instanceof Connection){ if (this._connection.isConnected){ return this._connection; } return gpgme_error('CONN_DISCONNECTED'); } return gpgme_error('CONN_NO_CONNECT'); } /** * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds * @param {Boolean} (optional) Include listing of secret keys * @returns {Promise.>} * */ getKeys(pattern, include_secret){ let msg = createMessage('listkeys'); if (msg instanceof Error){ return Promise.reject(msg); } if (pattern && typeof(pattern) === 'string'){ msg.setParameter('pattern', pattern); } if (include_secret){ msg.setParameter('with-secret', true); } this.connection.post(msg).then(function(result){ let fpr_list = []; let resultset = []; if (!Array.isArray(result.keys)){ //TODO check assumption keys = Array fpr_list = [result.keys]; } else { fpr_list = result.keys; } for (let i=0; i < fpr_list.length; i++){ let newKey = new GPGME_Key(fpr_list[i]); if (newKey instanceof GPGME_Key){ resultset.push(newKey); } } return Promise.resolve(resultset); + }, function(error){ + //TODO error handling }); } /** * @param {Object} flags subset filter expecting at least one of the * filters described below. True will filter on the condition, False will * reverse the filter, if not present or undefined, the filter will not be * considered. Please note that some combination may not make sense * @param {Boolean} flags.secret Only Keys containing a secret part. * @param {Boolean} flags.revoked revoked Keys only * @param {Boolean} flags.expired Expired Keys only * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds * @returns {Promise Array} * */ getSubset(flags, pattern){ if (flags === undefined) { throw('ERR_WRONG_PARAM'); }; let secretflag = false; if (flags.hasOwnProperty(secret) && flags.secret){ secretflag = true; } this.getKeys(pattern, secretflag).then(function(queryset){ let resultset = []; for (let i=0; i < queryset.length; i++ ){ let conditions = []; let anticonditions = []; if (secretflag === true){ conditions.push('hasSecret'); } else if (secretflag === false){ anticonditions.push('hasSecret'); } /** if (flags.defaultKey === true){ conditions.push('isDefault'); } else if (flags.defaultKey === false){ anticonditions.push('isDefault'); } */ /** * if (flags.valid === true){ anticonditions.push('isInvalid'); } else if (flags.valid === false){ conditions.push('isInvalid'); } */ if (flags.revoked === true){ conditions.push('isRevoked'); } else if (flags.revoked === false){ anticonditions.push('isRevoked'); } if (flags.expired === true){ conditions.push('isExpired'); } else if (flags.expired === false){ anticonditions.push('isExpired'); } let decision = undefined; for (let con = 0; con < conditions.length; con ++){ if (queryset[i][conditions[con]] !== true){ decision = false; } } for (let acon = 0; acon < anticonditions.length; acon ++){ if (queryset[i][anticonditions[acon]] === true){ decision = false; } } if (decision !== false){ resultset.push(queryset[i]); } } 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 @@ -1,117 +1,167 @@ /* 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 { permittedOperations } from './permittedOperations' import { gpgme_error } from './Errors' export function createMessage(operation){ if (typeof(operation) !== 'string'){ return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ return new GPGME_Message(operation); } else { return gpgme_error('MSG_WRONG_OP'); } } /** * Prepares a communication request. It checks operations and parameters in * ./permittedOperations. * @param {String} operation */ export class GPGME_Message { //TODO getter constructor(operation){ this.operation = operation; } set operation (op){ if (typeof(op) === "string"){ if (!this._msg){ this._msg = {}; } if (!this._msg.op & permittedOperations.hasOwnProperty(op)){ this._msg.op = op; } } } get operation(){ return this._msg.op; } /** * Sets a parameter for the message. Note that the operation has to be set * first, to be able to check if the parameter is permittted * @param {String} param Parameter to set * @param {any} value Value to set //TODO: Some type checking * @returns {Boolean} If the parameter was set successfully */ setParameter(param,value){ if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } let po = permittedOperations[this._msg.op]; 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; } /** * Check if the message has the minimum requirements to be sent, according * to the definitions in permittedOperations * @returns {Boolean} */ get isComplete(){ 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; } } return true; } /** * 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; } } } 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 @@ -1,183 +1,189 @@ /* 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 {Connection} from "./Connection" import {GPGME_Message, createMessage} from './Message' import {toKeyIdArray} from "./Helpers" import { gpgme_error } from "./Errors" import { GPGME_Keyring } from "./Keyring"; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port * TODO: add configuration */ constructor(connection){ 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){ if (ring && ring instanceof GPGME_Keyring){ this._Keyring = ring; } } get Keyring(){ return this._Keyring; } /** * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message */ encrypt(data, publicKeys, wildcard=false){ let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg) } // TODO temporary msg.setParameter('armor', true); msg.setParameter('always-trust', true); let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); 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')); + } } /** * @param {String} data TODO Format: base64? String? Message with the encrypted data * @returns {Promise} decrypted message: data: The decrypted data. This may be base64 encoded. base64: Boolean indicating whether data is base64 encoded. mime: A Boolean indicating whether the data is a MIME object. info: An optional object with extra information. * @async */ decrypt(data){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (msg instanceof Error){ return Promise.reject(msg); } putData(msg, data); return this.connection.post(msg); } deleteKey(key, delete_secret = false, no_confirm = false){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); let msg = createMessage('deletekey'); if (msg instanceof Error){ return Promise.reject(msg); } let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ return Promise.reject( gpgme_error('GENERIC_ERROR')); // TBD should always be ONE key? } msg.setParameter('key', key_arr[0]); if (delete_secret === true){ msg.setParameter('allow_secret', true); // TBD } if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? 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')); + } } } /** * Sets the data of the message, converting Uint8Array to base64 and setting * the base64 flag * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter * @param {String} propertyname // TODO unchecked still */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { 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); message.setParameter ('data', decoder.decode(data)); } else if (typeof(data) === 'string') { message.setParameter('base64', false); message.setParameter('data', data); } else if ( typeof(data) === 'object' && data.hasOwnProperty(getText)){ let txt = data.getText(); if (txt instanceof Uint8Array){ let decoder = new TextDecoder('utf8'); message.setParameter('base64', true); message.setParameter ('data', decoder.decode(txt)); } } else { return gpgme_error('PARAM_WRONG'); } } 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 @@ -1,275 +1,280 @@ /* 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+ */ /** * This is a compatibility API to be used as openpgpjs syntax. * Non-implemented options will throw an error if set (not null or undefined) * TODO Some info about differences */ import { GpgME } from "./gpgmejs"; import {GPGME_Keyring} from "./Keyring" import { GPGME_Key } from "./Key"; import { isFingerprint } from "./Helpers" import { gpgme_error } from "./Errors" export class GpgME_openpgpmode { constructor(connection, config = {}){ this.initGpgME(connection, config); } get Keyring(){ if (this._keyring){ return this._keyring; } return undefined; } initGpgME(connection, config = {}){ if (connection && typeof(config) ==='object'){ this._config = config; if (!this._GPGME){ this._GpgME = new GpgME(connection, config); } if (!this._keyring){ this._keyring = new GPGME_Keyring_openpgpmode(connection); } } } /** * Encrypt Message * Supported: * @param {String|Uint8Array} data * //an openpgp Message also accepted here. TODO: is this wanted? * @param {Key|Array} publicKeys * //Strings of Fingerprints * @param {Boolean} wildcard * TODO: * @param {Key|Array} privateKeys // -> encryptsign * @param {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress * @param {Boolean} armor // TODO base64 switch * @param {Boolean} detached // --> encryptsign * unsupported: * @param {String|Array} passwords * @param {Object} sessionKey * @param {Signature} signature * @param {Boolean} returnSessionKey * @param {String} filename * * Can be set, but will be ignored: * * @returns {Promise} * {data: ASCII armored message, * signature: detached signature if 'detached' is true * } * @async * @static */ encrypt({data = '', publicKeys = '', privateKeys, passwords=null, sessionKey = null, filename, compression, armor=true, detached=false, signature=null, returnSessionKey=null, wildcard=false, date=null}) { if (passwords !== null || sessionKey !== null || signature !== null || returnSessionKey !== null || date !== null ){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || compression || armor === false || detached == true){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } if (filename){ if (this._config.unconsidered_params === 'warn'){ GPMGEJS_Error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'error'){ 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 * supported openpgpjs parameters: * @param {Message|Uint8Array|String} message Message object from openpgpjs * Unsupported: * @param {String|Array} passwords * @param {Key|Array} privateKeys * @param {Object|Array} sessionKeys * Not yet supported, but planned * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification * Ignored values: can be safely set, but have no effect * @param {Date} date * @param {Key|Array} publicKeys * * @returns {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @async * @static */ decrypt({ message, privateKeys, passwords=null, sessionKeys, publicKeys, format='utf8', signature=null, date= null}) { if (passwords !== null || sessionKeys || privateKeys){ return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } if ( format !== 'utf8' || signature){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } if (date !== null || publicKeys){ if (this._config.unconsidered_params === 'warn'){ GPMGEJS_Error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'reject'){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } return this._GpgME.decrypt(message); // TODO: translate between: // openpgp: // { data:Uint8Array|String, // filename:String, // signatures:[{ keyid:String, valid:Boolean }] } // and gnupg: // data: The decrypted data. This may be base64 encoded. // base64: Boolean indicating whether data is base64 encoded. // mime: A Boolean indicating whether the data is a MIME object. // info: An optional object with extra information. } } /** * Translation layer offering basic Keyring API to be used in Mailvelope. * It may still be changed/expanded/merged with GPGME_Keyring */ class GPGME_Keyring_openpgpmode { constructor(connection){ this._gpgme_keyring = new GPGME_Keyring(connection); } /** * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This * includes keys openpgpjs considers 'private' (usable for signing), with * the difference that Key.armored will NOT contain any secret information. * Please also note that a GPGME_Key does not offer full openpgpjs- Key * compatibility. * @returns {Array} * //TODO: Check if IsDefault is also always hasSecret * TODO Check if async is required */ getPublicKeys(){ return translateKeys( this._gpgme_keyring.getKeys(null, true)); } /** * Returns the Default Key used for crypto operation in gnupg. * Please note that the armored property does not contained secret key blocks, * despite secret blocks being part of the key itself. * @returns {Promise } */ getDefaultKey(){ this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){ if (result.length === 1){ return Promise.resolve( translateKeys(result)[0]); } else { // TODO: Can there be "no default key"? // TODO: Can there be several default keys? return gpgme_error('TODO'); } + }, function(error){ + //TODO }); } /** * Deletes a Key * @param {Object} Object identifying key * @param {String} key.fingerprint - fingerprint of the to be deleted key * @param {Boolean} key.secret - indicator if private key should be deleted as well * @returns {Promise., Error>} TBD: Not sure what is wanted TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists */ deleteKey(key){ if (typeof(key) !== "object"){ return Promise.reject(gpgme_error('PARAM_WRONG')); } if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let key_to_delete = new GPGME_Key(key.fingerprint); return key_to_delete.deleteKey(key.secret); } } /** * TODO error handling. * Offers the Key information as the openpgpmode wants */ class GPGME_Key_openpgpmode { constructor(value){ this.init = value; } set init (value){ if (!this._GPGME_Key && value instanceof GPGME_Key){ this._GPGME_Key = value; } else if (!this._GPGME_Key && isFingerprint(value)){ this._GPGME_Key = new GPGME_Key(value); } } get fingerprint(){ return this._GPGME_Key.fingerprint; } get armor(){ return this._GPGME_Key.armored; } get secret(){ return this._GPGME_Key.hasSecret; } get default(){ return this._GPGME_Key.isDefault; } } /** * creates GPGME_Key_openpgpmode from GPGME_Keys */ function translateKeys(input){ + if (!input){ + return null; + } if (!Array.isArray(input)){ input = [input]; } let resultset; for (let i=0; i< input.length; i++){ resultset.push(new GPGME_Key_openpgpmode(input[i])); } return resultset; } \ No newline at end of file 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 @@ -1,83 +1,88 @@ /* 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 { GpgME } from "./gpgmejs"; import { gpgme_error } from "./Errors"; import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; import { defaultConf, availableConf } from "./Config"; /** * Initializes a nativeMessaging Connection and returns a GPGMEjs object * @param {Object} config Configuration. See Config.js for available parameters. Still TODO */ function init(config){ let _conf = parseconfiguration(config); if (_conf instanceof Error){ return Promise.reject(_conf); } return new Promise(function(resolve, reject){ let connection = new Connection; // TODO: Delayed reaction is ugly. We need to listen to the port's // event listener in isConnected, but this takes some time (<5ms) to // disconnect if there is no successfull connection. let delayedreaction = function(){ if (connection.isConnected === true){ if (_conf.api_style && _conf.api_style === 'gpgme_openpgpjs'){ resolve(new GpgME_openpgpmode(connection, _conf)); } else { resolve(new GpgME(connection)); } } else { reject(gpgme_error('CONN_NO_CONNECT')); } }; setTimeout(delayedreaction, 5); }); } -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 { result_config[conf_keys[i]] = value; } } else { 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; }; export default { init: init } \ No newline at end of file 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 @@ -1,126 +1,176 @@ /* 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+ */ /** * 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: The payload property of the answer. May be partial and in need of concatenation params: Array Information that do not change throughout the message infos: Array<*> arbitrary information that may result in a list } } */ 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'], params: ['base64'], infos: [] } }, 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}] } }, /** keyinfo: { // querying the Key's information. required: ['fingerprint'], anser: { type: ['TBD'], data: [], params: ['hasSecret', 'isRevoked', 'isExpired', 'armored', 'timestamp', 'expires', 'pubkey_algo'], infos: ['subkeys', 'userIds'] }*/ /** listkeys:{ optional: ['with-secret', 'pattern'], answer: { type: ['TBD'], //Array of fingerprints? infos: ['TBD'] //the property with infos }, */ /** importkey: { required: ['keyarmored'], answer: { type: ['TBD'], infos: [''], // for each key if import was a success, if it was an update } }, */ /** deletekey: { required: ['fingerprint'], answer: { type ['TBD'], infos: [''] //success:true? in gpgme, an error NO_ERROR is returned } } */ /** *get armored secret different treatment from keyinfo! */ /** * TBD key modification requests? */ } diff --git a/lang/js/test/Helpers.js b/lang/js/test/Helpers.js index 5d8909f9..6b5a5382 100644 --- a/lang/js/test/Helpers.js +++ b/lang/js/test/Helpers.js @@ -1,97 +1,97 @@ /* 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 { expect } from "../node_modules/chai/chai"; import { gpgme_error} from "../src/Errors"; 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'); 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(helper_params.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(helper_params.validFingerprint); expect(test0).to.be.true; }); it('isFingerprint(): invalid Fingerprint', function(){ let test0 = isFingerprint(helper_params.invalidFingerprint); expect(test0).to.be.false; }); }); describe('Converting to Fingerprint', function(){ it('Correct Inputs', function(){ it('Fingerprint string', function(){ let test0 = toKeyIdArray(helper_params.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(helper_params.validFingerprint); }); it('GPGME_Key', function(){ expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); let test0 = toKeyIdArray(helper_params.validGPGME_Key); expect(test0).to.be.an('array'); expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); }); it('Array of valid inputs', function(){ let test0 = toKeyIdArray(helper_params.validKeys); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf(helper_params.validKeys.length); }); }); describe('Incorrect inputs', function(){ it('valid Long ID', function(){ let test0 = toKeyIdArray(helper_params.validLongId); expect(test0).to.be.empty; }); it('invalidFingerprint', function(){ let test0 = toKeyIdArray(helper_params.invalidFingerprint); expect(test0).to.be.empty; }); it('invalidKeyArray', function(){ let test0 = toKeyIdArray(helper_params.invalidKeyArray); expect(test0).to.be.empty; }); it('Partially invalid array', function(){ let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf( helper_params.invalidKeyArray_OneBad.length - 1); }); }); }); }; export default Helpertest; \ No newline at end of file diff --git a/lang/js/test/Message.js b/lang/js/test/Message.js index 44206fba..a7dd3af5 100644 --- a/lang/js/test/Message.js +++ b/lang/js/test/Message.js @@ -1,43 +1,93 @@ /* 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 { 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; \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/test/index.js similarity index 83% copy from lang/js/BrowserTestExtension/tests/inputvalues.js copy to lang/js/test/index.js index 47600c84..b8520515 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/test/index.js @@ -1,28 +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 . * SPDX-License-Identifier: LGPL-2.1+ */ +import { Helpertest } from "./Helpers"; +import { Messagetest } from "./Message"; -var inputvalues = { - encrypt: { - good:{ - data : 'Hello World.', - keyid : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' - } - } -}; +/** + * 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 @@ -1,29 +1,39 @@ import {GPGME_Key} from "../src/Key" export const helper_params = { validLongId: '0A0A0A0A0A0A0A0A', validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], invalidLongId: '9A9A7A7A8A9A9A7A7A8A', invalidFingerprint: [{hello:'World'}], invalidKeyArray: {curiosity:'uncat'}, invalidKeyArray_OneBad: [ new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], invalidErrorCode: 'Please type in all your passwords.' } 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 } \ No newline at end of file