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
This commit is contained in:
Maximilian Krambach 2018-05-22 14:24:16 +02:00
parent 6b4caee039
commit ecad772635
7 changed files with 173 additions and 106 deletions

View File

@ -1,3 +1,4 @@
/* gpgme.js - Javascript integration for gpgme /* gpgme.js - Javascript integration for gpgme
* Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * 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', it('Roundtrip does not destroy trailing whitespace',
function (done) { function (done) {
let prm = Gpgmejs.init(); 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++){ 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 + ')', 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(); let prm = Gpgmejs.init();
prm.then(function (context) { prm.then(function (context) {
data = encryptedData; context.encrypt(data,
context.decrypt(data).then( inputvalues.encrypt.good.fingerprint).then(
function (result) { function (answer) {
expect(result).to.not.be.empty; expect(answer).to.not.be.empty;
expect(result.data).to.be.a('string'); expect(answer.data).to.be.a("string");
expect(result.data).to.equal( expect(answer.data).to.include(
'¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); 'BEGIN PGP MESSAGE');
done(); 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); }).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);
}); });

View File

@ -37,29 +37,4 @@ describe('Long running Encryption/Decryption', function () {
}).timeout(8000); }).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);
}); });

View File

@ -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){ it('Keys as Fingerprints', function(done){
let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'});
let input = inputvalues.encrypt.good.data_nonascii; let input = inputvalues.encrypt.good.data_nonascii;

View File

@ -93,7 +93,7 @@ export class Connection{
} }
let me = this; let me = this;
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
let answer = new Answer(message.operation); let answer = new Answer(message);
let listener = function(msg) { let listener = function(msg) {
if (!msg){ if (!msg){
me._connection.onMessage.removeListener(listener) me._connection.onMessage.removeListener(listener)
@ -147,8 +147,9 @@ export class Connection{
*/ */
class Answer{ class Answer{
constructor(operation){ constructor(message){
this.operation = operation; this.operation = message.operation;
this.expected = message.expected;
} }
/** /**
@ -210,26 +211,31 @@ class Answer{
} }
/** /**
* @returns {Object} the assembled message. * @returns {Object} the assembled message, original data assumed to be
* TODO: does not care yet if completed. * (javascript-) strings
*/ */
get message(){ get message(){
let keys = Object.keys(this._response); let keys = Object.keys(this._response);
let msg = {};
let poa = permittedOperations[this.operation].answer; let poa = permittedOperations[this.operation].answer;
for (let i=0; i < keys.length; i++) { for (let i=0; i < keys.length; i++) {
if (poa.data.indexOf(keys[i]) >= 0){ if (poa.data.indexOf(keys[i]) >= 0
if (this._response.base64 == true){ && this._response.base64 === true
let respatob = atob(this._response[keys[i]]); ) {
msg[keys[i]] = atob(this._response[keys[i]]);
let result = decodeURIComponent( if (this.expected === 'base64'){
respatob.split('').map(function(c) { msg[keys[i]] = this._response[keys[i]];
} else {
msg[keys[i]] = decodeURIComponent(
atob(this._response[keys[i]]).split('').map(function(c) {
return '%' + return '%' +
('00' + c.charCodeAt(0).toString(16)).slice(-2); ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join('')); }).join(''));
this._response[keys[i]] = result;
} }
} else {
msg[keys[i]] = this._response[keys[i]];
} }
} }
return this._response; return msg;
} }
} }

View File

@ -41,6 +41,7 @@ export class GPGME_Message {
constructor(operation){ constructor(operation){
this.operation = operation; this.operation = operation;
this._expected = 'string';
} }
set operation (op){ set operation (op){
@ -58,6 +59,19 @@ export class GPGME_Message {
return this._msg.op; 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 * 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 * first, to be able to check if the parameter is permittted

View File

@ -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<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message * @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message
* @param {Boolean} wildcard (optional) If true, recipient information will not be added to 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'); let msg = createMessage('encrypt');
if (msg instanceof Error){ if (msg instanceof Error){
@ -77,11 +77,14 @@ export class GpgME {
// TODO temporary // TODO temporary
msg.setParameter('armor', true); msg.setParameter('armor', true);
msg.setParameter('always-trust', true); msg.setParameter('always-trust', true);
if (base64 === true) {
msg.setParameter('base64', true);
}
let pubkeys = toKeyIdArray(publicKeys); let pubkeys = toKeyIdArray(publicKeys);
msg.setParameter('keys', pubkeys); msg.setParameter('keys', pubkeys);
putData(msg, data); putData(msg, data);
if (wildcard === true){msg.setParameter('throw-keyids', true); if (wildcard === true){
msg.setParameter('throw-keyids', true);
}; };
if (msg.isComplete === true){ if (msg.isComplete === true){
return this.connection.post(msg); 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<Object>} decrypted message: * @returns {Promise<Object>} decrypted message:
data: The decrypted data. This may be base64 encoded. data: The decrypted data. This may be base64 encoded.
base64: Boolean indicating whether data is base64 encoded. base64: Boolean indicating whether data is base64 encoded.
@ -100,11 +104,14 @@ export class GpgME {
* @async * @async
*/ */
decrypt(data){ decrypt(data, base64=false){
if (data === undefined){ if (data === undefined){
return Promise.reject(gpgme_error('MSG_EMPTY')); return Promise.reject(gpgme_error('MSG_EMPTY'));
} }
let msg = createMessage('decrypt'); let msg = createMessage('decrypt');
if (base64 === true){
msg.expected = 'base64';
}
if (msg instanceof Error){ if (msg instanceof Error){
return Promise.reject(msg); return Promise.reject(msg);
} }
@ -156,11 +163,9 @@ export class GpgME {
} }
/** /**
* Sets the data of the message, converting Uint8Array to base64 and setting * Sets the data of the message
* the base64 flag
* @param {GPGME_Message} message The message where this data will be set * @param {GPGME_Message} message The message where this data will be set
* @param {*} data The data to enter * @param {*} data The data to enter
* @param {String} propertyname // TODO unchecked still
*/ */
function putData(message, data){ function putData(message, data){
if (!message || !message instanceof GPGME_Message ) { if (!message || !message instanceof GPGME_Message ) {
@ -168,30 +173,15 @@ function putData(message, data){
} }
if (!data){ if (!data){
return gpgme_error('PARAM_WRONG'); 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') { } else if (typeof(data) === 'string') {
message.setParameter('base64', false);
message.setParameter('data', data); message.setParameter('data', data);
} else if ( } else if (
typeof(data) === 'object' && typeof(data) === 'object' &&
typeof(data.getText) === 'function' typeof(data.getText) === 'function'
){ ){
let txt = data.getText(); let txt = data.getText();
if (txt instanceof Uint8Array){ if (typeof(txt) === 'string'){
message.setParameter('base64', true); message.setParameter('data', txt);
message.setParameter ('data', btoa(txt));
}
else if (typeof(txt) === 'string'){
message.setParameter('base64', false);
message.setParameter ('data', txt);
} else { } else {
return gpgme_error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }

View File

@ -51,7 +51,7 @@ export const permittedOperations = {
array_allowed: true array_allowed: true
}, },
'data': { 'data': {
allowed: ['string', 'Uint8Array'] allowed: ['string']
} }
}, },
optional: { optional: {
@ -103,7 +103,7 @@ export const permittedOperations = {
pinentry: true, pinentry: true,
required: { required: {
'data': { 'data': {
allowed: ['string', 'Uint8Array'] allowed: ['string']
} }
}, },
optional: { optional: {