diff options
author | Maximilian Krambach <[email protected]> | 2018-05-25 17:02:18 +0000 |
---|---|---|
committer | Maximilian Krambach <[email protected]> | 2018-05-25 17:02:18 +0000 |
commit | 7a73d88aba106d571f121dc3230864c81a76e5db (patch) | |
tree | 171dafc5a054a725dafe317fcca7b9d0521637e1 /lang/js/src | |
parent | js: use version operation for connection checks (diff) | |
download | gpgme-7a73d88aba106d571f121dc3230864c81a76e5db.tar.gz gpgme-7a73d88aba106d571f121dc3230864c81a76e5db.zip |
js: implement Key handling (1)
--
* Keys can now be queried for information. Onne version queries gnug
directly (asynchronous Promise in javascript terms), the cached
version refreshes on demand.
* Small fixes:
src/Connection.js joins answers that stay json properly now
Diffstat (limited to 'lang/js/src')
-rw-r--r-- | lang/js/src/Connection.js | 8 | ||||
-rw-r--r-- | lang/js/src/Errors.js | 4 | ||||
-rw-r--r-- | lang/js/src/Helpers.js | 5 | ||||
-rw-r--r-- | lang/js/src/Key.js | 433 | ||||
-rw-r--r-- | lang/js/src/permittedOperations.js | 95 |
5 files changed, 379 insertions, 166 deletions
diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 07df5def..3b442622 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -215,7 +215,13 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = []; } - this._response[key].push(msg[key]); + if (Array.isArray(msg[key])) { + for (let i=0; i< msg[key].length; i++) { + this._response[key].push(msg[key][i]); + } + } else { + this._response[key].push(msg[key][i]); + } } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 7e98f319..fa8a4efe 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -67,6 +67,10 @@ const err_list = { msg:'Key object is invalid', type: 'error' }, + 'KEY_NOKEY': { + msg:'This key does not exist in GPG', + type: 'error' + }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index fd0e7200..b26f40fb 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -90,10 +90,11 @@ function hextest(key, len){ export function isFingerprint(string){ return hextest(string, 40); }; + /** - * TODO no usage; check if the input is a valid Hex string with a length of 16 + * check if the input is a valid Hex string with a length of 16 */ -function isLongId(string){ +export function isLongId(string){ return hextest(string, 16); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 075a190e..7d3d82b1 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -26,13 +26,19 @@ * */ -import { isFingerprint } from './Helpers' +import { isFingerprint, isLongId } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; import { Connection } from './Connection'; - +/** + * Validates the fingerprint, and checks for tha availability of a connection. + * If both are available, a Key will be returned. + * @param {String} fingerprint + * @param {Object} parent Either a Connection, or the invoking object with a + * Connection (e.g. Keyring) + */ export function createKey(fingerprint, parent){ if (!isFingerprint(fingerprint)){ return gpgme_error('PARAM_WRONG'); @@ -47,6 +53,9 @@ export function createKey(fingerprint, parent){ } } +/** + * Representing the Keys as stored in GPG + */ export class GPGME_Key { constructor(fingerprint, connection){ @@ -63,7 +72,7 @@ export class GPGME_Key { } get connection(){ - if (!this._fingerprint){ + if (!this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } if (!this._connection instanceof Connection){ @@ -74,171 +83,345 @@ export class GPGME_Key { } set fingerprint(fpr){ - if (isFingerprint(fpr) === true && !this._fingerprint){ - this._fingerprint = fpr; + if (isFingerprint(fpr) === true) { + if (this._data === undefined) { + this._data = {fingerprint: fpr}; + } else { + if (this._data.fingerprint === undefined){ + this._data.fingerprint = fpr; + } + } } } get fingerprint(){ - if (!this._fingerprint){ + if (!this._data || !this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } - return this._fingerprint; + return this._data.fingerprint; } /** - * hasSecret returns true if a secret subkey is included in this Key + * + * @param {Object} data Bulk set data for this key, with the Object as sent + * by gpgme-json. + * @returns {GPGME_Key|GPGME_Error} The Key object itself after values have + * been set */ - get hasSecret(){ - return this.checkKey('secret'); - } - - get isRevoked(){ - return this.checkKey('revoked'); - } - - get isExpired(){ - return this.checkKey('expired'); - } - - get isDisabled(){ - return this.checkKey('disabled'); - } - - get isInvalid(){ - return this.checkKey('invalid'); - } - - get canEncrypt(){ - return this.checkKey('can_encrypt'); - } + setKeydata(data){ + if (this._data === undefined) { + this._data = {}; + } + if ( + typeof(data) !== 'object') { + return gpgme_error('KEY_INVALID'); + } + if (!this._data.fingerprint && isFingerprint(data.fingerprint)){ + if (data.fingerprint !== this.fingerprint){ + return gpgme_error('KEY_INVALID'); + } + this._data.fingerprint = data.fingerprint; + } else if (this._data.fingerprint !== data.fingerprint){ + return gpgme_error('KEY_INVALID'); + } - get canSign(){ - return this.checkKey('can_sign'); - } + let booleans = ['expired', 'disabled','invalid','can_encrypt', + 'can_sign','can_certify','can_authenticate','secret', + 'is_qualified']; + for (let b =0; b < booleans.length; b++) { + if ( + !data.hasOwnProperty(booleans[b]) || + typeof(data[booleans[b]]) !== 'boolean' + ){ + return gpgme_error('KEY_INVALID'); + } + this._data[booleans[b]] = data[booleans[b]]; + } + if (typeof(data.protocol) !== 'string'){ + return gpgme_error('KEY_INVALID'); + } + // TODO check valid protocols? + this._data.protocol = data.protocol; - get canCertify(){ - return this.checkKey('can_certify'); - } + if (typeof(data.owner_trust) !== 'string'){ + return gpgme_error('KEY_INVALID'); + } + // TODO check valid values? + this._data.owner_trust = data.owner_trust; - get canAuthenticate(){ - return this.checkKey('can_authenticate'); - } + // TODO: what about origin ? + if (!Number.isInteger(data.last_update)){ + return gpgme_error('KEY_INVALID'); + } + this._data.last_update = data.last_update; - get isQualified(){ - return this.checkKey('is_qualified'); - } + this._data.subkeys = []; + if (data.hasOwnProperty('subkeys')){ + if (!Array.isArray(data.subkeys)){ + return gpgme_error('KEY_INVALID'); + } + for (let i=0; i< data.subkeys.length; i++) { + this._data.subkeys.push( + new GPGME_Subkey(data.subkeys[i])); + } + } - get armored(){ - let msg = createMessage ('export_key'); - msg.setParameter('armor', true); - if (msg instanceof Error){ - return gpgme_error('KEY_INVALID'); + this._data.userids = []; + if (data.hasOwnProperty('userids')){ + if (!Array.isArray(data.userids)){ + return gpgme_error('KEY_INVALID'); + } + for (let i=0; i< data.userids.length; i++) { + this._data.userids.push( + new GPGME_UserId(data.userids[i])); + } } - this.connection.post(msg).then(function(result){ - return result.data; - }); - // TODO return value not yet checked. Should result in an armored block - // in correct encoding + return this; } /** - * TODO returns true if this is the default key used to sign + * Query any property of the Key list + * (TODO: armor not in here, might be unexpected) + * @param {String} property Key property to be retreived + * @param {*} cached (optional) if false, the data will be directly queried + * from gnupg. + * @returns {*|Promise<*>} the value, or if not cached, a Promise + * resolving on the value */ - get isDefault(){ - throw('NOT_YET_IMPLEMENTED'); + get(property, cached=true) { + if (cached === false) { + let me = this; + return new Promise(function(resolve, reject) { + me.refreshKey().then(function(key){ + resolve(key.get(property, true)); + }, function(error){ + reject(error); + }); + }); + } else { + if (!this._data.hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); + } else { + return (this._data[property]); + } + } } /** - * get the Key's subkeys as GPGME_Key objects - * @returns {Array<GPGME_Key>} + * Reloads the Key from gnupg */ - get subkeys(){ - return this.checkKey('subkeys').then(function(result){ - // TBD expecting a list of fingerprints - if (!Array.isArray(result)){ - result = [result]; + refreshKey() { + let me = this; + return new Promise(function(resolve, reject) { + if (!me._data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); } - let resultset = []; - for (let i=0; i < result.length; i++){ - let subkey = new GPGME_Key(result[i], this.connection); - if (subkey instanceof GPGME_Key){ - resultset.push(subkey); + let msg = createMessage('keylist'); + msg.setParameter('sigs', true); + msg.setParameter('keys', me._data.fingerprint); + me.connection.post(msg).then(function(result){ + if (result.keys.length === 1){ + me.setKeydata(result.keys[0]); + resolve(me); + } else { + reject(gpgme_error('KEY_NOKEY')); } - } - return Promise.resolve(resultset); - }, function(error){ - //TODO this.checkKey fails + }, function (error) { + reject(gpgme_error('GNUPG_ERROR'), error); + }) }); } + //TODO: /** - * creation time stamp of the key - * @returns {Date|null} TBD + * Get the armored block of the non- secret parts of the Key. + * @returns {String} the armored Key block. + * Notice that this may be outdated cached info. Use the async getArmor if + * you need the most current info */ - get timestamp(){ - return this.checkKey('timestamp'); - //TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available. - } + // get armor(){ TODO } /** - * The expiration timestamp of this key TBD - * @returns {Date|null} TBD + * Query the armored block of the non- secret parts of the Key directly + * from gpg. + * Async, returns Promise<String> */ - get expires(){ - return this.checkKey('expires'); - // TODO convert to Date; check for 0 + // getArmor(){ TODO } + // + + // get hasSecret(){TODO} // confusing difference to Key.get('secret')! + // getHasSecret(){TODO async version} +} + +/** + * The subkeys of a Key. Currently, they cannot be refreshed separately + */ +class GPGME_Subkey { + + constructor(data){ + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++) { + this.setProperty(keys[i], data[keys[i]]); + } + } + + setProperty(property, value){ + if (!this._data){ + this._data = {}; + } + if (validSubKeyProperties.hasOwnProperty(property)){ + if (validSubKeyProperties[property](value) === true) { + this._data[property] = value; + } + } } /** - * getter name TBD - * @returns {String|Array<String>} The user ids associated with this key + * + * @param {String} property Information to request + * @returns {String | Number} + * TODO: date properties are numbers with Date in seconds */ - get userIds(){ - return this.checkKey('uids'); + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } + } +} + +class GPGME_UserId { + + constructor(data){ + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++) { + this.setProperty(keys[i], data[keys[i]]); + } + } + + setProperty(property, value){ + if (!this._data){ + this._data = {}; + } + if (validUserIdProperties.hasOwnProperty(property)){ + if (validUserIdProperties[property](value) === true) { + this._data[property] = value; + } + } } /** - * @returns {String} The public key algorithm supported by this subkey + * + * @param {String} property Information to request + * @returns {String | Number} + * TODO: date properties are numbers with Date in seconds */ - get pubkey_algo(){ - return this.checkKey('pubkey_algo'); + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } } +} - /** - * generic function to query gnupg information on a key. - * @param {*} property The gpgme-json property to check. - * TODO: check if Promise.then(return) - */ - checkKey(property){ - if (!this._fingerprint){ - return gpgme_error('KEY_INVALID'); +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 gpgme_error('NOT_YET_IMPLEMENTED'); - // TODO: async is not what is to be ecpected from Key information :( - if (!property || typeof(property) !== 'string' || - !permittedOperations['keyinfo'].hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); - } - let msg = createMessage ('keyinfo'); - if (msg instanceof Error){ - return gpgme_error('PARAM_WRONG'); - } - msg.setParameter('fingerprint', this.fingerprint); - this.connection.post(msg).then(function(result, error){ - if (error){ - return gpgme_error('GNUPG_ERROR',error.msg); - } else if (result.hasOwnProperty(property)){ - return result[property]; - } - else if (property == 'secret'){ - // TBD property undefined means "not true" in case of secret? - return false; - } else { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - }, function(error){ - return gpgme_error('GENERIC_ERROR'); - }); + 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); } -};
\ No newline at end of file +}; + +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); + } +} diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index aa02a8bc..42213ec3 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -172,49 +172,57 @@ export const permittedOperations = { } }, - - /** TBD: querying the Key's information (keyinfo) - TBD name: { - required: { - 'fingerprint': { - allowed: ['string'] - }, - }, - answer: { - type: ['TBD'], - data: [], - params: ['hasSecret','isRevoked','isExpired','armored', - 'timestamp','expires','pubkey_algo'], - infos: ['subkeys', 'userIds'] - // {'hasSecret': <Boolean>, - // 'isRevoked': <Boolean>, - // 'isExpired': <Boolean>, - // 'armored': <String>, // armored public Key block - // 'timestamp': <Number>, // - // 'expires': <Number>, - // 'pubkey_algo': TBD // TBD (optional?), - // 'userIds': Array<String>, - // 'subkeys': Array<String> Fingerprints of Subkeys - // } - }*/ - - /** - listkeys:{ - required: {}; + keylist:{ + required: {}, optional: { - 'with-secret':{ + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'chunksize': { + allowed: ['number'], + }, + // note: For the meaning of the flags, refer to + // https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html + 'secret': { allowed: ['boolean'] - },{ - 'pattern': { - allowed: ['string'] + }, + 'extern': { + allowed: ['boolean'] + }, + 'local':{ + allowed: ['boolean'] + }, + 'sigs':{ + allowed: ['boolean'] + }, + 'notations':{ + allowed: ['boolean'] + }, + 'tofu': { + allowed: ['boolean'] + }, + 'ephemeral': { + allowed: ['boolean'] + }, + 'validate': { + allowed: ['boolean'] + }, + // 'pattern': { TODO + // allowed: ['string'] + // }, + 'keys': { + allowed: ['string'], + array_allowed: true } }, - answer: { - type: ['TBD'], - infos: ['TBD'] - // keys: Array<String> Fingerprints representing the results + answer: { + type: [], + data: [], + params: [], + infos: ['keys'] + } }, - */ /** importkey: { @@ -256,4 +264,15 @@ export const permittedOperations = { * TBD key modification? * encryptsign: TBD */ + + version: { + required: {}, + optional: {}, + answer: { + type: [''], + data: ['gpgme'], + infos: ['info'], + params:[] + } + } } |