diff --git a/lang/js/.eslintrc.json b/lang/js/.eslintrc.json new file mode 100644 index 00000000..65253cf3 --- /dev/null +++ b/lang/js/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "indent": [ + "warn", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + "max-len": 1 + } +} \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index 3d81a9ec..f3d7a406 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -1,24 +1,25 @@

Browsertest

+ diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js new file mode 100644 index 00000000..e6eb5a30 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -0,0 +1,83 @@ +/* gpgme.js - Javascript integration for gpgme + * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * GPGME is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1+ + */ + +/* global describe, it, expect, Gpgmejs, ImportablePublicKey */ + +describe('Key importing', function () { + it('Prepare test Key (deleting it from gnupg, if present)', function(done){ + let prm = Gpgmejs.init(); + prm.then(function (context) { + expect(context.Keyring.getKeys).to.be.a('function'); + context.Keyring.getKeys(ImportablePublicKey.fingerprint).then( + function(result){ + if (result.length === 1) { + result[0].delete().then(function(result){ + expect(result).to.be.true; + done(); + }); + } else { + done(); + } + }); + }); + }); + + it('importing, updating, then deleting public Key', function (done) { + //This test runs in one large step, to ensure the proper state of the + // key in all stages. + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.Keyring.getKeys(ImportablePublicKey.fingerprint).then( + function(result){ + expect(result).to.be.an('array'); + expect(result.length).to.equal(0); + context.Keyring.importKey(ImportablePublicKey.key, true) + .then(function(result){ + expect(result[0]).to.not.be.undefined; + expect(result[0].key).to.be.an('object'); + expect(result[0].key.fingerprint).to.equal( + ImportablePublicKey.fingerprint); + expect(result[0].status).to.equal('newkey'); + context.Keyring.importKey( + ImportablePublicKey.keyChangedUserId,true) + .then(function(res){ + expect(res[0]).to.not.be.undefined; + expect(res[0].key).to.be.an('object'); + expect(res[0].key.fingerprint).to.equal( + ImportablePublicKey.fingerprint); + expect(res[0].status).to.equal( + 'change'); + expect( + res[0].changes.userId).to.be.true; + expect( + res[0].changes.subkey).to.be.false; + expect( + res[0].changes.signature).to.be.true; + res[0].key.delete().then(function(result){ + expect(result).to.be.true; + done(); + }); + }); + }); + }); + }); + }); + +}); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index f5d2be16..e5c2f749 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -1,218 +1,217 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ describe('Encryption and Decryption', function () { it('Successful encrypt and decrypt simple string', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( inputvalues.encrypt.good.data, inputvalues.encrypt.good.fingerprint).then(function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); context.decrypt(answer.data).then(function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(inputvalues.encrypt.good.data); done(); }); }); }); }); it('Decrypt simple non-ascii', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { let data = encryptedData; context.decrypt(data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal( '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); done(); }); }); }).timeout(3000); it('Roundtrip does not destroy trailing whitespace', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { let data = 'Keks. \rKeks \n Keks \r\n'; context.encrypt(data, inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); done(); }); }); }); }).timeout(5000); for (let j = 0; j < inputvalues.encrypt.good.data_nonascii_32.length; j++){ it('Roundtrip with >1MB non-ascii input meeting default chunksize (' + (j + 1) + '/' + inputvalues.encrypt.good.data_nonascii_32.length + ')', function (done) { let input = inputvalues.encrypt.good.data_nonascii_32[j]; expect(input).to.have.length(32); let prm = Gpgmejs.init(); prm.then(function (context) { let data = ''; for (let i=0; i < 34 * 1024; i++){ data += input; } context.encrypt(data, inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); done(); }); }); }); }).timeout(3000); }; it('Random data, as string', function (done) { let data = bigString(1000); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(data, inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); done(); }); }); }); }).timeout(3000); it('Data, input as base64', function (done) { let data = inputvalues.encrypt.good.data; let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, inputvalues.encrypt.good.fingerprint,).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(data).to.equal(data); done(); }); }); }); }).timeout(3000); it('Random data, input as base64', function (done) { - //TODO fails. The result is let data = bigBoringString(0.001); let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, inputvalues.encrypt.good.fingerprint, true).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); - context.decrypt(answer.data).then( + context.decrypt(answer.data, true).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); - expect(result.data).to.equal(data); + expect(result.data).to.equal(b64data); done(); }); }); }); }).timeout(3000); it('Random data, input and output as base64', function (done) { let data = bigBoringString(0.0001); let b64data = btoa(data); let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data, true).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(b64data); done(); }); }); }); }).timeout(3000); }); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 52e3a7b0..9dc13324 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -1,143 +1,220 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ -var inputvalues = { +var inputvalues = {// eslint-disable-line no-unused-vars encrypt: { good:{ data : 'Hello World.', - // Fingerprint of a key that has been imported to gnupg (i.e. see testkey.pub; testkey.sec) + // Fingerprint of a key that has been imported to gnupg + // (i.e. see testkey.pub; testkey.sec) fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день', // used for checking encoding consistency in > 2MB messages. data_nonascii_32: [ 'K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€', 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€', '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€', '²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³', 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€', 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€', 'üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü', 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', 'µAAAAµAAAAAAAAAAAAAAAAAAAAAAAAA€', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAµ€', 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°', '€AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', 'µ||||||||||||||||||||||||||||||€', 'æſæſ³¼„¬“³³¬“¬½”æſæſ³¼„¬“³³¬“¬½”' ] }, bad: { - // valid Hex value, but not usable (not imported to gnupg, or bogus fingerprint) + // valid Hex value, but not usable (not imported to gnupg, or + // bogus fingerprint) fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' } }, init: { // some parameters invalid_startups: [ {all_passwords: true}, 'openpgpmode', - {api_style:"frankenstein"} + {api_style:'frankenstein'} ] } }; // (Pseudo-)Random String covering all of utf8. -function bigString(length){ - var uint = ''; +function bigString(length){// eslint-disable-line no-unused-vars let arr = []; for (let i= 0; i < length; i++){ arr.push(String.fromCharCode( Math.floor(Math.random() * 10174) + 1) ); } return arr.join(''); } -function fixedLengthString(megabytes){ +function fixedLengthString(megabytes){// eslint-disable-line no-unused-vars let maxlength = 1024 * 1024 * megabytes / 2; let uint = new Uint8Array(maxlength); for (let i = 0; i < maxlength; i++){ uint[i] = Math.floor(Math.random()* 256); } let td = new TextDecoder('ascii'); let result = td.decode(uint); return result; } // (Pseudo-)Random Uint8Array, given size in Megabytes -function bigUint8(megabytes){ +function bigUint8(megabytes){// eslint-disable-line no-unused-vars let maxlength = 1024 * 1024 * megabytes; let uint = new Uint8Array(maxlength); for (let i= 0; i < maxlength; i++){ uint[i] = Math.random() * Math.floor(256); } return uint; } -// (Pseudo-)Random string with very limited charset (ascii only, no control chars) -function bigBoringString(megabytes){ +// (Pseudo-)Random string with very limited charset +// (ascii only, no control chars) +function bigBoringString(megabytes){// eslint-disable-line no-unused-vars let maxlength = 1024 * 1024 * megabytes; let string = []; - let chars = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let chars = + ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; for (let i= 0; i < maxlength; i++){ string.push(chars[Math.floor(Math.random() * chars.length)]); } return string.join(''); } // Some String with simple chars, with different characteristics, but still // expected to occur in an averag message +// eslint-disable-next-line no-unused-vars function slightlyLessBoringString(megabytes, set){ let maxlength = 1024 * 1024 * megabytes; let string = []; let chars = ''; if (set ===1 ) { - chars = '\n\"\r \''; + chars = '\n"\r \''; } else if (set === 2 ) { chars = '()=?`#+-{}[]'; } else if (set === 3){ chars = '^°/'; } else if (set ===4) { chars = 'äüßµüþÖ~ɁÑ||@'; } else { - chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; + chars = '*<>\n"\r§$%&/()=?`#+-{}[] \''; } for (let i= 0; i < maxlength; i++){ string.push(chars[Math.floor(Math.random() * chars.length)]); } return string.join(''); } // Data encrypted with testKey -var encryptedData = +var encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + '\n' + 'hQEMA6B8jfIUScGEAQgAlANd3uyhmhYLzVcfz4LEqA8tgUC3n719YH0iuKEzG/dv\n' + 'B8fsIK2HoeQh2T3/Cc2LBMjgn4K33ksG3k2MqrbIvxWGUQlOAuggc259hquWtX9B\n' + 'EcEoOAeh5DuZT/b8CM5seJKNEpPzNxbEDiGikp9DV9gfIQTTUnrDjAu5YtgCN9vA\n' + '3PJxihioH8ODoQw2jlYSkqgXpBVP2Fbx7qgTuxGNu5w36E0/P93//4hDXcKou7ez\n' + 'o0+NEGSkbaY+OPk1k7k9n+vBSC3F440dxsTNs5WmRvx9XZEotJkUBweE+8XaoLCn\n' + '3RrtyD/lj63qi3dbyI5XFLuPU1baFskJ4UAmI4wNhdJ+ASailpnFBnNgiFBh3ZfB\n' + 'G5Rmd3ocSL7l6lq1bVK9advXb7vcne502W1ldAfHgTdQgc2CueIDFUYAaXP2OvhP\n' + 'isGL7jOlDCBKwep67ted0cTRPLWkk3NSuLIlvD5xs6L4z3rPu92gXYgbZoMMdP0N\n' + 'kSAQYOHplfA7YJWkrlRm\n' + '=zap6\n' + '-----END PGP MESSAGE-----\n'; + +var ImportablePublicKey = {// eslint-disable-line no-unused-vars + fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A', + key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + + '\n' + + 'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' + + 'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' + + 'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' + + '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' + + 'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' + + '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' + + 'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' + + 'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' + + 'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' + + 'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' + + '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' + + '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' + + 'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' + + 'uzEXPGW+sq0WRp3hynn7kVP6QQYvuQENBFsPvK0BCACwvBcmbnGJk8XhEBRu2QN3\n' + + 'jKgVs3CG5nE2Xh20JipZwAuGHugDLv6/jlizzz5jtj3SAHVtJB8lJW8I0cNSEIX8\n' + + 'bRYH4C7lP2DTb9CgMcGErQIyK480+HIsbsZhJSNHdjUUl6IPEEVfSQzWaufmuswe\n' + + 'e+giqHiTsaiW20ytXilwVGpjlHBaxn/bpskZ0YRasgnPqKgJD3d5kunNqWoyCpMc\n' + + 'FYgDERvPbhhceFbvFE9G/u3gbcuV15mx53dDX0ImvPcvJnDOyJS9yr7ApdOV312p\n' + + 'A1MLbxfPnbnVu+dGXn7D/VCDd5aBYVPm+5ANrk6z9lYKH9aO5wgXpLAdJvutCOL5\n' + + 'ABEBAAGJATwEGAEIACYWIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUCWw+8rQIbDAUJ\n' + + 'A8JnAAAKCRD2N5DBHmAnigMVB/484G2+3R0cAaj3V/z4gW3MRSMhcYqEMyJ/ACdo\n' + + '7y8eoreYW843JWWVDRY6/YcYYGuBBP47WO4JuP2wIlVn17XOCSgnNjmmjsIYiAzk\n' + + 'op772TB27o0VeiFX5iWcawy0EI7JCb23xpI+QP31ksL2yyRYFXCtXSUfcOrLpCY8\n' + + 'aEQMQbAGtkag1wHTo/Tf/Vip8q0ZEQ4xOKTR2/ll6+inP8kzGyzadElUnH1Q1OUX\n' + + 'd2Lj/7BpBHE2++hAjBQRgnyaONF7mpUNEuw64iBNs0Ce6Ki4RV2+EBLnFubnFNRx\n' + + 'fFJcYXcijhuf3YCdWzqYmPpU/CtF4TgDlfSsdxHxVOmnZkY3\n' + + '=qP6s\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n', + + keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + + '\n' + + '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\n' + + '=9WZ7\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n' +}; diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 3480d811..8c4cba7c 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,269 +1,270 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ /** * A connection port will be opened for each communication between gpgmejs and * gnupg. It should be alive as long as there are additional messages to be * expected. */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from "./Errors" import { GPGME_Message, createMessage } from "./Message"; /** * A Connection handles the nativeMessaging interaction. */ export class Connection{ constructor(){ this.connect(); } /** * Retrieves the information about the backend. * @param {Boolean} details (optional) If set to false, the promise will * just return a connection status * @returns {Promise} * {String} The property 'gpgme': Version number of gpgme * {Array} 'info' Further information about the backends. * Example: * "protocol": "OpenPGP", * "fname": "/usr/bin/gpg", * "version": "2.2.6", * "req_version": "1.4.0", * "homedir": "default" */ checkConnection(details = true){ if (details === true) { return this.post(createMessage('version')); } else { let me = this; return new Promise(function(resolve,reject) { Promise.race([ me.post(createMessage('version')), new Promise(function(resolve, reject){ setTimeout(function(){ reject(gpgme_error('CONN_TIMEOUT')); }, 500); }) ]).then(function(result){ resolve(true); }, function(reject){ resolve(false); }); }); } } /** * Immediately closes the open port. */ disconnect() { if (this._connection){ this._connection.disconnect(); this._connection = null; } } /** * Opens a nativeMessaging port. */ connect(){ if (!this._connection){ this._connection = chrome.runtime.connectNative('gpgmejson'); } } /** * Sends a message and resolves with the answer. * @param {GPGME_Message} message * @returns {Promise} the gnupg answer, or rejection with error * information. */ post(message){ if (!this._connection) { } if (!message || !message instanceof GPGME_Message){ this.disconnect(); - return Promise.reject(gpgme_error('PARAM_WRONG'), message); + return Promise.reject(gpgme_error('PARAM_WRONG', 'Connection.post')); } if (message.isComplete !== true){ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; return new Promise(function(resolve, reject){ let answer = new Answer(message); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) me._connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(gpgme_error('GNUPG_ERROR', msg.msg)); } else { let answer_result = answer.add(msg); if (answer_result !== true){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(answer_result); } else if (msg.more === true){ me._connection.postMessage({'op': 'getmore'}); } else { me._connection.onMessage.removeListener(listener) me._connection.disconnect(); resolve(answer.message); } } }; me._connection.onMessage.addListener(listener); if (permittedOperations[message.operation].pinentry){ return me._connection.postMessage(message.message); } else { return Promise.race([ me._connection.postMessage(message.message), function(resolve, reject){ setTimeout(function(){ me._connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ return result; }, function(reject){ if(!reject instanceof Error) { me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; } }); } }); } }; /** * A class for answer objects, checking and processing the return messages of * the nativeMessaging communication. * @param {String} operation The operation, to look up validity of returning messages */ class Answer{ constructor(message){ this.operation = message.operation; this.expected = message.expected; } /** * Add the information to the answer * @param {Object} msg The message as received with nativeMessaging * returns true if successfull, gpgme_error otherwise */ add(msg){ if (this._response === undefined){ this._response = {}; } let messageKeys = Object.keys(msg); let poa = permittedOperations[this.operation].answer; if (messageKeys.length === 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } for (let i= 0; i < messageKeys.length; i++){ let key = messageKeys[i]; switch (key) { case 'type': if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; case 'more': break; default: //data should be concatenated if (poa.data.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[key] = ''; } this._response[key] += msg[key]; } //params should not change through the message else if (poa.params.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[key] = msg[key]; } else if (this._response[key] !== msg[key]){ return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]); } } //infos may be json objects etc. Not yet defined. // Pushing them into arrays for now else if (poa.infos.indexOf(key) >= 0){ if (!this._response.hasOwnProperty(key)){ this._response[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]); + this._response[key].push(msg[key]); } } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; } } return true; } /** * @returns {Object} the assembled message, original data assumed to be * (javascript-) strings */ get message(){ let keys = Object.keys(this._response); let msg = {}; let poa = permittedOperations[this.operation].answer; for (let i=0; i < keys.length; i++) { if (poa.data.indexOf(keys[i]) >= 0 && this._response.base64 === true ) { msg[keys[i]] = atob(this._response[keys[i]]); if (this.expected === 'base64'){ msg[keys[i]] = this._response[keys[i]]; } else { msg[keys[i]] = decodeURIComponent( atob(this._response[keys[i]]).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } } else { msg[keys[i]] = this._response[keys[i]]; } } return msg; } } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 3b53eeb4..2f2bfd5c 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -1,133 +1,137 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ const err_list = { // Connection 'CONN_NO_CONNECT': { msg:'Connection with the nativeMessaging host could not be' + ' established.', type: 'error' }, 'CONN_EMPTY_GPG_ANSWER':{ msg: 'The nativeMessaging answer was empty.', type: 'error' }, 'CONN_TIMEOUT': { msg: 'A connection timeout was exceeded.', type: 'error' }, 'CONN_UNEXPECTED_ANSWER': { msg: 'The answer from gnupg was not as expected.', type: 'error' }, 'CONN_ALREADY_CONNECTED':{ msg: 'A connection was already established.', type: 'warning' }, // Message/Data 'MSG_INCOMPLETE': { msg: 'The Message did not match the minimum requirements for' + ' the interaction.', type: 'error' }, 'MSG_EMPTY' : { msg: 'The Message is empty.', type: 'error' }, 'MSG_WRONG_OP': { msg: 'The operation requested could not be found', type: 'error' }, 'MSG_NO_KEYS' : { msg: 'There were no valid keys provided.', type: 'warning' }, 'MSG_NOT_A_FPR': { msg: 'The String is not an accepted fingerprint', type: 'warning' }, 'KEY_INVALID': { msg:'Key object is invalid', type: 'error' }, 'KEY_NOKEY': { 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', type: 'error' }, 'PARAM_IGNORED': { msg: 'An parameter was set that has no effect in gpgmejs', type: 'warning' }, 'GENERIC_ERROR': { msg: 'Unspecified error', type: 'error' } }; /** * Checks the given error code and returns an error object with some * information about meaning and origin * @param {*} code Error code. Should be in err_list or 'GNUPG_ERROR' * @param {*} info Error message passed through if code is 'GNUPG_ERROR' */ export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ return new GPGME_Error(code, info); } else { return new GPGME_Error('GENERIC_ERROR'); } } class GPGME_Error extends Error{ constructor(code, msg=''){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ - super(err_list[code].msg); + if (msg){ + super(err_list[code].msg + "--" + msg); + } else { + super(err_list[code].msg); + } } else { super(err_list['GENERIC_ERROR'].msg); } this.code = code || 'GENERIC_ERROR'; } set code(value){ this._code = value; } get code(){ return this._code; } } \ No newline at end of file diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index b26f40fb..5064d03e 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -1,104 +1,102 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ import { gpgme_error } from "./Errors"; import { GPGME_Key } from "./Key"; /** * Tries to return an array of fingerprints, either from input fingerprints or * from Key objects * @param {Key |Array| GPGME_Key | Array|String|Array} input * @returns {Array} Array of fingerprints. */ export function toKeyIdArray(input){ if (!input){ - gpgme_error('MSG_NO_KEYS'); return []; } if (!Array.isArray(input)){ input = [input]; } let result = []; for (let i=0; i < input.length; i++){ if (typeof(input[i]) === 'string'){ if (isFingerprint(input[i]) === true){ result.push(input[i]); } else { gpgme_error('MSG_NOT_A_FPR'); } } else if (typeof(input[i]) === 'object'){ let fpr = ''; if (input[i] instanceof GPGME_Key){ fpr = input[i].fingerprint; } else if (input[i].hasOwnProperty('primaryKey') && input[i].primaryKey.hasOwnProperty('getFingerprint')){ fpr = input[i].primaryKey.getFingerprint(); } if (isFingerprint(fpr) === true){ result.push(fpr); } else { gpgme_error('MSG_NOT_A_FPR'); } } else { return gpgme_error('PARAM_WRONG'); } } if (result.length === 0){ - gpgme_error('MSG_NO_KEYS'); return []; } else { return result; } }; /** * check if values are valid hexadecimal values of a specified length * @param {*} key input value. * @param {int} len the expected length of the value */ function hextest(key, len){ if (!key || typeof(key) !== "string"){ return false; } if (key.length !== len){ return false; } let regexp= /^[0-9a-fA-F]*$/i; return regexp.test(key); }; /** * check if the input is a valid Hex string with a length of 40 */ export function isFingerprint(string){ return hextest(string, 40); }; /** * check if the input is a valid Hex string with a length of 16 */ export function isLongId(string){ return hextest(string, 16); }; // TODO still not needed anywhere function isShortId(string){ return hextest(string, 8); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 454b1912..d85c8b6e 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -1,549 +1,549 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ /** * The key class allows to query the information defined in gpgme Key Objects * (see https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) * * This is a stub, as the gpgme-json side is not yet implemented * */ import { isFingerprint, isLongId } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; /** * Validates the fingerprint. * @param {String} fingerprint */ export function createKey(fingerprint){ if (!isFingerprint(fingerprint)){ return gpgme_error('PARAM_WRONG'); } else return new GPGME_Key(fingerprint); } /** * Representing the Keys as stored in GPG */ export class GPGME_Key { constructor(fingerprint){ this.fingerprint = fingerprint; } set 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._data || !this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } return this._data.fingerprint; } /** * * @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 */ 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'); } let dataKeys = Object.keys(data); for (let i=0; i< dataKeys.length; i++){ if (!validKeyProperties.hasOwnProperty(dataKeys[i])){ return gpgme_error('KEY_INVALID'); } if (validKeyProperties[dataKeys[i]](data[dataKeys[i]]) !== true ){ return gpgme_error('KEY_INVALID'); } 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; } /** * 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(property, cached=true) { if (cached === false) { let me = this; return new Promise(function(resolve, reject) { if (!validKeyProperties.hasOwnProperty(property)){ reject('PARAM_WRONG'); } else if (property === 'armored'){ 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 (!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 */ refreshKey() { let me = this; return new Promise(function(resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', me._data.fingerprint); msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeyData(result.keys[0]); resolve(me); } else { reject(gpgme_error('KEY_NOKEY')); } }, function (error) { reject(gpgme_error('GNUPG_ERROR'), error); }) }); } /** * Query the armored block of the non- secret parts of the Key directly * from gpg. * @returns {Promise} */ 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; + me._data.armored = result.data; resolve(result.data); }, function(error){ reject(error); }); }); } 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){ }) }); } /** * 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 + * @returns {Promise} 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); }) }); } } /** * 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; } } } /** * * @param {String} property Information to request * @returns {String | Number} * TODO: date properties are numbers with Date in seconds */ 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; } } } /** * * @param {String} property Information to request * @returns {String | Number} * TODO: date properties are numbers with Date in seconds */ get(property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } } } 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 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); } }; 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); } } 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'; } } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 9081cbe9..c5e613ec 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,101 +1,193 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ -import {createMessage} from './Message' -import {GPGME_Key, createKey} from './Key' -import { isFingerprint, toKeyIdArray } from './Helpers'; +import {createMessage} from './Message'; +import {createKey} from './Key'; +import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; export class GPGME_Keyring { constructor(){ } /** * @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, prepare_sync){ - let me = this; return new Promise(function(resolve, reject) { - let msg; - msg = createMessage('keylist'); + let msg = createMessage('keylist'); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); msg.post().then(function(result){ let resultset = []; 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()); + if (result.keys.length === 0){ + resolve([]); + } else { + for (let i=0; i< result.keys.length; i++){ + let k = createKey(result.keys[i].fingerprint); + k.setKeyData(result.keys[i]); + if (prepare_sync === true){ + promises.push(k.getArmor()); + promises.push(k.getHasSecret()); + } + resultset.push(k); } - resultset.push(k); - } - if (promises.length > 0) { - Promise.all(promises).then(function (res){ + if (promises.length > 0) { + Promise.all(promises).then(function() { + resolve(resultset); + }, function(error){ + reject(error); + }); + } else { resolve(resultset); - }, function(error){ - reject(error); - }); + } } - }, function(error){ - reject(error); }); }); } /** * 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} pattern (optional) * @returns {Promise} 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!) -}; + /** + * + * @param {String} armored Armored Key block of the Kex(s) to be imported into gnupg + * @param {Boolean} prepare_sync prepare the keys for synched use (see getKeys()). + * @returns {Promise>} An array of objects for the Keys considered. + * Key.key The key itself as a GPGME_Key + * Key.status String: + * 'nochange' if the Key was not changed, + * 'newkey' if the Key was imported in gpg, and did not exist previously, + * 'change' if the key existed, but details were updated. For details, + * Key.changes is available. + * Key.changes.userId: Boolean userIds changed + * Key.changes.signature: Boolean signatures changed + * Key.changes.subkey: Boolean subkeys changed + * // TODO: not yet implemented: Information about Keys that failed + * (e.g. malformed Keys, secretKeys are not accepted) + */ + importKey(armored, prepare_sync) { + if (!armored || typeof(armored) !== 'string'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('import'); + msg.setParameter('data', armored); + msg.post().then(function(response){ + let infos = {}; + let fprs = []; + for (var res=0; res < response.result[0].imports.length; res++) { + let result = response.result[0].imports[res]; + let status = ''; + if (result.status === 0){ + status = 'nochange'; + } else if ((result.status & 1) === 1){ + status = 'newkey'; + } else { + status = 'change'; + } + let changes = {}; + changes.userId = (result.status & 2) === 2; + changes.signature = (result.status & 4) === 4; + changes.subkey = (result.status & 8) === 8; + //16 new secret key: not implemented + + fprs.push(result.fingerprint); + infos[result.fingerprint] = { + changes: changes, + status: status + }; + } + let resultset = []; + if (prepare_sync === true){ + me.getKeys(fprs, true).then(function(result){ + for (let i=0; i < result.length; i++) { + resultset.push({ + key: result[i], + changes: infos[result[i].fingerprint].changes, + status: infos[result[i].fingerprint].status + }); + } + resolve(resultset); + }, function(error){ + reject(error); + }); + } else { + for (let i=0; i < fprs.length; i++) { + resultset.push({ + key: createKey(fprs[i]), + changes: infos[fprs[i]].changes, + status: infos[fprs[i]].status + }); + } + resolve(resultset); + } + + }, function(error){ + reject(error); + }); + + + }); + + + } + + deleteKey(fingerprint){ + if (isFingerprint(fingerprint) === true) { + let key = createKey(fingerprint); + key.delete(); + } + } + + // generateKey +} diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index 2a21a6ae..07147bac 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -1,55 +1,126 @@ -import {Connection} from "./src/Connection"; -import {createKey} from "./src/Key"; +import {Connection} from './src/Connection'; +import {createKey} from './src/Key'; let conn = new Connection; export const helper_params = { validLongId: '0A0A0A0A0A0A0A0A', validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], invalidLongId: '9A9A7A7A8A9A9A7A7A8A', invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], invalidKeyArray: {curiosity:'uncat'}, invalidKeyArray_OneBad: [ createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], invalidErrorCode: 'Please type in all your passwords.', validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), valid_openpgplike: { primaryKey: { getFingerprint: function(){ return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} - } } -} + } +}; export const message_params = { invalid_op_action : 'dance', invalid_op_type : [234, 34, '<>'], - valid_encrypt_data: "مرحبا بالعالم", + valid_encrypt_data: 'مرحبا بالعالم', invalid_param_test: { valid_op: 'encrypt', invalid_param_names: [22,'dance', {}], validparam_name_0: 'mime', invalid_values_0: [2134, 'All your passwords', createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), null] } -} +}; 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 + // 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. + // 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'] -} + 'can_sign','can_certify','can_authenticate','secret','is_qualified'] +}; +export const armoredKey = { + fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A', + key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + + '\n' + + 'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' + + 'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' + + 'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' + + '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' + + 'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' + + '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' + + 'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' + + 'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' + + 'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' + + 'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' + + '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' + + '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' + + 'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' + + 'uzEXPGW+sq0WRp3hynn7kVP6QQYvuQENBFsPvK0BCACwvBcmbnGJk8XhEBRu2QN3\n' + + 'jKgVs3CG5nE2Xh20JipZwAuGHugDLv6/jlizzz5jtj3SAHVtJB8lJW8I0cNSEIX8\n' + + 'bRYH4C7lP2DTb9CgMcGErQIyK480+HIsbsZhJSNHdjUUl6IPEEVfSQzWaufmuswe\n' + + 'e+giqHiTsaiW20ytXilwVGpjlHBaxn/bpskZ0YRasgnPqKgJD3d5kunNqWoyCpMc\n' + + 'FYgDERvPbhhceFbvFE9G/u3gbcuV15mx53dDX0ImvPcvJnDOyJS9yr7ApdOV312p\n' + + 'A1MLbxfPnbnVu+dGXn7D/VCDd5aBYVPm+5ANrk6z9lYKH9aO5wgXpLAdJvutCOL5\n' + + 'ABEBAAGJATwEGAEIACYWIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUCWw+8rQIbDAUJ\n' + + 'A8JnAAAKCRD2N5DBHmAnigMVB/484G2+3R0cAaj3V/z4gW3MRSMhcYqEMyJ/ACdo\n' + + '7y8eoreYW843JWWVDRY6/YcYYGuBBP47WO4JuP2wIlVn17XOCSgnNjmmjsIYiAzk\n' + + 'op772TB27o0VeiFX5iWcawy0EI7JCb23xpI+QP31ksL2yyRYFXCtXSUfcOrLpCY8\n' + + 'aEQMQbAGtkag1wHTo/Tf/Vip8q0ZEQ4xOKTR2/ll6+inP8kzGyzadElUnH1Q1OUX\n' + + 'd2Lj/7BpBHE2++hAjBQRgnyaONF7mpUNEuw64iBNs0Ce6Ki4RV2+EBLnFubnFNRx\n' + + 'fFJcYXcijhuf3YCdWzqYmPpU/CtF4TgDlfSsdxHxVOmnZkY3\n' + + '=qP6s\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n', + keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + + '\n' + + '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\n' + + '=9WZ7\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n' +}; \ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 443aa685..d1118448 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -1,358 +1,420 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ -import "./node_modules/mocha/mocha"; -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"; -import { GPGME_Key , createKey } from "./src/Key"; -import { GPGME_Keyring } from "./src/Keyring"; -import {GPGME_Message, createMessage} from "./src/Message"; -import { setTimeout } from "timers"; + +import './node_modules/mocha/mocha'; /*global mocha, it, describe*/ +import './node_modules/chai/chai';/*global 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'; +import { GPGME_Key , createKey } from './src/Key'; +import { GPGME_Keyring } from './src/Keyring'; +import {GPGME_Message, createMessage} from './src/Message'; mocha.setup('bdd'); var expect = chai.expect; chai.config.includeStack = true; function unittests (){ describe('Connection testing', function(){ it('Connecting', function(done) { let conn0 = new Connection; conn0.checkConnection().then(function(answer) { expect(answer).to.not.be.empty; expect(answer.gpgme).to.not.be.undefined; expect(answer.gpgme).to.be.a('string'); expect(answer.info).to.be.an('Array'); expect(conn0.disconnect).to.be.a('function'); expect(conn0.post).to.be.a('function'); done(); }); }); it('Disconnecting', function(done) { let conn0 = new Connection; conn0.checkConnection(false).then(function(answer) { expect(answer).to.be.true; conn0.disconnect(); conn0.checkConnection(false).then(function(result) { expect(result).to.be.false; done(); }); }); }); }); describe('Error Object handling', function(){ // TODO: new GPGME_Error codes it('check the Timeout error', function(){ let test0 = gpgme_error('CONN_TIMEOUT'); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('CONN_TIMEOUT'); }); - it('Error Object returns generic code if code is not listed', function(){ - let test0 = gpgme_error(hp.invalidErrorCode); + it('Error Object returns generic code if code is not listed', + function(){ + let test0 = gpgme_error(hp.invalidErrorCode); - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('GENERIC_ERROR'); - }); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('GENERIC_ERROR'); + } + ); it('Warnings like PARAM_IGNORED should not return errors', function(){ let test0 = gpgme_error('PARAM_IGNORED'); expect(test0).to.be.null; }); }); describe('Fingerprint checking', function(){ it('isFingerprint(): valid Fingerprint', function(){ let test0 = isFingerprint(hp.validFingerprint); expect(test0).to.be.true; }); it('isFingerprint(): invalid Fingerprints', function(){ for (let i=0; i < hp.invalidFingerprints.length; i++){ let test0 = isFingerprint(hp.invalidFingerprints[i]); expect(test0).to.be.false; } }); }); describe('toKeyIdArray() (converting input to fingerprint)', function(){ it('Correct fingerprint string', function(){ let test0 = toKeyIdArray(hp.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validFingerprint); }); it('correct GPGME_Key', function(){ expect(hp.validGPGME_Key).to.be.an.instanceof(GPGME_Key); let test0 = toKeyIdArray(hp.validGPGME_Key); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validGPGME_Key.fingerprint); }); it('openpgpjs-like object', function(){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); expect(test0).to.include( hp.valid_openpgplike.primaryKey.getFingerprint()); }); it('Array of valid inputs', function(){ let test0 = toKeyIdArray(hp.validKeys); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf(hp.validKeys.length); }); it('Incorrect inputs', function(){ it('valid Long ID', function(){ let test0 = toKeyIdArray(hp.validLongId); expect(test0).to.be.empty; }); it('invalidFingerprint', function(){ let test0 = toKeyIdArray(hp.invalidFingerprint); expect(test0).to.be.empty; }); it('invalidKeyArray', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray); expect(test0).to.be.empty; }); it('Partially invalid array', function(){ let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); expect(test0).to.have.lengthOf( hp.invalidKeyArray_OneBad.length - 1); }); }); }); describe('GPGME_Key', function(){ it('correct Key initialization', function(){ let key = createKey(kp.validKeyFingerprint); expect(key).to.be.an.instanceof(GPGME_Key); }); it('Key has data after a first refresh', function(done) { let key = createKey(kp.validKeyFingerprint); 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); done(); }); }); it('Non-cached key async data retrieval', function (done){ let key = createKey(kp.validKeyFingerprint); key.get('can_authenticate',false).then(function(result){ 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){ + key.get('armored', 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); key.refreshKey().then(function(){}, function(error){ expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('KEY_NOKEY'); done(); - }); + }); }); it('createKey returns error if parameters are wrong', function(){ for (let i=0; i< 4; i++){ let key0 = createKey(wp.four_invalid_params[i]); expect(key0).to.be.an.instanceof(Error); expect(key0.code).to.equal('PARAM_WRONG'); } }); it('malformed GPGME_Key cannot be used', function(){ for (let i=0; i < 4; i++){ let key = new GPGME_Key(wp.four_invalid_params[i]); expect(key.fingerprint).to.be.an.instanceof(Error); expect(key.fingerprint.code).to.equal('KEY_INVALID'); } }); // TODO: tests for subkeys // TODO: tests for userids // TODO: some invalid tests for key/keyring }); describe('GPGME_Keyring', function(){ it('correct Keyring initialization', function(){ let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.getKeys).to.be.a('function'); }); it('Loading Keys from Keyring, to be used synchronously', function(done){ let keyring = new GPGME_Keyring; 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( + expect(result[0].get('armored')).to.be.a('string'); + expect(result[0].get('armored')).to.include( '-----END PGP PUBLIC KEY BLOCK-----'); done(); }); }); + + it('Loading specific Key from Keyring, to be used synchronously', function(done){ + let keyring = new GPGME_Keyring; + keyring.getKeys(kp.validKeyFingerprint, true).then(function(result){ + expect(result).to.be.an('array'); + expect(result[0]).to.be.an.instanceof(GPGME_Key); + expect(result[0].get('armored')).to.be.a('string'); + expect(result[0].get('armored')).to.include( + '-----END PGP PUBLIC KEY BLOCK-----'); + done(); + }); + }); + + it('Querying non-existing Key from Keyring', function(done){ + let keyring = new GPGME_Keyring; + keyring.getKeys(kp.invalidKeyFingerprint, true).then(function(result){ + expect(result).to.be.an('array'); + expect(result.length).to.equal(0); + done(); + }); + }); }); + // describe('Keyring import/export', function(){ + // before(function(done) { + // let keyring = new GPGME_Keyring; + + // keyring.getKeys(ak.fingerprint, false).then(function(result){ + // if (result.length === 1){ + // result[0].delete().then(function(delete_result){ + // if (delete_result === true){ + // done(); + // } + // }); + // } else { + // done(); + // } + // }); + // }); + // it('Import Public Key', function(done){ + // keyring.importKey(ak.key).then(function(result){ + // expect(result).to.be.an('array'); + // expect(result[0].key).to.be.an.instanceof(GPGME_Key); + // expect(result[0].changed).to.equal('newkey'); + // expect(result[0].key.keyring).to.equal(ak.fingerprint); + // done(); + // }); + // }); + + // it('Update Public Key', function(done){ + // keyring.importKey(ak.key).then(function(result){ + // expect(result).to.be.an('array'); + // expect(result[0].key).to.be.an.instanceof(GPGME_Key); + // expect(result[0].changed).to.equal('change'); + // expect(result[0].changes.userId).to.be.true; + // expect(result[0].changes.subkeys).to.be.false; + // expect(result[0].key.keyring).to.equal(ak.fingerprint); + // done(); + // }); + // }); + // }); + describe('GPGME_Message', function(){ it('creating encrypt Message', function(){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); expect(test0.isComplete).to.be.false; }); it('Message is complete after setting mandatory data', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete).to.be.true; }); it('Message is not complete after mandatory data is empty', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', ''); test0.setParameter('keys', hp.validFingerprints); expect(test0.isComplete).to.be.false; }); it('Complete Message contains the data that was set', function(){ let test0 = createMessage('encrypt'); test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); expect(test0.message).to.not.be.null; expect(test0.message).to.have.keys('op', 'data', 'keys'); expect(test0.message.op).to.equal('encrypt'); expect(test0.message.data).to.equal( mp.valid_encrypt_data); }); it ('Not accepting non-allowed operation', function(){ let test0 = createMessage(mp.invalid_op_action); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('MSG_WRONG_OP'); }); it('Not accepting wrong parameter type', function(){ let test0 = createMessage(mp.invalid_op_type); expect(test0).to.be.an.instanceof(Error); expect(test0.code).to.equal('PARAM_WRONG'); }); it('Not accepting wrong parameter name', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let i=0; i < mp.invalid_param_test.invalid_param_names.length; i++){ - let ret = test0.setParameter( - mp.invalid_param_test.invalid_param_names[i], - 'Somevalue'); + let ret = test0.setParameter( + mp.invalid_param_test.invalid_param_names[i], + 'Somevalue'); - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + expect(ret).to.be.an.instanceof(Error); + expect(ret.code).to.equal('PARAM_WRONG'); } }); it('Not accepting wrong parameter value', function(){ let test0 = createMessage(mp.invalid_param_test.valid_op); for (let j=0; j < mp.invalid_param_test.invalid_values_0.length; j++){ - let ret = test0.setParameter( - mp.invalid_param_test.validparam_name_0, - mp.invalid_param_test.invalid_values_0[j]); + let ret = test0.setParameter( + mp.invalid_param_test.validparam_name_0, + mp.invalid_param_test.invalid_values_0[j]); - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + expect(ret).to.be.an.instanceof(Error); + expect(ret.code).to.equal('PARAM_WRONG'); } }); }); } export default {unittests}; \ No newline at end of file