js: removed Key.armor property in synchronous use

--

* src/Key.js The synchronous mode for a Key does not offer an armor/
  armored property anymore. This frees up a lot of performance issues,
  also the armored expoort is expected to change quite often, so a
  cached version is not advisable.

* hasSecret/getHasSecret is now refactored, to reflect their uses.
  With get('hasSecret') there is a method that fetches the result.

* src/Key.js also some refactoring
This commit is contained in:
Maximilian Krambach 2018-08-17 16:57:41 +02:00
parent 754e799d35
commit ad39d54d19
4 changed files with 145 additions and 93 deletions

View File

@ -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',

View File

@ -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'));
switch (property){
case 'armored':
return this.getArmor();
case 'hasSecret':
return this.getGnupgSecretState();
default:
return getGnupgState(property);
}
}, function(error){
reject(gpgme_error(error));
});
} else {
reject(gpgme_error('PARAM_WRONG'));
if (property === 'armored') {
return gpgme_error('KEY_ASYNC_ONLY');
}
});
} else {
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,18 +180,18 @@ 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<Boolean|GPGME_Error>} 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);
@ -236,6 +211,7 @@ class GPGME_Key {
}, 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<GPGME_Subkey>} subkeys
* @param {Array<GPGME_UserId>} userids
* @param {Array<String>} 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';
},
@ -624,3 +607,74 @@ 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));
});
}
});
}

View File

@ -38,12 +38,11 @@ export class GPGME_Keyring {
*
* @param {String | Array<String>} 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<Array<GPGME_Key>>}
@ -97,7 +96,6 @@ export class GPGME_Keyring {
break;
}
}
// TODO getArmor() to be used in sync
}
}
let k = createKey(result.keys[i].fingerprint,

View File

@ -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();
}
);