diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8d381f15..a60fd215 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,282 +1,283 @@ /* 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 } 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 */ export class Connection{ constructor(){ - let _connection = chrome.runtime.connectNative('gpgmejson'); - + this._connection = chrome.runtime.connectNative('gpgmejson'); + } - /** - * Immediately closes an open port. - */ - this.disconnect = function () { - if (_connection){ - _connection.disconnect(); - _connection = null; - } - }; + /** + * 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" - * } - */ + /** + * @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 - */ - this.checkConnection = function(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); - }); + /** + * 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 - * 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 - * @async - */ - this.post = function (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; - return new Promise(function(resolve, reject){ - let answer = Object.freeze(new Answer(message)); - let listener = function(msg) { - if (!msg){ - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); + /** + * Sends a {@link GPGME_Message} via tghe 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 + * @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 { - let answer_result = answer.collect(msg); - if (answer_result !== true){ - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - reject(answer_result); + if (msg.more === true){ + me._connection.postMessage({ + 'op': 'getmore', + 'chunksize': chunksize + }); } else { - if (msg.more === true){ - _connection.postMessage({ - 'op': 'getmore', - 'chunksize': chunksize - }); + me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); + const message = answer.getMessage(); + if (message instanceof Error){ + reject(message); } else { - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - const message = answer.getMessage(); - if (message instanceof Error){ - reject(message); - } else { - resolve(message); - } + resolve(message); } } } - }; - _connection.onMessage.addListener(listener); - if (permittedOperations[message.operation].pinentry){ - return _connection.postMessage(message.message); - } else { - return Promise.race([ - _connection.postMessage(message.message), - function(resolve, reject){ - setTimeout(function(){ - _connection.disconnect(); - reject(gpgme_error('CONN_TIMEOUT')); - }, 5000); - }]).then(function(result){ - return result; - }, function(reject){ - if(!(reject instanceof Error)) { - _connection.disconnect(); - return gpgme_error('GNUPG_ERROR', reject); - } else { - return reject; - } - }); } - }); - }; + }; + 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 */ class Answer{ /** * @param {GPGME_Message} message */ constructor(message){ - const operation = message.operation; - const expected = message.getExpect(); - let response_b64 = null; + this._operation = message.operation; + this._expected = message.expected; + this._response_b64 = null; + } - this.getOperation = function(){ - return operation; - }; + get operation (){ + return this._operation; + } - this.getExpect = function(){ - return expected; - }; + get expected (){ + return this._expected; + } - /** - * Adds incoming base64 encoded data to the existing response - * @param {*} msg base64 encoded data. - * @returns {Boolean} - * - * @private - */ - this.collect = function (msg){ - if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (response_b64 === null){ - response_b64 = msg.response; - return true; - } else { - response_b64 += msg.response; - return true; - } - }; - /** - * Returns the base64 encoded answer data with the content verified - * against {@link permittedOperations}. - */ - this.getMessage = function (){ - if (response_b64 === undefined){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - let _decodedResponse = JSON.parse(atob(response_b64)); - let _response = {}; - let messageKeys = Object.keys(_decodedResponse); - let poa = permittedOperations[this.getOperation()].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: - if (!poa.data.hasOwnProperty(key)){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if( typeof(_decodedResponse[key]) !== poa.data[key] ){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (_decodedResponse.base64 === true - && poa.data[key] === 'string' - && this.getExpect() !== 'base64' - ){ - _response[key] = decodeURIComponent( - atob(_decodedResponse[key]).split('').map( - function(c) { - return '%' + - ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - } else { - _response[key] = decode(_decodedResponse[key]); - } - break; + /** + * 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; + } + } + /** + * Returns the base64 encoded answer data with the content verified + * against {@link permittedOperations}. + */ + getMessage(){ + if (this._response_b64 === null){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + let _decodedResponse = JSON.parse(atob(this._response_b64)); + let _response = {}; + 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: + if (!poa.data.hasOwnProperty(key)){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if( typeof(_decodedResponse[key]) !== poa.data[key] ){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (_decodedResponse.base64 === true + && poa.data[key] === 'string' + && this.expected !== 'base64' + ){ + _response[key] = decodeURIComponent( + atob(_decodedResponse[key]).split('').map( + function(c) { + return '%' + + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + } else { + _response[key] = decode(_decodedResponse[key]); } + break; } - return _response; - }; + } + return _response; } } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index b22eca73..2a35bc5e 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,170 +1,169 @@ /* 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}. */ 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' }, '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' * @returns {GPGME_Error} */ export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ - return Object.freeze(new GPGME_Error(code)); + 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 Object.freeze(new GPGME_Error(code, info)); + return new GPGME_Error(code, info); } else { - return Object.freeze(new GPGME_Error('GENERIC_ERROR')); + return new GPGME_Error('GENERIC_ERROR'); } } /** * An error class with additional info about the origin of the error, as string * @property {String} code Short description of origin and type of the error * @property {String} msg Additional info * @class * @protected * @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.getCode = function(){ - return code; - }; + this._code = code; } get code(){ - return this.getCode(); + return this._code; } } \ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index ea6fd883..37ec7f9d 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,681 +1,683 @@ /* 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|GPGME_Error} The verified and updated data */ export function createKey(fingerprint, async = false, data){ if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } if (data !== undefined){ - data = validateKeyData(data); + data = validateKeyData(fingerprint, data); } if (data instanceof Error){ return gpgme_error('KEY_INVALID'); } else { - return Object.freeze(new GPGME_Key(fingerprint, async, data)); + 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) * * @class */ class GPGME_Key { constructor(fingerprint, async, data){ /** * @property {Boolean} If true, most answers will be asynchronous */ - this.isAsync = async; + this._async = async; - let _data = {fingerprint: fingerprint.toUpperCase()}; + this._data = {fingerprint: fingerprint.toUpperCase()}; if (data !== undefined - && data.fingerprint.toUpperCase() === _data.fingerprint + && data.fingerprint.toUpperCase() === this._data.fingerprint ) { - _data = data; + this._data = data; } - this.getFingerprint = function(){ - if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ - return gpgme_error('KEY_INVALID'); - } - return _data.fingerprint; - }; + } - /** - * Query any property of the Key listed in {@link validKeyProperties} - * @param {String} property property to be retreived - * @returns {Boolean| String | Date | Array | Object |GPGME_Error} - * 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}) - */ - this.get = function(property) { - if (this.isAsync === true) { - switch (property){ - case 'armored': - return this.getArmor(); - case 'hasSecret': - return this.getGnupgSecretState(); - default: - return getGnupgState(property); - } + /** + * Query any property of the Key listed in {@link validKeyProperties} + * @param {String} property property to be retreived + * @returns {Boolean| String | Date | Array | Object |GPGME_Error} + * 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}) + */ + 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') { + return gpgme_error('KEY_ASYNC_ONLY'); + } + if (!validKeyProperties.hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); } else { - if (property === 'armored') { - return gpgme_error('KEY_ASYNC_ONLY'); - } - if (!validKeyProperties.hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); - } else { - return (_data[property]); - } + return (this._data[property]); } - }; + } + } - /** - * Reloads the Key information from gnupg. This is only useful if you - * 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} - * @async - */ - this.refreshKey = function() { - let me = this; - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('keylist'); - msg.setParameter('sigs', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys.length === 1){ - const newdata = validateKeyData( - _data.fingerprint, result.keys[0]); - if (newdata instanceof Error){ - reject(gpgme_error('KEY_INVALID')); - } else { - _data = newdata; - me.getGnupgSecretState().then(function(){ - me.getArmor().then(function(){ - resolve(me); - }, function(error){ - reject(error); - }); + /** + * Reloads the Key information from gnupg. This is only useful if you + * 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} + * @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); }); - } - } else { - reject(gpgme_error('KEY_NOKEY')); + }, function(error){ + reject(error); + }); } - }, 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} - * @async - */ - this.getArmor = function(){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); + } else { + reject(gpgme_error('KEY_NOKEY')); } - let msg = createMessage('export'); - msg.setParameter('armor', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - resolve(result.data); - }, function(error){ - reject(error); - }); + }, function (error) { + reject(gpgme_error('GNUPG_ERROR'), 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. - * @async - */ - this.getGnupgSecretState = function (){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } else { - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.setParameter('secret', true); - msg.post().then(function(result){ - _data.hasSecret = null; - if ( - result.keys && - result.keys.length === 1 && - result.keys[0].secret === true - ) { - _data.hasSecret = true; - resolve(true); - } else { - _data.hasSecret = false; - resolve(false); - } - }, function(error){ - reject(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} + * @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); }); - }; + }); + } - /** - * 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. - */ - this.delete= function (){ - return new Promise(function(resolve, reject){ - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('delete'); - msg.setParameter('key', _data.fingerprint); + /** + * 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. + * @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){ - resolve(result.success); + 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. + */ + 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.getFingerprint(); + return this._data.fingerprint; } } /** * Representing a subkey of a Key. * @class * @protected */ class GPGME_Subkey { /** * Initializes with the json data sent by gpgme-json * @param {Object} data * @private */ constructor(data){ - let _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){ if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ - _data[property] = new Date(value * 1000); + me._data[property] = new Date(value * 1000); } else { - _data[property] = value; + 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} - */ - this.get = function(property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; + /** + * 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 * @class * @protected */ class GPGME_UserId { /** * Initializes with the json data sent by gpgme-json * @param {Object} data * @private */ constructor(data){ - let _data = {}; + this._data = {}; + const me = this; let keys = Object.keys(data); const setProperty = function(property, value){ if (validUserIdProperties.hasOwnProperty(property)){ if (validUserIdProperties[property](value) === true) { if (property === 'last_update'){ - _data[property] = new Date(value*1000); + me._data[property] = new Date(value*1000); } else { - _data[property] = value; + 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} - */ - this.get = function (property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; + /** + * 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(data){ +function validateKeyData(fingerprint, data){ const key = {}; - if ( typeof(data) !== 'object' - || !data.fingerprint){ + 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(Object.freeze( - new GPGME_Subkey(data.subkeys[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(Object.freeze( - new GPGME_UserId(data.userids[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(result){ - if (!result.keys || result.keys.length !== 1){ + msg.post().then(function(res){ + if (!res.keys || res.keys.length !== 1){ reject(gpgme_error('KEY_INVALID')); } else { - const key = result.keys[0]; + 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(Object.freeze( - new GPGME_Subkey(key.subkeys[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(Object.freeze( - new GPGME_UserId(key.userids[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 d18fb649..de21736e 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,425 +1,426 @@ /* 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 { constructor(){ + } - /** - * Queries Keys (all Keys or a subset) from gnupg. - * - * @param {String | Array} pattern (optional) A pattern to - * search for in userIds or KeyIds. - * @param {Boolean} 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} search (optional) retrieve Keys from external - * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) - * @returns {Promise>} - * @static - * @async - */ - this.getKeys = function(pattern, prepare_sync=false, search=false){ - return new Promise(function(resolve, reject) { - let msg = createMessage('keylist'); - if (pattern !== undefined && pattern !== null){ - 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([]); + /** + * Queries Keys (all Keys or a subset) from gnupg. + * + * @param {String | Array} pattern (optional) A pattern to + * search for in userIds or KeyIds. + * @param {Boolean} 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} search (optional) retrieve Keys from external + * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) + * @returns {Promise>} + * @static + * @async + */ + getKeys (pattern, prepare_sync=false, search=false){ + return new Promise(function(resolve, reject) { + let msg = createMessage('keylist'); + if (pattern !== undefined && pattern !== null){ + 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'); + msg2.setParameter('keys', pattern); + msg2.setParameter('secret', true); + return msg2.post(); + }; } else { - let secondrequest; - if (prepare_sync === true) { - secondrequest = function() { - let msg2 = createMessage('keylist'); - 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; + 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); - }); - } - }); + 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. - */ + /** + * @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 {String|Array} pattern (optional) The Pattern to - * search for - * @param {Boolean} with_secret_fpr (optional) also return a list of - * fingerprints for the keys that have a secret key available - * @returns {Promise} Object containing the - * armored Key(s) and additional information. - * @static - * @async - */ - this.getKeysArmored = function(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 !== undefined && pattern !== null){ - msg.setParameter('keys', pattern); + /** + * Fetches the armored public Key blocks for all Keys matching the + * pattern (if no pattern is given, fetches all keys known to gnupg). + * @param {String|Array} pattern (optional) The Pattern to + * search for + * @param {Boolean} with_secret_fpr (optional) also return a list of + * fingerprints for the keys that have a secret key available + * @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 !== undefined && pattern !== null){ + msg.setParameter('keys', pattern); + } + msg.post().then(function(answer){ + const result = {armored: answer.data}; + if (with_secret_fpr === true + && answer.hasOwnProperty('sec-fprs') + ) { + result.secret_fprs = answer['sec-fprs']; } - msg.post().then(function(answer){ - const result = {armored: answer.data}; - if (with_secret_fpr === true - && answer.hasOwnProperty('sec-fprs') - ) { - result.secret_fprs = answer['sec-fprs']; - } - resolve(result); - }, function(error){ - reject(error); - }); + 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} - * @async - * @static - */ - this.getDefaultKey = function(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(resp.option.value[0].string, 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')); + /** + * 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} + * @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(resp.option.value[0].string, true).then( + function(keys){ + if(keys.length === 1){ + resolve(keys[0]); } else { - for (let i=0; i< result.keys.length; i++ ) { - if (result.keys[i].invalid === false) { - 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')); - } - } + reject(gpgme_error('KEY_NO_DEFAULT')); } }, function(error){ reject(error); }); - } - }, 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) { + 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} 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 - */ + /** + * @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 - */ - this.importKey = function (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; + /** + * 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} - * @async - * @static - */ - this.deleteKey = function(fingerprint){ - if (isFingerprint(fingerprint) === true) { - let key = createKey(fingerprint); - return key.delete(); - } else { - return Promise.reject(gpgme_error('KEY_INVALID')); - } - }; + /** + * Convenience function for deleting a Key. See {@link Key.delete} for + * further information about the return values. + * @param {String} fingerprint + * @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 {String} userId The user Id, e.g. 'Foo Bar ' - * @param {String} algo (optional) algorithm (and optionally key size) - * to be used. See {@link supportedKeyAlgos} below for supported - * values. - * @param {Date} expires (optional) Expiration date. If not set, - * expiration will be set to 'never' - * - * @return {Promise} - * @async - */ - this.generateKey = function (userId, algo = 'default', expires){ - if ( - typeof(userId) !== 'string' || - supportedKeyAlgos.indexOf(algo) < 0 || - (expires && !(expires instanceof Date)) - ){ - return Promise.reject(gpgme_error('PARAM_WRONG')); + /** + * 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 {String} userId The user Id, e.g. 'Foo Bar ' + * @param {String} algo (optional) algorithm (and optionally key size) + * to be used. See {@link supportedKeyAlgos} below for supported + * values. + * @param {Date} expires (optional) Expiration date. If not set, + * expiration will be set to 'never' + * + * @return {Promise} + * @async + */ + generateKey(userId, algo = 'default', expires){ + if ( + typeof(userId) !== 'string' || + supportedKeyAlgos.indexOf(algo) < 0 || + (expires && !(expires instanceof Date)) + ){ + 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 (expires){ + msg.setParameter('expires', + Math.floor(expires.valueOf()/1000)); + } else { + msg.setParameter('expires', 0); } - let me = this; - return new Promise(function(resolve, reject){ - let msg = createMessage('createkey'); - msg.setParameter('userid', userId); - msg.setParameter('algo', algo ); - if (expires){ - msg.setParameter('expires', - Math.floor(expires.valueOf()/1000)); - } else { - msg.setParameter('expires', 0); - } - msg.post().then(function(response){ - me.getKeys(response.fingerprint, true).then( - // TODO prepare_sync? - function(result){ - resolve(result); - }, function(error){ - reject(error); - }); - }, function(error) { - reject(error); - }); + msg.post().then(function(response){ + me.getKeys(response.fingerprint, true).then( + // TODO prepare_sync? + 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 e2c07344..2134fe99 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -1,250 +1,239 @@ /* 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|GPGME_Error} The Message object */ export function createMessage(operation){ if (typeof(operation) !== 'string'){ return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ - return Object.freeze(new GPGME_Message(operation)); + return new GPGME_Message(operation); } else { return 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}. * @class */ export class GPGME_Message { constructor(operation){ - let _msg = { + this._msg = { op: operation, chunksize: 1023* 1024 }; - let expected = null; + this._expected = null; + } - this.getOperation = function(){ - return _msg.op; - }; + get operation(){ + return this._msg.op; + } - this.setExpect = function(value){ - if (value === 'base64'){ - expected = value; - } - }; - this.getExpect = function(){ - return expected; - }; + set expected (value){ + if (value === 'base64'){ + this._expected = value; + } + } - /** - * The maximum size of responses from gpgme in bytes. As of July 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. - */ - this.setChunksize = function (value){ - if ( - Number.isInteger(value) && - value > 10 * 1024 && - value <= 1024 * 1024 - ){ - _msg.chunksize = value; - } - }; + get expected() { + return this._expected; + } + /** + * The maximum size of responses from gpgme in bytes. As of July 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; + } + } - this.getMsg = function(){ - return _msg; - }; + get chunksize(){ + return this._msg.chunksize; + } - this.getChunksize= function() { - return _msg.chunksize; - }; + /** + * Returns the prepared message with parameters and completeness checked + * @returns {Object|null} Object to be posted to gnupg, or null if + * incomplete + */ + get message() { + if (this.isComplete() === true){ + return this._msg; + } else { + return null; + } + } - /** - * Sets a parameter for the message. It validates with - * {@link permittedOperations} - * @param {String} param Parameter to set - * @param {any} value Value to set - * @returns {Boolean} If the parameter was set successfully - */ - this.setParameter = function ( param,value ){ - if (!param || typeof(param) !== 'string'){ + /** + * Sets a parameter for the message. It validates with + * {@link permittedOperations} + * @param {String} param Parameter to set + * @param {any} value Value to set + * @returns {Boolean} If the parameter was set successfully + */ + setParameter ( param,value ){ + if (!param || typeof(param) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + let po = permittedOperations[this._msg.op]; + if (!po){ + return gpgme_error('MSG_WRONG_OP'); + } + let poparam = null; + if (po.required.hasOwnProperty(param)){ + poparam = po.required[param]; + } else if (po.optional.hasOwnProperty(param)){ + poparam = po.optional[param]; + } else { + return gpgme_error('PARAM_WRONG'); + } + // 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; + } return gpgme_error('PARAM_WRONG'); - } - let po = permittedOperations[_msg.op]; - if (!po){ - return gpgme_error('MSG_WRONG_OP'); - } - let poparam = null; - if (po.required.hasOwnProperty(param)){ - poparam = po.required[param]; - } else if (po.optional.hasOwnProperty(param)){ - poparam = po.optional[param]; - } else { + case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 + && isNaN(value) === false){ + return true; + } return 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; + + case 'boolean': + if (poparam.allowed.indexOf('boolean') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); } - return gpgme_error('PARAM_WRONG'); - case 'number': - if ( - poparam.allowed.indexOf('number') >= 0 - && isNaN(value) === false){ + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + if (val.length > 0) { return true; } - return gpgme_error('PARAM_WRONG'); - - case 'boolean': - if (poparam.allowed.indexOf('boolean') >= 0){ + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ return true; } return gpgme_error('PARAM_WRONG'); - case 'object': - if (Array.isArray(val)){ - if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); - } - for (let i=0; i < val.length; i++){ - let res = checktype(val[i]); - if (res !== true){ - return res; - } - } - if (val.length > 0) { - return true; - } - } else if (val instanceof Uint8Array){ - if (poparam.allowed.indexOf('Uint8Array') >= 0){ - return true; - } - return gpgme_error('PARAM_WRONG'); - } else { - return gpgme_error('PARAM_WRONG'); - } - break; - default: - return gpgme_error('PARAM_WRONG'); - } - }; - let typechecked = checktype(value); - if (typechecked !== true){ - return typechecked; - } - if (poparam.hasOwnProperty('allowed_data')){ - if (poparam.allowed_data.indexOf(value) < 0){ + } else { return gpgme_error('PARAM_WRONG'); } + break; + default: + return gpgme_error('PARAM_WRONG'); } - _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. - */ - this.isComplete = function(){ - if (!_msg.op){ - return false; - } - let reqParams = Object.keys( - permittedOperations[_msg.op].required); - let msg_params = Object.keys(_msg); - for (let i=0; i < reqParams.length; i++){ - if (msg_params.indexOf(reqParams[i]) < 0){ - return false; - } + 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'); } - return true; - }; - /** - * Sends the Message via nativeMessaging and resolves with the answer. - * @returns {Promise} - * @async - */ - this.post = function(){ - let me = this; - return new Promise(function(resolve, reject) { - if (me.isComplete() === true) { - - let conn = Object.freeze(new Connection); - conn.post(me).then(function(response) { - resolve(response); - }, function(reason) { - reject(reason); - }); - } - else { - reject(gpgme_error('MSG_INCOMPLETE')); - } - }); - }; + } + this._msg[param] = value; + return true; } + /** - * Returns the prepared message with parameters and completeness checked - * @returns {Object|null} Object to be posted to gnupg, or null if - * incomplete + * 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. */ - get message(){ - if (this.isComplete() === true){ - return this.getMsg(); + isComplete(){ + if (!this._msg.op){ + return false; } - else { - return null; + 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; } - get operation(){ - return this.getOperation(); - } - get chunksize(){ - return this.getChunksize(); + /** + * Sends the Message via nativeMessaging and resolves with the answer. + * @returns {Promise} + * @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 55131b01..65365772 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -1,233 +1,197 @@ /* 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 */ 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++){ 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++){ 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++){ if ( typeof(notation[notekeys[j]]) !== expNote[notekeys[j]] ){ return gpgme_error('SIG_WRONG'); } } } } - return Object.freeze(new GPGME_Signature(sigObject)); + 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){ - let _rawSigObject = sigObject; - - this.getFingerprint = function(){ - if (!_rawSigObject.fingerprint){ - return gpgme_error('SIG_WRONG'); - } else { - return _rawSigObject.fingerprint; - } - }; - - /** - * The expiration of this Signature as Javascript date, or null if - * signature does not expire - * @returns {Date | null} - */ - this.getExpiration = function(){ - if (!_rawSigObject.exp_timestamp){ - return null; - } - return new Date(_rawSigObject.exp_timestamp* 1000); - }; - - /** - * The creation date of this Signature in Javascript Date - * @returns {Date} - */ - this.getTimestamp= function (){ - return new Date(_rawSigObject.timestamp * 1000); - }; - - /** - * The overall validity of the key. If false, errorDetails may contain - * additional information. - */ - this.getValid= function() { - if (_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 - * for details on the values. - * @returns {Object} Object with boolean properties - */ - this.getErrorDetails = function (){ - 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 ( _rawSigObject.hasOwnProperty(properties[i]) ){ - result[properties[i]] = _rawSigObject[properties[i]]; - } - } - return result; - }; + this._rawSigObject = sigObject; } - - /** - * Convenience getter for {@link getFingerprint} - */ get fingerprint(){ - return this.getFingerprint(); + if (!this._rawSigObject.fingerprint){ + return gpgme_error('SIG_WRONG'); + } else { + return this._rawSigObject.fingerprint; + } } /** - * Convenience getter for {@link getExpiration} + * The expiration of this Signature as Javascript date, or null if + * signature does not expire + * @returns {Date | null} */ get expiration(){ - return this.getExpiration(); + if (!this._rawSigObject.exp_timestamp){ + return null; + } + return new Date(this._rawSigObject.exp_timestamp* 1000); } /** - * Convenience getter for {@link getTimeStamp} + * The creation date of this Signature in Javascript Date + * @returns {Date} */ - get timestamp(){ - return this.getTimestamp(); + get timestamp (){ + return new Date(this._rawSigObject.timestamp * 1000); } /** - * Convenience getter for {@link getValid} + * The overall validity of the key. If false, errorDetails may contain + * additional information. */ - get valid(){ - return this.getValid(); + get valid () { + if (this._rawSigObject.summary.valid === true){ + return true; + } else { + return false; + } } /** - * Convenience getter for {@link getErrorDetails} + * gives more information on non-valid signatures. Refer to the gpgme + * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html + * for details on the values. + * @returns {Object} Object with boolean properties */ get errorDetails(){ - return this.getErrorDetails(); + 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 */ 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 */ 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 */ 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 18164366..4aa51759 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,394 +1,377 @@ /* 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} data The decrypted data * @property {Boolean} base64 indicating whether data is base64 encoded. * @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 */ /** * @typedef {Object} encrypt_result The result of an encrypt operation * @property {String} data The encrypted message * @property {Boolean} base64 Indicating whether data is base64 encoded. */ /** * @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) */ /** @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 {String} file_name (optional) the original file name * @property {signatureDetails} signatures Verification details for * signatures */ /** * The main entry point for gpgme.js. * @class */ export class GpgME { constructor(){ - let _Keyring = null; - - /** - * Sets a new Keyring to be used - * @param {GPGME_Keyring} keyring - */ - this.setKeyring = function(keyring){ - if (keyring && keyring instanceof GPGME_Keyring){ - _Keyring = keyring; - } - }; + this._Keyring = null; + } - /** - * Accesses the {@link GPGME_Keyring}. - */ - this.getKeyring = function(){ - if (!_Keyring){ - _Keyring = Object.freeze(new GPGME_Keyring); - } - return _Keyring; - }; + /** + * 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}. + */ + get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } + return this._Keyring; + } - /** - * Encrypt (and optionally sign) data - * @param {String|Object} data text/data to be encrypted as String. Also - * accepts Objects with a getText method - * @param {inputKeys} publicKeys - * Keys used to encrypt the message - * @param {inputKeys} secretKeys (optional) Keys used to sign the - * message. If Keys are present, the operation requested is assumed - * to be 'encrypt and sign' - * @param {Boolean} base64 (optional) The data will be interpreted as - * base64 encoded data. - * @param {Boolean} armor (optional) Request the output as armored - * block. - * @param {Boolean} wildcard (optional) If true, recipient information - * will not be added to the message. - * @param {Object} additional use additional valid gpg options as - * defined in {@link permittedOperations} - * @returns {Promise} Object containing the encrypted - * message and additional info. - * @async - */ - this.encrypt = function (data, publicKeys, secretKeys, base64=false, - armor=true, wildcard=false, additional = {} - ){ - let msg = createMessage('encrypt'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - msg.setParameter('armor', armor); - msg.setParameter('always-trust', true); - if (base64 === true) { - msg.setParameter('base64', true); - } - let pubkeys = toKeyIdArray(publicKeys); - 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++) { - msg.setParameter(additional_Keys[k], - additional[additional_Keys[k]]); - } - } - if (msg.isComplete() === true){ - return msg.post(); - } else { - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + /** + * Encrypt (and optionally sign) data + * @param {String|Object} data text/data to be encrypted as String. Also + * accepts Objects with a getText method + * @param {inputKeys} publicKeys + * Keys used to encrypt the message + * @param {inputKeys} secretKeys (optional) Keys used to sign the + * message. If Keys are present, the operation requested is assumed + * to be 'encrypt and sign' + * @param {Boolean} base64 (optional) The data will be interpreted as + * base64 encoded data. + * @param {Boolean} armor (optional) Request the output as armored + * block. + * @param {Boolean} wildcard (optional) If true, recipient information + * will not be added to the message. + * @param {Object} 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=false, additional = {}){ + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg); + } + msg.setParameter('armor', armor); + msg.setParameter('always-trust', true); + if (base64 === true) { + msg.setParameter('base64', true); + } + let pubkeys = toKeyIdArray(publicKeys); + 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++) { + msg.setParameter(additional_Keys[k], + additional[additional_Keys[k]]); } - }; + } + if (msg.isComplete() === true){ + return msg.post(); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + } - /** - * Decrypts a Message - * @param {String|Object} data text/data to be decrypted. Accepts - * Strings and Objects with a getText method - * @param {Boolean} base64 (optional) false if the data is an armored - * block, true if it is base64 encoded binary data - * @returns {Promise} Decrypted Message and information - * @async - */ - this.decrypt = function (data, base64=false){ - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let msg = createMessage('decrypt'); + /** + * Decrypts a Message + * @param {String|Object} data text/data to be decrypted. Accepts + * Strings and Objects with a getText method + * @param {Boolean} base64 (optional) false if the data is an armored + * block, true if it is base64 encoded binary data + * @returns {Promise} Decrypted Message and information + * @async + */ + decrypt (data, base64=false){ + if (data === undefined){ + 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); - } - putData(msg, data); - if (base64 === true){ - msg.setParameter('base64', true); - } - return new Promise(function(resolve, reject){ - msg.post().then(function(result){ - let _result = {data: result.data}; - _result.base64 = result.base64 ? true: false; - _result.is_mime = result.is_mime ? true: false; - if (result.file_name){ - _result.file_name = result.file_name; - } else { - _result.file_name = null; - } - if ( - result.hasOwnProperty('signatures') && - Array.isArray(result.signatures) - ) { - _result.signatures = collectSignatures( - result.signatures); - } - resolve(_result); - }, function(error){ - reject(error); - }); + if (msg instanceof Error){ + return Promise.reject(msg); + } + if (base64 === true){ + msg.setParameter('base64', true); + } + putData(msg, data); + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function(result){ + let _result = {data: result.data}; + _result.base64 = result.base64 ? true: false; + _result.is_mime = result.is_mime ? true: false; + if (result.file_name){ + _result.file_name = result.file_name; + } else { + _result.file_name = null; + } + if ( + result.hasOwnProperty('signatures') && + Array.isArray(result.signatures) + ) { + _result.signatures = collectSignatures( + result.signatures); + } + resolve(_result); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Sign a Message - * @param {String|Object} data text/data to be signed. Accepts Strings - * and Objects with a getText method. - * @param {inputKeys} keys The key/keys to use for signing - * @param {String} mode The signing mode. Currently supported: - * 'clearsign':The Message is embedded into the signature; - * 'detached': The signature is stored separately - * @param {Boolean} base64 input is considered base64 - * @returns {Promise} - * @async - */ - this.sign = function (data, keys, mode='clearsign', base64=false) { - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let key_arr = toKeyIdArray(keys); - if (key_arr.length === 0){ - return Promise.reject(gpgme_error('MSG_NO_KEYS')); - } - let msg = createMessage('sign'); + /** + * Sign a Message + * @param {String|Object} data text/data to be signed. Accepts Strings + * and Objects with a getText method. + * @param {inputKeys} keys The key/keys to use for signing + * @param {String} mode The signing mode. Currently supported: + * 'clearsign':The Message is embedded into the signature; + * 'detached': The signature is stored separately + * @param {Boolean} base64 input is considered base64 + * @returns {Promise} + * @async + */ + sign (data, keys, mode='clearsign', base64=false) { + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let key_arr = toKeyIdArray(keys); + if (key_arr.length === 0){ + return Promise.reject(gpgme_error('MSG_NO_KEYS')); + } + let msg = createMessage('sign'); - msg.setParameter('keys', key_arr); - if (base64 === true){ - msg.setParameter('base64', true); + msg.setParameter('keys', key_arr); + if (base64 === true){ + msg.setParameter('base64', true); + } + msg.setParameter('mode', mode); + putData(msg, data); + return new Promise(function(resolve,reject) { + if (mode ==='detached'){ + msg.expected ='base64'; } - msg.setParameter('mode', mode); - putData(msg, data); - return new Promise(function(resolve,reject) { - if (mode ==='detached'){ - msg.setExpect('base64'); + msg.post().then( function(message) { + if (mode === 'clearsign'){ + resolve({ + data: message.data} + ); + } else if (mode === 'detached') { + resolve({ + data: data, + signature: message.data + }); } - 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); - }); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Verifies data. - * @param {String|Object} data text/data to be verified. Accepts Strings - * and Objects with a getText method - * @param {String} (optional) A detached signature. If not present, - * opaque mode is assumed - * @param {Boolean} (optional) Data and signature are base64 encoded - * @returns {Promise} - *@async - */ - this.verify= function (data, signature, base64 = false){ - let msg = createMessage('verify'); - let dt = putData(msg, data); - if (dt instanceof Error){ - return Promise.reject(dt); + /** + * Verifies data. + * @param {String|Object} data text/data to be verified. Accepts Strings + * and Objects with a getText method + * @param {String} (optional) A detached signature. If not present, + * opaque mode is assumed + * @param {Boolean} (optional) Data and signature are base64 encoded + * @returns {Promise} + *@async + */ + verify (data, signature, base64 = false){ + 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 (signature){ - if (typeof(signature)!== 'string'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); + } + 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 { - 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 _result = collectSignatures( - message.info.signatures); - _result.is_mime = message.info.is_mime? true: false; - if (message.info.filename){ - _result.file_name = message.info.filename; - } - _result.data = message.data; - resolve(_result); + let _result = collectSignatures( + message.info.signatures); + _result.is_mime = message.info.is_mime? true: false; + if (message.info.filename){ + _result.file_name = message.info.filename; } - }, function(error){ - reject(error); - }); + _result.data = message.data; + resolve(_result); + } + }, function(error){ + reject(error); }); - }; - } - - /** - * setter for {@link setKeyring}. - * @param {GPGME_Keyring} keyring A Keyring to use - */ - set Keyring(keyring){ - this.setKeyring(keyring); - } - - /** - * Accesses the {@link GPGME_Keyring}. - */ - get Keyring(){ - return this.getKeyring(); + }); } } /** * 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 */ 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(sigObj); } 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 51f07538..ad4b05b0 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -1,52 +1,52 @@ /* 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} * * @async */ function init(){ return new Promise(function(resolve, reject){ - const connection = Object.freeze(new Connection); + const connection = new Connection; connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(Object.freeze(new GpgME())); + 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 = Object.freeze({init:init}); +const exportvalue = {init:init}; export default exportvalue; \ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 25023bcb..47eeabf2 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,405 +1,375 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ import './node_modules/mocha/mocha'; /*global mocha, it, describe*/ import './node_modules/chai/chai';/*global chai*/ import { helper_params as hp } from './unittest_inputvalues'; import { message_params as mp } from './unittest_inputvalues'; import { whatever_params as wp } from './unittest_inputvalues'; import { key_params as kp } from './unittest_inputvalues'; import { Connection } from './src/Connection'; import { gpgme_error } from './src/Errors'; import { toKeyIdArray , isFingerprint } from './src/Helpers'; -import { GPGME_Key , createKey } from './src/Key'; +import { createKey } from './src/Key'; import { GPGME_Keyring } from './src/Keyring'; import {GPGME_Message, createMessage} from './src/Message'; mocha.setup('bdd'); const expect = chai.expect; chai.config.includeStack = true; function unittests (){ describe('Connection testing', function(){ it('Connecting', function(done) { let conn0 = new Connection; conn0.checkConnection().then(function(answer) { expect(answer).to.not.be.empty; expect(answer.gpgme).to.not.be.undefined; expect(answer.gpgme).to.be.a('string'); expect(answer.info).to.be.an('Array'); expect(conn0.disconnect).to.be.a('function'); expect(conn0.post).to.be.a('function'); done(); }); }); it('Disconnecting', function(done) { let conn0 = new Connection; conn0.checkConnection(false).then(function(answer) { expect(answer).to.be.true; conn0.disconnect(); conn0.checkConnection(false).then(function(result) { expect(result).to.be.false; done(); }); }); }); }); describe('Error Object handling', function(){ // TODO: new GPGME_Error codes it('check the Timeout error', function(){ let test0 = gpgme_error('CONN_TIMEOUT'); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('CONN_TIMEOUT'); }); it('Error Object returns generic code if code is not listed', function(){ let test0 = gpgme_error(hp.invalidErrorCode); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('GENERIC_ERROR'); } ); it('Warnings like PARAM_IGNORED should not return errors', function(){ let test0 = gpgme_error('PARAM_IGNORED'); expect(test0).to.be.null; }); }); describe('Fingerprint checking', function(){ it('isFingerprint(): valid Fingerprint', function(){ let test0 = isFingerprint(hp.validFingerprint); expect(test0).to.be.true; }); it('isFingerprint(): invalid Fingerprints', function(){ for (let i=0; i < hp.invalidFingerprints.length; i++){ let test0 = isFingerprint(hp.invalidFingerprints[i]); expect(test0).to.be.false; } }); }); describe('toKeyIdArray() (converting input to fingerprint)', function(){ it('Correct fingerprint string', function(){ let test0 = toKeyIdArray(hp.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validFingerprint); }); - it('correct GPGME_Key', function(){ - expect(hp.validGPGME_Key).to.be.an.instanceof(GPGME_Key); - let test0 = toKeyIdArray(hp.validGPGME_Key); - - expect(test0).to.be.an('array'); - expect(test0).to.include(hp.validGPGME_Key.fingerprint); - }); - it('openpgpjs-like object', function(){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); expect(test0).to.include( hp.valid_openpgplike.primaryKey.getFingerprint()); }); it('Array of valid inputs', function(){ let test0 = toKeyIdArray(hp.validKeys); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf(hp.validKeys.length); }); it('Incorrect inputs', function(){ it('valid Long ID', function(){ let test0 = toKeyIdArray(hp.validLongId); expect(test0).to.be.empty; }); it('invalidFingerprint', function(){ let test0 = toKeyIdArray(hp.invalidFingerprint); expect(test0).to.be.empty; }); it('invalidKeyArray', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray); expect(test0).to.be.empty; }); it('Partially invalid array', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf( hp.invalidKeyArray_OneBad.length - 1); }); }); }); describe('GPGME_Key', function(){ - - it('correct Key initialization', function(){ - let key = createKey(kp.validKeyFingerprint); - expect(key).to.be.an.instanceof(GPGME_Key); - }); it('Key has data after a first refresh', function(done) { let key = createKey(kp.validKeyFingerprint); key.refreshKey().then(function(key2){ - expect(key2).to.be.an.instanceof(GPGME_Key); expect(key2.get).to.be.a('function'); for (let i=0; i < kp.validKeyProperties.length; i++) { let prop = key2.get(kp.validKeyProperties[i]); expect(prop).to.not.be.undefined; expect(prop).to.be.a('boolean'); } expect(isFingerprint(key2.get('fingerprint'))).to.be.true; expect( key2.get('fingerprint')).to.equal(kp.validKeyFingerprint); expect( key2.get('fingerprint')).to.equal(key.fingerprint); done(); }); }); it('Non-cached key async data retrieval', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('can_authenticate').then(function(result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async armored Key', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('armored').then(function(result){ expect(result).to.be.a('string'); expect(result).to.include('KEY BLOCK-----'); done(); }); }); it('Non-cached key async hasSecret', function (done){ let key = createKey(kp.validKeyFingerprint, true); key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); done(); }); }); it('Non-cached key async hasSecret (no secret in Key)', function (done){ let key = createKey(kp.validFingerprintNoSecret, true); - expect(key).to.be.an.instanceof(GPGME_Key); key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); expect(result).to.equal(false); done(); }); }); it('Querying non-existing Key returns an error', function(done) { let key = createKey(kp.invalidKeyFingerprint); key.refreshKey().then(function(){}, function(error){ expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('KEY_NOKEY'); done(); }); }); it('createKey returns error if parameters are wrong', function(){ for (let i=0; i< 4; i++){ let key0 = createKey(wp.four_invalid_params[i]); expect(key0).to.be.an.instanceof(Error); expect(key0.code).to.equal('PARAM_WRONG'); } }); - it('malformed GPGME_Key cannot be used', function(){ - for (let i=0; i < 4; i++){ - let key = new GPGME_Key(wp.four_invalid_params[i]); - expect(key.fingerprint).to.be.an.instanceof(Error); - expect(key.fingerprint.code).to.equal('KEY_INVALID'); - } - }); - - it('Overwriting getFingerprint does not work', function(){ - const evilFunction = function(){ - return 'bad Data'; - }; - let key = createKey(kp.validKeyFingerprint, true); - expect(key.fingerprint).to.equal(kp.validKeyFingerprint); - try { - key.getFingerprint = evilFunction; - } - catch(e) { - expect(e).to.be.an.instanceof(TypeError); - } - expect(key.fingerprint).to.equal(kp.validKeyFingerprint); - expect(key.getFingerprint).to.not.equal(evilFunction); - }); - // TODO: tests for subkeys - // TODO: tests for userids - // TODO: some invalid tests for key/keyring + // it('Overwriting getFingerprint does not work', function(){ + // const evilFunction = function(){ + // return 'bad Data'; + // }; + // let key = createKey(kp.validKeyFingerprint, true); + // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + // try { + // key.getFingerprint = evilFunction; + // } + // catch(e) { + // expect(e).to.be.an.instanceof(TypeError); + // } + // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + // expect(key.getFingerprint).to.not.equal(evilFunction); + // }); }); describe('GPGME_Keyring', function(){ it('correct Keyring initialization', function(){ let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.getKeys).to.be.a('function'); }); it('Loading Keys from Keyring, to be used synchronously', function(done){ let keyring = new GPGME_Keyring; keyring.getKeys(null, true).then(function(result){ expect(result).to.be.an('array'); - expect(result[0]).to.be.an.instanceof(GPGME_Key); expect(result[0].get('hasSecret')).to.be.a('boolean'); - // expect(result[0].get('armored')).to.include( - // '-----END PGP PUBLIC KEY BLOCK-----'); done(); }); } ); it('Loading specific Key from Keyring, to be used synchronously', function(done){ let keyring = new GPGME_Keyring; keyring.getKeys(kp.validKeyFingerprint, true).then( function(result){ expect(result).to.be.an('array'); - expect(result[0]).to.be.an.instanceof(GPGME_Key); expect(result[0].get('hasSecret')).to.be.a('boolean'); done(); } ); } ); it('Querying non-existing Key from Keyring', function(done){ let keyring = new GPGME_Keyring; keyring.getKeys(kp.invalidKeyFingerprint, true).then( function(result){ expect(result).to.be.an('array'); expect(result.length).to.equal(0); done(); } ); }); }); describe('GPGME_Message', function(){ it('creating encrypt Message', function(){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); expect(test0.isComplete()).to.be.false; }); it('Message is complete after setting mandatory data', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete()).to.be.true; }); it('Message is not complete after mandatory data is empty', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', ''); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete()).to.be.false; }); it('Complete Message contains the data that was set', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.message).to.not.be.null; expect(test0.message).to.have.keys('op', 'data', 'keys', 'chunksize'); expect(test0.message.op).to.equal('encrypt'); expect(test0.message.data).to.equal( mp.valid_encrypt_data); }); it ('Not accepting non-allowed operation', function(){ let test0 = createMessage(mp.invalid_op_action); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('MSG_WRONG_OP'); }); it('Not accepting wrong parameter type', function(){ let test0 = createMessage(mp.invalid_op_type); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('PARAM_WRONG'); }); it('Not accepting wrong parameter name', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let i=0; i < mp.invalid_param_test.invalid_param_names.length; i++){ let ret = test0.setParameter( mp.invalid_param_test.invalid_param_names[i], 'Somevalue'); expect(ret).to.be.an.instanceof(Error); expect(ret.code).to.equal('PARAM_WRONG'); } }); it('Not accepting wrong parameter value', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let j=0; j < mp.invalid_param_test.invalid_values_0.length; j++){ let ret = test0.setParameter( mp.invalid_param_test.validparam_name_0, mp.invalid_param_test.invalid_values_0[j]); expect(ret).to.be.an.instanceof(Error); expect(ret.code).to.equal('PARAM_WRONG'); } }); }); } export default {unittests}; \ No newline at end of file