From eff27d6387b1cad2ef9901fa03dbee2ea86c786a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 25 May 2018 11:53:24 +0200 Subject: [PATCH] js: use version operation for connection checks -- * src/Connection.js: isConnected was renamed to checkConnection, that returns a promise with either version information or Boolean * Connection checks have been adapted to reflect that checkConnection returns a Promise * BrowsertestExtension: tests/signTest.js was missing from my last commit --- .../js/BrowserTestExtension/tests/signTest.js | 58 ++++++++++++++++++ lang/js/BrowserTestExtension/tests/startup.js | 3 - lang/js/src/Connection.js | 60 ++++++++++++------- lang/js/src/Errors.js | 4 -- lang/js/src/Keyring.js | 58 +++++++++--------- lang/js/src/gpgmejs.js | 8 +-- lang/js/src/index.js | 22 +++---- lang/js/unittests.js | 57 +++++++++--------- 8 files changed, 162 insertions(+), 108 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/signTest.js diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js new file mode 100644 index 00000000..e3323721 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -0,0 +1,58 @@ +/* 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('Signing', function () { + it('Sign a message', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = bigString(100); + context.sign( + 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 SIGNATURE'); + expect(answer.data).to.include('END PGP SIGNATURE'); + expect(answer.data).to.include(data); + context.connection.disconnect(); + done(); + }); + }); + }); + it('Detached sign a message', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = bigString(100); + context.sign( + data, + inputvalues.encrypt.good.fingerprint, + 'detached' + ).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include(data); + expect(answer.signature).to.be.a('string'); + expect(answer.signature).to.be.a('string'); + context.connection.disconnect(); + done(); + }); + }); + }); + +}); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 5de70a6b..ebecf4fb 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -30,9 +30,6 @@ expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); done(); - }, function(errorr){ - expect(error).to.be.undefined; - done(); }); }); }); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 9c2a6428..07df5def 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -25,7 +25,7 @@ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from "./Errors" -import { GPGME_Message } from "./Message"; +import { GPGME_Message, createMessage } from "./Message"; /** * A Connection handles the nativeMessaging interaction. @@ -34,18 +34,42 @@ 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 + * Retrieves the information about the backend. + * @param {Boolean} details (optional) If set to false, the promise will + * just return a connection status + * @returns {Promise} + * {String} The property 'gpgme': Version number of gpgme + * {Array} 'info' Further information about the backends. + * Example: + * "protocol": "OpenPGP", + * "fname": "/usr/bin/gpg", + * "version": "2.2.6", + * "req_version": "1.4.0", + * "homedir": "default" */ - get isConnected(){ - return this._isConnected; + checkConnection(details = true){ + if (details === true) { + return this.post(createMessage('version')); + } else { + let me = this; + return new Promise(function(resolve,reject) { + Promise.race([ + me.post(createMessage('version')), + new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(gpgme_error('CONN_TIMEOUT')); + }, 500); + }) + ]).then(function(result){ + resolve(true); + }, function(reject){ + resolve(false); + }); + }); + } } /** @@ -54,6 +78,7 @@ export class Connection{ disconnect() { if (this._connection){ this._connection.disconnect(); + this._connection = null; } } @@ -61,17 +86,8 @@ export class Connection{ * Opens a nativeMessaging port. */ connect(){ - if (this._isConnected === true){ - gpgme_error('CONN_ALREADY_CONNECTED'); - } else { - this._isConnected = true; + if (!this._connection){ this._connection = chrome.runtime.connectNative('gpgmejson'); - let me = this; - this._connection.onDisconnect.addListener( - function(){ - me._isConnected = false; - } - ); } } @@ -82,8 +98,8 @@ export class Connection{ * information. */ post(message){ - if (!this.isConnected){ - return Promise.reject(gpgme_error('CONN_DISCONNECTED')); + if (!this._connection) { + } if (!message || !message instanceof GPGME_Message){ return Promise.reject(gpgme_error('PARAM_WRONG'), message); @@ -199,7 +215,7 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = []; } - this._response.push(msg[key]); + this._response[key].push(msg[key]); } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index bfe3a2f4..7e98f319 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -25,10 +25,6 @@ const err_list = { + ' established.', type: 'error' }, - 'CONN_DISCONNECTED': { - msg:'Connection with the nativeMessaging host was lost.', - type: 'error' - }, 'CONN_EMPTY_GPG_ANSWER':{ msg: 'The nativeMessaging answer was empty.', type: 'error' diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 4596035a..80792f77 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -36,10 +36,7 @@ export class GPGME_Keyring { } get connection(){ if (this._connection instanceof Connection){ - if (this._connection.isConnected){ - return this._connection; - } - return gpgme_error('CONN_DISCONNECTED'); + return this._connection; } return gpgme_error('CONN_NO_CONNECT'); } @@ -51,36 +48,35 @@ export class GPGME_Keyring { * */ 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); - } let me = this; - - 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; + return new Promise(function(resolve, reject) { + let msg; + msg = createMessage('listkeys'); + if (pattern && typeof(pattern) === 'string'){ + msg.setParameter('pattern', pattern); } - for (let i=0; i < fpr_list.length; i++){ - let newKey = new GPGME_Key(fpr_list[i], me._connection); - if (newKey instanceof GPGME_Key){ - resultset.push(newKey); + if (include_secret){ + msg.setParameter('with-secret', true); + } + me.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; } - } - return Promise.resolve(resultset); - }, function(error){ - //TODO error handling + for (let i=0; i < fpr_list.length; i++){ + let newKey = new GPGME_Key(fpr_list[i], me._connection); + if (newKey instanceof GPGME_Key){ + resultset.push(newKey); + } + } + resolve(resultset); + }, function(error){ + reject(error); + }); }); } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 1e76655e..c182c175 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -44,13 +44,7 @@ export class GpgME { } get connection(){ - if (this._connection){ - if (this._connection.isConnected === true){ - return this._connection; - } - return undefined; - } - return undefined; + return this._connection; } set Keyring(keyring){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 8527b3f3..7f969fee 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -34,20 +34,16 @@ function init(config){ } 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 in some cases this takes some - // time (<5ms) to disconnect if there is no successfull connection. - let delayedreaction = function(){ - if (connection === undefined) { + connection.checkConnection(false).then( + function(result){ + if (result === true) { + resolve(new GpgME(connection, _conf)); + } else { + reject(gpgme_error('CONN_NO_CONNECT')); + } + }, function(error){ reject(gpgme_error('CONN_NO_CONNECT')); - } - if (connection.isConnected === true){ - resolve(new GpgME(connection, _conf)); - } else { - reject(gpgme_error('CONN_NO_CONNECT')); - } - }; - setTimeout(delayedreaction, 5); + }); }); } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index c437d599..06b2b23a 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -39,34 +39,29 @@ function unittests (){ it('Connecting', function(done) { let conn0 = new Connection; - let delayed = function(){ - expect(conn0.isConnected).to.be.true; - expect(conn0.connect).to.be.a('function'); + conn0.checkConnection().then(function(answer) { + expect(answer).to.not.be.empty; + expect(answer.gpgme).to.not.be.undefined; + expect(answer.gpgme).to.be.a('string'); + expect(answer.info).to.be.an('Array'); expect(conn0.disconnect).to.be.a('function'); expect(conn0.post).to.be.a('function'); done(); - }; - setTimeout(delayed, 5); + }); }); it('Disconnecting', function(done) { let conn0 = new Connection; - let delayed = function(){ - conn0.disconnect(); // TODO fails! - expect(conn0.isConnected).to.be.false; - done(); - }; - setTimeout(delayed, 5); + conn0.checkConnection(false).then(function(answer) { + expect(answer).to.be.true; + conn0.disconnect(); + conn0.checkConnection(false).then(function(result) { + expect(result).to.be.false; + done(); + }); + }); }); - - // broken - // it('Connect info still only available after a delay', function(done){ - // // if false, all delayed connections can be refactored - // let conn0 = new Connection; - // expect(conn0.isConnected).to.be.undefined; - // // - // }) }); describe('Error Object handling', function(){ @@ -181,14 +176,17 @@ function unittests (){ // TODO not implemented yet: Further Key functionality }); - it('Key can use the connection', function(){ + it('Key can use the connection', function(done){ let conn = new Connection; let key = createKey(hp.validFingerprint, conn); - - expect(key.connection.isConnected).to.be.true; - - key.connection.disconnect(); - expect(key.connection.isConnected).to.be.false; + key.connection.checkConnection(false).then(function(result){ + expect(result).to.be.true; + key.connection.disconnect(); + key.connection.checkConnection(false).then(function(result2){ + expect(result2).to.be.false; + done(); + }); + }); }); it('createKey returns error if parameters are wrong', function(){ @@ -232,12 +230,15 @@ function unittests (){ it('Keyring should return errors if not connected', function(){ let keyring = new GPGME_Keyring; - expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.connection).to.be.an.instanceof(Error); expect(keyring.connection.code).to.equal('CONN_NO_CONNECT'); - expect(keyring.getKeys).to.be.an.instanceof(Error); - expect(keyring.getkeys.code).to.equal('CONN_NO_CONNECT'); + // not yet implemented: + // keyring.getKeys().then( + // function(result){}, + //function(reject){ + // expect(reject).to.be.an.instanceof(Error); + // done(); }); //TODO not yet implemented: // getKeys(pattern, include_secret) //note: pattern can be null