js: Configuration and Error handling

--

* gpgmejs_openpgpjs
  - unsuported values with no negative consequences can now reject,
    warn or be ignored, according to config.unconsidered_params
  - cleanup of unsupported/supported parameters and TODOS

* A src/index.js init() now accepts a configuration object

* Errors will now be derived from Error, offering more info and a
  stacktrace.

* Fixed Connection.post() timeout triggering on wrong cases

* Added comments in permittedOperations.js, which gpgme interactions
  are still unimplemented and should be added next
This commit is contained in:
Maximilian Krambach 2018-04-25 15:59:36 +02:00
parent 5befa1c975
commit 1fb310cabe
10 changed files with 310 additions and 208 deletions

View File

@ -24,7 +24,7 @@
* expected. * expected.
*/ */
import { permittedOperations } from './permittedOperations' import { permittedOperations } from './permittedOperations'
import { GPGMEJS_Error } from "./Errors" import { gpgme_error } from "./Errors"
import { GPGME_Message } from "./Message"; import { GPGME_Message } from "./Message";
/** /**
@ -62,7 +62,7 @@ export class Connection{
*/ */
connect(){ connect(){
if (this._isConnected === true){ if (this._isConnected === true){
GPGMEJS_Error('CONN_ALREADY_CONNECTED'); gpgme_error('CONN_ALREADY_CONNECTED');
} else { } else {
this._isConnected = true; this._isConnected = true;
this._connection = chrome.runtime.connectNative('gpgmejson'); this._connection = chrome.runtime.connectNative('gpgmejson');
@ -83,13 +83,13 @@ export class Connection{
*/ */
post(message){ post(message){
if (!this.isConnected){ if (!this.isConnected){
return Promise.reject(GPGMEJS_Error('CONN_NO_CONNECT')); return Promise.reject(gpgme_error('CONN_NO_CONNECT'));
} }
if (!message || !message instanceof GPGME_Message){ if (!message || !message instanceof GPGME_Message){
return Promise.reject(GPGMEJS_Error('PARAM_WRONG'), message); return Promise.reject(gpgme_error('PARAM_WRONG'), message);
} }
if (message.isComplete !== true){ if (message.isComplete !== true){
return Promise.reject(GPGMEJS_Error('MSG_INCOMPLETE')); return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
} }
let me = this; let me = this;
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
@ -97,7 +97,7 @@ export class Connection{
let listener = function(msg) { let listener = function(msg) {
if (!msg){ if (!msg){
me._connection.onMessage.removeListener(listener) me._connection.onMessage.removeListener(listener)
reject(GPGMEJS_Error('CONN_EMPTY_GPG_ANSWER')); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER'));
} else if (msg.type === "error"){ } else if (msg.type === "error"){
me._connection.onMessage.removeListener(listener) me._connection.onMessage.removeListener(listener)
reject( reject(
@ -118,17 +118,18 @@ export class Connection{
}; };
me._connection.onMessage.addListener(listener); me._connection.onMessage.addListener(listener);
let timeout = new Promise(function(resolve, reject){
setTimeout(function(){
reject(GPGMEJS_Error('CONN_TIMEOUT'));
}, 5000);
});
if (permittedOperations[message.operation].pinentry){ if (permittedOperations[message.operation].pinentry){
return me._connection.postMessage(message.message); return me._connection.postMessage(message.message);
} else { } else {
return Promise.race([timeout, return Promise.race([
me._connection.postMessage(message.message) me._connection.postMessage(message.message),
]); function(resolve, reject){
setTimeout(function(){
reject(gpgme_error('CONN_TIMEOUT'));
}, 5000);
}]).then(function(result){
return result;
});
} }
}); });
} }
@ -148,7 +149,7 @@ class Answer{
/** /**
* Add the information to the answer * Add the information to the answer
* @param {Object} msg The message as received with nativeMessaging * @param {Object} msg The message as received with nativeMessaging
* returns true if successfull, GPGMEJS_Error otherwise * returns true if successfull, gpgme_error otherwise
*/ */
add(msg){ add(msg){
if (this._response === undefined){ if (this._response === undefined){
@ -157,14 +158,14 @@ class Answer{
let messageKeys = Object.keys(msg); let messageKeys = Object.keys(msg);
let poa = permittedOperations[this.operation].answer; let poa = permittedOperations[this.operation].answer;
if (messageKeys.length === 0){ if (messageKeys.length === 0){
return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER'); return gpgme_error('CONN_UNEXPECTED_ANSWER');
} }
for (let i= 0; i < messageKeys.length; i++){ for (let i= 0; i < messageKeys.length; i++){
let key = messageKeys[i]; let key = messageKeys[i];
switch (key) { switch (key) {
case 'type': case 'type':
if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){
return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER'); return gpgme_error('CONN_UNEXPECTED_ANSWER');
} }
break; break;
case 'more': case 'more':
@ -183,7 +184,7 @@ class Answer{
this._response[key] = msg[key]; this._response[key] = msg[key];
} }
else if (this._response[key] !== msg[key]){ else if (this._response[key] !== msg[key]){
return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER',msg[key]); return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]);
} }
} }
//infos may be json objects etc. Not yet defined. //infos may be json objects etc. Not yet defined.
@ -195,7 +196,7 @@ class Answer{
this._response.push(msg[key]); this._response.push(msg[key]);
} }
else { else {
return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER', key); return gpgme_error('CONN_UNEXPECTED_ANSWER', key);
} }
break; break;
} }

View File

@ -18,99 +18,125 @@
* SPDX-License-Identifier: LGPL-2.1+ * SPDX-License-Identifier: LGPL-2.1+
*/ */
/** const err_list = {
* Checks the given error code and returns some information about it's meaning // Connection
* @param {String} code The error code 'CONN_NO_CONNECT': {
* @returns {Object} An object containing string properties code and msg msg:'Connection with the nativeMessaging host could not be'
* TODO: error-like objects with the code 'GNUPG_ERROR' are errors sent + ' established.',
* directly by gnupg as answer in Connection.post() type: 'error'
*/ },
export function GPGMEJS_Error(code = 'GENERIC_ERROR'){ 'CONN_DISCONNECTED': {
if (!typeof(code) === 'string'){ msg:'Connection with the nativeMessaging host was lost.',
code = 'GENERIC_ERROR'; type: 'error'
} },
let errors = { //TODO: someplace else 'CONN_EMPTY_GPG_ANSWER':{
// Connection msg: 'The nativeMessaging answer was empty.',
'CONN_NO_CONNECT': { type: 'error'
msg:'Connection with the nativeMessaging host could not be' },
+ ' established.', 'CONN_TIMEOUT': {
type: 'error' msg: 'A connection timeout was exceeded.',
}, type: 'error'
'CONN_EMPTY_GPG_ANSWER':{ },
msg: 'The nativeMessaging answer was empty.', 'CONN_UNEXPECTED_ANSWER': {
type: 'error' msg: 'The answer from gnupg was not as expected.',
}, type: 'error'
'CONN_TIMEOUT': { },
msg: 'A connection timeout was exceeded.', 'CONN_ALREADY_CONNECTED':{
type: 'error' msg: 'A connection was already established.',
}, type: 'warning'
'CONN_UNEXPECTED_ANSWER': { },
msg: 'The answer from gnupg was not as expected.', // Message/Data
type: 'error' 'MSG_INCOMPLETE': {
}, msg: 'The Message did not match the minimum requirements for'
'CONN_ALREADY_CONNECTED':{ + ' the interaction.',
msg: 'A connection was already established.', type: 'error'
type: 'warn' },
}, 'MSG_EMPTY' : {
// Message/Data msg: 'The Message is empty.',
'MSG_INCOMPLETE': { type: 'error'
msg: 'The Message did not match the minimum requirements for' },
+ ' the interaction.', 'MSG_OP_PENDING': {
type: 'error' msg: 'There is no operation specified yet. The parameter cannot'
}, + ' be set',
'MSG_EMPTY' : { type: 'warning'
msg: 'The Message is empty.', },
type: 'error' 'MSG_WRONG_OP': {
}, msg: 'The operation requested could not be found',
'MSG_OP_PENDING': { type: 'warning'
msg: 'There is no operation specified yet. The parameter cannot' },
+ ' be set', 'MSG_NO_KEYS' : {
type: 'warning' msg: 'There were no valid keys provided.',
}, type: 'warning'
'MSG_WRONG_OP': { },
msg: 'The operation requested could not be found', 'MSG_NOT_A_FPR': {
type: 'warning' msg: 'The String is not an accepted fingerprint',
}, type: 'warning'
'MSG_NO_KEYS' : { },
msg: 'There were no valid keys provided.', 'KEY_INVALID': {
type: 'warn' msg:'Key object is invalid',
}, type: 'error'
'MSG_NOT_A_FPR': { },
msg: 'The String is not an accepted fingerprint', // generic
type: 'warn' 'PARAM_WRONG':{
}, msg: 'invalid parameter was found',
type: 'error'
},
'PARAM_IGNORED': {
msg: 'An parameter was set that has no effect in gpgmejs',
type: 'warning'
},
'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'
}
};
// generic /**
'PARAM_WRONG':{ * Checks the given error code and returns an error object with some
msg: 'invalid parameter was found', * information about meaning and origin
type: 'error' * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR'
}, * @param {*} info Error message passed through if code is 'GNUPG_ERROR'
'NOT_IMPLEMENTED': { */
msg: 'A openpgpjs parameter was submitted that is not implemented', export function gpgme_error(code = 'GENERIC_ERROR', info){
type: 'error' if (err_list.hasOwnProperty(code)){
}, if (err_list[code].type === 'error'){
'NOT_YET_IMPLEMENTED': { return new GPGME_Error(code);
msg: 'Support of this is probable, but it is not implemented yet',
type: 'error'
},
'GENERIC_ERROR': {
msg: 'Unspecified error',
type: 'error'
},
} }
if (code === 'TODO'){ if (err_list[code].type === 'warning'){
alert('TODO_Error!'); console.log(new GPGME_Error(code));
} }
if (errors.hasOwnProperty(code)){ return null;
code = 'GENERIC_ERROR'; } else if (code === 'GNUPG_ERROR'){
} return new GPGME_Error(code, info.msg);
if (errors.type === 'error'){ }
return {code: 'code', else {
msg: errors[code].msg return new GPGME_Error('GENERIC_ERROR');
}; }
} }
if (errors.type === 'warning'){
console.log(code + ': ' + error[code].msg); class GPGME_Error extends Error{
} constructor(code, msg=''){
return undefined; 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;
}
} }

View File

@ -17,7 +17,7 @@
* License along with this program; if not, see <http://www.gnu.org/licenses/>. * License along with this program; if not, see <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+ * SPDX-License-Identifier: LGPL-2.1+
*/ */
import { GPGMEJS_Error } from "./Errors"; import { gpgme_error } from "./Errors";
/** /**
* Tries to return an array of fingerprints, either from input fingerprints or * Tries to return an array of fingerprints, either from input fingerprints or
@ -28,7 +28,7 @@ import { GPGMEJS_Error } from "./Errors";
export function toKeyIdArray(input, nocheck){ export function toKeyIdArray(input, nocheck){
if (!input){ if (!input){
GPGMEJS_Error('MSG_NO_KEYS'); gpgme_error('MSG_NO_KEYS');
return []; return [];
} }
if (!Array.isArray(input)){ if (!Array.isArray(input)){
@ -40,7 +40,7 @@ export function toKeyIdArray(input, nocheck){
if (isFingerprint(input[i]) === true){ if (isFingerprint(input[i]) === true){
result.push(input[i]); result.push(input[i]);
} else { } else {
GPGMEJS_Error('MSG_NOT_A_FPR'); gpgme_error('MSG_NOT_A_FPR');
} }
} else if (typeof(input[i]) === 'object'){ } else if (typeof(input[i]) === 'object'){
let fpr = ''; let fpr = '';
@ -53,14 +53,14 @@ export function toKeyIdArray(input, nocheck){
if (isFingerprint(fpr) === true){ if (isFingerprint(fpr) === true){
result.push(fpr); result.push(fpr);
} else { } else {
GPGMEJS_Error('MSG_NOT_A_FPR'); gpgme_error('MSG_NOT_A_FPR');
} }
} else { } else {
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
} }
if (result.length === 0){ if (result.length === 0){
GPGMEJS_Error('MSG_NO_KEYS'); gpgme_error('MSG_NO_KEYS');
return []; return [];
} else { } else {
return result; return result;

View File

@ -27,7 +27,9 @@
*/ */
import {isFingerprint} from './Helpers' import {isFingerprint} from './Helpers'
import {GPGMEJS_Error} from './Errors' import {gpgme_error} from './Errors'
import { GPGME_Message } from './Message';
import { permittedOperations } from './permittedOperations';
export class GPGME_Key { export class GPGME_Key {
@ -172,32 +174,30 @@ export class GPGME_Key {
* *
*/ */
function checkKey(fingerprint, property){ function checkKey(fingerprint, property){
return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
if (!property ||
permittedOperations[keyinfo].indexOf(property) < 0){
return Promise.reject(gpgme_error('PARAM_WRONG'));
}
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
if (!isFingerprint(fingerprint)){ if (!isFingerprint(fingerprint)){
reject('not a fingerprint'); //TBD reject('KEY_INVALID');
} }
let conn = new Connection(); let msg = new GPGME_Message('keyinfo');
conn.post('getkey',{ // TODO not yet implemented in gpgme msg.setParameter('fingerprint', this.fingerprint);
'fingerprint': this.fingerprint}) return (this.connection.post(msg)).then(function(result){
.then(function(result){ if (result.hasOwnProperty(property)){
if (property !== undefined){ resolve(result[property]);
if (result.hasOwnProperty(key)){ }
resolve(result[property]); else if (property == 'secret'){
} // TBD property undefined means "not true" in case of secret?
else if (property == 'secret'){ resolve(false);
// property undefined means "not true" in case of secret } else {
resolve(false); reject(gpgme_error('CONN_UNEXPECTED_ANSWER'));
} else {
reject('ERR_INVALID_PROPERTY') //TBD
}
} }
resolve(result);
}, function(error){ }, function(error){
reject(error); reject({code: 'GNUPG_ERROR',
msg: error.msg});
}); });
}); });
}; };

View File

@ -21,6 +21,7 @@
import {GPGME_Message} from './Message' import {GPGME_Message} from './Message'
import {GPGME_Key} from './Key' import {GPGME_Key} from './Key'
import { isFingerprint, isLongId } from './Helpers'; import { isFingerprint, isLongId } from './Helpers';
import { gpgme_error } from './Errors';
export class GPGME_Keyring { export class GPGME_Keyring {
constructor(connection){ constructor(connection){
@ -37,9 +38,9 @@ export class GPGME_Keyring {
if (this._connection.isConnected){ if (this._connection.isConnected){
return this._connection; return this._connection;
} }
return undefined; //TODO: connection was lost! return gpgme_error('CONN_DISCONNECTED');
} }
return undefined; //TODO: no connection there return gpgme_error('CONN_NO_CONNECT');
} }
/** /**
@ -81,9 +82,7 @@ export class GPGME_Keyring {
* filters described below. True will filter on the condition, False will * filters described below. True will filter on the condition, False will
* reverse the filter, if not present or undefined, the filter will not be * reverse the filter, if not present or undefined, the filter will not be
* considered. Please note that some combination may not make sense * considered. Please note that some combination may not make sense
* @param {Boolean} flags.defaultKey Only Keys marked as Default Keys
* @param {Boolean} flags.secret Only Keys containing a secret part. * @param {Boolean} flags.secret Only Keys containing a secret part.
* @param {Boolean} flags.valid Valid Keys only
* @param {Boolean} flags.revoked revoked Keys only * @param {Boolean} flags.revoked revoked Keys only
* @param {Boolean} flags.expired Expired Keys only * @param {Boolean} flags.expired Expired Keys only
* @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds
@ -108,16 +107,20 @@ export class GPGME_Keyring {
} else if (secretflag === false){ } else if (secretflag === false){
anticonditions.push('hasSecret'); anticonditions.push('hasSecret');
} }
/**
if (flags.defaultKey === true){ if (flags.defaultKey === true){
conditions.push('isDefault'); conditions.push('isDefault');
} else if (flags.defaultKey === false){ } else if (flags.defaultKey === false){
anticonditions.push('isDefault'); anticonditions.push('isDefault');
} }
if (flags.valid === true){ */
/**
* if (flags.valid === true){
anticonditions.push('isInvalid'); anticonditions.push('isInvalid');
} else if (flags.valid === false){ } else if (flags.valid === false){
conditions.push('isInvalid'); conditions.push('isInvalid');
} }
*/
if (flags.revoked === true){ if (flags.revoked === true){
conditions.push('isRevoked'); conditions.push('isRevoked');
} else if (flags.revoked === false){ } else if (flags.revoked === false){

View File

@ -18,7 +18,7 @@
* SPDX-License-Identifier: LGPL-2.1+ * SPDX-License-Identifier: LGPL-2.1+
*/ */
import { permittedOperations } from './permittedOperations' import { permittedOperations } from './permittedOperations'
import { GPGMEJS_Error } from './Errors' import { gpgme_error } from './Errors'
export class GPGME_Message { export class GPGME_Message {
//TODO getter //TODO getter
@ -39,20 +39,20 @@ export class GPGME_Message {
*/ */
setParameter(param,value){ setParameter(param,value){
if (!param || typeof(param) !== 'string'){ if (!param || typeof(param) !== 'string'){
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
if (!this._msg || !this._msg.op){ if (!this._msg || !this._msg.op){
return GPGMEJS_Error('MSG_OP_PENDING'); return gpgme_error('MSG_OP_PENDING');
} }
let po = permittedOperations[this._msg.op]; let po = permittedOperations[this._msg.op];
if (!po){ if (!po){
return GPGMEJS_Error('MSG_WRONG_OP'); return gpgme_error('MSG_WRONG_OP');
} }
if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){
this._msg[param] = value; this._msg[param] = value;
return true; return true;
} }
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
/** /**
@ -98,7 +98,7 @@ export class GPGME_Message {
*/ */
function setOperation (scope, operation){ function setOperation (scope, operation){
if (!operation || typeof(operation) !== 'string'){ if (!operation || typeof(operation) !== 'string'){
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
if (permittedOperations.hasOwnProperty(operation)){ if (permittedOperations.hasOwnProperty(operation)){
if (!scope._msg){ if (!scope._msg){
@ -106,6 +106,6 @@ function setOperation (scope, operation){
} }
scope._msg.op = operation; scope._msg.op = operation;
} else { } else {
return GPGMEJS_Error('MSG_WRONG_OP'); return gpgme_error('MSG_WRONG_OP');
} }
} }

View File

@ -21,7 +21,7 @@
import {Connection} from "./Connection" import {Connection} from "./Connection"
import {GPGME_Message} from './Message' import {GPGME_Message} from './Message'
import {toKeyIdArray} from "./Helpers" import {toKeyIdArray} from "./Helpers"
import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors" import { gpgme_error } from "./Errors"
import { GPGME_Keyring } from "./Keyring"; import { GPGME_Keyring } from "./Keyring";
export class GpgME { export class GpgME {
@ -35,10 +35,12 @@ export class GpgME {
set connection(connection){ set connection(connection){
if (this._connection instanceof Connection){ if (this._connection instanceof Connection){
//TODO Warning: Connection already established gpgme_error('CONN_ALREADY_CONNECTED');
} }
if (connection instanceof Connection){ if (connection instanceof Connection){
this._connection = connection; this._connection = connection;
} else {
gpgme_error('PARAM_WRONG');
} }
} }
@ -54,11 +56,12 @@ export class GpgME {
set Keyring(keyring){ set Keyring(keyring){
if (ring && ring instanceof GPGME_Keyring){ if (ring && ring instanceof GPGME_Keyring){
this.Keyring = ring; this._Keyring = ring;
} }
} }
get Keyring(){ get Keyring(){
return this._Keyring;
} }
/** /**
@ -96,7 +99,7 @@ export class GpgME {
decrypt(data){ decrypt(data){
if (data === undefined){ if (data === undefined){
return Promise.reject(GPGMEJS_Error('MSG_EMPTY')); return Promise.reject(gpgme_error('MSG_EMPTY'));
} }
let msg = new GPGME_Message('decrypt'); let msg = new GPGME_Message('decrypt');
putData(msg, data); putData(msg, data);
@ -105,7 +108,7 @@ export class GpgME {
} }
deleteKey(key, delete_secret = false, no_confirm = false){ deleteKey(key, delete_secret = false, no_confirm = false){
return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
let msg = new GPGME_Message('deletekey'); let msg = new GPGME_Message('deletekey');
let key_arr = toKeyIdArray(key); let key_arr = toKeyIdArray(key);
if (key_arr.length !== 1){ if (key_arr.length !== 1){
@ -126,7 +129,7 @@ export class GpgME {
case 'ERR_NO_ERROR': case 'ERR_NO_ERROR':
return Promise.resolve('okay'); //TBD return Promise.resolve('okay'); //TBD
default: default:
return Promise.reject(GPGMEJS_Error('TODO') ); // return Promise.reject(gpgme_error('TODO') ); //
// INV_VALUE, // INV_VALUE,
// GPG_ERR_NO_PUBKEY, // GPG_ERR_NO_PUBKEY,
// GPG_ERR_AMBIGUOUS_NAME, // GPG_ERR_AMBIGUOUS_NAME,
@ -145,7 +148,7 @@ export class GpgME {
*/ */
function putData(message, data){ function putData(message, data){
if (!message || !message instanceof GPGME_Message ) { if (!message || !message instanceof GPGME_Message ) {
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
if (!data){ if (!data){
message.setParameter('data', ''); message.setParameter('data', '');
@ -164,6 +167,6 @@ function putData(message, data){
message.setParameter ('data', decoder.decode(txt)); message.setParameter ('data', decoder.decode(txt));
} }
} else { } else {
return GPGMEJS_Error('PARAM_WRONG'); return gpgme_error('PARAM_WRONG');
} }
} }

View File

@ -28,13 +28,13 @@
import {GPGME_Keyring} from "./Keyring" import {GPGME_Keyring} from "./Keyring"
import { GPGME_Key } from "./Key"; import { GPGME_Key } from "./Key";
import { isFingerprint } from "./Helpers" import { isFingerprint } from "./Helpers"
import { GPGMEJS_Error } from './Errors' import { gpgme_error } from "./Errors"
export class GpgME_openpgpmode { export class GpgME_openpgpmode {
constructor(connection){ constructor(connection, config = {}){
this.initGpgME(connection); this.initGpgME(connection, config);
} }
get Keyring(){ get Keyring(){
@ -44,9 +44,16 @@
return undefined; return undefined;
} }
initGpgME(connection){ initGpgME(connection, config = {}){
this._GpgME = new GpgME(connection); if (connection && typeof(config) ==='object'){
this._Keyring = new GPGME_Keyring_openpgpmode(connection); this._config = config;
if (!this._GPGME){
this._GpgME = new GpgME(connection, config);
}
if (!this._Keyring){
this._Keyring = new GPGME_Keyring_openpgpmode(connection);
}
}
} }
get GpgME(){ get GpgME(){
@ -59,19 +66,23 @@
* Encrypt Message * Encrypt Message
* Supported: * Supported:
* @param {String|Uint8Array} data * @param {String|Uint8Array} data
* //an openpgp Message also accepted here. TODO: is this wanted?
* @param {Key|Array<Key>} publicKeys * @param {Key|Array<Key>} publicKeys
* //Strings of Fingerprints
* @param {Boolean} wildcard * @param {Boolean} wildcard
* TODO: * TODO:
* @param {Key|Array<Key>} privateKeys * @param {Key|Array<Key>} privateKeys // -> encryptsign
* @param {String} filename * @param {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress
* @param {module:enums.compression} compression * @param {Boolean} armor // TODO base64 switch
* @param {Boolean} armor * @param {Boolean} detached // --> encryptsign
* @param {Boolean} detached
* unsupported: * unsupported:
* @param {String|Array<String>} passwords * @param {String|Array<String>} passwords
* @param {Object} sessionKey * @param {Object} sessionKey
* @param {Signature} signature * @param {Signature} signature
* @param {Boolean} returnSessionKey * @param {Boolean} returnSessionKey
* @param {String} filename
*
* Can be set, but will be ignored:
* *
* @returns {Promise<Object>} * @returns {Promise<Object>}
* {data: ASCII armored message, * {data: ASCII armored message,
@ -80,57 +91,66 @@
* @async * @async
* @static * @static
*/ */
encrypt({data = '', publicKeys = '', privateKeys, passwords, sessionKey, encrypt({data = '', publicKeys = '', privateKeys, passwords=null,
filename, compression, armor=true, detached=false, signature=null, sessionKey = null, filename, compression, armor=true, detached=false,
returnSessionKey=null, wildcard=false, date=null}) { signature=null, returnSessionKey=null, wildcard=false, date=null}) {
if (passwords !== undefined if (passwords !== null
|| sessionKey !== undefined || sessionKey !== null
|| signature !== null || signature !== null
|| returnSessionKey !== null || returnSessionKey !== null
|| date !== null){ || date !== null
){
return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
} }
if ( privateKeys if ( privateKeys
|| filename
|| compression || compression
|| armor === false || armor === false
|| detached == true){ || detached == true){
return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
}
if (filename){
if (this._config.unconsidered_params === 'warn'){
GPMGEJS_Error('PARAM_IGNORED');
} else if (this._config.unconsidered_params === 'error'){
return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
}
} }
return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard);
} }
/** Decrypt Message /** Decrypt Message
* supported * supported openpgpjs parameters:
* TODO: @param {Message} message TODO: for now it accepts an armored string only * @param {Message|Uint8Array|String} message Message object from openpgpjs
* Unsupported: * Unsupported:
* @param {String|Array<String>} passwords * @param {String|Array<String>} passwords
* @param {Key|Array<Key>} privateKeys
* @param {Object|Array<Object>} sessionKeys * @param {Object|Array<Object>} sessionKeys
* @param {Date} date * Not yet supported, but planned
* TODO
* @param {Key|Array<Key>} privateKey
* @param {Key|Array<Key>} publicKeys
* @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {String} format (optional) return data format either as 'utf8' or 'binary'
* @param {Signature} signature (optional) detached signature for verification * @param {Signature} signature (optional) detached signature for verification
* Ignored values: can be safely set, but have no effect
* @param {Date} date
* @param {Key|Array<Key>} publicKeys
*
* @returns {Promise<Object>} decrypted and verified message in the form: * @returns {Promise<Object>} decrypted and verified message in the form:
* { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
* @async * @async
* @static * @static
*/ */
decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null, date}) { decrypt({ message, privateKeys, passwords=null, sessionKeys,
if (passwords !== undefined publicKeys, format='utf8', signature=null, date= null}) {
|| sessionKeys if (passwords !== null || sessionKeys || privateKeys){
|| date){ return Promise.reject(gpgme_error('NOT_IMPLEMENTED'));
return Promise.reject(GPGMEJS_Error('NOT_IMPLEMENTED'));
} }
if ( privateKeys if ( format !== 'utf8' || signature){
|| publicKeys return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
|| format !== 'utf8' }
|| signature if (date !== null || publicKeys){
){ if (this._config.unconsidered_params === 'warn'){
return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); GPMGEJS_Error('PARAM_IGNORED');
} else if (this._config.unconsidered_params === 'reject'){
return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
}
} }
return this.GpgME.decrypt(message); return this.GpgME.decrypt(message);
// TODO: translate between: // TODO: translate between:
@ -185,7 +205,7 @@ class GPGME_Keyring_openpgpmode {
else { else {
// TODO: Can there be "no default key"? // TODO: Can there be "no default key"?
// TODO: Can there be several default keys? // TODO: Can there be several default keys?
return GPGMEJS_Error('TODO'); return gpgme_error('TODO');
} }
}); });
} }
@ -202,10 +222,10 @@ class GPGME_Keyring_openpgpmode {
*/ */
deleteKey(key){ deleteKey(key){
if (typeof(key) !== "object"){ if (typeof(key) !== "object"){
return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); return Promise.reject(gpgme_error('PARAM_WRONG'));
} }
if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){
return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); return Promise.reject(gpgme_error('PARAM_WRONG'));
} }
let key_to_delete = new GPGME_Key(key.fingerprint); let key_to_delete = new GPGME_Key(key.fingerprint);
return key_to_delete.deleteKey(key.secret); return key_to_delete.deleteKey(key.secret);
@ -224,8 +244,8 @@ class GPGME_Key_openpgpmode {
set init (value){ set init (value){
if (!this._GPGME_Key && value instanceof GPGME_Key){ if (!this._GPGME_Key && value instanceof GPGME_Key){
this._GPGME_Key = value; this._GPGME_Key = value;
} else if (!this._GPGME_Key && isFingerprint(fpr)){ } else if (!this._GPGME_Key && isFingerprint(value)){
this._GPGME_Key = new GPGME_Key; this._GPGME_Key = new GPGME_Key(value);
} }
} }

View File

@ -19,7 +19,7 @@
*/ */
import { GpgME } from "./gpgmejs"; import { GpgME } from "./gpgmejs";
import { GPGMEJS_Error } from "./Errors"; import { gpgme_error } from "./Errors";
import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs";
import { Connection } from "./Connection"; import { Connection } from "./Connection";
@ -29,7 +29,8 @@ import { Connection } from "./Connection";
*/ */
function init( config = { function init( config = {
api_style: 'gpgme', // | gpgme_openpgpjs api_style: 'gpgme', // | gpgme_openpgpjs
null_expire_is_never: true // Boolean null_expire_is_never: true, // Boolean
unconsidered_params: 'warn'//'warn' || 'reject'
}){ }){
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject){
let connection = new Connection; let connection = new Connection;
@ -41,12 +42,12 @@ function init( config = {
let gpgme = null; let gpgme = null;
if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ if (config.api_style && config.api_style === 'gpgme_openpgpjs'){
resolve( resolve(
new GpgME_openpgpmode(connection)); new GpgME_openpgpmode(connection, config));
} else { } else {
resolve(new GpgME(connection)); resolve(new GpgME(connection));
} }
} else { } else {
reject(GPGMEJS_Error('CONN_NO_CONNECT')); reject(gpgme_error('CONN_NO_CONNECT'));
} }
}; };
setTimeout(delayedreaction, 5); setTimeout(delayedreaction, 5);

View File

@ -31,7 +31,7 @@
partial and in need of concatenation partial and in need of concatenation
params: Array<String> Information that do not change throughout params: Array<String> Information that do not change throughout
the message the message
infos: Array<String> arbitrary information that may change infos: Array<*> arbitrary information that may result in a list
} }
} }
*/ */
@ -72,7 +72,55 @@ export const permittedOperations = {
type: ['plaintext'], type: ['plaintext'],
data: ['data'], data: ['data'],
params: ['base64', 'mime'], params: ['base64', 'mime'],
infos: ['info'] infos: [] // pending. Info about signatures and validity
//signature: [{Key Fingerprint, valid Boolean}]
}
},
/**
keyinfo: { // querying the Key's information.
required: ['fingerprint'],
anser: {
type: ['TBD'],
data: [],
params: ['hasSecret', 'isRevoked', 'isExpired', 'armored',
'timestamp', 'expires', 'pubkey_algo'],
infos: ['subkeys', 'userIds']
}*/
/**
listkeys:{
optional: ['with-secret', 'pattern'],
answer: {
type: ['TBD'], //Array of fingerprints?
infos: ['TBD'] //the property with infos
},
*/
/**
importkey: {
required: ['keyarmored'],
answer: {
type: ['TBD'],
infos: [''], // for each key if import was a success, if it was an update
}
},
*/
/**
deletekey: {
required: ['fingerprint'],
answer: {
type ['TBD'],
infos: [''] //success:true? in gpgme, an error NO_ERROR is returned
} }
} }
*/
/**
*get armored secret different treatment from keyinfo!
*/
/**
* TBD key modification requests?
*/
} }