From 94ee0988d4eaac27785de6efb7c19ca9976e1e9c Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Jul 2018 20:36:21 +0200 Subject: [PATCH] js: change the write access for js class methods -- * src/ [Connection, Error, Key, Keyring, MEssage, Signature, gpgmejs]: Functions and values that are not meant to be overwritten are now moved into their constructors, thus eliminating the possibility of overwrites after initialization. * Key: The mode of use (synchronous cached, or async promises) ivs now determined at initialization of that Key. The property Key.isAsync reflects this state. * unittests: fixed old Key syntax for testing. * Message.js isComplete is now a method and not a getter anymore. * Added some startup tests. --- .../BrowserTestExtension/tests/inputvalues.js | 25 +- lang/js/BrowserTestExtension/tests/startup.js | 15 +- lang/js/src/Connection.js | 123 ++++---- lang/js/src/Errors.js | 12 +- lang/js/src/Key.js | 297 +++++++++--------- lang/js/src/Keyring.js | 26 +- lang/js/src/Message.js | 127 ++++---- lang/js/src/Signature.js | 74 +++-- lang/js/src/gpgmejs.js | 77 +++-- lang/js/unittest_inputvalues.js | 11 +- lang/js/unittests.js | 22 +- 11 files changed, 409 insertions(+), 400 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 5289eab7..9d956b69 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -86,7 +86,9 @@ const inputvalues = {// eslint-disable-line no-unused-vars 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + '=ioB6\n' + '-----END PGP SIGNATURE-----\n', - } + }, + + someInputParameter: 'bad string' }; // (Pseudo-)Random String covering all of utf8. @@ -158,27 +160,6 @@ function slightlyLessBoringString(megabytes, set){ return string.join(''); } -// Take a gpg looking string and destroy it a bit by changing random values -// eslint-disable-next-line no-unused-vars -function destroylegitimateGpg(string, mutations=5){ - const allowed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/\n'; - for (let i=0; i < mutations.length; i++){ - // leave the first and last 35 chars (header/footer) intact - let position = Math.floor(Math.random() *(string.length - 70)) + 35; - let str0 = string.substring(0,position - 1); - let str1 = string.substring(position, position + 1); - let str2 = string.substring(position +1); - let success = false; - while (!success){ - let newchar = Math.floor(Math.random() * allowed.length); - if (newchar !== str1){ - string = str0 + newchar + str2; - success = true; - } - } - } -} - // Data encrypted with testKey const encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index dae94025..1e2784d9 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -21,17 +21,28 @@ * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, expect, Gpgmejs, inputvalues */ describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ let prm = Gpgmejs.init(); + const input = inputvalues.someInputParameter; prm.then( function(context){ expect(context).to.be.an('object'); expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); + expect(context.sign).to.be.a('function'); + expect(context.verify).to.be.a('function'); + context.Keyring = input; + expect(context.Keyring).to.be.an('object'); + expect(context.Keyring).to.not.equal(input); + expect(context._Keyring).to.equal(context.Keyring); + expect(context.Keyring.getKeys).to.be.a('function'); + expect(context.Keyring.getDefaultKey).to.be.a('function'); + expect(context.Keyring.importKey).to.be.a('function'); + expect(context.Keyring.generateKey).to.be.a('function'); done(); }); }); -}); +}); \ No newline at end of file diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index d89fa724..d482667e 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -38,8 +38,19 @@ import { GPGME_Message, createMessage } from './Message'; export class Connection{ constructor(){ - this.connect(); - } + let _connection = chrome.runtime.connectNative('gpgmejson'); + + + /** + * Immediately closes an open port. + */ + this.disconnect = function () { + if (_connection){ + _connection.disconnect(); + _connection = null; + } + }; + /** * @typedef {Object} backEndDetails @@ -63,14 +74,15 @@ export class Connection{ * backend * @async */ - checkConnection(details = true){ + this.checkConnection = function(details = true){ + const msg = createMessage('version'); if (details === true) { - return this.post(createMessage('version')); + return this.post(msg); } else { let me = this; return new Promise(function(resolve) { Promise.race([ - me.post(createMessage('version')), + me.post(msg), new Promise(function(resolve, reject){ setTimeout(function(){ reject(gpgme_error('CONN_TIMEOUT')); @@ -83,26 +95,7 @@ export class Connection{ }); }); } - } - - /** - * Immediately closes an 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 {@link GPGME_Message} via tghe nativeMessaging port. It resolves @@ -113,65 +106,65 @@ export class Connection{ * @returns {Promise} The collected answer * @async */ - post(message){ + this.post = function (message){ if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); return Promise.reject(gpgme_error( 'PARAM_WRONG', 'Connection.post')); } - if (message.isComplete !== true){ + if (message.isComplete() !== true){ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } - let me = this; let chunksize = message.chunksize; 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(); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else { let answer_result = answer.collect(msg); if (answer_result !== true){ - me._connection.onMessage.removeListener(listener); - me._connection.disconnect(); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); reject(answer_result); } else { if (msg.more === true){ - me._connection.postMessage({ + _connection.postMessage({ 'op': 'getmore', 'chunksize': chunksize }); } else { - me._connection.onMessage.removeListener(listener); - me._connection.disconnect(); - if (answer.message instanceof Error){ - reject(answer.message); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); + const message = answer.getMessage(); + if (message instanceof Error){ + reject(message); } else { - resolve(answer.message); + resolve(message); } } } } }; - me._connection.onMessage.addListener(listener); + _connection.onMessage.addListener(listener); if (permittedOperations[message.operation].pinentry){ - return me._connection.postMessage(message.message); + return _connection.postMessage(message.message); } else { return Promise.race([ - me._connection.postMessage(message.message), + _connection.postMessage(message.message), function(resolve, reject){ setTimeout(function(){ - me._connection.disconnect(); + _connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ return result; }, function(reject){ if(!(reject instanceof Error)) { - me._connection.disconnect(); + _connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; @@ -179,7 +172,8 @@ export class Connection{ }); } }); - } + }; +} } /** @@ -193,10 +187,16 @@ class Answer{ * @param {GPGME_Message} message */ constructor(message){ - this.operation = message.operation; - this.expect = message.expect; - } + const operation = message.operation; + const expect = message.expect; + let response_b64 = null; + this.getOperation = function(){ + return operation; + }; + this.getExpect = function(){ + return expect; + }; /** * Adds incoming base64 encoded data to the existing response * @param {*} msg base64 encoded data. @@ -204,34 +204,30 @@ class Answer{ * * @private */ - collect(msg){ + this.collect = function (msg){ if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - if (this._responseb64 === undefined){ - //this._responseb64 = [msg.response]; - this._responseb64 = msg.response; + if (response_b64 === null){ + response_b64 = msg.response; return true; } else { - //this._responseb64.push(msg.response); - this._responseb64 += msg.response; + response_b64 += msg.response; return true; } - } - - /** + }; + /** * Returns the base64 encoded answer data with the content verified against * {@link permittedOperations}. */ - get message(){ - if (this._responseb64 === undefined){ + this.getMessage = function (){ + if (response_b64 === undefined){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - // let _decodedResponse = JSON.parse(atob(this._responseb64.join(''))); - let _decodedResponse = JSON.parse(atob(this._responseb64)); + let _decodedResponse = JSON.parse(atob(response_b64)); let _response = {}; let messageKeys = Object.keys(_decodedResponse); - let poa = permittedOperations[this.operation].answer; + let poa = permittedOperations[this.getOperation()].answer; if (messageKeys.length === 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } @@ -262,7 +258,7 @@ class Answer{ } if (_decodedResponse.base64 === true && poa.data[key] === 'string' - && this.expect === undefined + && this.getExpect() === undefined ){ _response[key] = decodeURIComponent( atob(_decodedResponse[key]).split('').map( @@ -277,5 +273,6 @@ class Answer{ } } return _response; - } + }; +} } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index cb5c94c2..0cf1af19 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -143,7 +143,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ * @extends Error */ class GPGME_Error extends Error{ - constructor(code, msg=''){ + constructor(code = 'GENERIC_ERROR', msg=''){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ @@ -155,12 +155,12 @@ class GPGME_Error extends Error{ } else { super(err_list['GENERIC_ERROR'].msg); } - this.code = code || 'GENERIC_ERROR'; - } - set code(value){ - this._code = value; + this.getCode = function(){ + return code; + }; } + get code(){ - return this._code; + return this.getCode(); } } \ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index b024a77b..a7f7dd26 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -28,13 +28,16 @@ import { createMessage } from './Message'; /** * Validates the given fingerprint and creates a new {@link GPGME_Key} * @param {String} fingerprint + * @param {Boolean} async If True, Key properties (except fingerprint) will be + * queried from gnupg on each call, making the operation up-to-date, the + * answers will be Promises, and the performance will likely suffer * @returns {GPGME_Key|GPGME_Error} */ -export function createKey(fingerprint){ - if (!isFingerprint(fingerprint)){ +export function createKey(fingerprint, async = false){ + if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } - else return new GPGME_Key(fingerprint); + else return new GPGME_Key(fingerprint, async); } /** @@ -48,31 +51,30 @@ export function createKey(fingerprint){ */ export class GPGME_Key { - constructor(fingerprint){ - this.fingerprint = fingerprint; - } + constructor(fingerprint, async){ - 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; - } + /** + * @property {Boolean} If true, most answers will be asynchronous + */ + this.isAsync = async; + + let _data = {fingerprint: fingerprint}; + this.getFingerprint = function(){ + if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ + return gpgme_error('KEY_INVALID'); } - } - } + return _data.fingerprint; + }; /** - * @returns {String} The fingerprint defining this Key + * Property indicating if the Key possesses a private/secret part. If this + * information is not yet cached, it returns an {@link GPGME_Error} with + * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. + * @returns {Boolean} If the Key has a secret subkey. */ - get fingerprint(){ - if (!this._data || !this._data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - return this._data.fingerprint; - } + this.hasSecret= function (){ + return this.get('hasSecret', true); + }; /** * @param {Object} data Bulk set the data for this key, with an Object sent @@ -81,97 +83,89 @@ export class GPGME_Key { * error if something went wrong * @private */ - setKeyData(data){ - if (this._data === undefined) { - this._data = {}; - } - if ( - typeof(data) !== 'object') { + this.setKeyData = function (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){ + if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ return gpgme_error('KEY_INVALID'); } - let dataKeys = Object.keys(data); - for (let i=0; i< dataKeys.length; i++){ - if (!validKeyProperties.hasOwnProperty(dataKeys[i])){ - return gpgme_error('KEY_INVALID'); - } - if (validKeyProperties[dataKeys[i]](data[dataKeys[i]]) !== true ){ - return gpgme_error('KEY_INVALID'); - } - switch (dataKeys[i]){ + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++){ + if (!validKeyProperties.hasOwnProperty(keys[i])){ + return gpgme_error('KEY_INVALID'); + } + //running the defined validation function + if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ + return gpgme_error('KEY_INVALID'); + } + switch (keys[i]){ case 'subkeys': - this._data.subkeys = []; + _data.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { - this._data.subkeys.push( + _data.subkeys.push( new GPGME_Subkey(data.subkeys[i])); } break; case 'userids': - this._data.userids = []; + _data.userids = []; for (let i=0; i< data.userids.length; i++) { - this._data.userids.push( + _data.userids.push( new GPGME_UserId(data.userids[i])); } break; case 'last_update': - this._data[dataKeys[i]] = new Date( data[dataKeys[i]] * 1000 ); + _data[keys[i]] = new Date( data[keys[i]] * 1000 ); break; default: - this._data[dataKeys[i]] = data[dataKeys[i]]; + _data[keys[i]] = data[keys[i]]; } } return this; - } + }; /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @param {Boolean} cached (optional) if false, the data will be directly - * queried from gnupg, and the operation will be asynchronous. Else, the - * data will be fetched from the state of the initialization of the Key. - * The cached mode may contain outdated information, but can be used as - * synchronous operation, where the backend is not expected to change Keys - * during a session. The key still can be reloaded by invoking - * {@link refreshKey}. * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). * If 'cached' is false, the value will be resolved as a Promise. */ - get(property, cached=true) { - if (cached === false) { + this.get = function(property) { + if (this.isAsync === true) { let me = this; return new Promise(function(resolve, reject) { - if (!validKeyProperties.hasOwnProperty(property)){ - reject('PARAM_WRONG'); - } else if (property === 'armored'){ + if (property === 'armored'){ resolve(me.getArmor()); } else if (property === 'hasSecret'){ resolve(me.getHasSecret()); - } else { - me.refreshKey().then(function(key){ - resolve(key.get(property, true)); + } else if (validKeyProperties.hasOwnProperty(property)){ + let msg = createMessage('keylist'); + msg.setParameter('keys', _data.fingerprint); + msg.post().then(function(result){ + if (result.keys && result.keys.length === 1 && + result.keys[0].hasOwnProperty(property)){ + resolve(result.keys[0][property]); + } else { + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); + } }, function(error){ - reject(error); + reject(gpgme_error(error)); }); + } else { + reject(gpgme_error('PARAM_WRONG')); } }); } else { if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); } - if (!this._data.hasOwnProperty(property)){ + if (!_data.hasOwnProperty(property)){ return gpgme_error('KEY_NO_INIT'); } else { - return (this._data[property]); + return (_data[property]); } } - } + }; /** * Reloads the Key information from gnupg. This is only useful if you use @@ -181,15 +175,15 @@ export class GPGME_Key { * @returns {Promise} * @async */ - refreshKey() { + this.refreshKey = function() { let me = this; return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeyData(result.keys[0]); @@ -209,7 +203,7 @@ export class GPGME_Key { reject(gpgme_error('GNUPG_ERROR'), error); }); }); - } + }; /** * Query the armored block of the Key directly from gnupg. Please note that @@ -217,23 +211,22 @@ export class GPGME_Key { * @returns {Promise} * @async */ - getArmor(){ - let me = this; + this.getArmor = function(){ return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('export'); msg.setParameter('armor', true); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ - me._data.armored = result.data; + _data.armored = result.data; resolve(result.data); }, function(error){ reject(error); }); }); - } + }; /** * Find out if the Key includes a secret part. Note that this is a rather @@ -244,39 +237,61 @@ export class GPGME_Key { * available in the gnupg Keyring * @async */ - getHasSecret(){ - let me = this; + this.getHasSecret = function (){ return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.setParameter('secret', true); msg.post().then(function(result){ - me._data.hasSecret = null; + _data.hasSecret = null; if ( result.keys && result.keys.length === 1 && result.keys[0].secret === true ) { - me._data.hasSecret = true; + _data.hasSecret = true; resolve(true); } else { - me._data.hasSecret = false; + _data.hasSecret = false; resolve(false); } }, function(error){ reject(error); }); }); + }; + + /** + * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a + * secret key is not supported by the native backend. + * @returns {Promise} Success if key was deleted, + * rejects with a GPG error otherwise. + */ + this.delete= function (){ + return new Promise(function(resolve, reject){ + if (!_data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('delete'); + msg.setParameter('key', _data.fingerprint); + msg.post().then(function(result){ + resolve(result.success); + }, function(error){ + reject(error); + }); + }); + }; } /** - * Convenience functions to be directly used as properties of the Key - * Notice that these rely on cached info and may be outdated. Use the async - * get(property, false) if you need the most current info + * @returns {String} The fingerprint defining this Key */ + get fingerprint(){ + return this.getFingerprint(); + } /** * Property for the export of armored Key. If the armored Key is not @@ -287,38 +302,6 @@ export class GPGME_Key { get armored(){ return this.get('armored', true); } - - /** - * Property indicating if the Key possesses a private/secret part. If this - * information is not yet cached, it returns an {@link GPGME_Error} with - * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. - * @returns {Boolean} If the Key has a secret subkey. - */ - get hasSecret(){ - return this.get('hasSecret', true); - } - - /** - * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a - * secret key is not supported by the native backend. - * @returns {Promise} Success if key was deleted, - * rejects with a GPG error otherwise. - */ - delete(){ - let me = this; - return new Promise(function(resolve, reject){ - if (!me._data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('delete'); - msg.setParameter('key', me._data.fingerprint); - msg.post().then(function(result){ - resolve(result.success); - }, function(error){ - reject(error); - }); - }); - } } /** @@ -334,44 +317,45 @@ class GPGME_Subkey { * @private */ constructor(data){ + let _data = {}; let keys = Object.keys(data); - for (let i=0; i< keys.length; i++) { - this.setProperty(keys[i], data[keys[i]]); - } - } - /** + /** * Validates a subkey property against {@link validSubKeyProperties} and * sets it if validation is successful * @param {String} property * @param {*} value * @param private */ - setProperty(property, value){ - if (!this._data){ - this._data = {}; - } + const setProperty = function (property, value){ if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ - this._data[property] = new Date(value * 1000); + _data[property] = new Date(value * 1000); } else { - this._data[property] = value; + _data[property] = value; } } } + }; + for (let i=0; i< keys.length; i++) { + setProperty(keys[i], data[keys[i]]); } + + /** * Fetches any information about this subkey * @param {String} property Information to request * @returns {String | Number | Date} */ - get(property) { - if (this._data.hasOwnProperty(property)){ - return (this._data[property]); + this.get = function(property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); } - } + }; +} + } /** @@ -387,11 +371,23 @@ class GPGME_UserId { * @private */ constructor(data){ + let _data = {}; let keys = Object.keys(data); + const setProperty = function(property, value){ + if (validUserIdProperties.hasOwnProperty(property)){ + if (validUserIdProperties[property](value) === true) { + if (property === 'last_update'){ + _data[property] = new Date(value*1000); + } else { + _data[property] = value; + } + } + } + }; for (let i=0; i< keys.length; i++) { - this.setProperty(keys[i], data[keys[i]]); + setProperty(keys[i], data[keys[i]]); } - } + /** * Validates a subkey property against {@link validUserIdProperties} and * sets it if validation is successful @@ -399,34 +395,21 @@ class GPGME_UserId { * @param {*} value * @param private */ - setProperty(property, value){ - if (!this._data){ - this._data = {}; - } - if (validUserIdProperties.hasOwnProperty(property)){ - if (validUserIdProperties[property](value) === true) { - if (property === 'last_update'){ - this._data[property] = new Date(value*1000); - } else { - this._data[property] = value; - } - } - } - } /** * Fetches information about the user * @param {String} property Information to request * @returns {String | Number} */ - get(property) { - if (this._data.hasOwnProperty(property)){ - return (this._data[property]); + this.get = function (property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); } - } + }; } +} /** * Validation definition for userIds. Each valid userId property is represented * as a key- Value pair, with their value being a validation function to check diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index f2a71389..d863fe73 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -32,7 +32,6 @@ import { gpgme_error } from './Errors'; */ export class GPGME_Keyring { constructor(){ - } /** * Queries Keys (all Keys or a subset) from gnupg. @@ -50,7 +49,7 @@ export class GPGME_Keyring { * @static * @async */ - getKeys(pattern, prepare_sync=false, search=false){ + this.getKeys = function(pattern, prepare_sync=false, search=false){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined && pattern !== null){ @@ -107,7 +106,7 @@ export class GPGME_Keyring { } }); }); - } + }; /** * @typedef {Object} exportResult The result of a getKeysArmored operation. @@ -131,7 +130,7 @@ export class GPGME_Keyring { * @static * @async */ - getKeysArmored(pattern, with_secret_fpr) { + this.getKeysArmored = function(pattern, with_secret_fpr) { return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); @@ -153,7 +152,7 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; /** * Returns the Key used by default in gnupg. @@ -165,7 +164,7 @@ export class GPGME_Keyring { * @async * @static */ - getDefaultKey() { + this.getDefaultKey = function() { let me = this; return new Promise(function(resolve, reject){ let msg = createMessage('config_opt'); @@ -206,7 +205,7 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; /** * @typedef {Object} importResult The result of a Key update @@ -244,7 +243,7 @@ export class GPGME_Keyring { * @async * @static */ - importKey(armored, prepare_sync) { + this.importKey = function (armored, prepare_sync) { let feedbackValues = ['considered', 'no_user_id', 'imported', 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', 'new_signatures', 'new_revocations', 'secret_read', @@ -323,7 +322,7 @@ export class GPGME_Keyring { }); - } + }; /** * Convenience function for deleting a Key. See {@link Key.delete} for @@ -333,14 +332,14 @@ export class GPGME_Keyring { * @async * @static */ - deleteKey(fingerprint){ + this.deleteKey = function(fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); return key.delete(); } else { return Promise.reject(gpgme_error('KEY_INVALID')); } - } + }; /** * Generates a new Key pair directly in gpg, and returns a GPGME_Key @@ -356,7 +355,7 @@ export class GPGME_Keyring { * @return {Promise} * @async */ - generateKey(userId, algo = 'default', expires){ + this.generateKey = function (userId, algo = 'default', expires){ if ( typeof(userId) !== 'string' || supportedKeyAlgos.indexOf(algo) < 0 || @@ -385,7 +384,8 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; +} } /** diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 2e38142c..cf9f84ef 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -52,22 +52,14 @@ export function createMessage(operation){ export class GPGME_Message { constructor(operation){ - this.operation = operation; - } + let _msg = { + op: operation, + chunksize: 1023* 1024 + }; - 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; - } + this.getOperation = function(){ + return _msg.op; + }; /** * The maximum size of responses from gpgme in bytes. As of July 2018, @@ -78,41 +70,23 @@ export class GPGME_Message { * messages will be received in chunks. * If the value is not explicitly specified, 1023 KB is used. */ - set chunksize(value){ + this.setChunksize = function (value){ if ( Number.isInteger(value) && value > 10 * 1024 && value <= 1024 * 1024 ){ - this._chunksize = value; + _msg.chunksize = value; } - } - get chunksize(){ - if (this._chunksize === undefined){ - return 1024 * 1023; - } else { - return this._chunksize; - } - } + }; - /** - * Expect indicates which format data is expected to be in. It currently - * only supports 'base64', indicating that the response data from gnupg are - * expected to be base64 encoded binary - */ - set expect(value){ - if (value ==='base64'){ - this._expect = value; - } - } - - get expect(){ - if ( this._expect === 'base64'){ - return this._expect; - } - return undefined; - } + this.getMsg = function(){ + return _msg; + }; + this.getChunksize= function() { + return _msg.chunksize; + }; /** * Sets a parameter for the message. It validates with @@ -121,11 +95,11 @@ export class GPGME_Message { * @param {any} value Value to set * @returns {Boolean} If the parameter was set successfully */ - setParameter( param,value ){ + this.setParameter = function ( param,value ){ if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } - let po = permittedOperations[this._msg.op]; + let po = permittedOperations[_msg.op]; if (!po){ return gpgme_error('MSG_WRONG_OP'); } @@ -195,59 +169,42 @@ export class GPGME_Message { return gpgme_error('PARAM_WRONG'); } } - this._msg[param] = value; + _msg[param] = value; return true; - } + }; + + /** * Check if the message has the minimum requirements to be sent, that is * all 'required' parameters according to {@link permittedOperations}. * @returns {Boolean} true if message is complete. */ - get isComplete(){ - if (!this._msg.op){ + this.isComplete = function(){ + if (!_msg.op){ return false; } let reqParams = Object.keys( - permittedOperations[this._msg.op].required); - let msg_params = Object.keys(this._msg); + permittedOperations[_msg.op].required); + let msg_params = Object.keys(_msg); for (let i=0; i < reqParams.length; i++){ if (msg_params.indexOf(reqParams[i]) < 0){ 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){ - this._msg.chunksize = this.chunksize; - return this._msg; - } - else { - return null; - } - - } - + }; /** * Sends the Message via nativeMessaging and resolves with the answer. * @returns {Promise} * @async */ - post(){ + this.post = function(){ let me = this; return new Promise(function(resolve, reject) { - if (me.isComplete === true) { + if (me.isComplete() === true) { + let conn = new Connection; - if (me._msg.chunksize === undefined){ - me._msg.chunksize = me.chunksize; - } conn.post(me).then(function(response) { resolve(response); }, function(reason) { @@ -258,5 +215,27 @@ export class GPGME_Message { reject(gpgme_error('MSG_INCOMPLETE')); } }); - } + }; +} + + /** + * 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.getMsg(); + } + else { + return null; + } + } + +get operation(){ + return this.getOperation(); +} +get chunksize(){ + return this.getChunksize(); +} } diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index 191e00ab..ff4278ad 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -82,44 +82,47 @@ export function createSignature(sigObject){ class GPGME_Signature { constructor(sigObject){ - this._rawSigObject = sigObject; - } + let _rawSigObject = sigObject; - get fingerprint(){ - return this._rawSigObject.fingerprint; - } + this.getFingerprint = function(){ + if (!_rawSigObject.fingerprint){ + return gpgme_error('SIG_WRONG'); + } else { + return _rawSigObject.fingerprint; + } + }; /** * The expiration of this Signature as Javascript date, or null if * signature does not expire * @returns {Date | null} */ - get expiration(){ - if (!this._rawSigObject.exp_timestamp){ + this.getExpiration = function(){ + if (!_rawSigObject.exp_timestamp){ return null; } - return new Date(this._rawSigObject.exp_timestamp* 1000); - } + return new Date(_rawSigObject.exp_timestamp* 1000); + }; /** * The creation date of this Signature in Javascript Date * @returns {Date} */ - get timestamp(){ - return new Date(this._rawSigObject.timestamp * 1000); - } + this.getTimestamp= function (){ + return new Date(_rawSigObject.timestamp * 1000); + }; /** * The overall validity of the key. If false, errorDetails may contain * additional information */ - get valid() { - if (this._rawSigObject.summary.valid === true){ + this.getValid= function() { + if (_rawSigObject.summary.valid === true){ return true; } else { return false; } - } + }; /** * gives more information on non-valid signatures. Refer to the gpgme docs @@ -127,19 +130,54 @@ class GPGME_Signature { * details on the values * @returns {Object} Object with boolean properties */ - get errorDetails(){ + this.getErrorDetails = function (){ let properties = ['revoked', 'key-expired', 'sig-expired', 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', 'sys-error']; let result = {}; for (let i=0; i< properties.length; i++){ - if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ - result[properties[i]] = this._rawSigObject[properties[i]]; + if ( _rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = _rawSigObject[properties[i]]; } } return result; + }; +} + + /** + * Convenience getter for {@link getFingerprint} + */ + get fingerprint(){ + return this.getFingerprint(); } + /** + * Convenience getter for {@link getExpiration} + */ + get expiration(){ + return this.getExpiration(); + } + + /** + * Convenience getter for {@link getTimeStamp} + */ + get timestamp(){ + return this.getTimestamp(); + } + + /** + * Convenience getter for {@link getValid} + */ + get valid(){ + return this.getValid(); + } + + /** + * Convenience getter for {@link getErrorDetails} + */ + get errorDetails(){ + return this.getErrorDetails(); + } } /** diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 3f6dc947..f587e854 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -22,8 +22,8 @@ */ -import {GPGME_Message, createMessage} from './Message'; -import {toKeyIdArray} from './Helpers'; +import { GPGME_Message, createMessage } from './Message'; +import { toKeyIdArray } from './Helpers'; import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; import { createSignature } from './Signature'; @@ -85,23 +85,27 @@ import { createSignature } from './Signature'; export class GpgME { constructor(){ - } + let _Keyring = null; - set Keyring(keyring){ - if (keyring && keyring instanceof GPGME_Keyring){ - this._Keyring = keyring; - } - } + /** + * Sets a new Keyring to be used + * @param {GPGME_Keyring} keyring + */ + this.setKeyring = function(keyring){ + if (keyring && keyring instanceof GPGME_Keyring){ + _Keyring = keyring; + } + }; - /** - * Accesses the {@link GPGME_Keyring}. - */ - get Keyring(){ - if (!this._Keyring){ - this._Keyring = new GPGME_Keyring; - } - return this._Keyring; - } + /** + * Accesses the {@link GPGME_Keyring}. + */ + this.getKeyring = function(){ + if (!_Keyring){ + _Keyring = new GPGME_Keyring; + } + return _Keyring; + }; /** * Encrypt (and optionally sign) data @@ -123,8 +127,8 @@ export class GpgME { * message and additional info. * @async */ - encrypt(data, publicKeys, secretKeys, base64=false, armor=true, - wildcard=false, additional = {} + this.encrypt = function (data, publicKeys, secretKeys, base64=false, + armor=true, wildcard=false, additional = {} ){ let msg = createMessage('encrypt'); if (msg instanceof Error){ @@ -152,12 +156,12 @@ export class GpgME { additional[additional_Keys[k]]); } } - if (msg.isComplete === true){ + if (msg.isComplete() === true){ return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } - } + }; /** * Decrypts a Message @@ -168,7 +172,7 @@ export class GpgME { * @returns {Promise} Decrypted Message and information * @async */ - decrypt(data, base64=false){ + this.decrypt = function (data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -203,7 +207,7 @@ export class GpgME { reject(error); }); }); - } + }; /** * Sign a Message @@ -217,7 +221,7 @@ export class GpgME { * @returns {Promise} * @async */ - sign(data, keys, mode='clearsign', base64=false) { + this.sign = function (data, keys, mode='clearsign', base64=false) { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -252,7 +256,7 @@ export class GpgME { reject(error); }); }); - } + }; /** * Verifies data. @@ -264,7 +268,7 @@ export class GpgME { * @returns {Promise} *@async */ - verify(data, signature, base64 = false){ + this.verify= function (data, signature, base64 = false){ let msg = createMessage('verify'); let dt = putData(msg, data); if (dt instanceof Error){ @@ -297,6 +301,25 @@ export class GpgME { reject(error); }); }); + }; +} + + /** + * setter for {@link setKeyring}. + * @param {GPGME_Keyring} keyring A Keyring to use + */ + set Keyring(keyring){ + this.setKeyring(keyring); + } + + /** + * Accesses the {@link GPGME_Keyring}. + */ + get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } + return this._Keyring; } } @@ -309,7 +332,7 @@ export class GpgME { * @private */ function putData(message, data){ - if (!message || !(message instanceof GPGME_Message) ) { + if (!message || !message instanceof GPGME_Message) { return gpgme_error('PARAM_WRONG'); } if (!data){ diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index 07147bac..02bb5329 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -1,12 +1,9 @@ -import {Connection} from './src/Connection'; import {createKey} from './src/Key'; -let conn = new Connection; - export const helper_params = { validLongId: '0A0A0A0A0A0A0A0A', validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', - createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), + createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', @@ -15,11 +12,11 @@ export const helper_params = { invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], invalidKeyArray: {curiosity:'uncat'}, invalidKeyArray_OneBad: [ - createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), + createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], invalidErrorCode: 'Please type in all your passwords.', - validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), + validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', true), valid_openpgplike: { primaryKey: { getFingerprint: function(){ return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} @@ -36,7 +33,7 @@ export const message_params = { invalid_param_names: [22,'dance', {}], validparam_name_0: 'mime', invalid_values_0: [2134, 'All your passwords', - createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), null] + createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] } }; diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 04e15ef3..6228993b 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -194,16 +194,16 @@ function unittests (){ }); it('Non-cached key async data retrieval', function (done){ - let key = createKey(kp.validKeyFingerprint); - key.get('can_authenticate',false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('can_authenticate').then(function(result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async armored Key', function (done){ - let key = createKey(kp.validKeyFingerprint); - key.get('armored', false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('armored').then(function(result){ expect(result).to.be.a('string'); expect(result).to.include('KEY BLOCK-----'); done(); @@ -211,17 +211,17 @@ function unittests (){ }); it('Non-cached key async hasSecret', function (done){ - let key = createKey(kp.validKeyFingerprint); - key.get('hasSecret', false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async hasSecret (no secret in Key)', function (done){ - let key = createKey(kp.validFingerprintNoSecret); + let key = createKey(kp.validFingerprintNoSecret, true); expect(key).to.be.an.instanceof(GPGME_Key); - key.get('hasSecret', false).then(function(result){ + key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); expect(result).to.equal(false); done(); @@ -317,7 +317,7 @@ function unittests (){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); - expect(test0.isComplete).to.be.false; + expect(test0.isComplete()).to.be.false; }); it('Message is complete after setting mandatory data', function(){ @@ -325,14 +325,14 @@ function unittests (){ test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); - expect(test0.isComplete).to.be.true; + 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; + expect(test0.isComplete()).to.be.false; }); it('Complete Message contains the data that was set', function(){