diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 2fe955e6..f5d2be16 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -1,225 +1,218 @@ /* 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 and Decryption', function () { it('Successful encrypt and decrypt simple string', 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'); context.decrypt(answer.data).then(function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(inputvalues.encrypt.good.data); - context.connection.disconnect(); done(); }); }); }); }); it('Decrypt simple non-ascii', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { let data = encryptedData; context.decrypt(data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal( '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); done(); }); }); }).timeout(3000); it('Roundtrip does not destroy trailing whitespace', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { let data = 'Keks. \rKeks \n Keks \r\n'; context.encrypt(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'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(5000); for (let j = 0; j < inputvalues.encrypt.good.data_nonascii_32.length; j++){ it('Roundtrip with >1MB non-ascii input meeting default chunksize (' + (j + 1) + '/' + inputvalues.encrypt.good.data_nonascii_32.length + ')', function (done) { let input = inputvalues.encrypt.good.data_nonascii_32[j]; expect(input).to.have.length(32); let prm = Gpgmejs.init(); prm.then(function (context) { let data = ''; for (let i=0; i < 34 * 1024; i++){ data += input; } context.encrypt(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'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(3000); }; it('Random data, as string', function (done) { let data = bigString(1000); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(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'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(3000); it('Data, input as base64', function (done) { let data = inputvalues.encrypt.good.data; let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, 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'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(3000); it('Random data, input as base64', function (done) { //TODO fails. The result is let data = bigBoringString(0.001); let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, inputvalues.encrypt.good.fingerprint, true).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'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(3000); it('Random data, input and output as base64', function (done) { let data = bigBoringString(0.0001); let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, 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'); context.decrypt(answer.data, true).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(b64data); - context.connection.disconnect(); done(); }); }); }); }).timeout(3000); }); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 521ed276..a16f993c 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -1,156 +1,148 @@ /* 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('Successful 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'); - context.connection.disconnect(); done(); }); }); }); it('Successful encrypt 5 MB', function (done) { let prm = Gpgmejs.init(); let data = fixedLengthString(5); prm.then(function (context) { context.encrypt( 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'); - context.connection.disconnect(); done(); }); }); }).timeout(10000); it('Successful encrypt 20 MB', function (done) { let prm = Gpgmejs.init(); let data = fixedLengthString(20); prm.then(function (context) { context.encrypt( 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'); - context.connection.disconnect(); done(); }); }); }).timeout(20000); it('Successful encrypt 50 MB', function (done) { let prm = Gpgmejs.init(); let data = fixedLengthString(50); prm.then(function (context) { context.encrypt( 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'); - context.connection.disconnect(); done(); }); }); }).timeout(20000); it('Sending encryption without keys fails', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( inputvalues.encrypt.good.data, null).then(function (answer) { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); - context.connection.disconnect(); done(); }); }); }); it('Sending encryption without data fails', function (done) { 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'); - context.connection.disconnect(); done(); }); }); }); it('Sending encryption with non existing keys fails', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( inputvalues.encrypt.good.data, inputvalues.encrypt.bad.fingerprint).then(function (answer) { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.not.be.undefined; expect(error.code).to.equal('GNUPG_ERROR'); - context.connection.disconnect(); done(); }); }); }).timeout(5000);; it('Overly large message ( > 65MB) is rejected', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( fixedLengthString(65), inputvalues.encrypt.good.fingerprint).then(function (answer) { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an.instanceof(Error); // expect(error.code).to.equal('GNUPG_ERROR'); // TODO: there is a 64 MB hard limit at least in chrome at: // chromium//extensions/renderer/messaging_util.cc: // kMaxMessageLength - context.connection.disconnect(); done(); }); }); }).timeout(8000); // TODO check different valid parameter }); diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index 4e55fd26..5c588f27 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -1,40 +1,39 @@ describe('Long running Encryption/Decryption', function () { for (let i=0; i < 100; i++) { it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { let prm = Gpgmejs.init(); let data = bigString(2*1024*1024); prm.then(function (context) { context.encrypt(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'); context.decrypt(answer.data).then( function(result){ expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); if (result.data.length !== data.length) { console.log('diff: ' + (result.data.length - data.length)); for (let i=0; i < result.data.length; i++){ if (result.data[i] !== data[i]){ console.log('position: ' + i); console.log('result : '+ result.data.charCodeAt(i) + result.data[i-2] + result.data[i-1] + result.data[i] + result.data[i+1] + result.data[i+2]); console.log('original: ' + data.charCodeAt(i) + data[i-2] + data[i-1] + data[i] + data[i+1] + data[i+2]); break; } } } expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); }); }).timeout(8000); }; }); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js index e3323721..2e5edb30 100644 --- a/lang/js/BrowserTestExtension/tests/signTest.js +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -1,58 +1,56 @@ /* 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 ebecf4fb..7d13ea47 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -1,51 +1,48 @@ /* 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){ 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(); }); }); }); 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/src/Connection.js b/lang/js/src/Connection.js index 3b442622..3480d811 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,263 +1,269 @@ /* 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, createMessage } from "./Message"; /** * A Connection handles the nativeMessaging interaction. */ export class Connection{ constructor(){ this.connect(); } /** * 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" */ 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); }); }); } } /** * Immediately closes the open port. */ disconnect() { if (this._connection){ this._connection.disconnect(); this._connection = null; } } /** * Opens a nativeMessaging port. */ connect(){ if (!this._connection){ this._connection = chrome.runtime.connectNative('gpgmejson'); } } /** * 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._connection) { } if (!message || !message instanceof GPGME_Message){ + this.disconnect(); return Promise.reject(gpgme_error('PARAM_WRONG'), message); } if (message.isComplete !== true){ + this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; return new Promise(function(resolve, reject){ let answer = new Answer(message); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) + me._connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); reject(gpgme_error('GNUPG_ERROR', msg.msg)); } else { let answer_result = answer.add(msg); if (answer_result !== true){ me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); reject(answer_result); - } - if (msg.more === true){ + } else if (msg.more === true){ me._connection.postMessage({'op': 'getmore'}); } else { me._connection.onMessage.removeListener(listener) + me._connection.disconnect(); 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(){ + me._connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ - return result; + return result; }, function(reject){ if(!reject instanceof Error) { + me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; } }); } }); } }; /** * 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(message){ this.operation = message.operation; this.expected = message.expected; } /** * 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] += 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] = []; } if (Array.isArray(msg[key])) { for (let i=0; i< msg[key].length; i++) { this._response[key].push(msg[key][i]); } } else { this._response[key].push(msg[key][i]); } } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; } } return true; } /** * @returns {Object} the assembled message, original data assumed to be * (javascript-) strings */ get message(){ let keys = Object.keys(this._response); let msg = {}; let poa = permittedOperations[this.operation].answer; for (let i=0; i < keys.length; i++) { if (poa.data.indexOf(keys[i]) >= 0 && this._response.base64 === true ) { msg[keys[i]] = atob(this._response[keys[i]]); if (this.expected === 'base64'){ msg[keys[i]] = this._response[keys[i]]; } else { msg[keys[i]] = decodeURIComponent( atob(this._response[keys[i]]).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } } else { msg[keys[i]] = this._response[keys[i]]; } } return msg; } } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 7d3d82b1..13c99542 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,427 +1,397 @@ /* 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, isLongId } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; -import { Connection } from './Connection'; /** - * Validates the fingerprint, and checks for tha availability of a connection. - * If both are available, a Key will be returned. + * Validates the fingerprint. * @param {String} fingerprint - * @param {Object} parent Either a Connection, or the invoking object with a - * Connection (e.g. Keyring) */ -export function createKey(fingerprint, parent){ +export function createKey(fingerprint){ if (!isFingerprint(fingerprint)){ return gpgme_error('PARAM_WRONG'); } - if ( parent instanceof Connection){ - return new GPGME_Key(fingerprint, parent); - } else if ( parent.hasOwnProperty('connection') && - parent.connection instanceof Connection){ - return new GPGME_Key(fingerprint, parent.connection); - } else { - return gpgme_error('PARAM_WRONG'); - } + else return new GPGME_Key(fingerprint); } /** * Representing the Keys as stored in GPG */ export class GPGME_Key { - constructor(fingerprint, connection){ + constructor(fingerprint){ this.fingerprint = fingerprint; - this.connection = connection; - } - - set connection(conn){ - if (this._connection instanceof Connection) { - gpgme_error('CONN_ALREADY_CONNECTED'); - } else if (conn instanceof Connection ) { - this._connection = conn; - } - } - - get connection(){ - if (!this._data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - if (!this._connection instanceof Connection){ - return gpgme_error('CONN_NO_CONNECT'); - } else { - return this._connection; - } } set fingerprint(fpr){ if (isFingerprint(fpr) === true) { if (this._data === undefined) { this._data = {fingerprint: fpr}; } else { if (this._data.fingerprint === undefined){ this._data.fingerprint = fpr; } } } } get fingerprint(){ if (!this._data || !this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } return this._data.fingerprint; } /** * * @param {Object} data Bulk set data for this key, with the Object as sent * by gpgme-json. * @returns {GPGME_Key|GPGME_Error} The Key object itself after values have * been set */ setKeydata(data){ if (this._data === undefined) { this._data = {}; } if ( typeof(data) !== 'object') { return gpgme_error('KEY_INVALID'); } if (!this._data.fingerprint && isFingerprint(data.fingerprint)){ if (data.fingerprint !== this.fingerprint){ return gpgme_error('KEY_INVALID'); } this._data.fingerprint = data.fingerprint; } else if (this._data.fingerprint !== data.fingerprint){ return gpgme_error('KEY_INVALID'); } let booleans = ['expired', 'disabled','invalid','can_encrypt', 'can_sign','can_certify','can_authenticate','secret', 'is_qualified']; for (let b =0; b < booleans.length; b++) { if ( !data.hasOwnProperty(booleans[b]) || typeof(data[booleans[b]]) !== 'boolean' ){ return gpgme_error('KEY_INVALID'); } this._data[booleans[b]] = data[booleans[b]]; } if (typeof(data.protocol) !== 'string'){ return gpgme_error('KEY_INVALID'); } // TODO check valid protocols? this._data.protocol = data.protocol; if (typeof(data.owner_trust) !== 'string'){ return gpgme_error('KEY_INVALID'); } // TODO check valid values? this._data.owner_trust = data.owner_trust; // TODO: what about origin ? if (!Number.isInteger(data.last_update)){ return gpgme_error('KEY_INVALID'); } this._data.last_update = data.last_update; this._data.subkeys = []; if (data.hasOwnProperty('subkeys')){ if (!Array.isArray(data.subkeys)){ return gpgme_error('KEY_INVALID'); } for (let i=0; i< data.subkeys.length; i++) { this._data.subkeys.push( new GPGME_Subkey(data.subkeys[i])); } } this._data.userids = []; if (data.hasOwnProperty('userids')){ if (!Array.isArray(data.userids)){ return gpgme_error('KEY_INVALID'); } for (let i=0; i< data.userids.length; i++) { this._data.userids.push( new GPGME_UserId(data.userids[i])); } } return this; } /** * Query any property of the Key list * (TODO: armor not in here, might be unexpected) * @param {String} property Key property to be retreived * @param {*} cached (optional) if false, the data will be directly queried * from gnupg. * @returns {*|Promise<*>} the value, or if not cached, a Promise * resolving on the value */ get(property, cached=true) { if (cached === false) { let me = this; return new Promise(function(resolve, reject) { me.refreshKey().then(function(key){ resolve(key.get(property, true)); }, function(error){ reject(error); }); }); } else { if (!this._data.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); } else { return (this._data[property]); } } } /** * Reloads the Key from gnupg */ refreshKey() { let me = this; return new Promise(function(resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', me._data.fingerprint); - me.connection.post(msg).then(function(result){ + console.log(msg); + msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeydata(result.keys[0]); resolve(me); } else { reject(gpgme_error('KEY_NOKEY')); } }, function (error) { reject(gpgme_error('GNUPG_ERROR'), error); }) }); } //TODO: /** * Get the armored block of the non- secret parts of the Key. * @returns {String} the armored Key block. * Notice that this may be outdated cached info. Use the async getArmor if * you need the most current info */ // get armor(){ TODO } /** * Query the armored block of the non- secret parts of the Key directly * from gpg. * Async, returns Promise */ // getArmor(){ TODO } // // get hasSecret(){TODO} // confusing difference to Key.get('secret')! // getHasSecret(){TODO async version} } /** * The subkeys of a Key. Currently, they cannot be refreshed separately */ class GPGME_Subkey { constructor(data){ let keys = Object.keys(data); for (let i=0; i< keys.length; i++) { this.setProperty(keys[i], data[keys[i]]); } } setProperty(property, value){ if (!this._data){ this._data = {}; } if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { this._data[property] = value; } } } /** * * @param {String} property Information to request * @returns {String | Number} * TODO: date properties are numbers with Date in seconds */ get(property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } } } class GPGME_UserId { constructor(data){ let keys = Object.keys(data); for (let i=0; i< keys.length; i++) { this.setProperty(keys[i], data[keys[i]]); } } setProperty(property, value){ if (!this._data){ this._data = {}; } if (validUserIdProperties.hasOwnProperty(property)){ if (validUserIdProperties[property](value) === true) { this._data[property] = value; } } } /** * * @param {String} property Information to request * @returns {String | Number} * TODO: date properties are numbers with Date in seconds */ get(property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } } } const validUserIdProperties = { 'revoked': function(value){ return typeof(value) === 'boolean'; }, 'invalid': function(value){ return typeof(value) === 'boolean'; }, 'uid': function(value){ if (typeof(value) === 'string' || value === ''){ return true;; } return false; }, 'validity': function(value){ if (typeof(value) === 'string'){ return true;; } return false; }, 'name': function(value){ if (typeof(value) === 'string' || value === ''){ return true;; } return false; }, 'email': function(value){ if (typeof(value) === 'string' || value === ''){ return true;; } return false; }, 'address': function(value){ if (typeof(value) === 'string' || value === ''){ return true;; } return false; }, 'comment': function(value){ if (typeof(value) === 'string' || value === ''){ return true;; } return false; }, 'origin': function(value){ return Number.isInteger(value); }, 'last_update': function(value){ return Number.isInteger(value); } }; const validSubKeyProperties = { 'invalid': function(value){ return typeof(value) === 'boolean'; }, 'can_encrypt': function(value){ return typeof(value) === 'boolean'; }, 'can_sign': function(value){ return typeof(value) === 'boolean'; }, 'can_certify': function(value){ return typeof(value) === 'boolean'; }, 'can_authenticate': function(value){ return typeof(value) === 'boolean'; }, 'secret': function(value){ return typeof(value) === 'boolean'; }, 'is_qualified': function(value){ return typeof(value) === 'boolean'; }, 'is_cardkey': function(value){ return typeof(value) === 'boolean'; }, 'is_de_vs': function(value){ return typeof(value) === 'boolean'; }, 'pubkey_algo_name': function(value){ return typeof(value) === 'string'; // TODO: check against list of known?[''] }, 'pubkey_algo_string': function(value){ return typeof(value) === 'string'; // TODO: check against list of known?[''] }, 'keyid': function(value){ return isLongId(value); }, 'pubkey_algo': function(value) { return (Number.isInteger(value) && value >= 0); }, 'length': function(value){ return (Number.isInteger(value) && value > 0); }, 'timestamp': function(value){ return (Number.isInteger(value) && value > 0); }, 'expires': function(value){ return (Number.isInteger(value) && value > 0); } } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 80792f77..9abb9ec3 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,158 +1,144 @@ /* 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 } 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){ - return this._connection; - } - return gpgme_error('CONN_NO_CONNECT'); + constructor(){ } /** * @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 me = this; return new Promise(function(resolve, reject) { let msg; msg = createMessage('listkeys'); if (pattern && typeof(pattern) === 'string'){ msg.setParameter('pattern', pattern); } if (include_secret){ msg.setParameter('with-secret', true); } - me.connection.post(msg).then(function(result){ + msg.post().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], me._connection); + let newKey = new GPGME_Key(fpr_list[i]); if (newKey instanceof GPGME_Key){ resultset.push(newKey); } } resolve(resultset); }, function(error){ reject(error); }); }); } /** * @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 932212a6..5664f723 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -1,196 +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+ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from './Errors' +import { Connection } from './Connection'; 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; this._expected = 'string'; } 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; } set expected(string){ if (string === 'base64'){ this._expected = 'base64'; } } get expected() { if (this._expected === "base64"){ return this._expected; } return "string"; } /** * 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'); } 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'); } let checktype = function(val){ switch(typeof(val)){ case 'string': if (poparam.allowed.indexOf(typeof(val)) >= 0 && val.length > 0) { return true; } return gpgme_error('PARAM_WRONG'); break; case 'number': if ( poparam.allowed.indexOf('number') >= 0 && isNaN(value) === false){ return true; } return gpgme_error('PARAM_WRONG'); break; case 'boolean': if (poparam.allowed.indexOf('boolean') >= 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; } } if (val.length > 0) { 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 = Object.keys( permittedOperations[this._msg.op].required); let msg_params = Object.keys(this._msg); for (let i=0; i < reqParams.length; i++){ 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; } } + + post(){ + let me = this; + return new Promise(function(resolve, reject) { + if (me.isComplete === true) { + let conn = new Connection; + conn.post(me).then(function(response) { + resolve(response); + }, function(reason) { + reject(gpgme_error('GNUPG_ERROR', reason)); + }); + } + else { + reject(gpgme_error('MSG_INCOMPLETE')); + } + }); + } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index c182c175..88a91a60 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,224 +1,199 @@ /* 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; + constructor(config){ //TODO config not parsed + this._config = config; } - set connection(conn){ - if (this._connection instanceof Connection){ - gpgme_error('CONN_ALREADY_CONNECTED'); - } else if (conn instanceof Connection){ - this._connection = conn; - } else { - gpgme_error('PARAM_WRONG'); - } - } - - get connection(){ - return this._connection; - } - - set Keyring(keyring){ + set Keyring(keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } return this._Keyring; } /** * @param {String} data text/data to be encrypted as String * @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, base64=false, 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); if (base64 === true) { msg.setParameter('base64', true); } let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); }; if (msg.isComplete === true){ - return this.connection.post(msg); + return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } } /** * @param {String} data TODO base64? Message with the encrypted data * @param {Boolean} base64 (optional) Response should stay base64 * @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, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (base64 === true){ msg.expected = 'base64'; } if (msg instanceof Error){ return Promise.reject(msg); } putData(msg, data); - return this.connection.post(msg); + return msg.post(); } sign(data, keys, mode='clearsign', base64=false) { //sender if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let key_arr = toKeyIdArray(keys); if (key_arr.length === 0){ return Promise.reject(gpgme_error('MSG_NO_KEYS')); } let msg = createMessage('sign'); msg.setParameter('keys', key_arr); if (base64 === true){ msg.setParameter('base64', true); } msg.setParameter('mode', mode); putData(msg, data); if (mode === 'detached') { msg.expected = 'base64'; } let me = this; return new Promise(function(resolve,reject) { - me.connection.post(msg).then( function(message) { + msg.post().then( function(message) { if (mode === 'clearsign'){ resolve({ data: message.data} ); } else if (mode === 'detached') { resolve({ data: data, signature: message.data }); } }, function(error){ reject(error); }) }); } 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 } 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 - } - }); + return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } } } /** * Sets the data of the message * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { return gpgme_error('PARAM_WRONG'); } if (!data){ return gpgme_error('PARAM_WRONG'); } else if (typeof(data) === 'string') { message.setParameter('data', data); } else if ( typeof(data) === 'object' && typeof(data.getText) === 'function' ){ let txt = data.getText(); if (typeof(txt) === 'string'){ message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } } else { return gpgme_error('PARAM_WRONG'); } } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 7f969fee..220a6984 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -1,82 +1,82 @@ /* 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 { 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; connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(new GpgME(connection, _conf)); + resolve(new GpgME(_conf)); } else { reject(gpgme_error('CONN_NO_CONNECT')); } }, function(error){ reject(gpgme_error('CONN_NO_CONNECT')); }); }); } function parseconfiguration(rawconfig = {}){ if ( typeof(rawconfig) !== 'object'){ return gpgme_error('PARAM_WRONG'); }; 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 = 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/unittests.js b/lang/js/unittests.js index bb06309d..9830a2c5 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,378 +1,332 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ import "./node_modules/mocha/mocha"; import "./node_modules/chai/chai"; import { helper_params as hp } from "./unittest_inputvalues"; import { message_params as mp } from "./unittest_inputvalues"; import { whatever_params as wp } from "./unittest_inputvalues"; import { key_params as kp } from "./unittest_inputvalues"; import { Connection } from "./src/Connection"; import { gpgme_error } from "./src/Errors"; import { toKeyIdArray , isFingerprint } from "./src/Helpers"; import { GPGME_Key , createKey } from "./src/Key"; import { GPGME_Keyring } from "./src/Keyring"; import {GPGME_Message, createMessage} from "./src/Message"; import { setTimeout } from "timers"; mocha.setup('bdd'); var expect = chai.expect; chai.config.includeStack = true; function unittests (){ describe('Connection testing', function(){ it('Connecting', function(done) { let conn0 = new Connection; 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'); - conn0.disconnect(); done(); }); }); it('Disconnecting', function(done) { let conn0 = new Connection; 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(); }); }); }); }); describe('Error Object handling', function(){ // TODO: new GPGME_Error codes it('check the Timeout error', function(){ let test0 = gpgme_error('CONN_TIMEOUT'); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('CONN_TIMEOUT'); }); it('Error Object returns generic code if code is not listed', function(){ let test0 = gpgme_error(hp.invalidErrorCode); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('GENERIC_ERROR'); }); it('Warnings like PARAM_IGNORED should not return errors', function(){ let test0 = gpgme_error('PARAM_IGNORED'); expect(test0).to.be.null; }); }); describe('Fingerprint checking', function(){ it('isFingerprint(): valid Fingerprint', function(){ let test0 = isFingerprint(hp.validFingerprint); expect(test0).to.be.true; }); it('isFingerprint(): invalid Fingerprints', function(){ for (let i=0; i < hp.invalidFingerprints.length; i++){ let test0 = isFingerprint(hp.invalidFingerprints[i]); expect(test0).to.be.false; } }); }); describe('toKeyIdArray() (converting input to fingerprint)', function(){ it('Correct fingerprint string', function(){ let test0 = toKeyIdArray(hp.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validFingerprint); }); it('correct GPGME_Key', function(){ expect(hp.validGPGME_Key).to.be.an.instanceof(GPGME_Key); let test0 = toKeyIdArray(hp.validGPGME_Key); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validGPGME_Key.fingerprint); }); it('openpgpjs-like object', function(){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); expect(test0).to.include( hp.valid_openpgplike.primaryKey.getFingerprint()); }); it('Array of valid inputs', function(){ let test0 = toKeyIdArray(hp.validKeys); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf(hp.validKeys.length); }); it('Incorrect inputs', function(){ it('valid Long ID', function(){ let test0 = toKeyIdArray(hp.validLongId); expect(test0).to.be.empty; }); it('invalidFingerprint', function(){ let test0 = toKeyIdArray(hp.invalidFingerprint); expect(test0).to.be.empty; }); it('invalidKeyArray', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray); expect(test0).to.be.empty; }); it('Partially invalid array', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf( hp.invalidKeyArray_OneBad.length - 1); }); }); }); describe('GPGME_Key', function(){ it('correct Key initialization', function(){ - let conn = new Connection; - let key = createKey(kp.validKeyFingerprint, conn); + let key = createKey(kp.validKeyFingerprint); expect(key).to.be.an.instanceof(GPGME_Key); - expect(key.connection).to.be.an.instanceof(Connection); - conn.disconnect(); }); it('Key has data after a first refresh', function(done) { - let conn = new Connection; - let key = createKey(kp.validKeyFingerprint, conn); + let key = createKey(kp.validKeyFingerprint); key.refreshKey().then(function(key2){ expect(key2).to.be.an.instanceof(GPGME_Key); expect(key2.get).to.be.a('function'); for (let i=0; i < kp.validKeyProperties.length; i++) { let prop = key2.get(kp.validKeyProperties[i]); expect(prop).to.not.be.undefined; expect(prop).to.be.a('boolean'); } expect(isFingerprint(key2.get('fingerprint'))).to.be.true; expect( key2.get('fingerprint')).to.equal(kp.validKeyFingerprint); expect( key2.get('fingerprint')).to.equal(key.fingerprint); - conn.disconnect(); done(); }); }); it('Non-cached key async data retrieval', function (done){ - let conn = new Connection; - let key = createKey(kp.validKeyFingerprint, conn); + let key = createKey(kp.validKeyFingerprint); key.get('can_authenticate',false).then(function(result){ expect(result).to.be.a('boolean'); - conn.disconnect(); done(); }); }) it('Querying non-existing Key returns an error', function(done) { - let conn = new Connection; - let key = createKey(kp.invalidKeyFingerprint, conn); + let key = createKey(kp.invalidKeyFingerprint); key.refreshKey().then(function(){}, function(error){ expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('KEY_NOKEY'); - conn.disconnect(); - done(); - }); - }); - - - it('Key can use the connection', function(done){ - let conn = new Connection; - let key = createKey(hp.validFingerprint, conn); - 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; - conn.disconnect(); done(); - }); }); }); it('createKey returns error if parameters are wrong', function(){ - let conn = new Connection; for (let i=0; i< 4; i++){ - let key0 = createKey(wp.four_invalid_params[i], conn); - + let key0 = createKey(wp.four_invalid_params[i]); expect(key0).to.be.an.instanceof(Error); expect(key0.code).to.equal('PARAM_WRONG'); } - for (let i=0; i< 4; i++){ - let key0 = createKey( - hp.validFingerprint, wp.four_invalid_params[i]); - - expect(key0).to.be.an.instanceof(Error); - expect(key0.code).to.equal('PARAM_WRONG'); - } - conn.disconnect(); }); it('malformed GPGME_Key cannot be used', function(){ - let conn = new Connection; for (let i=0; i < 4; i++){ - let key = new GPGME_Key(wp.four_invalid_params[i], conn); + let key = new GPGME_Key(wp.four_invalid_params[i]); expect(key.fingerprint).to.be.an.instanceof(Error); expect(key.fingerprint.code).to.equal('KEY_INVALID'); } - conn.disconnect(); }); // TODO: tests for subkeys // TODO: tests for userids // TODO: some invalid tests for key/keyring }); describe('GPGME_Keyring', function(){ - it('correct initialization', function(){ - let conn = new Connection; - let keyring = new GPGME_Keyring(conn); - + it('correct Keyring initialization', function(){ + let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); - expect(keyring.connection).to.be.an.instanceof(Connection); expect(keyring.getKeys).to.be.a('function'); expect(keyring.getSubset).to.be.a('function'); }); - it('Keyring should return errors if not connected', function(){ + it('correct initialization', 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'); - // not yet implemented: - // keyring.getKeys().then( - // function(result){}, - //function(reject){ - // expect(reject).to.be.an.instanceof(Error); - // done(); + expect(keyring.getKeys).to.be.a('function'); + expect(keyring.getSubset).to.be.a('function'); }); //TODO not yet implemented: // getKeys(pattern, include_secret) //note: pattern can be null // getSubset(flags, pattern) // available Boolean flags: secret revoked expired }); describe('GPGME_Message', function(){ it('creating encrypt Message', function(){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); expect(test0.isComplete).to.be.false; }); it('Message is complete after setting mandatory data', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete).to.be.true; }); it('Message is not complete after mandatory data is empty', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', ''); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete).to.be.false; }); 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); }); it ('Not accepting non-allowed operation', function(){ let test0 = createMessage(mp.invalid_op_action); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('MSG_WRONG_OP'); }); it('Not accepting wrong parameter type', function(){ let test0 = createMessage(mp.invalid_op_type); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('PARAM_WRONG'); }); it('Not accepting wrong parameter name', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let i=0; i < mp.invalid_param_test.invalid_param_names.length; i++){ 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('Not accepting wrong parameter value', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let j=0; j < mp.invalid_param_test.invalid_values_0.length; j++){ 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 {unittests}; \ No newline at end of file