js: implement Key handling (1)
authorMaximilian Krambach <maximilian.krambach@intevation.de>
Fri, 25 May 2018 17:02:18 +0000 (19:02 +0200)
committerMaximilian Krambach <maximilian.krambach@intevation.de>
Fri, 25 May 2018 17:02:18 +0000 (19:02 +0200)
--

* 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

lang/js/src/Connection.js
lang/js/src/Errors.js
lang/js/src/Helpers.js
lang/js/src/Key.js
lang/js/src/permittedOperations.js
lang/js/unittest_inputvalues.js
lang/js/unittests.js

index 07df5de..3b44262 100644 (file)
@@ -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');
index 7e98f31..fa8a4ef 100644 (file)
@@ -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',
index fd0e720..b26f40f 100644 (file)
@@ -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);
 };
 
index 075a190..7d3d82b 100644 (file)
  *
  */
 
-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);
+    }
+}
index aa02a8b..42213ec 100644 (file)
@@ -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:[]
+        }
+    }
 }
index 3450afd..ca51f4a 100644 (file)
@@ -6,7 +6,7 @@ let conn = new Connection;
 export const helper_params = {
     validLongId: '0A0A0A0A0A0A0A0A',
     validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3',
-        createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn),
+        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn),
         'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'],
     validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A',
     validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A',
@@ -15,11 +15,11 @@ export const helper_params = {
     invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)],
     invalidKeyArray: {curiosity:'uncat'},
     invalidKeyArray_OneBad: [
-        createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn),
+        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn),
         'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A',
         '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'],
     invalidErrorCode: 'Please type in all your passwords.',
-    validGPGME_Key: createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn),
+    validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn),
     valid_openpgplike: { primaryKey: {
         getFingerprint: function(){
             return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';}
@@ -41,5 +41,11 @@ export const message_params = {
 }
 
 export const whatever_params = {
-    four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}']
+    four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'],
+}
+export const key_params = {
+    validKeyFingerprint: 'D41735B91236FDB882048C5A2301635EEFF0CB05',
+    invalidKeyFingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A',
+    validKeyProperties: ['expired', 'disabled','invalid','can_encrypt',
+    'can_sign','can_certify','can_authenticate','secret','is_qualified']
 }
index 06b2b23..bb06309 100644 (file)
@@ -22,6 +22,7 @@ import "./node_modules/chai/chai";
 import { helper_params as hp } from "./unittest_inputvalues";
 import { message_params as mp } from "./unittest_inputvalues";
 import { whatever_params as wp } from "./unittest_inputvalues";
+import { key_params as kp } from "./unittest_inputvalues";
 import { Connection } from "./src/Connection";
 import { gpgme_error } from "./src/Errors";
 import { toKeyIdArray , isFingerprint } from "./src/Helpers";
@@ -46,6 +47,7 @@ function unittests (){
                 expect(answer.info).to.be.an('Array');
                 expect(conn0.disconnect).to.be.a('function');
                 expect(conn0.post).to.be.a('function');
+                conn0.disconnect();
                 done();
             });
 
@@ -65,7 +67,7 @@ function unittests (){
     });
 
     describe('Error Object handling', function(){
-
+        // TODO: new GPGME_Error codes
         it('check the Timeout error', function(){
             let test0 = gpgme_error('CONN_TIMEOUT');
 
@@ -169,13 +171,55 @@ function unittests (){
 
         it('correct Key initialization', function(){
             let conn = new Connection;
-            let key = createKey(hp.validFingerprint, conn);
-
+            let key = createKey(kp.validKeyFingerprint, conn);
             expect(key).to.be.an.instanceof(GPGME_Key);
             expect(key.connection).to.be.an.instanceof(Connection);
-            // TODO not implemented yet: Further Key functionality
+            conn.disconnect();
+        });
+        it('Key has data after a first refresh', function(done) {
+            let conn = new Connection;
+            let key = createKey(kp.validKeyFingerprint, conn);
+            key.refreshKey().then(function(key2){
+                expect(key2).to.be.an.instanceof(GPGME_Key);
+                expect(key2.get).to.be.a('function');
+                for (let i=0; i < kp.validKeyProperties.length; i++) {
+                    let prop = key2.get(kp.validKeyProperties[i]);
+                    expect(prop).to.not.be.undefined;
+                    expect(prop).to.be.a('boolean');
+                }
+                expect(isFingerprint(key2.get('fingerprint'))).to.be.true;
+                expect(
+                    key2.get('fingerprint')).to.equal(kp.validKeyFingerprint);
+                expect(
+                    key2.get('fingerprint')).to.equal(key.fingerprint);
+                conn.disconnect();
+                done();
+            });
         });
 
+        it('Non-cached key async data retrieval', function (done){
+            let conn = new Connection;
+            let key = createKey(kp.validKeyFingerprint, conn);
+            key.get('can_authenticate',false).then(function(result){
+                expect(result).to.be.a('boolean');
+                conn.disconnect();
+                done();
+            });
+        })
+
+        it('Querying non-existing Key returns an error', function(done) {
+            let conn = new Connection;
+            let key = createKey(kp.invalidKeyFingerprint, conn);
+            key.refreshKey().then(function(){},
+                function(error){
+                    expect(error).to.be.an.instanceof(Error);
+                    expect(error.code).to.equal('KEY_NOKEY');
+                    conn.disconnect();
+                    done();
+            });
+        });
+
+
         it('Key can use the connection', function(done){
             let conn = new Connection;
             let key = createKey(hp.validFingerprint, conn);
@@ -184,6 +228,7 @@ function unittests (){
                 key.connection.disconnect();
                 key.connection.checkConnection(false).then(function(result2){
                     expect(result2).to.be.false;
+                    conn.disconnect();
                     done();
                 });
             });
@@ -204,16 +249,22 @@ function unittests (){
                 expect(key0).to.be.an.instanceof(Error);
                 expect(key0.code).to.equal('PARAM_WRONG');
             }
+            conn.disconnect();
         });
-        it('bad GPGME_Key returns Error if used', function(){
+
+        it('malformed GPGME_Key cannot be used', function(){
             let conn = new Connection;
             for (let i=0; i < 4; i++){
                 let key = new GPGME_Key(wp.four_invalid_params[i], conn);
-
-                expect(key.connection).to.be.an.instanceof(Error);
-                expect(key.connection.code).to.equal('KEY_INVALID');
+                expect(key.fingerprint).to.be.an.instanceof(Error);
+                expect(key.fingerprint.code).to.equal('KEY_INVALID');
             }
+            conn.disconnect();
         });
+
+        // TODO: tests for subkeys
+        // TODO: tests for userids
+        // TODO: some invalid tests for key/keyring
     });
 
     describe('GPGME_Keyring', function(){