diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js
new file mode 100644
index 00000000..e3323721
--- /dev/null
+++ b/lang/js/BrowserTestExtension/tests/signTest.js
@@ -0,0 +1,58 @@
+/* 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 5de70a6b..ebecf4fb 100644
--- a/lang/js/BrowserTestExtension/tests/startup.js
+++ b/lang/js/BrowserTestExtension/tests/startup.js
@@ -1,54 +1,51 @@
/* 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();
- }, function(errorr){
- expect(error).to.be.undefined;
- 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 9c2a6428..07df5def 100644
--- a/lang/js/src/Connection.js
+++ b/lang/js/src/Connection.js
@@ -1,241 +1,257 @@
/* 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 } from "./Message";
+import { GPGME_Message, createMessage } 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
+ * 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"
*/
- get isConnected(){
- return this._isConnected;
+ 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._isConnected === true){
- gpgme_error('CONN_ALREADY_CONNECTED');
- } else {
- this._isConnected = true;
+ if (!this._connection){
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(gpgme_error('CONN_DISCONNECTED'));
+ if (!this._connection) {
+
}
if (!message || !message instanceof GPGME_Message){
return Promise.reject(gpgme_error('PARAM_WRONG'), message);
}
if (message.isComplete !== true){
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)
reject(gpgme_error('CONN_EMPTY_GPG_ANSWER'));
} else if (msg.type === "error"){
me._connection.onMessage.removeListener(listener);
reject(gpgme_error('GNUPG_ERROR', msg.msg));
} else {
let answer_result = answer.add(msg);
if (answer_result !== true){
me._connection.onMessage.removeListener(listener);
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);
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(){
reject(gpgme_error('CONN_TIMEOUT'));
}, 5000);
}]).then(function(result){
return result;
}, function(reject){
if(!reject instanceof Error) {
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] = [];
}
- this._response.push(msg[key]);
+ this._response[key].push(msg[key]);
}
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/Errors.js b/lang/js/src/Errors.js
index bfe3a2f4..7e98f319 100644
--- a/lang/js/src/Errors.js
+++ b/lang/js/src/Errors.js
@@ -1,129 +1,125 @@
/* 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+
*/
const err_list = {
// Connection
'CONN_NO_CONNECT': {
msg:'Connection with the nativeMessaging host could not be'
+ ' established.',
type: 'error'
},
- 'CONN_DISCONNECTED': {
- msg:'Connection with the nativeMessaging host was lost.',
- 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: 'warning'
},
// 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_WRONG_OP': {
msg: 'The operation requested could not be found',
type: 'error'
},
'MSG_NO_KEYS' : {
msg: 'There were no valid keys provided.',
type: 'warning'
},
'MSG_NOT_A_FPR': {
msg: 'The String is not an accepted fingerprint',
type: 'warning'
},
'KEY_INVALID': {
msg:'Key object is invalid',
type: 'error'
},
// generic
'PARAM_WRONG':{
msg: 'Invalid parameter was found',
type: 'error'
},
'PARAM_IGNORED': {
msg: 'An parameter was set that has no effect in gpgmejs',
type: 'warning'
},
'GENERIC_ERROR': {
msg: 'Unspecified error',
type: 'error'
}
};
/**
* Checks the given error code and returns an error object with some
* information about meaning and origin
* @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR'
* @param {*} info Error message passed through if code is 'GNUPG_ERROR'
*/
export function gpgme_error(code = 'GENERIC_ERROR', info){
if (err_list.hasOwnProperty(code)){
if (err_list[code].type === 'error'){
return new GPGME_Error(code);
}
if (err_list[code].type === 'warning'){
console.warn(code + ': ' + err_list[code].msg);
}
return null;
} else if (code === 'GNUPG_ERROR'){
return new GPGME_Error(code, info);
}
else {
return new GPGME_Error('GENERIC_ERROR');
}
}
class GPGME_Error extends Error{
constructor(code, msg=''){
if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){
super(msg);
} else if (err_list.hasOwnProperty(code)){
super(err_list[code].msg);
} else {
super(err_list['GENERIC_ERROR'].msg);
}
this.code = code || 'GENERIC_ERROR';
}
set code(value){
this._code = value;
}
get code(){
return this._code;
}
}
\ No newline at end of file
diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js
index 4596035a..80792f77 100644
--- a/lang/js/src/Keyring.js
+++ b/lang/js/src/Keyring.js
@@ -1,162 +1,158 @@
/* 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){
- if (this._connection.isConnected){
- return this._connection;
- }
- return gpgme_error('CONN_DISCONNECTED');
+ return this._connection;
}
return gpgme_error('CONN_NO_CONNECT');
}
/**
* @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 msg = createMessage('listkeys');
- if (msg instanceof Error){
- return Promise.reject(msg);
- }
- if (pattern && typeof(pattern) === 'string'){
- msg.setParameter('pattern', pattern);
- }
- if (include_secret){
- msg.setParameter('with-secret', true);
- }
let me = this;
-
- this.connection.post(msg).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;
+ return new Promise(function(resolve, reject) {
+ let msg;
+ msg = createMessage('listkeys');
+ if (pattern && typeof(pattern) === 'string'){
+ msg.setParameter('pattern', pattern);
}
- for (let i=0; i < fpr_list.length; i++){
- let newKey = new GPGME_Key(fpr_list[i], me._connection);
- if (newKey instanceof GPGME_Key){
- resultset.push(newKey);
- }
+ if (include_secret){
+ msg.setParameter('with-secret', true);
}
- return Promise.resolve(resultset);
- }, function(error){
- //TODO error handling
+ me.connection.post(msg).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);
+ 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/gpgmejs.js b/lang/js/src/gpgmejs.js
index 1e76655e..c182c175 100644
--- a/lang/js/src/gpgmejs.js
+++ b/lang/js/src/gpgmejs.js
@@ -1,230 +1,224 @@
/* 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;
}
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(){
- if (this._connection){
- if (this._connection.isConnected === true){
- return this._connection;
- }
- return undefined;
- }
- return undefined;
+ return this._connection;
}
set Keyring(keyring){
if (keyring && keyring instanceof GPGME_Keyring){
this._Keyring = keyring;
}
}
get 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);
} 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);
}
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) {
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
}
});
} 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 8527b3f3..7f969fee 100644
--- a/lang/js/src/index.js
+++ b/lang/js/src/index.js
@@ -1,86 +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;
- // TODO: Delayed reaction is ugly. We need to listen to the port's
- // event listener in isConnected, but in some cases this takes some
- // time (<5ms) to disconnect if there is no successfull connection.
- let delayedreaction = function(){
- if (connection === undefined) {
+ connection.checkConnection(false).then(
+ function(result){
+ if (result === true) {
+ resolve(new GpgME(connection, _conf));
+ } else {
+ reject(gpgme_error('CONN_NO_CONNECT'));
+ }
+ }, function(error){
reject(gpgme_error('CONN_NO_CONNECT'));
- }
- if (connection.isConnected === true){
- resolve(new GpgME(connection, _conf));
- } else {
- reject(gpgme_error('CONN_NO_CONNECT'));
- }
- };
- setTimeout(delayedreaction, 5);
+ });
});
}
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 c437d599..06b2b23a 100644
--- a/lang/js/unittests.js
+++ b/lang/js/unittests.js
@@ -1,326 +1,327 @@
/* 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 { 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;
- let delayed = function(){
- expect(conn0.isConnected).to.be.true;
- expect(conn0.connect).to.be.a('function');
+ 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');
done();
- };
- setTimeout(delayed, 5);
+ });
});
it('Disconnecting', function(done) {
let conn0 = new Connection;
- let delayed = function(){
- conn0.disconnect(); // TODO fails!
- expect(conn0.isConnected).to.be.false;
- done();
- };
- setTimeout(delayed, 5);
+ 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();
+ });
+ });
});
-
- // broken
- // it('Connect info still only available after a delay', function(done){
- // // if false, all delayed connections can be refactored
- // let conn0 = new Connection;
- // expect(conn0.isConnected).to.be.undefined;
- // //
- // })
});
describe('Error Object handling', function(){
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(hp.validFingerprint, conn);
expect(key).to.be.an.instanceof(GPGME_Key);
expect(key.connection).to.be.an.instanceof(Connection);
// TODO not implemented yet: Further Key functionality
});
- it('Key can use the connection', function(){
+ it('Key can use the connection', function(done){
let conn = new Connection;
let key = createKey(hp.validFingerprint, conn);
-
- expect(key.connection.isConnected).to.be.true;
-
- key.connection.disconnect();
- expect(key.connection.isConnected).to.be.false;
+ 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;
+ 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);
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');
}
});
it('bad GPGME_Key returns Error if used', function(){
let conn = new Connection;
for (let i=0; i < 4; i++){
let key = new GPGME_Key(wp.four_invalid_params[i], conn);
expect(key.connection).to.be.an.instanceof(Error);
expect(key.connection.code).to.equal('KEY_INVALID');
}
});
});
describe('GPGME_Keyring', function(){
it('correct initialization', function(){
let conn = new Connection;
let keyring = new GPGME_Keyring(conn);
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(){
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');
- expect(keyring.getKeys).to.be.an.instanceof(Error);
- expect(keyring.getkeys.code).to.equal('CONN_NO_CONNECT');
+ // not yet implemented:
+ // keyring.getKeys().then(
+ // function(result){},
+ //function(reject){
+ // expect(reject).to.be.an.instanceof(Error);
+ // done();
});
//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