From ecad77263585cd5954758f797327d98232d880dc Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 22 May 2018 14:24:16 +0200 Subject: [PATCH] js: transfer encoding changes -- * Uint8Arrays are not supported for now there are unsolved issues in conversion, and they are lower priority * encrypt gains a new option to indicate that input values are base64 encoded * as decrypted values are always base64 encoded, the option base64 will not try to decode the result into utf, but leave it as it is --- .../tests/encryptDecryptTest.js | 133 ++++++++++++++++-- .../tests/longRunningTests.js | 25 ---- .../tests/openpgpModeTest.js | 27 ---- lang/js/src/Connection.js | 34 +++-- lang/js/src/Message.js | 14 ++ lang/js/src/gpgmejs.js | 42 +++--- lang/js/src/permittedOperations.js | 4 +- 7 files changed, 173 insertions(+), 106 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 5c534039..2fe955e6 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -1,3 +1,4 @@ + /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -39,6 +40,22 @@ describe('Encryption and Decryption', function () { }); }); }); + + 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(); @@ -64,7 +81,7 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(5000); + }).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 + ')', @@ -96,21 +113,113 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(5000); + }).timeout(3000); }; - it('Decrypt simple non-ascii', function (done) { + it('Random data, as string', function (done) { + let data = bigString(1000); let prm = Gpgmejs.init(); prm.then(function (context) { - 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(); - }); + 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/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index c95bebda..4e55fd26 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -37,29 +37,4 @@ describe('Long running Encryption/Decryption', function () { }).timeout(8000); }; - it('Successful encrypt 1 MB Uint8Array', function (done) { - //TODO: this succeeds, but result may be bogus (String with byte values as numbers) - let prm = Gpgmejs.init(); - let data = bigUint8(1); - 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); - done(); - }); - }); - }); - }).timeout(5000); - }); diff --git a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js index 98b6e1d8..cccaf604 100644 --- a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js +++ b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js @@ -41,33 +41,6 @@ describe('Encrypting-Decrypting in openpgp mode, using a Message object', functi }); }); }); - it('Encrypt-Decrypt, sending Uint8Array as data', function (done) { - //TODO! fails. Reason is that atob<->btoa destroys the uint8Array, - // resulting in a string of constituyent numbers - // (error already occurs in encryption) - - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - let input = bigUint8(0.3); - expect(input).to.be.an.instanceof(Uint8Array); - context.encrypt({ - data: input, - publicKeys: 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({message:answer.data}).then(function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.an.instanceof(Uint8Array); - expect(result.data).to.equal(input); - context._GpgME.connection.disconnect(); - done(); - }); - }); - }); - }); it('Keys as Fingerprints', function(done){ let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); let input = inputvalues.encrypt.good.data_nonascii; diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 1931a55b..9c2a6428 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -93,7 +93,7 @@ export class Connection{ } let me = this; return new Promise(function(resolve, reject){ - let answer = new Answer(message.operation); + let answer = new Answer(message); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) @@ -147,8 +147,9 @@ export class Connection{ */ class Answer{ - constructor(operation){ - this.operation = operation; + constructor(message){ + this.operation = message.operation; + this.expected = message.expected; } /** @@ -210,26 +211,31 @@ class Answer{ } /** - * @returns {Object} the assembled message. - * TODO: does not care yet if completed. + * @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){ - if (this._response.base64 == true){ - let respatob = atob(this._response[keys[i]]); - - let result = decodeURIComponent( - respatob.split('').map(function(c) { + 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); + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); - this._response[keys[i]] = result; } + } else { + msg[keys[i]] = this._response[keys[i]]; } } - return this._response; + return msg; } } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 6a59c3e0..932212a6 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -41,6 +41,7 @@ export class GPGME_Message { constructor(operation){ this.operation = operation; + this._expected = 'string'; } set operation (op){ @@ -58,6 +59,19 @@ export class GPGME_Message { 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 diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index d106f4f7..01cb92c3 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -64,11 +64,11 @@ export class GpgME { } /** - * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array + * @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, wildcard=false){ + encrypt(data, publicKeys, base64=false, wildcard=false){ let msg = createMessage('encrypt'); if (msg instanceof Error){ @@ -77,11 +77,14 @@ export class GpgME { // 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 (wildcard === true){ + msg.setParameter('throw-keyids', true); }; if (msg.isComplete === true){ return this.connection.post(msg); @@ -91,7 +94,8 @@ export class GpgME { } /** - * @param {String} data TODO Format: base64? String? Message with the encrypted data + * @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. @@ -100,11 +104,14 @@ export class GpgME { * @async */ - decrypt(data){ + 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); } @@ -156,11 +163,9 @@ export class GpgME { } /** - * Sets the data of the message, converting Uint8Array to base64 and setting - * the base64 flag + * Sets the data of the message * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter - * @param {String} propertyname // TODO unchecked still */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { @@ -168,30 +173,15 @@ function putData(message, data){ } if (!data){ return gpgme_error('PARAM_WRONG'); - } else if (data instanceof Uint8Array){ - message.setParameter('base64', true); - // TODO: btoa turns the array into a string - // of comma separated of numbers - // atob(data).split(',') would result in a "normal" array of numbers - // atob(btoa(data)).split(',') would result in a "normal" array of numbers - // would result in a "normal" array of numbers - message.setParameter ('data', btoa(data)); - } else if (typeof(data) === 'string') { - message.setParameter('base64', false); message.setParameter('data', data); } else if ( typeof(data) === 'object' && typeof(data.getText) === 'function' ){ let txt = data.getText(); - if (txt instanceof Uint8Array){ - message.setParameter('base64', true); - message.setParameter ('data', btoa(txt)); - } - else if (typeof(txt) === 'string'){ - message.setParameter('base64', false); - message.setParameter ('data', txt); + if (typeof(txt) === 'string'){ + message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 59597aaf..da46a1fd 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -51,7 +51,7 @@ export const permittedOperations = { array_allowed: true }, 'data': { - allowed: ['string', 'Uint8Array'] + allowed: ['string'] } }, optional: { @@ -103,7 +103,7 @@ export const permittedOperations = { pinentry: true, required: { 'data': { - allowed: ['string', 'Uint8Array'] + allowed: ['string'] } }, optional: {