aboutsummaryrefslogtreecommitdiffstats
path: root/lang/js/src
diff options
context:
space:
mode:
Diffstat (limited to 'lang/js/src')
-rw-r--r--lang/js/src/Config.js31
-rw-r--r--lang/js/src/Connection.js241
-rw-r--r--lang/js/src/Errors.js129
-rw-r--r--lang/js/src/Helpers.js103
-rw-r--r--lang/js/src/Key.js244
-rw-r--r--lang/js/src/Keyring.js162
-rw-r--r--lang/js/src/Message.js196
-rw-r--r--lang/js/src/gpgmejs.js192
-rw-r--r--lang/js/src/index.js86
-rw-r--r--lang/js/src/permittedOperations.js217
10 files changed, 1601 insertions, 0 deletions
diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js
new file mode 100644
index 00000000..e85bbb82
--- /dev/null
+++ b/lang/js/src/Config.js
@@ -0,0 +1,31 @@
+/* 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+
+ */
+
+export const availableConf = {
+ null_expire_is_never: [true, false],
+ // cachedKeys: Some Key info will not be queried on each invocation,
+ // manual refresh by Key.refresh()
+ cachedKeys: [true, false]
+};
+
+export const defaultConf = {
+ null_expire_is_never: false,
+ cachedKeys: false
+}; \ No newline at end of file
diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js
new file mode 100644
index 00000000..9c2a6428
--- /dev/null
+++ b/lang/js/src/Connection.js
@@ -0,0 +1,241 @@
+/* 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+
+ */
+
+/**
+ * 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";
+
+/**
+ * A Connection handles the nativeMessaging interaction.
+ */
+export class Connection{
+
+ constructor(){
+ this.connect();
+ let me = this;
+ }
+
+ /**
+ * (Simple) Connection check.
+ * @returns {Boolean} true if the onDisconnect event has not been fired.
+ * Please note that the event listener of the port takes some time
+ * (5 ms seems enough) to react after the port is created. Then this will
+ * return undefined
+ */
+ get isConnected(){
+ return this._isConnected;
+ }
+
+ /**
+ * Immediately closes the open port.
+ */
+ disconnect() {
+ if (this._connection){
+ this._connection.disconnect();
+ }
+ }
+
+ /**
+ * Opens a nativeMessaging port.
+ */
+ connect(){
+ if (this._isConnected === true){
+ gpgme_error('CONN_ALREADY_CONNECTED');
+ } else {
+ this._isConnected = true;
+ this._connection = chrome.runtime.connectNative('gpgmejson');
+ let me = this;
+ this._connection.onDisconnect.addListener(
+ function(){
+ me._isConnected = false;
+ }
+ );
+ }
+ }
+
+ /**
+ * Sends a message and resolves with the answer.
+ * @param {GPGME_Message} message
+ * @returns {Promise<Object>} the gnupg answer, or rejection with error
+ * information.
+ */
+ post(message){
+ if (!this.isConnected){
+ return Promise.reject(gpgme_error('CONN_DISCONNECTED'));
+ }
+ 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]);
+ }
+ 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
new file mode 100644
index 00000000..bfe3a2f4
--- /dev/null
+++ b/lang/js/src/Errors.js
@@ -0,0 +1,129 @@
+/* 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+
+ */
+
+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/Helpers.js b/lang/js/src/Helpers.js
new file mode 100644
index 00000000..fd0e7200
--- /dev/null
+++ b/lang/js/src/Helpers.js
@@ -0,0 +1,103 @@
+/* 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_error } from "./Errors";
+import { GPGME_Key } from "./Key";
+
+/**
+ * Tries to return an array of fingerprints, either from input fingerprints or
+ * from Key objects
+ * @param {Key |Array<Key>| GPGME_Key | Array<GPGME_Key>|String|Array<String>} input
+ * @returns {Array<String>} Array of fingerprints.
+ */
+
+export function toKeyIdArray(input){
+ if (!input){
+ gpgme_error('MSG_NO_KEYS');
+ return [];
+ }
+ 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 {
+ gpgme_error('MSG_NOT_A_FPR');
+ }
+ } 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 {
+ gpgme_error('MSG_NOT_A_FPR');
+ }
+ } else {
+ return gpgme_error('PARAM_WRONG');
+ }
+ }
+ if (result.length === 0){
+ gpgme_error('MSG_NO_KEYS');
+ return [];
+ } else {
+ return result;
+ }
+};
+
+/**
+ * check if values are valid hexadecimal values of a specified length
+ * @param {*} key input value.
+ * @param {int} len the expected length of the value
+ */
+function hextest(key, len){
+ if (!key || typeof(key) !== "string"){
+ return false;
+ }
+ if (key.length !== len){
+ return false;
+ }
+ let regexp= /^[0-9a-fA-F]*$/i;
+ return regexp.test(key);
+};
+
+/**
+ * check if the input is a valid Hex string with a length of 40
+ */
+export function isFingerprint(string){
+ return hextest(string, 40);
+};
+/**
+ * TODO no usage; check if the input is a valid Hex string with a length of 16
+ */
+function isLongId(string){
+ return hextest(string, 16);
+};
+
+// TODO still not needed anywhere
+function isShortId(string){
+ return hextest(string, 8);
+};
diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js
new file mode 100644
index 00000000..075a190e
--- /dev/null
+++ b/lang/js/src/Key.js
@@ -0,0 +1,244 @@
+/* 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 { gpgme_error } from './Errors'
+import { createMessage } from './Message';
+import { permittedOperations } from './permittedOperations';
+import { Connection } from './Connection';
+
+
+export function createKey(fingerprint, parent){
+ if (!isFingerprint(fingerprint)){
+ return gpgme_error('PARAM_WRONG');
+ }
+ if ( parent instanceof Connection){
+ return new GPGME_Key(fingerprint, parent);
+ } else if ( parent.hasOwnProperty('connection') &&
+ parent.connection instanceof Connection){
+ return new GPGME_Key(fingerprint, parent.connection);
+ } else {
+ return gpgme_error('PARAM_WRONG');
+ }
+}
+
+export class GPGME_Key {
+
+ constructor(fingerprint, connection){
+ this.fingerprint = fingerprint;
+ this.connection = connection;
+ }
+
+ set connection(conn){
+ if (this._connection instanceof Connection) {
+ gpgme_error('CONN_ALREADY_CONNECTED');
+ } else if (conn instanceof Connection ) {
+ this._connection = conn;
+ }
+ }
+
+ get connection(){
+ if (!this._fingerprint){
+ return gpgme_error('KEY_INVALID');
+ }
+ if (!this._connection instanceof Connection){
+ return gpgme_error('CONN_NO_CONNECT');
+ } else {
+ return this._connection;
+ }
+ }
+
+ set fingerprint(fpr){
+ if (isFingerprint(fpr) === true && !this._fingerprint){
+ this._fingerprint = fpr;
+ }
+ }
+
+ get fingerprint(){
+ if (!this._fingerprint){
+ return gpgme_error('KEY_INVALID');
+ }
+ return this._fingerprint;
+ }
+
+ /**
+ * hasSecret returns true if a secret subkey is included in this Key
+ */
+ get hasSecret(){
+ return this.checkKey('secret');
+ }
+
+ get isRevoked(){
+ return this.checkKey('revoked');
+ }
+
+ get isExpired(){
+ return this.checkKey('expired');
+ }
+
+ get isDisabled(){
+ return this.checkKey('disabled');
+ }
+
+ get isInvalid(){
+ return this.checkKey('invalid');
+ }
+
+ get canEncrypt(){
+ return this.checkKey('can_encrypt');
+ }
+
+ get canSign(){
+ return this.checkKey('can_sign');
+ }
+
+ get canCertify(){
+ return this.checkKey('can_certify');
+ }
+
+ get canAuthenticate(){
+ return this.checkKey('can_authenticate');
+ }
+
+ get isQualified(){
+ return this.checkKey('is_qualified');
+ }
+
+ get armored(){
+ let msg = createMessage ('export_key');
+ msg.setParameter('armor', true);
+ if (msg instanceof Error){
+ return gpgme_error('KEY_INVALID');
+ }
+ this.connection.post(msg).then(function(result){
+ return result.data;
+ });
+ // TODO return value not yet checked. Should result in an armored block
+ // in correct encoding
+ }
+
+ /**
+ * 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 this.checkKey('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], this.connection);
+ if (subkey instanceof GPGME_Key){
+ resultset.push(subkey);
+ }
+ }
+ return Promise.resolve(resultset);
+ }, function(error){
+ //TODO this.checkKey fails
+ });
+ }
+
+ /**
+ * creation time stamp of the key
+ * @returns {Date|null} TBD
+ */
+ get timestamp(){
+ return this.checkKey('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 this.checkKey('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 this.checkKey('uids');
+ }
+
+ /**
+ * @returns {String} The public key algorithm supported by this subkey
+ */
+ get pubkey_algo(){
+ return this.checkKey('pubkey_algo');
+ }
+
+ /**
+ * generic function to query gnupg information on a key.
+ * @param {*} property The gpgme-json property to check.
+ * TODO: check if Promise.then(return)
+ */
+ checkKey(property){
+ if (!this._fingerprint){
+ return gpgme_error('KEY_INVALID');
+ }
+ return gpgme_error('NOT_YET_IMPLEMENTED');
+ // TODO: async is not what is to be ecpected from Key information :(
+ if (!property || typeof(property) !== 'string' ||
+ !permittedOperations['keyinfo'].hasOwnProperty(property)){
+ return gpgme_error('PARAM_WRONG');
+ }
+ let msg = createMessage ('keyinfo');
+ if (msg instanceof Error){
+ return gpgme_error('PARAM_WRONG');
+ }
+ msg.setParameter('fingerprint', this.fingerprint);
+ this.connection.post(msg).then(function(result, error){
+ if (error){
+ return gpgme_error('GNUPG_ERROR',error.msg);
+ } else if (result.hasOwnProperty(property)){
+ return result[property];
+ }
+ else if (property == 'secret'){
+ // TBD property undefined means "not true" in case of secret?
+ return false;
+ } else {
+ return gpgme_error('CONN_UNEXPECTED_ANSWER');
+ }
+ }, function(error){
+ return gpgme_error('GENERIC_ERROR');
+ });
+ }
+}; \ No newline at end of file
diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js
new file mode 100644
index 00000000..4596035a
--- /dev/null
+++ b/lang/js/src/Keyring.js
@@ -0,0 +1,162 @@
+/* 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 {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 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.<Array<GPGME_Key>>}
+ *
+ */
+ 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<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], me._connection);
+ if (newKey instanceof GPGME_Key){
+ resultset.push(newKey);
+ }
+ }
+ return Promise.resolve(resultset);
+ }, function(error){
+ //TODO error handling
+ });
+ }
+
+ /**
+ * @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<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);
+ }, function(error){
+ //TODO error handling
+ });
+ }
+
+};
diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js
new file mode 100644
index 00000000..932212a6
--- /dev/null
+++ b/lang/js/src/Message.js
@@ -0,0 +1,196 @@
+/* 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 { permittedOperations } from './permittedOperations'
+import { gpgme_error } from './Errors'
+
+export function createMessage(operation){
+ if (typeof(operation) !== 'string'){
+ return gpgme_error('PARAM_WRONG');
+ }
+ if (permittedOperations.hasOwnProperty(operation)){
+ return new GPGME_Message(operation);
+ } else {
+ return gpgme_error('MSG_WRONG_OP');
+ }
+}
+
+/**
+ * Prepares a communication request. It checks operations and parameters in
+ * ./permittedOperations.
+ * @param {String} operation
+ */
+export class GPGME_Message {
+ //TODO getter
+
+ constructor(operation){
+ this.operation = operation;
+ this._expected = 'string';
+ }
+
+ set operation (op){
+ if (typeof(op) === "string"){
+ if (!this._msg){
+ this._msg = {};
+ }
+ if (!this._msg.op & permittedOperations.hasOwnProperty(op)){
+ this._msg.op = op;
+ }
+ }
+ }
+
+ get operation(){
+ return this._msg.op;
+ }
+
+ set expected(string){
+ if (string === 'base64'){
+ this._expected = 'base64';
+ }
+ }
+
+ get expected() {
+ if (this._expected === "base64"){
+ return this._expected;
+ }
+ return "string";
+ }
+
+ /**
+ * Sets a parameter for the message. Note that the operation has to be set
+ * first, to be able to check if the parameter is permittted
+ * @param {String} param Parameter to set
+ * @param {any} value Value to set //TODO: Some type checking
+ * @returns {Boolean} If the parameter was set successfully
+ */
+ setParameter(param,value){
+ if (!param || typeof(param) !== 'string'){
+ return gpgme_error('PARAM_WRONG');
+ }
+ let po = permittedOperations[this._msg.op];
+ if (!po){
+ return gpgme_error('MSG_WRONG_OP');
+ }
+ let poparam = null;
+ if (po.required.hasOwnProperty(param)){
+ poparam = po.required[param];
+ } else if (po.optional.hasOwnProperty(param)){
+ poparam = po.optional[param];
+ } else {
+ return gpgme_error('PARAM_WRONG');
+ }
+ let checktype = function(val){
+ switch(typeof(val)){
+ case 'string':
+ if (poparam.allowed.indexOf(typeof(val)) >= 0
+ && val.length > 0) {
+ return true;
+ }
+ return gpgme_error('PARAM_WRONG');
+ break;
+ case 'number':
+ if (
+ poparam.allowed.indexOf('number') >= 0
+ && isNaN(value) === false){
+ return true;
+ }
+ return gpgme_error('PARAM_WRONG');
+ break;
+ case 'boolean':
+ if (poparam.allowed.indexOf('boolean') >= 0){
+ return true;
+ }
+ return gpgme_error('PARAM_WRONG');
+ break;
+ case 'object':
+ if (Array.isArray(val)){
+ if (poparam.array_allowed !== true){
+ return gpgme_error('PARAM_WRONG');
+ }
+ for (let i=0; i < val.length; i++){
+ let res = checktype(val[i]);
+ if (res !== true){
+ return res;
+ }
+ }
+ if (val.length > 0) {
+ return true;
+ }
+ } else if (val instanceof Uint8Array){
+ if (poparam.allowed.indexOf('Uint8Array') >= 0){
+ return true;
+ }
+ return gpgme_error('PARAM_WRONG');
+ } else {
+ return gpgme_error('PARAM_WRONG');
+ }
+ break;
+ default:
+ return gpgme_error('PARAM_WRONG');
+ }
+ };
+ let typechecked = checktype(value);
+ if (typechecked !== true){
+ return typechecked;
+ }
+ if (poparam.hasOwnProperty('allowed_data')){
+ if (poparam.allowed_data.indexOf(value) < 0){
+ return gpgme_error('PARAM_WRONG');
+ }
+ }
+ this._msg[param] = value;
+ return true;
+ }
+
+ /**
+ * Check if the message has the minimum requirements to be sent, according
+ * to the definitions in permittedOperations
+ * @returns {Boolean}
+ */
+ get isComplete(){
+ if (!this._msg.op){
+ return false;
+ }
+ let reqParams = Object.keys(
+ permittedOperations[this._msg.op].required);
+ let msg_params = Object.keys(this._msg);
+ for (let i=0; i < reqParams.length; i++){
+ if (msg_params.indexOf(reqParams[i]) < 0){
+ console.log(reqParams[i] + ' missing');
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the prepared message with parameters and completeness checked
+ * @returns {Object|null} Object to be posted to gnupg, or null if
+ * incomplete
+ */
+ get message(){
+ if (this.isComplete === true){
+ return this._msg;
+ }
+ else {
+ return null;
+ }
+
+ }
+}
diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js
new file mode 100644
index 00000000..3aa5957a
--- /dev/null
+++ b/lang/js/src/gpgmejs.js
@@ -0,0 +1,192 @@
+/* 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 {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;
+ }
+
+ 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<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message
+ * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message
+ */
+ 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<Object>} 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);
+
+ }
+
+ 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
new file mode 100644
index 00000000..8527b3f3
--- /dev/null
+++ b/lang/js/src/index.js
@@ -0,0 +1,86 @@
+/* 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 } 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) {
+ 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/src/permittedOperations.js b/lang/js/src/permittedOperations.js
new file mode 100644
index 00000000..da46a1fd
--- /dev/null
+++ b/lang/js/src/permittedOperations.js
@@ -0,0 +1,217 @@
+/* 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+
+ */
+
+ /**
+ * Definition of the possible interactions with gpgme-json.
+ * operation: <Object>
+ required: Array<Object>
+ <String> name The name of the property
+ allowed: Array of allowed types. Currently accepted values:
+ ['number', 'string', 'boolean', 'Uint8Array']
+ array_allowed: Boolean. If the value can be an array of the above
+ allowed_data: <Array> If present, restricts to the given value
+ optional: Array<Object>
+ see 'required', with these parameters not being mandatory for a
+ complete message
+ pinentry: boolean If a pinentry dialog is expected, and a timeout of
+ 5000 ms would be too short
+ answer: <Object>
+ type: <String< The content type of answer expected
+ data: Array<String> The payload property of the answer. May be
+ partial and in need of concatenation
+ params: Array<String> Information that do not change throughout
+ the message
+ infos: Array<*> arbitrary information that may result in a list
+ }
+ }
+ */
+
+export const permittedOperations = {
+ encrypt: {
+ required: {
+ 'keys': {
+ allowed: ['string'],
+ array_allowed: true
+ },
+ 'data': {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ 'chunksize': {
+ allowed: ['number']
+ },
+ 'base64': {
+ allowed: ['boolean']
+ },
+ 'mime': {
+ allowed: ['boolean']
+ },
+ 'armor': {
+ allowed: ['boolean']
+ },
+ 'always-trust': {
+ allowed: ['boolean']
+ },
+ 'no-encrypt-to': {
+ allowed: ['string'],
+ array_allowed: true
+ },
+ 'no-compress': {
+ allowed: ['boolean']
+ },
+ 'throw-keyids': {
+ allowed: ['boolean']
+ },
+ 'want-address': {
+ allowed: ['boolean']
+ },
+ 'wrap': {
+ allowed: ['boolean']
+ },
+ },
+ answer: {
+ type: ['ciphertext'],
+ data: ['data'],
+ params: ['base64'],
+ infos: []
+ }
+ },
+
+ decrypt: {
+ pinentry: true,
+ required: {
+ 'data': {
+ allowed: ['string']
+ }
+ },
+ optional: {
+ 'protocol': {
+ allowed: ['string'],
+ allowed_data: ['cms', 'openpgp']
+ },
+ 'chunksize': {
+ allowed: ['number'],
+ },
+ 'base64': {
+ allowed: ['boolean']
+ }
+ },
+ answer: {
+ type: ['plaintext'],
+ data: ['data'],
+ params: ['base64', 'mime'],
+ infos: [] // TODO pending. Info about signatures and validity
+ //{
+ //signatures: [{
+ //Key : <String>Fingerprint,
+ //valid: <Boolean>
+ // }]
+ }
+ },
+ /** TBD: querying the Key's information (keyinfo)
+ TBD name: {
+ required: {
+ 'fingerprint': {
+ allowed: ['string']
+ },
+ },
+ answer: {
+ type: ['TBD'],
+ data: [],
+ params: ['hasSecret','isRevoked','isExpired','armored',
+ 'timestamp','expires','pubkey_algo'],
+ infos: ['subkeys', 'userIds']
+ // {'hasSecret': <Boolean>,
+ // 'isRevoked': <Boolean>,
+ // 'isExpired': <Boolean>,
+ // 'armored': <String>, // armored public Key block
+ // 'timestamp': <Number>, //
+ // 'expires': <Number>,
+ // 'pubkey_algo': TBD // TBD (optional?),
+ // 'userIds': Array<String>,
+ // 'subkeys': Array<String> Fingerprints of Subkeys
+ // }
+ }*/
+
+ /**
+ listkeys:{
+ required: {};
+ optional: {
+ 'with-secret':{
+ allowed: ['boolean']
+ },{
+ 'pattern': {
+ allowed: ['string']
+ }
+ },
+ answer: {
+ type: ['TBD'],
+ infos: ['TBD']
+ // keys: Array<String> Fingerprints representing the results
+ },
+ */
+
+ /**
+ importkey: {
+ required: {
+ 'keyarmored': {
+ allowed: ['string']
+ }
+ },
+ answer: {
+ type: ['TBD'],
+ infos: ['TBD'],
+ // for each key if import was a success,
+ // and if it was an update of preexisting key
+ }
+ },
+ */
+
+ /**
+ deletekey: {
+ pinentry: true,
+ required: {
+ 'fingerprint': {
+ allowed: ['string'],
+ // array_allowed: TBD Allow several Keys to be deleted at once?
+ },
+ optional: {
+ 'TBD' //Flag to delete secret Key ?
+ }
+ answer: {
+ type ['TBD'],
+ infos: ['']
+ // TBD (optional) Some kind of 'ok' if delete was successful.
+ }
+ }
+ */
+
+ /**
+ *TBD get armored secret different treatment from keyinfo!
+ * TBD key modification?
+ * encryptsign: TBD
+ * verify: TBD
+ */
+}