diff --git a/lang/js/jsdoc.conf b/lang/js/jsdoc.conf index 12ae35e9..976f4e41 100644 --- a/lang/js/jsdoc.conf +++ b/lang/js/jsdoc.conf @@ -1,24 +1,24 @@ { "tags": { "allowUnknownTags": false, "dictionaries": ["jsdoc"] }, "source": { - "include": ["./src"], + "include": ["jsdoc_index.md", "./src"], "includePattern": ".+\\.js(doc|x)?$", "excludePattern": "(^|\\/|\\\\)_" }, "opts":{ "destination": "./doc/", "recurse": true }, "sourceType": "module", "plugins": [], "templates": { "cleverLinks": false, "monospaceLinks": false, "default": { "outputSourceFiles": true } } } \ No newline at end of file diff --git a/lang/js/jsdoc_index.md b/lang/js/jsdoc_index.md new file mode 100644 index 00000000..b7371ad5 --- /dev/null +++ b/lang/js/jsdoc_index.md @@ -0,0 +1,50 @@ +Using gpgme.js +--------------- +At first, make sure that the environment you want to use gpgme.js in has access +and permissions for nativeMessaging, and gpgme-json installed. For details, +see the README. + +The library itself is started via the {@link init} method. This will test the +nativeMessaging connection, and then resolve into an Object offering +the top level API: + +* [encrypt]{@link GpgME#encrypt} +* [decrypt]{@link GpgME#decrypt} +* [sign]{@link GpgME#sign} +* [verify]{@link GpgME#verify} +* [Keyring]{@link GPGME_Keyring} + +``` +gpgmejs.init() + .then(function(GPGME) { + // using GPGME + }, function(error){ + // error handling; + }) +``` + +All methods that require communication with nativeMessaging are asynchronous, +using Promises. Rejections will be instances of {@link GPGME_Error}. + +An exaeption are Keys, which can be initialized in a 'sync' mode, allowing them +to be cached and used synchronously until manually refreshed. + +Keyring and Keys +---------------- +The gnupg keys can be accessed via the [Keyring]{@link GPGME_Keyring}. + +The Keyring offers the methods for accessing information on all Keys known to +gnupg. + +**Due to security constraints, the javascript-binding currently only offers +limited support for secret-Key interaction.** + +The existance of secret Keys is not secret, and those secret Keys can be used +for signing, but Operations that may expose, modify or delete secret Keys are +not supported. + +* [getKeysArmored]{@link GPGME_Keyring#getKeysArmored} +* [getKeys]{@link GPGME_Keyring#getKeys} +* [getDefaultKey]{@link GPGME_Keyring#getDefaultKey} +* [generateKey]{@link GPGME_Keyring#generateKey} +* [deleteKey]{@link GPGME_Keyring#deleteKey} diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index a421985a..4055da6a 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,311 +1,316 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ /* global chrome */ import { permittedOperations } from './permittedOperations'; import { gpgme_error } from './Errors'; import { GPGME_Message, createMessage } from './Message'; import { decode, atobArray, Utf8ArrayToStr } from './Helpers'; /** * A Connection handles the nativeMessaging interaction via a port. As the * protocol only allows up to 1MB of message sent from the nativeApp to the * browser, the connection will stay open until all parts of a communication * are finished. For a new request, a new port will open, to avoid mixing * contexts. * @class + * @private */ export class Connection{ constructor (){ this._connection = chrome.runtime.connectNative('gpgmejson'); } /** * Immediately closes an open port. */ disconnect () { if (this._connection){ this._connection.disconnect(); this._connection = null; } } /** * @typedef {Object} backEndDetails * @property {String} gpgme Version number of gpgme * @property {Array} info Further information about the backend * and the used applications (Example: + *
     * {
     *          "protocol":     "OpenPGP",
     *          "fname":        "/usr/bin/gpg",
     *          "version":      "2.2.6",
     *          "req_version":  "1.4.0",
     *          "homedir":      "default"
     * }
+    * 
*/ /** * Retrieves the information about the backend. * @param {Boolean} details (optional) If set to false, the promise will * just return if a connection was successful. * @returns {Promise|Promise} Details from the * backend * @async */ checkConnection (details = true){ const msg = createMessage('version'); if (details === true) { return this.post(msg); } else { let me = this; return new Promise(function (resolve) { Promise.race([ me.post(msg), new Promise(function (resolve, reject){ setTimeout(function (){ reject(gpgme_error('CONN_TIMEOUT')); }, 500); }) ]).then(function (){ // success resolve(true); }, function (){ // failure resolve(false); }); }); } } /** - * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It + * Sends a {@link GPGME_Message} via the nativeMessaging port. It * resolves with the completed answer after all parts have been * received and reassembled, or rejects with an {@link GPGME_Error}. * * @param {GPGME_Message} message - * @returns {Promise} The collected answer + * @returns {Promise<*>} The collected answer, depending on the messages' + * operation + * @private * @async */ post (message){ if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); return Promise.reject(gpgme_error( 'PARAM_WRONG', 'Connection.post')); } if (message.isComplete() !== true){ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let chunksize = message.chunksize; const me = this; return new Promise(function (resolve, reject){ let answer = new Answer(message); let listener = function (msg) { if (!msg){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else { let answer_result = answer.collect(msg); if (answer_result !== true){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(answer_result); } else { if (msg.more === true){ me._connection.postMessage({ 'op': 'getmore', 'chunksize': chunksize }); } else { me._connection.onMessage.removeListener(listener); me._connection.disconnect(); const message = answer.getMessage(); if (message instanceof Error){ reject(message); } else { resolve(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 (){ me._connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); } ]).then(function (result){ return result; }, function (reject){ if (!(reject instanceof Error)) { me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; } }); } }); } } /** * A class for answer objects, checking and processing the return messages of * the nativeMessaging communication. - * @protected + * @private */ class Answer{ /** * @param {GPGME_Message} message */ constructor (message){ this._operation = message.operation; this._expected = message.expected; this._response_b64 = null; } get operation (){ return this._operation; } get expected (){ return this._expected; } /** * Adds incoming base64 encoded data to the existing response * @param {*} msg base64 encoded data. * @returns {Boolean} * * @private */ collect (msg){ if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (!this._response_b64){ this._response_b64 = msg.response; return true; } else { this._response_b64 += msg.response; return true; } } /** * Decodes and verifies the base64 encoded answer data. Verified against * {@link permittedOperations}. * @returns {Object} The readable gpnupg answer */ getMessage (){ if (this._response_b64 === null){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } let _decodedResponse = JSON.parse(atob(this._response_b64)); let _response = { format: 'ascii' }; let messageKeys = Object.keys(_decodedResponse); 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 (_decodedResponse.type === 'error'){ return (gpgme_error('GNUPG_ERROR', decode(_decodedResponse.msg))); } else if (poa.type.indexOf(_decodedResponse.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; } case 'base64': { break; } case 'msg': { if (_decodedResponse.type === 'error'){ return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); } break; } default: { let answerType = null; if (poa.payload && poa.payload.hasOwnProperty(key)){ answerType = 'p'; } else if (poa.info && poa.info.hasOwnProperty(key)){ answerType = 'i'; } if (answerType !== 'p' && answerType !== 'i'){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (answerType === 'i') { if ( typeof (_decodedResponse[key]) !== poa.info[key] ){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } _response[key] = decode(_decodedResponse[key]); } else if (answerType === 'p') { if (_decodedResponse.base64 === true && poa.payload[key] === 'string' ) { if (this.expected === 'uint8'){ _response[key] = atobArray(_decodedResponse[key]); _response.format = 'uint8'; } else if (this.expected === 'base64'){ _response[key] = _decodedResponse[key]; _response.format = 'base64'; } else { // no 'expected' _response[key] = Utf8ArrayToStr( atobArray(_decodedResponse[key])); _response.format = 'string'; } } else if (poa.payload[key] === 'string') { _response[key] = _decodedResponse[key]; } else { // fallthrough, should not be reached // (payload is always string) return gpgme_error('CONN_UNEXPECTED_ANSWER'); } } break; } } } return _response; } } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 145c3a59..2f66c83d 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,173 +1,177 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ /** * Listing of all possible error codes and messages of a {@link GPGME_Error}. */ export const err_list = { // Connection 'CONN_NO_CONNECT': { msg:'Connection with the nativeMessaging host could not be' + ' established.', type: 'error' }, 'CONN_EMPTY_GPG_ANSWER':{ msg: 'The nativeMessaging answer was empty.', type: 'error' }, 'CONN_TIMEOUT': { msg: 'A connection timeout was exceeded.', type: 'error' }, 'CONN_UNEXPECTED_ANSWER': { msg: 'The answer from gnupg was not as expected.', type: 'error' }, 'CONN_ALREADY_CONNECTED':{ msg: 'A connection was already established.', type: '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' }, 'KEY_NOKEY': { msg:'This key does not exist in GPG', type: 'error' }, 'KEY_NO_INIT': { msg:'This property has not been retrieved yet from GPG', type: 'error' }, 'KEY_ASYNC_ONLY': { msg: 'This property cannot be used in synchronous calls', type: 'error' }, 'KEY_NO_DEFAULT': { msg:'A default key could not be established. Please check yout gpg ' + 'configuration', type: 'error' }, 'SIG_WRONG': { msg:'A malformed signature was created', type: 'error' }, 'SIG_NO_SIGS': { msg:'There were no signatures found', type: 'error' }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', type: 'error' }, 'DECODE_FAIL': { msg: 'Decoding failed due to unexpected data', 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 {@link GPGME_Error} 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' + * @param {String} code Error code as defined in {@link err_list}. + * @param {String} info Possible additional error message to pass through. + * Currently used for errors sent as answer by gnupg via a native Message port * @returns {GPGME_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'){ // eslint-disable-next-line no-console // 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'); } } /** * An error class with additional info about the origin of the error, as string + * It is created by {@link gpgme_error}, and its' codes are defined in + * {@link err_list}. + * * @property {String} code Short description of origin and type of the error * @property {String} msg Additional info - * @class * @protected + * @class * @extends Error */ class GPGME_Error extends Error{ constructor (code = 'GENERIC_ERROR', msg=''){ if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ super(err_list[code].msg + '--' + msg); } else { super(err_list[code].msg); } } else { super(err_list['GENERIC_ERROR'].msg); } this._code = code; } 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 index 952c09fc..f370fe60 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -1,213 +1,219 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ import { gpgme_error } from './Errors'; /** - * Tries to return an array of fingerprints, either from input fingerprints or - * from Key objects (openpgp Keys or GPGME_Keys are both accepted). + * Helper function that tries to return an array of fingerprints, either from + * input fingerprints or from Key objects (openpgp Keys or GPGME_Keys are both + * accepted). * - * @param {Object | Array | String | Array} input - * @returns {Array} Array of fingerprints, or an empty array + * @param {Object | Object[] | String | String[] } input + * @returns {String[]} Array of fingerprints, or an empty array */ export function toKeyIdArray (input){ if (!input){ 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 { // MSG_NOT_A_FPR is just a console warning if warning enabled // in src/Errors.js gpgme_error('MSG_NOT_A_FPR'); } } else if (typeof (input[i]) === 'object'){ let fpr = ''; if (input[i].hasOwnProperty('fingerprint')){ 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){ return []; } else { return result; } } /** * Check if values are valid hexadecimal values of a specified length * @param {String} key input value. * @param {int} len the expected length of the value * @returns {Boolean} true if value passes test * @private */ 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 Fingerprint + * Checks if the input is a valid Fingerprint * (Hex string with a length of 40 characters) * @param {String} value to check * @returns {Boolean} true if value passes test */ export function isFingerprint (value){ return hextest(value, 40); } /** * check if the input is a valid gnupg long ID (Hex string with a length of 16 * characters) * @param {String} value to check * @returns {Boolean} true if value passes test */ export function isLongId (value){ return hextest(value, 16); } /** - * Recursively decodes input (utf8) to output (utf-16; javascript) strings + * Recursively decodes input (utf8) to output (utf-16; javascript) strings. * @param {Object | Array | String} property + * @private */ export function decode (property){ if (typeof property === 'string'){ try { return decodeURIComponent(escape(unescape(property))); } catch (error){ if (error instanceof URIError) { return property; } } } else if (Array.isArray(property)){ let res = []; for (let arr=0; arr < property.length; arr++){ res.push(decode(property[arr])); } return res; } else if (typeof property === 'object'){ const keys = Object.keys(property); if (keys.length){ let res = {}; for (let k=0; k < keys.length; k++ ){ res[keys[k]] = decode(property[keys[k]]); } return res; } return property; } return property; } /** * Turns a base64 encoded string into an uint8 array + * adapted from https://gist.github.com/borismus/1032746 * @param {String} base64 encoded String * @returns {Uint8Array} - * adapted from https://gist.github.com/borismus/1032746 + * @private */ export function atobArray (base64) { if (typeof (base64) !== 'string'){ throw gpgme_error('DECODE_FAIL'); } const raw = window.atob(base64); const rawLength = raw.length; let array = new Uint8Array(new ArrayBuffer(rawLength)); for (let i = 0; i < rawLength; i++) { array[i] = raw.charCodeAt(i); } return array; } /** * Turns a Uint8Array into an utf8-String - * @param {*} array Uint8Array - * @returns {String} + *
  * Taken and slightly adapted from
  *  http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
  * (original header:
  *   utf.js - UTF-8 <=> UTF-16 convertion
  *
  *   Copyright (C) 1999 Masanao Izumo 
  *   Version: 1.0
  *   LastModified: Dec 25 1999
  *   This library is free.  You can redistribute it and/or modify it.
  *  )
+ * 
+ * @param {*} array Uint8Array + * @returns {String} + * @private */ export function Utf8ArrayToStr (array) { let out, i, len, c, char2, char3; out = ''; len = array.length; i = 0; if (array instanceof Uint8Array === false){ throw gpgme_error('DECODE_FAIL'); } while (i < len) { c = array[i++]; switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx out += String.fromCharCode(c); break; case 12: case 13: // 110x xxxx 10xx xxxx char2 = array[i++]; out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx char2 = array[i++]; char3 = array[i++]; out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); break; default: break; } } return out; } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index d0f87eda..7f0554c4 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,688 +1,711 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ + import { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; import { createMessage } from './Message'; /** * Validates the given fingerprint and creates a new {@link GPGME_Key} * @param {String} fingerprint * @param {Boolean} async If True, Key properties (except fingerprint) will be * queried from gnupg on each call, making the operation up-to-date, the * answers will be Promises, and the performance will likely suffer * @param {Object} data additional initial properties this Key will have. Needs * a full object as delivered by gpgme-json * @returns {Object} The verified and updated data */ export function createKey (fingerprint, async = false, data){ if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){ throw gpgme_error('PARAM_WRONG'); } if (data !== undefined){ data = validateKeyData(fingerprint, data); } if (data instanceof Error){ throw gpgme_error('KEY_INVALID'); } else { return new GPGME_Key(fingerprint, async, data); } } /** - * Represents the Keys as stored in the gnupg backend - * It allows to query almost all information defined in gpgme Key Objects - * Refer to {@link validKeyProperties} for available information, and the gpgme - * documentation on their meaning - * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) + * Represents the Keys as stored in the gnupg backend. A key is defined by a + * fingerprint. + * A key cannot be directly created via the new operator, please use + * {@link createKey} instead. + * A GPGME_Key object allows to query almost all information defined in gpgme + * Keys. It offers two modes, async: true/false. In async mode, Key properties + * with the exception of the fingerprint will be queried from gnupg on each + * call, making the operation up-to-date, the answers will be Promises, and + * the performance will likely suffer. In Sync modes, all information except + * for the armored Key export will be cached and can be refreshed by + * [refreshKey]{@link GPGME_Key#refreshKey}. + * + *
+ * see also:
+ *      {@link GPGME_UserId} user Id objects
+ *      {@link GPGME_Subkey} subKey objects
+ * 
+ * For other Key properteis, refer to {@link validKeyProperties}, + * and to the [gpgme documentation]{@link https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html} + * for meanings and further details. * * @class */ class GPGME_Key { constructor (fingerprint, async, data){ /** - * @property {Boolean} If true, most answers will be asynchronous + * @property {Boolean} _async If true, the Key was initialized without + * cached data */ this._async = async; this._data = { fingerprint: fingerprint.toUpperCase() }; if (data !== undefined && data.fingerprint.toUpperCase() === this._data.fingerprint ) { this._data = data; } } /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived * @returns {Boolean| String | Date | Array | Object} - * the value of the property. If the Key is set to Async, the value - * will be fetched from gnupg and resolved as a Promise. If Key is not - * async, the armored property is not available (it can still be - * retrieved asynchronously by {@link Key.getArmor}) + * @returns {Promise} (if in async + * mode) + *
+     * Returns the value of the property requested. If the Key is set to async,
+     * the value will be fetched from gnupg and resolved as a Promise. If Key
+     * is not  async, the armored property is not available (it can still be
+     * retrieved asynchronously by [getArmor]{@link GPGME_Key#getArmor})
      */
     get (property) {
         if (this._async === true) {
             switch (property){
             case 'armored':
                 return this.getArmor();
             case 'hasSecret':
                 return this.getGnupgSecretState();
             default:
                 return getGnupgState(this.fingerprint, property);
             }
         } else {
             if (property === 'armored') {
                 throw gpgme_error('KEY_ASYNC_ONLY');
             }
             // eslint-disable-next-line no-use-before-define
             if (!validKeyProperties.hasOwnProperty(property)){
                 throw gpgme_error('PARAM_WRONG');
             } else {
                 return (this._data[property]);
             }
         }
     }
 
     /**
-     * Reloads the Key information from gnupg. This is only useful if you
+     * Reloads the Key information from gnupg. This is only useful if the Key
      * use the GPGME_Keys cached. Note that this is a performance hungry
      * operation. If you desire more than a few refreshs, it may be
-     * advisable to run {@link Keyring.getKeys} instead.
-     * @returns {Promise}
+     * advisable to run [Keyring.getKeys]{@link Keyring#getKeys} instead.
+     * @returns {Promise}
      * @async
      */
     refreshKey () {
         let me = this;
         return new Promise(function (resolve, reject) {
             if (!me._data.fingerprint){
                 reject(gpgme_error('KEY_INVALID'));
             }
             let msg = createMessage('keylist');
             msg.setParameter('sigs', true);
             msg.setParameter('keys', me._data.fingerprint);
             msg.post().then(function (result){
                 if (result.keys.length === 1){
                     const newdata = validateKeyData(
                         me._data.fingerprint, result.keys[0]);
                     if (newdata instanceof Error){
                         reject(gpgme_error('KEY_INVALID'));
                     } else {
                         me._data = newdata;
                         me.getGnupgSecretState().then(function (){
                             me.getArmor().then(function (){
                                 resolve(me);
                             }, function (error){
                                 reject(error);
                             });
                         }, function (error){
                             reject(error);
                         });
                     }
                 } else {
                     reject(gpgme_error('KEY_NOKEY'));
                 }
             }, function (error) {
                 reject(gpgme_error('GNUPG_ERROR'), error);
             });
         });
     }
 
     /**
      * Query the armored block of the Key directly from gnupg. Please note
      * that this will not get you any export of the secret/private parts of
      * a Key
-     * @returns {Promise}
+     * @returns {Promise}
      * @async
      */
     getArmor () {
         const me = this;
         return new Promise(function (resolve, reject) {
             if (!me._data.fingerprint){
                 reject(gpgme_error('KEY_INVALID'));
             }
             let msg = createMessage('export');
             msg.setParameter('armor', true);
             msg.setParameter('keys', me._data.fingerprint);
             msg.post().then(function (result){
                 resolve(result.data);
             }, function (error){
                 reject(error);
             });
         });
     }
 
     /**
      * Find out if the Key is part of a Key pair including public and
      * private key(s). If you want this information about more than a few
      * Keys in synchronous mode, it may be advisable to run
-     * {@link Keyring.getKeys} instead, as it performs faster in bulk
-     * querying this state.
-     * @returns {Promise} True if a private Key is
-     * available in the gnupg Keyring.
+     * [Keyring.getKeys]{@link Keyring#getKeys} instead, as it performs faster
+     * in bulk querying.
+     * @returns {Promise} True if a private Key is available in the
+     * gnupg Keyring.
      * @async
      */
     getGnupgSecretState (){
         const me = this;
         return new Promise(function (resolve, reject) {
             if (!me._data.fingerprint){
                 reject(gpgme_error('KEY_INVALID'));
             } else {
                 let msg = createMessage('keylist');
                 msg.setParameter('keys', me._data.fingerprint);
                 msg.setParameter('secret', true);
                 msg.post().then(function (result){
                     me._data.hasSecret = null;
                     if (
                         result.keys &&
                         result.keys.length === 1 &&
                         result.keys[0].secret === true
                     ) {
                         me._data.hasSecret = true;
                         resolve(true);
                     } else {
                         me._data.hasSecret = false;
                         resolve(false);
                     }
                 }, function (error){
                     reject(error);
                 });
             }
         });
     }
 
     /**
      * Deletes the (public) Key from the GPG Keyring. Note that a deletion
-     * of a secret key is not supported by the native backend.
-     * @returns {Promise} Success if key was deleted,
-     * rejects with a GPG error otherwise.
+     * of a secret key is not supported by the native backend, and gnupg will
+     * refuse to delete a Key if there is still a secret/private Key present
+     * to that public Key
+     * @returns {Promise} Success if key was deleted.
      */
     delete (){
         const me = this;
         return new Promise(function (resolve, reject){
             if (!me._data.fingerprint){
                 reject(gpgme_error('KEY_INVALID'));
             }
             let msg = createMessage('delete');
             msg.setParameter('key', me._data.fingerprint);
             msg.post().then(function (result){
                 resolve(result.success);
             }, function (error){
                 reject(error);
             });
         });
     }
 
     /**
      * @returns {String} The fingerprint defining this Key. Convenience getter
      */
     get fingerprint (){
         return this._data.fingerprint;
     }
 }
 
 /**
- * Representing a subkey of a Key.
+ * Representing a subkey of a Key. See {@link validSubKeyProperties} for
+ * possible properties.
  * @class
  * @protected
  */
 class GPGME_Subkey {
 
     /**
      * Initializes with the json data sent by gpgme-json
      * @param {Object} data
      * @private
      */
     constructor (data){
         this._data = {};
         let keys = Object.keys(data);
         const me = this;
 
         /**
          * Validates a subkey property against {@link validSubKeyProperties} and
          * sets it if validation is successful
          * @param {String} property
          * @param {*} value
          * @param private
          */
         const setProperty = function (property, value){
             // eslint-disable-next-line no-use-before-define
             if (validSubKeyProperties.hasOwnProperty(property)){
                 // eslint-disable-next-line no-use-before-define
                 if (validSubKeyProperties[property](value) === true) {
                     if (property === 'timestamp' || property === 'expires'){
                         me._data[property] = new Date(value * 1000);
                     } else {
                         me._data[property] = value;
                     }
                 }
             }
         };
         for (let i=0; i< keys.length; i++) {
             setProperty(keys[i], data[keys[i]]);
         }
     }
 
     /**
      * Fetches any information about this subkey
      * @param {String} property Information to request
      * @returns {String | Number | Date}
      */
     get (property) {
         if (this._data.hasOwnProperty(property)){
             return (this._data[property]);
         }
     }
 
 }
 
 /**
- * Representing user attributes associated with a Key or subkey
+ * Representing user attributes associated with a Key or subkey. See
+ * {@link validUserIdProperties} for possible properties.
  * @class
  * @protected
  */
 class GPGME_UserId {
 
     /**
      * Initializes with the json data sent by gpgme-json
      * @param {Object} data
      * @private
      */
     constructor (data){
         this._data = {};
         const me = this;
         let keys = Object.keys(data);
         const setProperty = function (property, value){
             // eslint-disable-next-line no-use-before-define
             if (validUserIdProperties.hasOwnProperty(property)){
                 // eslint-disable-next-line no-use-before-define
                 if (validUserIdProperties[property](value) === true) {
                     if (property === 'last_update'){
                         me._data[property] = new Date(value*1000);
                     } else {
                         me._data[property] = value;
                     }
                 }
             }
         };
         for (let i=0; i< keys.length; i++) {
             setProperty(keys[i], data[keys[i]]);
         }
     }
 
     /**
      * Fetches information about the user
      * @param {String} property Information to request
      * @returns {String | Number}
      */
     get (property) {
         if (this._data.hasOwnProperty(property)){
             return (this._data[property]);
         }
     }
 
 }
 
 /**
  * Validation definition for userIds. Each valid userId property is represented
  * as a key- Value pair, with their value being a validation function to check
  * against
  * @protected
  * @const
  */
 const validUserIdProperties = {
     'revoked': function (value){
         return typeof (value) === 'boolean';
     },
     'invalid':  function (value){
         return typeof (value) === 'boolean';
     },
     'uid': function (value){
         if (typeof (value) === 'string' || value === ''){
             return true;
         }
         return false;
     },
     'validity': function (value){
         if (typeof (value) === 'string'){
             return true;
         }
         return false;
     },
     'name': function (value){
         if (typeof (value) === 'string' || value === ''){
             return true;
         }
         return false;
     },
     'email': function (value){
         if (typeof (value) === 'string' || value === ''){
             return true;
         }
         return false;
     },
     'address': function (value){
         if (typeof (value) === 'string' || value === ''){
             return true;
         }
         return false;
     },
     'comment': function (value){
         if (typeof (value) === 'string' || value === ''){
             return true;
         }
         return false;
     },
     'origin':  function (value){
         return Number.isInteger(value);
     },
     'last_update':  function (value){
         return Number.isInteger(value);
     }
 };
 
 /**
  * Validation definition for subKeys. Each valid userId property is represented
  * as a key-value pair, with the value being a validation function
  * @protected
  * @const
  */
 const validSubKeyProperties = {
     'invalid': function (value){
         return typeof (value) === 'boolean';
     },
     'can_encrypt': function (value){
         return typeof (value) === 'boolean';
     },
     'can_sign': function (value){
         return typeof (value) === 'boolean';
     },
     'can_certify':  function (value){
         return typeof (value) === 'boolean';
     },
     'can_authenticate':  function (value){
         return typeof (value) === 'boolean';
     },
     'secret': function (value){
         return typeof (value) === 'boolean';
     },
     'is_qualified': function (value){
         return typeof (value) === 'boolean';
     },
     'is_cardkey':  function (value){
         return typeof (value) === 'boolean';
     },
     'is_de_vs':  function (value){
         return typeof (value) === 'boolean';
     },
     'pubkey_algo_name': function (value){
         return typeof (value) === 'string';
         // TODO: check against list of known?['']
     },
     'pubkey_algo_string': function (value){
         return typeof (value) === 'string';
         // TODO: check against list of known?['']
     },
     'keyid': function (value){
         return isLongId(value);
     },
     'pubkey_algo': function (value) {
         return (Number.isInteger(value) && value >= 0);
     },
     'length': function (value){
         return (Number.isInteger(value) && value > 0);
     },
     'timestamp': function (value){
         return (Number.isInteger(value) && value > 0);
     },
     'expires': function (value){
         return (Number.isInteger(value) && value > 0);
     }
 };
 
 /**
  * Validation definition for Keys. Each valid Key property is represented
  * as a key-value pair, with their value being a validation function. For
  * details on the meanings, please refer to the gpgme documentation
  * https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#Key-objects
  * @param {String} fingerprint
  * @param {Boolean} revoked
  * @param {Boolean} expired
  * @param {Boolean} disabled
  * @param {Boolean} invalid
  * @param {Boolean} can_encrypt
  * @param {Boolean} can_sign
  * @param {Boolean} can_certify
  * @param {Boolean} can_authenticate
  * @param {Boolean} secret
  * @param {Boolean}is_qualified
  * @param {String} protocol
  * @param {String} issuer_serial
  * @param {String} issuer_name
  * @param {Boolean} chain_id
  * @param {String} owner_trust
  * @param {Date} last_update
  * @param {String} origin
  * @param {Array} subkeys
  * @param {Array} userids
  * @param {Array} tofu
  * @param {Boolean} hasSecret
  * @protected
  * @const
  */
 const validKeyProperties = {
     'fingerprint': function (value){
         return isFingerprint(value);
     },
     'revoked': function (value){
         return typeof (value) === 'boolean';
     },
     'expired': function (value){
         return typeof (value) === 'boolean';
     },
     'disabled': function (value){
         return typeof (value) === 'boolean';
     },
     'invalid': function (value){
         return typeof (value) === 'boolean';
     },
     'can_encrypt': function (value){
         return typeof (value) === 'boolean';
     },
     'can_sign': function (value){
         return typeof (value) === 'boolean';
     },
     'can_certify': function (value){
         return typeof (value) === 'boolean';
     },
     'can_authenticate': function (value){
         return typeof (value) === 'boolean';
     },
     'secret': function (value){
         return typeof (value) === 'boolean';
     },
     'is_qualified': function (value){
         return typeof (value) === 'boolean';
     },
     'protocol': function (value){
         return typeof (value) === 'string';
         // TODO check for implemented ones
     },
     'issuer_serial': function (value){
         return typeof (value) === 'string';
     },
     'issuer_name': function (value){
         return typeof (value) === 'string';
     },
     'chain_id': function (value){
         return typeof (value) === 'string';
     },
     'owner_trust': function (value){
         return typeof (value) === 'string';
     },
     'last_update': function (value){
         return (Number.isInteger(value));
         // TODO undefined/null possible?
     },
     'origin': function (value){
         return (Number.isInteger(value));
     },
     'subkeys': function (value){
         return (Array.isArray(value));
     },
     'userids': function (value){
         return (Array.isArray(value));
     },
     'tofu': function (value){
         return (Array.isArray(value));
     },
     'hasSecret': function (value){
         return typeof (value) === 'boolean';
     }
 
 };
 
 /**
 * sets the Key data in bulk. It can only be used from inside a Key, either
 * during construction or on a refresh callback.
 * @param {Object} key the original internal key data.
 * @param {Object} data Bulk set the data for this key, with an Object structure
 * as sent by gpgme-json.
 * @returns {Object|GPGME_Error} the changed data after values have been set,
 * an error if something went wrong.
 * @private
 */
 function validateKeyData (fingerprint, data){
     const key = {};
     if (!fingerprint || typeof (data) !== 'object' || !data.fingerprint
      || fingerprint !== data.fingerprint.toUpperCase()
     ){
         return gpgme_error('KEY_INVALID');
     }
     let props = Object.keys(data);
     for (let i=0; i< props.length; i++){
         if (!validKeyProperties.hasOwnProperty(props[i])){
             return gpgme_error('KEY_INVALID');
         }
         // running the defined validation function
         if (validKeyProperties[props[i]](data[props[i]]) !== true ){
             return gpgme_error('KEY_INVALID');
         }
         switch (props[i]){
         case 'subkeys':
             key.subkeys = [];
             for (let i=0; i< data.subkeys.length; i++) {
                 key.subkeys.push(
                     new GPGME_Subkey(data.subkeys[i]));
             }
             break;
         case 'userids':
             key.userids = [];
             for (let i=0; i< data.userids.length; i++) {
                 key.userids.push(
                     new GPGME_UserId(data.userids[i]));
             }
             break;
         case 'last_update':
             key[props[i]] = new Date( data[props[i]] * 1000 );
             break;
         default:
             key[props[i]] = data[props[i]];
         }
     }
     return key;
 }
 
 /**
  * Fetches and sets properties from gnupg
  * @param {String} fingerprint
  * @param {String} property to search for.
  * @private
  * @async
  */
 function getGnupgState (fingerprint, property){
     return new Promise(function (resolve, reject) {
         if (!isFingerprint(fingerprint)) {
             reject(gpgme_error('KEY_INVALID'));
         } else {
             let msg = createMessage('keylist');
             msg.setParameter('keys', fingerprint);
             msg.post().then(function (res){
                 if (!res.keys || res.keys.length !== 1){
                     reject(gpgme_error('KEY_INVALID'));
                 } else {
                     const key = res.keys[0];
                     let result;
                     switch (property){
                     case 'subkeys':
                         result = [];
                         if (key.subkeys.length){
                             for (let i=0; i < key.subkeys.length; i++) {
                                 result.push(
                                     new GPGME_Subkey(key.subkeys[i]));
                             }
                         }
                         resolve(result);
                         break;
                     case 'userids':
                         result = [];
                         if (key.userids.length){
                             for (let i=0; i< key.userids.length; i++) {
                                 result.push(
                                     new GPGME_UserId(key.userids[i]));
                             }
                         }
                         resolve(result);
                         break;
                     case 'last_update':
                         if (key.last_update === undefined){
                             reject(gpgme_error('CONN_UNEXPECTED_ANSWER'));
                         } else if (key.last_update !== null){
                             resolve(new Date( key.last_update * 1000));
                         } else {
                             resolve(null);
                         }
                         break;
                     default:
                         if (!validKeyProperties.hasOwnProperty(property)){
                             reject(gpgme_error('PARAM_WRONG'));
                         } else {
                             if (key.hasOwnProperty(property)){
                                 resolve(key[property]);
                             } else {
                                 reject(gpgme_error(
                                     'CONN_UNEXPECTED_ANSWER'));
                             }
                         }
                         break;
                     }
                 }
             }, function (error){
                 reject(gpgme_error(error));
             });
         }
     });
 }
\ No newline at end of file
diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js
index 0c64f337..e223284b 100644
--- a/lang/js/src/Keyring.js
+++ b/lang/js/src/Keyring.js
@@ -1,447 +1,447 @@
 /* gpgme.js - Javascript integration for gpgme
  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GPGME.
  *
  * GPGME is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * GPGME is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this program; if not, see .
  * SPDX-License-Identifier: LGPL-2.1+
  *
  * Author(s):
  *     Maximilian Krambach 
  */
 
 
 import { createMessage } from './Message';
 import { createKey } from './Key';
 import { isFingerprint } from './Helpers';
 import { gpgme_error } from './Errors';
 
 /**
  * This class offers access to the gnupg keyring
  */
 export class GPGME_Keyring {
 
     /**
      * Queries Keys (all Keys or a subset) from gnupg.
      *
      * @param {Object} options:
      * @param {String | Array} options.pattern (optional) A pattern to
      * search for in userIds or KeyIds.
      * @param {Boolean} options.prepare_sync (optional) if set to true, most
      * data (with the exception of armored Key blocks) will be cached for the
      * Keys. This enables direct, synchronous use of these properties for
      * all keys. It does not check for changes on the backend. The cached
      * information can be updated with the {@link Key.refresh} method.
      * @param {Boolean} options.search (optional) retrieve Keys from external
      * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup)
-     * @returns {Promise>}
+     * @returns {Promise}
      * @static
      * @async
      */
     getKeys ({ pattern, prepare_sync = false, search = false } = {}){
         if (typeof arguments[0] !== 'object') {
             return Promise.reject(gpgme_error('PARAM_WRONG'));
         }
         if (arguments.length && typeof arguments[0] !== 'object') {
             return Promise.reject(gpgme_error('PARAM_WRONG'));
         }
         return new Promise(function (resolve, reject) {
             let msg = createMessage('keylist');
             if (pattern) {
                 msg.setParameter('keys', pattern);
             }
             msg.setParameter('sigs', true);
             if (search === true){
                 msg.setParameter('locate', true);
             }
             msg.post().then(function (result){
                 let resultset = [];
                 if (result.keys.length === 0){
                     resolve([]);
                 } else {
                     let secondrequest;
                     if (prepare_sync === true) {
                         secondrequest = function () {
                             let msg2 = createMessage('keylist');
                             if (pattern){
                                 msg2.setParameter('keys', pattern);
                             }
                             msg2.setParameter('secret', true);
                             return msg2.post();
                         };
                     } else {
                         secondrequest = function () {
                             return Promise.resolve(true);
                         };
                     }
                     secondrequest().then(function (answer) {
                         for (let i=0; i < result.keys.length; i++){
                             if (prepare_sync === true){
                                 if (answer && answer.keys) {
                                     for (let j=0;
                                         j < answer.keys.length; j++ ){
                                         const a = answer.keys[j];
                                         const b = result.keys[i];
                                         if (
                                             a.fingerprint === b.fingerprint
                                         ) {
                                             if (a.secret === true){
                                                 b.hasSecret = true;
                                             } else {
                                                 b.hasSecret = false;
                                             }
                                             break;
                                         }
                                     }
                                 }
                             }
                             let k = createKey(result.keys[i].fingerprint,
                                 !prepare_sync, result.keys[i]);
                             resultset.push(k);
                         }
                         resolve(resultset);
                     }, function (error){
                         reject(error);
                     });
                 }
             });
         });
     }
 
     /**
      * @typedef {Object} exportResult The result of a getKeysArmored
      * operation.
      * @property {String} armored The public Key(s) as armored block. Note
      * that the result is one armored block, and not a block per key.
      * @property {Array} secret_fprs (optional) list of
      * fingerprints for those Keys that also have a secret Key available in
      * gnupg. The secret key will not be exported, but the fingerprint can
      * be used in operations needing a secret key.
      */
 
     /**
      * Fetches the armored public Key blocks for all Keys matching the
      * pattern (if no pattern is given, fetches all keys known to gnupg).
      * @param {Object} options (optional)
      * @param {String|Array} options.pattern The Pattern to
      * search for
      * @param {Boolean} options.with_secret_fpr also return a list of
      * fingerprints for the keys that have a secret key available
-     * @returns {Promise} Object containing the
+     * @returns {Promise} Object containing the
      * armored Key(s) and additional information.
      * @static
      * @async
      */
     getKeysArmored ({ pattern, with_secret_fpr }) {
         return new Promise(function (resolve, reject) {
             let msg = createMessage('export');
             msg.setParameter('armor', true);
             if (with_secret_fpr === true) {
                 msg.setParameter('with-sec-fprs', true);
             }
             if (pattern){
                 msg.setParameter('keys', pattern);
             }
             msg.post().then(function (answer){
                 const result = { armored: answer.data };
                 if (with_secret_fpr === true){
                     if (answer.hasOwnProperty('sec-fprs')){
                         result.secret_fprs = answer['sec-fprs'];
                     } else {
                         result.secret_fprs = [];
                     }
                 }
                 resolve(result);
             }, function (error){
                 reject(error);
             });
         });
     }
 
     /**
      * Returns the Key used by default in gnupg.
      * (a.k.a. 'primary Key or 'main key').
      * It looks up the gpg configuration if set, or the first key that
      * contains a secret key.
      *
-     * @returns {Promise}
+     * @returns {Promise}
      * @async
      * @static
      */
     getDefaultKey (prepare_sync = false) {
         let me = this;
         return new Promise(function (resolve, reject){
             let msg = createMessage('config_opt');
             msg.setParameter('component', 'gpg');
             msg.setParameter('option', 'default-key');
             msg.post().then(function (resp){
                 if (resp.option !== undefined
                     && resp.option.hasOwnProperty('value')
                     && resp.option.value.length === 1
                     && resp.option.value[0].hasOwnProperty('string')
                     && typeof (resp.option.value[0].string) === 'string'){
                     me.getKeys({ pattern: resp.option.value[0].string,
                         prepare_sync: true }).then(
                         function (keys){
                             if (keys.length === 1){
                                 resolve(keys[0]);
                             } else {
                                 reject(gpgme_error('KEY_NO_DEFAULT'));
                             }
                         }, function (error){
                             reject(error);
                         });
                 } else {
                     let msg = createMessage('keylist');
                     msg.setParameter('secret', true);
                     msg.post().then(function (result){
                         if (result.keys.length === 0){
                             reject(gpgme_error('KEY_NO_DEFAULT'));
                         } else {
                             for (let i=0; i< result.keys.length; i++ ) {
                                 if (
                                     result.keys[i].invalid === false &&
                                     result.keys[i].expired === false &&
                                     result.keys[i].revoked === false &&
                                     result.keys[i].can_sign === true
                                 ) {
                                     let k = createKey(
                                         result.keys[i].fingerprint,
                                         !prepare_sync,
                                         result.keys[i]);
                                     resolve(k);
                                     break;
                                 } else if (i === result.keys.length - 1){
                                     reject(gpgme_error('KEY_NO_DEFAULT'));
                                 }
                             }
                         }
                     }, function (error){
                         reject(error);
                     });
                 }
             }, function (error){
                 reject(error);
             });
         });
     }
 
     /**
      * @typedef {Object} importResult The result of a Key update
      * @property {Object} summary Numerical summary of the result. See the
      * feedbackValues variable for available Keys values and the gnupg
      * documentation.
      * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html
      * for details on their meaning.
      * @property {Array} Keys Array of Object containing
      * GPGME_Keys with additional import information
      *
      */
 
     /**
      * @typedef {Object} importedKeyResult
      * @property {GPGME_Key} key The resulting key
      * @property {String} status:
      *  'nochange' if the Key was not changed,
      *  'newkey' if the Key was imported in gpg, and did not exist
      *    previously,
      *  'change' if the key existed, but details were updated. For details,
      *    Key.changes is available.
      * @property {Boolean} changes.userId Changes in userIds
      * @property {Boolean} changes.signature Changes in signatures
      * @property {Boolean} changes.subkey Changes in subkeys
      */
 
     /**
      * Import an armored Key block into gnupg. Note that this currently
      * will not succeed on private Key blocks.
      * @param {String} armored Armored Key block of the Key(s) to be
      * imported into gnupg
      * @param {Boolean} prepare_sync prepare the keys for synched use
      * (see {@link getKeys}).
      * @returns {Promise} A summary and Keys considered.
      * @async
      * @static
      */
     importKey (armored, prepare_sync) {
         let feedbackValues = ['considered', 'no_user_id', 'imported',
             'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys',
             'new_signatures', 'new_revocations', 'secret_read',
             'secret_imported', 'secret_unchanged', 'skipped_new_keys',
             'not_imported', 'skipped_v3_keys'];
         if (!armored || typeof (armored) !== 'string'){
             return Promise.reject(gpgme_error('PARAM_WRONG'));
         }
         let me = this;
         return new Promise(function (resolve, reject){
             let msg = createMessage('import');
             msg.setParameter('data', armored);
             msg.post().then(function (response){
                 let infos = {};
                 let fprs = [];
                 let summary = {};
                 for (let i=0; i < feedbackValues.length; i++ ){
                     summary[feedbackValues[i]] =
                         response.result[feedbackValues[i]];
                 }
                 if (!response.result.hasOwnProperty('imports') ||
                     response.result.imports.length === 0
                 ){
                     resolve({ Keys:[],summary: summary });
                     return;
                 }
                 for (let res=0; res}
+     * @returns {Promise}
      * @async
      * @static
      */
     deleteKey (fingerprint){
         if (isFingerprint(fingerprint) === true) {
             let key = createKey(fingerprint);
             return key.delete();
         } else {
             return Promise.reject(gpgme_error('KEY_INVALID'));
         }
     }
 
     /**
      * Generates a new Key pair directly in gpg, and returns a GPGME_Key
      * representing that Key. Please note that due to security concerns,
      * secret Keys can not be deleted or exported from inside gpgme.js.
      * @param {Object} options
      * @param {String} option.userId The user Id, e.g. 'Foo Bar '
      * @param {String} option.algo (optional) algorithm (and optionally key
      * size) to be used. See {@link supportedKeyAlgos} below for supported
      * values. If ommitted, 'default' is used.
      * @param {Number} option.expires (optional) Expiration time in seconds
      * from now. If not set or set to 0, expiration will be 'never'
      * @param {String} options.subkey_algo (optional) algorithm of the
      * encryption subkey. If ommited the same as algo is used.
      *
      * @return {Promise}
      * @async
      */
     generateKey ({ userId, algo = 'default', expires= 0, subkey_algo } = {}){
         if (typeof userId !== 'string'
             // eslint-disable-next-line no-use-before-define
             || (algo && supportedKeyAlgos.indexOf(algo) < 0 )
             || (!Number.isInteger(expires) || expires < 0 )
         ){
             return Promise.reject(gpgme_error('PARAM_WRONG'));
         }
         // eslint-disable-next-line no-use-before-define
         if (subkey_algo && supportedKeyAlgos.indexOf(subkey_algo) < 0){
             return Promise.reject(gpgme_error('PARAM_WRONG'));
         }
         let me = this;
         return new Promise(function (resolve, reject){
             let msg = createMessage('createkey');
             msg.setParameter('userid', userId);
             msg.setParameter('algo', algo);
             if (subkey_algo) {
                 msg.setParameter('subkey-algo',subkey_algo );
             }
             msg.setParameter('expires', expires);
             msg.post().then(function (response){
                 me.getKeys({
                     pattern: response.fingerprint,
                     prepare_sync: true
                 }).then(function (result){
                     resolve(result);
                 }, function (error){
                     reject(error);
                 });
             }, function (error) {
                 reject(error);
             });
         });
     }
 }
 
 
 /**
  * List of algorithms supported for key generation. Please refer to the gnupg
  * documentation for details
  */
 const supportedKeyAlgos = [
     'default',
     'rsa', 'rsa2048', 'rsa3072', 'rsa4096',
     'dsa', 'dsa2048', 'dsa3072', 'dsa4096',
     'elg', 'elg2048', 'elg3072', 'elg4096',
     'ed25519',
     'cv25519',
     'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1',
     'NIST P-256', 'NIST P-384', 'NIST P-521'
 ];
\ No newline at end of file
diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js
index fff20fbe..9f6abb75 100644
--- a/lang/js/src/Message.js
+++ b/lang/js/src/Message.js
@@ -1,239 +1,243 @@
 /* gpgme.js - Javascript integration for gpgme
  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GPGME.
  *
  * GPGME is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * GPGME is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this program; if not, see .
  * SPDX-License-Identifier: LGPL-2.1+
  *
  * Author(s):
  *     Maximilian Krambach 
  */
 
 import { permittedOperations } from './permittedOperations';
 import { gpgme_error } from './Errors';
 import { Connection } from './Connection';
 
 /**
  * Initializes a message for gnupg, validating the message's purpose with
  *   {@link permittedOperations} first
  * @param {String} operation
  * @returns {GPGME_Message} The Message object
  */
 export function createMessage (operation){
     if (typeof (operation) !== 'string'){
         throw gpgme_error('PARAM_WRONG');
     }
     if (permittedOperations.hasOwnProperty(operation)){
         return new GPGME_Message(operation);
     } else {
         throw gpgme_error('MSG_WRONG_OP');
     }
 }
 
 /**
  * A Message collects, validates and handles all information required to
  * successfully establish a meaningful communication with gpgme-json via
- * {@link Connection.post}. The definition on which communication is available
- * can be found in {@link permittedOperations}.
+ * [Connection.post]{@link Connection#post}. The definition on which
+ * communication is available can be found in {@link permittedOperations}.
  * @class
+ * @protected
  */
 export class GPGME_Message {
 
     constructor (operation){
         this._msg = {
             op: operation,
             chunksize: 1023* 1024
         };
         this._expected = null;
     }
 
     get operation (){
         return this._msg.op;
     }
 
     set expected (value){
         if (value === 'uint8' || value === 'base64'){
             this._expected = value;
         }
     }
 
     get expected () {
         return this._expected;
     }
     /**
-     * The maximum size of responses from gpgme in bytes. As of July 2018,
+     * The maximum size of responses from gpgme in bytes. As of September 2018,
      * most browsers will only accept answers up to 1 MB of size.
      * Everything above that threshold will not pass through
      * nativeMessaging; answers that are larger need to be sent in parts.
      * The lower limit is set to 10 KB. Messages smaller than the threshold
      * will not encounter problems, larger messages will be received in
      * chunks. If the value is not explicitly specified, 1023 KB is used.
      */
     set chunksize (value){
         if (
             Number.isInteger(value) &&
             value > 10 * 1024 &&
             value <= 1024 * 1024
         ){
             this._msg.chunksize = value;
         }
     }
 
     get chunksize (){
         return this._msg.chunksize;
     }
 
     /**
-     * Returns the prepared message with parameters and completeness checked
+     * Returns the prepared message after their parameters and the completion
+     * of required parameters have been 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;
         }
     }
 
     /**
      * Sets a parameter for the message. It validates with
-     *      {@link permittedOperations}
+     * {@link permittedOperations}
      * @param {String} param Parameter to set
      * @param {any} value Value to set
-     * @returns {Boolean} If the parameter was set successfully
+     * @returns {Boolean} True if the parameter was set successfully.
+     * Throws errors if the parameters don't match the message operation
      */
     setParameter ( param,value ){
         if (!param || typeof (param) !== 'string'){
             throw gpgme_error('PARAM_WRONG');
         }
         let po = permittedOperations[this._msg.op];
         if (!po){
             throw 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 {
             throw gpgme_error('PARAM_WRONG');
         }
         // check incoming value for correctness
         let checktype = function (val){
             switch (typeof (val)){
             case 'string':
                 if (poparam.allowed.indexOf(typeof (val)) >= 0
                         && val.length > 0) {
                     return true;
                 }
                 throw gpgme_error('PARAM_WRONG');
             case 'number':
                 if (
                     poparam.allowed.indexOf('number') >= 0
                         && isNaN(value) === false){
                     return true;
                 }
                 throw gpgme_error('PARAM_WRONG');
 
             case 'boolean':
                 if (poparam.allowed.indexOf('boolean') >= 0){
                     return true;
                 }
                 throw gpgme_error('PARAM_WRONG');
             case 'object':
                 if (Array.isArray(val)){
                     if (poparam.array_allowed !== true){
                         throw 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;
                     }
                     throw gpgme_error('PARAM_WRONG');
                 } else {
                     throw gpgme_error('PARAM_WRONG');
                 }
                 break;
             default:
                 throw 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, that is
      * all 'required' parameters according to {@link permittedOperations}.
      * @returns {Boolean} true if message is complete.
      */
     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){
                 return false;
             }
         }
         return true;
     }
+
     /**
      * Sends the Message via nativeMessaging and resolves with the answer.
-     * @returns {Promise}
+     * @returns {Promise} GPGME response
      * @async
      */
     post (){
         let me = this;
         return new Promise(function (resolve, reject) {
             if (me.isComplete() === true) {
 
                 let conn  = new Connection;
                 conn.post(me).then(function (response) {
                     resolve(response);
                 }, function (reason) {
                     reject(reason);
                 });
             }
             else {
                 reject(gpgme_error('MSG_INCOMPLETE'));
             }
         });
     }
 
 }
diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js
index 530590f9..7f24f320 100644
--- a/lang/js/src/Signature.js
+++ b/lang/js/src/Signature.js
@@ -1,203 +1,206 @@
 /* gpgme.js - Javascript integration for gpgme
  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GPGME.
  *
  * GPGME is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * GPGME is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this program; if not, see .
  * SPDX-License-Identifier: LGPL-2.1+
  *
  * Author(s):
  *     Maximilian Krambach 
  */
 import { gpgme_error } from './Errors';
 
 /**
  * Validates an object containing a signature, as sent by the nativeMessaging
  * interface
  * @param {Object} sigObject Object as returned by gpgme-json. The definition
  * of the expected values are to be found in {@link expKeys}, {@link expSum},
  * {@link expNote}.
  * @returns {GPGME_Signature|GPGME_Error} Signature Object
+ * @private
  */
 export function createSignature (sigObject){
     if (
         typeof (sigObject) !=='object' ||
         !sigObject.hasOwnProperty('summary') ||
         !sigObject.hasOwnProperty('fingerprint') ||
         !sigObject.hasOwnProperty('timestamp')
         // TODO check if timestamp is mandatory in specification
     ){
         return gpgme_error('SIG_WRONG');
     }
     let keys = Object.keys(sigObject);
     for (let i=0; i< keys.length; i++){
         // eslint-disable-next-line no-use-before-define
         if ( typeof (sigObject[keys[i]]) !== expKeys[keys[i]] ){
             return gpgme_error('SIG_WRONG');
         }
     }
     let sumkeys = Object.keys(sigObject.summary);
     for (let i=0; i< sumkeys.length; i++){
         // eslint-disable-next-line no-use-before-define
         if ( typeof (sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){
             return gpgme_error('SIG_WRONG');
         }
     }
     if (sigObject.hasOwnProperty('notations')){
         if (!Array.isArray(sigObject.notations)){
             return gpgme_error('SIG_WRONG');
         }
         for (let i=0; i < sigObject.notations.length; i++){
             let notation = sigObject.notations[i];
             let notekeys = Object.keys(notation);
             for (let j=0; j < notekeys.length; j++){
                 // eslint-disable-next-line no-use-before-define
                 if ( typeof (notation[notekeys[j]]) !== expNote[notekeys[j]] ){
                     return gpgme_error('SIG_WRONG');
                 }
             }
         }
     }
     return new GPGME_Signature(sigObject);
 }
 
 
 /**
  * Representing the details of a signature. The full details as given by
  * gpgme-json can be read from the _rawSigObject.
  *
  * Note to reviewers: This class should be read only except via
  * {@link createSignature}
  * @protected
  * @class
  */
 class GPGME_Signature {
 
     constructor (sigObject){
         this._rawSigObject = sigObject;
     }
     /**
      * @returns {String} the fingerprint of this signature
      */
     get fingerprint (){
         if (!this._rawSigObject.fingerprint){
             throw gpgme_error('SIG_WRONG');
         } else {
             return this._rawSigObject.fingerprint;
         }
     }
 
     /**
      * The expiration of this Signature as Javascript date, or null if
      * signature does not expire
      * @returns {Date | null}
      */
     get expiration (){
         if (!this._rawSigObject.exp_timestamp){
             return null;
         }
         return new Date(this._rawSigObject.exp_timestamp* 1000);
     }
 
     /**
      * The creation date of this Signature in Javascript Date
      * @returns {Date}
      */
     get timestamp (){
         return new Date(this._rawSigObject.timestamp * 1000);
     }
 
     /**
      * The overall validity of the key. If false, errorDetails may contain
      * additional information.
      */
     get valid () {
         if (this._rawSigObject.summary.valid === true){
             return true;
         } else {
             return false;
         }
     }
 
     /**
-     * gives more information on non-valid signatures. Refer to the gpgme
-     * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html
+     * Object with boolean properties giving more information on non-valid
+     * signatures. Refer to the [gpgme docs]{@link https://www.gnupg.org/documentation/manuals/gpgme/Verify.html}
      * for details on the values.
-     * @returns {Object} Object with boolean properties
      */
     get errorDetails (){
         let properties = ['revoked', 'key-expired', 'sig-expired',
             'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy',
             'sys-error'];
         let result = {};
         for (let i=0; i< properties.length; i++){
             if ( this._rawSigObject.hasOwnProperty(properties[i]) ){
                 result[properties[i]] = this._rawSigObject[properties[i]];
             }
         }
         return result;
     }
 }
 
 /**
- * Keys and their value's type for the signature Object
+ * Expected keys and their value's type for the signature Object
+ * @private
  */
 const expKeys = {
     'wrong_key_usage': 'boolean',
     'chain_model': 'boolean',
     'summary': 'object',
     'is_de_vs': 'boolean',
     'status_string':'string',
     'fingerprint':'string',
     'validity_string': 'string',
     'pubkey_algo_name':'string',
     'hash_algo_name':'string',
     'pka_address':'string',
     'status_code':'number',
     'timestamp':'number',
     'exp_timestamp':'number',
     'pka_trust':'number',
     'validity':'number',
     'validity_reason':'number',
     'notations': 'object'
 };
 
 /**
  * Keys and their value's type for the summary
+ * @private
  */
 const expSum = {
     'valid': 'boolean',
     'green': 'boolean',
     'red': 'boolean',
     'revoked': 'boolean',
     'key-expired': 'boolean',
     'sig-expired': 'boolean',
     'key-missing': 'boolean',
     'crl-missing': 'boolean',
     'crl-too-old': 'boolean',
     'bad-policy': 'boolean',
     'sys-error': 'boolean',
     'sigsum': 'object'
 };
 
 /**
  * Keys and their value's type for notations objects
+ * @private
  */
 const expNote = {
     'human_readable': 'boolean',
     'critical':'boolean',
     'name': 'string',
     'value': 'string',
     'flags': 'number'
 };
diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js
index 7b835ac2..91057242 100644
--- a/lang/js/src/gpgmejs.js
+++ b/lang/js/src/gpgmejs.js
@@ -1,448 +1,465 @@
 /* gpgme.js - Javascript integration for gpgme
  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GPGME.
  *
  * GPGME is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * GPGME is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this program; if not, see .
  * SPDX-License-Identifier: LGPL-2.1+
  *
  * Author(s):
  *     Maximilian Krambach 
  */
 
 
 import { GPGME_Message, createMessage } from './Message';
 import { toKeyIdArray } from './Helpers';
 import { gpgme_error } from './Errors';
 import { GPGME_Keyring } from './Keyring';
 import { createSignature } from './Signature';
 
 /**
  * @typedef {Object} decrypt_result
- * @property {String|Uint8Array} data The decrypted data
+ * @property {String|Uint8Array} data The decrypted data.
  * @property {String} format Indicating how the data was converted after being
- * received from gpgme.
+ * received from gpgme:
+ * 
  *      'ascii': Data was ascii-encoded and no further processed
  *      'string': Data was decoded into an utf-8 string,
  *      'base64': Data was not processed and is a base64 string
  *      'uint8': data was turned into a Uint8Array
- *
- * @property {Boolean} is_mime (optional) the data claims to be a MIME
- * object.
+ * 
+ * @property {Boolean} is_mime (optional) the data claims to be a MIME object. * @property {String} file_name (optional) the original file name * @property {signatureDetails} signatures Verification details for * signatures */ /** * @typedef {Object} signatureDetails - * @property {Boolean} all_valid Summary if all signatures are fully valid - * @property {Number} count Number of signatures found - * @property {Number} failures Number of invalid signatures - * @property {Array} signatures.good All valid signatures - * @property {Array} signatures.bad All invalid signatures + * @property {Boolean} all_valid Quick summary. True if all signatures are + * fully valid according to gnupg. + * @property {Number} count Number of signatures parsed. + * @property {Number} failures Number of signatures not passing as valid. This + * may imply bad signatures, or signatures with e.g. the public Key not being + * available. + * @property {GPGME_Signature[]} signatures.good Array of all signatures + * considered valid. + * @property {GPGME_Signature[]} signatures.bad All invalid signatures. */ /** - * @typedef {Object} encrypt_result The result of an encrypt operation - * @property {String} data The encrypted message + * @typedef {Object} encrypt_result The result of an encrypt operation, + * containing the encrypted data and some additional information. + * @property {String} data The encrypted message. * @property {String} format Indicating how the data was converted after being * received from gpgme. + *
  *      'ascii': Data was ascii-encoded and no further processed
  *      'string': Data was decoded into an utf-8 string,
  *      'base64': Data was not processed and is a base64 string
  *      'uint8': Data was turned into a Uint8Array
+ * 
*/ /** * @typedef { GPGME_Key | String | Object } inputKeys * Accepts different identifiers of a gnupg Key that can be parsed by * {@link toKeyIdArray}. Expected inputs are: One or an array of * GPGME_Keys; one or an array of fingerprint strings; one or an array of * openpgpjs Key objects. */ /** * @typedef {Object} signResult The result of a signing operation * @property {String} data The resulting data. Includes the signature in * clearsign mode - * @property {String} signature The detached signature (if in detached mode) + * @property {String} signature The detached signature (only present in in + * detached mode) */ /** @typedef {Object} verifyResult The result of a verification * @property {Boolean} data: The verified data * @property {Boolean} is_mime (optional) the data claims to be a MIME * object. * @property {signatureDetails} signatures Verification details for * signatures */ /** * The main entry point for gpgme.js. * @class */ export class GpgME { constructor (){ this._Keyring = null; } - /** - * setter for {@link setKeyring}. - * @param {GPGME_Keyring} keyring A Keyring to use - */ set Keyring (keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } + /** - * Accesses the {@link GPGME_Keyring}. + * Accesses the {@link GPGME_Keyring}. From the Keyring, all Keys can be + * accessed. */ get Keyring (){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; } return this._Keyring; } /** - * Encrypt (and optionally sign) data + * Encrypt data for the recipients specified in publicKeys. If privateKeys + * are submitted, the data will be signed by those Keys. * @param {Object} options * @param {String|Object} options.data text/data to be encrypted as String. - * Also accepts Objects with a getText method + * Also accepts Objects with a getText method. * @param {inputKeys} options.publicKeys * Keys used to encrypt the message - * @param {inputKeys} opions.secretKeys (optional) Keys used to sign the + * @param {inputKeys} options.secretKeys (optional) Keys used to sign the * message. If Keys are present, the operation requested is assumed * to be 'encrypt and sign' - * @param {Boolean} options.base64 (optional) The data will be interpreted - * as base64 encoded data. - * @param {Boolean} options.armor (optional) Request the output as armored - * block. - * @param {Boolean} options.wildcard (optional) If true, recipient - * information will not be added to the message. - * @param {Boolean} always_trust (optional, default true) This assumes that - * used keys are fully trusted. If set to false, encryption to a key not - * fully trusted in gnupg will fail - * @param {String} expect in case of armored:false, request how to return - * the binary result. Accepts 'base64' or 'uint8', defaults to 'base64'. - * @param {Object} additional use additional valid gpg options as + * @param {Boolean} options.base64 (optional, default: false) The data will + * be interpreted as base64 encoded data. + * @param {Boolean} options.armor (optional, default: true) Request the + * output as armored block. + * @param {Boolean} options.wildcard (optional, default: false) If true, + * recipient information will not be added to the message. + * @param {Boolean} options.always_trust (optional, default true) This + * assumes that used keys are fully trusted. If set to false, encryption to + * a key not fully trusted in gnupg will fail. + * @param {String} options.expect (default: 'base64') In case of + * armored:false, request how to return the binary result. + * Accepts 'base64' or 'uint8' + * @param {Object} options.additional use additional valid gpg options as * defined in {@link permittedOperations} * @returns {Promise} Object containing the encrypted * message and additional info. * @async */ encrypt ({ data, publicKeys, secretKeys, base64 = false, armor = true, wildcard, always_trust = true, expect = 'base64', additional = {} } = {}){ if (typeof arguments[0] !== 'object') { return Promise.reject(gpgme_error('PARAM_WRONG')); } if (!data || !publicKeys){ return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg); } if (armor === false){ msg.setParameter('armor', false); if (expect === 'uint8' || expect === 'base64') { msg.expected = expect; } else { return Promise.reject(gpgme_error('PARAM_WRONG')); } } else if (armor === true) { msg.setParameter('armor', true); } if (base64 === true) { msg.setParameter('base64', true); } if (always_trust === true) { msg.setParameter('always-trust', true); } let pubkeys = toKeyIdArray(publicKeys); if (!pubkeys.length) { return Promise.reject(gpgme_error('MSG_NO_KEYS')); } msg.setParameter('keys', pubkeys); let sigkeys = toKeyIdArray(secretKeys); if (sigkeys.length > 0) { msg.setParameter('signing_keys', sigkeys); } putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); } if (additional){ let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { try { msg.setParameter(additional_Keys[k], additional[additional_Keys[k]]); } catch (error){ return Promise.reject(error); } } } if (msg.isComplete() === true){ return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } } /** - * Decrypts a Message + * Decrypts (and verifies, if applicable) a message. * @param {Object} options * @param {String|Object} options.data text/data to be decrypted. Accepts - * Strings and Objects with a getText method - * @param {Boolean} options.base64 (optional) false if the data is an - * armored block, true if it is base64 encoded binary data - * @param {String} options.expect (optional) can be set to 'uint8' or - * 'base64'. Does no extra decoding on the data, and returns the decoded - * data as either Uint8Array or unprocessed(base64 encoded) string. + * Strings and Objects with a getText method. + * @param {Boolean} options.base64 (optional, default: false). Indicate that + * the input given is base64-encoded binary instead of an armored block in + * gpg armored form. + * @param {String} options.expect (optional). By default, the output is + * expected to be a string compatible with javascript. In cases of binary + * data the decryption may fail due to encoding problems. For data expected + * to return as binary data, the decroding after decryption can be bypassed: + *
+    *   'uint8': Return as Uint8Array
+    *   'base64': Return as unprocessed (base64 encoded) string.
+    * 
* @returns {Promise} Decrypted Message and information * @async */ decrypt ({ data, base64, expect } = {}){ if (typeof arguments[0] !== 'object') { return Promise.reject(gpgme_error('PARAM_WRONG')); } if (!data){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (msg instanceof Error){ return Promise.reject(msg); } if (base64 === true){ msg.setParameter('base64', true); } if (expect === 'base64' || expect === 'uint8'){ msg.expected = expect; } putData(msg, data); return new Promise(function (resolve, reject){ msg.post().then(function (result){ let returnValue = { data: result.data }; returnValue.format = result.format ? result.format : null; if (result.hasOwnProperty('dec_info')){ returnValue.is_mime = result.dec_info.is_mime ? true: false; if (result.dec_info.file_name) { returnValue.file_name = result.dec_info.file_name; } } if (!returnValue.file_name) { returnValue.file_name = null; } if (result.hasOwnProperty('info') && result.info.hasOwnProperty('signatures') && Array.isArray(result.info.signatures) ) { returnValue.signatures = collectSignatures( result.info.signatures); } if (returnValue.signatures instanceof Error){ reject(returnValue.signatures); } else { resolve(returnValue); } }, function (error){ reject(error); }); }); } /** - * Sign a Message + * Sign a Message. * @param {Object} options Signing options * @param {String|Object} options.data text/data to be signed. Accepts * Strings and Objects with a getText method. * @param {inputKeys} options.keys The key/keys to use for signing * @param {String} options.mode The signing mode. Currently supported: - * 'clearsign':The Message is embedded into the signature; - * 'detached': The signature is stored separately + *
+     *      'clearsign':The Message is embedded into the signature;
+     *      'detached': The signature is stored separately
+     * 
* @param {Boolean} options.base64 input is considered base64 * @returns {Promise} * @async */ sign ({ data, keys, mode = 'clearsign', base64 } = {}){ if (typeof arguments[0] !== 'object') { return Promise.reject(gpgme_error('PARAM_WRONG')); } if (!data){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let key_arr = toKeyIdArray(keys); if (key_arr.length === 0){ return Promise.reject(gpgme_error('MSG_NO_KEYS')); } let msg = createMessage('sign'); msg.setParameter('keys', key_arr); if (base64 === true){ msg.setParameter('base64', true); } msg.setParameter('mode', mode); putData(msg, data); return new Promise(function (resolve,reject) { msg.post().then( function (message) { if (mode === 'clearsign'){ resolve({ data: message.data } ); } else if (mode === 'detached') { resolve({ data: data, signature: message.data }); } }, function (error){ reject(error); }); }); } /** * Verifies data. * @param {Object} options * @param {String|Object} options.data text/data to be verified. Accepts * Strings and Objects with a getText method * @param {String} options.signature A detached signature. If not present, * opaque mode is assumed * @param {Boolean} options.base64 Indicating that data and signature are * base64 encoded * @returns {Promise} *@async */ verify ({ data, signature, base64 } = {}){ if (typeof arguments[0] !== 'object') { return Promise.reject(gpgme_error('PARAM_WRONG')); } if (!data){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let msg = createMessage('verify'); let dt = putData(msg, data); if (dt instanceof Error){ return Promise.reject(dt); } if (signature){ if (typeof signature !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } else { msg.setParameter('signature', signature); } } if (base64 === true){ msg.setParameter('base64', true); } return new Promise(function (resolve, reject){ msg.post().then(function (message){ if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { let returnValue = { signatures: collectSignatures(message.info.signatures) }; if (returnValue.signatures instanceof Error){ reject(returnValue.signatures); } else { returnValue.is_mime = message.info.is_mime? true: false; if (message.info.filename){ returnValue.file_name = message.info.filename; } returnValue.data = message.data; resolve(returnValue); } } }, function (error){ reject(error); }); }); } } /** * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set * @param { String| Object } data The data to enter. Expects either a string of * data, or an object with a getText method * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise * @private */ 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'); } } /** * Parses, validates and converts incoming objects into signatures. * @param {Array} sigs * @returns {signatureDetails} Details about the signatures + * @private */ function collectSignatures (sigs){ if (!Array.isArray(sigs)){ return gpgme_error('SIG_NO_SIGS'); } let summary = { all_valid: false, count: sigs.length, failures: 0, signatures: { good: [], bad: [], } }; for (let i=0; i< sigs.length; i++){ let sigObj = createSignature(sigs[i]); if (sigObj instanceof Error) { return gpgme_error('SIG_WRONG'); } if (sigObj.valid !== true){ summary.failures += 1; summary.signatures.bad.push(sigObj); } else { summary.signatures.good.push(sigObj); } } if (summary.failures === 0){ summary.all_valid = true; } return summary; } \ No newline at end of file diff --git a/lang/js/src/index.js b/lang/js/src/index.js index cf6e2d03..c52460cc 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -1,52 +1,54 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ import { GpgME } from './gpgmejs'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; /** - * Initializes gpgme.js by testing the nativeMessaging connection once. - * @returns {Promise | GPGME_Error} - * + * Main entry point for gpgme.js. It initializes by testing the nativeMessaging + * connection once, and then offers the available functions as method of the + * response object. + * An unsuccessful attempt will reject as a GPGME_Error. + * @returns {Promise} * @async */ function init (){ return new Promise(function (resolve, reject){ const connection = new Connection; connection.checkConnection(false).then( function (result){ if (result === true) { resolve(new GpgME()); } else { reject(gpgme_error('CONN_NO_CONNECT')); } }, function (){ // unspecific connection error. Should not happen reject(gpgme_error('CONN_NO_CONNECT')); }); }); } const exportvalue = { init:init }; export default exportvalue; \ No newline at end of file