diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 39e3a74a..b22eca73 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -81,6 +81,10 @@ const err_list = { 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', diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 8d7fd948..5d0c8160 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -80,55 +80,31 @@ class GPGME_Key { return _data.fingerprint; }; - /** - * Property indicating if the Key possesses a private/secret part. If - * this information is not yet cached, it returns an - * {@link GPGME_Error} with code 'KEY_NO_INIT'. Running - * {@link refreshKey} may help in this case. - * @returns {Boolean} If the Key has a secret subkey. - */ - this.hasSecret= function (){ - return this.get('hasSecret'); - }; - - /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). - * If 'cached' is false, the value will be resolved as a Promise. + * @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) { - let me = this; - return new Promise(function(resolve, reject) { - if (property === 'armored'){ - resolve(me.getArmor()); - } else if (property === 'hasSecret'){ - resolve(me.getHasSecret()); - } else if (validKeyProperties.hasOwnProperty(property)){ - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys && result.keys.length === 1 && - result.keys[0].hasOwnProperty(property)){ - resolve(result.keys[0][property]); - } else { - reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); - } - }, function(error){ - reject(gpgme_error(error)); - }); - } else { - reject(gpgme_error('PARAM_WRONG')); - } - }); + switch (property){ + case 'armored': + return this.getArmor(); + case 'hasSecret': + return this.getGnupgSecretState(); + default: + return getGnupgState(property); + } } else { + if (property === 'armored') { + return gpgme_error('KEY_ASYNC_ONLY'); + } if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); - } - if (!_data.hasOwnProperty(property)){ - return gpgme_error('KEY_NO_INIT'); } else { return (_data[property]); } @@ -160,7 +136,7 @@ class GPGME_Key { reject(gpgme_error('KEY_INVALID')); } else { _data = newdata; - me.getHasSecret().then(function(){ + me.getGnupgSecretState().then(function(){ me.getArmor().then(function(){ resolve(me); }, function(error){ @@ -195,7 +171,6 @@ class GPGME_Key { msg.setParameter('armor', true); msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ - _data.armored = result.data; resolve(result.data); }, function(error){ reject(error); @@ -205,37 +180,38 @@ class GPGME_Key { /** * Find out if the Key includes a secret part. Note that this is a - * rather nonperformant operation, as it needs to query gnupg twice. + * rather nonperformant operation. * If you want this inforrmation about more than a few Keys, it may be * advisable to run {@link Keyring.getKeys} instead. * @returns {Promise} True if a secret/private Key * is available in the gnupg Keyring * @async */ - this.getHasSecret = function (){ + 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); + }); } - 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); - }); }); }; @@ -262,25 +238,11 @@ class GPGME_Key { } /** - * @returns {String} The fingerprint defining this Key + * @returns {String} The fingerprint defining this Key. Convenience getter */ get fingerprint(){ return this.getFingerprint(); } - - /** - * Property for the export of armored Key. If the armored Key is not - * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. - * Running {@link refreshKey} may help in this case. - * @returns {String|GPGME_Error} The armored public Key block. - */ - get armored(){ - if (this.isAsync === true){ - return gpgme_error('KEY_NO_INIT'); - } else { - return this.get('armored'); - } - } } /** @@ -496,7 +458,31 @@ const validSubKeyProperties = { /** * Validation definition for Keys. Each valid Key property is represented - * as a key-value pair, with their value being a validation function + * 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 */ @@ -504,9 +490,6 @@ const validKeyProperties = { 'fingerprint': function(value){ return isFingerprint(value); }, - 'armored': function(value){ - return typeof(value === 'string'); - }, 'revoked': function(value){ return typeof(value) === 'boolean'; }, @@ -623,4 +606,75 @@ function validateKeyData(data){ } } 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){ + reject(gpgme_error('KEY_INVALID')); + } else { + const key = result.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]))); + } + } + 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]))); + } + } + 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 43ab96c8..766bab15 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -38,12 +38,11 @@ export class GPGME_Keyring { * * @param {String | Array} pattern (optional) A pattern to * search for in userIds or KeyIds. - * @param {Boolean} prepare_sync (optional) if set to true, the - * 'hasSecret' and 'armored' properties will be fetched for the Keys as - * well. These require additional calls to gnupg, resulting in a - * performance hungry operation. Calling them here enables direct, - * synchronous use of these properties for all keys, without having to - * resort to a refresh() first. + * @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>} @@ -97,7 +96,6 @@ export class GPGME_Keyring { break; } } - // TODO getArmor() to be used in sync } } let k = createKey(result.keys[i].fingerprint, diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 3304b1eb..25023bcb 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -304,10 +304,6 @@ function unittests (){ expect(result).to.be.an('array'); expect(result[0]).to.be.an.instanceof(GPGME_Key); expect(result[0].get('hasSecret')).to.be.a('boolean'); - // TODO: preparing sync for armored is still in discussion - // expect(result[0].get('armored')).to.be.a('string'); - // expect(result[0].get('armored')).to.include( - // '-----END PGP PUBLIC KEY BLOCK-----'); done(); } );