diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js
index 8bc3d42a..4270be58 100644
--- a/lang/js/src/Connection.js
+++ b/lang/js/src/Connection.js
@@ -1,209 +1,213 @@
/* 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 { GPGMEJS_Error } from "./Errors"
import { GPGME_Message } from "./Message";
/**
* A Connection handles the nativeMessaging interaction.
*/
export class Connection{
constructor(){
this.connect();
let me = this;
}
/**
* (Simple) Connection check.
* @returns {Boolean} true if the onDisconnect event has not been fired.
* Please note that the event listener of the port takes some time
* (5 ms seems enough) to react after the port is created. Then this will
* return undefined
*/
get isConnected(){
return this._isConnected;
}
/**
* Immediately closes the open port.
*/
disconnect() {
if (this._connection){
this._connection.disconnect();
}
}
/**
* Opens a nativeMessaging port.
*/
connect(){
if (this._isConnected === true){
GPGMEJS_Error('CONN_ALREADY_CONNECTED');
} else {
this._isConnected = true;
this._connection = chrome.runtime.connectNative('gpgmejson');
let me = this;
this._connection.onDisconnect.addListener(
function(){
me._isConnected = false;
}
);
}
}
/**
* 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.isConnected){
return Promise.reject(GPGMEJS_Error('CONN_NO_CONNECT'));
}
if (!message || !message instanceof GPGME_Message){
return Promise.reject(GPGMEJS_Error('PARAM_WRONG'), message);
}
if (message.isComplete !== true){
return Promise.reject(GPGMEJS_Error('MSG_INCOMPLETE'));
}
let me = this;
return new Promise(function(resolve, reject){
let answer = new Answer(message.operation);
let listener = function(msg) {
if (!msg){
me._connection.onMessage.removeListener(listener)
reject(GPGMEJS_Error('CONN_EMPTY_GPG_ANSWER'));
} else if (msg.type === "error"){
me._connection.onMessage.removeListener(listener)
reject(
{code: 'GNUPG_ERROR',
msg: msg.msg} );
} else {
let answer_result = answer.add(msg);
if (answer_result !== true){
reject(answer_result);
}
if (msg.more === true){
me._connection.postMessage({'op': 'getmore'});
} else {
me._connection.onMessage.removeListener(listener)
resolve(answer.message);
}
}
};
me._connection.onMessage.addListener(listener);
- me._connection.postMessage(message.message);
-
- //TBD: needs to be aware if there is a pinentry pending
- // setTimeout(
- // function(){
- // me.disconnect();
- // reject(GPGMEJS_Error('CONN_TIMEOUT'));
- // }, timeout);
+ let timeout = new Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(GPGMEJS_Error('CONN_TIMEOUT'));
+ }, 5000);
+ });
+ if (permittedOperations[message.operation].pinentry){
+ return me._connection.postMessage(message.message);
+ } else {
+ return Promise.race([timeout,
+ me._connection.postMessage(message.message)
+ ]);
+ }
});
}
};
/**
* 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(operation){
this.operation = operation;
}
/**
* Add the information to the answer
* @param {Object} msg The message as received with nativeMessaging
* returns true if successfull, GPGMEJS_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 GPGMEJS_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 GPGMEJS_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] = this._response[key].concat(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 GPGMEJS_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] = [];
}
this._response.push(msg[key]);
}
else {
return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER', key);
}
break;
}
}
return true;
}
/**
* @returns {Object} the assembled message.
* TODO: does not care yet if completed.
*/
get message(){
return this._response;
}
}
diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js
index c49bfe21..04b13e10 100644
--- a/lang/js/src/Errors.js
+++ b/lang/js/src/Errors.js
@@ -1,116 +1,116 @@
/* 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+
*/
/**
* Checks the given error code and returns some information about it's meaning
* @param {String} code The error code
* @returns {Object} An object containing string properties code and msg
* TODO: error-like objects with the code 'GNUPG_ERROR' are errors sent
* directly by gnupg as answer in Connection.post()
*/
export function GPGMEJS_Error(code = 'GENERIC_ERROR'){
if (!typeof(code) === 'string'){
code = 'GENERIC_ERROR';
}
let errors = { //TODO: someplace else
// Connection
'CONN_NO_CONNECT': {
msg:'Connection with the nativeMessaging host could not be'
+ ' established.',
type: 'error'
},
'CONN_EMPTY_GPG_ANSWER':{
msg: 'The nativeMessaging answer was empty.',
type: 'error'
},
'CONN_TIMEOUT': {
msg: 'A connection timeout was exceeded.',
type: 'error'
},
'CONN_UNEXPECTED_ANSWER': {
msg: 'The answer from gnupg was not as expected.',
type: 'error'
},
'CONN_ALREADY_CONNECTED':{
msg: 'A connection was already established.',
type: 'warn'
},
// Message/Data
'MSG_INCOMPLETE': {
msg: 'The Message did not match the minimum requirements for'
+ ' the interaction.',
type: 'error'
},
'MSG_EMPTY' : {
msg: 'The Message is empty.',
type: 'error'
},
'MSG_OP_PENDING': {
msg: 'There is no operation specified yet. The parameter cannot'
+ ' be set',
type: 'warning'
},
'MSG_WRONG_OP': {
msg: 'The operation requested could not be found',
type: 'warning'
},
'MSG_NO_KEYS' : {
msg: 'There were no valid keys provided.',
type: 'warn'
},
'MSG_NOT_A_FPR': {
msg: 'The String is not an accepted fingerprint',
type: 'warn'
},
// generic
'PARAM_WRONG':{
msg: 'invalid parameter was found',
type: 'error'
},
'NOT_IMPLEMENTED': {
msg: 'A openpgpjs parameter was submitted that is not implemented',
type: 'error'
},
'NOT_YET_IMPLEMENTED': {
msg: 'Support of this is probable, but it is not implemented yet',
type: 'error'
},
'GENERIC_ERROR': {
msg: 'Unspecified error',
type: 'error'
},
}
if (code === 'TODO'){
alert('TODO_Error!');
}
if (errors.hasOwnProperty(code)){
code = 'GENERIC_ERROR';
}
- if (error.type === 'error'){
+ if (errors.type === 'error'){
return {code: 'code',
msg: errors[code].msg
};
}
- if (error.type === 'warning'){
+ if (errors.type === 'warning'){
console.log(code + ': ' + error[code].msg);
}
return undefined;
}
diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js
index 3c11b8e0..892f4f2e 100644
--- a/lang/js/src/permittedOperations.js
+++ b/lang/js/src/permittedOperations.js
@@ -1,75 +1,78 @@
/* 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+
*/
/**
* Definition of the possible interactions with gpgme-json.
* operation:
required: Array
optional: Array
+ pinentry: Boolean If a pinentry dialog is expected, and a timeout of
+ 5000 ms would be too short
answer:
type: The payload property of the answer. May be
partial and in need of concatenation
params: Array Information that do not change throughout
the message
infos: Array arbitrary information that may change
}
}
*/
export const permittedOperations = {
encrypt: {
required: ['keys', 'data'],
optional: [
'protocol',
'chunksize',
'base64',
'mime',
'armor',
'always-trust',
'no-encrypt-to',
'no-compress',
'throw-keyids',
'want-address',
'wrap'
],
answer: {
type: ['ciphertext'],
data: ['data'],
params: ['base64'],
infos: []
}
},
decrypt: {
+ pinentry: true,
required: ['data'],
optional: [
'protocol',
'chunksize',
'base64'
],
answer: {
type: ['plaintext'],
data: ['data'],
params: ['base64', 'mime'],
infos: ['info']
}
}
}
diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js
index f47299e8..b2cb4c23 100644
--- a/lang/js/testapplication.js
+++ b/lang/js/testapplication.js
@@ -1,55 +1,55 @@
/* 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+
*
*/
document.addEventListener('DOMContentLoaded', function() {
Gpgmejs.init().then(function(gpgmejs){
document.getElementById("buttonencrypt").addEventListener("click",
function(){
let data = document.getElementById('cleartext').value;
let keyId = document.getElementById('pubkey').value;
gpgmejs.encrypt(data, keyId).then(
function(answer){
console.log(answer);
if (answer.data){
console.log(answer.data);
document.getElementById('answer').value = answer.data;
}
}, function(errormsg){
- alert('Error: '+ errormsg);
+ alert( errormsg.code + ' ' + errormsg.msg);
});
});
document.getElementById("buttondecrypt").addEventListener("click",
function(){
let data = document.getElementById("ciphertext").value;
gpgmejs.decrypt(data).then(
function(answer){
console.log(answer);
if (answer.data){
document.getElementById('answer').value = answer.data;
}
}, function(errormsg){
- alert('Error: '+ errormsg);
+ alert( errormsg.code + ' ' + errormsg.msg);
});
});
},
function(error){console.log(error)});
});