js: more Keyring/Key handling

--

* src/Keys.js
  - made setKeyData more consistent with other methods
  - added convenience methods (Key.armored, Key.hasSecret)
  - Added a Key delete function

* src/Keyring.js:
  - added a getkeysArmored which allows for bulk export of public Keys

gpgmejs:
  - removed deleteKey. It is now a method of the Key itself
  - Encrypt: Added some common options as parameter, and the
    possibility to set all allowed flags via an additional Object
This commit is contained in:
Maximilian Krambach 2018-05-30 17:05:54 +02:00
parent 53ce2b94bc
commit 332b4adbcc
4 changed files with 227 additions and 104 deletions

View File

@ -71,6 +71,10 @@ const err_list = {
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'
}
// generic
'PARAM_WRONG':{
msg: 'Invalid parameter was found',

View File

@ -93,56 +93,31 @@ export class GPGME_Key {
} else if (this._data.fingerprint !== data.fingerprint){
return gpgme_error('KEY_INVALID');
}
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'
){
let dataKeys = Object.keys(data);
for (let i=0; i< dataKeys.length; i++){
if (!validKeyProperties.hasOwnProperty(dataKeys[i])){
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;
if (typeof(data.owner_trust) !== 'string'){
return gpgme_error('KEY_INVALID');
}
// TODO check valid values?
this._data.owner_trust = data.owner_trust;
// TODO: what about origin ?
if (!Number.isInteger(data.last_update)){
return gpgme_error('KEY_INVALID');
}
this._data.last_update = data.last_update;
this._data.subkeys = [];
if (data.hasOwnProperty('subkeys')){
if (!Array.isArray(data.subkeys)){
if (validKeyProperties[dataKeys[i]](data[dataKeys[i]]) !== true ){
return gpgme_error('KEY_INVALID');
}
for (let i=0; i< data.subkeys.length; i++) {
this._data.subkeys.push(
new GPGME_Subkey(data.subkeys[i]));
}
}
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]));
switch (dataKeys[i]){
case 'subkeys':
this._data.subkeys = [];
for (let i=0; i< data.subkeys.length; i++) {
this._data.subkeys.push(
new GPGME_Subkey(data.subkeys[i]));
}
break;
case 'userids':
this._data.userids = [];
for (let i=0; i< data.userids.length; i++) {
this._data.userids.push(
new GPGME_UserId(data.userids[i]));
}
break;
default:
this._data[dataKeys[i]] = data[dataKeys[i]];
}
}
return this;
@ -161,7 +136,9 @@ export class GPGME_Key {
if (cached === false) {
let me = this;
return new Promise(function(resolve, reject) {
if (property === 'armor'){
if (!validKeyProperties.hasOwnProperty(property)){
reject('PARAM_WRONG');
} else if (property === 'armored'){
resolve(me.getArmor());
} else if (property === 'hasSecret'){
resolve(me.getHasSecret());
@ -173,15 +150,23 @@ export class GPGME_Key {
});
}
});
} else {
if (!this._data.hasOwnProperty(property)){
} else {
if (!validKeyProperties.hasOwnProperty(property)){
return gpgme_error('PARAM_WRONG');
}
if (!this._data.hasOwnProperty(property)){
return gpgme_error('KEY_NO_INIT');
} else {
return (this._data[property]);
}
}
}
get armored () {
return this.get('armored');
//TODO exception if empty
}
/**
* Reloads the Key from gnupg
*/
@ -207,15 +192,6 @@ export class GPGME_Key {
});
}
/**
* 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 armor(){ TODO }
/**
* Query the armored block of the non- secret parts of the Key directly
* from gpg.
@ -279,6 +255,49 @@ export class GPGME_Key {
})
});
}
/**
* Convenience function to be directly used as properties of the Key
* Notice that these rely on cached info and may be outdated. Use the async
* get(property, false) if you need the most current info
*/
/**
* @returns {String} The armored public Key block
*/
get armored(){
return this.get('armored', true);
}
/**
* @returns {Boolean} If the key is considered a "private Key",
* i.e. owns a secret subkey.
*/
get hasSecret(){
return this.get('hasSecret', true);
}
/**
* Deletes the public Key from the GPG Keyring. Note that a deletion of a
* secret key is not supported by the native backend.
* @returns {Boolean} Success if key was deleted, rejects with a GPG error
* otherwise
*/
delete(){
let 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);
})
});
}
}
/**
@ -453,3 +472,78 @@ const validSubKeyProperties = {
return (Number.isInteger(value) && value > 0);
}
}
const validKeyProperties = {
//TODO better validation?
'fingerprint': function(value){
return isFingerprint(value);
},
'armored': function(value){
return typeof(value === 'string');
},
'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';
}
}

View File

@ -20,7 +20,7 @@
import {createMessage} from './Message'
import {GPGME_Key, createKey} from './Key'
import { isFingerprint } from './Helpers';
import { isFingerprint, toKeyIdArray } from './Helpers';
import { gpgme_error } from './Errors';
export class GPGME_Keyring {
@ -43,10 +43,10 @@ export class GPGME_Keyring {
return new Promise(function(resolve, reject) {
let msg;
msg = createMessage('keylist');
if (pattern && typeof(pattern) === 'string'){
if (pattern !== undefined){
msg.setParameter('keys', pattern);
}
msg.setParameter('sigs', true); //TODO do we need this?
msg.setParameter('sigs', true);
msg.post().then(function(result){
let resultset = [];
let promises = [];
@ -72,10 +72,30 @@ export class GPGME_Keyring {
});
});
}
// TODO:
// deleteKey(key, include_secret=false)
// getKeysArmored(pattern) //just dump all armored keys
/**
* 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<String>} pattern (optional)
* @returns {Promise<String>} Armored Key blocks
*/
getKeysArmored(pattern) {
if (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);
});
}
// getDefaultKey() Big TODO
// importKeys(armoredKeys)
// generateKey --> TODO (Andre noch anfragen!)
};

View File

@ -46,28 +46,48 @@ export class GpgME {
}
/**
* @param {String} data text/data to be encrypted as String
* 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<String>|Array<GPGME_Key>} publicKeys Keys used to encrypt the message
* @param {GPGME_Key|String|Array<String>|Array<GPGME_Key>} secretKeys (optional) Keys used to sign the message
* @param {Boolean} base64 (optional) The data is already considered to be in base64 encoding
* @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<Object>} Encrypted message:
* data: The encrypted message
* base64: Boolean indicating whether data is base64 encoded.
* @async
*/
encrypt(data, publicKeys, base64=false, wildcard=false){
encrypt(data, publicKeys, secretKeys, base64=false, armor=true,
wildcard=false, additional = {}
){
let msg = createMessage('encrypt');
if (msg instanceof Error){
return Promise.reject(msg)
}
// TODO temporary
msg.setParameter('armor', true);
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 {
@ -76,16 +96,17 @@ export class GpgME {
}
/**
* @param {String} data TODO base64? Message with the encrypted data
* @param {Boolean} base64 (optional) Response should stay base64
* Decrypt a Message
* @param {String|Object} data text/data to be decrypted. Accepts Strings and Objects with a getText method
* @param {Boolean} base64 (optional) Response is expected to be base64 encoded
* @returns {Promise<Object>} decrypted message:
data: The decrypted data. This may be base64 encoded.
base64: Boolean indicating whether data is base64 encoded.
mime: A Boolean indicating whether the data is a MIME object.
info: An optional object with extra information.
signatures: Array of signature Objects TODO not yet implemented.
// should be an object that can tell if all signatures are valid etc.
* @async
*/
decrypt(data, base64=false){
if (data === undefined){
return Promise.reject(gpgme_error('MSG_EMPTY'));
@ -99,10 +120,22 @@ export class GpgME {
}
putData(msg, data);
return msg.post();
}
sign(data, keys, mode='clearsign', base64=false) { //sender
/**
* 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<String>|Array<GPGME_Key>} 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<Object>}
* data: The resulting data. In clearsign mode this includes the signature
* 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'));
}
@ -139,38 +172,10 @@ export class GpgME {
})
});
}
deleteKey(key, delete_secret = false, no_confirm = false){
return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
let msg = createMessage('deletekey');
if (msg instanceof Error){
return Promise.reject(msg);
}
let key_arr = toKeyIdArray(key);
if (key_arr.length !== 1){
return Promise.reject(
gpgme_error('GENERIC_ERROR'));
// TBD should always be ONE key?
}
msg.setParameter('key', key_arr[0]);
if (delete_secret === true){
msg.setParameter('allow_secret', true);
// TBD
}
if (no_confirm === true){ //TODO: Do we want this hidden deep in the code?
msg.setParameter('delete_force', true);
// TBD
}
if (msg.isComplete === true){
return msg.post();
} else {
return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
}
}
}
/**
* Sets the data of the message
* 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
*/