js: Key handling stubs, Error handling, refactoring
-- * Error handling: introduced GPGMEJS_Error class that handles errors at a more centralized and consistent position * src/Connection.js: The nativeMessaging port now opens per session instead of per message. Some methods were added that reflect this change - added methods disconnect() and reconnect() - added connection status query * src/gpgmejs.js - stub for key deletion - error handling - high level API for changing connection status * src/gpgmejs_openpgpjs.js - added stubs for Key/Keyring handling according to current state of discussion. It is still subject to change * src/Helpers.js - toKeyIdArray creates an array of KeyIds, now accepting fingerprints, GPGMEJS_Key objects and openpgp Key objects. * Key objects (src/Key.js) Querying information about a key directly from gnupg. Currently a stub, only the Key.fingerprint is functional. * Keyring queries (src/Keyring.js): Listing and searching keys. Currently a stub.
This commit is contained in:
parent
6ab25e40d9
commit
d62f66b1fb
@ -26,31 +26,19 @@ import { GPGME_Message } from "./Message";
|
||||
* expected.
|
||||
*/
|
||||
import { permittedOperations } from './permittedOperations'
|
||||
import { GPGMEJS_Error} from "./Errors"
|
||||
|
||||
/**
|
||||
* A Connection handles the nativeMessaging interaction.
|
||||
*/
|
||||
export class Connection{
|
||||
|
||||
/**
|
||||
* Opens and closes a port. Thus, it is made sure that the connection can
|
||||
* be used.
|
||||
* THIS BEHAVIOUR MAY CHANGE!
|
||||
* discussion is to keep a port alive as long as the context stays the same
|
||||
*
|
||||
* TODO returns nothing, but triggers exceptions if not successfull
|
||||
*/
|
||||
constructor(){
|
||||
this._connection = chrome.runtime.connectNative('gpgmejson');
|
||||
if (!this._connection){
|
||||
if (chrome.runtime.lastError){
|
||||
throw('NO_CONNECT_RLE');
|
||||
} else {
|
||||
throw('NO_CONNECT');
|
||||
}
|
||||
}
|
||||
this._flags = {}; // TODO general config
|
||||
this.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately closes the open port
|
||||
* Immediately closes the open port.
|
||||
*/
|
||||
disconnect() {
|
||||
if (this._connection){
|
||||
@ -58,27 +46,56 @@ export class Connection{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a nativeMessaging port.
|
||||
* returns nothing, but triggers errors if not successfull:
|
||||
* NO_CONNECT: connection not successfull, chrome.runtime.lastError may be
|
||||
* available
|
||||
* ALREADY_CONNECTED: There is already a connection present.
|
||||
*/
|
||||
connect(){
|
||||
if (this._connection){
|
||||
return new GPGMEJS_Error('ALREADY_CONNECTED');
|
||||
}
|
||||
this._connection = chrome.runtime.connectNative('gpgmejson');
|
||||
if (!this._connection){
|
||||
return new GPGMEJS_Error('NO_CONNECT');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the connection is established
|
||||
* TODO: some kind of ping to see if the other side responds as expected?
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get connected(){
|
||||
return this._connection ? true: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message and resolves with the answer.
|
||||
* @param {GPGME_Message} message
|
||||
* @returns {Promise<Object>} the gnupg answer, or rejection with error
|
||||
* information
|
||||
* TODO: better/more consistent error information
|
||||
* information.
|
||||
*/
|
||||
post(message){
|
||||
if (!message || !message instanceof GPGME_Message){
|
||||
return Promise.reject('ERR_NO_MSG');
|
||||
return Promise.reject(new GPGMEJS_Error('WRONGPARAM'));
|
||||
}
|
||||
if (message.isComplete !== true){
|
||||
return Promise.reject(new GPGMEJS_Error('MSG_INCOMPLETE'));
|
||||
}
|
||||
// let timeout = 5000; //TODO config
|
||||
let me = this;
|
||||
return new Promise(function(resolve, reject){
|
||||
let answer = new Answer(message.op);
|
||||
let answer = new Answer(message.operation);
|
||||
let listener = function(msg) {
|
||||
if (!msg){
|
||||
me._connection.onMessage.removeListener(listener)
|
||||
reject('EMPTY_ANSWER');
|
||||
reject(new GPGMEJS_Error('EMPTY_GPG_ANSWER'));
|
||||
} else if (msg.type === "error"){
|
||||
me._connection.onMessage.removeListener(listener)
|
||||
//TODO: GPGMEJS_Error?
|
||||
reject(msg.msg);
|
||||
} else {
|
||||
answer.add(msg);
|
||||
@ -92,12 +109,12 @@ export class Connection{
|
||||
};
|
||||
|
||||
me._connection.onMessage.addListener(listener);
|
||||
me._connection.postMessage(message);
|
||||
me._connection.postMessage(message.message);
|
||||
//TBD: needs to be aware if there is a pinentry pending
|
||||
// setTimeout(
|
||||
// function(){
|
||||
// me.disconnect();
|
||||
// reject('TIMEOUT');
|
||||
// reject(new GPGMEJS_Error('TIMEOUT', 5000));
|
||||
// }, timeout);
|
||||
});
|
||||
}
|
||||
@ -105,8 +122,8 @@ export class Connection{
|
||||
|
||||
/**
|
||||
* 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 return keys
|
||||
* the nativeMessaging communication.
|
||||
* @param {String} operation The operation, to look up validity of returning messages
|
||||
*/
|
||||
class Answer{
|
||||
|
||||
@ -115,9 +132,8 @@ class Answer{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Add the information to the answer
|
||||
* @param {Object} msg The message as received with nativeMessaging
|
||||
* TODO: "error" and "more" handling are not in here, but in post()
|
||||
*/
|
||||
add(msg){
|
||||
if (this._response === undefined){
|
||||
@ -130,9 +146,7 @@ class Answer{
|
||||
switch (key) {
|
||||
case 'type':
|
||||
if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){
|
||||
console.log( 'unexpected answer type: ' + msg.type);
|
||||
throw('UNEXPECTED_TYPE');
|
||||
|
||||
return new GPGMEJS_Error('UNEXPECTED_ANSWER');
|
||||
}
|
||||
break;
|
||||
case 'more':
|
||||
@ -151,7 +165,7 @@ class Answer{
|
||||
this._response[key] = msg[key];
|
||||
}
|
||||
else if (this._response[key] !== msg[key]){
|
||||
throw('UNEXPECTED_TYPE');
|
||||
return new GPGMEJS_Error('UNEXPECTED_ANSWER',msg[key]);
|
||||
}
|
||||
}
|
||||
//infos may be json objects etc. Not yet defined.
|
||||
@ -163,8 +177,7 @@ class Answer{
|
||||
this._response.push(msg[key]);
|
||||
}
|
||||
else {
|
||||
console.log('unexpected answer parameter: ' + key);
|
||||
throw('UNEXPECTED_PARAM');
|
||||
return new GPGMEJS_Error('UNEXPECTED_ANSWER', key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -172,7 +185,8 @@ class Answer{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the assembled message. TODO: does not care yet if completed.
|
||||
* @returns {Object} the assembled message.
|
||||
* TODO: does not care yet if completed.
|
||||
*/
|
||||
get message(){
|
||||
return this._response;
|
||||
|
148
lang/js/src/Errors.js
Normal file
148
lang/js/src/Errors.js
Normal file
@ -0,0 +1,148 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
// This is a preliminary collection of erors and warnings to be thrown and implemented.
|
||||
|
||||
// general idea: if throw , throw the NAME
|
||||
// return false || 'return' property
|
||||
|
||||
//TODO: Connection.NOCONNECT promise
|
||||
//connection.timeout: Be aware of pinentry
|
||||
|
||||
export class GPGMEJS_Error {
|
||||
|
||||
constructor(code = 'GENERIC_ERROR', details){
|
||||
let config = { //TODO TEMP
|
||||
debug: 'console', // |'alert'
|
||||
throw: 'default' // | 'always' | 'never'
|
||||
};
|
||||
let errors = { //TODO: someplace else
|
||||
//Connection errors
|
||||
'ALREADY_CONNECTED':{
|
||||
msg: 'The connection was already established. The action would overwrite the context',
|
||||
throw: true
|
||||
},
|
||||
'NO_CONNECT': {
|
||||
msg:'Connection with the nativeMessaging host could not be established.',
|
||||
throw: true
|
||||
},
|
||||
'EMPTY_GPG_ANSWER':{
|
||||
msg: 'The nativeMesaging answer was empty',
|
||||
throw: true
|
||||
},
|
||||
'TIMEOUT': {
|
||||
msg: 'A timeout was exceeded.',
|
||||
throw: false
|
||||
},
|
||||
|
||||
'UNEXPECTED_ANSWER': {
|
||||
msg: 'The answer from gnupg was not as expected',
|
||||
throw: true
|
||||
},
|
||||
|
||||
// Message/Data Errors
|
||||
|
||||
'NO_KEYS' : {
|
||||
msg: 'There were no valid keys provided.',
|
||||
throw: true
|
||||
},
|
||||
'NOT_A_FPR': {
|
||||
msg: 'The String is not an accepted fingerprint',
|
||||
throw: false
|
||||
},
|
||||
'MSG_INCOMPLETE': {
|
||||
msg: 'The Message did not match the minimum requirements for the interaction',
|
||||
throw: true
|
||||
},
|
||||
'EMPTY_MSG' : {
|
||||
msg: 'The Message has no data.',
|
||||
throw: true
|
||||
},
|
||||
'MSG_NODATA':{
|
||||
msg: 'The data sent is empty. This may be unintentional.',
|
||||
throw: false
|
||||
},
|
||||
'MSG_OP_PENDING': {
|
||||
msg: 'There is no operation specified yet. The parameter cannot be set',
|
||||
throw: false
|
||||
},
|
||||
'WRONG_OP': {
|
||||
msg: "The operation requested could not be found",
|
||||
throw: true
|
||||
},
|
||||
|
||||
//generic errors
|
||||
|
||||
'WRONGPARAM':{
|
||||
msg: 'invalid parameter was found',
|
||||
throw: true
|
||||
},
|
||||
'WRONGTYPE':{
|
||||
msg: 'invalid parameter type was found',
|
||||
throw: true
|
||||
},
|
||||
'NOT_IMPLEMENTED': {
|
||||
msg: 'A openpgpjs parameter was submitted that is not implemented',
|
||||
throw: true
|
||||
},
|
||||
'GENERIC_ERROR': {
|
||||
msg: 'Unspecified error',
|
||||
throw: true
|
||||
},
|
||||
|
||||
// hopefully temporary errors
|
||||
|
||||
'NOT_YET_IMPLEMENTED': {
|
||||
msg: 'Support of this is probable, but it is not implemented yet',
|
||||
throw: false
|
||||
}
|
||||
}
|
||||
if (!errors.hasOwnProperty(code)){
|
||||
throw('GENERIC_ERROR');
|
||||
}
|
||||
let msg = code;
|
||||
if (errors[code].msg !== undefined){
|
||||
msg = msg + ': ' + errors[code].msg;
|
||||
}
|
||||
if (details){
|
||||
msg = msg + ' ' + details;
|
||||
}
|
||||
if (config.debug === 'console'){
|
||||
console.log(msg);
|
||||
} else if (config.debug === 'alert'){
|
||||
alert(msg);
|
||||
}
|
||||
switch (config.throw) {
|
||||
case 'default':
|
||||
if (errors[code].throw === true){
|
||||
throw(code);
|
||||
}
|
||||
break;
|
||||
case 'always':
|
||||
throw(code);
|
||||
break;
|
||||
|
||||
case 'never':
|
||||
break;
|
||||
default:
|
||||
throw('GENERIC_ERROR');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { GPGMEJS_Error } from "./Errors";
|
||||
|
||||
/* gpgme.js - Javascript integration for gpgme
|
||||
* Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
|
||||
*
|
||||
@ -21,33 +23,46 @@
|
||||
/**
|
||||
* Tries to return an array of fingerprints, either from input fingerprints or
|
||||
* from Key objects
|
||||
* @param {String|Array<String>} input Input value.
|
||||
* @param {Key |Array<Key>| GPGME_Key | Array<GPGME_Key>|String|Array<String>} input
|
||||
* @param {Boolean} nocheck if set, an empty result is acceptable
|
||||
* @returns {Array<String>} Array of fingerprints.
|
||||
*/
|
||||
export function toKeyIdArray(input){
|
||||
|
||||
export function toKeyIdArray(input, nocheck){
|
||||
if (!input){
|
||||
return [];
|
||||
// TODO: Warning or error here? Did we expect something or is "nothing" okay?
|
||||
return (nocheck ===true)? [] : new GPGMEJS_Error('NO_KEYS');
|
||||
}
|
||||
if (input instanceof Array){
|
||||
let result = [];
|
||||
for (let i=0; i < input.length; i++){
|
||||
if (!Array.isArray(input)){
|
||||
input = [input];
|
||||
}
|
||||
let result = [];
|
||||
for (let i=0; i < input.length; i++){
|
||||
if (typeof(input[i]) === 'string'){
|
||||
if (isFingerprint(input[i]) === true){
|
||||
result.push(input[i]);
|
||||
} else {
|
||||
//TODO error?
|
||||
console.log('gpgmejs/Helpers.js Warning: '+
|
||||
input[i] +
|
||||
' is not a valid key fingerprint and will not be used');
|
||||
GPGMEJS_Error
|
||||
}
|
||||
} else if (typeof(input[i]) === 'object'){
|
||||
let fpr = '';
|
||||
if (input[i] instanceof GPGME_Key){
|
||||
fpr = input[i].fingerprint;
|
||||
} else if (input[i].hasOwnProperty(primaryKey) &&
|
||||
input[i].primaryKey.hasOwnProperty(getFingerprint)){
|
||||
fpr = input[i].primaryKey.getFingerprint();
|
||||
}
|
||||
if (isFingerprint(fpr) === true){
|
||||
result.push(fpr);
|
||||
}
|
||||
} else {
|
||||
return new GPGMEJS_Error('WRONGTYPE');
|
||||
}
|
||||
return result;
|
||||
} else if (isFingerprint(input) === true) {
|
||||
return [input];
|
||||
}
|
||||
console.log('gpgmejs/Helpers.js Warning: ' + input +
|
||||
' is not a valid key fingerprint and will not be used');
|
||||
return [];
|
||||
if (result.length === 0){
|
||||
return (nocheck===true)? [] : new GPGMEJS_Error('NO_KEYS');
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,13 +87,14 @@ function hextest(key, len){
|
||||
export function isFingerprint(string){
|
||||
return hextest(string, 40);
|
||||
};
|
||||
|
||||
//TODO needed anywhere?
|
||||
/**
|
||||
* check if the input is a valid Hex string with a length of 16
|
||||
*/
|
||||
function isLongId(string){
|
||||
return hextest(string, 16);
|
||||
};
|
||||
|
||||
//TODO needed anywhere?
|
||||
// TODO still not needed anywhere
|
||||
function isShortId(string){
|
||||
return hextest(string, 8);
|
||||
};
|
||||
|
201
lang/js/src/Key.js
Normal file
201
lang/js/src/Key.js
Normal file
@ -0,0 +1,201 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
/**
|
||||
* The key class allows to query the information defined in gpgme Key Objects
|
||||
* (see https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html)
|
||||
*
|
||||
* This is a stub, as the gpgme-json side is not yet implemented
|
||||
*
|
||||
*/
|
||||
|
||||
import {isFingerprint} from './Helpers'
|
||||
import {GPGMEJS_Error} from './Errors'
|
||||
|
||||
export class GPGME_Key {
|
||||
|
||||
constructor(fingerprint){
|
||||
if (isFingerprint(fingerprint) === true){
|
||||
this._fingerprint = fingerprint;
|
||||
} else {
|
||||
return new GPGMEJS_Error('WRONGPARAM', 'Key.js: invalid fingerprint');
|
||||
}
|
||||
}
|
||||
|
||||
get fingerprint(){
|
||||
return this._fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* hasSecret returns true if a secret subkey is included in this Key
|
||||
*/
|
||||
get hasSecret(){
|
||||
checkKey(this._fingerprint, 'secret').then( function(result){
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get isRevoked(){
|
||||
return checkKey(this._fingerprint, 'revoked');
|
||||
}
|
||||
|
||||
get isExpired(){
|
||||
return checkKey(this._fingerprint, 'expired');
|
||||
}
|
||||
|
||||
get isDisabled(){
|
||||
return checkKey(this._fingerprint, 'disabled');
|
||||
}
|
||||
|
||||
get isInvalid(){
|
||||
return checkKey(this._fingerprint, 'invalid');
|
||||
}
|
||||
|
||||
get canEncrypt(){
|
||||
return checkKey(this._fingerprint, 'can_encrypt');
|
||||
}
|
||||
|
||||
get canSign(){
|
||||
return checkKey(this._fingerprint, 'can_sign');
|
||||
}
|
||||
|
||||
get canCertify(){
|
||||
return checkKey(this._fingerprint, 'can_certify');
|
||||
}
|
||||
|
||||
get canAuthenticate(){
|
||||
return checkKey(this._fingerprint, 'can_authenticate');
|
||||
}
|
||||
|
||||
get isQualified(){
|
||||
return checkKey(this._fingerprint, 'is_qualified');
|
||||
}
|
||||
|
||||
get armored(){
|
||||
let me = this;
|
||||
return new Promise(function(resolve, reject){
|
||||
let conn = new Connection();
|
||||
conn.setFlag('armor', true);
|
||||
conn.post('export',{'fpr': me._fingerprint});
|
||||
});
|
||||
// TODO return value not yet checked. Should result in an armored block
|
||||
// in correct encoding
|
||||
// TODO openpgpjs also returns secKey if private = true?
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO returns true if this is the default key used to sign
|
||||
*/
|
||||
get isDefault(){
|
||||
throw('NOT_YET_IMPLEMENTED');
|
||||
}
|
||||
|
||||
/**
|
||||
* get the Key's subkeys as GPGME_Key objects
|
||||
* @returns {Array<GPGME_Key>}
|
||||
*/
|
||||
get subkeys(){
|
||||
return checkKey(this._fingerprint, 'subkeys').then(function(result){
|
||||
// TBD expecting a list of fingerprints
|
||||
if (!Array.isArray(result)){
|
||||
result = [result];
|
||||
}
|
||||
let resultset = [];
|
||||
for (let i=0; i < result.length; i++){
|
||||
let subkey = new GPGME_Key(result[i]);
|
||||
if (subkey instanceof GPGME_Key){
|
||||
resultset.push(subkey);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(resultset);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* creation time stamp of the key
|
||||
* @returns {Date|null} TBD
|
||||
*/
|
||||
get timestamp(){
|
||||
return checkKey(this._fingerprint, 'timestamp');
|
||||
//TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available.
|
||||
}
|
||||
|
||||
/**
|
||||
* The expiration timestamp of this key TBD
|
||||
* @returns {Date|null} TBD
|
||||
*/
|
||||
get expires(){
|
||||
return checkKey(this._fingerprint, 'expires');
|
||||
// TODO convert to Date; check for 0
|
||||
}
|
||||
|
||||
/**
|
||||
* getter name TBD
|
||||
* @returns {String|Array<String>} The user ids associated with this key
|
||||
*/
|
||||
get userIds(){
|
||||
return checkKey(this._fingerprint, 'uids');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String} The public key algorithm supported by this subkey
|
||||
*/
|
||||
get pubkey_algo(){
|
||||
return checkKey(this._fingerprint, 'pubkey_algo');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* generic function to query gnupg information on a key.
|
||||
* @param {*} fingerprint The identifier of the Keyring
|
||||
* @param {*} property The gpgme-json property to check
|
||||
*
|
||||
*/
|
||||
function checkKey(fingerprint, property){
|
||||
return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED'));
|
||||
|
||||
return new Promise(function(resolve, reject){
|
||||
if (!isFingerprint(fingerprint)){
|
||||
reject('not a fingerprint'); //TBD
|
||||
}
|
||||
let conn = new Connection();
|
||||
conn.post('getkey',{ // TODO not yet implemented in gpgme
|
||||
'fingerprint': this.fingerprint})
|
||||
.then(function(result){
|
||||
if (property !== undefined){
|
||||
if (result.hasOwnProperty(key)){
|
||||
resolve(result[property]);
|
||||
}
|
||||
else if (property == 'secret'){
|
||||
// property undefined means "not true" in case of secret
|
||||
resolve(false);
|
||||
} else {
|
||||
reject('ERR_INVALID_PROPERTY') //TBD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resolve(result);
|
||||
}, function(error){
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
151
lang/js/src/Keyring.js
Normal file
151
lang/js/src/Keyring.js
Normal file
@ -0,0 +1,151 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
import {GPGME_Message} from './Message'
|
||||
import {Connection} from './Connection'
|
||||
import {GPGME_Key} from './Key'
|
||||
import { isFingerprint, isLongId } from './Helpers';
|
||||
|
||||
export class GPGME_Keyring {
|
||||
constructor(){
|
||||
this.reconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)-establishes the connection
|
||||
* TODO TEMP: should we better use the connection of our parent,
|
||||
* which we do not control?
|
||||
*/
|
||||
reconnect(){
|
||||
if (!this._connection || ! this._connection instanceof Connection){
|
||||
this._connection = new Connection;
|
||||
} else {
|
||||
this._connection.disconnect();
|
||||
this._connection.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds
|
||||
* @param {Boolean} (optional) Include listing of secret keys
|
||||
* @returns {Promise.<Array<GPGME_Key>>}
|
||||
*
|
||||
*/
|
||||
getKeys(pattern, include_secret){
|
||||
let msg = new GPGME_Message;
|
||||
msg.operation = 'listkeys';
|
||||
if (pattern && typeof(pattern) === 'string'){
|
||||
msg.setParameter('pattern', pattern);
|
||||
}
|
||||
if (include_secret){
|
||||
msg.setParameter('with-secret', true);
|
||||
}
|
||||
|
||||
this._connection.post(msg).then(function(result){
|
||||
let fpr_list = [];
|
||||
let resultset = [];
|
||||
if (!Array.isArray(result.keys)){
|
||||
//TODO check assumption keys = Array<String fingerprints>
|
||||
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]);
|
||||
if (newKey instanceof GPGME_Key){
|
||||
resultset.push(newKey);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(resultset);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.defaultKey Only Keys marked as Default Keys
|
||||
* @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.expired Expired Keys only
|
||||
* @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds
|
||||
* @returns {Promise Array<GPGME_Key>}
|
||||
*
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
@ -18,21 +18,24 @@
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
import { permittedOperations } from './permittedOperations'
|
||||
|
||||
import { GPGMEJS_Error } from './Errors'
|
||||
export class GPGME_Message {
|
||||
//TODO getter
|
||||
|
||||
constructor(){
|
||||
constructor(operation){
|
||||
if (operation){
|
||||
this.operation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the operation this message will have
|
||||
* @param {String} operation Mus be defined in permittedOperations
|
||||
* @param {String} operation Must be defined in permittedOperations
|
||||
* TODO: move to constructor?
|
||||
*/
|
||||
set operation (operation){
|
||||
if (!operation || typeof(operation) !== 'string'){
|
||||
throw('ERR_WRONG_PARAM');
|
||||
return new GPGMEJS_Error('WRONGPARAM');
|
||||
}
|
||||
if (operation in permittedOperations){
|
||||
if (!this._msg){
|
||||
@ -40,10 +43,14 @@ export class GPGME_Message {
|
||||
}
|
||||
this._msg.op = operation;
|
||||
} else {
|
||||
throw('ERR_NOT_IMPLEMENTED');
|
||||
return new GPGMEJS_Error('WRONG_OP');
|
||||
}
|
||||
}
|
||||
|
||||
get operation(){
|
||||
return this._msg.op;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -53,25 +60,20 @@ export class GPGME_Message {
|
||||
*/
|
||||
setParameter(param,value){
|
||||
if (!param || typeof(param) !== 'string'){
|
||||
throw('ERR_WRONG_PARAM');
|
||||
return new GPGMEJS_Error('WRONGPARAM', 'type check failed');
|
||||
}
|
||||
if (!this._msg || !this._msg.op){
|
||||
console.log('There is no operation specified yet. '+
|
||||
'The parameter cannot be set');
|
||||
return false;
|
||||
return new GPGMEJS_Error('MSG_OP_PENDING');
|
||||
}
|
||||
let po = permittedOperations[this._msg.op];
|
||||
if (!po){
|
||||
throw('LAZY_PROGRAMMER');
|
||||
//TODO
|
||||
return false;
|
||||
return new GPGMEJS_Error('WRONG_OP', param);
|
||||
}
|
||||
if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){
|
||||
this._msg[param] = value;
|
||||
return true;
|
||||
}
|
||||
console.log('' + param + ' is invalid and could not be set');
|
||||
return false;
|
||||
return new GPGMEJS_Error('WRONGPARAM', param);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,7 +87,9 @@ export class GPGME_Message {
|
||||
}
|
||||
let reqParams = permittedOperations[this._msg.op].required;
|
||||
for (let i=0; i < reqParams.length; i++){
|
||||
if (!reqParams[i] in this._msg){
|
||||
|
||||
if (!this._msg.hasOwnProperty(reqParams[i])){
|
||||
console.log(reqParams[i] + 'missing');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -21,26 +21,54 @@
|
||||
import {Connection} from "./Connection"
|
||||
import {GPGME_Message} from './Message'
|
||||
import {toKeyIdArray} from "./Helpers"
|
||||
import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors"
|
||||
|
||||
export class GpgME {
|
||||
/**
|
||||
* initial check if connection si successfull. Will throw ERR_NO_CONNECT or
|
||||
* ERR_NO_CONNECT_RLE (if chrome.runtime.lastError is available) if the
|
||||
* connection fails.
|
||||
* TODO The connection to the nativeMessaging host will, for now, be closed
|
||||
* after each interaction. Session management with gpg_agent is TBD.
|
||||
* initializes GpgME by opening a nativeMessaging port
|
||||
* TODO: add configuration
|
||||
*/
|
||||
constructor(){
|
||||
let conn = new Connection();
|
||||
// this.keyring = new Keyring(); TBD
|
||||
// TODO config, e.g.
|
||||
this.configuration = {
|
||||
null_expire_is_never: true
|
||||
};
|
||||
conn.disconnect();
|
||||
constructor(configuration = {
|
||||
null_expire_is_never: false
|
||||
}){
|
||||
this._connection = new Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the nativeApp connection
|
||||
*/
|
||||
reconnect(){
|
||||
if (!this._connection || ! this._connection instanceof Connection){
|
||||
this._connection = new Connection;
|
||||
} else {
|
||||
this._connection.disconnect();
|
||||
this._connection.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* inmediately tries to destroy the nativeMessaging connection.
|
||||
* TODO: may not be included in final API, as it is redundant.
|
||||
* For now, it just serves paranoia
|
||||
*/
|
||||
disconnect(){
|
||||
if (this._connection){
|
||||
this._connection.disconnect();
|
||||
this._connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tests the nativeApp connection
|
||||
*/
|
||||
get connected(){
|
||||
if (!this._connection || ! this._connection instanceof Connection){
|
||||
return false;
|
||||
}
|
||||
return this._connection.connected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array
|
||||
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message
|
||||
@ -62,14 +90,7 @@ export class GpgME {
|
||||
if (wildcard === true){msg.setParameter('throw-keyids', true);
|
||||
};
|
||||
|
||||
if (msg.isComplete === true) {
|
||||
let conn = new Connection();
|
||||
return (conn.post(msg.message));
|
||||
}
|
||||
else {
|
||||
return Promise.reject('NO_CONNECT');
|
||||
//TODO
|
||||
}
|
||||
return (this._connection.post(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,22 +106,47 @@ export class GpgME {
|
||||
decrypt(data){
|
||||
|
||||
if (data === undefined){
|
||||
throw('ERR_EMPTY_MSG');
|
||||
return Promise.reject(new GPGMEJS_Error ('EMPTY_MSG'));
|
||||
}
|
||||
let msg = new GPGME_Message;
|
||||
msg.operation = 'decrypt';
|
||||
putData(msg, data);
|
||||
// TODO: needs proper EOL to be decrypted.
|
||||
return this._connection.post(msg);
|
||||
|
||||
if (msg.isComplete === true){
|
||||
let conn = new Connection();
|
||||
return conn.post(msg.message);
|
||||
}
|
||||
else {
|
||||
return Promise.reject('NO_CONNECT');
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
deleteKey(key, delete_secret = false, no_confirm = false){
|
||||
return Promise.reject(new GPGMEJS_Error ('NOT_YET_IMPLEMENTED'));
|
||||
let msg = new GPGME_Message;
|
||||
msg.operation = 'deletekey';
|
||||
let key_arr = toKeyIdArray(key);
|
||||
if (key_arr.length !== 1){
|
||||
throw('TODO');
|
||||
//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
|
||||
}
|
||||
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(new GPGMEJS_Error);
|
||||
// INV_VALUE,
|
||||
// GPG_ERR_NO_PUBKEY,
|
||||
// GPG_ERR_AMBIGUOUS_NAME,
|
||||
// GPG_ERR_CONFLICT
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +158,7 @@ export class GpgME {
|
||||
*/
|
||||
function putData(message, data){
|
||||
if (!message || !message instanceof GPGME_Message ) {
|
||||
throw('NO_MESSAGE_OBJECT');
|
||||
return new GPGMEJS_Error('WRONGPARAM');
|
||||
}
|
||||
if (!data){
|
||||
//TODO Debug only! No data is legitimate
|
||||
@ -126,6 +172,6 @@ function putData(message, data){
|
||||
message.setParameter('base64', false);
|
||||
message.setParameter('data', data);
|
||||
} else {
|
||||
throw('ERR_WRONG_TYPE');
|
||||
return new GPGMEJS_Error('WRONGPARAM');
|
||||
}
|
||||
}
|
@ -25,13 +25,18 @@
|
||||
*/
|
||||
|
||||
import { GpgME } from "./gpgmejs";
|
||||
// import {Keyring} from "./Keyring" TODO
|
||||
|
||||
import {GPGME_Keyring} from "./Keyring"
|
||||
import { GPGME_Key } from "./Key";
|
||||
import { isFingerprint } from "./Helpers"
|
||||
import { GPGMEJS_Error } from './Errors'
|
||||
|
||||
export class GpgME_openPGPCompatibility {
|
||||
|
||||
constructor(){
|
||||
this._gpgme = new GpgME;
|
||||
this._gpgme = new GpgME({
|
||||
null_expire_is_never: false
|
||||
});
|
||||
this.Keyring = this.initKeyring();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,15 +72,14 @@ export class GpgME_openPGPCompatibility {
|
||||
|| signature !== null
|
||||
|| returnSessionKey !== null
|
||||
|| date !== null){
|
||||
throw('NOT_IMPLEMENTED');
|
||||
return Promise.reject(new GPMGEJS_Error('NOT_IMPLEMENTED'));
|
||||
}
|
||||
if ( privateKeys
|
||||
|| filename
|
||||
|| compression
|
||||
|| armor === false
|
||||
|| detached == true){
|
||||
console.log('may be implemented later');
|
||||
throw('NOT_YET_IMPLEMENTED');
|
||||
return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED'));
|
||||
}
|
||||
return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard);
|
||||
}
|
||||
@ -103,16 +107,14 @@ export class GpgME_openPGPCompatibility {
|
||||
if (passwords !== undefined
|
||||
|| sessionKeys
|
||||
|| date){
|
||||
|
||||
throw('NOT_IMPLEMENTED');
|
||||
return Promise.reject(new GPGMEJS_Error('NOT_IMPLEMENTED'));
|
||||
}
|
||||
if ( privateKeys
|
||||
|| publicKeys
|
||||
|| format !== 'utf8'
|
||||
|| signature
|
||||
){
|
||||
console.log('may be implemented later');
|
||||
throw('NOT_YET_IMPLEMENTED');
|
||||
return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED'));
|
||||
}
|
||||
return this.GpgME.decrypt(message);
|
||||
// TODO: translate between:
|
||||
@ -126,31 +128,74 @@ export class GpgME_openPGPCompatibility {
|
||||
// mime: A Boolean indicating whether the data is a MIME object.
|
||||
// info: An optional object with extra information.
|
||||
}
|
||||
initKeyring(){
|
||||
return new GPGME_Keyring_openPGPCompatibility;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object | String} Key Either a (presumably openpgp Key) Object with a
|
||||
* primaryKeyproperty and a method getFingerprint, or a string.
|
||||
* @returns {String} Unchecked string value claiming to be a fingerprint
|
||||
* TODO: gpgmejs checks again, so it's okay here.
|
||||
* Translation layer offering basic Keyring API to be used in Mailvelope.
|
||||
* It may still be changed/expanded/merged with GPGME_Keyring
|
||||
*/
|
||||
function translateKeyInput(Key){
|
||||
if (!Key){
|
||||
return [];
|
||||
class GPGME_Keyring_openPGPCompatibility {
|
||||
constructor(){
|
||||
this._gpgme_keyring = new GPGME_Keyring;
|
||||
}
|
||||
if (!Array.isArray(Key)){
|
||||
Key = [Key];
|
||||
|
||||
/**
|
||||
* Returns a GPGME_Key Object for each Key in the gnupg Keyring. This
|
||||
* includes keys openpgpjs considers 'private' (usable for signing), with
|
||||
* the difference that Key.armored will NOT contain any secret information.
|
||||
* Please also note that a GPGME_Key does not offer full openpgpjs- Key
|
||||
* compatibility.
|
||||
* @returns {Array<GPGME_Key>} with the objects offering at least:
|
||||
* @property {String} armored The armored key block (does not include secret blocks)
|
||||
* @property {Boolean} hasSecret Indicator if a private/secret key exists
|
||||
* @property {Boolean} isDefault Indicator if private key exists and is the default key in this keyring
|
||||
* @property {String} fingerprint The fingerprint identifying this key
|
||||
* //TODO: Check if IsDefault is also always hasSecret
|
||||
*/
|
||||
getPublicKeys(){
|
||||
return this._gpgme_keyring.getKeys(null, true);
|
||||
}
|
||||
let resultslist = [];
|
||||
for (let i=0; i < Key.length; i++){
|
||||
if (typeof(Key[i]) === 'string'){
|
||||
resultslist.push(Key);
|
||||
} else if (
|
||||
Key[i].hasOwnProperty(primaryKey) &&
|
||||
Key[i].primaryKey.hasOwnProperty(getFingerprint)){
|
||||
resultslist.push(Key[i].primaryKey.getFingerprint());
|
||||
|
||||
/**
|
||||
* Returns the Default Key used for crypto operation in gnupg.
|
||||
* Please note that the armored property does not contained secret key blocks,
|
||||
* despite secret blocks being part of the key itself.
|
||||
* @returns {Promise <GPGME_Key>}
|
||||
*/
|
||||
getDefaultKey(){
|
||||
this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){
|
||||
if (result.length === 1){
|
||||
return Promise.resolve(result[0]);
|
||||
}
|
||||
else {
|
||||
// TODO: Can there be "no default key"?
|
||||
// TODO: Can there be several default keys?
|
||||
return new GPGMEJS_Error; //TODO
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a Key
|
||||
* @param {Object} Object identifying key
|
||||
* @param {String} key.fingerprint - fingerprint of the to be deleted key
|
||||
* @param {Boolean} key.secret - indicator if private key should be deleted as well
|
||||
|
||||
* @returns {Promise.<Array.<undefined>, Error>} TBD: Not sure what is wanted
|
||||
TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint
|
||||
TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists
|
||||
*/
|
||||
deleteKey(key){
|
||||
if (typeof(key) !== "object"){
|
||||
return Promise.reject(new GPGMEJS_Error('WRONGPARAM'));
|
||||
}
|
||||
if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){
|
||||
return Promise.reject(new GPGMEJS_Error('WRONGPARAM'));
|
||||
}
|
||||
let key_to_delete = new GPGME_Key(key.fingerprint);
|
||||
return key_to_delete.deleteKey(key.secret);
|
||||
}
|
||||
return resultslist;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user