diff --git a/lang/js/BrowserTestExtension/testkey2.pub b/lang/js/BrowserTestExtension/testkey2.pub new file mode 100644 index 00000000..557bd5be --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey2.pub @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFsMHecBCACqdJgqa+CeNYwPCK+MpOwAV6uFVjDyO2LmOs6+XfDWRBU/Zjtz +8zdYNKSbLjkWN4ujV5aiyA7MtEofszzYLEoKUt1wiDScHMpW8qmEFDvl9g26MeAV +rTno9D5KodHvEIs8wnrqBs8ix0WLbh6J1Dtt8HQgIbN+v3gaRQrgBFe6z2ZYpHHx +ZfOu3iFKlm2WE/NekRkvvFIo3ApGvRhGIYw6JMmugBlo7s5xosJK0I9dkPGlEEtt +aF1RkcMj8sWG9vHAXcjlGgFfXSN9YLppydXpkuZGm4+gjLB2a3rbQCZVFnxCyG4O +ybjkP8Jw6Udm89bK2ucYFfjdrmYn/nJqRxeNABEBAAG0I1Rlc3QgTm9Qcml2S2V5 +IDxub2JvZHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4FiEE4Fmh4IZtMa4TEXCITZou +EzBBU9EFAlsMHecCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQTZouEzBB +U9F+qwf/SHj4uRnTWgyJ71FBxQDYCBq3jbi6e7hMkRPbJyJdnPIMAb2p0PJjBgjW +0pp4+kDPZans3UDHbma1u/SFI4/y6isJiK94Bk5xp5YliLGnUceTjgDFe6lBhfQ1 +zVWZC/NF3tPgbziIxXQTNt34nS+9dbV/QFDLW0POcN7C0jR/hgkBjMEH2PezWhSj +mL/yLfLfUYAoxVpXjfC5aPJKqw0tR7m5ibznjCphE+FUMRg8EOmJcg6soeJ5QspU +k2dPN3+Y0zCTNRgAHEI+yIQbM6pio6v2c+UCtT1QhW4xSI38/kcEG8QiM55r1TUy +FcWAY5n5t1nNZtMxxse3LqEon3rKiLkBDQRbDB3nAQgAqfAjSjcngERtM+ZYOwN0 +QF2v2FuEuMe8mhju7Met7SN2zGv1LnjhTNshEa9IABEfjZirE2Tqx4xCWDwDedK4 +u1ToFvcnuAMnq2O47Sh+eTypsf6WPFtPBWf6ctKY31hFXjgoyDBULBvl43XU/D9C +Mt7nsKDPYHVrrnge/qWPYVcb+cO0sSwNImMcwQSdTQ3VBq7MeNS9ZeBcXi+XCjhN +kjNum2AQqpkHHDQV7871yQ8RIILvZSSfkLb0/SNDU+bGaw2G3lcyKdIfZi2EWWZT +oCbH38I/+LV7nAEe4zFpHwW8X0Dkx2aLgxe6UszDH9L3eGhTLpJhOSiaanG+zZKm ++QARAQABiQE2BBgBCAAgFiEE4Fmh4IZtMa4TEXCITZouEzBBU9EFAlsMHecCGwwA +CgkQTZouEzBBU9H5TQgAolWvIsez/WW8N2tmZEnX0LOFNB+1S4L4X983njwNdoVI +w19pbj+8RIHF/H9kcPGi7jK96gvlykQn3uez/95D2AiRFW5KYdOouFisKgHpv8Ay +BrhclHv11yK+X/0iTD0scYaG7np5162xLkaxSO9hsz2fGv20RKaXCWkI69fWw0BR +XlI5pZh2YFei2ZhH/tIMIW65h3w0gtgaZBBdpZTOOW4zvghyN+0MSObqkI1BvUJu +caDFI4d6ZTmp5SY+pZyktZ4bg/vMH5VFxdIKgbLx9uVeTvOupvbAW0TNulYGUBQE +nm+S0zr3W18t64e4sS3oHse8zCqo1iiImpba6F1Oaw== +=y6DD +-----END PGP PUBLIC KEY BLOCK----- diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 13c99542..f2a16b42 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -77,7 +77,7 @@ export class GPGME_Key { * @returns {GPGME_Key|GPGME_Error} The Key object itself after values have * been set */ - setKeydata(data){ + setKeyData(data){ if (this._data === undefined) { this._data = {}; } @@ -161,11 +161,17 @@ export class GPGME_Key { 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); - }); + if (property === 'armor'){ + resolve(me.getArmor()); + } else if (property === 'hasSecret'){ + resolve(me.getHasSecret()); + } else { + me.refreshKey().then(function(key){ + resolve(key.get(property, true)); + }, function(error){ + reject(error); + }); + } }); } else { if (!this._data.hasOwnProperty(property)){ @@ -188,10 +194,9 @@ export class GPGME_Key { let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', me._data.fingerprint); - console.log(msg); msg.post().then(function(result){ if (result.keys.length === 1){ - me.setKeydata(result.keys[0]); + me.setKeyData(result.keys[0]); resolve(me); } else { reject(gpgme_error('KEY_NOKEY')); @@ -202,25 +207,78 @@ export class GPGME_Key { }); } - //TODO: /** * 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. - * Async, returns Promise + * @returns {Promise} */ - // getArmor(){ TODO } - // + getArmor(){ + let me = this; + return new Promise(function(resolve, reject) { + if (!me._data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('export'); + msg.setParameter('armor', true); + msg.setParameter('keys', me._data.fingerprint); + msg.post().then(function(result){ + me._data.armor = result.data; + resolve(result.data); + }, function(error){ + reject(error); + }); + }); + } - // get hasSecret(){TODO} // confusing difference to Key.get('secret')! - // getHasSecret(){TODO async version} + getHasSecret(){ + let me = this; + return new Promise(function(resolve, reject) { + if (!me._data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('keylist'); + msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('secret', true); + msg.post().then(function(result){ + me._data.hasSecret = null; + if (result.keys === undefined || result.keys.length < 1) { + me._data.hasSecret = false; + resolve(false); + } + else if (result.keys.length === 1){ + let key = result.keys[0]; + if (!key.subkeys){ + me._data.hasSecret = false; + resolve(false); + } else { + for (let i=0; i < key.subkeys.length; i++) { + if (key.subkeys[i].secret === true) { + me._data.hasSecret = true; + resolve(true); + break; + } + if (i === (key.subkeys.length -1)) { + me._data.hasSecret = false; + resolve(false); + } + } + } + } else { + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')) + } + }, function(error){ + }) + }); + } } /** diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 9abb9ec3..7e13dfe2 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -19,7 +19,7 @@ */ import {createMessage} from './Message' -import {GPGME_Key} from './Key' +import {GPGME_Key, createKey} from './Key' import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; @@ -28,117 +28,54 @@ export class GPGME_Keyring { } /** - * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds - * @param {Boolean} (optional) Include listing of secret keys + * @param {String} pattern (optional) pattern A pattern to search for, + * in userIds or KeyIds + * @param {Boolean} prepare_sync (optional, default true) if set to true, + * Key.armor and Key.hasSecret will be called, so they can be used + * inmediately. This allows for full synchronous use. If set to false, + * these will initially only be available as Promises in getArmor() and + * getHasSecret() * @returns {Promise.>} * */ - getKeys(pattern, include_secret){ + getKeys(pattern, prepare_sync){ let me = this; return new Promise(function(resolve, reject) { let msg; - msg = createMessage('listkeys'); + msg = createMessage('keylist'); if (pattern && typeof(pattern) === 'string'){ - msg.setParameter('pattern', pattern); - } - if (include_secret){ - msg.setParameter('with-secret', true); + msg.setParameter('keys', pattern); } + msg.setParameter('sigs', true); //TODO do we need this? msg.post().then(function(result){ - let fpr_list = []; let resultset = []; - if (!Array.isArray(result.keys)){ - //TODO check assumption keys = Array - fpr_list = [result.keys]; - } else { - fpr_list = result.keys; - } - for (let i=0; i < fpr_list.length; i++){ - let newKey = new GPGME_Key(fpr_list[i]); - if (newKey instanceof GPGME_Key){ - resultset.push(newKey); + let promises = []; + // TODO check if result.key is not empty + for (let i=0; i< result.keys.length; i++){ + let k = createKey(result.keys[i].fingerprint, me); + k.setKeyData(result.keys[i]); + if (prepare_sync === true){ + promises.push(k.getArmor()); + promises.push(k.getHasSecret()); } + resultset.push(k); + } + if (promises.length > 0) { + Promise.all(promises).then(function (res){ + resolve(resultset); + }, function(error){ + reject(error); + }); } - resolve(resultset); }, function(error){ reject(error); }); }); } - - /** - * @param {Object} flags subset filter expecting at least one of the - * filters described below. True will filter on the condition, False will - * reverse the filter, if not present or undefined, the filter will not be - * considered. Please note that some combination may not make sense - * @param {Boolean} flags.secret Only Keys containing a secret part. - * @param {Boolean} flags.revoked revoked Keys only - * @param {Boolean} flags.expired Expired Keys only - * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds - * @returns {Promise Array} - * - */ - getSubset(flags, pattern){ - if (flags === undefined) { - throw('ERR_WRONG_PARAM'); - }; - let secretflag = false; - if (flags.hasOwnProperty(secret) && flags.secret){ - secretflag = true; - } - this.getKeys(pattern, secretflag).then(function(queryset){ - let resultset = []; - for (let i=0; i < queryset.length; i++ ){ - let conditions = []; - let anticonditions = []; - if (secretflag === true){ - conditions.push('hasSecret'); - } else if (secretflag === false){ - anticonditions.push('hasSecret'); - } - /** - if (flags.defaultKey === true){ - conditions.push('isDefault'); - } else if (flags.defaultKey === false){ - anticonditions.push('isDefault'); - } - */ - /** - * if (flags.valid === true){ - anticonditions.push('isInvalid'); - } else if (flags.valid === false){ - conditions.push('isInvalid'); - } - */ - if (flags.revoked === true){ - conditions.push('isRevoked'); - } else if (flags.revoked === false){ - anticonditions.push('isRevoked'); - } - if (flags.expired === true){ - conditions.push('isExpired'); - } else if (flags.expired === false){ - anticonditions.push('isExpired'); - } - let decision = undefined; - for (let con = 0; con < conditions.length; con ++){ - if (queryset[i][conditions[con]] !== true){ - decision = false; - } - } - for (let acon = 0; acon < anticonditions.length; acon ++){ - if (queryset[i][anticonditions[acon]] === true){ - decision = false; - } - } - if (decision !== false){ - resultset.push(queryset[i]); - } - } - return Promise.resolve(resultset); - }, function(error){ - //TODO error handling - }); - } +// TODO: + // deleteKey(key, include_secret=false) + // getKeysArmored(pattern) //just dump all armored keys + // getDefaultKey() Big TODO + // importKeys(armoredKeys) }; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 42213ec3..e4f9bd22 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -45,6 +45,7 @@ export const permittedOperations = { encrypt: { + pinentry: true, //TODO only with signing_keys required: { 'keys': { allowed: ['string'], @@ -59,38 +60,42 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - 'chunksize': { - allowed: ['number'] - }, - 'base64': { - allowed: ['boolean'] - }, - 'mime': { - allowed: ['boolean'] - }, - 'armor': { - allowed: ['boolean'] - }, - 'always-trust': { - allowed: ['boolean'] - }, - 'no-encrypt-to': { - allowed: ['string'], - array_allowed: true - }, - 'no-compress': { - allowed: ['boolean'] - }, - 'throw-keyids': { - allowed: ['boolean'] - }, - 'want-address': { - allowed: ['boolean'] - }, - 'wrap': { - allowed: ['boolean'] - }, + 'signing_keys': { + allowed: ['string'], + array_allowed: true }, + 'chunksize': { + allowed: ['number'] + }, + 'base64': { + allowed: ['boolean'] + }, + 'mime': { + allowed: ['boolean'] + }, + 'armor': { + allowed: ['boolean'] + }, + 'always-trust': { + allowed: ['boolean'] + }, + 'no-encrypt-to': { + allowed: ['string'], + array_allowed: true + }, + 'no-compress': { + allowed: ['boolean'] + }, + 'throw-keyids': { + allowed: ['boolean'] + }, + 'want-address': { + allowed: ['boolean'] + }, + 'wrap': { + allowed: ['boolean'] + } + }, answer: { type: ['ciphertext'], data: ['data'], @@ -122,12 +127,7 @@ export const permittedOperations = { type: ['plaintext'], data: ['data'], params: ['base64', 'mime'], - infos: [] // TODO pending. Info about signatures and validity - //{ - //signatures: [{ - //Key : Fingerprint, - //valid: - // }] + infos: ['signatures'] } }, @@ -208,61 +208,107 @@ export const permittedOperations = { 'validate': { allowed: ['boolean'] }, - // 'pattern': { TODO - // allowed: ['string'] - // }, 'keys': { allowed: ['string'], array_allowed: true } }, answer: { - type: [], + type: ['keys'], data: [], - params: [], + params: ['base64'], infos: ['keys'] } }, - /** - importkey: { + export: { + required: {}, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'chunksize': { + allowed: ['number'], + }, + 'keys': { + allowed: ['string'], + array_allowed: true + }, + 'armor': { + allowed: ['boolean'] + }, + 'extern': { + allowed: ['boolean'] + }, + 'minimal': { + allowed: ['boolean'] + }, + 'raw': { + allowed: ['boolean'] + }, + 'pkcs12':{ + allowed: ['boolean'] + } + // secret: not yet implemented + }, + answer: { + type: ['keys'], + data: ['data'], + params: ['base64'] + } + }, + + import: { required: { - 'keyarmored': { + 'data': { allowed: ['string'] } }, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'base64': { + allowed: ['boolean'] + }, + }, answer: { - type: ['TBD'], - infos: ['TBD'], - // for each key if import was a success, - // and if it was an update of preexisting key + infos: ['result'], + type: [], + data: [], + params: [] } }, - */ - /** - deletekey: { + delete: { pinentry: true, - required: { - 'fingerprint': { - allowed: ['string'], - // array_allowed: TBD Allow several Keys to be deleted at once? - }, + required:{ + 'key': { + allowed: ['string'] + } + }, optional: { - 'TBD' //Flag to delete secret Key ? - } - answer: { - type ['TBD'], - infos: [''] - // TBD (optional) Some kind of 'ok' if delete was successful. - } - } - */ + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + // 'secret': { not yet implemented + // allowed: ['boolean'] + // } + }, + answer: { + data: [], + params:['success'], + infos: [] + } + }, /** *TBD get armored secret different treatment from keyinfo! * TBD key modification? - * encryptsign: TBD + */ version: { diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index ca51f4ae..2a21a6ae 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -44,7 +44,11 @@ export const whatever_params = { four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'], } export const key_params = { +// A Key you own (= having a secret Key) in GPG. See testkey.pub/testkey.sec validKeyFingerprint: 'D41735B91236FDB882048C5A2301635EEFF0CB05', +// A Key you do not own (= having no secret Key) in GPG. See testkey2.pub + validFingerprintNoSecret: 'E059A1E0866D31AE131170884D9A2E13304153D1', +// A Key not in your Keyring. This is just a random hex string. invalidKeyFingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A', validKeyProperties: ['expired', 'disabled','invalid','can_encrypt', 'can_sign','can_certify','can_authenticate','secret','is_qualified'] diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 9830a2c5..443aa685 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -197,7 +197,34 @@ function unittests (){ expect(result).to.be.a('boolean'); done(); }); - }) + }); + + it('Non-cached key async armored Key', function (done){ + let key = createKey(kp.validKeyFingerprint); + key.get('armor', false).then(function(result){ + expect(result).to.be.a('string'); + expect(result).to.include('KEY BLOCK-----'); + done(); + }); + }); + + it('Non-cached key async hasSecret', function (done){ + let key = createKey(kp.validKeyFingerprint); + key.get('hasSecret', false).then(function(result){ + expect(result).to.be.a('boolean'); + done(); + }); + }); + + it('Non-cached key async hasSecret (no secret in Key)', function (done){ + let key = createKey(kp.validFingerprintNoSecret); + expect(key).to.be.an.instanceof(GPGME_Key); + key.get('hasSecret', false).then(function(result){ + expect(result).to.be.a('boolean'); + expect(result).to.equal(false); + done(); + }); + }); it('Querying non-existing Key returns an error', function(done) { let key = createKey(kp.invalidKeyFingerprint); @@ -224,7 +251,6 @@ function unittests (){ expect(key.fingerprint.code).to.equal('KEY_INVALID'); } }); - // TODO: tests for subkeys // TODO: tests for userids // TODO: some invalid tests for key/keyring @@ -236,19 +262,19 @@ function unittests (){ let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.getKeys).to.be.a('function'); - expect(keyring.getSubset).to.be.a('function'); }); - it('correct initialization', function(){ + it('Loading Keys from Keyring, to be used synchronously', function(done){ let keyring = new GPGME_Keyring; - expect(keyring).to.be.an.instanceof(GPGME_Keyring); - expect(keyring.getKeys).to.be.a('function'); - expect(keyring.getSubset).to.be.a('function'); + keyring.getKeys(null, true).then(function(result){ + expect(result).to.be.an('array'); + expect(result[0]).to.be.an.instanceof(GPGME_Key); + expect(result[0].get('armor')).to.be.a('string'); + expect(result[0].get('armor')).to.include( + '-----END PGP PUBLIC KEY BLOCK-----'); + done(); + }); }); - //TODO not yet implemented: - // getKeys(pattern, include_secret) //note: pattern can be null - // getSubset(flags, pattern) - // available Boolean flags: secret revoked expired }); describe('GPGME_Message', function(){