diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 73e74382..a8cd8b56 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,146 +1,154 @@ /* 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 */ 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_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 error object with some * information about meaning and origin * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR' * @param {*} info Error message passed through if code is 'GNUPG_ERROR' */ export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ // eslint-disable-next-line no-console console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ return new GPGME_Error(code, info); } else { return new GPGME_Error('GENERIC_ERROR'); } } class GPGME_Error extends Error{ constructor(code, msg=''){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ super(err_list[code].msg + '--' + msg); } else { super(err_list[code].msg); } } else { super(err_list['GENERIC_ERROR'].msg); } this.code = code || 'GENERIC_ERROR'; } set code(value){ this._code = value; } get code(){ return this._code; } } \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index e07a5934..451f936a 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,313 +1,311 @@ /* 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'; export class GPGME_Keyring { constructor(){ } /** * @param {String} pattern (optional) pattern A pattern to search for, * in userIds or KeyIds * @param {Boolean} prepare_sync (optional, default true) if set to true, * Key.armor and Key.hasSecret will be called, so they can be used * inmediately. This allows for full synchronous use. If set to false, * these will initially only be available as Promises in getArmor() and * getHasSecret() * @returns {Promise.>} * */ getKeys(pattern, prepare_sync){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); msg.post().then(function(result){ let resultset = []; let promises = []; if (result.keys.length === 0){ resolve([]); } else { for (let i=0; i< result.keys.length; i++){ let k = createKey(result.keys[i].fingerprint); k.setKeyData(result.keys[i]); if (prepare_sync === true){ promises.push(k.getArmor()); promises.push(k.getHasSecret()); } resultset.push(k); } if (promises.length > 0) { Promise.all(promises).then(function() { resolve(resultset); }, function(error){ reject(error); }); } else { resolve(resultset); } } }); }); } /** * Fetches the armored public Key blocks for all Keys matchin the pattern * (if no pattern is given, fetches all known to gnupg) * @param {String|Array} pattern (optional) * @returns {Promise} Armored Key blocks */ getKeysArmored(pattern) { return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.post().then(function(result){ resolve(result.data); }, function(error){ reject(error); }); }); } /** * Returns the Key to be used by default for signing operations, * looking up the gpg configuration, or returning the first key that * contains a secret key. * @returns {Promise} * * @async * TODO: getHasSecret always returns false at this moment, so this fucntion * still does not fully work as intended. * */ getDefaultKey() { 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(response){ if (response.value !== undefined && response.value.hasOwnProperty('string') && typeof(response.value.string) === 'string' ){ me.getKeys(response.value.string,true).then(function(keys){ if(keys.length === 1){ resolve(keys[0]); } else { reject(gpgme_error('KEY_NO_DEFAULT')); } }, function(error){ reject(error); }); } else { // TODO: this is overly 'expensive' in communication // and probably performance, too me.getKeys(null,true).then(function(keys){ for (let i=0; i < keys.length; i++){ - console.log(keys[i]); - console.log(keys[i].get('hasSecret')); if (keys[i].get('hasSecret') === true){ resolve(keys[i]); break; } if (i === keys.length -1){ reject(gpgme_error('KEY_NO_DEFAULT')); } } }, function(error){ reject(error); }); } }, function(error){ reject(error); }); }); } /** * * @param {String} armored Armored Key block of the Kex(s) to be imported * into gnupg * @param {Boolean} prepare_sync prepare the keys for synched use * (see getKeys()). * @returns {Promise>} An array of objects for the Keys * considered: * Key.key : The key itself as a GPGME_Key * Key.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. * Key.changes.userId: userIds changed * Key.changes.signature: signatures changed * Key.changes.subkey: subkeys changed * // TODO: not yet implemented: Information about Keys that failed * (e.g. malformed Keys, secretKeys are not accepted) */ importKey(armored, prepare_sync) { 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 = []; for (let res=0; res < response.result[0].imports.length; res++){ let result = response.result[0].imports[res]; let status = ''; if (result.status === 0){ status = 'nochange'; } else if ((result.status & 1) === 1){ status = 'newkey'; } else { status = 'change'; } let changes = {}; changes.userId = (result.status & 2) === 2; changes.signature = (result.status & 4) === 4; changes.subkey = (result.status & 8) === 8; //16 new secret key: not implemented fprs.push(result.fingerprint); infos[result.fingerprint] = { changes: changes, status: status }; } let resultset = []; if (prepare_sync === true){ me.getKeys(fprs, true).then(function(result){ for (let i=0; i < result.length; i++) { resultset.push({ key: result[i], changes: infos[result[i].fingerprint].changes, status: infos[result[i].fingerprint].status }); } resolve(resultset); }, function(error){ reject(error); }); } else { for (let i=0; i < fprs.length; i++) { resultset.push({ key: createKey(fprs[i]), changes: infos[fprs[i]].changes, status: infos[fprs[i]].status }); } resolve(resultset); } }, function(error){ reject(error); }); }); } deleteKey(fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); key.delete(); } } /** * 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_ from inside gpgmejs. * * @param {String} userId The user Id, e.g. "Foo Bar " * @param {*} algo (optional) algorithm to be used. See * {@link supportedKeyAlgos } below for supported values. * @param {Number} keyLength (optional) TODO * @param {Date} expires (optional) Expiration date. If not set, expiration * will be set to 'never' * * @returns{Promise} */ generateKey(userId, algo = 'default', keyLength, expires){ if ( typeof(userId) !== 'string' || supportedKeyAlgos.indexOf(algo) < 0 || (expires && !(expires instanceof Date)) // TODO keylength // TODO check for completeness of algos ){ 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)); } // TODO append keylength to algo msg.post().then(function(response){ me.getKeys(response.fingerprint, true).then( // TODO make prepare_sync (second parameter) optional here. function(result){ resolve(result); }, function(error){ reject(error); }); }, function(error) { reject(error); }); }); } } /** * A list of algorithms supported for key generation. */ const supportedKeyAlgos = [ 'default', 'rsa', 'dsa', 'elg', 'ed25519', 'cv25519' ]; \ No newline at end of file diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js new file mode 100644 index 00000000..d7d05983 --- /dev/null +++ b/lang/js/src/Signature.js @@ -0,0 +1,193 @@ +/* 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 + */ + +/** + * Validates a signature object and returns + * @param {Object} sigObject Object as returned by gpgme-json. The definition + * of the expected values are to be found in the constants 'expKeys', 'expSum', + * 'expNote' in this file. + * @returns {GPGME_Signature} Signature Object + */ + +import { gpgme_error } from './Errors'; + +export function createSignature(sigObject){ + if ( + typeof(sigObject) !=='object' || + !sigObject.hasOwnProperty('summary') || + !sigObject.hasOwnProperty('fingerpprint') || + !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 new GPGME_Signature(sigObject); +} + + +/** + * Representing the details of a signature. It is supposed to be read-only. The + * full details as given by gpgme-json can be accessed from the _rawSigObject. + * ) + */ +class GPGME_Signature { + constructor(sigObject){ + this._rawSigObject = sigObject; + } + + /** + * The signatures' fingerprint + */ + get fingerprint(){ + return this._rawSigObject.fingerprint; + } + + /** + * The expiration of this Signature as Javascript date, or null if + * signature does not expire + * @returns {Date | null} + */ + get expiration(){ + if (!this._rawSigObject.exp_timestamp){ + return null; + } + return new Date(this._rawSigObject.exp_timestamp* 1000); + } + + /** + * The creation date of this Signature in Javascript Date + * @returns {Date} + */ + get timestamp(){ + return new Date(this._rawSigObject.timestamp* 1000); + } + + /** + * The overall validity of the key. If false, errorDetails may contain + * additional information + */ + get valid() { + if (this._rawSigObject.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 + */ + get errorDetails(){ + let properties = ['revoked', 'key-expired', 'sig-expired', + 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', + 'sys-error']; + let result = {}; + for (let i=0; i< properties.length; i++){ + if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = this._rawSigObject[properties[i]]; + } + } + return result; + } + +} + +/** + * Keys and their value's type for the signature Object + */ +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' +}; + +/** + * 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 7fa7643c..a0f7e968 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,211 +1,333 @@ /* 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'; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port */ constructor(){ } set Keyring(keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } get Keyring(){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; } return this._Keyring; } /** * Encrypt (and optionally sign) a Message * @param {String|Object} data text/data to be encrypted as String. Also * accepts Objects with a getText method * @param {GPGME_Key|String|Array|Array} publicKeys * Keys used to encrypt the message * @param {GPGME_Key|String|Array|Array} secretKeys * (optional) Keys used to sign the message * @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 gpg options * (refer to src/permittedOperations) * @returns {Promise} Encrypted message: * data: The encrypted message * base64: Boolean indicating whether data is base64 encoded. * @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')); } } /** * Decrypt a Message * @param {String|Object} data text/data to be decrypted. Accepts Strings * and Objects with a getText method - * @returns {Promise} decrypted message: - data: The decrypted data. - base64: Boolean indicating whether data is base64 encoded. - mime: A Boolean indicating whether the data is a MIME object. - signatures: Array of signature Objects TODO not yet implemented. - // should be an object that can tell if all signatures are valid. + * @param {Boolean} base64 (optional) false if the data is an armored block, + * true if it is base64 encoded binary data + * @returns {Promise} result: Decrypted Message and information + * @returns {String} result.data: The decrypted data. + * @returns {Boolean} result.base64: indicating whether data is base64 + * encoded. + * @returns {Boolean} result.is_mime: Indicating whether the data is a MIME + * object. + * @returns {String} result.file_name: The optional original file name + * @returns {Object} message.signatures Verification details for signatures: + * @returns {Boolean} message.signatures.all_valid: true if all signatures + * are valid + * @returns {Number} message.signatures.count: Number of signatures found + * @returns {Number} message.signatures.failures Number of invalid + * signatures + * @returns {Array} message.signatures.signatures. Two arrays + * (good & bad) of {@link GPGME_Signature} objects, offering further + * information. + * * @async */ - decrypt(data){ + 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); - return msg.post(); + 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.mime ? true: false; + if (result.file_name){ + _result.file_name = result.file_name; + } + 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 decrypted. Accepts Strings * and Objects with a gettext methos * @param {GPGME_Key|String|Array|Array} keys The * key/keys to use for signing * @param {*} mode The signing mode. Currently supported: * 'clearsign': (default) The Message is embedded into the signature * 'detached': The signature is stored separately * @param {*} base64 input is considered base64 * @returns {Promise} * data: The resulting data. Includes the signature in clearsign mode * signature: The detached signature (if in detached mode) * @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('mode', mode); putData(msg, data); return new Promise(function(resolve,reject) { if (mode ==='detached'){ msg.expect= 'base64'; } msg.post().then( function(message) { if (mode === 'clearsign'){ resolve({ data: message.data} ); } else if (mode === 'detached') { resolve({ data: data, signature: message.data }); } }, function(error){ reject(error); }); }); } + + /** + * Verifies data. + * @param {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 + * // TODO verify if signature really is assumed to be base64 + * @returns {Promise} result: + * @returns {Boolean} result.data: The verified data + * @returns {Boolean} result.is_mime: The message claims it is MIME + * @returns {String} result.file_name: The optional filename of the message + * @returns {Boolean} result.all_valid: true if all signatures are valid + * @returns {Number} result.count: Number of signatures found + * @returns {Number} result.failures Number of unsuccessful signatures + * @returns {Array} result.signatures. Two arrays (good & bad) of + * {@link GPGME_Signature} objects, offering further information. + */ + verify(data, signature, base64 = false){ + let msg = createMessage('verify'); + let dt = this.putData(msg, data); + if (dt instanceof Error){ + return Promise.reject(dt); + } + if (signature){ + if (typeof(signature)!== 'string'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } else { + msg.setParameter('signature', signature); + } + } + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function (message){ + if (!message.info.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); + } + }, function(error){ + reject(error); + }); + }); + } } /** * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter */ function putData(message, data){ if (!message || !(message instanceof GPGME_Message) ) { return gpgme_error('PARAM_WRONG'); } if (!data){ return gpgme_error('PARAM_WRONG'); } else if (typeof(data) === 'string') { message.setParameter('data', data); } else if ( typeof(data) === 'object' && typeof(data.getText) === 'function' ){ let txt = data.getText(); if (typeof(txt) === 'string'){ message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } } else { return gpgme_error('PARAM_WRONG'); } } + +function collectSignatures(sigs){ + if (!Array.isArray(sigs)){ + return gpgme_error('SIG_NO_SIGS'); + } + let summary = { + all_valid: false, + count: sigs.length, + failures: 0, + signatures: { + good: [], + bad: [], + } + }; + for (let i=0; i< sigs.length; i++){ + let sigObj = createSignature(sigs[i]); + if (sigObj instanceof Error){ + return gpgme_error('SIG_WRONG'); + } + if (sigObj.valid !== true){ + summary.failures += 1; + summary.signatures.bad.push(sigObj); + } else { + summary.signatures.good.push(sigObj); + } + } + if (summary.failures === 0){ + summary.all_valid = true; + } + return summary; +} \ No newline at end of file