From eef3a509fa5744e5f09ec8084985e6070b78226b Mon Sep 17 00:00:00 2001 From: "raimund.renkert@intevation.de" Date: Tue, 10 Apr 2018 11:33:14 +0200 Subject: js: Initial commit for JavaScript Native Messaging API -- Note this code misses all the legal boilerplate; please add this as soon as possible and provide a DCO so we can merge it into master. I also removed the dist/ directory because that was not source code. --- lang/README | 2 +- lang/js/CHECKLIST | 30 ++++++++ lang/js/CHECKLIST_build | 9 +++ lang/js/README | 52 +++++++++++++ lang/js/manifest.json | 18 +++++ lang/js/package.json | 17 +++++ lang/js/src/Connection.js | 76 ++++++++++++++++++ lang/js/src/gpgmejs.js | 187 +++++++++++++++++++++++++++++++++++++++++++++ lang/js/src/index.js | 14 ++++ lang/js/testapplication.js | 21 +++++ lang/js/testicon.png | Bin 0 -> 16192 bytes lang/js/ui.css | 10 +++ lang/js/ui.html | 24 ++++++ lang/js/webpack.conf.js | 13 ++++ 14 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 lang/js/CHECKLIST create mode 100644 lang/js/CHECKLIST_build create mode 100644 lang/js/README create mode 100644 lang/js/manifest.json create mode 100644 lang/js/package.json create mode 100644 lang/js/src/Connection.js create mode 100644 lang/js/src/gpgmejs.js create mode 100644 lang/js/src/index.js create mode 100644 lang/js/testapplication.js create mode 100644 lang/js/testicon.png create mode 100644 lang/js/ui.css create mode 100644 lang/js/ui.html create mode 100644 lang/js/webpack.conf.js diff --git a/lang/README b/lang/README index ee99f0f1..afd7b083 100644 --- a/lang/README +++ b/lang/README @@ -13,4 +13,4 @@ cl Common Lisp cpp C++ qt Qt-Framework API python Python 2 and 3 (module name: gpg) -javascript Native messaging client for the gpgme-json server. +js Native messaging client for the gpgme-json server. diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST new file mode 100644 index 00000000..79a35cb7 --- /dev/null +++ b/lang/js/CHECKLIST @@ -0,0 +1,30 @@ +NativeConnection: + + [X] nativeConnection: successfully sending an encrypt request, +receiving an answer + [X] nativeConnection successfull on Chromium, chrome and firefox + [ ] nativeConnection successfull on Windows, macOS, Linux + [ ] nativeConnection with delayed, multipart (> 1MB) answer + +replicating Openpgpjs API: + + [*] Message handling (encrypt, verify, sign) + [ ] Key handling (import/export, modifying, status queries) + [ ] Configuration handling + [ ] check for completeness + [ ] handling of differences to openpgpjs + +Communication with other implementations + + [ ] option to export SECRET Key into localstore used by e.g. mailvelope + +Management: + [*] Define the gpgme interface + [ ] check Permissions (e.g. csp) for the different envs + [ ] agree on license + [ ] tests + + +Problems: + [X] gpgme-json: interactive mode vs. bytelength; filename + [X] nativeApp chokes on arrays. We will get rid of that bnativeapp anyhow diff --git a/lang/js/CHECKLIST_build b/lang/js/CHECKLIST_build new file mode 100644 index 00000000..fa162a10 --- /dev/null +++ b/lang/js/CHECKLIST_build @@ -0,0 +1,9 @@ +- Checklist for build/install: + +browsers' manifests (see README) need allowedextension added, and the path set + +manifest.json/ csp needs adaption + +/dist contains a current build which is used by example app. +We may either want to update it on every commit, or never at all, but not +inconsistently. diff --git a/lang/js/README b/lang/js/README new file mode 100644 index 00000000..3ca07439 --- /dev/null +++ b/lang/js/README @@ -0,0 +1,52 @@ +This is an example app for gpgme-json. +As of now, it only encrypts a given text. + +Installation +------------- + +gpgmejs uses webpack, the builds can be found in dist/ +(the testapplication uses that script at that location). To create a new +package, the command is npx webpack --config webpack.conf.js. +If you want a more debuggable (i.e. not minified) build, just change the mode +in webpack.conf.js. + +Demo WebExtension: +As soon as a bundled webpack is in dist/ (TODO: .gitignore or not?), +the gpgmejs folder can just be included in the extensions tab of the browser in +questions (extension debug mode needs to be active). For chrome, selecting the +folder is sufficient, for firefox, the manifest.json needs to be selected. + +In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json' +is needed, with the following content: + +(The path to the native app gpgme-json may need adaption) + +Chromium: +~/.config/chromium/NativeMessagingHosts/gpgmejson.json + +{ + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] +} +The ExtensionIdentifier can be seen on the chrome://extensions page, and +changes on each reinstallation. Note the slashes in allowed_origins. + + +Firefox: +~/.mozilla/native-messaging-hosts/gpgmejson.json +{ + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_extensions": ["ExtensionIdentifier@temporary-addon"] +} +The ExtensionIdentifier can be seen as Extension ID on the about:addons page if +addon-debugging is active. In firefox, the temporary addon is removed once +firefox exits, and the identifier will need to be changed more often. + +For testing purposes, it could be a good idea to change the keyID in the +ui.html, to not having to type it every time. diff --git a/lang/js/manifest.json b/lang/js/manifest.json new file mode 100644 index 00000000..8bb5c58d --- /dev/null +++ b/lang/js/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 2, + + "name": "gpgme-json with native Messaging", + "description": "This should be able to encrypt a text using gpgme-json", + "version": "0.1", + "content_security_policy": "default-src 'self' 'unsafe-eval' filesystem", + "browser_action": { + "default_icon": "testicon.png", + "default_title": "gpgme.js", + "default_popup": "ui.html" + }, + "permissions": ["nativeMessaging", "activeTab"], + + "background": { + "scripts": [ "dist/gpgmejs.bundle.js"] + } +} diff --git a/lang/js/package.json b/lang/js/package.json new file mode 100644 index 00000000..46b60fd2 --- /dev/null +++ b/lang/js/package.json @@ -0,0 +1,17 @@ +{ + "name": "gpgmejs", + "version": "0.0.1", + "description": "javascript part of a nativeMessaging gnupg integration", + "main": "src/gpgmejs.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "", + "devDependencies": { + "webpack": "^4.3.0", + "webpack-cli": "^2.0.13" + } +} diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js new file mode 100644 index 00000000..e8fea542 --- /dev/null +++ b/lang/js/src/Connection.js @@ -0,0 +1,76 @@ +/** + * 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. + */ + +export function Connection(){ + if (!this.connection){ + this.connection = connect(); + this._msg = { + 'always-trust': true, + // 'no-encrypt-to': false, + // 'no-compress': true, + // 'throw-keyids': false, + // 'wrap': false, + 'armor': true, + 'base64': false + }; + }; + + this.disconnect = function () { + if (this.connection){ + this.connection.disconnect(); + } + }; + + /** + * Sends a message and resolves with the answer. + * @param {*} operation The interaction requested from gpgme + * @param {*} message A json-capable object to pass the operation details. + * TODO: _msg should contain configurable parameters + */ + this.post = function(operation, message){ + let timeout = 5000; + let me = this; + if (!message || !operation){ + return Promise.reject('no message'); // TBD + } + + let keys = Object.keys(message); + for (let i=0; i < keys.length; i++){ + let property = keys[i]; + me._msg[property] = message[property]; + } + me._msg['op'] = operation; + // TODO fancier checks if what we want is consistent with submitted content + return new Promise(function(resolve, reject){ + me.connection.onMessage.addListener(function(msg) { + if (!msg){ + reject('empty answer.'); + } + if (msg.type === "error"){ + reject(msg.msg); + } + resolve(msg); + }); + + me.connection.postMessage(me._msg); + setTimeout( + function(){ + me.disconnect(); + reject('Timeout'); + }, timeout); + }); + }; +}; + + +function connect(){ + let connection = chrome.runtime.connectNative('gpgmejson'); + if (!connection){ + let msg = chrome.runtime.lastError || 'no message'; //TBD + throw(msg); + } + return connection; +}; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js new file mode 100644 index 00000000..dedbf809 --- /dev/null +++ b/lang/js/src/gpgmejs.js @@ -0,0 +1,187 @@ +import {Connection} from "./Connection" + +export function encrypt(data, publicKeys, privateKeys, passwords=null, + sessionKey, filename, compression, armor=true, detached=false, + signature=null, returnSessionKey=false, wildcard=false, date=new Date()){ + // gpgme_op_encrypt ( <-gpgme doc on this operation + // gpgme_ctx_t ctx, + // gpgme_key_t recp[], + // gpgme_encrypt_flags_t flags, + // gpgme_data_t plain, + // gpgme_data_t cipher) + // flags: + // GPGME_ENCRYPT_ALWAYS_TRUST + // GPGME_ENCRYPT_NO_ENCRYPT_TO + // GPGME_ENCRYPT_NO_COMPRESS + // GPGME_ENCRYPT_PREPARE + // GPGME_ENCRYPT_EXPECT_SIGN + // GPGME_ENCRYPT_SYMMETRIC + // GPGME_ENCRYPT_THROW_KEYIDS + // GPGME_ENCRYPT_WRAP + if (passwords !== null){ + throw('Password!'); // TBD + } + + let pubkeys = toKeyIdArray(publicKeys); + let privkeys = toKeyIdArray(privateKeys); + + // TODO filename: data is supposed to be empty, file is provided + // TODO config compression detached signature + // TODO signature to add to the encrypted message (?) || privateKeys: signature is desired + // gpgme_op_encrypt_sign (gpgme_ctx_t ctx, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t cipher) + + // TODO sign date overwriting implemented in gnupg? + + let conn = new Connection(); + if (wildcard){ + // Connection.set('throw-keyids', true); TODO Connection.set not yet existant + } + return conn.post('encrypt', { + 'data': data, + 'keys': publicKeys, + 'armor': armor}); +}; + +export function decrypt(message, privateKeys, passwords, sessionKeys, publicKeys, + format='utf8', signature=null, date=new Date()) { + if (passwords !== null){ + throw('Password!'); // TBD + } + if (format === 'binary'){ + // Connection.set('base64', true); + } + if (publicKeys || signature){ + // Connection.set('signature', signature); + // request verification, too + } + //privateKeys optionally if keyId was thrown? + // gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) + // response is gpgme_op_decrypt_result (gpgme_ctx_t ctx) (next available?) + return conn.post('decrypt', { + 'data': message + }); +} + +// BIG TODO. +export function generateKey({userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve="", date=new Date()}){ + throw('not implemented here'); + // gpgme_op_createkey (gpgme_ctx_t ctx, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t extrakey, unsigned int flags); + return false; +} + +export function sign({ data, privateKeys, armor=true, detached=false, date=new Date() }) { + //TODO detached GPGME_SIG_MODE_DETACH | GPGME_SIG_MODE_NORMAL + // gpgme_op_sign (gpgme_ctx_t ctx, gpgme_data_t plain, gpgme_data_t sig, gpgme_sig_mode_t mode) + // TODO date not supported + + let conn = new Connection(); + let privkeys = toKeyIdArray(privateKeys); + return conn.post('sign', { + 'data': data, + 'keys': privkeys, + 'armor': armor}); +}; + +export function verify({ message, publicKeys, signature=null, date=new Date() }) { + //TODO extra signature: sig, signed_text, plain: null + // inline sig: signed_text:null, plain as writable (?) + // date not supported + //gpgme_op_verify (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plain) + let conn = new Connection(); + let privkeys = toKeyIdArray(privateKeys); + return conn.post('sign', { + 'data': data, + 'keys': privkeys, + 'armor': armor}); +} + + +export function reformatKey(privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0){ + let privKey = toKeyIdArray(privateKey); + if (privKey.length !== 1){ + return false; //TODO some error handling. There is not exactly ONE key we are editing + } + let conn = new Connection(); + // TODO key management needs to be changed somewhat + return conn.post('TODO', { + 'key': privKey[0], + 'keyExpirationTime': keyExpirationTime, //TODO check if this is 0 or a positive and plausible number + 'userIds': userIds //TODO check if empty or plausible strings + }); + // unlocked will be ignored +} + +export function decryptKey({ privateKey, passphrase }) { + throw('not implemented here'); + return false; +}; + +export function encryptKey({ privateKey, passphrase }) { + throw('not implemented here'); + return false; +}; + +export function encryptSessionKey({data, algorithm, publicKeys, passwords, wildcard=false }) { + //openpgpjs: + // Encrypt a symmetric session key with public keys, passwords, or both at + // once. At least either public keys or passwords must be specified. + throw('not implemented here'); + return false; +}; + +export function decryptSessionKeys({ message, privateKeys, passwords }) { + throw('not implemented here'); + return false; +}; + +// //TODO worker handling + +// //TODO key representation +// //TODO: keyring handling + + +/** + * Helper functions and checks + */ + +/** + * Checks if the submitted value is a keyID. + * TODO: should accept all strings that are accepted as keyID by gnupg + * TODO: See if Key becomes an object later on + * @param {*} key input value. Is expected to be a string of 8,16 or 40 chars + * representing hex values. Will return false if that expectation is not met + */ +function isKeyId(key){ + if (!key || typeof(key) !== "string"){ + return false; + } + if ([8,16,40].indexOf(key.length) < 0){ + return false; + } + let regexp= /^[0-9a-fA-F]*$/i; + return regexp.test(key); +}; + +/** + * Tries to return an array of keyID values, either from a string or an array. + * Filters out those that do not meet the criteria. (TODO: silently for now) + * @param {*} array Input value. + */ +function toKeyIdArray(array){ + let result = []; + if (!array){ + return result; + } + if (!Array.isArray(array)){ + if (isKeyId(array) === true){ + return [keyId]; + } + return result; + } + for (let i=0; i < array.length; i++){ + if (isKeyId(array[i]) === true){ + result.push(array[i]); + } + } + return result; +}; diff --git a/lang/js/src/index.js b/lang/js/src/index.js new file mode 100644 index 00000000..02dc919d --- /dev/null +++ b/lang/js/src/index.js @@ -0,0 +1,14 @@ +import * as gpgmejs from'./gpgmejs' +export default gpgmejs; + +/** + * Export each high level api function separately. + * Usage: + * + * import { encryptMessage } from 'gpgme.js' + * encryptMessage(keys, text) + */ +export { + encrypt, decrypt, sign, verify, + generateKey, reformatKey + } from './gpgmejs'; diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js new file mode 100644 index 00000000..d01aca99 --- /dev/null +++ b/lang/js/testapplication.js @@ -0,0 +1,21 @@ +/** +* Testing nativeMessaging. This is a temporary plugin using the gpgmejs + implemetation as contained in src/ +*/ +function buttonclicked(event){ + let data = document.getElementById("text0").value; + let keyId = document.getElementById("key").value; + let enc = Gpgmejs.encrypt(data, [keyId]).then(function(answer){ + console.log(answer); + console.log(answer.type); + console.log(answer.data); + alert(answer.data); + }, function(errormsg){ + alert('Error: '+ errormsg); + }); +}; + +document.addEventListener('DOMContentLoaded', function() { + document.getElementById("button0").addEventListener("click", + buttonclicked); + }); diff --git a/lang/js/testicon.png b/lang/js/testicon.png new file mode 100644 index 00000000..12c3f5df Binary files /dev/null and b/lang/js/testicon.png differ diff --git a/lang/js/ui.css b/lang/js/ui.css new file mode 100644 index 00000000..9c88698b --- /dev/null +++ b/lang/js/ui.css @@ -0,0 +1,10 @@ +ul { + list-style-type: none; + padding-left: 0px; +} + +ul li span { + float: left; + width: 120px; + margin-top: 6px; +} diff --git a/lang/js/ui.html b/lang/js/ui.html new file mode 100644 index 00000000..9c56c2e5 --- /dev/null +++ b/lang/js/ui.html @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+
+ + diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js new file mode 100644 index 00000000..71b71161 --- /dev/null +++ b/lang/js/webpack.conf.js @@ -0,0 +1,13 @@ +const path = require('path'); + +module.exports = { + entry: './src/index.js', + // mode: 'development', + mode: 'production', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'gpgmejs.bundle.js', + libraryTarget: 'var', + library: 'Gpgmejs' + } +}; -- cgit v1.2.3 From 6ab25e40d904007755c5d999bf66ae264236e745 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 18 Apr 2018 16:38:06 +0200 Subject: js: encrypt improvement and decrypt method * Compatibility class gpgme_openpgpjs offers an API that should accept openpgpjs syntax, throwing errors if a parameter is unexpected/not implemented * tried to be more generic in methods * waiting for multiple answers if 'more' is in the answer * more consistency checking on sending and receiving * updated the example extension -- --- lang/js/CHECKLIST | 18 ++- lang/js/CHECKLIST_build | 6 +- lang/js/README | 23 +-- lang/js/manifest.json | 10 +- lang/js/package.json | 6 +- lang/js/src/Connection.js | 208 ++++++++++++++++++++------- lang/js/src/Helpers.js | 84 +++++++++++ lang/js/src/Message.js | 109 ++++++++++++++ lang/js/src/gpgmejs.js | 282 +++++++++++++++---------------------- lang/js/src/gpgmejs_openpgpjs.js | 156 ++++++++++++++++++++ lang/js/src/index.js | 33 +++-- lang/js/src/permittedOperations.js | 75 ++++++++++ lang/js/test_index.js | 25 ++++ lang/js/testapplication.js | 70 ++++++--- lang/js/testapplication_index.html | 9 ++ lang/js/ui.html | 24 ---- lang/js/ui2.html | 33 +++++ lang/js/webpack.conf.js | 22 +++ 18 files changed, 887 insertions(+), 306 deletions(-) create mode 100644 lang/js/src/Helpers.js create mode 100644 lang/js/src/Message.js create mode 100644 lang/js/src/gpgmejs_openpgpjs.js create mode 100644 lang/js/src/permittedOperations.js create mode 100644 lang/js/test_index.js create mode 100644 lang/js/testapplication_index.html delete mode 100644 lang/js/ui.html create mode 100644 lang/js/ui2.html diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST index 79a35cb7..49b17265 100644 --- a/lang/js/CHECKLIST +++ b/lang/js/CHECKLIST @@ -3,16 +3,19 @@ NativeConnection: [X] nativeConnection: successfully sending an encrypt request, receiving an answer [X] nativeConnection successfull on Chromium, chrome and firefox - [ ] nativeConnection successfull on Windows, macOS, Linux - [ ] nativeConnection with delayed, multipart (> 1MB) answer + [*] nativeConnection successfull on Windows, macOS, Linux + [*] nativeConnection with delayed, multipart (> 1MB) answer replicating Openpgpjs API: [*] Message handling (encrypt, verify, sign) - [ ] Key handling (import/export, modifying, status queries) + [x] encrypt + [ ] verify + [ ] sign + [*] Key handling (import/export, modifying, status queries) [ ] Configuration handling [ ] check for completeness - [ ] handling of differences to openpgpjs + [*] handling of differences to openpgpjs Communication with other implementations @@ -21,10 +24,5 @@ Communication with other implementations Management: [*] Define the gpgme interface [ ] check Permissions (e.g. csp) for the different envs - [ ] agree on license + [X] agree on license [ ] tests - - -Problems: - [X] gpgme-json: interactive mode vs. bytelength; filename - [X] nativeApp chokes on arrays. We will get rid of that bnativeapp anyhow diff --git a/lang/js/CHECKLIST_build b/lang/js/CHECKLIST_build index fa162a10..19eb2146 100644 --- a/lang/js/CHECKLIST_build +++ b/lang/js/CHECKLIST_build @@ -4,6 +4,6 @@ browsers' manifests (see README) need allowedextension added, and the path set manifest.json/ csp needs adaption -/dist contains a current build which is used by example app. -We may either want to update it on every commit, or never at all, but not -inconsistently. +/dist should be built which is used by the example app. + +csp in manifest.json MUST NOT contain "unsafe-eval" in production! diff --git a/lang/js/README b/lang/js/README index 3ca07439..5dc3f50b 100644 --- a/lang/js/README +++ b/lang/js/README @@ -1,20 +1,28 @@ -This is an example app for gpgme-json. -As of now, it only encrypts a given text. +gpgmejs, as contained in this directory, is a javascript library for direct use +of gnupg in browsers, with the help of nativeMessaging. Installation ------------- +gpgmejs uses webpack, and thus depends on nodejs for building. Webpack can be +installed by running +`npm install webpack webpack-cli --save-dev`. -gpgmejs uses webpack, the builds can be found in dist/ -(the testapplication uses that script at that location). To create a new -package, the command is npx webpack --config webpack.conf.js. +To create a current version of the package, the command is +`npx webpack --config webpack.conf.js`. If you want a more debuggable (i.e. not minified) build, just change the mode in webpack.conf.js. +TODO: gpgme_openpgpjs aims to offer an API similar to openpgpjs, throwing errors +if some part of the API is not implemented, 'translating' objects if possible. +This will be incorporated into the build process later, for now it is a line to +uncomment in src/index.js + Demo WebExtension: -As soon as a bundled webpack is in dist/ (TODO: .gitignore or not?), +As soon as a bundled webpack is in dist/ the gpgmejs folder can just be included in the extensions tab of the browser in questions (extension debug mode needs to be active). For chrome, selecting the folder is sufficient, for firefox, the manifest.json needs to be selected. +Please note that it is just for demonstration/debug purposes! In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json' is needed, with the following content: @@ -47,6 +55,3 @@ Firefox: The ExtensionIdentifier can be seen as Extension ID on the about:addons page if addon-debugging is active. In firefox, the temporary addon is removed once firefox exits, and the identifier will need to be changed more often. - -For testing purposes, it could be a good idea to change the keyID in the -ui.html, to not having to type it every time. diff --git a/lang/js/manifest.json b/lang/js/manifest.json index 8bb5c58d..e5e17aa5 100644 --- a/lang/js/manifest.json +++ b/lang/js/manifest.json @@ -4,15 +4,11 @@ "name": "gpgme-json with native Messaging", "description": "This should be able to encrypt a text using gpgme-json", "version": "0.1", - "content_security_policy": "default-src 'self' 'unsafe-eval' filesystem", + "content_security_policy": "default-src 'self' 'unsafe-eval' filesystem:", "browser_action": { "default_icon": "testicon.png", "default_title": "gpgme.js", - "default_popup": "ui.html" + "default_popup": "testapplication_index.html" }, - "permissions": ["nativeMessaging", "activeTab"], - - "background": { - "scripts": [ "dist/gpgmejs.bundle.js"] - } + "permissions": ["nativeMessaging", "activeTab"] } diff --git a/lang/js/package.json b/lang/js/package.json index 46b60fd2..2b7dd7ee 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -2,7 +2,7 @@ "name": "gpgmejs", "version": "0.0.1", "description": "javascript part of a nativeMessaging gnupg integration", - "main": "src/gpgmejs.js", + "main": "src/index.js", "private": true, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -11,7 +11,7 @@ "author": "", "license": "", "devDependencies": { - "webpack": "^4.3.0", - "webpack-cli": "^2.0.13" + "webpack": "^4.5.0", + "webpack-cli": "^2.0.14" } } diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index e8fea542..784929e9 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,76 +1,180 @@ +import { GPGME_Message } from "./Message"; + +/* 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' -export function Connection(){ - if (!this.connection){ - this.connection = connect(); - this._msg = { - 'always-trust': true, - // 'no-encrypt-to': false, - // 'no-compress': true, - // 'throw-keyids': false, - // 'wrap': false, - 'armor': true, - 'base64': false - }; - }; +export class Connection{ - this.disconnect = function () { - if (this.connection){ - this.connection.disconnect(); + /** + * Opens and closes a port. Thus, it is made sure that the connection can + * be used. + * THIS BEHAVIOUR MAY CHANGE! + * discussion is to keep a port alive as long as the context stays the same + * + * TODO returns nothing, but triggers exceptions if not successfull + */ + constructor(){ + this._connection = chrome.runtime.connectNative('gpgmejson'); + if (!this._connection){ + if (chrome.runtime.lastError){ + throw('NO_CONNECT_RLE'); + } else { + throw('NO_CONNECT'); + } } - }; + this._flags = {}; // TODO general config + } /** - * Sends a message and resolves with the answer. - * @param {*} operation The interaction requested from gpgme - * @param {*} message A json-capable object to pass the operation details. - * TODO: _msg should contain configurable parameters + * Immediately closes the open port */ - this.post = function(operation, message){ - let timeout = 5000; - let me = this; - if (!message || !operation){ - return Promise.reject('no message'); // TBD + disconnect() { + if (this._connection){ + this._connection.disconnect(); } + } - let keys = Object.keys(message); - for (let i=0; i < keys.length; i++){ - let property = keys[i]; - me._msg[property] = message[property]; + /** + * Sends a message and resolves with the answer. + * @param {GPGME_Message} message + * @returns {Promise} the gnupg answer, or rejection with error + * information + * TODO: better/more consistent error information + */ + post(message){ + if (!message || !message instanceof GPGME_Message){ + return Promise.reject('ERR_NO_MSG'); } - me._msg['op'] = operation; - // TODO fancier checks if what we want is consistent with submitted content + // let timeout = 5000; //TODO config + let me = this; return new Promise(function(resolve, reject){ - me.connection.onMessage.addListener(function(msg) { + let answer = new Answer(message.op); + let listener = function(msg) { if (!msg){ - reject('empty answer.'); - } - if (msg.type === "error"){ + me._connection.onMessage.removeListener(listener) + reject('EMPTY_ANSWER'); + } else if (msg.type === "error"){ + me._connection.onMessage.removeListener(listener) reject(msg.msg); + } else { + answer.add(msg); + if (msg.more === true){ + me._connection.postMessage({'op': 'getmore'}); + } else { + me._connection.onMessage.removeListener(listener) + resolve(answer.message); + } } - resolve(msg); - }); + }; - me.connection.postMessage(me._msg); - setTimeout( - function(){ - me.disconnect(); - reject('Timeout'); - }, timeout); + me._connection.onMessage.addListener(listener); + me._connection.postMessage(message); + //TBD: needs to be aware if there is a pinentry pending + // setTimeout( + // function(){ + // me.disconnect(); + // reject('TIMEOUT'); + // }, timeout); }); - }; + } }; +/** + * 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 return keys + */ +class Answer{ -function connect(){ - let connection = chrome.runtime.connectNative('gpgmejson'); - if (!connection){ - let msg = chrome.runtime.lastError || 'no message'; //TBD - throw(msg); + constructor(operation){ + this.operation = operation; } - return connection; -}; + + /** + * + * @param {Object} msg The message as received with nativeMessaging + * TODO: "error" and "more" handling are not in here, but in post() + */ + add(msg){ + if (this._response === undefined){ + this._response = {}; + } + let messageKeys = Object.keys(msg); + let poa = permittedOperations[this.operation].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){ + console.log( 'unexpected answer type: ' + msg.type); + throw('UNEXPECTED_TYPE'); + + } + 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] = this._response[key].concat(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]){ + throw('UNEXPECTED_TYPE'); + } + } + //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] = []; + } + this._response.push(msg[key]); + } + else { + console.log('unexpected answer parameter: ' + key); + throw('UNEXPECTED_PARAM'); + } + break; + } + } + } + + /** + * Returns the assembled message. TODO: does not care yet if completed. + */ + get message(){ + return this._response; + } +} diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js new file mode 100644 index 00000000..eeb7a3c4 --- /dev/null +++ b/lang/js/src/Helpers.js @@ -0,0 +1,84 @@ +/* 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+ + */ + +/** + * Tries to return an array of fingerprints, either from input fingerprints or + * from Key objects + * @param {String|Array} input Input value. + * @returns {Array} Array of fingerprints. + */ +export function toKeyIdArray(input){ + if (!input){ + return []; + // TODO: Warning or error here? Did we expect something or is "nothing" okay? + } + if (input instanceof Array){ + let result = []; + for (let i=0; i < input.length; i++){ + if (isFingerprint(input[i]) === true){ + result.push(input[i]); + } else { + //TODO error? + console.log('gpgmejs/Helpers.js Warning: '+ + input[i] + + ' is not a valid key fingerprint and will not be used'); + } + } + return result; + } else if (isFingerprint(input) === true) { + return [input]; + } + console.log('gpgmejs/Helpers.js Warning: ' + input + + ' is not a valid key fingerprint and will not be used'); + return []; +}; + +/** + * 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); +}; + +//TODO needed anywhere? +function isLongId(string){ + return hextest(string, 16); +}; + +//TODO needed anywhere? +function isShortId(string){ + return hextest(string, 8); +}; diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js new file mode 100644 index 00000000..90b554a1 --- /dev/null +++ b/lang/js/src/Message.js @@ -0,0 +1,109 @@ +/* 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 { permittedOperations } from './permittedOperations' + +export class GPGME_Message { + //TODO getter + + constructor(){ + } + + /** + * Defines the operation this message will have + * @param {String} operation Mus be defined in permittedOperations + * TODO: move to constructor? + */ + set operation (operation){ + if (!operation || typeof(operation) !== 'string'){ + throw('ERR_WRONG_PARAM'); + } + if (operation in permittedOperations){ + if (!this._msg){ + this._msg = {}; + } + this._msg.op = operation; + } else { + throw('ERR_NOT_IMPLEMENTED'); + } + } + + /** + * Sets a parameter for the message. Note that the operation has to be set + * first, to be able to check if the parameter is permittted + * @param {String} param Parameter to set + * @param {any} value Value to set //TODO: Some type checking + * @returns {Boolean} If the parameter was set successfully + */ + setParameter(param,value){ + if (!param || typeof(param) !== 'string'){ + throw('ERR_WRONG_PARAM'); + } + if (!this._msg || !this._msg.op){ + console.log('There is no operation specified yet. '+ + 'The parameter cannot be set'); + return false; + } + let po = permittedOperations[this._msg.op]; + if (!po){ + throw('LAZY_PROGRAMMER'); + //TODO + return false; + } + if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ + this._msg[param] = value; + return true; + } + console.log('' + param + ' is invalid and could not be set'); + return false; + } + + /** + * Check if the message has the minimum requirements to be sent, according + * to the definitions in permittedOperations + * @returns {Boolean} + */ + get isComplete(){ + if (!this._msg.op){ + return false; + } + let reqParams = permittedOperations[this._msg.op].required; + for (let i=0; i < reqParams.length; i++){ + if (!reqParams[i] in this._msg){ + return false; + } + } + return true; + } + + /** + * Returns the prepared message with parameters and completeness checked + * @returns {Object|null} Object to be posted to gnupg, or null if + * incomplete + */ + get message(){ + if (this.isComplete === true){ + return this._msg; + } + else { + return null; + } + + } +} \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index dedbf809..8323ac3b 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,187 +1,131 @@ -import {Connection} from "./Connection" - -export function encrypt(data, publicKeys, privateKeys, passwords=null, - sessionKey, filename, compression, armor=true, detached=false, - signature=null, returnSessionKey=false, wildcard=false, date=new Date()){ - // gpgme_op_encrypt ( <-gpgme doc on this operation - // gpgme_ctx_t ctx, - // gpgme_key_t recp[], - // gpgme_encrypt_flags_t flags, - // gpgme_data_t plain, - // gpgme_data_t cipher) - // flags: - // GPGME_ENCRYPT_ALWAYS_TRUST - // GPGME_ENCRYPT_NO_ENCRYPT_TO - // GPGME_ENCRYPT_NO_COMPRESS - // GPGME_ENCRYPT_PREPARE - // GPGME_ENCRYPT_EXPECT_SIGN - // GPGME_ENCRYPT_SYMMETRIC - // GPGME_ENCRYPT_THROW_KEYIDS - // GPGME_ENCRYPT_WRAP - if (passwords !== null){ - throw('Password!'); // TBD - } - - let pubkeys = toKeyIdArray(publicKeys); - let privkeys = toKeyIdArray(privateKeys); - - // TODO filename: data is supposed to be empty, file is provided - // TODO config compression detached signature - // TODO signature to add to the encrypted message (?) || privateKeys: signature is desired - // gpgme_op_encrypt_sign (gpgme_ctx_t ctx, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t cipher) - - // TODO sign date overwriting implemented in gnupg? - - let conn = new Connection(); - if (wildcard){ - // Connection.set('throw-keyids', true); TODO Connection.set not yet existant - } - return conn.post('encrypt', { - 'data': data, - 'keys': publicKeys, - 'armor': armor}); -}; +/* 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+ + */ -export function decrypt(message, privateKeys, passwords, sessionKeys, publicKeys, - format='utf8', signature=null, date=new Date()) { - if (passwords !== null){ - throw('Password!'); // TBD - } - if (format === 'binary'){ - // Connection.set('base64', true); - } - if (publicKeys || signature){ - // Connection.set('signature', signature); - // request verification, too +import {Connection} from "./Connection" +import {GPGME_Message} from './Message' +import {toKeyIdArray} from "./Helpers" + +export class GpgME { + /** + * initial check if connection si successfull. Will throw ERR_NO_CONNECT or + * ERR_NO_CONNECT_RLE (if chrome.runtime.lastError is available) if the + * connection fails. + * TODO The connection to the nativeMessaging host will, for now, be closed + * after each interaction. Session management with gpg_agent is TBD. + * TODO: add configuration + */ + constructor(){ + let conn = new Connection(); + // this.keyring = new Keyring(); TBD + // TODO config, e.g. + this.configuration = { + null_expire_is_never: true + }; + conn.disconnect(); } - //privateKeys optionally if keyId was thrown? - // gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) - // response is gpgme_op_decrypt_result (gpgme_ctx_t ctx) (next available?) - return conn.post('decrypt', { - 'data': message - }); -} -// BIG TODO. -export function generateKey({userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve="", date=new Date()}){ - throw('not implemented here'); - // gpgme_op_createkey (gpgme_ctx_t ctx, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t extrakey, unsigned int flags); - return false; -} + /** + * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array + * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message + * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message + */ + encrypt (data, publicKeys, wildcard=false){ -export function sign({ data, privateKeys, armor=true, detached=false, date=new Date() }) { - //TODO detached GPGME_SIG_MODE_DETACH | GPGME_SIG_MODE_NORMAL - // gpgme_op_sign (gpgme_ctx_t ctx, gpgme_data_t plain, gpgme_data_t sig, gpgme_sig_mode_t mode) - // TODO date not supported + let msg = new GPGME_Message; + msg.operation = 'encrypt'; - let conn = new Connection(); - let privkeys = toKeyIdArray(privateKeys); - return conn.post('sign', { - 'data': data, - 'keys': privkeys, - 'armor': armor}); -}; + // TODO temporary + msg.setParameter('armor', true); + msg.setParameter('always-trust', true); -export function verify({ message, publicKeys, signature=null, date=new Date() }) { - //TODO extra signature: sig, signed_text, plain: null - // inline sig: signed_text:null, plain as writable (?) - // date not supported - //gpgme_op_verify (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plain) - let conn = new Connection(); - let privkeys = toKeyIdArray(privateKeys); - return conn.post('sign', { - 'data': data, - 'keys': privkeys, - 'armor': armor}); -} + let pubkeys = toKeyIdArray(publicKeys); + msg.setParameter('keys', pubkeys); + putData(msg, data); + if (wildcard === true){msg.setParameter('throw-keyids', true); + }; -export function reformatKey(privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0){ - let privKey = toKeyIdArray(privateKey); - if (privKey.length !== 1){ - return false; //TODO some error handling. There is not exactly ONE key we are editing + if (msg.isComplete === true) { + let conn = new Connection(); + return (conn.post(msg.message)); + } + else { + return Promise.reject('NO_CONNECT'); + //TODO + } } - let conn = new Connection(); - // TODO key management needs to be changed somewhat - return conn.post('TODO', { - 'key': privKey[0], - 'keyExpirationTime': keyExpirationTime, //TODO check if this is 0 or a positive and plausible number - 'userIds': userIds //TODO check if empty or plausible strings - }); - // unlocked will be ignored -} -export function decryptKey({ privateKey, passphrase }) { - throw('not implemented here'); - return false; -}; + /** + * @param {String} data TODO Format: base64? String? Message with the encrypted data + * @returns {Promise} decrypted message: + data: The decrypted data. This may be base64 encoded. + base64: Boolean indicating whether data is base64 encoded. + mime: A Boolean indicating whether the data is a MIME object. + info: An optional object with extra information. + * @async + */ -export function encryptKey({ privateKey, passphrase }) { - throw('not implemented here'); - return false; -}; + decrypt(data){ -export function encryptSessionKey({data, algorithm, publicKeys, passwords, wildcard=false }) { - //openpgpjs: - // Encrypt a symmetric session key with public keys, passwords, or both at - // once. At least either public keys or passwords must be specified. - throw('not implemented here'); - return false; -}; - -export function decryptSessionKeys({ message, privateKeys, passwords }) { - throw('not implemented here'); - return false; -}; - -// //TODO worker handling - -// //TODO key representation -// //TODO: keyring handling - - -/** - * Helper functions and checks - */ - -/** - * Checks if the submitted value is a keyID. - * TODO: should accept all strings that are accepted as keyID by gnupg - * TODO: See if Key becomes an object later on - * @param {*} key input value. Is expected to be a string of 8,16 or 40 chars - * representing hex values. Will return false if that expectation is not met - */ -function isKeyId(key){ - if (!key || typeof(key) !== "string"){ - return false; - } - if ([8,16,40].indexOf(key.length) < 0){ - return false; + if (data === undefined){ + throw('ERR_EMPTY_MSG'); + } + let msg = new GPGME_Message; + msg.operation = 'decrypt'; + putData(msg, data); + // TODO: needs proper EOL to be decrypted. + + if (msg.isComplete === true){ + let conn = new Connection(); + return conn.post(msg.message); + } + else { + return Promise.reject('NO_CONNECT'); + //TODO + } } - let regexp= /^[0-9a-fA-F]*$/i; - return regexp.test(key); -}; +} /** - * Tries to return an array of keyID values, either from a string or an array. - * Filters out those that do not meet the criteria. (TODO: silently for now) - * @param {*} array Input value. + * Sets the data of the message, converting Uint8Array to base64 and setting + * the base64 flag + * @param {GPGME_Message} message The message where this data will be set + * @param {*} data The data to enter + * @param {String} propertyname // TODO unchecked still */ -function toKeyIdArray(array){ - let result = []; - if (!array){ - return result; - } - if (!Array.isArray(array)){ - if (isKeyId(array) === true){ - return [keyId]; - } - return result; +function putData(message, data){ + if (!message || !message instanceof GPGME_Message ) { + throw('NO_MESSAGE_OBJECT'); } - for (let i=0; i < array.length; i++){ - if (isKeyId(array[i]) === true){ - result.push(array[i]); - } + if (!data){ + //TODO Debug only! No data is legitimate + console.log('Warning. no data in message'); + message.setParameter('data', ''); + } else if (data instanceof Uint8Array){ + let decoder = new TextDecoder('utf8'); + message.setParameter('base64', true); + message.setParameter ('data', decoder.decode(data)); + } else if (typeof(data) === 'string') { + message.setParameter('base64', false); + message.setParameter('data', data); + } else { + throw('ERR_WRONG_TYPE'); } - return result; -}; +} \ No newline at end of file diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js new file mode 100644 index 00000000..1eec4da4 --- /dev/null +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -0,0 +1,156 @@ +/* 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+ + */ + +/** + * This is a compatibility API to be used as openpgpjs syntax. + * Non-implemented options will throw an error if set (not null or undefined) + * TODO Some info about differences + */ + + import { GpgME } from "./gpgmejs"; +// import {Keyring} from "./Keyring" TODO + + +export class GpgME_openPGPCompatibility { + + constructor(){ + this._gpgme = new GpgME; + } + + /** + * Encrypt Message + * Supported: + * @param {String|Uint8Array} data + * @param {Key|Array} publicKeys + * @param {Boolean} wildcard + * TODO: + * @param {Key|Array} privateKeys + * @param {String} filename + * @param {module:enums.compression} compression + * @param {Boolean} armor + * @param {Boolean} detached + * unsupported: + * @param {String|Array} passwords + * @param {Object} sessionKey + * @param {Signature} signature + * @param {Boolean} returnSessionKey + * + * @returns {Promise} + * {data: ASCII armored message, + * signature: detached signature if 'detached' is true + * } + * @async + * @static + */ + encrypt({data = '', publicKeys = '', privateKeys, passwords, sessionKey, + filename, compression, armor=true, detached=false, signature=null, + returnSessionKey=null, wildcard=false, date=null}) { + if (passwords !== undefined + || sessionKey !== undefined + || signature !== null + || returnSessionKey !== null + || date !== null){ + throw('NOT_IMPLEMENTED'); + } + if ( privateKeys + || filename + || compression + || armor === false + || detached == true){ + console.log('may be implemented later'); + throw('NOT_YET_IMPLEMENTED'); + } + return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); + } + + /** Decrypt Message + * supported + * TODO: @param {Message} message TODO: for now it accepts an armored string only + * Unsupported: + * @param {String|Array} passwords + * @param {Object|Array} sessionKeys + * @param {Date} date + + * TODO + * @param {Key|Array} privateKey + * @param {Key|Array} publicKeys + * @param {String} format (optional) return data format either as 'utf8' or 'binary' + * @param {Signature} signature (optional) detached signature for verification + + * @returns {Promise} decrypted and verified message in the form: + * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } + * @async + * @static + */ + decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null, date}) { + if (passwords !== undefined + || sessionKeys + || date){ + + throw('NOT_IMPLEMENTED'); + } + if ( privateKeys + || publicKeys + || format !== 'utf8' + || signature + ){ + console.log('may be implemented later'); + throw('NOT_YET_IMPLEMENTED'); + } + return this.GpgME.decrypt(message); + // TODO: translate between: + // openpgp: + // { data:Uint8Array|String, + // filename:String, + // signatures:[{ keyid:String, valid:Boolean }] } + // and gnupg: + // data: The decrypted data. This may be base64 encoded. + // base64: Boolean indicating whether data is base64 encoded. + // mime: A Boolean indicating whether the data is a MIME object. + // info: An optional object with extra information. + } +} + +/** + * + * @param {Object | String} Key Either a (presumably openpgp Key) Object with a + * primaryKeyproperty and a method getFingerprint, or a string. + * @returns {String} Unchecked string value claiming to be a fingerprint + * TODO: gpgmejs checks again, so it's okay here. + */ +function translateKeyInput(Key){ + if (!Key){ + return []; + } + if (!Array.isArray(Key)){ + Key = [Key]; + } + let resultslist = []; + for (let i=0; i < Key.length; i++){ + if (typeof(Key[i]) === 'string'){ + resultslist.push(Key); + } else if ( + Key[i].hasOwnProperty(primaryKey) && + Key[i].primaryKey.hasOwnProperty(getFingerprint)){ + resultslist.push(Key[i].primaryKey.getFingerprint()); + } + } + return resultslist; +} \ No newline at end of file diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 02dc919d..f70bd2d8 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -1,14 +1,23 @@ -import * as gpgmejs from'./gpgmejs' -export default gpgmejs; - -/** - * Export each high level api function separately. - * Usage: +/* 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. * - * import { encryptMessage } from 'gpgme.js' - * encryptMessage(keys, text) + * 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+ */ -export { - encrypt, decrypt, sign, verify, - generateKey, reformatKey - } from './gpgmejs'; + +import { GpgME as gpgmejs } from "./gpgmejs"; +// import { GpgME_openPGPCompatibility as gpgmejs } from "./gpgmejs_openpgpjs"; +export default gpgmejs; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js new file mode 100644 index 00000000..3c11b8e0 --- /dev/null +++ b/lang/js/src/permittedOperations.js @@ -0,0 +1,75 @@ +/* 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+ + */ + + /** + * Definition of the possible interactions with gpgme-json. + * operation: + required: Array + optional: Array + answer: + type: The payload property of the answer. May be + partial and in need of concatenation + params: Array Information that do not change throughout + the message + infos: Array arbitrary information that may change + } + } + */ + +export const permittedOperations = { + encrypt: { + required: ['keys', 'data'], + optional: [ + 'protocol', + 'chunksize', + 'base64', + 'mime', + 'armor', + 'always-trust', + 'no-encrypt-to', + 'no-compress', + 'throw-keyids', + 'want-address', + 'wrap' + ], + answer: { + type: ['ciphertext'], + data: ['data'], + params: ['base64'], + infos: [] + } + }, + + decrypt: { + required: ['data'], + optional: [ + 'protocol', + 'chunksize', + 'base64' + ], + answer: { + type: ['plaintext'], + data: ['data'], + params: ['base64', 'mime'], + infos: ['info'] + } + } +} diff --git a/lang/js/test_index.js b/lang/js/test_index.js new file mode 100644 index 00000000..9119d271 --- /dev/null +++ b/lang/js/test_index.js @@ -0,0 +1,25 @@ +/* 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+ + * + */ +document.addEventListener('DOMContentLoaded', function() { + chrome.tabs.create({ + url: './ui2.html' + }); +}); diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js index d01aca99..97b35527 100644 --- a/lang/js/testapplication.js +++ b/lang/js/testapplication.js @@ -1,21 +1,57 @@ -/** -* Testing nativeMessaging. This is a temporary plugin using the gpgmejs - implemetation as contained in src/ -*/ -function buttonclicked(event){ - let data = document.getElementById("text0").value; - let keyId = document.getElementById("key").value; - let enc = Gpgmejs.encrypt(data, [keyId]).then(function(answer){ - console.log(answer); - console.log(answer.type); - console.log(answer.data); - alert(answer.data); - }, function(errormsg){ - alert('Error: '+ errormsg); +/* 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+ + * + */ + +function encryptbuttonclicked(event){ + let data = document.getElementById('cleartext').value; + let keyId = document.getElementById('pubkey').value; + let communication = new Gpgmejs; + let enc = communication.encrypt(data, keyId).then( + function(answer){ + console.log(answer); + if (answer.data){ + console.log(answer.data); + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); + }); +}; + +function decryptbuttonclicked(event){ + let data = document.getElementById("ciphertext").value; + let communication = new Gpgmejs; + let enc = communication.decrypt(data).then( + function(answer){ + console.log(answer); + if (answer.data){ + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); }); }; document.addEventListener('DOMContentLoaded', function() { - document.getElementById("button0").addEventListener("click", - buttonclicked); - }); + document.getElementById("buttonencrypt").addEventListener("click", + encryptbuttonclicked); + document.getElementById("buttondecrypt").addEventListener("click", + decryptbuttonclicked); +}); diff --git a/lang/js/testapplication_index.html b/lang/js/testapplication_index.html new file mode 100644 index 00000000..866b1135 --- /dev/null +++ b/lang/js/testapplication_index.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/lang/js/ui.html b/lang/js/ui.html deleted file mode 100644 index 9c56c2e5..00000000 --- a/lang/js/ui.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - -
    -
  • - Text: - -
  • -
  • - Public key ID: - -
  • -
-
-
- - diff --git a/lang/js/ui2.html b/lang/js/ui2.html new file mode 100644 index 00000000..8d0abd97 --- /dev/null +++ b/lang/js/ui2.html @@ -0,0 +1,33 @@ + + + + + + + + + +
    +
  • + Text: + +
  • +
  • + Public key ID: + +
  • +
+
+
+
    +
  • + Encrypted armored Text: + +
  • +
+
+
+

Result data:

+ + + diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js index 71b71161..7a5392ee 100644 --- a/lang/js/webpack.conf.js +++ b/lang/js/webpack.conf.js @@ -1,3 +1,24 @@ +/* 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+ + * + * This is the configuration file for building the gpgmejs-Library with webpack + */ const path = require('path'); module.exports = { @@ -8,6 +29,7 @@ module.exports = { path: path.resolve(__dirname, 'dist'), filename: 'gpgmejs.bundle.js', libraryTarget: 'var', + libraryExport: 'default', library: 'Gpgmejs' } }; -- cgit v1.2.3 From d62f66b1fb47f2075770d896f672748a4136e70b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 23 Apr 2018 17:18:46 +0200 Subject: js: Key handling stubs, Error handling, refactoring -- * Error handling: introduced GPGMEJS_Error class that handles errors at a more centralized and consistent position * src/Connection.js: The nativeMessaging port now opens per session instead of per message. Some methods were added that reflect this change - added methods disconnect() and reconnect() - added connection status query * src/gpgmejs.js - stub for key deletion - error handling - high level API for changing connection status * src/gpgmejs_openpgpjs.js - added stubs for Key/Keyring handling according to current state of discussion. It is still subject to change * src/Helpers.js - toKeyIdArray creates an array of KeyIds, now accepting fingerprints, GPGMEJS_Key objects and openpgp Key objects. * Key objects (src/Key.js) Querying information about a key directly from gnupg. Currently a stub, only the Key.fingerprint is functional. * Keyring queries (src/Keyring.js): Listing and searching keys. Currently a stub. --- lang/js/src/Connection.js | 86 ++++++++++------- lang/js/src/Errors.js | 148 ++++++++++++++++++++++++++++ lang/js/src/Helpers.js | 54 +++++++---- lang/js/src/Key.js | 201 +++++++++++++++++++++++++++++++++++++++ lang/js/src/Keyring.js | 151 +++++++++++++++++++++++++++++ lang/js/src/Message.js | 34 ++++--- lang/js/src/gpgmejs.js | 108 +++++++++++++++------ lang/js/src/gpgmejs_openpgpjs.js | 105 ++++++++++++++------ 8 files changed, 756 insertions(+), 131 deletions(-) create mode 100644 lang/js/src/Errors.js create mode 100644 lang/js/src/Key.js create mode 100644 lang/js/src/Keyring.js diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 784929e9..87ec8cf7 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -26,31 +26,19 @@ import { GPGME_Message } from "./Message"; * expected. */ import { permittedOperations } from './permittedOperations' +import { GPGMEJS_Error} from "./Errors" +/** + * A Connection handles the nativeMessaging interaction. + */ export class Connection{ - /** - * Opens and closes a port. Thus, it is made sure that the connection can - * be used. - * THIS BEHAVIOUR MAY CHANGE! - * discussion is to keep a port alive as long as the context stays the same - * - * TODO returns nothing, but triggers exceptions if not successfull - */ constructor(){ - this._connection = chrome.runtime.connectNative('gpgmejson'); - if (!this._connection){ - if (chrome.runtime.lastError){ - throw('NO_CONNECT_RLE'); - } else { - throw('NO_CONNECT'); - } - } - this._flags = {}; // TODO general config + this.connect(); } /** - * Immediately closes the open port + * Immediately closes the open port. */ disconnect() { if (this._connection){ @@ -58,27 +46,56 @@ export class Connection{ } } + /** + * Opens a nativeMessaging port. + * returns nothing, but triggers errors if not successfull: + * NO_CONNECT: connection not successfull, chrome.runtime.lastError may be + * available + * ALREADY_CONNECTED: There is already a connection present. + */ + connect(){ + if (this._connection){ + return new GPGMEJS_Error('ALREADY_CONNECTED'); + } + this._connection = chrome.runtime.connectNative('gpgmejson'); + if (!this._connection){ + return new GPGMEJS_Error('NO_CONNECT'); + } + } + + /** + * checks if the connection is established + * TODO: some kind of ping to see if the other side responds as expected? + * @returns {Boolean} + */ + get connected(){ + return this._connection ? true: false; + } + /** * Sends a message and resolves with the answer. * @param {GPGME_Message} message * @returns {Promise} the gnupg answer, or rejection with error - * information - * TODO: better/more consistent error information + * information. */ post(message){ if (!message || !message instanceof GPGME_Message){ - return Promise.reject('ERR_NO_MSG'); + return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); + } + if (message.isComplete !== true){ + return Promise.reject(new GPGMEJS_Error('MSG_INCOMPLETE')); } // let timeout = 5000; //TODO config let me = this; return new Promise(function(resolve, reject){ - let answer = new Answer(message.op); + let answer = new Answer(message.operation); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) - reject('EMPTY_ANSWER'); + reject(new GPGMEJS_Error('EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener) + //TODO: GPGMEJS_Error? reject(msg.msg); } else { answer.add(msg); @@ -92,12 +109,12 @@ export class Connection{ }; me._connection.onMessage.addListener(listener); - me._connection.postMessage(message); + me._connection.postMessage(message.message); //TBD: needs to be aware if there is a pinentry pending // setTimeout( // function(){ // me.disconnect(); - // reject('TIMEOUT'); + // reject(new GPGMEJS_Error('TIMEOUT', 5000)); // }, timeout); }); } @@ -105,8 +122,8 @@ export class Connection{ /** * 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 return keys + * the nativeMessaging communication. + * @param {String} operation The operation, to look up validity of returning messages */ class Answer{ @@ -115,9 +132,8 @@ class Answer{ } /** - * + * Add the information to the answer * @param {Object} msg The message as received with nativeMessaging - * TODO: "error" and "more" handling are not in here, but in post() */ add(msg){ if (this._response === undefined){ @@ -130,9 +146,7 @@ class Answer{ switch (key) { case 'type': if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ - console.log( 'unexpected answer type: ' + msg.type); - throw('UNEXPECTED_TYPE'); - + return new GPGMEJS_Error('UNEXPECTED_ANSWER'); } break; case 'more': @@ -151,7 +165,7 @@ class Answer{ this._response[key] = msg[key]; } else if (this._response[key] !== msg[key]){ - throw('UNEXPECTED_TYPE'); + return new GPGMEJS_Error('UNEXPECTED_ANSWER',msg[key]); } } //infos may be json objects etc. Not yet defined. @@ -163,8 +177,7 @@ class Answer{ this._response.push(msg[key]); } else { - console.log('unexpected answer parameter: ' + key); - throw('UNEXPECTED_PARAM'); + return new GPGMEJS_Error('UNEXPECTED_ANSWER', key); } break; } @@ -172,7 +185,8 @@ class Answer{ } /** - * Returns the assembled message. TODO: does not care yet if completed. + * @returns {Object} the assembled message. + * TODO: does not care yet if completed. */ get message(){ return this._response; diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js new file mode 100644 index 00000000..c2356f7c --- /dev/null +++ b/lang/js/src/Errors.js @@ -0,0 +1,148 @@ +/* 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+ + */ + +// This is a preliminary collection of erors and warnings to be thrown and implemented. + +// general idea: if throw , throw the NAME +// return false || 'return' property + +//TODO: Connection.NOCONNECT promise +//connection.timeout: Be aware of pinentry + +export class GPGMEJS_Error { + + constructor(code = 'GENERIC_ERROR', details){ + let config = { //TODO TEMP + debug: 'console', // |'alert' + throw: 'default' // | 'always' | 'never' + }; + let errors = { //TODO: someplace else + //Connection errors + 'ALREADY_CONNECTED':{ + msg: 'The connection was already established. The action would overwrite the context', + throw: true + }, + 'NO_CONNECT': { + msg:'Connection with the nativeMessaging host could not be established.', + throw: true + }, + 'EMPTY_GPG_ANSWER':{ + msg: 'The nativeMesaging answer was empty', + throw: true + }, + 'TIMEOUT': { + msg: 'A timeout was exceeded.', + throw: false + }, + + 'UNEXPECTED_ANSWER': { + msg: 'The answer from gnupg was not as expected', + throw: true + }, + + // Message/Data Errors + + 'NO_KEYS' : { + msg: 'There were no valid keys provided.', + throw: true + }, + 'NOT_A_FPR': { + msg: 'The String is not an accepted fingerprint', + throw: false + }, + 'MSG_INCOMPLETE': { + msg: 'The Message did not match the minimum requirements for the interaction', + throw: true + }, + 'EMPTY_MSG' : { + msg: 'The Message has no data.', + throw: true + }, + 'MSG_NODATA':{ + msg: 'The data sent is empty. This may be unintentional.', + throw: false + }, + 'MSG_OP_PENDING': { + msg: 'There is no operation specified yet. The parameter cannot be set', + throw: false + }, + 'WRONG_OP': { + msg: "The operation requested could not be found", + throw: true + }, + + //generic errors + + 'WRONGPARAM':{ + msg: 'invalid parameter was found', + throw: true + }, + 'WRONGTYPE':{ + msg: 'invalid parameter type was found', + throw: true + }, + 'NOT_IMPLEMENTED': { + msg: 'A openpgpjs parameter was submitted that is not implemented', + throw: true + }, + 'GENERIC_ERROR': { + msg: 'Unspecified error', + throw: true + }, + + // hopefully temporary errors + + 'NOT_YET_IMPLEMENTED': { + msg: 'Support of this is probable, but it is not implemented yet', + throw: false + } + } + if (!errors.hasOwnProperty(code)){ + throw('GENERIC_ERROR'); + } + let msg = code; + if (errors[code].msg !== undefined){ + msg = msg + ': ' + errors[code].msg; + } + if (details){ + msg = msg + ' ' + details; + } + if (config.debug === 'console'){ + console.log(msg); + } else if (config.debug === 'alert'){ + alert(msg); + } + switch (config.throw) { + case 'default': + if (errors[code].throw === true){ + throw(code); + } + break; + case 'always': + throw(code); + break; + + case 'never': + break; + default: + throw('GENERIC_ERROR'); + } + } +} diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index eeb7a3c4..922ca06c 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -1,3 +1,5 @@ +import { GPGMEJS_Error } from "./Errors"; + /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -21,33 +23,46 @@ /** * Tries to return an array of fingerprints, either from input fingerprints or * from Key objects - * @param {String|Array} input Input value. + * @param {Key |Array| GPGME_Key | Array|String|Array} input + * @param {Boolean} nocheck if set, an empty result is acceptable * @returns {Array} Array of fingerprints. */ -export function toKeyIdArray(input){ + +export function toKeyIdArray(input, nocheck){ if (!input){ - return []; - // TODO: Warning or error here? Did we expect something or is "nothing" okay? + return (nocheck ===true)? [] : new GPGMEJS_Error('NO_KEYS'); + } + if (!Array.isArray(input)){ + input = [input]; } - if (input instanceof Array){ - let result = []; - for (let i=0; i < input.length; i++){ + 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 { - //TODO error? - console.log('gpgmejs/Helpers.js Warning: '+ - input[i] + - ' is not a valid key fingerprint and will not be used'); + GPGMEJS_Error + } + } 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 { + return new GPGMEJS_Error('WRONGTYPE'); } + } + if (result.length === 0){ + return (nocheck===true)? [] : new GPGMEJS_Error('NO_KEYS'); + } else { return result; - } else if (isFingerprint(input) === true) { - return [input]; } - console.log('gpgmejs/Helpers.js Warning: ' + input + - ' is not a valid key fingerprint and will not be used'); - return []; }; /** @@ -72,13 +87,14 @@ function hextest(key, len){ export function isFingerprint(string){ return hextest(string, 40); }; - -//TODO needed anywhere? +/** + * check if the input is a valid Hex string with a length of 16 + */ function isLongId(string){ return hextest(string, 16); }; -//TODO needed anywhere? +// 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 new file mode 100644 index 00000000..d8f16c55 --- /dev/null +++ b/lang/js/src/Key.js @@ -0,0 +1,201 @@ +/* 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} from './Helpers' +import {GPGMEJS_Error} from './Errors' + +export class GPGME_Key { + + constructor(fingerprint){ + if (isFingerprint(fingerprint) === true){ + this._fingerprint = fingerprint; + } else { + return new GPGMEJS_Error('WRONGPARAM', 'Key.js: invalid fingerprint'); + } + } + + get fingerprint(){ + return this._fingerprint; + } + + /** + * hasSecret returns true if a secret subkey is included in this Key + */ + get hasSecret(){ + checkKey(this._fingerprint, 'secret').then( function(result){ + return Promise.resolve(result); + }); + + } + + get isRevoked(){ + return checkKey(this._fingerprint, 'revoked'); + } + + get isExpired(){ + return checkKey(this._fingerprint, 'expired'); + } + + get isDisabled(){ + return checkKey(this._fingerprint, 'disabled'); + } + + get isInvalid(){ + return checkKey(this._fingerprint, 'invalid'); + } + + get canEncrypt(){ + return checkKey(this._fingerprint, 'can_encrypt'); + } + + get canSign(){ + return checkKey(this._fingerprint, 'can_sign'); + } + + get canCertify(){ + return checkKey(this._fingerprint, 'can_certify'); + } + + get canAuthenticate(){ + return checkKey(this._fingerprint, 'can_authenticate'); + } + + get isQualified(){ + return checkKey(this._fingerprint, 'is_qualified'); + } + + get armored(){ + let me = this; + return new Promise(function(resolve, reject){ + let conn = new Connection(); + conn.setFlag('armor', true); + conn.post('export',{'fpr': me._fingerprint}); + }); + // TODO return value not yet checked. Should result in an armored block + // in correct encoding + // TODO openpgpjs also returns secKey if private = true? + } + + /** + * TODO returns true if this is the default key used to sign + */ + get isDefault(){ + throw('NOT_YET_IMPLEMENTED'); + } + + /** + * get the Key's subkeys as GPGME_Key objects + * @returns {Array} + */ + get subkeys(){ + return checkKey(this._fingerprint, 'subkeys').then(function(result){ + // TBD expecting a list of fingerprints + if (!Array.isArray(result)){ + result = [result]; + } + let resultset = []; + for (let i=0; i < result.length; i++){ + let subkey = new GPGME_Key(result[i]); + if (subkey instanceof GPGME_Key){ + resultset.push(subkey); + } + } + return Promise.resolve(resultset); + }); + } + + /** + * creation time stamp of the key + * @returns {Date|null} TBD + */ + get timestamp(){ + return checkKey(this._fingerprint, 'timestamp'); + //TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available. + } + + /** + * The expiration timestamp of this key TBD + * @returns {Date|null} TBD + */ + get expires(){ + return checkKey(this._fingerprint, 'expires'); + // TODO convert to Date; check for 0 + } + + /** + * getter name TBD + * @returns {String|Array} The user ids associated with this key + */ + get userIds(){ + return checkKey(this._fingerprint, 'uids'); + } + + /** + * @returns {String} The public key algorithm supported by this subkey + */ + get pubkey_algo(){ + return checkKey(this._fingerprint, 'pubkey_algo'); + } +}; + +/** + * generic function to query gnupg information on a key. + * @param {*} fingerprint The identifier of the Keyring + * @param {*} property The gpgme-json property to check + * + */ +function checkKey(fingerprint, property){ + return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + + return new Promise(function(resolve, reject){ + if (!isFingerprint(fingerprint)){ + reject('not a fingerprint'); //TBD + } + let conn = new Connection(); + conn.post('getkey',{ // TODO not yet implemented in gpgme + 'fingerprint': this.fingerprint}) + .then(function(result){ + if (property !== undefined){ + if (result.hasOwnProperty(key)){ + resolve(result[property]); + } + else if (property == 'secret'){ + // property undefined means "not true" in case of secret + resolve(false); + } else { + reject('ERR_INVALID_PROPERTY') //TBD + } + } + + + resolve(result); + }, function(error){ + reject(error); + }); + }); +}; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js new file mode 100644 index 00000000..52fa7f71 --- /dev/null +++ b/lang/js/src/Keyring.js @@ -0,0 +1,151 @@ +/* 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_Message} from './Message' +import {Connection} from './Connection' +import {GPGME_Key} from './Key' +import { isFingerprint, isLongId } from './Helpers'; + +export class GPGME_Keyring { + constructor(){ + this.reconnect(); + } + + /** + * (Re)-establishes the connection + * TODO TEMP: should we better use the connection of our parent, + * which we do not control? + */ + reconnect(){ + if (!this._connection || ! this._connection instanceof Connection){ + this._connection = new Connection; + } else { + this._connection.disconnect(); + this._connection.connect(); + } + } + + /** + * @param {String} (optional) pattern A pattern to search for, in userIds or KeyIds + * @param {Boolean} (optional) Include listing of secret keys + * @returns {Promise.>} + * + */ + getKeys(pattern, include_secret){ + let msg = new GPGME_Message; + msg.operation = 'listkeys'; + if (pattern && typeof(pattern) === 'string'){ + msg.setParameter('pattern', pattern); + } + if (include_secret){ + msg.setParameter('with-secret', true); + } + + this._connection.post(msg).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); + } + } + return Promise.resolve(resultset); + }); + } + + /** + * @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.defaultKey Only Keys marked as Default Keys + * @param {Boolean} flags.secret Only Keys containing a secret part. + * @param {Boolean} flags.valid Valid Keys only + * @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); + }); + } + +}; diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 90b554a1..6a93b6f4 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -18,21 +18,24 @@ * SPDX-License-Identifier: LGPL-2.1+ */ import { permittedOperations } from './permittedOperations' - +import { GPGMEJS_Error } from './Errors' export class GPGME_Message { //TODO getter - constructor(){ + constructor(operation){ + if (operation){ + this.operation(operation); + } } /** * Defines the operation this message will have - * @param {String} operation Mus be defined in permittedOperations + * @param {String} operation Must be defined in permittedOperations * TODO: move to constructor? */ set operation (operation){ if (!operation || typeof(operation) !== 'string'){ - throw('ERR_WRONG_PARAM'); + return new GPGMEJS_Error('WRONGPARAM'); } if (operation in permittedOperations){ if (!this._msg){ @@ -40,10 +43,14 @@ export class GPGME_Message { } this._msg.op = operation; } else { - throw('ERR_NOT_IMPLEMENTED'); + return new GPGMEJS_Error('WRONG_OP'); } } + get operation(){ + return this._msg.op; + } + /** * Sets a parameter for the message. Note that the operation has to be set * first, to be able to check if the parameter is permittted @@ -53,25 +60,20 @@ export class GPGME_Message { */ setParameter(param,value){ if (!param || typeof(param) !== 'string'){ - throw('ERR_WRONG_PARAM'); + return new GPGMEJS_Error('WRONGPARAM', 'type check failed'); } if (!this._msg || !this._msg.op){ - console.log('There is no operation specified yet. '+ - 'The parameter cannot be set'); - return false; + return new GPGMEJS_Error('MSG_OP_PENDING'); } let po = permittedOperations[this._msg.op]; if (!po){ - throw('LAZY_PROGRAMMER'); - //TODO - return false; + return new GPGMEJS_Error('WRONG_OP', param); } if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ this._msg[param] = value; return true; } - console.log('' + param + ' is invalid and could not be set'); - return false; + return new GPGMEJS_Error('WRONGPARAM', param); } /** @@ -85,7 +87,9 @@ export class GPGME_Message { } let reqParams = permittedOperations[this._msg.op].required; for (let i=0; i < reqParams.length; i++){ - if (!reqParams[i] in this._msg){ + + if (!this._msg.hasOwnProperty(reqParams[i])){ + console.log(reqParams[i] + 'missing'); return false; } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 8323ac3b..c23a356b 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -21,26 +21,54 @@ import {Connection} from "./Connection" import {GPGME_Message} from './Message' import {toKeyIdArray} from "./Helpers" +import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors" export class GpgME { /** - * initial check if connection si successfull. Will throw ERR_NO_CONNECT or - * ERR_NO_CONNECT_RLE (if chrome.runtime.lastError is available) if the - * connection fails. - * TODO The connection to the nativeMessaging host will, for now, be closed - * after each interaction. Session management with gpg_agent is TBD. + * initializes GpgME by opening a nativeMessaging port * TODO: add configuration */ - constructor(){ - let conn = new Connection(); - // this.keyring = new Keyring(); TBD - // TODO config, e.g. - this.configuration = { - null_expire_is_never: true - }; - conn.disconnect(); + constructor(configuration = { + null_expire_is_never: false + }){ + this._connection = new Connection; + } + + /** + * refreshes the nativeApp connection + */ + reconnect(){ + if (!this._connection || ! this._connection instanceof Connection){ + this._connection = new Connection; + } else { + this._connection.disconnect(); + this._connection.connect(); + } + } + + /** + * inmediately tries to destroy the nativeMessaging connection. + * TODO: may not be included in final API, as it is redundant. + * For now, it just serves paranoia + */ + disconnect(){ + if (this._connection){ + this._connection.disconnect(); + this._connection = null; + } + } + + /** + * tests the nativeApp connection + */ + get connected(){ + if (!this._connection || ! this._connection instanceof Connection){ + return false; + } + return this._connection.connected; } + /** * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message @@ -62,14 +90,7 @@ export class GpgME { if (wildcard === true){msg.setParameter('throw-keyids', true); }; - if (msg.isComplete === true) { - let conn = new Connection(); - return (conn.post(msg.message)); - } - else { - return Promise.reject('NO_CONNECT'); - //TODO - } + return (this._connection.post(msg)); } /** @@ -85,22 +106,47 @@ export class GpgME { decrypt(data){ if (data === undefined){ - throw('ERR_EMPTY_MSG'); + return Promise.reject(new GPGMEJS_Error ('EMPTY_MSG')); } let msg = new GPGME_Message; msg.operation = 'decrypt'; putData(msg, data); - // TODO: needs proper EOL to be decrypted. + return this._connection.post(msg); + + } - if (msg.isComplete === true){ - let conn = new Connection(); - return conn.post(msg.message); + deleteKey(key, delete_secret = false, no_confirm = false){ + return Promise.reject(new GPGMEJS_Error ('NOT_YET_IMPLEMENTED')); + let msg = new GPGME_Message; + msg.operation = 'deletekey'; + let key_arr = toKeyIdArray(key); + if (key_arr.length !== 1){ + throw('TODO'); + //should always be ONE key + } + msg.setParameter('key', key_arr[0]); + if (delete_secret === true){ + msg.setParameter('allow_secret', true); //TBD } - else { - return Promise.reject('NO_CONNECT'); - //TODO + if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? + msg.setParameter('delete_force', true); //TBD } + this._connection.post(msg).then(function(success){ + //TODO: it seems that there is always errors coming back: + }, function(error){ + switch (error.msg){ + case 'ERR_NO_ERROR': + return Promise.resolve('okay'); //TBD + default: + return Promise.reject(new GPGMEJS_Error); + // INV_VALUE, + // GPG_ERR_NO_PUBKEY, + // GPG_ERR_AMBIGUOUS_NAME, + // GPG_ERR_CONFLICT + } + }); } + } /** @@ -112,7 +158,7 @@ export class GpgME { */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { - throw('NO_MESSAGE_OBJECT'); + return new GPGMEJS_Error('WRONGPARAM'); } if (!data){ //TODO Debug only! No data is legitimate @@ -126,6 +172,6 @@ function putData(message, data){ message.setParameter('base64', false); message.setParameter('data', data); } else { - throw('ERR_WRONG_TYPE'); + return new GPGMEJS_Error('WRONGPARAM'); } } \ No newline at end of file diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index 1eec4da4..54b9dd45 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -25,13 +25,18 @@ */ import { GpgME } from "./gpgmejs"; -// import {Keyring} from "./Keyring" TODO - + import {GPGME_Keyring} from "./Keyring" + import { GPGME_Key } from "./Key"; + import { isFingerprint } from "./Helpers" + import { GPGMEJS_Error } from './Errors' export class GpgME_openPGPCompatibility { constructor(){ - this._gpgme = new GpgME; + this._gpgme = new GpgME({ + null_expire_is_never: false + }); + this.Keyring = this.initKeyring(); } /** @@ -67,15 +72,14 @@ export class GpgME_openPGPCompatibility { || signature !== null || returnSessionKey !== null || date !== null){ - throw('NOT_IMPLEMENTED'); + return Promise.reject(new GPMGEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || filename || compression || armor === false || detached == true){ - console.log('may be implemented later'); - throw('NOT_YET_IMPLEMENTED'); + return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); } return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); } @@ -103,16 +107,14 @@ export class GpgME_openPGPCompatibility { if (passwords !== undefined || sessionKeys || date){ - - throw('NOT_IMPLEMENTED'); + return Promise.reject(new GPGMEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || publicKeys || format !== 'utf8' || signature ){ - console.log('may be implemented later'); - throw('NOT_YET_IMPLEMENTED'); + return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); } return this.GpgME.decrypt(message); // TODO: translate between: @@ -126,31 +128,74 @@ export class GpgME_openPGPCompatibility { // mime: A Boolean indicating whether the data is a MIME object. // info: An optional object with extra information. } + initKeyring(){ + return new GPGME_Keyring_openPGPCompatibility; + } } /** - * - * @param {Object | String} Key Either a (presumably openpgp Key) Object with a - * primaryKeyproperty and a method getFingerprint, or a string. - * @returns {String} Unchecked string value claiming to be a fingerprint - * TODO: gpgmejs checks again, so it's okay here. + * Translation layer offering basic Keyring API to be used in Mailvelope. + * It may still be changed/expanded/merged with GPGME_Keyring */ -function translateKeyInput(Key){ - if (!Key){ - return []; +class GPGME_Keyring_openPGPCompatibility { + constructor(){ + this._gpgme_keyring = new GPGME_Keyring; } - if (!Array.isArray(Key)){ - Key = [Key]; + + /** + * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This + * includes keys openpgpjs considers 'private' (usable for signing), with + * the difference that Key.armored will NOT contain any secret information. + * Please also note that a GPGME_Key does not offer full openpgpjs- Key + * compatibility. + * @returns {Array} with the objects offering at least: + * @property {String} armored The armored key block (does not include secret blocks) + * @property {Boolean} hasSecret Indicator if a private/secret key exists + * @property {Boolean} isDefault Indicator if private key exists and is the default key in this keyring + * @property {String} fingerprint The fingerprint identifying this key + * //TODO: Check if IsDefault is also always hasSecret + */ + getPublicKeys(){ + return this._gpgme_keyring.getKeys(null, true); } - let resultslist = []; - for (let i=0; i < Key.length; i++){ - if (typeof(Key[i]) === 'string'){ - resultslist.push(Key); - } else if ( - Key[i].hasOwnProperty(primaryKey) && - Key[i].primaryKey.hasOwnProperty(getFingerprint)){ - resultslist.push(Key[i].primaryKey.getFingerprint()); + + /** + * Returns the Default Key used for crypto operation in gnupg. + * Please note that the armored property does not contained secret key blocks, + * despite secret blocks being part of the key itself. + * @returns {Promise } + */ + getDefaultKey(){ + this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){ + if (result.length === 1){ + return Promise.resolve(result[0]); + } + else { + // TODO: Can there be "no default key"? + // TODO: Can there be several default keys? + return new GPGMEJS_Error; //TODO + } + }); + } + + /** + * Deletes a Key + * @param {Object} Object identifying key + * @param {String} key.fingerprint - fingerprint of the to be deleted key + * @param {Boolean} key.secret - indicator if private key should be deleted as well + + * @returns {Promise., Error>} TBD: Not sure what is wanted + TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint + TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists + */ + deleteKey(key){ + if (typeof(key) !== "object"){ + return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); + } + if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ + return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); } + let key_to_delete = new GPGME_Key(key.fingerprint); + return key_to_delete.deleteKey(key.secret); } - return resultslist; -} \ No newline at end of file +} -- cgit v1.2.3 From 727340b295f25e04cb595022ba143cda48364697 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 23 Apr 2018 19:15:40 +0200 Subject: js: don't allow message operation changes -- Once an operation is changed, their set of allowed/required parameters will change. So we shouldn't set/change the operation later. --- lang/js/src/Keyring.js | 3 +-- lang/js/src/Message.js | 42 ++++++++++++++++++++---------------------- lang/js/src/gpgmejs.js | 9 +++------ 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 52fa7f71..d8cb84b2 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -49,8 +49,7 @@ export class GPGME_Keyring { * */ getKeys(pattern, include_secret){ - let msg = new GPGME_Message; - msg.operation = 'listkeys'; + let msg = new GPGME_Message('listkeys'); if (pattern && typeof(pattern) === 'string'){ msg.setParameter('pattern', pattern); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 6a93b6f4..f5e21e00 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -23,28 +23,7 @@ export class GPGME_Message { //TODO getter constructor(operation){ - if (operation){ - this.operation(operation); - } - } - - /** - * Defines the operation this message will have - * @param {String} operation Must be defined in permittedOperations - * TODO: move to constructor? - */ - set operation (operation){ - if (!operation || typeof(operation) !== 'string'){ - return new GPGMEJS_Error('WRONGPARAM'); - } - if (operation in permittedOperations){ - if (!this._msg){ - this._msg = {}; - } - this._msg.op = operation; - } else { - return new GPGMEJS_Error('WRONG_OP'); - } + setOperation(this, operation); } get operation(){ @@ -110,4 +89,23 @@ export class GPGME_Message { } } +} + +/** + * Defines the operation this message will have + * @param {String} operation Must be defined in permittedOperations + * TODO: move to constructor? + */ +function setOperation (scope, operation){ + if (!operation || typeof(operation) !== 'string'){ + return new GPGMEJS_Error('WRONGTYPE'); + } + if (permittedOperations.hasOwnProperty(operation)){ + if (!scope._msg){ + scope._msg = {}; + } + scope._msg.op = operation; + } else { + return new GPGMEJS_Error('WRONG_OP'); + } } \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index c23a356b..b15477f0 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -76,8 +76,7 @@ export class GpgME { */ encrypt (data, publicKeys, wildcard=false){ - let msg = new GPGME_Message; - msg.operation = 'encrypt'; + let msg = new GPGME_Message('encrypt'); // TODO temporary msg.setParameter('armor', true); @@ -108,8 +107,7 @@ export class GpgME { if (data === undefined){ return Promise.reject(new GPGMEJS_Error ('EMPTY_MSG')); } - let msg = new GPGME_Message; - msg.operation = 'decrypt'; + let msg = new GPGME_Message('decrypt'); putData(msg, data); return this._connection.post(msg); @@ -117,8 +115,7 @@ export class GpgME { deleteKey(key, delete_secret = false, no_confirm = false){ return Promise.reject(new GPGMEJS_Error ('NOT_YET_IMPLEMENTED')); - let msg = new GPGME_Message; - msg.operation = 'deletekey'; + let msg = new GPGME_Message('deletekey'); let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ throw('TODO'); -- cgit v1.2.3 From 461dd0c8b41683a91073b362d100ee5217ec53f6 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Apr 2018 18:44:30 +0200 Subject: js: change in initialization ancd connection handling -- * The Connection will now be started before an object is created, to better account for failures. * index.js: now exposes an init(), which returns a Promise of configurable with an established connection. * TODO: There is currently no way to recover from a "connection lost" * Connection.js offers Connection.isConnected, which toggles on port closing. --- lang/js/src/Connection.js | 41 ++++++++++++++----------- lang/js/src/Keyring.js | 30 +++++++++--------- lang/js/src/gpgmejs.js | 61 ++++++++++++++++--------------------- lang/js/src/gpgmejs_openpgpjs.js | 33 ++++++++++++++------ lang/js/src/index.js | 40 ++++++++++++++++++++++-- lang/js/testapplication.js | 66 +++++++++++++++++++--------------------- 6 files changed, 157 insertions(+), 114 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 87ec8cf7..e6ff67be 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -35,6 +35,18 @@ export class Connection{ constructor(){ this.connect(); + let me = this; + } + + /** + * (Simple) Connection check. + * @returns {Boolean} true if the onDisconnect event has not been fired. + * Please note that the event listener of the port takes some time + * (5 ms seems enough) to react after the port is created. Then this will + * return undefined + */ + get isConnected(){ + return this._isConnected; } /** @@ -48,28 +60,20 @@ export class Connection{ /** * Opens a nativeMessaging port. - * returns nothing, but triggers errors if not successfull: - * NO_CONNECT: connection not successfull, chrome.runtime.lastError may be - * available - * ALREADY_CONNECTED: There is already a connection present. + * TODO: Error handling ALREADY_CONNECTED */ connect(){ - if (this._connection){ + if (this._isConnected === true){ return new GPGMEJS_Error('ALREADY_CONNECTED'); } + this._isConnected = true; this._connection = chrome.runtime.connectNative('gpgmejson'); - if (!this._connection){ - return new GPGMEJS_Error('NO_CONNECT'); - } - } - - /** - * checks if the connection is established - * TODO: some kind of ping to see if the other side responds as expected? - * @returns {Boolean} - */ - get connected(){ - return this._connection ? true: false; + let me = this; + this._connection.onDisconnect.addListener( + function(){ + me._isConnected = false; + } + ); } /** @@ -79,6 +83,9 @@ export class Connection{ * information. */ post(message){ + if (!this.isConnected){ + return Promise.reject(new GPGMEJS_Error('NO_CONNECT')); + } if (!message || !message instanceof GPGME_Message){ return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d8cb84b2..ef8028ff 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -19,27 +19,27 @@ */ import {GPGME_Message} from './Message' -import {Connection} from './Connection' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; export class GPGME_Keyring { - constructor(){ - this.reconnect(); + constructor(connection){ + this.connection = connection; } - /** - * (Re)-establishes the connection - * TODO TEMP: should we better use the connection of our parent, - * which we do not control? - */ - reconnect(){ - if (!this._connection || ! this._connection instanceof Connection){ - this._connection = new Connection; - } else { - this._connection.disconnect(); - this._connection.connect(); + set connection(connection){ + if (!this._connection && connection instanceof Connection){ + this._connection = connection; + } + } + get connection(){ + if (this._connection instanceof Connection){ + if (this._connection.isConnected){ + return this._connection; + } + return undefined; //TODO: connection was lost! } + return undefined; //TODO: no connection there } /** @@ -57,7 +57,7 @@ export class GPGME_Keyring { msg.setParameter('with-secret', true); } - this._connection.post(msg).then(function(result){ + this.connection.post(msg).then(function(result){ let fpr_list = []; let resultset = []; if (!Array.isArray(result.keys)){ diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index b15477f0..4b2a03a4 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -22,59 +22,51 @@ import {Connection} from "./Connection" import {GPGME_Message} from './Message' import {toKeyIdArray} from "./Helpers" import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors" +import { GPGME_Keyring } from "./Keyring"; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port * TODO: add configuration */ - constructor(configuration = { - null_expire_is_never: false - }){ - this._connection = new Connection; + constructor(connection){ + this.connection = connection; } - /** - * refreshes the nativeApp connection - */ - reconnect(){ - if (!this._connection || ! this._connection instanceof Connection){ - this._connection = new Connection; - } else { - this._connection.disconnect(); - this._connection.connect(); + set connection(connection){ + if (this._connection instanceof Connection){ + //TODO Warning: Connection already established + } + if (connection instanceof Connection){ + this._connection = connection; } } - /** - * inmediately tries to destroy the nativeMessaging connection. - * TODO: may not be included in final API, as it is redundant. - * For now, it just serves paranoia - */ - disconnect(){ - if (this._connection){ - this._connection.disconnect(); - this._connection = null; + get connection(){ + if (this._connection instanceof Connection){ + if (this._connection.isConnected){ + return this._connection; + } + return undefined; //TODO: connection was lost! } + return undefined; //TODO: no connection there } - /** - * tests the nativeApp connection - */ - get connected(){ - if (!this._connection || ! this._connection instanceof Connection){ - return false; + set Keyring(keyring){ + if (ring && ring instanceof GPGME_Keyring){ + this.Keyring = ring; } - return this._connection.connected; } + get Keyring(){ + } /** * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message */ - encrypt (data, publicKeys, wildcard=false){ + encrypt(data, publicKeys, wildcard=false){ let msg = new GPGME_Message('encrypt'); @@ -89,7 +81,7 @@ export class GpgME { if (wildcard === true){msg.setParameter('throw-keyids', true); }; - return (this._connection.post(msg)); + return (this.connection.post(msg)); } /** @@ -109,7 +101,7 @@ export class GpgME { } let msg = new GPGME_Message('decrypt'); putData(msg, data); - return this._connection.post(msg); + return this.connection.post(msg); } @@ -128,7 +120,7 @@ export class GpgME { if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? msg.setParameter('delete_force', true); //TBD } - this._connection.post(msg).then(function(success){ + this.connection.post(msg).then(function(success){ //TODO: it seems that there is always errors coming back: }, function(error){ switch (error.msg){ @@ -143,7 +135,6 @@ export class GpgME { } }); } - } /** @@ -171,4 +162,4 @@ function putData(message, data){ } else { return new GPGMEJS_Error('WRONGPARAM'); } -} \ No newline at end of file +} diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index 54b9dd45..f1ddb5d6 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -30,13 +30,29 @@ import { isFingerprint } from "./Helpers" import { GPGMEJS_Error } from './Errors' + export class GpgME_openPGPCompatibility { - constructor(){ - this._gpgme = new GpgME({ - null_expire_is_never: false - }); - this.Keyring = this.initKeyring(); + constructor(connection){ + this.initGpgME(connection); + } + + get Keyring(){ + if (this._keyring){ + return this._keyring; + } + return undefined; + } + + initGpgME(connection){ + this._GpgME = new GpgME(connection); + this._Keyring = new GPGME_Keyring_openPGPCompatibility(connection); + } + + get GpgME(){ + if (this._GpGME){ + return this._GpGME; + } } /** @@ -128,9 +144,6 @@ export class GpgME_openPGPCompatibility { // mime: A Boolean indicating whether the data is a MIME object. // info: An optional object with extra information. } - initKeyring(){ - return new GPGME_Keyring_openPGPCompatibility; - } } /** @@ -138,8 +151,8 @@ export class GpgME_openPGPCompatibility { * It may still be changed/expanded/merged with GPGME_Keyring */ class GPGME_Keyring_openPGPCompatibility { - constructor(){ - this._gpgme_keyring = new GPGME_Keyring; + constructor(connection){ + this._gpgme_keyring = new GPGME_Keyring(connection); } /** diff --git a/lang/js/src/index.js b/lang/js/src/index.js index f70bd2d8..0e2beda4 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -18,6 +18,40 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import { GpgME as gpgmejs } from "./gpgmejs"; -// import { GpgME_openPGPCompatibility as gpgmejs } from "./gpgmejs_openpgpjs"; -export default gpgmejs; +import { GpgME } from "./gpgmejs"; +import { GpgME_openPGPCompatibility } from "./gpgmejs_openpgpjs"; +import { Connection } from "./Connection"; + +/** + * Initializes a nativeMessaging Connection and returns a GPGMEjs object + * @param {*} conf Configuration. TBD + */ +function init( config = { + api_style: 'gpgme', // | gpgme_openpgpjs + null_expire_is_never: true // Boolean + }){ + return new Promise(function(resolve, reject){ + let connection = new Connection; + // TODO: Delayed reaction is ugly. We need to listen to the port's + // event listener in isConnected, but this takes some time (<5ms) to + // disconnect if there is no successfull connection. + let delayedreaction = function(){ + if (connection.isConnected === true){ + let gpgme = null; + if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ + resolve( + new GpgME_openPGPCompatibility(connection)); + } else { + resolve(new GpgME(connection)); + } + } else { + reject('NO_CONNECT'); + } + }; + setTimeout(delayedreaction, 5); + }); +}; + +export default { + init: init +} \ No newline at end of file diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js index 97b35527..f47299e8 100644 --- a/lang/js/testapplication.js +++ b/lang/js/testapplication.js @@ -19,39 +19,37 @@ * */ -function encryptbuttonclicked(event){ - let data = document.getElementById('cleartext').value; - let keyId = document.getElementById('pubkey').value; - let communication = new Gpgmejs; - let enc = communication.encrypt(data, keyId).then( - function(answer){ - console.log(answer); - if (answer.data){ - console.log(answer.data); - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert('Error: '+ errormsg); - }); -}; - -function decryptbuttonclicked(event){ - let data = document.getElementById("ciphertext").value; - let communication = new Gpgmejs; - let enc = communication.decrypt(data).then( - function(answer){ - console.log(answer); - if (answer.data){ - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert('Error: '+ errormsg); - }); -}; - document.addEventListener('DOMContentLoaded', function() { - document.getElementById("buttonencrypt").addEventListener("click", - encryptbuttonclicked); - document.getElementById("buttondecrypt").addEventListener("click", - decryptbuttonclicked); + Gpgmejs.init().then(function(gpgmejs){ + document.getElementById("buttonencrypt").addEventListener("click", + function(){ + let data = document.getElementById('cleartext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.encrypt(data, keyId).then( + function(answer){ + console.log(answer); + if (answer.data){ + console.log(answer.data); + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); + }); + }); + + document.getElementById("buttondecrypt").addEventListener("click", + function(){ + let data = document.getElementById("ciphertext").value; + gpgmejs.decrypt(data).then( + function(answer){ + console.log(answer); + if (answer.data){ + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert('Error: '+ errormsg); + }); + }); + }, + function(error){console.log(error)}); }); -- cgit v1.2.3 From e2aa8066a9b3ce694169ad9fcc26cae486a804af Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Apr 2018 19:29:32 +0200 Subject: js: Key object adjustments after discussion -- * src/aKey.js changed fingerprint to setter (to avoid overwrites) * src/gpgmejs_openpgpjs.js - Added a class GPGME_Key_openpgpmode, which allows for renaming and deviation from GPGME. - renamed classes *_openPGPCompatibility to *_openpgpmode. They are not fully compatible, but only offer a subset of properties. Also, the name seems less clunky --- lang/js/src/Key.js | 8 +++-- lang/js/src/gpgmejs_openpgpjs.js | 67 ++++++++++++++++++++++++++++++++++------ lang/js/src/index.js | 4 +-- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index d8f16c55..f59b9901 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -32,10 +32,12 @@ import {GPGMEJS_Error} from './Errors' export class GPGME_Key { constructor(fingerprint){ - if (isFingerprint(fingerprint) === true){ + this.fingerprint = fingerprint; + } + + set fingerprint(fpr){ + if (isFingerprint(fpr) === true && !this._fingerprint){ this._fingerprint = fingerprint; - } else { - return new GPGMEJS_Error('WRONGPARAM', 'Key.js: invalid fingerprint'); } } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index f1ddb5d6..23076569 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -31,7 +31,7 @@ import { GPGMEJS_Error } from './Errors' -export class GpgME_openPGPCompatibility { + export class GpgME_openpgpmode { constructor(connection){ this.initGpgME(connection); @@ -46,7 +46,7 @@ export class GpgME_openPGPCompatibility { initGpgME(connection){ this._GpgME = new GpgME(connection); - this._Keyring = new GPGME_Keyring_openPGPCompatibility(connection); + this._Keyring = new GPGME_Keyring_openpgpmode(connection); } get GpgME(){ @@ -150,7 +150,7 @@ export class GpgME_openPGPCompatibility { * Translation layer offering basic Keyring API to be used in Mailvelope. * It may still be changed/expanded/merged with GPGME_Keyring */ -class GPGME_Keyring_openPGPCompatibility { +class GPGME_Keyring_openpgpmode { constructor(connection){ this._gpgme_keyring = new GPGME_Keyring(connection); } @@ -161,15 +161,13 @@ class GPGME_Keyring_openPGPCompatibility { * the difference that Key.armored will NOT contain any secret information. * Please also note that a GPGME_Key does not offer full openpgpjs- Key * compatibility. - * @returns {Array} with the objects offering at least: - * @property {String} armored The armored key block (does not include secret blocks) - * @property {Boolean} hasSecret Indicator if a private/secret key exists - * @property {Boolean} isDefault Indicator if private key exists and is the default key in this keyring - * @property {String} fingerprint The fingerprint identifying this key + * @returns {Array} * //TODO: Check if IsDefault is also always hasSecret + * TODO Check if async is required */ getPublicKeys(){ - return this._gpgme_keyring.getKeys(null, true); + return translateKeys( + this._gpgme_keyring.getKeys(null, true)); } /** @@ -181,7 +179,8 @@ class GPGME_Keyring_openPGPCompatibility { getDefaultKey(){ this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){ if (result.length === 1){ - return Promise.resolve(result[0]); + return Promise.resolve( + translateKeys(result)[0]); } else { // TODO: Can there be "no default key"? @@ -212,3 +211,51 @@ class GPGME_Keyring_openPGPCompatibility { return key_to_delete.deleteKey(key.secret); } } + +/** + * TODO error handling. + * Offers the Key information as the openpgpmode wants + */ +class GPGME_Key_openpgpmode { + constructor(value){ + this.init = value; + } + + set init (value){ + if (!this._GPGME_Key && value instanceof GPGME_Key){ + this._GPGME_Key = value; + } else if (!this._GPGME_Key && isFingerprint(fpr)){ + this._GPGME_Key = new GPGME_Key; + } + } + + get fingerprint(){ + return this._GPGME_Key.fingerprint; + } + + get armor(){ + return this._GPGME_Key.armored; + } + + get secret(){ + return this._GPGME_Key.hasSecret; + } + + get default(){ + return this._GPGME_Key.isDefault; + } +} + +/** + * creates GPGME_Key_openpgpmode from GPGME_Keys + */ +function translateKeys(input){ + if (!Array.isArray(input)){ + input = [input]; + } + let resultset; + for (let i=0; i< input.length; i++){ + resultset.push(new GPGME_Key_openpgpmode(input[i])); + } + return resultset; +} \ No newline at end of file diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 0e2beda4..0cb2301c 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -19,7 +19,7 @@ */ import { GpgME } from "./gpgmejs"; -import { GpgME_openPGPCompatibility } from "./gpgmejs_openpgpjs"; +import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; /** @@ -40,7 +40,7 @@ function init( config = { let gpgme = null; if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ resolve( - new GpgME_openPGPCompatibility(connection)); + new GpgME_openpgpmode(connection)); } else { resolve(new GpgME(connection)); } -- cgit v1.2.3 From 30c47d80a27054aa340cbd6dc39d1b8a5dc5cf22 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Apr 2018 19:47:48 +0200 Subject: js: allow openpgp-like Message objects as Data -- * src/gpgmejs.js: If a message offers a getText, consider it as the message's content --- lang/js/src/gpgmejs.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 4b2a03a4..03ed5cb6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -159,6 +159,13 @@ function putData(message, data){ } else if (typeof(data) === 'string') { message.setParameter('base64', false); message.setParameter('data', data); + } else if ( typeof(data) === 'object' && data.hasOwnProperty(getText)){ + let txt = data.getText(); + if (txt instanceof Uint8Array){ + let decoder = new TextDecoder('utf8'); + message.setParameter('base64', true); + message.setParameter ('data', decoder.decode(txt)); + } } else { return new GPGMEJS_Error('WRONGPARAM'); } -- cgit v1.2.3 From c72adc00965fe4fcedd9d18609211021a091b28b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 25 Apr 2018 10:54:24 +0200 Subject: js: change in Error behaviour -- * Error objects will now return the error code if defined as error type in src/Errors.js, or do a console.log if it is a warning. Errors from the native gpgme-json will be marked as GNUPG_ERROR. --- lang/js/src/Connection.js | 58 +++++++------ lang/js/src/Errors.js | 170 ++++++++++++++++----------------------- lang/js/src/Helpers.js | 16 ++-- lang/js/src/Key.js | 2 +- lang/js/src/Message.js | 12 +-- lang/js/src/gpgmejs.js | 13 ++- lang/js/src/gpgmejs_openpgpjs.js | 14 ++-- lang/js/src/index.js | 3 +- 8 files changed, 132 insertions(+), 156 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index e6ff67be..8bc3d42a 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -1,5 +1,3 @@ -import { GPGME_Message } from "./Message"; - /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -26,7 +24,8 @@ import { GPGME_Message } from "./Message"; * expected. */ import { permittedOperations } from './permittedOperations' -import { GPGMEJS_Error} from "./Errors" +import { GPGMEJS_Error } from "./Errors" +import { GPGME_Message } from "./Message"; /** * A Connection handles the nativeMessaging interaction. @@ -60,20 +59,20 @@ export class Connection{ /** * Opens a nativeMessaging port. - * TODO: Error handling ALREADY_CONNECTED */ connect(){ if (this._isConnected === true){ - return new GPGMEJS_Error('ALREADY_CONNECTED'); + GPGMEJS_Error('CONN_ALREADY_CONNECTED'); + } else { + this._isConnected = true; + this._connection = chrome.runtime.connectNative('gpgmejson'); + let me = this; + this._connection.onDisconnect.addListener( + function(){ + me._isConnected = false; + } + ); } - this._isConnected = true; - this._connection = chrome.runtime.connectNative('gpgmejson'); - let me = this; - this._connection.onDisconnect.addListener( - function(){ - me._isConnected = false; - } - ); } /** @@ -84,28 +83,31 @@ export class Connection{ */ post(message){ if (!this.isConnected){ - return Promise.reject(new GPGMEJS_Error('NO_CONNECT')); + return Promise.reject(GPGMEJS_Error('CONN_NO_CONNECT')); } if (!message || !message instanceof GPGME_Message){ - return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); + return Promise.reject(GPGMEJS_Error('PARAM_WRONG'), message); } if (message.isComplete !== true){ - return Promise.reject(new GPGMEJS_Error('MSG_INCOMPLETE')); + return Promise.reject(GPGMEJS_Error('MSG_INCOMPLETE')); } - // let timeout = 5000; //TODO config let me = this; return new Promise(function(resolve, reject){ let answer = new Answer(message.operation); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) - reject(new GPGMEJS_Error('EMPTY_GPG_ANSWER')); + reject(GPGMEJS_Error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener) - //TODO: GPGMEJS_Error? - reject(msg.msg); + reject( + {code: 'GNUPG_ERROR', + msg: msg.msg} ); } else { - answer.add(msg); + let answer_result = answer.add(msg); + if (answer_result !== true){ + reject(answer_result); + } if (msg.more === true){ me._connection.postMessage({'op': 'getmore'}); } else { @@ -117,11 +119,12 @@ export class Connection{ me._connection.onMessage.addListener(listener); me._connection.postMessage(message.message); + //TBD: needs to be aware if there is a pinentry pending // setTimeout( // function(){ // me.disconnect(); - // reject(new GPGMEJS_Error('TIMEOUT', 5000)); + // reject(GPGMEJS_Error('CONN_TIMEOUT')); // }, timeout); }); } @@ -141,6 +144,7 @@ class Answer{ /** * Add the information to the answer * @param {Object} msg The message as received with nativeMessaging + * returns true if successfull, GPGMEJS_Error otherwise */ add(msg){ if (this._response === undefined){ @@ -148,12 +152,15 @@ class Answer{ } let messageKeys = Object.keys(msg); let poa = permittedOperations[this.operation].answer; + if (messageKeys.length === 0){ + return GPGMEJS_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 new GPGMEJS_Error('UNEXPECTED_ANSWER'); + return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER'); } break; case 'more': @@ -172,7 +179,7 @@ class Answer{ this._response[key] = msg[key]; } else if (this._response[key] !== msg[key]){ - return new GPGMEJS_Error('UNEXPECTED_ANSWER',msg[key]); + return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER',msg[key]); } } //infos may be json objects etc. Not yet defined. @@ -184,11 +191,12 @@ class Answer{ this._response.push(msg[key]); } else { - return new GPGMEJS_Error('UNEXPECTED_ANSWER', key); + return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER', key); } break; } } + return true; } /** diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index c2356f7c..c49bfe21 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -18,131 +18,99 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -// This is a preliminary collection of erors and warnings to be thrown and implemented. - -// general idea: if throw , throw the NAME -// return false || 'return' property - -//TODO: Connection.NOCONNECT promise -//connection.timeout: Be aware of pinentry - -export class GPGMEJS_Error { - - constructor(code = 'GENERIC_ERROR', details){ - let config = { //TODO TEMP - debug: 'console', // |'alert' - throw: 'default' // | 'always' | 'never' - }; +/** + * Checks the given error code and returns some information about it's meaning + * @param {String} code The error code + * @returns {Object} An object containing string properties code and msg + * TODO: error-like objects with the code 'GNUPG_ERROR' are errors sent + * directly by gnupg as answer in Connection.post() + */ +export function GPGMEJS_Error(code = 'GENERIC_ERROR'){ + if (!typeof(code) === 'string'){ + code = 'GENERIC_ERROR'; + } let errors = { //TODO: someplace else - //Connection errors - 'ALREADY_CONNECTED':{ - msg: 'The connection was already established. The action would overwrite the context', - throw: true + // Connection + 'CONN_NO_CONNECT': { + msg:'Connection with the nativeMessaging host could not be' + + ' established.', + type: 'error' }, - 'NO_CONNECT': { - msg:'Connection with the nativeMessaging host could not be established.', - throw: true + 'CONN_EMPTY_GPG_ANSWER':{ + msg: 'The nativeMessaging answer was empty.', + type: 'error' }, - 'EMPTY_GPG_ANSWER':{ - msg: 'The nativeMesaging answer was empty', - throw: true + 'CONN_TIMEOUT': { + msg: 'A connection timeout was exceeded.', + type: 'error' }, - 'TIMEOUT': { - msg: 'A timeout was exceeded.', - throw: false + 'CONN_UNEXPECTED_ANSWER': { + msg: 'The answer from gnupg was not as expected.', + type: 'error' }, - - 'UNEXPECTED_ANSWER': { - msg: 'The answer from gnupg was not as expected', - throw: true - }, - - // Message/Data Errors - - 'NO_KEYS' : { - msg: 'There were no valid keys provided.', - throw: true - }, - 'NOT_A_FPR': { - msg: 'The String is not an accepted fingerprint', - throw: false + 'CONN_ALREADY_CONNECTED':{ + msg: 'A connection was already established.', + type: 'warn' }, + // Message/Data 'MSG_INCOMPLETE': { - msg: 'The Message did not match the minimum requirements for the interaction', - throw: true - }, - 'EMPTY_MSG' : { - msg: 'The Message has no data.', - throw: true + msg: 'The Message did not match the minimum requirements for' + + ' the interaction.', + type: 'error' }, - 'MSG_NODATA':{ - msg: 'The data sent is empty. This may be unintentional.', - throw: false + 'MSG_EMPTY' : { + msg: 'The Message is empty.', + type: 'error' }, 'MSG_OP_PENDING': { - msg: 'There is no operation specified yet. The parameter cannot be set', - throw: false + msg: 'There is no operation specified yet. The parameter cannot' + + ' be set', + type: 'warning' }, - 'WRONG_OP': { - msg: "The operation requested could not be found", - throw: true + 'MSG_WRONG_OP': { + msg: 'The operation requested could not be found', + type: 'warning' + }, + 'MSG_NO_KEYS' : { + msg: 'There were no valid keys provided.', + type: 'warn' + }, + 'MSG_NOT_A_FPR': { + msg: 'The String is not an accepted fingerprint', + type: 'warn' }, - //generic errors - - 'WRONGPARAM':{ + // generic + 'PARAM_WRONG':{ msg: 'invalid parameter was found', - throw: true - }, - 'WRONGTYPE':{ - msg: 'invalid parameter type was found', - throw: true + type: 'error' }, 'NOT_IMPLEMENTED': { msg: 'A openpgpjs parameter was submitted that is not implemented', - throw: true + type: 'error' + }, + 'NOT_YET_IMPLEMENTED': { + msg: 'Support of this is probable, but it is not implemented yet', + type: 'error' }, 'GENERIC_ERROR': { msg: 'Unspecified error', - throw: true + type: 'error' }, - - // hopefully temporary errors - - 'NOT_YET_IMPLEMENTED': { - msg: 'Support of this is probable, but it is not implemented yet', - throw: false - } - } - if (!errors.hasOwnProperty(code)){ - throw('GENERIC_ERROR'); } - let msg = code; - if (errors[code].msg !== undefined){ - msg = msg + ': ' + errors[code].msg; + if (code === 'TODO'){ + alert('TODO_Error!'); } - if (details){ - msg = msg + ' ' + details; + if (errors.hasOwnProperty(code)){ + code = 'GENERIC_ERROR'; } - if (config.debug === 'console'){ - console.log(msg); - } else if (config.debug === 'alert'){ - alert(msg); + if (error.type === 'error'){ + return {code: 'code', + msg: errors[code].msg + }; } - switch (config.throw) { - case 'default': - if (errors[code].throw === true){ - throw(code); - } - break; - case 'always': - throw(code); - break; - - case 'never': - break; - default: - throw('GENERIC_ERROR'); + if (error.type === 'warning'){ + console.log(code + ': ' + error[code].msg); } - } + return undefined; } diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 922ca06c..d9750ba7 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -1,5 +1,3 @@ -import { GPGMEJS_Error } from "./Errors"; - /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -19,18 +17,19 @@ import { GPGMEJS_Error } from "./Errors"; * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ +import { GPGMEJS_Error } from "./Errors"; /** * Tries to return an array of fingerprints, either from input fingerprints or * from Key objects * @param {Key |Array| GPGME_Key | Array|String|Array} input - * @param {Boolean} nocheck if set, an empty result is acceptable * @returns {Array} Array of fingerprints. */ export function toKeyIdArray(input, nocheck){ if (!input){ - return (nocheck ===true)? [] : new GPGMEJS_Error('NO_KEYS'); + GPGMEJS_Error('MSG_NO_KEYS'); + return []; } if (!Array.isArray(input)){ input = [input]; @@ -41,7 +40,7 @@ export function toKeyIdArray(input, nocheck){ if (isFingerprint(input[i]) === true){ result.push(input[i]); } else { - GPGMEJS_Error + GPGMEJS_Error('MSG_NOT_A_FPR'); } } else if (typeof(input[i]) === 'object'){ let fpr = ''; @@ -53,13 +52,16 @@ export function toKeyIdArray(input, nocheck){ } if (isFingerprint(fpr) === true){ result.push(fpr); + } else { + GPGMEJS_Error('MSG_NOT_A_FPR'); } } else { - return new GPGMEJS_Error('WRONGTYPE'); + return GPGMEJS_Error('PARAM_WRONG'); } } if (result.length === 0){ - return (nocheck===true)? [] : new GPGMEJS_Error('NO_KEYS'); + GPGMEJS_Error('MSG_NO_KEYS'); + return []; } else { return result; } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index f59b9901..5ae80438 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -172,7 +172,7 @@ export class GPGME_Key { * */ function checkKey(fingerprint, property){ - return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); return new Promise(function(resolve, reject){ if (!isFingerprint(fingerprint)){ diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index f5e21e00..1b36f11d 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -39,20 +39,20 @@ export class GPGME_Message { */ setParameter(param,value){ if (!param || typeof(param) !== 'string'){ - return new GPGMEJS_Error('WRONGPARAM', 'type check failed'); + return GPGMEJS_Error('PARAM_WRONG'); } if (!this._msg || !this._msg.op){ - return new GPGMEJS_Error('MSG_OP_PENDING'); + return GPGMEJS_Error('MSG_OP_PENDING'); } let po = permittedOperations[this._msg.op]; if (!po){ - return new GPGMEJS_Error('WRONG_OP', param); + return GPGMEJS_Error('MSG_WRONG_OP'); } if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ this._msg[param] = value; return true; } - return new GPGMEJS_Error('WRONGPARAM', param); + return GPGMEJS_Error('PARAM_WRONG'); } /** @@ -98,7 +98,7 @@ export class GPGME_Message { */ function setOperation (scope, operation){ if (!operation || typeof(operation) !== 'string'){ - return new GPGMEJS_Error('WRONGTYPE'); + return GPGMEJS_Error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ if (!scope._msg){ @@ -106,6 +106,6 @@ function setOperation (scope, operation){ } scope._msg.op = operation; } else { - return new GPGMEJS_Error('WRONG_OP'); + return GPGMEJS_Error('MSG_WRONG_OP'); } } \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 03ed5cb6..b20ff0f2 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -95,9 +95,8 @@ export class GpgME { */ decrypt(data){ - if (data === undefined){ - return Promise.reject(new GPGMEJS_Error ('EMPTY_MSG')); + return Promise.reject(GPGMEJS_Error('MSG_EMPTY')); } let msg = new GPGME_Message('decrypt'); putData(msg, data); @@ -106,7 +105,7 @@ export class GpgME { } deleteKey(key, delete_secret = false, no_confirm = false){ - return Promise.reject(new GPGMEJS_Error ('NOT_YET_IMPLEMENTED')); + return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); let msg = new GPGME_Message('deletekey'); let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ @@ -127,7 +126,7 @@ export class GpgME { case 'ERR_NO_ERROR': return Promise.resolve('okay'); //TBD default: - return Promise.reject(new GPGMEJS_Error); + return Promise.reject(GPGMEJS_Error('TODO') ); // // INV_VALUE, // GPG_ERR_NO_PUBKEY, // GPG_ERR_AMBIGUOUS_NAME, @@ -146,11 +145,9 @@ export class GpgME { */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { - return new GPGMEJS_Error('WRONGPARAM'); + return GPGMEJS_Error('PARAM_WRONG'); } if (!data){ - //TODO Debug only! No data is legitimate - console.log('Warning. no data in message'); message.setParameter('data', ''); } else if (data instanceof Uint8Array){ let decoder = new TextDecoder('utf8'); @@ -167,6 +164,6 @@ function putData(message, data){ message.setParameter ('data', decoder.decode(txt)); } } else { - return new GPGMEJS_Error('WRONGPARAM'); + return GPGMEJS_Error('PARAM_WRONG'); } } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index 23076569..e32f43a3 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -88,14 +88,14 @@ || signature !== null || returnSessionKey !== null || date !== null){ - return Promise.reject(new GPMGEJS_Error('NOT_IMPLEMENTED')); + return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || filename || compression || armor === false || detached == true){ - return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); } return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); } @@ -123,14 +123,14 @@ if (passwords !== undefined || sessionKeys || date){ - return Promise.reject(new GPGMEJS_Error('NOT_IMPLEMENTED')); + return Promise.reject(GPGMEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || publicKeys || format !== 'utf8' || signature ){ - return Promise.reject(new GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); } return this.GpgME.decrypt(message); // TODO: translate between: @@ -185,7 +185,7 @@ class GPGME_Keyring_openpgpmode { else { // TODO: Can there be "no default key"? // TODO: Can there be several default keys? - return new GPGMEJS_Error; //TODO + return GPGMEJS_Error('TODO'); } }); } @@ -202,10 +202,10 @@ class GPGME_Keyring_openpgpmode { */ deleteKey(key){ if (typeof(key) !== "object"){ - return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); + return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); } if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ - return Promise.reject(new GPGMEJS_Error('WRONGPARAM')); + return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); } let key_to_delete = new GPGME_Key(key.fingerprint); return key_to_delete.deleteKey(key.secret); diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 0cb2301c..a54277c2 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -19,6 +19,7 @@ */ import { GpgME } from "./gpgmejs"; +import { GPGMEJS_Error } from "./Errors"; import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; @@ -45,7 +46,7 @@ function init( config = { resolve(new GpgME(connection)); } } else { - reject('NO_CONNECT'); + reject(GPGMEJS_Error('CONN_NO_CONNECT')); } }; setTimeout(delayedreaction, 5); -- cgit v1.2.3 From 5befa1c9751fe54b5ae87906d7f09772ce9de6ea Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 25 Apr 2018 11:32:21 +0200 Subject: js: reactivate timeout on connection -- * A timeout of 5 seconds is activated for functions that do not require a pinentry. This definition is written to src/permittedOperations.js * testapplication.js now alerts the proper error codes and messages. * src/Errors.js fixed two typos in error handling --- lang/js/src/Connection.js | 20 ++++++++++++-------- lang/js/src/Errors.js | 4 ++-- lang/js/src/permittedOperations.js | 3 +++ lang/js/testapplication.js | 4 ++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8bc3d42a..4270be58 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -118,14 +118,18 @@ export class Connection{ }; me._connection.onMessage.addListener(listener); - me._connection.postMessage(message.message); - - //TBD: needs to be aware if there is a pinentry pending - // setTimeout( - // function(){ - // me.disconnect(); - // reject(GPGMEJS_Error('CONN_TIMEOUT')); - // }, timeout); + let timeout = new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(GPGMEJS_Error('CONN_TIMEOUT')); + }, 5000); + }); + if (permittedOperations[message.operation].pinentry){ + return me._connection.postMessage(message.message); + } else { + return Promise.race([timeout, + me._connection.postMessage(message.message) + ]); + } }); } }; diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index c49bfe21..04b13e10 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -104,12 +104,12 @@ export function GPGMEJS_Error(code = 'GENERIC_ERROR'){ if (errors.hasOwnProperty(code)){ code = 'GENERIC_ERROR'; } - if (error.type === 'error'){ + if (errors.type === 'error'){ return {code: 'code', msg: errors[code].msg }; } - if (error.type === 'warning'){ + if (errors.type === 'warning'){ console.log(code + ': ' + error[code].msg); } return undefined; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 3c11b8e0..892f4f2e 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -23,6 +23,8 @@ * operation: required: Array optional: Array + pinentry: Boolean If a pinentry dialog is expected, and a timeout of + 5000 ms would be too short answer: type: The payload property of the answer. May be @@ -59,6 +61,7 @@ export const permittedOperations = { }, decrypt: { + pinentry: true, required: ['data'], optional: [ 'protocol', diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js index f47299e8..b2cb4c23 100644 --- a/lang/js/testapplication.js +++ b/lang/js/testapplication.js @@ -33,7 +33,7 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('answer').value = answer.data; } }, function(errormsg){ - alert('Error: '+ errormsg); + alert( errormsg.code + ' ' + errormsg.msg); }); }); @@ -47,7 +47,7 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('answer').value = answer.data; } }, function(errormsg){ - alert('Error: '+ errormsg); + alert( errormsg.code + ' ' + errormsg.msg); }); }); }, -- cgit v1.2.3 From 1fb310cabe578625f96fce5d84ff6f0092c08d24 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 25 Apr 2018 15:59:36 +0200 Subject: js: Configuration and Error handling -- * gpgmejs_openpgpjs - unsuported values with no negative consequences can now reject, warn or be ignored, according to config.unconsidered_params - cleanup of unsupported/supported parameters and TODOS * A src/index.js init() now accepts a configuration object * Errors will now be derived from Error, offering more info and a stacktrace. * Fixed Connection.post() timeout triggering on wrong cases * Added comments in permittedOperations.js, which gpgme interactions are still unimplemented and should be added next --- lang/js/src/Connection.js | 39 +++---- lang/js/src/Errors.js | 208 +++++++++++++++++++++---------------- lang/js/src/Helpers.js | 12 +-- lang/js/src/Key.js | 44 ++++---- lang/js/src/Keyring.js | 13 ++- lang/js/src/Message.js | 14 +-- lang/js/src/gpgmejs.js | 19 ++-- lang/js/src/gpgmejs_openpgpjs.js | 106 +++++++++++-------- lang/js/src/index.js | 9 +- lang/js/src/permittedOperations.js | 52 +++++++++- 10 files changed, 309 insertions(+), 207 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 4270be58..5b092ab0 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -24,7 +24,7 @@ * expected. */ import { permittedOperations } from './permittedOperations' -import { GPGMEJS_Error } from "./Errors" +import { gpgme_error } from "./Errors" import { GPGME_Message } from "./Message"; /** @@ -62,7 +62,7 @@ export class Connection{ */ connect(){ if (this._isConnected === true){ - GPGMEJS_Error('CONN_ALREADY_CONNECTED'); + gpgme_error('CONN_ALREADY_CONNECTED'); } else { this._isConnected = true; this._connection = chrome.runtime.connectNative('gpgmejson'); @@ -83,13 +83,13 @@ export class Connection{ */ post(message){ if (!this.isConnected){ - return Promise.reject(GPGMEJS_Error('CONN_NO_CONNECT')); + return Promise.reject(gpgme_error('CONN_NO_CONNECT')); } if (!message || !message instanceof GPGME_Message){ - return Promise.reject(GPGMEJS_Error('PARAM_WRONG'), message); + return Promise.reject(gpgme_error('PARAM_WRONG'), message); } if (message.isComplete !== true){ - return Promise.reject(GPGMEJS_Error('MSG_INCOMPLETE')); + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; return new Promise(function(resolve, reject){ @@ -97,7 +97,7 @@ export class Connection{ let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) - reject(GPGMEJS_Error('CONN_EMPTY_GPG_ANSWER')); + reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener) reject( @@ -118,17 +118,18 @@ export class Connection{ }; me._connection.onMessage.addListener(listener); - let timeout = new Promise(function(resolve, reject){ - setTimeout(function(){ - reject(GPGMEJS_Error('CONN_TIMEOUT')); - }, 5000); - }); if (permittedOperations[message.operation].pinentry){ return me._connection.postMessage(message.message); } else { - return Promise.race([timeout, - me._connection.postMessage(message.message) - ]); + return Promise.race([ + me._connection.postMessage(message.message), + function(resolve, reject){ + setTimeout(function(){ + reject(gpgme_error('CONN_TIMEOUT')); + }, 5000); + }]).then(function(result){ + return result; + }); } }); } @@ -148,7 +149,7 @@ class Answer{ /** * Add the information to the answer * @param {Object} msg The message as received with nativeMessaging - * returns true if successfull, GPGMEJS_Error otherwise + * returns true if successfull, gpgme_error otherwise */ add(msg){ if (this._response === undefined){ @@ -157,14 +158,14 @@ class Answer{ let messageKeys = Object.keys(msg); let poa = permittedOperations[this.operation].answer; if (messageKeys.length === 0){ - return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER'); + 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 GPGMEJS_Error('CONN_UNEXPECTED_ANSWER'); + return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; case 'more': @@ -183,7 +184,7 @@ class Answer{ this._response[key] = msg[key]; } else if (this._response[key] !== msg[key]){ - return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER',msg[key]); + return gpgme_error('CONN_UNEXPECTED_ANSWER',msg[key]); } } //infos may be json objects etc. Not yet defined. @@ -195,7 +196,7 @@ class Answer{ this._response.push(msg[key]); } else { - return GPGMEJS_Error('CONN_UNEXPECTED_ANSWER', key); + return gpgme_error('CONN_UNEXPECTED_ANSWER', key); } break; } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 04b13e10..2f53aa89 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -18,99 +18,125 @@ * 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_DISCONNECTED': { + msg:'Connection with the nativeMessaging host was lost.', + 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_OP_PENDING': { + msg: 'There is no operation specified yet. The parameter cannot' + + ' be set', + type: 'warning' + }, + 'MSG_WRONG_OP': { + msg: 'The operation requested could not be found', + type: 'warning' + }, + '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' + }, + // 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' + }, + 'NOT_IMPLEMENTED': { + msg: 'A openpgpjs parameter was submitted that is not implemented', + type: 'error' + }, + 'NOT_YET_IMPLEMENTED': { + msg: 'Support of this is probable, but it is not implemented yet', + type: 'error' + }, + 'GENERIC_ERROR': { + msg: 'Unspecified error', + type: 'error' + } +}; + /** - * Checks the given error code and returns some information about it's meaning - * @param {String} code The error code - * @returns {Object} An object containing string properties code and msg - * TODO: error-like objects with the code 'GNUPG_ERROR' are errors sent - * directly by gnupg as answer in Connection.post() + * 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 GPGMEJS_Error(code = 'GENERIC_ERROR'){ - if (!typeof(code) === 'string'){ - code = 'GENERIC_ERROR'; - } - let errors = { //TODO: someplace else - // 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: 'warn' - }, - // 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_OP_PENDING': { - msg: 'There is no operation specified yet. The parameter cannot' - + ' be set', - type: 'warning' - }, - 'MSG_WRONG_OP': { - msg: 'The operation requested could not be found', - type: 'warning' - }, - 'MSG_NO_KEYS' : { - msg: 'There were no valid keys provided.', - type: 'warn' - }, - 'MSG_NOT_A_FPR': { - msg: 'The String is not an accepted fingerprint', - type: 'warn' - }, - - // generic - 'PARAM_WRONG':{ - msg: 'invalid parameter was found', - type: 'error' - }, - 'NOT_IMPLEMENTED': { - msg: 'A openpgpjs parameter was submitted that is not implemented', - type: 'error' - }, - 'NOT_YET_IMPLEMENTED': { - msg: 'Support of this is probable, but it is not implemented yet', - type: 'error' - }, - 'GENERIC_ERROR': { - msg: 'Unspecified error', - type: 'error' - }, - } - if (code === 'TODO'){ - alert('TODO_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 (errors.hasOwnProperty(code)){ - code = 'GENERIC_ERROR'; + if (err_list[code].type === 'warning'){ + console.log(new GPGME_Error(code)); } - if (errors.type === 'error'){ - return {code: 'code', - msg: errors[code].msg - }; - } - if (errors.type === 'warning'){ - console.log(code + ': ' + error[code].msg); - } - return undefined; + return null; + } else if (code === 'GNUPG_ERROR'){ + return new GPGME_Error(code, info.msg); + } + 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); + } 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 d9750ba7..841c0eda 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -17,7 +17,7 @@ * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ -import { GPGMEJS_Error } from "./Errors"; +import { gpgme_error } from "./Errors"; /** * Tries to return an array of fingerprints, either from input fingerprints or @@ -28,7 +28,7 @@ import { GPGMEJS_Error } from "./Errors"; export function toKeyIdArray(input, nocheck){ if (!input){ - GPGMEJS_Error('MSG_NO_KEYS'); + gpgme_error('MSG_NO_KEYS'); return []; } if (!Array.isArray(input)){ @@ -40,7 +40,7 @@ export function toKeyIdArray(input, nocheck){ if (isFingerprint(input[i]) === true){ result.push(input[i]); } else { - GPGMEJS_Error('MSG_NOT_A_FPR'); + gpgme_error('MSG_NOT_A_FPR'); } } else if (typeof(input[i]) === 'object'){ let fpr = ''; @@ -53,14 +53,14 @@ export function toKeyIdArray(input, nocheck){ if (isFingerprint(fpr) === true){ result.push(fpr); } else { - GPGMEJS_Error('MSG_NOT_A_FPR'); + gpgme_error('MSG_NOT_A_FPR'); } } else { - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } } if (result.length === 0){ - GPGMEJS_Error('MSG_NO_KEYS'); + gpgme_error('MSG_NO_KEYS'); return []; } else { return result; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 5ae80438..f6fa7ae3 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -27,7 +27,9 @@ */ import {isFingerprint} from './Helpers' -import {GPGMEJS_Error} from './Errors' +import {gpgme_error} from './Errors' +import { GPGME_Message } from './Message'; +import { permittedOperations } from './permittedOperations'; export class GPGME_Key { @@ -172,32 +174,30 @@ export class GPGME_Key { * */ function checkKey(fingerprint, property){ - return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); - + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); + if (!property || + permittedOperations[keyinfo].indexOf(property) < 0){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } return new Promise(function(resolve, reject){ if (!isFingerprint(fingerprint)){ - reject('not a fingerprint'); //TBD + reject('KEY_INVALID'); } - let conn = new Connection(); - conn.post('getkey',{ // TODO not yet implemented in gpgme - 'fingerprint': this.fingerprint}) - .then(function(result){ - if (property !== undefined){ - if (result.hasOwnProperty(key)){ - resolve(result[property]); - } - else if (property == 'secret'){ - // property undefined means "not true" in case of secret - resolve(false); - } else { - reject('ERR_INVALID_PROPERTY') //TBD - } + let msg = new GPGME_Message('keyinfo'); + msg.setParameter('fingerprint', this.fingerprint); + return (this.connection.post(msg)).then(function(result){ + if (result.hasOwnProperty(property)){ + resolve(result[property]); + } + else if (property == 'secret'){ + // TBD property undefined means "not true" in case of secret? + resolve(false); + } else { + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } - - - resolve(result); }, function(error){ - reject(error); + reject({code: 'GNUPG_ERROR', + msg: error.msg}); }); }); }; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index ef8028ff..e1f0a50f 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -21,6 +21,7 @@ import {GPGME_Message} from './Message' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; +import { gpgme_error } from './Errors'; export class GPGME_Keyring { constructor(connection){ @@ -37,9 +38,9 @@ export class GPGME_Keyring { if (this._connection.isConnected){ return this._connection; } - return undefined; //TODO: connection was lost! + return gpgme_error('CONN_DISCONNECTED'); } - return undefined; //TODO: no connection there + return gpgme_error('CONN_NO_CONNECT'); } /** @@ -81,9 +82,7 @@ export class GPGME_Keyring { * 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.defaultKey Only Keys marked as Default Keys * @param {Boolean} flags.secret Only Keys containing a secret part. - * @param {Boolean} flags.valid Valid Keys only * @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 @@ -108,16 +107,20 @@ export class GPGME_Keyring { } 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){ + */ + /** + * 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){ diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 1b36f11d..06ac8db2 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -18,7 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ */ import { permittedOperations } from './permittedOperations' -import { GPGMEJS_Error } from './Errors' +import { gpgme_error } from './Errors' export class GPGME_Message { //TODO getter @@ -39,20 +39,20 @@ export class GPGME_Message { */ setParameter(param,value){ if (!param || typeof(param) !== 'string'){ - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } if (!this._msg || !this._msg.op){ - return GPGMEJS_Error('MSG_OP_PENDING'); + return gpgme_error('MSG_OP_PENDING'); } let po = permittedOperations[this._msg.op]; if (!po){ - return GPGMEJS_Error('MSG_WRONG_OP'); + return gpgme_error('MSG_WRONG_OP'); } if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ this._msg[param] = value; return true; } - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } /** @@ -98,7 +98,7 @@ export class GPGME_Message { */ function setOperation (scope, operation){ if (!operation || typeof(operation) !== 'string'){ - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ if (!scope._msg){ @@ -106,6 +106,6 @@ function setOperation (scope, operation){ } scope._msg.op = operation; } else { - return GPGMEJS_Error('MSG_WRONG_OP'); + return gpgme_error('MSG_WRONG_OP'); } } \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index b20ff0f2..b504a457 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -21,7 +21,7 @@ import {Connection} from "./Connection" import {GPGME_Message} from './Message' import {toKeyIdArray} from "./Helpers" -import {GPGMEJS_Error as Error, GPGMEJS_Error} from "./Errors" +import { gpgme_error } from "./Errors" import { GPGME_Keyring } from "./Keyring"; export class GpgME { @@ -35,10 +35,12 @@ export class GpgME { set connection(connection){ if (this._connection instanceof Connection){ - //TODO Warning: Connection already established + gpgme_error('CONN_ALREADY_CONNECTED'); } if (connection instanceof Connection){ this._connection = connection; + } else { + gpgme_error('PARAM_WRONG'); } } @@ -54,11 +56,12 @@ export class GpgME { set Keyring(keyring){ if (ring && ring instanceof GPGME_Keyring){ - this.Keyring = ring; + this._Keyring = ring; } } get Keyring(){ + return this._Keyring; } /** @@ -96,7 +99,7 @@ export class GpgME { decrypt(data){ if (data === undefined){ - return Promise.reject(GPGMEJS_Error('MSG_EMPTY')); + return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = new GPGME_Message('decrypt'); putData(msg, data); @@ -105,7 +108,7 @@ export class GpgME { } deleteKey(key, delete_secret = false, no_confirm = false){ - return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); let msg = new GPGME_Message('deletekey'); let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ @@ -126,7 +129,7 @@ export class GpgME { case 'ERR_NO_ERROR': return Promise.resolve('okay'); //TBD default: - return Promise.reject(GPGMEJS_Error('TODO') ); // + return Promise.reject(gpgme_error('TODO') ); // // INV_VALUE, // GPG_ERR_NO_PUBKEY, // GPG_ERR_AMBIGUOUS_NAME, @@ -145,7 +148,7 @@ export class GpgME { */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } if (!data){ message.setParameter('data', ''); @@ -164,6 +167,6 @@ function putData(message, data){ message.setParameter ('data', decoder.decode(txt)); } } else { - return GPGMEJS_Error('PARAM_WRONG'); + return gpgme_error('PARAM_WRONG'); } } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index e32f43a3..4e5e1ea0 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -28,13 +28,13 @@ import {GPGME_Keyring} from "./Keyring" import { GPGME_Key } from "./Key"; import { isFingerprint } from "./Helpers" - import { GPGMEJS_Error } from './Errors' + import { gpgme_error } from "./Errors" export class GpgME_openpgpmode { - constructor(connection){ - this.initGpgME(connection); + constructor(connection, config = {}){ + this.initGpgME(connection, config); } get Keyring(){ @@ -44,9 +44,16 @@ return undefined; } - initGpgME(connection){ - this._GpgME = new GpgME(connection); - this._Keyring = new GPGME_Keyring_openpgpmode(connection); + initGpgME(connection, config = {}){ + if (connection && typeof(config) ==='object'){ + this._config = config; + if (!this._GPGME){ + this._GpgME = new GpgME(connection, config); + } + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring_openpgpmode(connection); + } + } } get GpgME(){ @@ -59,19 +66,23 @@ * Encrypt Message * Supported: * @param {String|Uint8Array} data + * //an openpgp Message also accepted here. TODO: is this wanted? * @param {Key|Array} publicKeys + * //Strings of Fingerprints * @param {Boolean} wildcard * TODO: - * @param {Key|Array} privateKeys - * @param {String} filename - * @param {module:enums.compression} compression - * @param {Boolean} armor - * @param {Boolean} detached + * @param {Key|Array} privateKeys // -> encryptsign + * @param {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress + * @param {Boolean} armor // TODO base64 switch + * @param {Boolean} detached // --> encryptsign * unsupported: * @param {String|Array} passwords * @param {Object} sessionKey * @param {Signature} signature * @param {Boolean} returnSessionKey + * @param {String} filename + * + * Can be set, but will be ignored: * * @returns {Promise} * {data: ASCII armored message, @@ -80,57 +91,66 @@ * @async * @static */ - encrypt({data = '', publicKeys = '', privateKeys, passwords, sessionKey, - filename, compression, armor=true, detached=false, signature=null, - returnSessionKey=null, wildcard=false, date=null}) { - if (passwords !== undefined - || sessionKey !== undefined + encrypt({data = '', publicKeys = '', privateKeys, passwords=null, + sessionKey = null, filename, compression, armor=true, detached=false, + signature=null, returnSessionKey=null, wildcard=false, date=null}) { + if (passwords !== null + || sessionKey !== null || signature !== null || returnSessionKey !== null - || date !== null){ + || date !== null + ){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys - || filename || compression || armor === false || detached == true){ - return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); + } + if (filename){ + if (this._config.unconsidered_params === 'warn'){ + GPMGEJS_Error('PARAM_IGNORED'); + } else if (this._config.unconsidered_params === 'error'){ + return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); + } } return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); } /** Decrypt Message - * supported - * TODO: @param {Message} message TODO: for now it accepts an armored string only + * supported openpgpjs parameters: + * @param {Message|Uint8Array|String} message Message object from openpgpjs * Unsupported: * @param {String|Array} passwords + * @param {Key|Array} privateKeys * @param {Object|Array} sessionKeys - * @param {Date} date - - * TODO - * @param {Key|Array} privateKey - * @param {Key|Array} publicKeys + * Not yet supported, but planned * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification - + * Ignored values: can be safely set, but have no effect + * @param {Date} date + * @param {Key|Array} publicKeys + * * @returns {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @async * @static */ - decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null, date}) { - if (passwords !== undefined - || sessionKeys - || date){ - return Promise.reject(GPGMEJS_Error('NOT_IMPLEMENTED')); + decrypt({ message, privateKeys, passwords=null, sessionKeys, + publicKeys, format='utf8', signature=null, date= null}) { + if (passwords !== null || sessionKeys || privateKeys){ + return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } - if ( privateKeys - || publicKeys - || format !== 'utf8' - || signature - ){ - return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED')); + if ( format !== 'utf8' || signature){ + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); + } + if (date !== null || publicKeys){ + if (this._config.unconsidered_params === 'warn'){ + GPMGEJS_Error('PARAM_IGNORED'); + } else if (this._config.unconsidered_params === 'reject'){ + return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); + } } return this.GpgME.decrypt(message); // TODO: translate between: @@ -185,7 +205,7 @@ class GPGME_Keyring_openpgpmode { else { // TODO: Can there be "no default key"? // TODO: Can there be several default keys? - return GPGMEJS_Error('TODO'); + return gpgme_error('TODO'); } }); } @@ -202,10 +222,10 @@ class GPGME_Keyring_openpgpmode { */ deleteKey(key){ if (typeof(key) !== "object"){ - return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); + return Promise.reject(gpgme_error('PARAM_WRONG')); } if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ - return Promise.reject(GPGMEJS_Error('PARAM_WRONG')); + return Promise.reject(gpgme_error('PARAM_WRONG')); } let key_to_delete = new GPGME_Key(key.fingerprint); return key_to_delete.deleteKey(key.secret); @@ -224,8 +244,8 @@ class GPGME_Key_openpgpmode { set init (value){ if (!this._GPGME_Key && value instanceof GPGME_Key){ this._GPGME_Key = value; - } else if (!this._GPGME_Key && isFingerprint(fpr)){ - this._GPGME_Key = new GPGME_Key; + } else if (!this._GPGME_Key && isFingerprint(value)){ + this._GPGME_Key = new GPGME_Key(value); } } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index a54277c2..48904316 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -19,7 +19,7 @@ */ import { GpgME } from "./gpgmejs"; -import { GPGMEJS_Error } from "./Errors"; +import { gpgme_error } from "./Errors"; import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; @@ -29,7 +29,8 @@ import { Connection } from "./Connection"; */ function init( config = { api_style: 'gpgme', // | gpgme_openpgpjs - null_expire_is_never: true // Boolean + null_expire_is_never: true, // Boolean + unconsidered_params: 'warn'//'warn' || 'reject' }){ return new Promise(function(resolve, reject){ let connection = new Connection; @@ -41,12 +42,12 @@ function init( config = { let gpgme = null; if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ resolve( - new GpgME_openpgpmode(connection)); + new GpgME_openpgpmode(connection, config)); } else { resolve(new GpgME(connection)); } } else { - reject(GPGMEJS_Error('CONN_NO_CONNECT')); + reject(gpgme_error('CONN_NO_CONNECT')); } }; setTimeout(delayedreaction, 5); diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 892f4f2e..79e74223 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -31,7 +31,7 @@ partial and in need of concatenation params: Array Information that do not change throughout the message - infos: Array arbitrary information that may change + infos: Array<*> arbitrary information that may result in a list } } */ @@ -72,7 +72,55 @@ export const permittedOperations = { type: ['plaintext'], data: ['data'], params: ['base64', 'mime'], - infos: ['info'] + infos: [] // pending. Info about signatures and validity + //signature: [{Key Fingerprint, valid Boolean}] + } + }, + /** + keyinfo: { // querying the Key's information. + required: ['fingerprint'], + anser: { + type: ['TBD'], + data: [], + params: ['hasSecret', 'isRevoked', 'isExpired', 'armored', + 'timestamp', 'expires', 'pubkey_algo'], + infos: ['subkeys', 'userIds'] + }*/ + + /** + listkeys:{ + optional: ['with-secret', 'pattern'], + answer: { + type: ['TBD'], //Array of fingerprints? + infos: ['TBD'] //the property with infos + }, + */ + + /** + importkey: { + required: ['keyarmored'], + answer: { + type: ['TBD'], + infos: [''], // for each key if import was a success, if it was an update + } + }, + */ + + /** + deletekey: { + required: ['fingerprint'], + answer: { + type ['TBD'], + infos: [''] //success:true? in gpgme, an error NO_ERROR is returned } } + */ + + /** + *get armored secret different treatment from keyinfo! + */ + + /** + * TBD key modification requests? + */ } -- cgit v1.2.3 From 3685913bf510a14b8cb324d980217d90489e6453 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 25 Apr 2018 19:45:39 +0200 Subject: js: First testing and improvements -- * Introduced Mocha/chai as testsuite. After development build 'npm test' should run the unit tests. Functionality exclusive to Browsers/WebExtensions cannot be run this way, so some other testing is still needed. - package.json: Added required development packages - .babelrc indirect configuration for mocha. ES6 transpiling needs some babel configuration, but mocha has no setting for it. - test/mocha.opts Vonfiguration for mocha runs * Fixed errors: - Helpers.js toKeyIdArray; isLongId is now exported - Key.js Key constructor failed - Message.js will not throw an Error during construction, a new message is now created with createMessage, which can return an Error or a GPGME_Message object * Tests: - test/Helpers: exports from Helpers.js, GPGME_Error handling - test/Message: first init test with bad parameters --- lang/js/.babelrc | 1 + lang/js/package.json | 6 ++- lang/js/src/Connection.js | 2 +- lang/js/src/Errors.js | 11 ++--- lang/js/src/Helpers.js | 7 +-- lang/js/src/Key.js | 15 ++++--- lang/js/src/Keyring.js | 7 ++- lang/js/src/Message.js | 47 ++++++++++---------- lang/js/src/gpgmejs.js | 31 ++++++++----- lang/js/test/Helpers.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++ lang/js/test/Message.js | 42 ++++++++++++++++++ lang/js/test/mocha.opts | 4 ++ 12 files changed, 227 insertions(+), 56 deletions(-) create mode 100644 lang/js/.babelrc create mode 100644 lang/js/test/Helpers.js create mode 100644 lang/js/test/Message.js create mode 100644 lang/js/test/mocha.opts diff --git a/lang/js/.babelrc b/lang/js/.babelrc new file mode 100644 index 00000000..9d8d5165 --- /dev/null +++ b/lang/js/.babelrc @@ -0,0 +1 @@ +{ "presets": ["es2015"] } diff --git a/lang/js/package.json b/lang/js/package.json index 2b7dd7ee..a794188a 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -5,13 +5,15 @@ "main": "src/index.js", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha" }, "keywords": [], "author": "", "license": "", "devDependencies": { "webpack": "^4.5.0", - "webpack-cli": "^2.0.14" + "webpack-cli": "^2.0.14", + "chai": "^4.1.2", + "mocha": "^5.1.1" } } diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 5b092ab0..a10f9d9a 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -83,7 +83,7 @@ export class Connection{ */ post(message){ if (!this.isConnected){ - return Promise.reject(gpgme_error('CONN_NO_CONNECT')); + return Promise.reject(gpgme_error('CONN_DISCONNECTED')); } if (!message || !message instanceof GPGME_Message){ return Promise.reject(gpgme_error('PARAM_WRONG'), message); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 2f53aa89..d26aab18 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -55,14 +55,9 @@ const err_list = { msg: 'The Message is empty.', type: 'error' }, - 'MSG_OP_PENDING': { - msg: 'There is no operation specified yet. The parameter cannot' - + ' be set', - type: 'warning' - }, 'MSG_WRONG_OP': { msg: 'The operation requested could not be found', - type: 'warning' + type: 'error' }, 'MSG_NO_KEYS' : { msg: 'There were no valid keys provided.', @@ -78,7 +73,7 @@ const err_list = { }, // generic 'PARAM_WRONG':{ - msg: 'invalid parameter was found', + msg: 'Invalid parameter was found', type: 'error' }, 'PARAM_IGNORED': { @@ -111,7 +106,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ - console.log(new GPGME_Error(code)); + console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 841c0eda..9a69f851 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -18,6 +18,7 @@ * 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 @@ -26,7 +27,7 @@ import { gpgme_error } from "./Errors"; * @returns {Array} Array of fingerprints. */ -export function toKeyIdArray(input, nocheck){ +export function toKeyIdArray(input){ if (!input){ gpgme_error('MSG_NO_KEYS'); return []; @@ -46,7 +47,7 @@ export function toKeyIdArray(input, nocheck){ let fpr = ''; if (input[i] instanceof GPGME_Key){ fpr = input[i].fingerprint; - } else if (input[i].hasOwnProperty(primaryKey) && + } else if (input[i].hasOwnProperty('primaryKey') && input[i].primaryKey.hasOwnProperty(getFingerprint)){ fpr = input[i].primaryKey.getFingerprint(); } @@ -92,7 +93,7 @@ export function isFingerprint(string){ /** * check if the input is a valid Hex string with a length of 16 */ -function isLongId(string){ +export function isLongId(string){ return hextest(string, 16); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index f6fa7ae3..0b44b245 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -26,9 +26,9 @@ * */ -import {isFingerprint} from './Helpers' -import {gpgme_error} from './Errors' -import { GPGME_Message } from './Message'; +import { isFingerprint } from './Helpers' +import { gpgme_error } from './Errors' +import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; export class GPGME_Key { @@ -39,7 +39,7 @@ export class GPGME_Key { set fingerprint(fpr){ if (isFingerprint(fpr) === true && !this._fingerprint){ - this._fingerprint = fingerprint; + this._fingerprint = fpr; } } @@ -181,9 +181,12 @@ function checkKey(fingerprint, property){ } return new Promise(function(resolve, reject){ if (!isFingerprint(fingerprint)){ - reject('KEY_INVALID'); + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage ('keyinfo'); + if (msg instanceof Error){ + reject(gpgme_error('PARAM_WRONG')); } - let msg = new GPGME_Message('keyinfo'); msg.setParameter('fingerprint', this.fingerprint); return (this.connection.post(msg)).then(function(result){ if (result.hasOwnProperty(property)){ diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index e1f0a50f..470eeeec 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -18,7 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import {GPGME_Message} from './Message' +import {createMessage} from './Message' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; @@ -50,7 +50,10 @@ export class GPGME_Keyring { * */ getKeys(pattern, include_secret){ - let msg = new GPGME_Message('listkeys'); + let msg = createMessage('listkeys'); + if (msg instanceof Error){ + return Promise.reject(msg); + } if (pattern && typeof(pattern) === 'string'){ msg.setParameter('pattern', pattern); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 06ac8db2..4d242277 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -19,13 +19,34 @@ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from './Errors' -export class GPGME_Message { + +export function createMessage(operation){ + if (typeof(operation) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + if (permittedOperations.hasOwnProperty(operation)){ + return new GPGME_Message(operation); + } else { + return gpgme_error('MSG_WRONG_OP'); + } +} + +/** + * Prepares a communication request. It checks operations and parameters in + * ./permittedOperations. + * @param {String} operation + */ +class GPGME_Message { //TODO getter constructor(operation){ - setOperation(this, operation); + this.operation = operation; } + set operation (op){ + + + } get operation(){ return this._msg.op; } @@ -41,9 +62,6 @@ export class GPGME_Message { if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } - if (!this._msg || !this._msg.op){ - return gpgme_error('MSG_OP_PENDING'); - } let po = permittedOperations[this._msg.op]; if (!po){ return gpgme_error('MSG_WRONG_OP'); @@ -90,22 +108,3 @@ export class GPGME_Message { } } - -/** - * Defines the operation this message will have - * @param {String} operation Must be defined in permittedOperations - * TODO: move to constructor? - */ -function setOperation (scope, operation){ - if (!operation || typeof(operation) !== 'string'){ - return gpgme_error('PARAM_WRONG'); - } - if (permittedOperations.hasOwnProperty(operation)){ - if (!scope._msg){ - scope._msg = {}; - } - scope._msg.op = operation; - } else { - return gpgme_error('MSG_WRONG_OP'); - } -} \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index b504a457..2ddf2964 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -19,7 +19,7 @@ */ import {Connection} from "./Connection" -import {GPGME_Message} from './Message' +import {GPGME_Message, createMessage} from './Message' import {toKeyIdArray} from "./Helpers" import { gpgme_error } from "./Errors" import { GPGME_Keyring } from "./Keyring"; @@ -71,8 +71,10 @@ export class GpgME { */ encrypt(data, publicKeys, wildcard=false){ - let msg = new GPGME_Message('encrypt'); - + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg) + } // TODO temporary msg.setParameter('armor', true); msg.setParameter('always-trust', true); @@ -101,7 +103,10 @@ export class GpgME { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } - let msg = new GPGME_Message('decrypt'); + let msg = createMessage('decrypt'); + if (msg instanceof Error){ + return Promise.reject(msg); + } putData(msg, data); return this.connection.post(msg); @@ -109,21 +114,27 @@ export class GpgME { deleteKey(key, delete_secret = false, no_confirm = false){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - let msg = new GPGME_Message('deletekey'); + let msg = createMessage('deletekey'); + if (msg instanceof Error){ + return Promise.reject(msg); + } let key_arr = toKeyIdArray(key); if (key_arr.length !== 1){ - throw('TODO'); - //should always be ONE key + return Promise.reject( + gpgme_error('GENERIC_ERROR')); + // TBD should always be ONE key? } msg.setParameter('key', key_arr[0]); if (delete_secret === true){ - msg.setParameter('allow_secret', true); //TBD + msg.setParameter('allow_secret', true); + // TBD } if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? - msg.setParameter('delete_force', true); //TBD + msg.setParameter('delete_force', true); + // TBD } this.connection.post(msg).then(function(success){ - //TODO: it seems that there is always errors coming back: + // TODO: it seems that there is always errors coming back: }, function(error){ switch (error.msg){ case 'ERR_NO_ERROR': diff --git a/lang/js/test/Helpers.js b/lang/js/test/Helpers.js new file mode 100644 index 00000000..590f9f65 --- /dev/null +++ b/lang/js/test/Helpers.js @@ -0,0 +1,110 @@ +/* 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 { expect } from "../node_modules/chai/chai"; +import { gpgme_error} from "../src/Errors"; +import { GPGME_Key } from "../src/Key"; +import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers" + +const helper_params = { + validLongId: '0A0A0A0A0A0A0A0A', + validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), + validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), + 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', + new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], + validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + invalidLongId: '9A9A7A7A8A9A9A7A7A8A', + invalidFingerprint: [{hello:'World'}], + invalidKeyArray: {curiosity:'uncat'}, + invalidKeyArray_OneBad: [ + new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), + 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', + '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], + invalidErrorCode: 'Please type in all your passwords.' +} + +describe('Error Object handling', function(){ + 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(helper_params.invalidErrorCode); + 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(helper_params.validFingerprint); + expect(test0).to.be.true; + }); + it('isFingerprint(): invalid Fingerprint', function(){ + let test0 = isFingerprint(helper_params.invalidFingerprint); + expect(test0).to.be.false; + }); +}); +describe('Converting to Fingerprint', function(){ + it('Correct Inputs', function(){ + it('Fingerprint string', function(){ + let test0 = toKeyIdArray(helper_params.validFingerprint); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validFingerprint); + }); + it('GPGME_Key', function(){ + expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); + let test0 = toKeyIdArray(helper_params.validGPGME_Key); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); + }); + it('Array of valid inputs', function(){ + let test0 = toKeyIdArray(helper_params.validKeys); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf(helper_params.validKeys.length); + }); + }); + describe('Incorrect inputs', function(){ + it('valid Long ID', function(){ + let test0 = toKeyIdArray(helper_params.validLongId); + expect(test0).to.be.empty; + }); + it('invalidFingerprint', function(){ + let test0 = toKeyIdArray(helper_params.invalidFingerprint); + expect(test0).to.be.empty; + }); + it('invalidKeyArray', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray); + expect(test0).to.be.empty; + }); + it('Partially invalid array', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf( + helper_params.invalidKeyArray_OneBad.length - 1); + }); + }); +}); diff --git a/lang/js/test/Message.js b/lang/js/test/Message.js new file mode 100644 index 00000000..454b8ca3 --- /dev/null +++ b/lang/js/test/Message.js @@ -0,0 +1,42 @@ +/* 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 { expect } from "../node_modules/chai/chai"; +import { GPGME_Message, createMessage } from "../src/Message"; + +const message_params = { + invalid_op_action : 'dance', + invalid_op_type : [234, 34, '<>'], +} + +describe('Message Object', function(){ + describe('incorrect initialization', function(){ + it('non-allowed operation', function(){ + let test0 = createMessage(message_params.invalid_op_action); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('MSG_WRONG_OP'); + }); + it('wrong parameter type in constructor', function(){ + let test0 = createMessage(message_params.invalid_op_type); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('PARAM_WRONG'); + }); + }); +}); diff --git a/lang/js/test/mocha.opts b/lang/js/test/mocha.opts new file mode 100644 index 00000000..65adc1c3 --- /dev/null +++ b/lang/js/test/mocha.opts @@ -0,0 +1,4 @@ +--require babel-register +--reporter spec +--ui bdd +--colors -- cgit v1.2.3 From 1f7b19512cfa7e1b153b99d6a2b40bad82a5496e Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 26 Apr 2018 17:13:34 +0200 Subject: js: created TestExtension and smaller fixes -- * Extensions: - Moved testapplication to Demoextension - Created BrowserTestExtension. Includes mocha and chai. For running tests that cannot be run outside a WebExtension Both Extensions can be found zipped in build/extensions after running build_extensions.sh * Code changes: - src/Config: Place for the configuration - small fixes raised during testing in Keyring.js, Message.js, - src/gpgmejs_openpgpjs.js don't offer direct GpgME object to the outside, as it only causes confusion - index.js init() now checks the config for validity * Tests: - Reordered tests in test/. - Input values are now in a separate file which may be of use for bulk testing * moved the build directory from dist to build --- lang/js/BrowserTestExtension/browsertest.html | 23 ++++ lang/js/BrowserTestExtension/manifest.json | 13 ++ lang/js/BrowserTestExtension/popup.html | 9 ++ lang/js/BrowserTestExtension/popup.js | 44 +++++++ lang/js/BrowserTestExtension/runbrowsertest.js | 21 ++++ lang/js/BrowserTestExtension/setup_testing.js | 22 ++++ lang/js/BrowserTestExtension/testicon.png | Bin 0 -> 16192 bytes lang/js/BrowserTestExtension/tests/inputvalues.js | 28 +++++ lang/js/BrowserTestExtension/tests/startup.js | 51 ++++++++ lang/js/CHECKLIST | 8 +- lang/js/CHECKLIST_build | 6 - lang/js/DemoExtension/entry.js | 25 ++++ lang/js/DemoExtension/maindemo.js | 55 +++++++++ lang/js/DemoExtension/mainui.html | 33 +++++ lang/js/DemoExtension/manifest.json | 14 +++ lang/js/DemoExtension/popup.html | 9 ++ lang/js/DemoExtension/testicon.png | Bin 0 -> 16192 bytes lang/js/DemoExtension/ui.css | 10 ++ lang/js/build_extensions.sh | 14 +++ lang/js/manifest.json | 14 --- lang/js/src/Config.js | 31 +++++ lang/js/src/Keyring.js | 1 + lang/js/src/Message.js | 2 +- lang/js/src/gpgmejs_openpgpjs.js | 14 +-- lang/js/src/index.js | 70 +++++++---- lang/js/test/Helpers.js | 139 ++++++++++------------ lang/js/test/Message.js | 33 ++--- lang/js/test/inputvalues.js | 29 +++++ lang/js/test_index.js | 25 ---- lang/js/testapplication.js | 55 --------- lang/js/testapplication_index.html | 9 -- lang/js/testicon.png | Bin 16192 -> 0 bytes lang/js/ui.css | 10 -- lang/js/ui2.html | 33 ----- lang/js/webpack.conf.js | 2 +- 35 files changed, 569 insertions(+), 283 deletions(-) create mode 100644 lang/js/BrowserTestExtension/browsertest.html create mode 100644 lang/js/BrowserTestExtension/manifest.json create mode 100644 lang/js/BrowserTestExtension/popup.html create mode 100644 lang/js/BrowserTestExtension/popup.js create mode 100644 lang/js/BrowserTestExtension/runbrowsertest.js create mode 100644 lang/js/BrowserTestExtension/setup_testing.js create mode 100644 lang/js/BrowserTestExtension/testicon.png create mode 100644 lang/js/BrowserTestExtension/tests/inputvalues.js create mode 100644 lang/js/BrowserTestExtension/tests/startup.js create mode 100644 lang/js/DemoExtension/entry.js create mode 100644 lang/js/DemoExtension/maindemo.js create mode 100644 lang/js/DemoExtension/mainui.html create mode 100644 lang/js/DemoExtension/manifest.json create mode 100644 lang/js/DemoExtension/popup.html create mode 100644 lang/js/DemoExtension/testicon.png create mode 100644 lang/js/DemoExtension/ui.css create mode 100755 lang/js/build_extensions.sh delete mode 100644 lang/js/manifest.json create mode 100644 lang/js/src/Config.js create mode 100644 lang/js/test/inputvalues.js delete mode 100644 lang/js/test_index.js delete mode 100644 lang/js/testapplication.js delete mode 100644 lang/js/testapplication_index.html delete mode 100644 lang/js/testicon.png delete mode 100644 lang/js/ui.css delete mode 100644 lang/js/ui2.html diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html new file mode 100644 index 00000000..d2c6396f --- /dev/null +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -0,0 +1,23 @@ + + + + + + + +

Browsertest

+
+ + + + + + + + + + + + + + diff --git a/lang/js/BrowserTestExtension/manifest.json b/lang/js/BrowserTestExtension/manifest.json new file mode 100644 index 00000000..a9e605bc --- /dev/null +++ b/lang/js/BrowserTestExtension/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 2, + + "name": "Browsertests for gpgmejs", + "description": "Run the browsertests.", + "version": "0.1", + "content_security_policy": "default-src 'self' filesystem:", + "browser_action": { + "default_icon": "testicon.png", + "default_popup": "popup.html" + }, + "permissions": ["nativeMessaging", "activeTab"] + } diff --git a/lang/js/BrowserTestExtension/popup.html b/lang/js/BrowserTestExtension/popup.html new file mode 100644 index 00000000..f17f262a --- /dev/null +++ b/lang/js/BrowserTestExtension/popup.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/popup.js b/lang/js/BrowserTestExtension/popup.js new file mode 100644 index 00000000..4764df55 --- /dev/null +++ b/lang/js/BrowserTestExtension/popup.js @@ -0,0 +1,44 @@ +/* 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+ + */ +/* 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+ + */ + +document.addEventListener('DOMContentLoaded', function() { + chrome.tabs.create({ + url: './browsertest.html' + }); +}); diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js new file mode 100644 index 00000000..39bc3fb9 --- /dev/null +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -0,0 +1,21 @@ +/* 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+ + */ + +mocha.run(); diff --git a/lang/js/BrowserTestExtension/setup_testing.js b/lang/js/BrowserTestExtension/setup_testing.js new file mode 100644 index 00000000..7f70d347 --- /dev/null +++ b/lang/js/BrowserTestExtension/setup_testing.js @@ -0,0 +1,22 @@ +/* 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+ + */ +mocha.setup('bdd'); +var expect = chai.expect; +chai.config.includeStack = true; \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/testicon.png b/lang/js/BrowserTestExtension/testicon.png new file mode 100644 index 00000000..12c3f5df Binary files /dev/null and b/lang/js/BrowserTestExtension/testicon.png differ diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js new file mode 100644 index 00000000..47600c84 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -0,0 +1,28 @@ +/* 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 = { + encrypt: { + good:{ + data : 'Hello World.', + keyid : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' + } + } +}; diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js new file mode 100644 index 00000000..14d12c0a --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -0,0 +1,51 @@ +/* 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('GPGME context', function(){ + it('Starting a GpgME instance', function(done){ + Gpgmejs.init().then( + function(context){ + expect(context.connection).to.not.be.undefined; + expect(context).to.be.an('object'); + expect(context.connection).to.be.an('object'); + expect(context.Keyring).to.be.undefined; + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(err){ + done(err); + }); + }); + it('Starting an openpgp mode GPGME instance', function(done){ + Gpgmejs.init({api_style:"gpgme_openpgpjs"}).then( + function(context){ + console.log(context); + done(); + // expect(context).to.be.an('object'); + // expect(context.connection).to.be.undefined; + // expect(context.Keyring).to.be.an('object'); + // expect(context.encrypt).to.be.a('function'); + // expect(context.decrypt).to.be.a('function'); + // done(); + }, function(err){ + done(err); + }); + }); + }); diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST index 49b17265..75664ae5 100644 --- a/lang/js/CHECKLIST +++ b/lang/js/CHECKLIST @@ -8,8 +8,8 @@ receiving an answer replicating Openpgpjs API: - [*] Message handling (encrypt, verify, sign) - [x] encrypt + [*] Message handling (encrypt, decrypt verify, sign) + [x] encrypt, decrypt [ ] verify [ ] sign [*] Key handling (import/export, modifying, status queries) @@ -23,6 +23,6 @@ Communication with other implementations Management: [*] Define the gpgme interface - [ ] check Permissions (e.g. csp) for the different envs + [x] check Permissions (e.g. csp) for the different envs [X] agree on license - [ ] tests + [*] tests diff --git a/lang/js/CHECKLIST_build b/lang/js/CHECKLIST_build index 19eb2146..a7c8d08d 100644 --- a/lang/js/CHECKLIST_build +++ b/lang/js/CHECKLIST_build @@ -1,9 +1,3 @@ - Checklist for build/install: browsers' manifests (see README) need allowedextension added, and the path set - -manifest.json/ csp needs adaption - -/dist should be built which is used by the example app. - -csp in manifest.json MUST NOT contain "unsafe-eval" in production! diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js new file mode 100644 index 00000000..7e5e1ffe --- /dev/null +++ b/lang/js/DemoExtension/entry.js @@ -0,0 +1,25 @@ +/* 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+ + * + */ +document.addEventListener('DOMContentLoaded', function() { + chrome.tabs.create({ + url: './uimainui.html' + }); +}); diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js new file mode 100644 index 00000000..b2cb4c23 --- /dev/null +++ b/lang/js/DemoExtension/maindemo.js @@ -0,0 +1,55 @@ +/* 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+ + * + */ + +document.addEventListener('DOMContentLoaded', function() { + Gpgmejs.init().then(function(gpgmejs){ + document.getElementById("buttonencrypt").addEventListener("click", + function(){ + let data = document.getElementById('cleartext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.encrypt(data, keyId).then( + function(answer){ + console.log(answer); + if (answer.data){ + console.log(answer.data); + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.code + ' ' + errormsg.msg); + }); + }); + + document.getElementById("buttondecrypt").addEventListener("click", + function(){ + let data = document.getElementById("ciphertext").value; + gpgmejs.decrypt(data).then( + function(answer){ + console.log(answer); + if (answer.data){ + document.getElementById('answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.code + ' ' + errormsg.msg); + }); + }); + }, + function(error){console.log(error)}); +}); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html new file mode 100644 index 00000000..d85e7a46 --- /dev/null +++ b/lang/js/DemoExtension/mainui.html @@ -0,0 +1,33 @@ + + + + + + + + + +
    +
  • + Text: + +
  • +
  • + Public key ID: + +
  • +
+
+
+
    +
  • + Encrypted armored Text: + +
  • +
+
+
+

Result data:

+ + + diff --git a/lang/js/DemoExtension/manifest.json b/lang/js/DemoExtension/manifest.json new file mode 100644 index 00000000..9e057b35 --- /dev/null +++ b/lang/js/DemoExtension/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + + "name": "gpgme-json with native Messaging", + "description": "A simple demo application", + "version": "0.1", + "content_security_policy": "default-src 'self' filesystem:", + "browser_action": { + "default_icon": "testicon.png", + "default_title": "gpgme.js", + "default_popup": "popup.html" + }, + "permissions": ["nativeMessaging", "activeTab"] +} diff --git a/lang/js/DemoExtension/popup.html b/lang/js/DemoExtension/popup.html new file mode 100644 index 00000000..866b1135 --- /dev/null +++ b/lang/js/DemoExtension/popup.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/lang/js/DemoExtension/testicon.png b/lang/js/DemoExtension/testicon.png new file mode 100644 index 00000000..12c3f5df Binary files /dev/null and b/lang/js/DemoExtension/testicon.png differ diff --git a/lang/js/DemoExtension/ui.css b/lang/js/DemoExtension/ui.css new file mode 100644 index 00000000..9c88698b --- /dev/null +++ b/lang/js/DemoExtension/ui.css @@ -0,0 +1,10 @@ +ul { + list-style-type: none; + padding-left: 0px; +} + +ul li span { + float: left; + width: 120px; + margin-top: 6px; +} diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh new file mode 100755 index 00000000..be7b0584 --- /dev/null +++ b/lang/js/build_extensions.sh @@ -0,0 +1,14 @@ +#/!bin/bash + +npx webpack --config webpack.conf.js +mkdir -p BrowserTestExtension/libs +cp node_modules/chai/chai.js \ + node_modules/mocha/mocha.css \ + node_modules/mocha/mocha.js \ + build/gpgmejs.bundle.js BrowserTestExtension/libs +mkdir -p build/extensions +zip -r build/extensions/browsertest.zip BrowserTestExtension + +mkdir -p DemoExtension/libs +cp build/gpgmejs.bundle.js DemoExtension/libs +zip -r build/extensions/demoextension.zip DemoExtension diff --git a/lang/js/manifest.json b/lang/js/manifest.json deleted file mode 100644 index e5e17aa5..00000000 --- a/lang/js/manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "manifest_version": 2, - - "name": "gpgme-json with native Messaging", - "description": "This should be able to encrypt a text using gpgme-json", - "version": "0.1", - "content_security_policy": "default-src 'self' 'unsafe-eval' filesystem:", - "browser_action": { - "default_icon": "testicon.png", - "default_title": "gpgme.js", - "default_popup": "testapplication_index.html" - }, - "permissions": ["nativeMessaging", "activeTab"] -} diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js new file mode 100644 index 00000000..e18728de --- /dev/null +++ b/lang/js/src/Config.js @@ -0,0 +1,31 @@ +/* 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+ + */ + +export const availableConf = { + api_style: ['gpgme', 'gpgme_openpgpjs'], + null_expire_is_never: [true, false], + unconsidered_params: ['warn','reject', 'ignore'], +}; + +export const defaultConf = { + api_style: 'gpgme', + null_expire_is_never: false, + unconsidered_params: 'reject', +}; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 470eeeec..364bfb46 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -22,6 +22,7 @@ import {createMessage} from './Message' import {GPGME_Key} from './Key' import { isFingerprint, isLongId } from './Helpers'; import { gpgme_error } from './Errors'; +import { Connection } from './Connection'; export class GPGME_Keyring { constructor(connection){ diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 4d242277..9e7a8835 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -36,7 +36,7 @@ export function createMessage(operation){ * ./permittedOperations. * @param {String} operation */ -class GPGME_Message { +export class GPGME_Message { //TODO getter constructor(operation){ diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index 4e5e1ea0..cc2afde1 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -50,18 +50,12 @@ if (!this._GPGME){ this._GpgME = new GpgME(connection, config); } - if (!this._Keyring){ - this._Keyring = new GPGME_Keyring_openpgpmode(connection); + if (!this._keyring){ + this._keyring = new GPGME_Keyring_openpgpmode(connection); } } } - get GpgME(){ - if (this._GpGME){ - return this._GpGME; - } - } - /** * Encrypt Message * Supported: @@ -115,7 +109,7 @@ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } - return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); + return this._GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); } /** Decrypt Message @@ -152,7 +146,7 @@ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } - return this.GpgME.decrypt(message); + return this._GpgME.decrypt(message); // TODO: translate between: // openpgp: // { data:Uint8Array|String, diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 48904316..4de98457 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -22,36 +22,60 @@ import { GpgME } from "./gpgmejs"; import { gpgme_error } from "./Errors"; import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; +import { defaultConf, availableConf } from "./Config"; /** * Initializes a nativeMessaging Connection and returns a GPGMEjs object - * @param {*} conf Configuration. TBD + * @param {Object} config Configuration. See Config.js for available parameters. Still TODO */ -function init( config = { - api_style: 'gpgme', // | gpgme_openpgpjs - null_expire_is_never: true, // Boolean - unconsidered_params: 'warn'//'warn' || 'reject' - }){ - return new Promise(function(resolve, reject){ - let connection = new Connection; - // TODO: Delayed reaction is ugly. We need to listen to the port's - // event listener in isConnected, but this takes some time (<5ms) to - // disconnect if there is no successfull connection. - let delayedreaction = function(){ - if (connection.isConnected === true){ - let gpgme = null; - if (config.api_style && config.api_style === 'gpgme_openpgpjs'){ - resolve( - new GpgME_openpgpmode(connection, config)); - } else { - resolve(new GpgME(connection)); - } +function init(config){ + let _conf = parseconfiguration(config); + if (_conf instanceof Error){ + return Promise.reject(_conf); + } + return new Promise(function(resolve, reject){ + let connection = new Connection; + // TODO: Delayed reaction is ugly. We need to listen to the port's + // event listener in isConnected, but this takes some time (<5ms) to + // disconnect if there is no successfull connection. + let delayedreaction = function(){ + if (connection.isConnected === true){ + if (_conf.api_style && _conf.api_style === 'gpgme_openpgpjs'){ + resolve(new GpgME_openpgpmode(connection, _conf)); } else { - reject(gpgme_error('CONN_NO_CONNECT')); + resolve(new GpgME(connection)); } - }; - setTimeout(delayedreaction, 5); + } else { + reject(gpgme_error('CONN_NO_CONNECT')); + } + }; + setTimeout(delayedreaction, 5); }); +} + +function parseconfiguration(config){ + if (!config){ + return defaultConf; + } + if ( typeof(config) !== 'object'){ + return gpgme_error('PARAM_WRONG'); + }; + let result_config = defaultConf; + let conf_keys = Object.keys(config); + for (let i=0; i < conf_keys; i++){ + if (availableConf.hasOwnProperty(conf_keys[i])){ + let value = config[conf_keys[i]]; + if (availableConf[conf_keys[i]].indexOf(value) < 0){ + return gpgme_error('PARAM_WRONG'); + } else { + result_config[conf_keys[i]] = value; + } + } + else { + return gpgme_error('PARAM_WRONG'); + } + } + return result_config; }; export default { diff --git a/lang/js/test/Helpers.js b/lang/js/test/Helpers.js index 590f9f65..5d8909f9 100644 --- a/lang/js/test/Helpers.js +++ b/lang/js/test/Helpers.js @@ -22,89 +22,76 @@ import { expect } from "../node_modules/chai/chai"; import { gpgme_error} from "../src/Errors"; import { GPGME_Key } from "../src/Key"; import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers" +import { helper_params } from "./inputvalues"; -const helper_params = { - validLongId: '0A0A0A0A0A0A0A0A', - validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), - validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), - 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', - new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], - validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', - invalidLongId: '9A9A7A7A8A9A9A7A7A8A', - invalidFingerprint: [{hello:'World'}], - invalidKeyArray: {curiosity:'uncat'}, - invalidKeyArray_OneBad: [ - new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), - 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', - '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], - invalidErrorCode: 'Please type in all your passwords.' -} - -describe('Error Object handling', function(){ - 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(helper_params.invalidErrorCode); - 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(helper_params.validFingerprint); - expect(test0).to.be.true; - }); - it('isFingerprint(): invalid Fingerprint', function(){ - let test0 = isFingerprint(helper_params.invalidFingerprint); - expect(test0).to.be.false; - }); -}); -describe('Converting to Fingerprint', function(){ - it('Correct Inputs', function(){ - it('Fingerprint string', function(){ - let test0 = toKeyIdArray(helper_params.validFingerprint); - expect(test0).to.be.an('array'); - expect(test0).to.include(helper_params.validFingerprint); +function Helpertest(){ + describe('Error Object handling', function(){ + 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('GPGME_Key', function(){ - expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); - let test0 = toKeyIdArray(helper_params.validGPGME_Key); - expect(test0).to.be.an('array'); - expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); + it('Error Object returns generic code if code is not listed', function(){ + let test0 = gpgme_error(helper_params.invalidErrorCode); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('GENERIC_ERROR'); }); - it('Array of valid inputs', function(){ - let test0 = toKeyIdArray(helper_params.validKeys); - expect(test0).to.be.an('array'); - expect(test0).to.have.lengthOf(helper_params.validKeys.length); + + it('Warnings like PARAM_IGNORED should not return errors', function(){ + let test0 = gpgme_error('PARAM_IGNORED'); + expect(test0).to.be.null; }); }); - describe('Incorrect inputs', function(){ - it('valid Long ID', function(){ - let test0 = toKeyIdArray(helper_params.validLongId); - expect(test0).to.be.empty; + + describe('Fingerprint checking', function(){ + it('isFingerprint(): valid Fingerprint', function(){ + let test0 = isFingerprint(helper_params.validFingerprint); + expect(test0).to.be.true; }); - it('invalidFingerprint', function(){ - let test0 = toKeyIdArray(helper_params.invalidFingerprint); - expect(test0).to.be.empty; + it('isFingerprint(): invalid Fingerprint', function(){ + let test0 = isFingerprint(helper_params.invalidFingerprint); + expect(test0).to.be.false; }); - it('invalidKeyArray', function(){ - let test0 = toKeyIdArray(helper_params.invalidKeyArray); - expect(test0).to.be.empty; + }); + describe('Converting to Fingerprint', function(){ + it('Correct Inputs', function(){ + it('Fingerprint string', function(){ + let test0 = toKeyIdArray(helper_params.validFingerprint); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validFingerprint); + }); + it('GPGME_Key', function(){ + expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); + let test0 = toKeyIdArray(helper_params.validGPGME_Key); + expect(test0).to.be.an('array'); + expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); + }); + it('Array of valid inputs', function(){ + let test0 = toKeyIdArray(helper_params.validKeys); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf(helper_params.validKeys.length); + }); }); - it('Partially invalid array', function(){ - let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); - expect(test0).to.be.an('array'); - expect(test0).to.have.lengthOf( - helper_params.invalidKeyArray_OneBad.length - 1); + describe('Incorrect inputs', function(){ + it('valid Long ID', function(){ + let test0 = toKeyIdArray(helper_params.validLongId); + expect(test0).to.be.empty; + }); + it('invalidFingerprint', function(){ + let test0 = toKeyIdArray(helper_params.invalidFingerprint); + expect(test0).to.be.empty; + }); + it('invalidKeyArray', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray); + expect(test0).to.be.empty; + }); + it('Partially invalid array', function(){ + let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); + expect(test0).to.be.an('array'); + expect(test0).to.have.lengthOf( + helper_params.invalidKeyArray_OneBad.length - 1); + }); }); }); -}); +}; +export default Helpertest; \ No newline at end of file diff --git a/lang/js/test/Message.js b/lang/js/test/Message.js index 454b8ca3..44206fba 100644 --- a/lang/js/test/Message.js +++ b/lang/js/test/Message.js @@ -21,22 +21,23 @@ import { expect } from "../node_modules/chai/chai"; import { GPGME_Message, createMessage } from "../src/Message"; -const message_params = { - invalid_op_action : 'dance', - invalid_op_type : [234, 34, '<>'], -} +import { message_params } from "./inputvalues"; -describe('Message Object', function(){ - describe('incorrect initialization', function(){ - it('non-allowed operation', function(){ - let test0 = createMessage(message_params.invalid_op_action); - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('MSG_WRONG_OP'); - }); - it('wrong parameter type in constructor', function(){ - let test0 = createMessage(message_params.invalid_op_type); - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('PARAM_WRONG'); +function Messagetest(){ + + describe('Message Object', function(){ + describe('incorrect initialization', function(){ + it('non-allowed operation', function(){ + let test0 = createMessage(message_params.invalid_op_action); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('MSG_WRONG_OP'); + }); + it('wrong parameter type in constructor', function(){ + let test0 = createMessage(message_params.invalid_op_type); + expect(test0).to.be.an.instanceof(Error); + expect(test0.code).to.equal('PARAM_WRONG'); + }); }); }); -}); +}; +export default Messagetest; \ No newline at end of file diff --git a/lang/js/test/inputvalues.js b/lang/js/test/inputvalues.js new file mode 100644 index 00000000..a50c8162 --- /dev/null +++ b/lang/js/test/inputvalues.js @@ -0,0 +1,29 @@ + +import {GPGME_Key} from "../src/Key" + +export const helper_params = { + validLongId: '0A0A0A0A0A0A0A0A', + validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), + validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), + 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', + new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], + validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + invalidLongId: '9A9A7A7A8A9A9A7A7A8A', + invalidFingerprint: [{hello:'World'}], + invalidKeyArray: {curiosity:'uncat'}, + invalidKeyArray_OneBad: [ + new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), + 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', + '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], + invalidErrorCode: 'Please type in all your passwords.' +} + +export const message_params = { + invalid_op_action : 'dance', + invalid_op_type : [234, 34, '<>'], +} + +export default { + helper_params: helper_params, + message_params: message_params +} \ No newline at end of file diff --git a/lang/js/test_index.js b/lang/js/test_index.js deleted file mode 100644 index 9119d271..00000000 --- a/lang/js/test_index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* 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+ - * - */ -document.addEventListener('DOMContentLoaded', function() { - chrome.tabs.create({ - url: './ui2.html' - }); -}); diff --git a/lang/js/testapplication.js b/lang/js/testapplication.js deleted file mode 100644 index b2cb4c23..00000000 --- a/lang/js/testapplication.js +++ /dev/null @@ -1,55 +0,0 @@ -/* 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+ - * - */ - -document.addEventListener('DOMContentLoaded', function() { - Gpgmejs.init().then(function(gpgmejs){ - document.getElementById("buttonencrypt").addEventListener("click", - function(){ - let data = document.getElementById('cleartext').value; - let keyId = document.getElementById('pubkey').value; - gpgmejs.encrypt(data, keyId).then( - function(answer){ - console.log(answer); - if (answer.data){ - console.log(answer.data); - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert( errormsg.code + ' ' + errormsg.msg); - }); - }); - - document.getElementById("buttondecrypt").addEventListener("click", - function(){ - let data = document.getElementById("ciphertext").value; - gpgmejs.decrypt(data).then( - function(answer){ - console.log(answer); - if (answer.data){ - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert( errormsg.code + ' ' + errormsg.msg); - }); - }); - }, - function(error){console.log(error)}); -}); diff --git a/lang/js/testapplication_index.html b/lang/js/testapplication_index.html deleted file mode 100644 index 866b1135..00000000 --- a/lang/js/testapplication_index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/lang/js/testicon.png b/lang/js/testicon.png deleted file mode 100644 index 12c3f5df..00000000 Binary files a/lang/js/testicon.png and /dev/null differ diff --git a/lang/js/ui.css b/lang/js/ui.css deleted file mode 100644 index 9c88698b..00000000 --- a/lang/js/ui.css +++ /dev/null @@ -1,10 +0,0 @@ -ul { - list-style-type: none; - padding-left: 0px; -} - -ul li span { - float: left; - width: 120px; - margin-top: 6px; -} diff --git a/lang/js/ui2.html b/lang/js/ui2.html deleted file mode 100644 index 8d0abd97..00000000 --- a/lang/js/ui2.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -
    -
  • - Text: - -
  • -
  • - Public key ID: - -
  • -
-
-
-
    -
  • - Encrypted armored Text: - -
  • -
-
-
-

Result data:

- - - diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js index 7a5392ee..b2ad9098 100644 --- a/lang/js/webpack.conf.js +++ b/lang/js/webpack.conf.js @@ -26,7 +26,7 @@ module.exports = { // mode: 'development', mode: 'production', output: { - path: path.resolve(__dirname, 'dist'), + path: path.resolve(__dirname, 'build'), filename: 'gpgmejs.bundle.js', libraryTarget: 'var', libraryExport: 'default', -- cgit v1.2.3 From f45b926816340d3cca37f013a9eb1b1d9cdb0cfe Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 26 Apr 2018 17:59:40 +0200 Subject: js: fixed wrong paths in DemoExtension -- * Some forgotten internal links after the move to a subdir and cleaning --- lang/js/DemoExtension/entry.js | 2 +- lang/js/DemoExtension/mainui.html | 2 +- lang/js/DemoExtension/popup.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js index 7e5e1ffe..62583421 100644 --- a/lang/js/DemoExtension/entry.js +++ b/lang/js/DemoExtension/entry.js @@ -20,6 +20,6 @@ */ document.addEventListener('DOMContentLoaded', function() { chrome.tabs.create({ - url: './uimainui.html' + url: './mainui.html' }); }); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html index d85e7a46..76b8a221 100644 --- a/lang/js/DemoExtension/mainui.html +++ b/lang/js/DemoExtension/mainui.html @@ -3,7 +3,7 @@ - + diff --git a/lang/js/DemoExtension/popup.html b/lang/js/DemoExtension/popup.html index 866b1135..50070311 100644 --- a/lang/js/DemoExtension/popup.html +++ b/lang/js/DemoExtension/popup.html @@ -2,7 +2,7 @@ - + -- cgit v1.2.3 From eb7129f3196ae4f0807ceba0c1fc9e818ea6cd22 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Apr 2018 10:21:13 +0200 Subject: js: fixed empty operation setter in Message -- * src/Message.js Messages failed because they were not assigned operations --- lang/js/src/Message.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 9e7a8835..95d043ba 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -44,9 +44,16 @@ export class GPGME_Message { } set operation (op){ - - + if (typeof(op) === "string"){ + if (!this._msg){ + this._msg = {}; + } + if (!this._msg.op & permittedOperations.hasOwnProperty(op)){ + this._msg.op = op; + } + } } + get operation(){ return this._msg.op; } -- cgit v1.2.3 From fda7b13f1b673962ce34b6f429158a7eb9cef47b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Apr 2018 20:03:09 +0200 Subject: js: more testing -- * Tests: initialization of the two modes, encryption * gpgme.js: reintroduced message check before calling Connection.post() * gpgmejs_openpgp.js: Fixed openpgp mode not passing keys * index.js: fixed some confusion in parseconfig() * Inserted some TODO stubs for missing error handling --- lang/js/BrowserTestExtension/tests/encryptTest.js | 71 ++++++++++++++++ lang/js/BrowserTestExtension/tests/inputvalues.js | 6 +- lang/js/BrowserTestExtension/tests/startup.js | 67 ++++++++++------ lang/js/build_extensions.sh | 1 + lang/js/src/Connection.js | 6 +- lang/js/src/Key.js | 23 +++--- lang/js/src/Keyring.js | 4 + lang/js/src/Message.js | 66 +++++++++++++-- lang/js/src/gpgmejs.js | 56 +++++++------ lang/js/src/gpgmejs_openpgpjs.js | 7 +- lang/js/src/index.js | 23 +++--- lang/js/src/permittedOperations.js | 98 +++++++++++++++++------ lang/js/test/Helpers.js | 2 +- lang/js/test/Message.js | 62 ++++++++++++-- lang/js/test/index.js | 28 +++++++ lang/js/test/inputvalues.js | 10 +++ 16 files changed, 416 insertions(+), 114 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/encryptTest.js create mode 100644 lang/js/test/index.js diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js new file mode 100644 index 00000000..e6000003 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -0,0 +1,71 @@ +/* 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', function(){ + + it('Successfull encrypt', 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'); + done(); + }, function(err){ + expect(err).to.be.undefined; + done(); + }); + }); + }); + + it('Sending encryption without keys fails', function(){ + let prm = Gpgmejs.init(); + prm.then(function(context){ + context.encrypt( + inputvalues.encrypt.good.data, + null).then(function(answer){ + expect(answer).to.be.undefined; + done(); + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done() + }); + }); + }); + + it('Sending encryption without data fails', function(){ + let prm = Gpgmejs.init(); + prm.then(function(context){ + context.encrypt( + null,inputvalues.encrypt.good.keyid).then(function(answer){ + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); + }); + }); + }); + + // TODO check different valid parameter +}); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 47600c84..1761c82f 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -22,7 +22,11 @@ var inputvalues = { encrypt: { good:{ data : 'Hello World.', - keyid : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' + fingerprint : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' } + }, + init: { + invalid_startups: [{all_passwords: true}, 'openpgpmode', {api_style:"frankenstein"}] } + }; diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 14d12c0a..a5614a83 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -20,32 +20,51 @@ describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ - Gpgmejs.init().then( + let prm = Gpgmejs.init(); + prm.then( function(context){ - expect(context.connection).to.not.be.undefined; - expect(context).to.be.an('object'); - expect(context.connection).to.be.an('object'); - expect(context.Keyring).to.be.undefined; - expect(context.encrypt).to.be.a('function'); - expect(context.decrypt).to.be.a('function'); - done(); - }, function(err){ - done(err); + expect(context.connection).to.not.be.undefined; + expect(context).to.be.an('object'); + expect(context.connection).to.be.an('object'); + expect(context.Keyring).to.be.undefined; + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(errorr){ + expect(error).to.be.undefined; + done(); }); }); - it('Starting an openpgp mode GPGME instance', function(done){ - Gpgmejs.init({api_style:"gpgme_openpgpjs"}).then( - function(context){ - console.log(context); - done(); - // expect(context).to.be.an('object'); - // expect(context.connection).to.be.undefined; - // expect(context.Keyring).to.be.an('object'); - // expect(context.encrypt).to.be.a('function'); - // expect(context.decrypt).to.be.a('function'); - // done(); - }, function(err){ - done(err); +}); +describe('openpgp mode', function(){ + it('startup of openpgp mode returns the correct parameters', function(done){ + let prm = Gpgmejs.init({api_style:"gpgme_openpgpjs"}); + prm.then(function(context){ + expect(context).to.be.an('object'); + expect(context.connection).to.be.undefined; + expect(context.Keyring).to.be.an('object'); + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }, function(error){ + expect(error).to.be.undefined; + done(); }); }); - }); +}); + +describe('GPGME does not start with invalid parameters', function(){ + for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ + it('Parameter '+ i, function(done){ + let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); + prm.then(function(context){ + expect(context).to.be.undefined; + done(); + }, function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('PARAM_WRONG'); + done(); + }); + }) + } +}); \ No newline at end of file diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh index be7b0584..b99a362c 100755 --- a/lang/js/build_extensions.sh +++ b/lang/js/build_extensions.sh @@ -6,6 +6,7 @@ cp node_modules/chai/chai.js \ node_modules/mocha/mocha.css \ node_modules/mocha/mocha.js \ build/gpgmejs.bundle.js BrowserTestExtension/libs +rm -rf build/extensions mkdir -p build/extensions zip -r build/extensions/browsertest.zip BrowserTestExtension diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index a10f9d9a..a198bdc6 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -100,9 +100,7 @@ export class Connection{ reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ me._connection.onMessage.removeListener(listener) - reject( - {code: 'GNUPG_ERROR', - msg: msg.msg} ); + reject(gpgme_error('GNUPG_ERROR', msg.msg)); } else { let answer_result = answer.add(msg); if (answer_result !== true){ @@ -129,6 +127,8 @@ export class Connection{ }, 5000); }]).then(function(result){ return result; + }, function(error){ + return error; }); } }); diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 0b44b245..30449d63 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -51,10 +51,7 @@ export class GPGME_Key { * hasSecret returns true if a secret subkey is included in this Key */ get hasSecret(){ - checkKey(this._fingerprint, 'secret').then( function(result){ - return Promise.resolve(result); - }); - + return checkKey(this._fingerprint, 'secret'); } get isRevoked(){ @@ -130,6 +127,8 @@ export class GPGME_Key { } } return Promise.resolve(resultset); + }, function(error){ + //TODO checkKey fails }); } @@ -175,8 +174,7 @@ export class GPGME_Key { */ function checkKey(fingerprint, property){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - if (!property || - permittedOperations[keyinfo].indexOf(property) < 0){ + if (!property || !permittedOperations[keyinfo].hasOwnProperty(property)){ return Promise.reject(gpgme_error('PARAM_WRONG')); } return new Promise(function(resolve, reject){ @@ -188,19 +186,20 @@ function checkKey(fingerprint, property){ reject(gpgme_error('PARAM_WRONG')); } msg.setParameter('fingerprint', this.fingerprint); - return (this.connection.post(msg)).then(function(result){ - if (result.hasOwnProperty(property)){ + return (this.connection.post(msg)).then(function(result, error){ + if (error){ + reject(gpgme_error('GNUPG_ERROR',error.msg)); + } else if (result.hasOwnProperty(property)){ resolve(result[property]); } else if (property == 'secret'){ - // TBD property undefined means "not true" in case of secret? - resolve(false); + // TBD property undefined means "not true" in case of secret? + resolve(false); } else { reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } }, function(error){ - reject({code: 'GNUPG_ERROR', - msg: error.msg}); + //TODO error handling }); }); }; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 364bfb46..d1f4122e 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -78,6 +78,8 @@ export class GPGME_Keyring { } } return Promise.resolve(resultset); + }, function(error){ + //TODO error handling }); } @@ -151,6 +153,8 @@ export class GPGME_Keyring { } } return Promise.resolve(resultset); + }, function(error){ + //TODO error handling }); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 95d043ba..c42480f2 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -73,11 +73,60 @@ export class GPGME_Message { if (!po){ return gpgme_error('MSG_WRONG_OP'); } - if (po.required.indexOf(param) >= 0 || po.optional.indexOf(param) >= 0){ - this._msg[param] = value; - return true; + let poparam = null; + if (po.required.hasOwnProperty(param)){ + poparam = po.required[param]; + } else if (po.optional.hasOwnProperty(param)){ + poparam = po.optional[param]; + } else { + return gpgme_error('PARAM_WRONG'); } - return gpgme_error('PARAM_WRONG'); + let checktype = function(val){ + switch(typeof(val)){ + case 'string': + case 'number': + case 'boolean': + if (poparam.allowed.indexOf(typeof(val)) >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + break; + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); + } + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + return true; + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + } else { + return gpgme_error('PARAM_WRONG'); + } + break; + default: + return gpgme_error('PARAM_WRONG'); + } + }; + let typechecked = checktype(value); + if (typechecked !== true){ + return typechecked; + } + if (poparam.hasOwnProperty('allowed_data')){ + if (poparam.allowed_data.indexOf(value) < 0){ + return gpgme_error('PARAM_WRONG'); + } + } + this._msg[param] = value; + return true; } /** @@ -89,11 +138,12 @@ export class GPGME_Message { if (!this._msg.op){ return false; } - let reqParams = permittedOperations[this._msg.op].required; + let reqParams = Object.keys( + permittedOperations[this._msg.op].required); + let msg_params = Object.keys(this._msg); for (let i=0; i < reqParams.length; i++){ - - if (!this._msg.hasOwnProperty(reqParams[i])){ - console.log(reqParams[i] + 'missing'); + if (msg_params.indexOf(reqParams[i]) < 0){ + console.log(reqParams[i] + ' missing'); return false; } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 2ddf2964..9475b2b0 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -33,25 +33,24 @@ export class GpgME { this.connection = connection; } - set connection(connection){ + set connection(conn){ if (this._connection instanceof Connection){ gpgme_error('CONN_ALREADY_CONNECTED'); - } - if (connection instanceof Connection){ - this._connection = connection; + } else if (conn instanceof Connection){ + this._connection = conn; } else { gpgme_error('PARAM_WRONG'); } } get connection(){ - if (this._connection instanceof Connection){ - if (this._connection.isConnected){ + if (this._connection){ + if (this._connection.isConnected === true){ return this._connection; } - return undefined; //TODO: connection was lost! + return undefined; } - return undefined; //TODO: no connection there + return undefined; } set Keyring(keyring){ @@ -85,8 +84,11 @@ export class GpgME { putData(msg, data); if (wildcard === true){msg.setParameter('throw-keyids', true); }; - - return (this.connection.post(msg)); + if (msg.isComplete === true){ + return this.connection.post(msg); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } } /** @@ -133,20 +135,24 @@ export class GpgME { msg.setParameter('delete_force', true); // TBD } - this.connection.post(msg).then(function(success){ - // TODO: it seems that there is always errors coming back: - }, function(error){ - switch (error.msg){ - case 'ERR_NO_ERROR': - return Promise.resolve('okay'); //TBD - default: - return Promise.reject(gpgme_error('TODO') ); // - // INV_VALUE, - // GPG_ERR_NO_PUBKEY, - // GPG_ERR_AMBIGUOUS_NAME, - // GPG_ERR_CONFLICT - } - }); + if (msg.isComplete === true){ + this.connection.post(msg).then(function(success){ + // TODO: it seems that there is always errors coming back: + }, function(error){ + switch (error.msg){ + case 'ERR_NO_ERROR': + return Promise.resolve('okay'); //TBD + default: + return Promise.reject(gpgme_error('TODO') ); // + // INV_VALUE, + // GPG_ERR_NO_PUBKEY, + // GPG_ERR_AMBIGUOUS_NAME, + // GPG_ERR_CONFLICT + } + }); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } } } @@ -162,7 +168,7 @@ function putData(message, data){ return gpgme_error('PARAM_WRONG'); } if (!data){ - message.setParameter('data', ''); + return gpgme_error('PARAM_WRONG'); } else if (data instanceof Uint8Array){ let decoder = new TextDecoder('utf8'); message.setParameter('base64', true); diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index cc2afde1..c80d5a86 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -109,7 +109,7 @@ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } - return this._GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); + return this._GpgME.encrypt(data, translateKeys(publicKeys), wildcard); } /** Decrypt Message @@ -201,6 +201,8 @@ class GPGME_Keyring_openpgpmode { // TODO: Can there be several default keys? return gpgme_error('TODO'); } + }, function(error){ + //TODO }); } @@ -264,6 +266,9 @@ class GPGME_Key_openpgpmode { * creates GPGME_Key_openpgpmode from GPGME_Keys */ function translateKeys(input){ + if (!input){ + return null; + } if (!Array.isArray(input)){ input = [input]; } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 4de98457..90fe99e3 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -53,18 +53,17 @@ function init(config){ }); } -function parseconfiguration(config){ - if (!config){ - return defaultConf; - } - if ( typeof(config) !== 'object'){ +function parseconfiguration(rawconfig = {}){ + if ( typeof(rawconfig) !== 'object'){ return gpgme_error('PARAM_WRONG'); }; - let result_config = defaultConf; - let conf_keys = Object.keys(config); - for (let i=0; i < conf_keys; i++){ + let result_config = {}; + let conf_keys = Object.keys(rawconfig); + + for (let i=0; i < conf_keys.length; i++){ + if (availableConf.hasOwnProperty(conf_keys[i])){ - let value = config[conf_keys[i]]; + let value = rawconfig[conf_keys[i]]; if (availableConf[conf_keys[i]].indexOf(value) < 0){ return gpgme_error('PARAM_WRONG'); } else { @@ -75,6 +74,12 @@ function parseconfiguration(config){ return gpgme_error('PARAM_WRONG'); } } + let default_keys = Object.keys(defaultConf); + for (let j=0; j < default_keys.length; j++){ + if (!result_config.hasOwnProperty(default_keys[j])){ + result_config[default_keys[j]] = defaultConf[default_keys[j]]; + } + } return result_config; }; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 79e74223..274e037e 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -21,9 +21,16 @@ /** * Definition of the possible interactions with gpgme-json. * operation: - required: Array - optional: Array - pinentry: Boolean If a pinentry dialog is expected, and a timeout of + required: Array + name The name of the property + allowed: Array of allowed types. Currently accepted values: + ['number', 'string', 'boolean', 'Uint8Array'] + array_allowed: Boolean. If the value can be an array of the above + allowed_data: If present, restricts to the given value + optional: Array + see 'required', with these parameters not being mandatory for a + complete message + pinentry: boolean If a pinentry dialog is expected, and a timeout of 5000 ms would be too short answer: type: . + * SPDX-License-Identifier: LGPL-2.1+ + */ +import { Helpertest } from "./Helpers"; +import { Messagetest } from "./Message"; + +/** + * Unit tests. + */ + +Helpertest(); +Messagetest(); diff --git a/lang/js/test/inputvalues.js b/lang/js/test/inputvalues.js index a50c8162..f6cd75aa 100644 --- a/lang/js/test/inputvalues.js +++ b/lang/js/test/inputvalues.js @@ -8,6 +8,8 @@ export const helper_params = { 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], invalidLongId: '9A9A7A7A8A9A9A7A7A8A', invalidFingerprint: [{hello:'World'}], invalidKeyArray: {curiosity:'uncat'}, @@ -21,8 +23,16 @@ export const helper_params = { export const message_params = { invalid_op_action : 'dance', invalid_op_type : [234, 34, '<>'], + 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', new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] + } } + export default { helper_params: helper_params, message_params: message_params -- cgit v1.2.3 From 6f67814eb45725bc7f3736a2638bad0a7470f17a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 3 May 2018 14:12:10 +0200 Subject: js: changed Key class stub -- * src/Key.js: A Key object cannot offer more than basic functionality outside a connection, so it now requires a connection to be present. --- lang/js/src/Key.js | 124 ++++++++++++++++++++++++--------------- lang/js/src/Keyring.js | 3 +- lang/js/src/gpgmejs_openpgpjs.js | 26 +++++--- lang/js/src/index.js | 7 ++- lang/js/test/inputvalues.js | 18 +++--- 5 files changed, 110 insertions(+), 68 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 30449d63..1e0d3195 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -30,11 +30,42 @@ import { isFingerprint } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; +import { Connection } from './Connection'; + + +export function createKey(fingerprint, parent){ + if (!isFingerprint(fingerprint)){ + return gpgme_error('KEY_INVALID'); + } + if ( parent instanceof Connection){ + return new GPGME_Key(fingerprint, parent); + } else if ( parent.hasOwnProperty('connection') && + parent.connection instanceof Connection){ + return new GPGME_Key(fingerprint, parent.connection); + } +} export class GPGME_Key { - constructor(fingerprint){ + constructor(fingerprint, connection){ this.fingerprint = fingerprint; + this.connection = connection; + } + + set connection(conn){ + if (this._connection instanceof Connection) { + gpgme_error('CONN_ALREADY_CONNECTED'); + } else if (conn instanceof Connection ) { + this._connection = conn; + } + } + + get connection(){ + if (!this._connection instanceof Connection){ + return gpgme_error('CONN_NO_CONNECT'); + } else { + return this._connection; + } } set fingerprint(fpr){ @@ -51,55 +82,56 @@ export class GPGME_Key { * hasSecret returns true if a secret subkey is included in this Key */ get hasSecret(){ - return checkKey(this._fingerprint, 'secret'); + return this.checkKey('secret'); } get isRevoked(){ - return checkKey(this._fingerprint, 'revoked'); + return this.checkKey('revoked'); } get isExpired(){ - return checkKey(this._fingerprint, 'expired'); + return this.checkKey('expired'); } get isDisabled(){ - return checkKey(this._fingerprint, 'disabled'); + return this.checkKey('disabled'); } get isInvalid(){ - return checkKey(this._fingerprint, 'invalid'); + return this.checkKey('invalid'); } get canEncrypt(){ - return checkKey(this._fingerprint, 'can_encrypt'); + return this.checkKey('can_encrypt'); } get canSign(){ - return checkKey(this._fingerprint, 'can_sign'); + return this.checkKey('can_sign'); } get canCertify(){ - return checkKey(this._fingerprint, 'can_certify'); + return this.checkKey('can_certify'); } get canAuthenticate(){ - return checkKey(this._fingerprint, 'can_authenticate'); + return this.checkKey('can_authenticate'); } get isQualified(){ - return checkKey(this._fingerprint, 'is_qualified'); + return this.checkKey('is_qualified'); } get armored(){ - let me = this; - return new Promise(function(resolve, reject){ - let conn = new Connection(); - conn.setFlag('armor', true); - conn.post('export',{'fpr': me._fingerprint}); + let msg = createMessage ('export_key'); + msg.setParameter('armor', true); + if (msg instanceof Error){ + return gpgme_error('INVALID_KEY'); + } + 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 - // TODO openpgpjs also returns secKey if private = true? } /** @@ -114,21 +146,21 @@ export class GPGME_Key { * @returns {Array} */ get subkeys(){ - return checkKey(this._fingerprint, 'subkeys').then(function(result){ + return this.checkKey('subkeys').then(function(result){ // TBD expecting a list of fingerprints if (!Array.isArray(result)){ result = [result]; } let resultset = []; for (let i=0; i < result.length; i++){ - let subkey = new GPGME_Key(result[i]); + let subkey = new GPGME_Key(result[i], this.connection); if (subkey instanceof GPGME_Key){ resultset.push(subkey); } } return Promise.resolve(resultset); }, function(error){ - //TODO checkKey fails + //TODO this.checkKey fails }); } @@ -137,7 +169,7 @@ export class GPGME_Key { * @returns {Date|null} TBD */ get timestamp(){ - return checkKey(this._fingerprint, 'timestamp'); + return this.checkKey('timestamp'); //TODO GPGME: -1 if the timestamp is invalid, and 0 if it is not available. } @@ -146,7 +178,7 @@ export class GPGME_Key { * @returns {Date|null} TBD */ get expires(){ - return checkKey(this._fingerprint, 'expires'); + return this.checkKey('expires'); // TODO convert to Date; check for 0 } @@ -155,51 +187,47 @@ export class GPGME_Key { * @returns {String|Array} The user ids associated with this key */ get userIds(){ - return checkKey(this._fingerprint, 'uids'); + return this.checkKey('uids'); } /** * @returns {String} The public key algorithm supported by this subkey */ get pubkey_algo(){ - return checkKey(this._fingerprint, 'pubkey_algo'); + return this.checkKey('pubkey_algo'); } -}; -/** - * generic function to query gnupg information on a key. - * @param {*} fingerprint The identifier of the Keyring - * @param {*} property The gpgme-json property to check - * - */ -function checkKey(fingerprint, property){ - return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - if (!property || !permittedOperations[keyinfo].hasOwnProperty(property)){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - return new Promise(function(resolve, reject){ - if (!isFingerprint(fingerprint)){ - reject(gpgme_error('KEY_INVALID')); + /** + * 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){ + 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){ - reject(gpgme_error('PARAM_WRONG')); + return gpgme_error('PARAM_WRONG'); } msg.setParameter('fingerprint', this.fingerprint); - return (this.connection.post(msg)).then(function(result, error){ + this.connection.post(msg).then(function(result, error){ if (error){ - reject(gpgme_error('GNUPG_ERROR',error.msg)); + return gpgme_error('GNUPG_ERROR',error.msg); } else if (result.hasOwnProperty(property)){ - resolve(result[property]); + return result[property]; } else if (property == 'secret'){ - // TBD property undefined means "not true" in case of secret? - resolve(false); + // TBD property undefined means "not true" in case of secret? + return false; } else { - reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); + return gpgme_error('CONN_UNEXPECTED_ANSWER'); } }, function(error){ - //TODO error handling + return gpgme_error('GENERIC_ERROR'); }); - }); + } }; \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d1f4122e..2cf87c24 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -61,6 +61,7 @@ export class GPGME_Keyring { if (include_secret){ msg.setParameter('with-secret', true); } + let me = this; this.connection.post(msg).then(function(result){ let fpr_list = []; @@ -72,7 +73,7 @@ export class GPGME_Keyring { fpr_list = result.keys; } for (let i=0; i < fpr_list.length; i++){ - let newKey = new GPGME_Key(fpr_list[i]); + let newKey = new GPGME_Key(fpr_list[i], me._connection); if (newKey instanceof GPGME_Key){ resultset.push(newKey); } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index c80d5a86..b233f0fa 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -26,9 +26,10 @@ import { GpgME } from "./gpgmejs"; import {GPGME_Keyring} from "./Keyring" - import { GPGME_Key } from "./Key"; + import { GPGME_Key, createKey } from "./Key"; import { isFingerprint } from "./Helpers" import { gpgme_error } from "./Errors" +import { Connection } from "./Connection"; export class GpgME_openpgpmode { @@ -47,7 +48,7 @@ initGpgME(connection, config = {}){ if (connection && typeof(config) ==='object'){ this._config = config; - if (!this._GPGME){ + if (!this._GpgME){ this._GpgME = new GpgME(connection, config); } if (!this._keyring){ @@ -223,7 +224,7 @@ class GPGME_Keyring_openpgpmode { if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ return Promise.reject(gpgme_error('PARAM_WRONG')); } - let key_to_delete = new GPGME_Key(key.fingerprint); + let key_to_delete = createKey(key.fingerprint, this._gpgme_keyring_GpgME); return key_to_delete.deleteKey(key.secret); } } @@ -233,15 +234,22 @@ class GPGME_Keyring_openpgpmode { * Offers the Key information as the openpgpmode wants */ class GPGME_Key_openpgpmode { - constructor(value){ - this.init = value; + constructor(value, connection){ + this.init(value, connection); } - set init (value){ + /** + * Can be either constructed using an existing GPGME_Key, or a fingerprint + * and a connection + * @param {String|GPGME_Key} value + * @param {Connection} connection + */ + init (value, connection){ if (!this._GPGME_Key && value instanceof GPGME_Key){ this._GPGME_Key = value; - } else if (!this._GPGME_Key && isFingerprint(value)){ - this._GPGME_Key = new GPGME_Key(value); + } else if (!this._GPGME_Key && isFingerprint(value) && + connection instanceof Connection){ + this._GPGME_Key = createKey(value, connection); } } @@ -264,6 +272,8 @@ class GPGME_Key_openpgpmode { /** * creates GPGME_Key_openpgpmode from GPGME_Keys + * @param {GPGME_Key|Array} input keys + * @returns {Array} */ function translateKeys(input){ if (!input){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 90fe99e3..fc406c66 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -36,9 +36,12 @@ function init(config){ return new Promise(function(resolve, reject){ let connection = new Connection; // TODO: Delayed reaction is ugly. We need to listen to the port's - // event listener in isConnected, but this takes some time (<5ms) to - // disconnect if there is no successfull connection. + // event listener in isConnected, but in some cases this takes some + // time (<5ms) to disconnect if there is no successfull connection. let delayedreaction = function(){ + if (connection === undefined) { + reject(gpgme_error('CONN_NO_CONNECT')); + } if (connection.isConnected === true){ if (_conf.api_style && _conf.api_style === 'gpgme_openpgpjs'){ resolve(new GpgME_openpgpmode(connection, _conf)); diff --git a/lang/js/test/inputvalues.js b/lang/js/test/inputvalues.js index f6cd75aa..7752b82f 100644 --- a/lang/js/test/inputvalues.js +++ b/lang/js/test/inputvalues.js @@ -1,12 +1,10 @@ - -import {GPGME_Key} from "../src/Key" +import { gpgme_error } from "../src/Errors"; export const helper_params = { validLongId: '0A0A0A0A0A0A0A0A', - validGPGME_Key: new GPGME_Key('ADDBC303B6D31026F5EB4591A27EABDF283121BB'), - validKeys: [new GPGME_Key('A1E3BC45BDC8E87B74F4392D53B151A1368E50F3'), + validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', - new GPGME_Key('EE17AEE730F88F1DE7713C54BBE0A4FF7851650A')], + 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], @@ -14,10 +12,10 @@ export const helper_params = { invalidFingerprint: [{hello:'World'}], invalidKeyArray: {curiosity:'uncat'}, invalidKeyArray_OneBad: [ - new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), + '12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], - invalidErrorCode: 'Please type in all your passwords.' + invalidErrorCode: 'Please type in all your passwords.', } export const message_params = { @@ -28,10 +26,12 @@ export const message_params = { valid_op: 'encrypt', invalid_param_names: [22,'dance', {}], validparam_name_0: 'mime', - invalid_values_0: [2134, 'All your passwords', new GPGME_Key('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] + invalid_values_0: [2134, 'All your passwords', gpgme_error('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] } } - +export const whatever_params = { + four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'] +} export default { helper_params: helper_params, -- cgit v1.2.3 From c755287ba845c4cbbf1d50e5aafecb2e687c7ac9 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 3 May 2018 18:03:22 +0200 Subject: js: Added browser testing for unit tests -- * Added unittests to be run inside a Browser. To be able to access the non-exposed functions and classes, a testing bundle will be created, containing the tests (unittests.js) and the items to be tested. * src/Helpelpers, src/Key, src/Keyring: fixed some errors found during testing. --- lang/js/BrowserTestExtension/browsertest.html | 2 + lang/js/BrowserTestExtension/runbrowsertest.js | 1 + lang/js/BrowserTestExtension/tests/encryptTest.js | 14 +- lang/js/README_testing | 14 + lang/js/build_extensions.sh | 4 +- lang/js/src/Helpers.js | 4 +- lang/js/src/Key.js | 11 +- lang/js/src/Keyring.js | 2 +- lang/js/unittest_inputvalues.js | 45 +++ lang/js/unittests.js | 321 ++++++++++++++++++++++ lang/js/webpack.conf_unittests.js | 34 +++ 11 files changed, 436 insertions(+), 16 deletions(-) create mode 100644 lang/js/README_testing create mode 100644 lang/js/unittest_inputvalues.js create mode 100644 lang/js/unittests.js create mode 100644 lang/js/webpack.conf_unittests.js diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index d2c6396f..ce037a11 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -14,9 +14,11 @@ + + diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js index 39bc3fb9..308c716d 100644 --- a/lang/js/BrowserTestExtension/runbrowsertest.js +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -19,3 +19,4 @@ */ mocha.run(); +Gpgmejs_test.unittests(); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index e6000003..2178efac 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -18,8 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ */ describe('Encryption', function(){ - - it('Successfull encrypt', function(done){ + it('Successfull encrypt', function(){ let prm = Gpgmejs.init(); prm.then(function(context){ context.encrypt( @@ -29,10 +28,6 @@ describe('Encryption', function(){ expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); - done(); - }, function(err){ - expect(err).to.be.undefined; - done(); }); }); }); @@ -44,11 +39,10 @@ describe('Encryption', function(){ inputvalues.encrypt.good.data, null).then(function(answer){ expect(answer).to.be.undefined; - done(); }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); - done() + //TODO: MSG_INCOMPLETE desired, GNUPG_ERROR coming }); }); }); @@ -61,11 +55,9 @@ describe('Encryption', function(){ expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('MSG_INCOMPLETE'); - done(); + expect(error.code).to.equal('PARAM_WRONG'); }); }); }); - // TODO check different valid parameter }); diff --git a/lang/js/README_testing b/lang/js/README_testing new file mode 100644 index 00000000..b61ca1a6 --- /dev/null +++ b/lang/js/README_testing @@ -0,0 +1,14 @@ +Test extension: + +The test extension contains tests with mocha and chai. It will be packed as an +extra extension (see build_extension.sh). + +Tests from BrowserTestExtension/tests will be run against the gpgmejs.bundle.js +itself. They aim to test the outward facing functionality and API. + +Unittests as defined in ./unittests.js will be bundled in +gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, +which mostly are not exported. + +The BrowserExtension can be installed the same way as the DemoExtension +(see README). \ No newline at end of file diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh index b99a362c..91d5479b 100755 --- a/lang/js/build_extensions.sh +++ b/lang/js/build_extensions.sh @@ -1,11 +1,13 @@ #/!bin/bash npx webpack --config webpack.conf.js +npx webpack --config webpack.conf_unittests.js mkdir -p BrowserTestExtension/libs cp node_modules/chai/chai.js \ node_modules/mocha/mocha.css \ node_modules/mocha/mocha.js \ - build/gpgmejs.bundle.js BrowserTestExtension/libs + build/gpgmejs.bundle.js \ + build/gpgmejs_unittests.bundle.js BrowserTestExtension/libs rm -rf build/extensions mkdir -p build/extensions zip -r build/extensions/browsertest.zip BrowserTestExtension diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 9a69f851..ea056fff 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -91,9 +91,9 @@ export function isFingerprint(string){ return hextest(string, 40); }; /** - * check if the input is a valid Hex string with a length of 16 + * TODO no usage; check if the input is a valid Hex string with a length of 16 */ -export function isLongId(string){ +function isLongId(string){ return hextest(string, 16); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 1e0d3195..6d3cf17d 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -61,6 +61,9 @@ export class GPGME_Key { } get connection(){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } if (!this._connection instanceof Connection){ return gpgme_error('CONN_NO_CONNECT'); } else { @@ -75,6 +78,9 @@ export class GPGME_Key { } get fingerprint(){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } return this._fingerprint; } @@ -125,7 +131,7 @@ export class GPGME_Key { let msg = createMessage ('export_key'); msg.setParameter('armor', true); if (msg instanceof Error){ - return gpgme_error('INVALID_KEY'); + return gpgme_error('KEY_INVALID'); } this.connection.post(msg).then(function(result){ return result.data; @@ -203,6 +209,9 @@ export class GPGME_Key { * TODO: check if Promise.then(return) */ checkKey(property){ + if (!this._fingerprint){ + return gpgme_error('KEY_INVALID'); + } return gpgme_error('NOT_YET_IMPLEMENTED'); // TODO: async is not what is to be ecpected from Key information :( if (!property || typeof(property) !== 'string' || diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 2cf87c24..4596035a 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -20,7 +20,7 @@ import {createMessage} from './Message' import {GPGME_Key} from './Key' -import { isFingerprint, isLongId } from './Helpers'; +import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js new file mode 100644 index 00000000..3450afd2 --- /dev/null +++ b/lang/js/unittest_inputvalues.js @@ -0,0 +1,45 @@ +import {Connection} from "./src/Connection"; +import {createKey} from "./src/Key"; + +let conn = new Connection; + +export const helper_params = { + validLongId: '0A0A0A0A0A0A0A0A', + validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', + createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn), + 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], + validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', + '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], + invalidLongId: '9A9A7A7A8A9A9A7A7A8A', + invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], + invalidKeyArray: {curiosity:'uncat'}, + invalidKeyArray_OneBad: [ + createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), + 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', + '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], + invalidErrorCode: 'Please type in all your passwords.', + validGPGME_Key: createKey('ADDBC303B6D31026F5EB4591A27EABDF283121BB', conn), + valid_openpgplike: { primaryKey: { + getFingerprint: function(){ + return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} + } + } +} + +export const message_params = { + invalid_op_action : 'dance', + invalid_op_type : [234, 34, '<>'], + 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}}}}'] +} diff --git a/lang/js/unittests.js b/lang/js/unittests.js new file mode 100644 index 00000000..0a1b4b48 --- /dev/null +++ b/lang/js/unittests.js @@ -0,0 +1,321 @@ +/* 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 { 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"; + +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; + let delayed = function(){ + expect(conn0.isConnected).to.be.true; + expect(conn0.connect).to.be.a('function'); + expect(conn0.disconnect).to.be.a('function'); + expect(conn0.post).to.be.a('function'); + done(); + }; + setTimeout(delayed, 5); + + }); + + it('Disconnecting', function(done) { + let conn0 = new Connection; + let delayed = function(){ + conn0.disconnect(); // TODO fails! + expect(conn0.isConnected).to.be.false; + done(); + }; + setTimeout(delayed, 5); + }); + + // broken + // it('Connect info still only available after a delay', function(done){ + // // if false, all delayed connections can be refactored + // let conn0 = new Connection; + // expect(conn0.isConnected).to.be.undefined; + // // + // }) + }); + + describe('Error Object handling', function(){ + + 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); + + 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); + console.log(test0); + 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 conn = new Connection; + let key = createKey(hp.validFingerprint, conn); + + expect(key).to.be.an.instanceof(GPGME_Key); + expect(key.connection).to.be.an.instanceof(Connection); + // TODO not implemented yet: Further Key functionality + }); + + it('Key can use the connection', function(){ + let conn = new Connection; + let key = createKey(hp.validFingerprint, conn); + + expect(key.connection.isConnected).to.be.true; + + key.connection.disconnect(); + expect(key.connection.isConnected).to.be.false; + }); + + it('createKey returns error if parameters are wrong', function(){ + let conn = new Connection; + for (let i=0; i< 4; i++){ + let key0 = createKey(wp.four_invalid_params[i], conn); + + expect(key0).to.be.an.instanceof(Error); + expect(key0.code).to.equal('PARAM_WRONG'); + } + for (let i=0; i< 4; i++){ + let key0 = createKey( + hp.validFingerprint, wp.four_invalid_params[i]); + + expect(key0).to.be.an.instanceof(Error); + expect(key0.code).to.equal('PARAM_WRONG'); + } + }); + it('bad GPGME_Key returns Error if 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'); + } + }); + }); + + describe('GPGME_Keyring', function(){ + + it('correct initialization', function(){ + let conn = new Connection; + let keyring = new GPGME_Keyring(conn); + + expect(keyring).to.be.an.instanceof(GPGME_Keyring); + expect(keyring.connection).to.be.an.instanceof(Connection); + expect(keyring.getKeys).to.be.a('function'); + expect(keyring.getSubset).to.be.a('function'); + }); + + it('Keyring should return errors if not connected', function(){ + let keyring = new GPGME_Keyring; + + expect(keyring).to.be.an.instanceof(GPGME_Keyring); + expect(keyring.connection).to.be.an.instanceof(Error); + expect(keyring.connection.code).to.equal('CONN_NO_CONNECT'); + expect(keyring.getKeys).to.be.an.instanceof(Error); + expect(keyring.getkeys.code).to.equal('CONN_NO_CONNECT'); + }); + //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(){ + + 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 mandatoy 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('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'); + + 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]); + + expect(ret).to.be.an.instanceof(Error); + expect(ret.code).to.equal('PARAM_WRONG'); + } + }); + }); + + mocha.run(); +} + +export default {unittests}; \ No newline at end of file diff --git a/lang/js/webpack.conf_unittests.js b/lang/js/webpack.conf_unittests.js new file mode 100644 index 00000000..4b903be6 --- /dev/null +++ b/lang/js/webpack.conf_unittests.js @@ -0,0 +1,34 @@ +/* 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+ + * + * This is the configuration file for building the gpgmejs-Library with webpack + */ +const path = require('path'); + +module.exports = { + entry: './unittests.js', + mode: 'production', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'gpgmejs_unittests.bundle.js', + libraryTarget: 'var', + libraryExport: 'default', + library: 'Gpgmejs_test' + } +}; -- cgit v1.2.3 From cf075846fb48c8d71937100d2c45069d37d54a38 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 4 May 2018 12:56:59 +0200 Subject: js: fixing errors found by testing -- * Key.js: Error code for wrong parameter in createKey should be "PARAM_WRONG" * Helpers.js: The property openpgpjs-like Objects were checked for in toKeyIdArray was not defined. * src/permittedOperations.js: updated more expectations and assumptions for the native API --- lang/js/src/Helpers.js | 2 +- lang/js/src/Key.js | 4 +- lang/js/src/permittedOperations.js | 81 ++++++++++++++++++++++++++++---------- lang/js/unittests.js | 2 +- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index ea056fff..fd0e7200 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -48,7 +48,7 @@ export function toKeyIdArray(input){ if (input[i] instanceof GPGME_Key){ fpr = input[i].fingerprint; } else if (input[i].hasOwnProperty('primaryKey') && - input[i].primaryKey.hasOwnProperty(getFingerprint)){ + input[i].primaryKey.hasOwnProperty('getFingerprint')){ fpr = input[i].primaryKey.getFingerprint(); } if (isFingerprint(fpr) === true){ diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 6d3cf17d..075a190e 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -35,13 +35,15 @@ import { Connection } from './Connection'; export function createKey(fingerprint, parent){ if (!isFingerprint(fingerprint)){ - return gpgme_error('KEY_INVALID'); + return gpgme_error('PARAM_WRONG'); } if ( parent instanceof Connection){ return new GPGME_Key(fingerprint, parent); } else if ( parent.hasOwnProperty('connection') && parent.connection instanceof Connection){ return new GPGME_Key(fingerprint, parent.connection); + } else { + return gpgme_error('PARAM_WRONG'); } } diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 274e037e..59597aaf 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -122,55 +122,96 @@ export const permittedOperations = { type: ['plaintext'], data: ['data'], params: ['base64', 'mime'], - infos: [] // pending. Info about signatures and validity - //signature: [{Key Fingerprint, valid boolean}] + infos: [] // TODO pending. Info about signatures and validity + //{ + //signatures: [{ + //Key : Fingerprint, + //valid: + // }] } }, - /** - keyinfo: { // querying the Key's information. - required: ['fingerprint'], - anser: { + /** 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'], + params: ['hasSecret','isRevoked','isExpired','armored', + 'timestamp','expires','pubkey_algo'], infos: ['subkeys', 'userIds'] + // {'hasSecret': , + // 'isRevoked': , + // 'isExpired': , + // 'armored': , // armored public Key block + // 'timestamp': , // + // 'expires': , + // 'pubkey_algo': TBD // TBD (optional?), + // 'userIds': Array, + // 'subkeys': Array Fingerprints of Subkeys + // } }*/ /** listkeys:{ - optional: ['with-secret', 'pattern'], + required: {}; + optional: { + 'with-secret':{ + allowed: ['boolean'] + },{ + 'pattern': { + allowed: ['string'] + } + }, answer: { - type: ['TBD'], //Array of fingerprints? - infos: ['TBD'] //the property with infos + type: ['TBD'], + infos: ['TBD'] + // keys: Array Fingerprints representing the results }, */ /** importkey: { - required: ['keyarmored'], + required: { + 'keyarmored': { + allowed: ['string'] + } + }, answer: { type: ['TBD'], - infos: [''], // for each key if import was a success, if it was an update + infos: ['TBD'], + // for each key if import was a success, + // and if it was an update of preexisting key } }, */ /** deletekey: { - required: ['fingerprint'], + pinentry: true, + required: { + 'fingerprint': { + allowed: ['string'], + // array_allowed: TBD Allow several Keys to be deleted at once? + }, + optional: { + 'TBD' //Flag to delete secret Key ? + } answer: { type ['TBD'], - infos: [''] //success:true? in gpgme, an error NO_ERROR is returned + infos: [''] + // TBD (optional) Some kind of 'ok' if delete was successful. } } */ /** - *get armored secret different treatment from keyinfo! - */ - - /** - * TBD key modification requests? + *TBD get armored secret different treatment from keyinfo! + * TBD key modification? + * encryptsign: TBD + * verify: TBD */ } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 0a1b4b48..6c0d1890 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -109,7 +109,7 @@ function unittests (){ }); }); - describe('toKeyIdArray() (converting input to fingerprint', function(){ + describe('toKeyIdArray() (converting input to fingerprint)', function(){ it('Correct fingerprint string', function(){ let test0 = toKeyIdArray(hp.validFingerprint); -- cgit v1.2.3 From 8f3d83e5f0903323ec92f588f60dcecb0ae96de4 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 7 May 2018 18:27:25 +0200 Subject: js: fixing errors found by testing: encrypt/decrypt -- * Key.js: Error code for wrong parameter in createKey should be "PARAM_WRONG" * Helpers.js: The property openpgpjs-like Objects were checked for in toKeyIdArray was not defined. * src/permittedOperations.js: updated more expectations and assumptions for the native API * new Problems: - There seems to be a message size limit of about 21 MB for nativeMessaging, much lower than the documented 4GB. - Some bytes are lost with random data in an encrypt-decrypt roundtrip. The culprit is unclear. --- lang/js/BrowserTestExtension/browsertest.html | 1 + .../tests/encryptDecryptTest.js | 72 +++++++++++++ lang/js/BrowserTestExtension/tests/encryptTest.js | 114 ++++++++++++++++++--- lang/js/BrowserTestExtension/tests/inputvalues.js | 12 +++ lang/js/src/Connection.js | 13 ++- lang/js/src/Message.js | 19 +++- lang/js/unittests.js | 11 +- 7 files changed, 219 insertions(+), 23 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/encryptDecryptTest.js diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index ce037a11..d12e03cf 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -18,6 +18,7 @@ + diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js new file mode 100644 index 00000000..7abf2d94 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -0,0 +1,72 @@ +/* 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', 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(); + }); + }); + }); + }); + +/** + * Fails with random data! Some bytes (up to 100) of the original are missing in + * the result + */ +/** + for (let i=0; i< 20; i++) { + it('Successful encrypt 1 MB '+ i+ '/20', function (done) { + let prm = Gpgmejs.init(); + let data = bigString(0.1); + 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(10000); + };*/ +}); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 2178efac..37b319ed 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -17,47 +17,133 @@ * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ -describe('Encryption', function(){ - it('Successfull encrypt', function(){ +describe('Encryption', function () { + it('Successful encrypt', function (done) { let prm = Gpgmejs.init(); - prm.then(function(context){ + prm.then(function (context) { context.encrypt( - inputvalues.encrypt.good.data, - inputvalues.encrypt.good.fingerprint).then(function(answer){ + 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'); + done(); }); }); }); - it('Sending encryption without keys fails', function(){ + it('Successful encrypt 5 MB', function (done) { let prm = Gpgmejs.init(); - prm.then(function(context){ + let data = bigString(5); + 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'); + done(); + }); + }); + }).timeout(5000); + + it('Successful encrypt 20 MB', function (done) { + let prm = Gpgmejs.init(); + let data = bigString(20); + 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'); + done(); + }); + }); + }).timeout(20000); + +/** + it('Successful encrypt 30 MB', function (done) { + // TODO: There seems to be a limit imposed at least by chrome at about 21 MB + let prm = Gpgmejs.init(); + let data = bigString(30); + 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'); + done(); + }); + }); + }).timeout(20000); +*/ + + it('Sending encryption without keys fails', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { context.encrypt( inputvalues.encrypt.good.data, - null).then(function(answer){ + null).then(function (answer) { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); - //TODO: MSG_INCOMPLETE desired, GNUPG_ERROR coming + done(); }); }); }); - it('Sending encryption without data fails', function(){ + it('Sending encryption without data fails', function (done) { let prm = Gpgmejs.init(); - prm.then(function(context){ + prm.then(function (context) { context.encrypt( - null,inputvalues.encrypt.good.keyid).then(function(answer){ + null, inputvalues.encrypt.good.keyid).then(function (answer) { expect(answer).to.be.undefined; - }, function(error){ + }, function (error) { expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('PARAM_WRONG'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); + }); + }); + }); + + + it('Sending encryption with non existing keys fails', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + inputvalues.encrypt.good.data, + inputvalues.encrypt.bad.fingerprint).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.not.be.undefined; + expect(error.code).to.equal('GNUPG_ERROR'); + done(); }); }); }); + + it('Overly large message ( >= 48MB) is rejected', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt( + bigString(48), + inputvalues.encrypt.good.fingerprint).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + // TODO who is throwing the error here? + // It is not a GPGME_Error! + done(); + }); + }); + }).timeout(8000); // TODO check different valid parameter }); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 1761c82f..3cd1e92a 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -23,6 +23,9 @@ var inputvalues = { good:{ data : 'Hello World.', fingerprint : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' + }, + bad: { + fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' } }, init: { @@ -30,3 +33,12 @@ var inputvalues = { } }; + +function bigString(megabytes){ + 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 new TextDecoder('utf-8').decode(uint); +} diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index a198bdc6..2c8792d6 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -99,11 +99,12 @@ export class Connection{ me._connection.onMessage.removeListener(listener) reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else if (msg.type === "error"){ - me._connection.onMessage.removeListener(listener) + me._connection.onMessage.removeListener(listener); reject(gpgme_error('GNUPG_ERROR', msg.msg)); } else { let answer_result = answer.add(msg); if (answer_result !== true){ + me._connection.onMessage.removeListener(listener); reject(answer_result); } if (msg.more === true){ @@ -127,8 +128,12 @@ export class Connection{ }, 5000); }]).then(function(result){ return result; - }, function(error){ - return error; + }, function(reject){ + if(!reject instanceof Error) { + return gpgme_error('GNUPG_ERROR', reject); + } else { + return reject; + } }); } }); @@ -196,7 +201,7 @@ class Answer{ this._response.push(msg[key]); } else { - return gpgme_error('CONN_UNEXPECTED_ANSWER', key); + return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index c42480f2..6a59c3e0 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -84,9 +84,22 @@ export class GPGME_Message { let checktype = function(val){ switch(typeof(val)){ case 'string': + if (poparam.allowed.indexOf(typeof(val)) >= 0 + && val.length > 0) { + return true; + } + return gpgme_error('PARAM_WRONG'); + break; case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 + && isNaN(value) === false){ + return true; + } + return gpgme_error('PARAM_WRONG'); + break; case 'boolean': - if (poparam.allowed.indexOf(typeof(val)) >= 0){ + if (poparam.allowed.indexOf('boolean') >= 0){ return true; } return gpgme_error('PARAM_WRONG'); @@ -102,7 +115,9 @@ export class GPGME_Message { return res; } } - return true; + if (val.length > 0) { + return true; + } } else if (val instanceof Uint8Array){ if (poparam.allowed.indexOf('Uint8Array') >= 0){ return true; diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 6c0d1890..c437d599 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -130,7 +130,6 @@ function unittests (){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); - console.log(test0); expect(test0).to.include( hp.valid_openpgplike.primaryKey.getFingerprint()); }); @@ -255,7 +254,7 @@ function unittests (){ expect(test0.isComplete).to.be.false; }); - it('Message is complete after setting mandatoy data', function(){ + 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); @@ -263,6 +262,13 @@ function unittests (){ 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); @@ -315,7 +321,6 @@ function unittests (){ }); }); - mocha.run(); } export default {unittests}; \ No newline at end of file -- cgit v1.2.3 From cca40627b0afa2efc85ef7f5f1a1060a221ff2a2 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 8 May 2018 18:33:41 +0200 Subject: js: more testing -- * Tests: Under certain circumstances, some data change during encrypt-decrypt. Committing the current state so the problem can be discussed. * Fixes: - disconnecting the test ports after tests are complete - fixed passing of the error message from gpgme-json --- lang/js/BrowserTestExtension/testkey.pub | 30 ++++ lang/js/BrowserTestExtension/testkey.sec | 57 +++++++ .../tests/encryptDecryptTest.js | 186 +++++++++++++++++++-- lang/js/BrowserTestExtension/tests/encryptTest.js | 13 +- lang/js/BrowserTestExtension/tests/inputvalues.js | 62 ++++++- lang/js/src/Errors.js | 2 +- lang/js/src/gpgmejs.js | 11 +- 7 files changed, 342 insertions(+), 19 deletions(-) create mode 100644 lang/js/BrowserTestExtension/testkey.pub create mode 100644 lang/js/BrowserTestExtension/testkey.sec diff --git a/lang/js/BrowserTestExtension/testkey.pub b/lang/js/BrowserTestExtension/testkey.pub new file mode 100644 index 00000000..cfc329f3 --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey.pub @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf +PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE +BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c +PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870 ++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M +yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAG0EHRlc3RAZXhhbXBsZS5v +cmeJAVQEEwEIAD4WIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8Jn +AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGw +Fq07W9N01HWULyhHKoMmcHL6rfZ64oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6 +kaBe+wF6Kqir6udFSBW9rPcFg6/VZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rN +GkYo1JCBR0XdRJYCSX3yB4TWv/eXnZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k7 +0DevQeBsv+UjVXjWpNTZmPbvDnd995uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6U +unOf9Rlp1oMzdxMool/d1MlCxg2h3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ +1J8Nsv87SZeEuQENBFrsKEkBCADjoEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6 +yE+x2hk5FoQCajxKa/d4AVxOnJpdwhAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0p +O7bgAiZxkA6RHxtNqhpPnPQoXvUzkzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/ +8i9Taz67pdZwuJjac8qBuJHjzAo1bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt +3RuqVGP316Fk+Sy2+60tC/HlX8jgMyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyL +UGKU+L8cB2g1PGGp2biBFWqZbudZoyRBet/0yH/zirBdQJw1ABEBAAGJATwEGAEI +ACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAKCRAjAWNe +7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ5F32NDJ9 +PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7Jq3e/4Iy +0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02Lkr+2Cc/Q +k6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe23nntMSD +A+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t9Mmd7bM1 ++UyPgbPEr0iWMeyctYsuOLeUyQKMscDT +=QyY6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/lang/js/BrowserTestExtension/testkey.sec b/lang/js/BrowserTestExtension/testkey.sec new file mode 100644 index 00000000..ced8f3ec --- /dev/null +++ b/lang/js/BrowserTestExtension/testkey.sec @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf +PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE +BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c +PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870 ++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M +yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAEAB/wLJ0gyMjs2fFfT83wM +5Lzz2yQIwV4t3bblBAujdHTqeN5Zmsm/oakFyjSokULK96Kv0R4ej9eoIgMFvxFk +HRkrggxTrbsNJ7I6QcKYHTPeIIj318ykNL6fj0WJUcdPIENukXl5jbqNyk3/4D2y +TTDySyq6jHTgvMH4K4KJUSpglvSJPntTk9RhuFGHAF+sNR9atygDYctAaERMRtSg +LCoSt/AoX5GRMlQjXT9oqQjwSQoZyF4s8HMC8wdTFIE/E0L4IVdHVp8sz2UszNtT +W/evmCA+KVruKjRH/Fhrq4hHkEamW28+j4L6uAyagONP7BONs+S5Oo2zTT9+tV2R +ILTZBADdgLuAgF6C5Lu9jCF6DfFgaT/uafMyQNkEGNlxOHMWHTgLHe475V2eG9gA +amd4yXKyEFKU1PWnvlGuicQSGdzVcwmq61msvXgYD0FK3LP3yWzKnE4X1tzrC9Vp +/uHJxKjewCuyt1f5in919v+T8TbUxBYKC0zX/qWtX+10cTx77QQA6leqhToJ95Yc +u4UBrKMEO+y2v8Svb3LG7yI5oY8tkw0EkJ/kpZ8xTAfZYCe6fXdvVE3PHg2lrxyc +Wv/EU3QY/qA3G82mbXYeJ2jNZaTNYo4MylMrt4Mx25x4ke7JlsE8SVrQ+4CrHkqp +OjSIa7fppLrQ78uW980AtN8NNQGrlTsD/A9aoA60Igxy1Q3K2uSyDCyjLknv57ym +ZSBD3/t7m0l6Q6gbdfhNGosT+Hd4y3actqEqzXZHW2VG4dKZ/wRNkxtSm9adU9vs +EHyzxjb6mKIH32zAG5TaFT20hC+NK6lsyHr9UE2ZrS6ma2sLxGW2O40hqNsdD+5m +NrqeBc2I/js1PMK0EHRlc3RAZXhhbXBsZS5vcmeJAVQEEwEIAD4WIQTUFzW5Ejb9 +uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIe +AQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGwFq07W9N01HWULyhHKoMmcHL6rfZ6 +4oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6kaBe+wF6Kqir6udFSBW9rPcFg6/V +ZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rNGkYo1JCBR0XdRJYCSX3yB4TWv/eX +nZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k70DevQeBsv+UjVXjWpNTZmPbvDnd9 +95uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6UunOf9Rlp1oMzdxMool/d1MlCxg2h +3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ1J8Nsv87SZeEnQOYBFrsKEkBCADj +oEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6yE+x2hk5FoQCajxKa/d4AVxOnJpd +whAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0pO7bgAiZxkA6RHxtNqhpPnPQoXvUz +kzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/8i9Taz67pdZwuJjac8qBuJHjzAo1 +bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt3RuqVGP316Fk+Sy2+60tC/HlX8jg +MyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyLUGKU+L8cB2g1PGGp2biBFWqZbudZ +oyRBet/0yH/zirBdQJw1ABEBAAEAB/4lN3gXOI4OuoOcsvHak4pebx61Mt0YP9cT +qZASIBqxok5x8E28pFh/tYfkYdqRCtdNYZOnxcEoUWh5j6nfwZkEnJ9P/T8GPNk7 +pMKnKXmExi05b5uGHD8nU1rSbf/YkvAF0vpbxd4/RDxbbtQhbUwGzusSI+pBLM0w +5TreEB+vRGBc2gOvXXOtKLNEa7M9rH2EwbAkP3jOGGwgk6adxbQdBcRxq4merqhL +YrVz73bCj8TDc0fsNJyIaZZJ++ejfBFYavsF1pvx9z7FNFi8rSXoiB3SBtaWGfhr +bwNaMZrDc7TRIq/fgGaL6g//bzcWrr1YaHXZ10Bgx6UymDOlYkCpBADm0Hv46sPw +07SO8+IACcaQliOto1pndOPwTimCeo58/7rf8I2a5uuJloGrnPwAX65bKDnUALp6 +X3lnXRNMhnB3Uewx4i00LQmjsxhJfQiGLpMv0j58tn64s7GqQzGVV1JKcQm992RV +jFOydyjZ+K4LGWEOITG/bZrMEVNGCM+OnQQA/Haz8xN0NFSlq7tyfFc0pkx/TiCX +xGfBqbO0wU2b5GMnZbY/06HENpidIzpa231VQaw5/nPTvfhlLKW1iGAkc148cX1q +lL9w2ksXuaHR3LXud2VcfVTIdxU/7h7u1dD/85+c0+7jlGObD9cXKxlM6OjpIJz1 +l5/1h3C5S0TuxHkEAL/3BGihkhNfv1Xx0rWu0/732usX/nE/A9C26hGu41FUf3fp +0ilonKpKZUEwWt5hWSEFCSrznNVekiO0rxvuu3RVegvzThPNU4Pf4JZtJpRVhvUQ +d9ulxJw7V9rs75uNBatTNC0kXuGoXhehw4Bn93xa67gYGd3LfrH+oT0GCDpTSHCJ +ATwEGAEIACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAK +CRAjAWNe7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ +5F32NDJ9PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7 +Jq3e/4Iy0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02L +kr+2Cc/Qk6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe +23nntMSDA+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t +9Mmd7bM1+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT +=hkUm +-----END PGP PRIVATE KEY BLOCK----- diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 7abf2d94..e28dd66b 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -24,30 +24,144 @@ describe('Encryption and Decryption', function () { prm.then(function (context) { context.encrypt( inputvalues.encrypt.good.data, - inputvalues.encrypt.good.fingerprint).then( function(answer){ + 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){ + 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); + context.connection.disconnect(); done(); }); }); }); }); + /** + * Fails with random data! Some bytes (up to 100) of the original are missing in + * the result +*/ /** - * Fails with random data! Some bytes (up to 100) of the original are missing in - * the result - */ + for (let j = 0; j < 10; j++){ + it('Successful encrypt and decrypt specific sets: ', + function (done) { + let prm = Gpgmejs.init(); + let data = bigBoringString(5); //see ./inputvalues.js + expect(Object.prototype.toString.call(data)).to.equal("[object String]"); + 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) { + if (data.length !== result.data.length) { + + for (let k = 0; k < data.length; k++) { + if (data[k] !== result.data[k]) { + console.log(k); + console.log(data[k - 2] + data[k - 1] + data[k] + data[k + 1]); + console.log(result.data[k - 2] + result.data[k - 1] + result.data[k] + result.data[k + 1]); + break; + } + } + } + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + + }); + }); + }); + }).timeout(5000); + } + + + 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); + context.connection.disconnect(); + done(); + + }); + }); + }); + }).timeout(3000); + + it('Test with simple non-ascii input', + function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = ''; + for (let i=0; i < 1024 * 1024 * 0.1; i++){ + data += inputvalues.encrypt.good.data_nonascii; + } + 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'); + console.log(answer); + context.decrypt(answer.data).then( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + if (data.length !== result.data.length) { + + for (let k = 0; k < data.length; k++) { + if (data[k] !== result.data[k]) { + console.log(k); + console.log(data[k - 2] + data[k - 1] + data[k] + data[k + 1]); + console.log(result.data[k - 2] + result.data[k - 1] + result.data[k] + result.data[k + 1]); + break; + } + } + } + console.log(data.length - result.data.length); + expect(result.data).to.equal(data); + context.connection.disconnect(); + done(); + + }); + }); + }); + }).timeout(3000); +*/ /** - for (let i=0; i< 20; i++) { - it('Successful encrypt 1 MB '+ i+ '/20', function (done) { + for (let i=0; i< 100; i++) { + it('Successful encrypt random data '+ (i+1) + '/100', function (done) { let prm = Gpgmejs.init(); - let data = bigString(0.1); + let data = bigString(0.2); // << set source data here prm.then(function (context) { context.encrypt(data, inputvalues.encrypt.good.fingerprint).then( @@ -63,10 +177,62 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); + context.connection.disconnect(); done(); }); }); }); - }).timeout(10000); - };*/ + }).timeout(5000); + }; +*/ + +/** still fails + it('Successful encrypt 0.8 MB Uint8Array', function (done) { + let prm = Gpgmejs.init(); + let data = bigUint8(0.8); + 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(5000); +*/ + + it('Decrypt simple non-ascii', + function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + 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(inputvalues.encrypt.good.data_nonascii); + context.encrypt(inputvalues.encrypt.good.data_nonascii, inputvalues.encrypt.good.fingerprint).then( + function(result){ + context.decrypt(result.data).then(function(answer){ + expect(answer.data).to.equal(inputvalues.encrypt.good.data_nonascii); + context.connection.disconnect(); + done(); + }); + }); + }); + + }); + }).timeout(8000); + }); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 37b319ed..2e95151b 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -28,6 +28,7 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); done(); }); }); @@ -44,11 +45,13 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); done(); }); }); }).timeout(5000); +/** it('Successful encrypt 20 MB', function (done) { let prm = Gpgmejs.init(); let data = bigString(20); @@ -60,11 +63,12 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); done(); }); }); }).timeout(20000); - +*/ /** it('Successful encrypt 30 MB', function (done) { // TODO: There seems to be a limit imposed at least by chrome at about 21 MB @@ -78,6 +82,7 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); + context.connection.disconnect(); done(); }); }); @@ -94,6 +99,7 @@ describe('Encryption', function () { }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); + context.connection.disconnect(); done(); }); }); @@ -108,6 +114,7 @@ describe('Encryption', function () { }, function (error) { expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('MSG_INCOMPLETE'); + context.connection.disconnect(); done(); }); }); @@ -125,10 +132,11 @@ describe('Encryption', function () { expect(error).to.be.an('Error'); expect(error.code).to.not.be.undefined; expect(error.code).to.equal('GNUPG_ERROR'); + context.connection.disconnect(); done(); }); }); - }); + }).timeout(5000);; it('Overly large message ( >= 48MB) is rejected', function (done) { let prm = Gpgmejs.init(); @@ -141,6 +149,7 @@ describe('Encryption', function () { expect(error).to.be.an.instanceof(Error); // TODO who is throwing the error here? // It is not a GPGME_Error! + context.connection.disconnect(); done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 3cd1e92a..bc8c97bb 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -22,7 +22,8 @@ var inputvalues = { encrypt: { good:{ data : 'Hello World.', - fingerprint : 'CDC3A2B2860625CCBFC5A5A9FC6D1B604967FC40' + fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', + data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n' }, bad: { fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' @@ -42,3 +43,62 @@ function bigString(megabytes){ } return new TextDecoder('utf-8').decode(uint); } + +function bigUint8(megabytes){ + 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; +} + +function bigBoringString(megabytes){ + let maxlength = 1024 * 1024 * megabytes; + let string = ''; + let chars = ' ä0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + for (let i= 0; i < maxlength; i++){ + string = string + chars[Math.floor(Math.random() * chars.length)]; + } + return string; +} + +function slightlyLessBoringString(megabytes, set){ + let maxlength = 1024 * 1024 * megabytes; + let string = ''; + let chars = ''; + if (!set){ + + } else if (set ===1 ) { + chars = '\n\"\r \''; + } else if (set === 2 ) { + chars = '()=?`#+-{}[]'; + } else if (set === 3){ + chars = '^°/'; + //'*<>\\^°/'; + } else if (set ===4) { + chars = 'äüßµüþÖ~ɁÑ||@'; + } else { + chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; //fails! + + } + for (let i= 0; i < maxlength; i++){ + string = string + chars[Math.floor(Math.random() * chars.length)]; + } + return string; +} + +var encryptedData = + '-----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'; \ No newline at end of file diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index d26aab18..b71004a5 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -110,7 +110,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ } return null; } else if (code === 'GNUPG_ERROR'){ - return new GPGME_Error(code, info.msg); + return new GPGME_Error(code, info); } else { return new GPGME_Error('GENERIC_ERROR'); diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 9475b2b0..c1a01377 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -170,18 +170,19 @@ function putData(message, data){ if (!data){ return gpgme_error('PARAM_WRONG'); } else if (data instanceof Uint8Array){ - let decoder = new TextDecoder('utf8'); message.setParameter('base64', true); - message.setParameter ('data', decoder.decode(data)); + message.setParameter ('data', btoa(data)); } else if (typeof(data) === 'string') { message.setParameter('base64', false); message.setParameter('data', data); - } else if ( typeof(data) === 'object' && data.hasOwnProperty(getText)){ + } else if ( typeof(data) === 'object' && data.hasOwnProperty('getText')){ let txt = data.getText(); if (txt instanceof Uint8Array){ - let decoder = new TextDecoder('utf8'); message.setParameter('base64', true); - message.setParameter ('data', decoder.decode(txt)); + message.setParameter ('data', btoa(txt)); + } + else { + return gpgme_error('PARAM_WRONG'); } } else { return gpgme_error('PARAM_WRONG'); -- cgit v1.2.3 From c92326cc257cf7c8b6c0ddc43ec81573c409bc64 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 9 May 2018 19:40:57 +0200 Subject: js: more testing of nativeMessaging connection -- * There were some inconsistencies between utf-8, transfer and browsers' utf16, which broke characters that were split between individual messages. src/Connection now contains a workaround that reassembles javascripts' format from passed base64 strings. This needs someone more experienced looking. * Added several new tests which were failing during initial debugging of this issue * reorganized BrowsertestExtension to avoid cluttering. --- lang/js/BrowserTestExtension/browsertest.html | 3 - lang/js/BrowserTestExtension/index.html | 40 +++++ lang/js/BrowserTestExtension/longTests.html | 22 +++ lang/js/BrowserTestExtension/popup.js | 2 +- lang/js/BrowserTestExtension/runbrowsertest.js | 1 - lang/js/BrowserTestExtension/rununittests.js | 21 +++ .../tests/encryptDecryptTest.js | 191 +++++---------------- lang/js/BrowserTestExtension/tests/encryptTest.js | 2 +- lang/js/BrowserTestExtension/tests/inputvalues.js | 119 ++++++++++++- .../BrowserTestExtension/tests/longRunningTests.js | 53 ++++++ lang/js/BrowserTestExtension/unittests.html | 17 ++ lang/js/src/Connection.js | 19 +- 12 files changed, 328 insertions(+), 162 deletions(-) create mode 100644 lang/js/BrowserTestExtension/index.html create mode 100644 lang/js/BrowserTestExtension/longTests.html create mode 100644 lang/js/BrowserTestExtension/rununittests.js create mode 100644 lang/js/BrowserTestExtension/tests/longRunningTests.js create mode 100644 lang/js/BrowserTestExtension/unittests.html diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index d12e03cf..c379ef53 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -12,14 +12,11 @@ - - - diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html new file mode 100644 index 00000000..05d413ba --- /dev/null +++ b/lang/js/BrowserTestExtension/index.html @@ -0,0 +1,40 @@ + + + + + + + +

gpgmejs - Tests

+

+ The unittests rely on a separately packaged version of gpgmejs, + with the different classes and functions exposed. These tests and their + input values can be found in gpgme/lang/js/test. They do not test the + overall functionality, but the individual behaviour of the components. +

+

+

+ The functionality tests, to be found in + gpgme/lang/js/BrowserTestExtension, check the overall functionality of + the standard packaged version of gpgmejs. +

+

+ + diff --git a/lang/js/BrowserTestExtension/longTests.html b/lang/js/BrowserTestExtension/longTests.html new file mode 100644 index 00000000..8ff969b7 --- /dev/null +++ b/lang/js/BrowserTestExtension/longTests.html @@ -0,0 +1,22 @@ + + + + + + + +

Browsertest

+
+ + + + + + + + + + + + + diff --git a/lang/js/BrowserTestExtension/popup.js b/lang/js/BrowserTestExtension/popup.js index 4764df55..12beb1eb 100644 --- a/lang/js/BrowserTestExtension/popup.js +++ b/lang/js/BrowserTestExtension/popup.js @@ -39,6 +39,6 @@ document.addEventListener('DOMContentLoaded', function() { chrome.tabs.create({ - url: './browsertest.html' + url: './index.html' }); }); diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js index 308c716d..39bc3fb9 100644 --- a/lang/js/BrowserTestExtension/runbrowsertest.js +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -19,4 +19,3 @@ */ mocha.run(); -Gpgmejs_test.unittests(); diff --git a/lang/js/BrowserTestExtension/rununittests.js b/lang/js/BrowserTestExtension/rununittests.js new file mode 100644 index 00000000..f85ed8b5 --- /dev/null +++ b/lang/js/BrowserTestExtension/rununittests.js @@ -0,0 +1,21 @@ +/* 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+ + */ +Gpgmejs_test.unittests(); +mocha.run(); diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index e28dd66b..a66e1534 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -19,7 +19,7 @@ */ describe('Encryption and Decryption', function () { - it('Successful encrypt and decrypt', function (done) { + it('Successful encrypt and decrypt simple string', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( @@ -39,54 +39,6 @@ describe('Encryption and Decryption', function () { }); }); }); - - /** - * Fails with random data! Some bytes (up to 100) of the original are missing in - * the result -*/ -/** - for (let j = 0; j < 10; j++){ - it('Successful encrypt and decrypt specific sets: ', - function (done) { - let prm = Gpgmejs.init(); - let data = bigBoringString(5); //see ./inputvalues.js - expect(Object.prototype.toString.call(data)).to.equal("[object String]"); - 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) { - if (data.length !== result.data.length) { - - for (let k = 0; k < data.length; k++) { - if (data[k] !== result.data[k]) { - console.log(k); - console.log(data[k - 2] + data[k - 1] + data[k] + data[k + 1]); - console.log(result.data[k - 2] + result.data[k - 1] + result.data[k] + result.data[k + 1]); - break; - } - } - } - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - expect(result.data).to.equal(data); - context.connection.disconnect(); - done(); - - }); - }); - }); - }).timeout(5000); - } - - it('Roundtrip does not destroy trailing whitespace', function (done) { let prm = Gpgmejs.init(); @@ -112,88 +64,22 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(3000); - - it('Test with simple non-ascii input', - function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - let data = ''; - for (let i=0; i < 1024 * 1024 * 0.1; i++){ - data += inputvalues.encrypt.good.data_nonascii; - } - 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'); - console.log(answer); - context.decrypt(answer.data).then( - function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - if (data.length !== result.data.length) { - - for (let k = 0; k < data.length; k++) { - if (data[k] !== result.data[k]) { - console.log(k); - console.log(data[k - 2] + data[k - 1] + data[k] + data[k + 1]); - console.log(result.data[k - 2] + result.data[k - 1] + result.data[k] + result.data[k + 1]); - break; - } - } - } - console.log(data.length - result.data.length); - expect(result.data).to.equal(data); - context.connection.disconnect(); - done(); + }).timeout(5000); - }); - }); - }); - }).timeout(3000); -*/ -/** - for (let i=0; i< 100; i++) { - it('Successful encrypt random data '+ (i+1) + '/100', function (done) { - let prm = Gpgmejs.init(); - let data = bigString(0.2); // << set source data here + 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); - context.connection.disconnect(); - done(); - }); - }); - }); - }).timeout(5000); - }; -*/ - -/** still fails - it('Successful encrypt 0.8 MB Uint8Array', function (done) { - let prm = Gpgmejs.init(); - let data = bigUint8(0.8); - prm.then(function (context) { - context.encrypt(data, - inputvalues.encrypt.good.fingerprint).then( - function (answer){ + function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a("string"); expect(answer.data).to.include( @@ -201,38 +87,39 @@ describe('Encryption and Decryption', function () { expect(answer.data).to.include( 'END PGP MESSAGE'); context.decrypt(answer.data).then( - function(result){ + function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - done(); - }); - }); - }); - }).timeout(5000); -*/ - - it('Decrypt simple non-ascii', - function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - 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(inputvalues.encrypt.good.data_nonascii); - context.encrypt(inputvalues.encrypt.good.data_nonascii, inputvalues.encrypt.good.fingerprint).then( - function(result){ - context.decrypt(result.data).then(function(answer){ - expect(answer.data).to.equal(inputvalues.encrypt.good.data_nonascii); context.connection.disconnect(); done(); }); + }); + }); + }).timeout(5000); + }; + + it('Encrypt-decrypt simple non-ascii', function (done) { + //FAILS TODO: Check newline at the end + let prm = Gpgmejs.init(); + prm.then(function (context) { + 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(inputvalues.encrypt.good.data_nonascii); + context.encrypt(inputvalues.encrypt.good.data_nonascii, inputvalues.encrypt.good.fingerprint).then( + function(result){ + context.decrypt(result.data).then(function(answer){ + expect(answer.data).to.equal('¡Äußerste µ€ før ñoquis@hóme! Добрый день'); + context.connection.disconnect(); + done(); }); }); + }); - }); - }).timeout(8000); + }); + }).timeout(6000); -}); \ No newline at end of file +}); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 2e95151b..5ef68a32 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -49,7 +49,7 @@ describe('Encryption', function () { done(); }); }); - }).timeout(5000); + }).timeout(10000); /** it('Successful encrypt 20 MB', function (done) { diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index bc8c97bb..e23b7786 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -23,7 +23,23 @@ var inputvalues = { good:{ data : 'Hello World.', fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', - data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n' + data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день', + data_nonascii_32: [ + 'K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€', //fails result has 3 chars more + '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€', //fails 3 chars + '²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€', //fails 2 chars + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€', //is okay if 2 chunksizes. + 'üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü', + 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'µAAAAµAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAµ€', + 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°', + '€AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', + 'µ||||||||||||||||||||||||||||||€', + 'æſæſ³¼„¬“³³¬“¬½”æſæſ³¼„¬“³³¬“¬½”' + ] }, bad: { fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' @@ -56,7 +72,7 @@ function bigUint8(megabytes){ function bigBoringString(megabytes){ let maxlength = 1024 * 1024 * megabytes; let string = ''; - let chars = ' ä0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let chars = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; for (let i= 0; i < maxlength; i++){ string = string + chars[Math.floor(Math.random() * chars.length)]; } @@ -101,4 +117,101 @@ var encryptedData = 'isGL7jOlDCBKwep67ted0cTRPLWkk3NSuLIlvD5xs6L4z3rPu92gXYgbZoMMdP0N\n' + 'kSAQYOHplfA7YJWkrlRm\n' + '=zap6\n' + - '-----END PGP MESSAGE-----\n'; \ No newline at end of file + '-----END PGP MESSAGE-----\n'; +var encryptedBroken = '-----BEGIN PGP MESSAGE-----\n' + +'\n' + +'hQEMA6B8jfIUScGEAQf/bUYF70KRCHWITfNH7zaYaLa8P+QoCo+NpFzc3U9J4mty\n' + +'FxjIpoNwxEvQ9UUEMi6LgHhvURYCbWrCV5XYjo/sE66CRXsEuNirfYkAzXVNcUf7\n' + +'BaAzio/QzyyvBfzwHHqMLSxAcNggs+f5lob+TcBnBghwpn1lh5BgNUuhDKVq21/F\n' + +'wWK4rqjmmjrpoR3tKcl916+/Z0VI5SAkPG4IrWUfumxG0xbePB9IFT8uGMmXy2qr\n' + +'ICmEfPakLUIo7NLrdMNInnVQaAeNS/5u5TbpZpRxZWtRP7m4EyUoEA+TgSkp+hG8\n' + +'Um7hmbFsB99H0yiyCSLicN5AxzmgCrL3D77Fqh7LaNLsAYjcyVZm+R7te4vwpv9P\n' + +'F/MCAEUFKGfNYHqyVjBhBlm4/PMC+YtOE9jF920hwtDckT/V3L2POk1Kr78+nVjw\n' + +'1HXTfK/Tk6QMGrzCd2ril5aB2RCi+Fr41B2ftS8SLwcrnrFkP2enH6VYBserx5l8\n' + +'qZlgRR53QNnLvqnn7h/NO1ZNN5cnD2pf0PWBkSHmr5ph82JQ+XyB0h4eV1kwX80K\n' + +'8IkBAq6hFpfm7TU4gy5x1VNTeVoCRdlzESkzVwbvjNZ+OU6+vcpfCaHMbuVBUmYz\n' + +'xjTKYlenevSzwfF1RY7noDTrPUQrBrVor2cPjN3ROLCbFpARrQf44BfzGaq5XdWc\n' + +'NZWFgiRKVGVJQeBQjRyqHAv4e8rkcr5qwnY8kyZpLYAKIVBgtqnh7GExaW5efWRG\n' + +'tyJMgUuP+dF/+HymhlEmMKZabLf5W8J3p8+uBOkU359OX/HOS8mPr6a7bnI4895W\n' + +'7Dt5vkpHRR81V1Le0+Mtcj7G46hsvFMA0dgw29mBbaOA8fhOrumqTBOh01lZliwI\n' + +'6/OF6iqAeBAH3hJQlodCACf1yTxHynF6Ro/SnIa/3BN4CN4PPRHdLMHBJevRm3Ih\n' + +'CbqXVmSdtrihHsViPKjc8+u+7g2n/lt9LHrMyOmptyVX8vT9B/AQYHxf0FDmv4Vg\n' + +'62Mo+eDRWZF+XmKPQYedM6nF5hcyxc/1aCM4yXtu8qQir/GDvyghPbfnKkium5kk\n' + +'+XOb+aIUsxbNzhdLowp2mZcy1MYMPHIJNjIXmVjPnc/GwB8S2SX/gHn1quz52ENq\n' + +'l12ome7rfAp9JkrVbHOK11iDPbd3UdHSTfFNO8wQrxtqnZhUwqLhZwteOi4EGSSh\n' + +'OrWihjdonqL0qcfiS6N9QemJz2w40fR8ZwDuGvPgl6LeNtKjihyqsWvh+zJzwmwM\n' + +'R2Y50wNyvQnXGH4RJJUQVAKO/vMp63K2j3DnHsyz/XLbmp25QGn9f1QIjfplY64D\n' + +'q3lp2W6GvhpYWLRzBfIo6ebwLtqHTsTgON9TA4CD+1QbOXMIxQKAb9hhzEtp/5zN\n' + +'+gJhF4pOvEu5Cg1j9CtXh93iE0J9rwrjyMujzBSiaoqxHabXtRarv8d2v/w75AKh\n' + +'6Avt+WFYRdSLKCstdHeuREXEibIaM55nUUIEO0v9kcb0Y7LyH/vFVGAo0QFh3u+t\n' + +'zMupQwywjeuuUwM18KeWjKrhGuRf1WWCDRnnH1yEztDPLx5kyxadsC31/XyqLjYl\n' + +'zt+vUSm+JrXujhba9VaYO3DSB9hL0qdrA3gaK2DAl2nvFGRn0fjtw0xfa9VJlafN\n' + +'JLosw7MDDEFx962vHbx5XfjJRGaEdDnsco5E5VUkQ+RjhWWrzMHpIPYWYacXiUKr\n' + +'TcNTAg1jR5M2FRz/QOk7qsTl98RyNCYXTUmuPh/pLJI0kJ5rtTPrlzFNgVjwiYEJ\n' + +'+iNITXhqx5KJ5ifY89BXeNVavIb1Tp0xc1+637U/ztH9D0Jp6m0w/VIHW+881Ik3\n' + +'fMKw8A/RuEdTil/PU0bjVRNYLS/KCQCqrlYdItYh57IAkt+sQNxvw0xg46QN+OkO\n' + +'QHKnIazexhGAqyBe6c2KYuRLW46h9grGbCJnqvmoThBRrqL7twmp00O846tvRms8\n' + +'3QEXL3oXqBTH1d6bRd/E6m++X/n9I6VaKMgYe6GNQEqwvtSySFi65VK5cH1jnEGw\n' + +'wr2ZkXUrVbNTfXci6SdNqh+W8DRnFvlRyKzG1jnibsOW5FwGSMT3kVRUvnnJbzlc\n' + +'wj1cJC/NMvkoQtGHppHkMjE23byjBhJlZXBTbGc3kSOfXKAMAT7I9Dm/GgEpbbpD\n' + +'4fgzqNEeWucrCWgbXviXt1pWOyNtudb9rHWgvIQlE9JeykPgvmg+pl4Av42lQTYp\n' + +'kyNFjq46niWT9VsYlsW52x4jCQifT7HkxTuSaD9JyVqjQWS11rci9UM/NuoXfqrv\n' + +'vJYMBJGhzTxFzzFCzSRSERbjN0iXJ2E8vFKkpd5nCZxRMz6XBMk1NVyrE956BMum\n' + +'yNaSy5mwR+ekS3xM7oUdbqyyDwFEDxpPhtIRqRfFugpIn8tRy7jwDZB9mctFGfKo\n' + +'th5dCzcaU0qPfUJWPVQVh2LCPneLGhLENgFUhoNZ+rzaf5SltLeB4vuVjZMLe+PW\n' + +'KqtT9l6QFQajbe7pj99BScteaI8lpiQiNTvQq/LZRFWr9eb5z0Xk5Wc3aYZgymkp\n' + +'EYxyVqwomyz4wPf2BrgsSdKk0OZKIkAxfA3i73tHvCsCQOHeriRMSfLzFN3J54nf\n' + +'+MOuUm1hKLsLbPLQxOfzPiymVGp6DjYCkrRmafvZUJHkvGubvVVR5Yq0txznM1Vg\n' + +'yZq4HoF3RGgKzJtk8N4me5YsVaM2/q+2B2ziVa/HeEFt/cZfcH/byY3ooW3OnAum\n' + +'KTe/+T2BEjXfipmbIMA6iK3IKIoguuVwvSJz+5QfjMH1o8HIUdDOhnrbBBHmkvNK\n' + +'MG+dV+oDijC2rL3n0qRURu4VWdk/bqKcaaLoZC5iDGLThZ20q+9jlFKahmlKe1WH\n' + +'2Rch+JJfqSHtNYVKxZU0CC0I9Wg/Ws6TQJREKCiJf0/aTvxWRSHZtecFiZK7q+zn\n' + +'NyRdWnqAv+HKRjN/tVZcf8I0CERswxmixF9uWMTjH+hq0u/h4It3I3tOObNyAQO3\n' + +'iY9uSZEZbrKBSM3DqFF75toLjooWXU8yaC9so3mQVf5MnSZpG3PA5klwusLmi0QU\n' + +'HD1eZ2aXUnTx7TbHuovWLjI40SIUKnaMAf0TCUHfBvJ5rLUPYez35QwrYRx0Qixn\n' + +'Pcj7KCCXrT5cqwH64vGTiW6JCZJlLzneiE+dmnAT+wnNRNxbVooi6ejWce5HYbYd\n' + +'c2SyBHJstGn0zuNN/248qhV+r5AMBgZ+vDilV8Bmdh3N/xlXBIgLIocegL6Kc+S0\n' + +'Pr60DHKLcnZIunQwZOwyRb8wG9jV6I718CmbSw94gKNCi99B8BSDZ7z2ai+0yv44\n' + +'ErR4Qp/gnCp9/6NXNmafluYn5Pgl9vZCozcJ8EN8mzD4szZBL19btecoT6Wcnve2\n' + +'fYDRuYPWpT79QyRDSMSSzrQoFpezIOtPS2nrN+II81TxyTgOMY+jzR4TRJyMt185\n' + +'7OG4t8Q+WOgzNS4clmPHnmgBBhsueWob72SvIgRtq5pQYB0fStx9qUDMZPnePdhS\n' + +'rI+K82k1/eY5vTQ/eDXMN7UUfdLriuK0UXnJFu5CQSwrMD1u5nFVbQYC9PEwgdUc\n' + +'XEASt9/jh2wDgSXAGegc6mLRI+Zu5H5ygpCIAMs8pNwFJ5DhCsve5RbalGEbYbuL\n' + +'NwB1rRExCCUBjnAkpwNU0TL991y1Gn+gpN2lNvITq/BroE3HLjXbnEACTN+hwNPB\n' + +'KJi38zKSb6/k27/zpTMuEKRXkSz4QuuviQbGJTmCbub+l2aVBQhVNwooGI92Gt8n\n' + +'EQjGOzqeS4J0KQGZmhYRGVc7DdwjBYLV5pi1WkCIt1a1PDK9VZ4vzz978gLaxSZM\n' + +'yozdL97g9wo0IJcAj+36b1Wewj+hL81t0SgIShEO0aIGSNDlFZM4mKQNmCUhvWuO\n' + +'M1CpniR8cBN4MHUaQdBIlW2ua9Ba8JM7LNwcD8JddGvmUBwzFr5w4Hu4ylweacXP\n' + +'5zUfZpJyFZKoxJe1cPY47NmXemOLuBVJRlThnUazvhM/KRxfyu2q4WOz6VSm6LEq\n' + +'PFfr/NYH1AxIda/Z4tLLAs0nLbV+HrqRFMJOBGdY6dMxuvaiUutY3MZCMCKupz8f\n' + +'yHh2p2lFy2jQvZs4HAKN6hTx8X7at1ue0RYw3hdjoPHa/NBKDzrkKjGInfraTVr6\n' + +'qrxqW09/yNuiatISi+KxuBM4o9L/w85Zf01RNEZTS5zCKX0ml33JHgNxQgPosp+7\n' + +'R0TUK2lANdKVTXJe8V/IT4tGUD4mg0EjMVRmFV2CL3LgBbW3ScOC15D4mzD14Yyb\n' + +'KTUHwfX189GHKjJhHnSuZ3QgVKynoSII+0x4fiDHsdhdXdMj/qvVdZIMlABWKRD0\n' + +'JVmrkFpzFtt4yXupl62+9ZYZehSKNKurlO4A8OBeg6xKDUKuvrI7Ug/2s5Q0pCxp\n' + +'EgtxwOhhYrAhd8mN2ilKeB++JCAmZ2KwnwCGFF8kZ/5TOwWZHm/RNKEchTRC5kws\n' + +'KsDUxq/19ORifzCA19f6Tc5s9HcPwxvnrscvb6LLTGGiROp3BlcitHjmPsH5bRUX\n' + +'OAqV069l1JKeiCkGgQmlRviBGG0yO2zIcAeoDIPhaO4O0K6/VHo4p6kAlZAzWJuT\n' + +'QmHI0ETyO+2m0jySoxW0EUU1FB3eQ4KBocneYqJUgCbOCeXf14TO8HekDtkfoKOK\n' + +'bded3iCtnSAH6I9ERtPebqiWdR2tVCO4Yyqkf2f3vzCWrtyXHUWtZtC1I08HNLin\n' + +'zGhEdQZ/VFCLP8CWmbtLU8BPeu88VTpw7i8G76QuHq5+0DY9eBgHWxcBYiwRisT/\n' + +'DHXH0TvjuPedJ4F/sNmlktTXLLMqVu+J8i/qJ48E1r9wXkHTICnFy8jvm5MpQ4gu\n' + +'rwzpyjSFLJZpzDMAxcPSXYGi1kchW+CDg/N/cdeYlVLCoBrUn6dEq6CC05Y6JmDW\n' + +'t46R6lFHbQoq1WsMWZSKomB4WlxWP+hYDsssQOUR9Y7wwI4KXPtf6Ar9W2T9cSfO\n' + +'mtDpgfeOVq/vE01TQGlZc4zwF5dcXBV3OLYBSXlv4JFIreOlKDi/IbPc6TYw0mbV\n' + +'wFuzPi8VpHip3YoGdM7XUDvO1sE07FX8/xrEQVkJfzgl/v+mQ66TCb+/g13QPgZI\n' + +'UftRS6hLeKNTd0pZc8+CTbNzgrCDGqbYn5ZpyPFYF+fVGZnqqLUid5NTjkwI1IoD\n' + +'PgOSHQEo+pIlNfTtR2DCYgqOiMaBSZ4bc4b6SohAKGJkPhNmlMJ61MwGN2J8pFpl\n' + +'1uG2MO3TUo6MxQAkCcKe4twwy1bQh4kO3kReUqTDW/VTnp6HfZhqtYc1tBGLcahu\n' + +'C0ZX7B/8Wbu1PWN4Y34F7ouuSu2l6ASnoAc/Ek1S9R1uyiwLtaPuK58oUbVisDh3\n' + +'cYmnjP0DelYq8FpJPWPrSGwqlERotf3KU3L1k84SHYUB1pHFYPF46KAKYH5qTrsO\n' + +'T3id3CO3mt1gtgWAEGRkEQ+qVmvWtINBOwyFYVAD9ZqXflzF83ZGvdmvdJ6kzRZ7\n' + +'fY5ACZGMghb3f4mfLlbF81WluDbk2k+t186qmRFrJFtJPvAl3VxXczo8pw5bSAdK\n' + +'R6c7cagA6ql4QaYqtbIHpFbgz7iQ9ESe23Q2+o82lkTbUFdG+GDhnZFOL+ldWf/g\n' + +'ufSCqY7IlNxj3hYxgTpaXb2lWvVVdo7C4VhPHyIDbQUCdUE80t2cDgJqPFABe3la\n' + +'Y+UsW9W787mGGuuNSF/iI0tANw5twlQjdRQtqxnF1yETh/hFA4bgD9bmBOBFd+GT\n' + +'+ECxkqI4/UYMgYfVMFja/e6+dQTWLblzuNaZh6wHASeNqpFmeQSBawBVV7qK3nC7\n' + +'CDY9r6Aq9JYMiJTE/TzyfBmBhnxtL1aKTu6EHy3siDlID7EjQx1Xyr/EtbJCmsVl\n' + +'E14StpggdK8=\n' + +'=enm3\n' + +'-----END PGP MESSAGE-----\n'; \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js new file mode 100644 index 00000000..0f32ca92 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -0,0 +1,53 @@ +describe('Long running Encryption/Decryption', function () { + for (let i=0; i< 100; i++) { + it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { + let prm = Gpgmejs.init(); + let data = bigString(2); + 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); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(5000); + }; + + it('Successful encrypt 1 MB Uint8Array', function (done) { + let prm = Gpgmejs.init(); + let data = bigUint8(1); + 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(5000); + +}); diff --git a/lang/js/BrowserTestExtension/unittests.html b/lang/js/BrowserTestExtension/unittests.html new file mode 100644 index 00000000..6f7da3f1 --- /dev/null +++ b/lang/js/BrowserTestExtension/unittests.html @@ -0,0 +1,17 @@ + + + + + + + + +

Unit tests

+
+ + + + + + + diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 2c8792d6..64621f60 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -181,7 +181,8 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = ''; } - this._response[key] = this._response[key].concat(msg[key]); + // console.log(msg[key]); + this._response[key] += msg[key]; } //params should not change through the message else if (poa.params.indexOf(key) >= 0){ @@ -214,6 +215,22 @@ class Answer{ * TODO: does not care yet if completed. */ get message(){ + let keys = Object.keys(this._response); + let poa = permittedOperations[this.operation].answer; + for (let i=0; i < keys.length; i++) { + if (poa.data.indexOf(keys[i]) >= 0){ + if (this._response.base64 == true){ + let respatob = atob(this._response[keys[i]]); + + let result = decodeURIComponent( + respatob.split('').map(function(c) { + return '%' + + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + this._response[keys[i]] = result; + } + } + } return this._response; } } -- cgit v1.2.3 From 987b31746809dfe04966e37edd759a448a28d975 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 14 May 2018 16:23:24 +0200 Subject: js: Tests and improvements for openpgp mode -- * Added openpgp - Mode tests to the browsertest Extension. These tests require openpgp, which should not be a hard dependency for the main project. Packing openpgpjs into the extension is still TODO * Fixes: - openpgp mode API now correctly handles parameters as an object, similar to openpgpjs - proper check and parsing of openpgpjs Message Objects --- lang/js/BrowserTestExtension/index.html | 7 + lang/js/BrowserTestExtension/openpgpModeTest.html | 23 +++ .../tests/encryptDecryptTest.js | 21 +-- .../tests/inputValues_openpgpjs.js | 32 ++++ lang/js/BrowserTestExtension/tests/inputvalues.js | 131 +++----------- .../BrowserTestExtension/tests/longRunningTests.js | 1 + .../BrowserTestExtension/tests/openpgpModeTest.js | 196 +++++++++++++++++++++ lang/js/src/Connection.js | 1 - lang/js/src/gpgmejs.js | 18 +- lang/js/src/gpgmejs_openpgpjs.js | 77 ++++---- 10 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 lang/js/BrowserTestExtension/openpgpModeTest.html create mode 100644 lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js create mode 100644 lang/js/BrowserTestExtension/tests/openpgpModeTest.js diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html index 05d413ba..c49aedae 100644 --- a/lang/js/BrowserTestExtension/index.html +++ b/lang/js/BrowserTestExtension/index.html @@ -34,6 +34,13 @@ Functionality tests with larger/longer running data sets. +
  • + + Testing openPGP mode. + - Please notice that, due to comparing + the inputs and outputs with openpgpjs objects, this test + requires a copy of openpgpjs in libs. +
  • diff --git a/lang/js/BrowserTestExtension/openpgpModeTest.html b/lang/js/BrowserTestExtension/openpgpModeTest.html new file mode 100644 index 00000000..e7a12be9 --- /dev/null +++ b/lang/js/BrowserTestExtension/openpgpModeTest.html @@ -0,0 +1,23 @@ + + + + + + + +

    Openpgp mode test

    +
    + + + + + + + + + + + + + + diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index a66e1534..5c534039 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -99,8 +99,7 @@ describe('Encryption and Decryption', function () { }).timeout(5000); }; - it('Encrypt-decrypt simple non-ascii', function (done) { - //FAILS TODO: Check newline at the end + it('Decrypt simple non-ascii', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { data = encryptedData; @@ -108,18 +107,10 @@ describe('Encryption and Decryption', function () { function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); - expect(result.data).to.equal(inputvalues.encrypt.good.data_nonascii); - context.encrypt(inputvalues.encrypt.good.data_nonascii, inputvalues.encrypt.good.fingerprint).then( - function(result){ - context.decrypt(result.data).then(function(answer){ - expect(answer.data).to.equal('¡Äußerste µ€ før ñoquis@hóme! Добрый день'); - context.connection.disconnect(); - done(); - }); - }); - }); - + expect(result.data).to.equal( + '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); + done(); + }); }); - }).timeout(6000); - + }).timeout(3000); }); diff --git a/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js b/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js new file mode 100644 index 00000000..945955be --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js @@ -0,0 +1,32 @@ +const openpgpInputs = { + pubKeyArmored: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + + '\n' + + 'mQENBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf\n' + + 'PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE\n' + + 'BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c\n' + + 'PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870\n' + + '+O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M\n' + + 'yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAG0EHRlc3RAZXhhbXBsZS5v\n' + + 'cmeJAVQEEwEIAD4WIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8Jn\n' + + 'AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGw\n' + + 'Fq07W9N01HWULyhHKoMmcHL6rfZ64oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6\n' + + 'kaBe+wF6Kqir6udFSBW9rPcFg6/VZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rN\n' + + 'GkYo1JCBR0XdRJYCSX3yB4TWv/eXnZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k7\n' + + '0DevQeBsv+UjVXjWpNTZmPbvDnd995uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6U\n' + + 'unOf9Rlp1oMzdxMool/d1MlCxg2h3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ\n' + + '1J8Nsv87SZeEuQENBFrsKEkBCADjoEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6\n' + + 'yE+x2hk5FoQCajxKa/d4AVxOnJpdwhAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0p\n' + + 'O7bgAiZxkA6RHxtNqhpPnPQoXvUzkzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/\n' + + '8i9Taz67pdZwuJjac8qBuJHjzAo1bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt\n' + + '3RuqVGP316Fk+Sy2+60tC/HlX8jgMyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyL\n' + + 'UGKU+L8cB2g1PGGp2biBFWqZbudZoyRBet/0yH/zirBdQJw1ABEBAAGJATwEGAEI\n' + + 'ACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAKCRAjAWNe\n' + + '7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ5F32NDJ9\n' + + 'PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7Jq3e/4Iy\n' + + '0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02Lkr+2Cc/Q\n' + + 'k6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe23nntMSD\n' + + 'A+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t9Mmd7bM1\n' + + '+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT\n' + + '=QyY6\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n' +}; diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index e23b7786..38ee6aad 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -22,15 +22,18 @@ var inputvalues = { encrypt: { good:{ data : 'Hello World.', + // 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€', - 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€', //fails result has 3 chars more - '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€', //fails 3 chars + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€', + '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€', '²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³', - 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€', //fails 2 chars - 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€', //is okay if 2 chunksizes. + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€', + 'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€', 'üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü', 'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€', 'µAAAAµAAAAAAAAAAAAAAAAAAAAAAAAA€', @@ -42,15 +45,21 @@ var inputvalues = { ] }, bad: { + // valid Hex value, but not usable (not imported to gnupg, or bogus fingerprint) fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' } }, init: { - invalid_startups: [{all_passwords: true}, 'openpgpmode', {api_style:"frankenstein"}] + // some parameters + invalid_startups: [ + {all_passwords: true}, + 'openpgpmode', + {api_style:"frankenstein"} + ] } - }; +// (Pseudo-)Random String from a Uint8Array, given approx. size in Megabytes function bigString(megabytes){ let maxlength = 1024 * 1024 * megabytes; let uint = new Uint8Array(maxlength); @@ -60,6 +69,7 @@ function bigString(megabytes){ return new TextDecoder('utf-8').decode(uint); } +// (Pseudo-)Random Uint8Array, given size in Megabytes function bigUint8(megabytes){ let maxlength = 1024 * 1024 * megabytes; let uint = new Uint8Array(maxlength); @@ -69,6 +79,7 @@ function bigUint8(megabytes){ return uint; } +// (Pseudo-)Random string with very limited charset (ascii only, no control chars) function bigBoringString(megabytes){ let maxlength = 1024 * 1024 * megabytes; let string = ''; @@ -79,24 +90,22 @@ function bigBoringString(megabytes){ return string; } +// Some String with simple chars, with different characteristics, but still +// expected to occur in an averag message function slightlyLessBoringString(megabytes, set){ let maxlength = 1024 * 1024 * megabytes; let string = ''; let chars = ''; - if (!set){ - - } else if (set ===1 ) { + if (set ===1 ) { chars = '\n\"\r \''; } else if (set === 2 ) { chars = '()=?`#+-{}[]'; } else if (set === 3){ chars = '^°/'; - //'*<>\\^°/'; } else if (set ===4) { chars = 'äüßµüþÖ~ɁÑ||@'; } else { - chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; //fails! - + chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; } for (let i= 0; i < maxlength; i++){ string = string + chars[Math.floor(Math.random() * chars.length)]; @@ -104,6 +113,7 @@ function slightlyLessBoringString(megabytes, set){ return string; } +// Data encrypted with testKey var encryptedData = '-----BEGIN PGP MESSAGE-----\n' + '\n' + @@ -118,100 +128,3 @@ var encryptedData = 'kSAQYOHplfA7YJWkrlRm\n' + '=zap6\n' + '-----END PGP MESSAGE-----\n'; -var encryptedBroken = '-----BEGIN PGP MESSAGE-----\n' + -'\n' + -'hQEMA6B8jfIUScGEAQf/bUYF70KRCHWITfNH7zaYaLa8P+QoCo+NpFzc3U9J4mty\n' + -'FxjIpoNwxEvQ9UUEMi6LgHhvURYCbWrCV5XYjo/sE66CRXsEuNirfYkAzXVNcUf7\n' + -'BaAzio/QzyyvBfzwHHqMLSxAcNggs+f5lob+TcBnBghwpn1lh5BgNUuhDKVq21/F\n' + -'wWK4rqjmmjrpoR3tKcl916+/Z0VI5SAkPG4IrWUfumxG0xbePB9IFT8uGMmXy2qr\n' + -'ICmEfPakLUIo7NLrdMNInnVQaAeNS/5u5TbpZpRxZWtRP7m4EyUoEA+TgSkp+hG8\n' + -'Um7hmbFsB99H0yiyCSLicN5AxzmgCrL3D77Fqh7LaNLsAYjcyVZm+R7te4vwpv9P\n' + -'F/MCAEUFKGfNYHqyVjBhBlm4/PMC+YtOE9jF920hwtDckT/V3L2POk1Kr78+nVjw\n' + -'1HXTfK/Tk6QMGrzCd2ril5aB2RCi+Fr41B2ftS8SLwcrnrFkP2enH6VYBserx5l8\n' + -'qZlgRR53QNnLvqnn7h/NO1ZNN5cnD2pf0PWBkSHmr5ph82JQ+XyB0h4eV1kwX80K\n' + -'8IkBAq6hFpfm7TU4gy5x1VNTeVoCRdlzESkzVwbvjNZ+OU6+vcpfCaHMbuVBUmYz\n' + -'xjTKYlenevSzwfF1RY7noDTrPUQrBrVor2cPjN3ROLCbFpARrQf44BfzGaq5XdWc\n' + -'NZWFgiRKVGVJQeBQjRyqHAv4e8rkcr5qwnY8kyZpLYAKIVBgtqnh7GExaW5efWRG\n' + -'tyJMgUuP+dF/+HymhlEmMKZabLf5W8J3p8+uBOkU359OX/HOS8mPr6a7bnI4895W\n' + -'7Dt5vkpHRR81V1Le0+Mtcj7G46hsvFMA0dgw29mBbaOA8fhOrumqTBOh01lZliwI\n' + -'6/OF6iqAeBAH3hJQlodCACf1yTxHynF6Ro/SnIa/3BN4CN4PPRHdLMHBJevRm3Ih\n' + -'CbqXVmSdtrihHsViPKjc8+u+7g2n/lt9LHrMyOmptyVX8vT9B/AQYHxf0FDmv4Vg\n' + -'62Mo+eDRWZF+XmKPQYedM6nF5hcyxc/1aCM4yXtu8qQir/GDvyghPbfnKkium5kk\n' + -'+XOb+aIUsxbNzhdLowp2mZcy1MYMPHIJNjIXmVjPnc/GwB8S2SX/gHn1quz52ENq\n' + -'l12ome7rfAp9JkrVbHOK11iDPbd3UdHSTfFNO8wQrxtqnZhUwqLhZwteOi4EGSSh\n' + -'OrWihjdonqL0qcfiS6N9QemJz2w40fR8ZwDuGvPgl6LeNtKjihyqsWvh+zJzwmwM\n' + -'R2Y50wNyvQnXGH4RJJUQVAKO/vMp63K2j3DnHsyz/XLbmp25QGn9f1QIjfplY64D\n' + -'q3lp2W6GvhpYWLRzBfIo6ebwLtqHTsTgON9TA4CD+1QbOXMIxQKAb9hhzEtp/5zN\n' + -'+gJhF4pOvEu5Cg1j9CtXh93iE0J9rwrjyMujzBSiaoqxHabXtRarv8d2v/w75AKh\n' + -'6Avt+WFYRdSLKCstdHeuREXEibIaM55nUUIEO0v9kcb0Y7LyH/vFVGAo0QFh3u+t\n' + -'zMupQwywjeuuUwM18KeWjKrhGuRf1WWCDRnnH1yEztDPLx5kyxadsC31/XyqLjYl\n' + -'zt+vUSm+JrXujhba9VaYO3DSB9hL0qdrA3gaK2DAl2nvFGRn0fjtw0xfa9VJlafN\n' + -'JLosw7MDDEFx962vHbx5XfjJRGaEdDnsco5E5VUkQ+RjhWWrzMHpIPYWYacXiUKr\n' + -'TcNTAg1jR5M2FRz/QOk7qsTl98RyNCYXTUmuPh/pLJI0kJ5rtTPrlzFNgVjwiYEJ\n' + -'+iNITXhqx5KJ5ifY89BXeNVavIb1Tp0xc1+637U/ztH9D0Jp6m0w/VIHW+881Ik3\n' + -'fMKw8A/RuEdTil/PU0bjVRNYLS/KCQCqrlYdItYh57IAkt+sQNxvw0xg46QN+OkO\n' + -'QHKnIazexhGAqyBe6c2KYuRLW46h9grGbCJnqvmoThBRrqL7twmp00O846tvRms8\n' + -'3QEXL3oXqBTH1d6bRd/E6m++X/n9I6VaKMgYe6GNQEqwvtSySFi65VK5cH1jnEGw\n' + -'wr2ZkXUrVbNTfXci6SdNqh+W8DRnFvlRyKzG1jnibsOW5FwGSMT3kVRUvnnJbzlc\n' + -'wj1cJC/NMvkoQtGHppHkMjE23byjBhJlZXBTbGc3kSOfXKAMAT7I9Dm/GgEpbbpD\n' + -'4fgzqNEeWucrCWgbXviXt1pWOyNtudb9rHWgvIQlE9JeykPgvmg+pl4Av42lQTYp\n' + -'kyNFjq46niWT9VsYlsW52x4jCQifT7HkxTuSaD9JyVqjQWS11rci9UM/NuoXfqrv\n' + -'vJYMBJGhzTxFzzFCzSRSERbjN0iXJ2E8vFKkpd5nCZxRMz6XBMk1NVyrE956BMum\n' + -'yNaSy5mwR+ekS3xM7oUdbqyyDwFEDxpPhtIRqRfFugpIn8tRy7jwDZB9mctFGfKo\n' + -'th5dCzcaU0qPfUJWPVQVh2LCPneLGhLENgFUhoNZ+rzaf5SltLeB4vuVjZMLe+PW\n' + -'KqtT9l6QFQajbe7pj99BScteaI8lpiQiNTvQq/LZRFWr9eb5z0Xk5Wc3aYZgymkp\n' + -'EYxyVqwomyz4wPf2BrgsSdKk0OZKIkAxfA3i73tHvCsCQOHeriRMSfLzFN3J54nf\n' + -'+MOuUm1hKLsLbPLQxOfzPiymVGp6DjYCkrRmafvZUJHkvGubvVVR5Yq0txznM1Vg\n' + -'yZq4HoF3RGgKzJtk8N4me5YsVaM2/q+2B2ziVa/HeEFt/cZfcH/byY3ooW3OnAum\n' + -'KTe/+T2BEjXfipmbIMA6iK3IKIoguuVwvSJz+5QfjMH1o8HIUdDOhnrbBBHmkvNK\n' + -'MG+dV+oDijC2rL3n0qRURu4VWdk/bqKcaaLoZC5iDGLThZ20q+9jlFKahmlKe1WH\n' + -'2Rch+JJfqSHtNYVKxZU0CC0I9Wg/Ws6TQJREKCiJf0/aTvxWRSHZtecFiZK7q+zn\n' + -'NyRdWnqAv+HKRjN/tVZcf8I0CERswxmixF9uWMTjH+hq0u/h4It3I3tOObNyAQO3\n' + -'iY9uSZEZbrKBSM3DqFF75toLjooWXU8yaC9so3mQVf5MnSZpG3PA5klwusLmi0QU\n' + -'HD1eZ2aXUnTx7TbHuovWLjI40SIUKnaMAf0TCUHfBvJ5rLUPYez35QwrYRx0Qixn\n' + -'Pcj7KCCXrT5cqwH64vGTiW6JCZJlLzneiE+dmnAT+wnNRNxbVooi6ejWce5HYbYd\n' + -'c2SyBHJstGn0zuNN/248qhV+r5AMBgZ+vDilV8Bmdh3N/xlXBIgLIocegL6Kc+S0\n' + -'Pr60DHKLcnZIunQwZOwyRb8wG9jV6I718CmbSw94gKNCi99B8BSDZ7z2ai+0yv44\n' + -'ErR4Qp/gnCp9/6NXNmafluYn5Pgl9vZCozcJ8EN8mzD4szZBL19btecoT6Wcnve2\n' + -'fYDRuYPWpT79QyRDSMSSzrQoFpezIOtPS2nrN+II81TxyTgOMY+jzR4TRJyMt185\n' + -'7OG4t8Q+WOgzNS4clmPHnmgBBhsueWob72SvIgRtq5pQYB0fStx9qUDMZPnePdhS\n' + -'rI+K82k1/eY5vTQ/eDXMN7UUfdLriuK0UXnJFu5CQSwrMD1u5nFVbQYC9PEwgdUc\n' + -'XEASt9/jh2wDgSXAGegc6mLRI+Zu5H5ygpCIAMs8pNwFJ5DhCsve5RbalGEbYbuL\n' + -'NwB1rRExCCUBjnAkpwNU0TL991y1Gn+gpN2lNvITq/BroE3HLjXbnEACTN+hwNPB\n' + -'KJi38zKSb6/k27/zpTMuEKRXkSz4QuuviQbGJTmCbub+l2aVBQhVNwooGI92Gt8n\n' + -'EQjGOzqeS4J0KQGZmhYRGVc7DdwjBYLV5pi1WkCIt1a1PDK9VZ4vzz978gLaxSZM\n' + -'yozdL97g9wo0IJcAj+36b1Wewj+hL81t0SgIShEO0aIGSNDlFZM4mKQNmCUhvWuO\n' + -'M1CpniR8cBN4MHUaQdBIlW2ua9Ba8JM7LNwcD8JddGvmUBwzFr5w4Hu4ylweacXP\n' + -'5zUfZpJyFZKoxJe1cPY47NmXemOLuBVJRlThnUazvhM/KRxfyu2q4WOz6VSm6LEq\n' + -'PFfr/NYH1AxIda/Z4tLLAs0nLbV+HrqRFMJOBGdY6dMxuvaiUutY3MZCMCKupz8f\n' + -'yHh2p2lFy2jQvZs4HAKN6hTx8X7at1ue0RYw3hdjoPHa/NBKDzrkKjGInfraTVr6\n' + -'qrxqW09/yNuiatISi+KxuBM4o9L/w85Zf01RNEZTS5zCKX0ml33JHgNxQgPosp+7\n' + -'R0TUK2lANdKVTXJe8V/IT4tGUD4mg0EjMVRmFV2CL3LgBbW3ScOC15D4mzD14Yyb\n' + -'KTUHwfX189GHKjJhHnSuZ3QgVKynoSII+0x4fiDHsdhdXdMj/qvVdZIMlABWKRD0\n' + -'JVmrkFpzFtt4yXupl62+9ZYZehSKNKurlO4A8OBeg6xKDUKuvrI7Ug/2s5Q0pCxp\n' + -'EgtxwOhhYrAhd8mN2ilKeB++JCAmZ2KwnwCGFF8kZ/5TOwWZHm/RNKEchTRC5kws\n' + -'KsDUxq/19ORifzCA19f6Tc5s9HcPwxvnrscvb6LLTGGiROp3BlcitHjmPsH5bRUX\n' + -'OAqV069l1JKeiCkGgQmlRviBGG0yO2zIcAeoDIPhaO4O0K6/VHo4p6kAlZAzWJuT\n' + -'QmHI0ETyO+2m0jySoxW0EUU1FB3eQ4KBocneYqJUgCbOCeXf14TO8HekDtkfoKOK\n' + -'bded3iCtnSAH6I9ERtPebqiWdR2tVCO4Yyqkf2f3vzCWrtyXHUWtZtC1I08HNLin\n' + -'zGhEdQZ/VFCLP8CWmbtLU8BPeu88VTpw7i8G76QuHq5+0DY9eBgHWxcBYiwRisT/\n' + -'DHXH0TvjuPedJ4F/sNmlktTXLLMqVu+J8i/qJ48E1r9wXkHTICnFy8jvm5MpQ4gu\n' + -'rwzpyjSFLJZpzDMAxcPSXYGi1kchW+CDg/N/cdeYlVLCoBrUn6dEq6CC05Y6JmDW\n' + -'t46R6lFHbQoq1WsMWZSKomB4WlxWP+hYDsssQOUR9Y7wwI4KXPtf6Ar9W2T9cSfO\n' + -'mtDpgfeOVq/vE01TQGlZc4zwF5dcXBV3OLYBSXlv4JFIreOlKDi/IbPc6TYw0mbV\n' + -'wFuzPi8VpHip3YoGdM7XUDvO1sE07FX8/xrEQVkJfzgl/v+mQ66TCb+/g13QPgZI\n' + -'UftRS6hLeKNTd0pZc8+CTbNzgrCDGqbYn5ZpyPFYF+fVGZnqqLUid5NTjkwI1IoD\n' + -'PgOSHQEo+pIlNfTtR2DCYgqOiMaBSZ4bc4b6SohAKGJkPhNmlMJ61MwGN2J8pFpl\n' + -'1uG2MO3TUo6MxQAkCcKe4twwy1bQh4kO3kReUqTDW/VTnp6HfZhqtYc1tBGLcahu\n' + -'C0ZX7B/8Wbu1PWN4Y34F7ouuSu2l6ASnoAc/Ek1S9R1uyiwLtaPuK58oUbVisDh3\n' + -'cYmnjP0DelYq8FpJPWPrSGwqlERotf3KU3L1k84SHYUB1pHFYPF46KAKYH5qTrsO\n' + -'T3id3CO3mt1gtgWAEGRkEQ+qVmvWtINBOwyFYVAD9ZqXflzF83ZGvdmvdJ6kzRZ7\n' + -'fY5ACZGMghb3f4mfLlbF81WluDbk2k+t186qmRFrJFtJPvAl3VxXczo8pw5bSAdK\n' + -'R6c7cagA6ql4QaYqtbIHpFbgz7iQ9ESe23Q2+o82lkTbUFdG+GDhnZFOL+ldWf/g\n' + -'ufSCqY7IlNxj3hYxgTpaXb2lWvVVdo7C4VhPHyIDbQUCdUE80t2cDgJqPFABe3la\n' + -'Y+UsW9W787mGGuuNSF/iI0tANw5twlQjdRQtqxnF1yETh/hFA4bgD9bmBOBFd+GT\n' + -'+ECxkqI4/UYMgYfVMFja/e6+dQTWLblzuNaZh6wHASeNqpFmeQSBawBVV7qK3nC7\n' + -'CDY9r6Aq9JYMiJTE/TzyfBmBhnxtL1aKTu6EHy3siDlID7EjQx1Xyr/EtbJCmsVl\n' + -'E14StpggdK8=\n' + -'=enm3\n' + -'-----END PGP MESSAGE-----\n'; \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index 0f32ca92..f67cbdf1 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -27,6 +27,7 @@ describe('Long running Encryption/Decryption', function () { }; it('Successful encrypt 1 MB Uint8Array', function (done) { + //TODO: this succeeds, but result may be bogus (String with byte values as numbers) let prm = Gpgmejs.init(); let data = bigUint8(1); prm.then(function (context) { diff --git a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js new file mode 100644 index 00000000..98b6e1d8 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js @@ -0,0 +1,196 @@ +/* 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('Encrypting-Decrypting in openpgp mode, using a Message object', function () { + it('Simple Encrypt-Decrypt', function (done) { + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + prm.then(function (context) { + context.encrypt({ + data: openpgp.message.fromText(inputvalues.encrypt.good.data), + publicKeys: inputvalues.encrypt.good.fingerprint} + ).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer).to.be.an("object"); + expect(answer.data).to.include('BEGIN PGP MESSAGE'); + expect(answer.data).to.include('END PGP MESSAGE'); + let msg = openpgp.message.fromText(answer.data); + context.decrypt({message:msg}).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); + context._GpgME.connection.disconnect(); + done(); + }); + }); + }); + }); + it('Encrypt-Decrypt, sending Uint8Array as data', function (done) { + //TODO! fails. Reason is that atob<->btoa destroys the uint8Array, + // resulting in a string of constituyent numbers + // (error already occurs in encryption) + + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + prm.then(function (context) { + let input = bigUint8(0.3); + expect(input).to.be.an.instanceof(Uint8Array); + context.encrypt({ + data: input, + publicKeys: 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({message:answer.data}).then(function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.an.instanceof(Uint8Array); + expect(result.data).to.equal(input); + context._GpgME.connection.disconnect(); + done(); + }); + }); + }); + }); + it('Keys as Fingerprints', function(done){ + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + let input = inputvalues.encrypt.good.data_nonascii; + prm.then(function (context) { + context.encrypt({ + data: input, + publicKeys: 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({message:answer.data}).then(function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(input); + context._GpgME.connection.disconnect(); + done(); + }); + }); + }); + }); + it('Keys as openpgp Keys', function(){ + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + let data = inputvalues.encrypt.good.data_nonascii; + let key = openpgp.key.readArmored(openpgpInputs.pubKeyArmored); + expect(key).to.be.an('object'); + prm.then(function (context) { + context.encrypt({ + data: data, + publicKeys: [key]} + ).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({message:answer.data}).then( function (result){ + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context._GpgME.connection.disconnect(); + done(); + }); + }); + }); + }); + it('Trying to send non-implemented parameters: passwords', function(done){ + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + let data = 'Hello World'; + let key = inputvalues.encrypt.good.fingerprint; + prm.then(function (context) { + context.encrypt({ + data: data, + publicKeys: [key], + passwords: 'My secret password'} + ).then( function(){}, + function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).equal('NOT_IMPLEMENTED'); + done(); + }); + }); + }); + it('Trying to send non-implemented parameters: signature', function(done){ + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + let data = 'Hello World'; + let key = inputvalues.encrypt.good.fingerprint; + prm.then(function (context) { + context.encrypt({ + data: data, + publicKeys: [key], + signature: {any: 'value'} + }).then( + function(){}, + function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).equal('NOT_IMPLEMENTED'); + done(); + }); + }); + }); +}); + +describe('Keyring in openpgp mode', function(){ + it('Check Existence and structure of Keyring after init', function(done){ + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + prm.then(function (context) { + expect(context.Keyring).to.be.an('object'); + expect(context.Keyring.getPublicKeys).to.be.a('function'); + expect(context.Keyring.deleteKey).to.be.a('function'); + expect(context.Keyring.getDefaultKey).to.be.a('function'); + done(); + }); + }); + // TODO: gpgme key interface not yet there +}); + +describe('Decrypting and verification in openpgp mode', function(){ + it('Decrypt', function(){ + let msg = openpgp.message.fromText(inputvalues.encryptedData); + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + prm.then(function (context) { + context.decrypt({message: msg}) + .then(function(answer){ + expect(answer.data).to.be.a('string'); + expect(result.data).to.equal('¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); + done(); + }); + }); + }); + it('Decryption attempt with bad data returns gnupg error', function(done){ + let msg = openpgp.message.fromText(bigString(0.1)); + let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); + prm.then(function (context) { + context.decrypt({message: msg}) + .then( function(){}, + function(error){ + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('GNUPG_ERROR'); + expect(error.message).to.be.a('string'); + // TBD: Type of error + done(); + }); + }); + }).timeout(4000); +}); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 64621f60..1931a55b 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -181,7 +181,6 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = ''; } - // console.log(msg[key]); this._response[key] += msg[key]; } //params should not change through the message diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index c1a01377..d106f4f7 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -80,7 +80,6 @@ export class GpgME { let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); - putData(msg, data); if (wildcard === true){msg.setParameter('throw-keyids', true); }; @@ -171,19 +170,32 @@ function putData(message, data){ return gpgme_error('PARAM_WRONG'); } else if (data instanceof Uint8Array){ message.setParameter('base64', true); + // TODO: btoa turns the array into a string + // of comma separated of numbers + // atob(data).split(',') would result in a "normal" array of numbers + // atob(btoa(data)).split(',') would result in a "normal" array of numbers + // would result in a "normal" array of numbers message.setParameter ('data', btoa(data)); + } else if (typeof(data) === 'string') { message.setParameter('base64', false); message.setParameter('data', data); - } else if ( typeof(data) === 'object' && data.hasOwnProperty('getText')){ + } else if ( + typeof(data) === 'object' && + typeof(data.getText) === 'function' + ){ let txt = data.getText(); if (txt instanceof Uint8Array){ message.setParameter('base64', true); message.setParameter ('data', btoa(txt)); } - else { + else if (typeof(txt) === 'string'){ + message.setParameter('base64', false); + message.setParameter ('data', txt); + } else { return gpgme_error('PARAM_WRONG'); } + } else { return gpgme_error('PARAM_WRONG'); } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js index b233f0fa..9c8cd2cc 100644 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ b/lang/js/src/gpgmejs_openpgpjs.js @@ -25,10 +25,10 @@ */ import { GpgME } from "./gpgmejs"; - import {GPGME_Keyring} from "./Keyring" + import {GPGME_Keyring} from "./Keyring"; import { GPGME_Key, createKey } from "./Key"; - import { isFingerprint } from "./Helpers" - import { gpgme_error } from "./Errors" + import { isFingerprint } from "./Helpers"; + import { gpgme_error } from "./Errors"; import { Connection } from "./Connection"; @@ -60,8 +60,8 @@ import { Connection } from "./Connection"; /** * Encrypt Message * Supported: - * @param {String|Uint8Array} data - * //an openpgp Message also accepted here. TODO: is this wanted? + * @param {String|Message} data + * an openpgp Message is accepted here. * @param {Key|Array} publicKeys * //Strings of Fingerprints * @param {Boolean} wildcard @@ -86,36 +86,39 @@ import { Connection } from "./Connection"; * @async * @static */ - encrypt({data = '', publicKeys = '', privateKeys, passwords=null, - sessionKey = null, filename, compression, armor=true, detached=false, - signature=null, returnSessionKey=null, wildcard=false, date=null}) { - if (passwords !== null - || sessionKey !== null - || signature !== null - || returnSessionKey !== null - || date !== null + encrypt(options) { + if (!options || typeof(options) !== 'object'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } + if (options.passwords + || options.sessionKey + || options.signature + || options.returnSessionKey + || (options.hasOwnProperty('date') && options.date !== null) ){ - return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); + return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } - if ( privateKeys - || compression - || armor === false - || detached == true){ - return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); + if ( options.privateKeys + || options.compression + || (options.hasOwnProperty('armor') && options.armor === false) + || (options.hasOwnProperty('detached') && options.detached == true) + ){ + return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } - if (filename){ + if (options.filename){ if (this._config.unconsidered_params === 'warn'){ - GPMGEJS_Error('PARAM_IGNORED'); + gpgme_error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'error'){ - return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); + return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } } - return this._GpgME.encrypt(data, translateKeys(publicKeys), wildcard); + return this._GpgME.encrypt( + options.data, options.publicKeys, options.wildcard); } /** Decrypt Message * supported openpgpjs parameters: - * @param {Message|Uint8Array|String} message Message object from openpgpjs + * @param {Message|String} message Message object from openpgpjs * Unsupported: * @param {String|Array} passwords * @param {Key|Array} privateKeys @@ -128,26 +131,33 @@ import { Connection } from "./Connection"; * @param {Key|Array} publicKeys * * @returns {Promise} decrypted and verified message in the form: - * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } + * { data:String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @async * @static */ - decrypt({ message, privateKeys, passwords=null, sessionKeys, - publicKeys, format='utf8', signature=null, date= null}) { - if (passwords !== null || sessionKeys || privateKeys){ + decrypt(options) { + if (options.passwords + || options.sessionKeys + || options.privateKeys + ){ return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } - if ( format !== 'utf8' || signature){ + if ((options.hasOwnProperty('format') && options.format !== 'utf8') + || options.signature + ){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } - if (date !== null || publicKeys){ + if ((options.hasOwnProperty('date') && options.date !== null) + || options.publicKeys + ){ if (this._config.unconsidered_params === 'warn'){ GPMGEJS_Error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'reject'){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } - return this._GpgME.decrypt(message); + return this._GpgME.decrypt(options.message); + // TODO: translate between: // openpgp: // { data:Uint8Array|String, @@ -276,14 +286,15 @@ class GPGME_Key_openpgpmode { * @returns {Array} */ function translateKeys(input){ + //TODO: does not check if inpout is okay! if (!input){ return null; } if (!Array.isArray(input)){ input = [input]; } - let resultset; - for (let i=0; i< input.length; i++){ + let resultset = []; + for (let i=0; i< input.length; i++) { resultset.push(new GPGME_Key_openpgpmode(input[i])); } return resultset; -- cgit v1.2.3 From d1ca90ef75aa4ab61cb8f7563be6fc55012a3900 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 14 May 2018 16:36:05 +0200 Subject: js: remove non-browser tests -- * The majority of tests needs to be run in a nativeMessaging context. Offering the few tests that don't need this context at two places introduces issues with maintainability. All tests now removed can be found in ./unittests.js --- lang/js/package.json | 3 -- lang/js/test/Helpers.js | 97 --------------------------------------------- lang/js/test/Message.js | 93 ------------------------------------------- lang/js/test/index.js | 28 ------------- lang/js/test/inputvalues.js | 39 ------------------ lang/js/test/mocha.opts | 4 -- 6 files changed, 264 deletions(-) delete mode 100644 lang/js/test/Helpers.js delete mode 100644 lang/js/test/Message.js delete mode 100644 lang/js/test/index.js delete mode 100644 lang/js/test/inputvalues.js delete mode 100644 lang/js/test/mocha.opts diff --git a/lang/js/package.json b/lang/js/package.json index a794188a..be52a554 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -4,9 +4,6 @@ "description": "javascript part of a nativeMessaging gnupg integration", "main": "src/index.js", "private": true, - "scripts": { - "test": "mocha" - }, "keywords": [], "author": "", "license": "", diff --git a/lang/js/test/Helpers.js b/lang/js/test/Helpers.js deleted file mode 100644 index 6b5a5382..00000000 --- a/lang/js/test/Helpers.js +++ /dev/null @@ -1,97 +0,0 @@ -/* 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 { expect } from "../node_modules/chai/chai"; -import { gpgme_error} from "../src/Errors"; -import { GPGME_Key } from "../src/Key"; -import { isLongId, isFingerprint, toKeyIdArray } from "../src/Helpers" -import { helper_params } from "./inputvalues"; - -export function Helpertest(){ - describe('Error Object handling', function(){ - 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(helper_params.invalidErrorCode); - 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(helper_params.validFingerprint); - expect(test0).to.be.true; - }); - it('isFingerprint(): invalid Fingerprint', function(){ - let test0 = isFingerprint(helper_params.invalidFingerprint); - expect(test0).to.be.false; - }); - }); - describe('Converting to Fingerprint', function(){ - it('Correct Inputs', function(){ - it('Fingerprint string', function(){ - let test0 = toKeyIdArray(helper_params.validFingerprint); - expect(test0).to.be.an('array'); - expect(test0).to.include(helper_params.validFingerprint); - }); - it('GPGME_Key', function(){ - expect(helper_params.validGPGME_Key).to.be.an.instanceof(GPGME_Key); - let test0 = toKeyIdArray(helper_params.validGPGME_Key); - expect(test0).to.be.an('array'); - expect(test0).to.include(helper_params.validGPGME_Key.fingerprint); - }); - it('Array of valid inputs', function(){ - let test0 = toKeyIdArray(helper_params.validKeys); - expect(test0).to.be.an('array'); - expect(test0).to.have.lengthOf(helper_params.validKeys.length); - }); - }); - describe('Incorrect inputs', function(){ - it('valid Long ID', function(){ - let test0 = toKeyIdArray(helper_params.validLongId); - expect(test0).to.be.empty; - }); - it('invalidFingerprint', function(){ - let test0 = toKeyIdArray(helper_params.invalidFingerprint); - expect(test0).to.be.empty; - }); - it('invalidKeyArray', function(){ - let test0 = toKeyIdArray(helper_params.invalidKeyArray); - expect(test0).to.be.empty; - }); - it('Partially invalid array', function(){ - let test0 = toKeyIdArray(helper_params.invalidKeyArray_OneBad); - expect(test0).to.be.an('array'); - expect(test0).to.have.lengthOf( - helper_params.invalidKeyArray_OneBad.length - 1); - }); - }); - }); -}; -export default Helpertest; \ No newline at end of file diff --git a/lang/js/test/Message.js b/lang/js/test/Message.js deleted file mode 100644 index a7dd3af5..00000000 --- a/lang/js/test/Message.js +++ /dev/null @@ -1,93 +0,0 @@ -/* 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 { expect } from "../node_modules/chai/chai"; -import { GPGME_Message, createMessage } from "../src/Message"; - -import { message_params as mp, helper_params as hp} from "./inputvalues"; - -export function Messagetest(){ - - describe('Message Object', function(){ - describe('correct initialization of an encrypt Message', function(){ - it('creating 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 mandatoy 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('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); - }); - }); - - describe('Incorrect initialization', function(){ - it('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('wrong parameter type in constructor', function(){ - let test0 = createMessage(mp.invalid_op_type); - expect(test0).to.be.an.instanceof(Error); - expect(test0.code).to.equal('PARAM_WRONG'); - }); - }); - - describe('Setting wrong parameters', function(){ - it('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'); - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); - } - }); - it('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]); - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); - } - }); - }); - }); -} -export default Messagetest; \ No newline at end of file diff --git a/lang/js/test/index.js b/lang/js/test/index.js deleted file mode 100644 index b8520515..00000000 --- a/lang/js/test/index.js +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 { Helpertest } from "./Helpers"; -import { Messagetest } from "./Message"; - -/** - * Unit tests. - */ - -Helpertest(); -Messagetest(); diff --git a/lang/js/test/inputvalues.js b/lang/js/test/inputvalues.js deleted file mode 100644 index 7752b82f..00000000 --- a/lang/js/test/inputvalues.js +++ /dev/null @@ -1,39 +0,0 @@ -import { gpgme_error } from "../src/Errors"; - -export const helper_params = { - validLongId: '0A0A0A0A0A0A0A0A', - validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3', - 'ADDBC303B6D31026F5EB4591A27EABDF283121BB', - 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], - validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', - validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', - '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], - invalidLongId: '9A9A7A7A8A9A9A7A7A8A', - invalidFingerprint: [{hello:'World'}], - invalidKeyArray: {curiosity:'uncat'}, - invalidKeyArray_OneBad: [ - '12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', - 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', - '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], - invalidErrorCode: 'Please type in all your passwords.', -} - -export const message_params = { - invalid_op_action : 'dance', - invalid_op_type : [234, 34, '<>'], - 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', gpgme_error('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] - } -} -export const whatever_params = { - four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'] -} - -export default { - helper_params: helper_params, - message_params: message_params -} \ No newline at end of file diff --git a/lang/js/test/mocha.opts b/lang/js/test/mocha.opts deleted file mode 100644 index 65adc1c3..00000000 --- a/lang/js/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---require babel-register ---reporter spec ---ui bdd ---colors -- cgit v1.2.3 From 6b4caee039af6fd97912426aff143745bf7e191a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 14 May 2018 19:02:49 +0200 Subject: js: Testing lare messages -- * Some assumption on messages were wrong. Now the tests use more reasonable sizes. * bigString now uses the full utf8-extent, with the exception of U+0000. This code gets dropped during the encryption-decryption process. --- lang/js/BrowserTestExtension/tests/encryptTest.js | 26 +++++++-------- lang/js/BrowserTestExtension/tests/inputvalues.js | 37 +++++++++++++++------- .../BrowserTestExtension/tests/longRunningTests.js | 17 ++++++++-- 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 5ef68a32..521ed276 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -36,7 +36,7 @@ describe('Encryption', function () { it('Successful encrypt 5 MB', function (done) { let prm = Gpgmejs.init(); - let data = bigString(5); + let data = fixedLengthString(5); prm.then(function (context) { context.encrypt( data, @@ -51,10 +51,9 @@ describe('Encryption', function () { }); }).timeout(10000); -/** it('Successful encrypt 20 MB', function (done) { let prm = Gpgmejs.init(); - let data = bigString(20); + let data = fixedLengthString(20); prm.then(function (context) { context.encrypt( data, @@ -68,12 +67,10 @@ describe('Encryption', function () { }); }); }).timeout(20000); -*/ -/** - it('Successful encrypt 30 MB', function (done) { - // TODO: There seems to be a limit imposed at least by chrome at about 21 MB + + it('Successful encrypt 50 MB', function (done) { let prm = Gpgmejs.init(); - let data = bigString(30); + let data = fixedLengthString(50); prm.then(function (context) { context.encrypt( data, @@ -87,7 +84,6 @@ describe('Encryption', function () { }); }); }).timeout(20000); -*/ it('Sending encryption without keys fails', function (done) { let prm = Gpgmejs.init(); @@ -120,7 +116,6 @@ describe('Encryption', function () { }); }); - it('Sending encryption with non existing keys fails', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { @@ -138,21 +133,24 @@ describe('Encryption', function () { }); }).timeout(5000);; - it('Overly large message ( >= 48MB) is rejected', function (done) { + it('Overly large message ( > 65MB) is rejected', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( - bigString(48), + fixedLengthString(65), inputvalues.encrypt.good.fingerprint).then(function (answer) { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an.instanceof(Error); - // TODO who is throwing the error here? - // It is not a GPGME_Error! + // expect(error.code).to.equal('GNUPG_ERROR'); + // TODO: there is a 64 MB hard limit at least in chrome at: + // chromium//extensions/renderer/messaging_util.cc: + // kMaxMessageLength context.connection.disconnect(); done(); }); }); }).timeout(8000); + // TODO check different valid parameter }); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 38ee6aad..52e3a7b0 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -59,14 +59,27 @@ var inputvalues = { } }; -// (Pseudo-)Random String from a Uint8Array, given approx. size in Megabytes -function bigString(megabytes){ - let maxlength = 1024 * 1024 * megabytes; +// (Pseudo-)Random String covering all of utf8. +function bigString(length){ + var uint = ''; + 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){ + let maxlength = 1024 * 1024 * megabytes / 2; let uint = new Uint8Array(maxlength); - for (let i= 0; i < maxlength; i++){ - uint[i] = Math.random() * Math.floor(256); + for (let i = 0; i < maxlength; i++){ + uint[i] = Math.floor(Math.random()* 256); } - return new TextDecoder('utf-8').decode(uint); + let td = new TextDecoder('ascii'); + let result = td.decode(uint); + return result; } // (Pseudo-)Random Uint8Array, given size in Megabytes @@ -82,19 +95,19 @@ function bigUint8(megabytes){ // (Pseudo-)Random string with very limited charset (ascii only, no control chars) function bigBoringString(megabytes){ let maxlength = 1024 * 1024 * megabytes; - let string = ''; + let string = []; let chars = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; for (let i= 0; i < maxlength; i++){ - string = string + chars[Math.floor(Math.random() * chars.length)]; + string.push(chars[Math.floor(Math.random() * chars.length)]); } - return string; + return string.join(''); } // Some String with simple chars, with different characteristics, but still // expected to occur in an averag message function slightlyLessBoringString(megabytes, set){ let maxlength = 1024 * 1024 * megabytes; - let string = ''; + let string = []; let chars = ''; if (set ===1 ) { chars = '\n\"\r \''; @@ -108,9 +121,9 @@ function slightlyLessBoringString(megabytes, set){ chars = '*<>\n\"\r§$%&/()=?`#+-{}[] \''; } for (let i= 0; i < maxlength; i++){ - string = string + chars[Math.floor(Math.random() * chars.length)]; + string.push(chars[Math.floor(Math.random() * chars.length)]); } - return string; + return string.join(''); } // Data encrypted with testKey diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index f67cbdf1..c95bebda 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -1,8 +1,8 @@ describe('Long running Encryption/Decryption', function () { - for (let i=0; i< 100; i++) { + for (let i=0; i < 100; i++) { it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { let prm = Gpgmejs.init(); - let data = bigString(2); + let data = bigString(2*1024*1024); prm.then(function (context) { context.encrypt(data, inputvalues.encrypt.good.fingerprint).then( @@ -17,13 +17,24 @@ describe('Long running Encryption/Decryption', function () { function(result){ expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); + if (result.data.length !== data.length) { + console.log('diff: ' + (result.data.length - data.length)); + for (let i=0; i < result.data.length; i++){ + if (result.data[i] !== data[i]){ + console.log('position: ' + i); + console.log('result : '+ result.data.charCodeAt(i) + result.data[i-2] + result.data[i-1] + result.data[i] + result.data[i+1] + result.data[i+2]); + console.log('original: ' + data.charCodeAt(i) + data[i-2] + data[i-1] + data[i] + data[i+1] + data[i+2]); + break; + } + } + } expect(result.data).to.equal(data); context.connection.disconnect(); done(); }); }); }); - }).timeout(5000); + }).timeout(8000); }; it('Successful encrypt 1 MB Uint8Array', function (done) { -- cgit v1.2.3 From ecad77263585cd5954758f797327d98232d880dc Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 22 May 2018 14:24:16 +0200 Subject: js: transfer encoding changes -- * Uint8Arrays are not supported for now there are unsolved issues in conversion, and they are lower priority * encrypt gains a new option to indicate that input values are base64 encoded * as decrypted values are always base64 encoded, the option base64 will not try to decode the result into utf, but leave it as it is --- .../tests/encryptDecryptTest.js | 133 +++++++++++++++++++-- .../BrowserTestExtension/tests/longRunningTests.js | 25 ---- .../BrowserTestExtension/tests/openpgpModeTest.js | 27 ----- lang/js/src/Connection.js | 34 +++--- lang/js/src/Message.js | 14 +++ lang/js/src/gpgmejs.js | 42 +++---- lang/js/src/permittedOperations.js | 4 +- 7 files changed, 173 insertions(+), 106 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 5c534039..2fe955e6 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -1,3 +1,4 @@ + /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -39,6 +40,22 @@ describe('Encryption and Decryption', function () { }); }); }); + + 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(); @@ -64,7 +81,7 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(5000); + }).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 + ')', @@ -96,21 +113,113 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(5000); + }).timeout(3000); }; - it('Decrypt simple non-ascii', function (done) { + it('Random data, as string', function (done) { + let data = bigString(1000); let prm = Gpgmejs.init(); prm.then(function (context) { - 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(); - }); + 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); + context.connection.disconnect(); + 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); + context.connection.disconnect(); + 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( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(data); + context.connection.disconnect(); + 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); + context.connection.disconnect(); + done(); + }); + }); + }); + }).timeout(3000); + + }); diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index c95bebda..4e55fd26 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -37,29 +37,4 @@ describe('Long running Encryption/Decryption', function () { }).timeout(8000); }; - it('Successful encrypt 1 MB Uint8Array', function (done) { - //TODO: this succeeds, but result may be bogus (String with byte values as numbers) - let prm = Gpgmejs.init(); - let data = bigUint8(1); - 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(5000); - }); diff --git a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js index 98b6e1d8..cccaf604 100644 --- a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js +++ b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js @@ -41,33 +41,6 @@ describe('Encrypting-Decrypting in openpgp mode, using a Message object', functi }); }); }); - it('Encrypt-Decrypt, sending Uint8Array as data', function (done) { - //TODO! fails. Reason is that atob<->btoa destroys the uint8Array, - // resulting in a string of constituyent numbers - // (error already occurs in encryption) - - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - let input = bigUint8(0.3); - expect(input).to.be.an.instanceof(Uint8Array); - context.encrypt({ - data: input, - publicKeys: 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({message:answer.data}).then(function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.an.instanceof(Uint8Array); - expect(result.data).to.equal(input); - context._GpgME.connection.disconnect(); - done(); - }); - }); - }); - }); it('Keys as Fingerprints', function(done){ let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); let input = inputvalues.encrypt.good.data_nonascii; diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 1931a55b..9c2a6428 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -93,7 +93,7 @@ export class Connection{ } let me = this; return new Promise(function(resolve, reject){ - let answer = new Answer(message.operation); + let answer = new Answer(message); let listener = function(msg) { if (!msg){ me._connection.onMessage.removeListener(listener) @@ -147,8 +147,9 @@ export class Connection{ */ class Answer{ - constructor(operation){ - this.operation = operation; + constructor(message){ + this.operation = message.operation; + this.expected = message.expected; } /** @@ -210,26 +211,31 @@ class Answer{ } /** - * @returns {Object} the assembled message. - * TODO: does not care yet if completed. + * @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){ - if (this._response.base64 == true){ - let respatob = atob(this._response[keys[i]]); - - let result = decodeURIComponent( - respatob.split('').map(function(c) { + 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); + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); - this._response[keys[i]] = result; } + } else { + msg[keys[i]] = this._response[keys[i]]; } } - return this._response; + return msg; } } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 6a59c3e0..932212a6 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -41,6 +41,7 @@ export class GPGME_Message { constructor(operation){ this.operation = operation; + this._expected = 'string'; } set operation (op){ @@ -58,6 +59,19 @@ export class GPGME_Message { return this._msg.op; } + set expected(string){ + if (string === 'base64'){ + this._expected = 'base64'; + } + } + + get expected() { + if (this._expected === "base64"){ + return this._expected; + } + return "string"; + } + /** * Sets a parameter for the message. Note that the operation has to be set * first, to be able to check if the parameter is permittted diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index d106f4f7..01cb92c3 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -64,11 +64,11 @@ export class GpgME { } /** - * @param {String|Uint8Array} data text/data to be encrypted as String/Uint8Array + * @param {String} data text/data to be encrypted as String * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message */ - encrypt(data, publicKeys, wildcard=false){ + encrypt(data, publicKeys, base64=false, wildcard=false){ let msg = createMessage('encrypt'); if (msg instanceof Error){ @@ -77,11 +77,14 @@ export class GpgME { // TODO temporary msg.setParameter('armor', true); msg.setParameter('always-trust', true); - + if (base64 === true) { + msg.setParameter('base64', true); + } let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); putData(msg, data); - if (wildcard === true){msg.setParameter('throw-keyids', true); + if (wildcard === true){ + msg.setParameter('throw-keyids', true); }; if (msg.isComplete === true){ return this.connection.post(msg); @@ -91,7 +94,8 @@ export class GpgME { } /** - * @param {String} data TODO Format: base64? String? Message with the encrypted data + * @param {String} data TODO base64? Message with the encrypted data + * @param {Boolean} base64 (optional) Response should stay base64 * @returns {Promise} decrypted message: data: The decrypted data. This may be base64 encoded. base64: Boolean indicating whether data is base64 encoded. @@ -100,11 +104,14 @@ export class GpgME { * @async */ - decrypt(data){ + decrypt(data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); + if (base64 === true){ + msg.expected = 'base64'; + } if (msg instanceof Error){ return Promise.reject(msg); } @@ -156,11 +163,9 @@ export class GpgME { } /** - * Sets the data of the message, converting Uint8Array to base64 and setting - * the base64 flag + * Sets the data of the message * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter - * @param {String} propertyname // TODO unchecked still */ function putData(message, data){ if (!message || !message instanceof GPGME_Message ) { @@ -168,30 +173,15 @@ function putData(message, data){ } if (!data){ return gpgme_error('PARAM_WRONG'); - } else if (data instanceof Uint8Array){ - message.setParameter('base64', true); - // TODO: btoa turns the array into a string - // of comma separated of numbers - // atob(data).split(',') would result in a "normal" array of numbers - // atob(btoa(data)).split(',') would result in a "normal" array of numbers - // would result in a "normal" array of numbers - message.setParameter ('data', btoa(data)); - } else if (typeof(data) === 'string') { - message.setParameter('base64', false); message.setParameter('data', data); } else if ( typeof(data) === 'object' && typeof(data.getText) === 'function' ){ let txt = data.getText(); - if (txt instanceof Uint8Array){ - message.setParameter('base64', true); - message.setParameter ('data', btoa(txt)); - } - else if (typeof(txt) === 'string'){ - message.setParameter('base64', false); - message.setParameter ('data', txt); + if (typeof(txt) === 'string'){ + message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 59597aaf..da46a1fd 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -51,7 +51,7 @@ export const permittedOperations = { array_allowed: true }, 'data': { - allowed: ['string', 'Uint8Array'] + allowed: ['string'] } }, optional: { @@ -103,7 +103,7 @@ export const permittedOperations = { pinentry: true, required: { 'data': { - allowed: ['string', 'Uint8Array'] + allowed: ['string'] } }, optional: { -- cgit v1.2.3 From f7ed80ff6a66f2c5ee6f1c3daebd597f4592733d Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 23 May 2018 12:56:23 +0200 Subject: js: remove openpgp mode -- * After discussion, that mode is not required, and can result in being quite misleading and a maintenance hassle later on. --- lang/js/BrowserTestExtension/index.html | 7 - lang/js/BrowserTestExtension/openpgpModeTest.html | 23 -- .../tests/inputValues_openpgpjs.js | 32 --- .../BrowserTestExtension/tests/openpgpModeTest.js | 169 ------------ lang/js/BrowserTestExtension/tests/startup.js | 16 -- lang/js/CHECKLIST | 7 +- lang/js/README | 5 - lang/js/src/Config.js | 8 +- lang/js/src/Errors.js | 8 - lang/js/src/gpgmejs.js | 4 +- lang/js/src/gpgmejs_openpgpjs.js | 301 --------------------- lang/js/src/index.js | 7 +- 12 files changed, 9 insertions(+), 578 deletions(-) delete mode 100644 lang/js/BrowserTestExtension/openpgpModeTest.html delete mode 100644 lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js delete mode 100644 lang/js/BrowserTestExtension/tests/openpgpModeTest.js delete mode 100644 lang/js/src/gpgmejs_openpgpjs.js diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html index c49aedae..05d413ba 100644 --- a/lang/js/BrowserTestExtension/index.html +++ b/lang/js/BrowserTestExtension/index.html @@ -34,13 +34,6 @@ Functionality tests with larger/longer running data sets. -
  • - - Testing openPGP mode. - - Please notice that, due to comparing - the inputs and outputs with openpgpjs objects, this test - requires a copy of openpgpjs in libs. -
  • diff --git a/lang/js/BrowserTestExtension/openpgpModeTest.html b/lang/js/BrowserTestExtension/openpgpModeTest.html deleted file mode 100644 index e7a12be9..00000000 --- a/lang/js/BrowserTestExtension/openpgpModeTest.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -

    Openpgp mode test

    -
    - - - - - - - - - - - - - - diff --git a/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js b/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js deleted file mode 100644 index 945955be..00000000 --- a/lang/js/BrowserTestExtension/tests/inputValues_openpgpjs.js +++ /dev/null @@ -1,32 +0,0 @@ -const openpgpInputs = { - pubKeyArmored: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' - + '\n' - + 'mQENBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf\n' - + 'PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE\n' - + 'BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c\n' - + 'PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870\n' - + '+O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M\n' - + 'yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAG0EHRlc3RAZXhhbXBsZS5v\n' - + 'cmeJAVQEEwEIAD4WIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8Jn\n' - + 'AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGw\n' - + 'Fq07W9N01HWULyhHKoMmcHL6rfZ64oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6\n' - + 'kaBe+wF6Kqir6udFSBW9rPcFg6/VZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rN\n' - + 'GkYo1JCBR0XdRJYCSX3yB4TWv/eXnZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k7\n' - + '0DevQeBsv+UjVXjWpNTZmPbvDnd995uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6U\n' - + 'unOf9Rlp1oMzdxMool/d1MlCxg2h3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ\n' - + '1J8Nsv87SZeEuQENBFrsKEkBCADjoEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6\n' - + 'yE+x2hk5FoQCajxKa/d4AVxOnJpdwhAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0p\n' - + 'O7bgAiZxkA6RHxtNqhpPnPQoXvUzkzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/\n' - + '8i9Taz67pdZwuJjac8qBuJHjzAo1bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt\n' - + '3RuqVGP316Fk+Sy2+60tC/HlX8jgMyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyL\n' - + 'UGKU+L8cB2g1PGGp2biBFWqZbudZoyRBet/0yH/zirBdQJw1ABEBAAGJATwEGAEI\n' - + 'ACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAKCRAjAWNe\n' - + '7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ5F32NDJ9\n' - + 'PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7Jq3e/4Iy\n' - + '0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02Lkr+2Cc/Q\n' - + 'k6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe23nntMSD\n' - + 'A+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t9Mmd7bM1\n' - + '+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT\n' - + '=QyY6\n' - + '-----END PGP PUBLIC KEY BLOCK-----\n' -}; diff --git a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js b/lang/js/BrowserTestExtension/tests/openpgpModeTest.js deleted file mode 100644 index cccaf604..00000000 --- a/lang/js/BrowserTestExtension/tests/openpgpModeTest.js +++ /dev/null @@ -1,169 +0,0 @@ -/* 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('Encrypting-Decrypting in openpgp mode, using a Message object', function () { - it('Simple Encrypt-Decrypt', function (done) { - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - context.encrypt({ - data: openpgp.message.fromText(inputvalues.encrypt.good.data), - publicKeys: inputvalues.encrypt.good.fingerprint} - ).then(function (answer) { - expect(answer).to.not.be.empty; - expect(answer).to.be.an("object"); - expect(answer.data).to.include('BEGIN PGP MESSAGE'); - expect(answer.data).to.include('END PGP MESSAGE'); - let msg = openpgp.message.fromText(answer.data); - context.decrypt({message:msg}).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); - context._GpgME.connection.disconnect(); - done(); - }); - }); - }); - }); - it('Keys as Fingerprints', function(done){ - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - let input = inputvalues.encrypt.good.data_nonascii; - prm.then(function (context) { - context.encrypt({ - data: input, - publicKeys: 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({message:answer.data}).then(function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - expect(result.data).to.equal(input); - context._GpgME.connection.disconnect(); - done(); - }); - }); - }); - }); - it('Keys as openpgp Keys', function(){ - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - let data = inputvalues.encrypt.good.data_nonascii; - let key = openpgp.key.readArmored(openpgpInputs.pubKeyArmored); - expect(key).to.be.an('object'); - prm.then(function (context) { - context.encrypt({ - data: data, - publicKeys: [key]} - ).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({message:answer.data}).then( function (result){ - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - expect(result.data).to.equal(data); - context._GpgME.connection.disconnect(); - done(); - }); - }); - }); - }); - it('Trying to send non-implemented parameters: passwords', function(done){ - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - let data = 'Hello World'; - let key = inputvalues.encrypt.good.fingerprint; - prm.then(function (context) { - context.encrypt({ - data: data, - publicKeys: [key], - passwords: 'My secret password'} - ).then( function(){}, - function(error){ - expect(error).to.be.an.instanceof(Error); - expect(error.code).equal('NOT_IMPLEMENTED'); - done(); - }); - }); - }); - it('Trying to send non-implemented parameters: signature', function(done){ - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - let data = 'Hello World'; - let key = inputvalues.encrypt.good.fingerprint; - prm.then(function (context) { - context.encrypt({ - data: data, - publicKeys: [key], - signature: {any: 'value'} - }).then( - function(){}, - function(error){ - expect(error).to.be.an.instanceof(Error); - expect(error.code).equal('NOT_IMPLEMENTED'); - done(); - }); - }); - }); -}); - -describe('Keyring in openpgp mode', function(){ - it('Check Existence and structure of Keyring after init', function(done){ - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - expect(context.Keyring).to.be.an('object'); - expect(context.Keyring.getPublicKeys).to.be.a('function'); - expect(context.Keyring.deleteKey).to.be.a('function'); - expect(context.Keyring.getDefaultKey).to.be.a('function'); - done(); - }); - }); - // TODO: gpgme key interface not yet there -}); - -describe('Decrypting and verification in openpgp mode', function(){ - it('Decrypt', function(){ - let msg = openpgp.message.fromText(inputvalues.encryptedData); - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - context.decrypt({message: msg}) - .then(function(answer){ - expect(answer.data).to.be.a('string'); - expect(result.data).to.equal('¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); - done(); - }); - }); - }); - it('Decryption attempt with bad data returns gnupg error', function(done){ - let msg = openpgp.message.fromText(bigString(0.1)); - let prm = Gpgmejs.init({api_style: 'gpgme_openpgpjs'}); - prm.then(function (context) { - context.decrypt({message: msg}) - .then( function(){}, - function(error){ - expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('GNUPG_ERROR'); - expect(error.message).to.be.a('string'); - // TBD: Type of error - done(); - }); - }); - }).timeout(4000); -}); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index a5614a83..5de70a6b 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -36,22 +36,6 @@ }); }); }); -describe('openpgp mode', function(){ - it('startup of openpgp mode returns the correct parameters', function(done){ - let prm = Gpgmejs.init({api_style:"gpgme_openpgpjs"}); - prm.then(function(context){ - expect(context).to.be.an('object'); - expect(context.connection).to.be.undefined; - expect(context.Keyring).to.be.an('object'); - expect(context.encrypt).to.be.a('function'); - expect(context.decrypt).to.be.a('function'); - done(); - }, function(error){ - expect(error).to.be.undefined; - done(); - }); - }); -}); describe('GPGME does not start with invalid parameters', function(){ for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST index 75664ae5..278f39dd 100644 --- a/lang/js/CHECKLIST +++ b/lang/js/CHECKLIST @@ -4,18 +4,15 @@ NativeConnection: receiving an answer [X] nativeConnection successfull on Chromium, chrome and firefox [*] nativeConnection successfull on Windows, macOS, Linux - [*] nativeConnection with delayed, multipart (> 1MB) answer - -replicating Openpgpjs API: + [X] nativeConnection with delayed, multipart (> 1MB) answer [*] Message handling (encrypt, decrypt verify, sign) [x] encrypt, decrypt [ ] verify [ ] sign [*] Key handling (import/export, modifying, status queries) - [ ] Configuration handling + [*] Configuration handling [ ] check for completeness - [*] handling of differences to openpgpjs Communication with other implementations diff --git a/lang/js/README b/lang/js/README index 5dc3f50b..b597adb2 100644 --- a/lang/js/README +++ b/lang/js/README @@ -12,11 +12,6 @@ To create a current version of the package, the command is If you want a more debuggable (i.e. not minified) build, just change the mode in webpack.conf.js. -TODO: gpgme_openpgpjs aims to offer an API similar to openpgpjs, throwing errors -if some part of the API is not implemented, 'translating' objects if possible. -This will be incorporated into the build process later, for now it is a line to -uncomment in src/index.js - Demo WebExtension: As soon as a bundled webpack is in dist/ the gpgmejs folder can just be included in the extensions tab of the browser in diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js index e18728de..e85bbb82 100644 --- a/lang/js/src/Config.js +++ b/lang/js/src/Config.js @@ -19,13 +19,13 @@ */ export const availableConf = { - api_style: ['gpgme', 'gpgme_openpgpjs'], null_expire_is_never: [true, false], - unconsidered_params: ['warn','reject', 'ignore'], + // cachedKeys: Some Key info will not be queried on each invocation, + // manual refresh by Key.refresh() + cachedKeys: [true, false] }; export const defaultConf = { - api_style: 'gpgme', null_expire_is_never: false, - unconsidered_params: 'reject', + cachedKeys: false }; \ No newline at end of file diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index b71004a5..bfe3a2f4 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -80,14 +80,6 @@ const err_list = { msg: 'An parameter was set that has no effect in gpgmejs', type: 'warning' }, - 'NOT_IMPLEMENTED': { - msg: 'A openpgpjs parameter was submitted that is not implemented', - type: 'error' - }, - 'NOT_YET_IMPLEMENTED': { - msg: 'Support of this is probable, but it is not implemented yet', - type: 'error' - }, 'GENERIC_ERROR': { msg: 'Unspecified error', type: 'error' diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 01cb92c3..3aa5957a 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -54,8 +54,8 @@ export class GpgME { } set Keyring(keyring){ - if (ring && ring instanceof GPGME_Keyring){ - this._Keyring = ring; + if (keyring && keyring instanceof GPGME_Keyring){ + this._Keyring = keyring; } } diff --git a/lang/js/src/gpgmejs_openpgpjs.js b/lang/js/src/gpgmejs_openpgpjs.js deleted file mode 100644 index 9c8cd2cc..00000000 --- a/lang/js/src/gpgmejs_openpgpjs.js +++ /dev/null @@ -1,301 +0,0 @@ -/* 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+ - */ - -/** - * This is a compatibility API to be used as openpgpjs syntax. - * Non-implemented options will throw an error if set (not null or undefined) - * TODO Some info about differences - */ - - import { GpgME } from "./gpgmejs"; - import {GPGME_Keyring} from "./Keyring"; - import { GPGME_Key, createKey } from "./Key"; - import { isFingerprint } from "./Helpers"; - import { gpgme_error } from "./Errors"; -import { Connection } from "./Connection"; - - - export class GpgME_openpgpmode { - - constructor(connection, config = {}){ - this.initGpgME(connection, config); - } - - get Keyring(){ - if (this._keyring){ - return this._keyring; - } - return undefined; - } - - initGpgME(connection, config = {}){ - if (connection && typeof(config) ==='object'){ - this._config = config; - if (!this._GpgME){ - this._GpgME = new GpgME(connection, config); - } - if (!this._keyring){ - this._keyring = new GPGME_Keyring_openpgpmode(connection); - } - } - } - - /** - * Encrypt Message - * Supported: - * @param {String|Message} data - * an openpgp Message is accepted here. - * @param {Key|Array} publicKeys - * //Strings of Fingerprints - * @param {Boolean} wildcard - * TODO: - * @param {Key|Array} privateKeys // -> encryptsign - * @param {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress - * @param {Boolean} armor // TODO base64 switch - * @param {Boolean} detached // --> encryptsign - * unsupported: - * @param {String|Array} passwords - * @param {Object} sessionKey - * @param {Signature} signature - * @param {Boolean} returnSessionKey - * @param {String} filename - * - * Can be set, but will be ignored: - * - * @returns {Promise} - * {data: ASCII armored message, - * signature: detached signature if 'detached' is true - * } - * @async - * @static - */ - encrypt(options) { - if (!options || typeof(options) !== 'object'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - if (options.passwords - || options.sessionKey - || options.signature - || options.returnSessionKey - || (options.hasOwnProperty('date') && options.date !== null) - ){ - return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); - } - if ( options.privateKeys - || options.compression - || (options.hasOwnProperty('armor') && options.armor === false) - || (options.hasOwnProperty('detached') && options.detached == true) - ){ - return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - } - if (options.filename){ - if (this._config.unconsidered_params === 'warn'){ - gpgme_error('PARAM_IGNORED'); - } else if (this._config.unconsidered_params === 'error'){ - return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); - } - } - return this._GpgME.encrypt( - options.data, options.publicKeys, options.wildcard); - } - - /** Decrypt Message - * supported openpgpjs parameters: - * @param {Message|String} message Message object from openpgpjs - * Unsupported: - * @param {String|Array} passwords - * @param {Key|Array} privateKeys - * @param {Object|Array} sessionKeys - * Not yet supported, but planned - * @param {String} format (optional) return data format either as 'utf8' or 'binary' - * @param {Signature} signature (optional) detached signature for verification - * Ignored values: can be safely set, but have no effect - * @param {Date} date - * @param {Key|Array} publicKeys - * - * @returns {Promise} decrypted and verified message in the form: - * { data:String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } - * @async - * @static - */ - decrypt(options) { - if (options.passwords - || options.sessionKeys - || options.privateKeys - ){ - return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); - } - if ((options.hasOwnProperty('format') && options.format !== 'utf8') - || options.signature - ){ - return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - } - if ((options.hasOwnProperty('date') && options.date !== null) - || options.publicKeys - ){ - if (this._config.unconsidered_params === 'warn'){ - GPMGEJS_Error('PARAM_IGNORED'); - } else if (this._config.unconsidered_params === 'reject'){ - return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); - } - } - return this._GpgME.decrypt(options.message); - - // TODO: translate between: - // openpgp: - // { data:Uint8Array|String, - // filename:String, - // signatures:[{ keyid:String, valid:Boolean }] } - // and gnupg: - // data: The decrypted data. This may be base64 encoded. - // base64: Boolean indicating whether data is base64 encoded. - // mime: A Boolean indicating whether the data is a MIME object. - // info: An optional object with extra information. - } -} - -/** - * Translation layer offering basic Keyring API to be used in Mailvelope. - * It may still be changed/expanded/merged with GPGME_Keyring - */ -class GPGME_Keyring_openpgpmode { - constructor(connection){ - this._gpgme_keyring = new GPGME_Keyring(connection); - } - - /** - * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This - * includes keys openpgpjs considers 'private' (usable for signing), with - * the difference that Key.armored will NOT contain any secret information. - * Please also note that a GPGME_Key does not offer full openpgpjs- Key - * compatibility. - * @returns {Array} - * //TODO: Check if IsDefault is also always hasSecret - * TODO Check if async is required - */ - getPublicKeys(){ - return translateKeys( - this._gpgme_keyring.getKeys(null, true)); - } - - /** - * Returns the Default Key used for crypto operation in gnupg. - * Please note that the armored property does not contained secret key blocks, - * despite secret blocks being part of the key itself. - * @returns {Promise } - */ - getDefaultKey(){ - this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){ - if (result.length === 1){ - return Promise.resolve( - translateKeys(result)[0]); - } - else { - // TODO: Can there be "no default key"? - // TODO: Can there be several default keys? - return gpgme_error('TODO'); - } - }, function(error){ - //TODO - }); - } - - /** - * Deletes a Key - * @param {Object} Object identifying key - * @param {String} key.fingerprint - fingerprint of the to be deleted key - * @param {Boolean} key.secret - indicator if private key should be deleted as well - - * @returns {Promise., Error>} TBD: Not sure what is wanted - TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint - TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists - */ - deleteKey(key){ - if (typeof(key) !== "object"){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - let key_to_delete = createKey(key.fingerprint, this._gpgme_keyring_GpgME); - return key_to_delete.deleteKey(key.secret); - } -} - -/** - * TODO error handling. - * Offers the Key information as the openpgpmode wants - */ -class GPGME_Key_openpgpmode { - constructor(value, connection){ - this.init(value, connection); - } - - /** - * Can be either constructed using an existing GPGME_Key, or a fingerprint - * and a connection - * @param {String|GPGME_Key} value - * @param {Connection} connection - */ - init (value, connection){ - if (!this._GPGME_Key && value instanceof GPGME_Key){ - this._GPGME_Key = value; - } else if (!this._GPGME_Key && isFingerprint(value) && - connection instanceof Connection){ - this._GPGME_Key = createKey(value, connection); - } - } - - get fingerprint(){ - return this._GPGME_Key.fingerprint; - } - - get armor(){ - return this._GPGME_Key.armored; - } - - get secret(){ - return this._GPGME_Key.hasSecret; - } - - get default(){ - return this._GPGME_Key.isDefault; - } -} - -/** - * creates GPGME_Key_openpgpmode from GPGME_Keys - * @param {GPGME_Key|Array} input keys - * @returns {Array} - */ -function translateKeys(input){ - //TODO: does not check if inpout is okay! - if (!input){ - return null; - } - if (!Array.isArray(input)){ - input = [input]; - } - let resultset = []; - for (let i=0; i< input.length; i++) { - resultset.push(new GPGME_Key_openpgpmode(input[i])); - } - return resultset; -} \ No newline at end of file diff --git a/lang/js/src/index.js b/lang/js/src/index.js index fc406c66..8527b3f3 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -20,7 +20,6 @@ import { GpgME } from "./gpgmejs"; import { gpgme_error } from "./Errors"; -import { GpgME_openpgpmode } from "./gpgmejs_openpgpjs"; import { Connection } from "./Connection"; import { defaultConf, availableConf } from "./Config"; @@ -43,11 +42,7 @@ function init(config){ reject(gpgme_error('CONN_NO_CONNECT')); } if (connection.isConnected === true){ - if (_conf.api_style && _conf.api_style === 'gpgme_openpgpjs'){ - resolve(new GpgME_openpgpmode(connection, _conf)); - } else { - resolve(new GpgME(connection)); - } + resolve(new GpgME(connection, _conf)); } else { reject(gpgme_error('CONN_NO_CONNECT')); } -- cgit v1.2.3 From a4ba80c553c2ac42f9e311344302c04ec9aa715b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 24 May 2018 15:16:18 +0200 Subject: js: adding sign method -- * src/gpgmejs.js: method, update in src/permittedOperations * basic testing in BrowsertestExtension --- lang/js/BrowserTestExtension/browsertest.html | 1 + lang/js/src/gpgmejs.js | 38 +++++++++++++++++++++++ lang/js/src/permittedOperations.js | 44 ++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index c379ef53..3d81a9ec 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -17,6 +17,7 @@ + diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 3aa5957a..1e76655e 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -120,6 +120,44 @@ export class GpgME { } + sign(data, keys, mode='clearsign', base64=false) { //sender + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let key_arr = toKeyIdArray(keys); + if (key_arr.length === 0){ + return Promise.reject(gpgme_error('MSG_NO_KEYS')); + } + let msg = createMessage('sign'); + + msg.setParameter('keys', key_arr); + if (base64 === true){ + msg.setParameter('base64', true); + } + msg.setParameter('mode', mode); + putData(msg, data); + if (mode === 'detached') { + msg.expected = 'base64'; + } + let me = this; + return new Promise(function(resolve,reject) { + me.connection.post(msg).then( function(message) { + if (mode === 'clearsign'){ + resolve({ + data: message.data} + ); + } else if (mode === 'detached') { + resolve({ + data: data, + signature: message.data + }); + } + }, function(error){ + reject(error); + }) + }); + } + deleteKey(key, delete_secret = false, no_confirm = false){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); let msg = createMessage('deletekey'); diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index da46a1fd..aa02a8bc 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -130,6 +130,49 @@ export const permittedOperations = { // }] } }, + + sign: { + pinentry: true, + required: { + 'data': { + allowed: ['string']}, + 'keys': { + allowed: ['string'], + array_allowed: true + } + }, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'chunksize': { + allowed: ['number'], + }, + 'sender': { + allowed: ['string'], + }, + 'mode': { + allowed: ['string'], + allowed_data: ['detached', 'clearsign'] // TODO 'opaque' not used + }, + 'base64': { + allowed: ['boolean'] + }, + 'armor': { + allowed: ['boolean'] + }, + }, + answer: { + type: ['signature', 'ciphertext'], + data: ['data'], // Unless armor mode is used a Base64 encoded binary + // signature. In armor mode a string with an armored + // OpenPGP or a PEM message. + params: ['base64'] + } + }, + + /** TBD: querying the Key's information (keyinfo) TBD name: { required: { @@ -212,6 +255,5 @@ export const permittedOperations = { *TBD get armored secret different treatment from keyinfo! * TBD key modification? * encryptsign: TBD - * verify: TBD */ } -- cgit v1.2.3 From eff27d6387b1cad2ef9901fa03dbee2ea86c786a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 25 May 2018 11:53:24 +0200 Subject: js: use version operation for connection checks -- * src/Connection.js: isConnected was renamed to checkConnection, that returns a promise with either version information or Boolean * Connection checks have been adapted to reflect that checkConnection returns a Promise * BrowsertestExtension: tests/signTest.js was missing from my last commit --- lang/js/BrowserTestExtension/tests/signTest.js | 58 +++++++++++++++++++++++++ lang/js/BrowserTestExtension/tests/startup.js | 3 -- lang/js/src/Connection.js | 60 ++++++++++++++++---------- lang/js/src/Errors.js | 4 -- lang/js/src/Keyring.js | 58 ++++++++++++------------- lang/js/src/gpgmejs.js | 8 +--- lang/js/src/index.js | 22 ++++------ lang/js/unittests.js | 57 ++++++++++++------------ 8 files changed, 162 insertions(+), 108 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/signTest.js diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js new file mode 100644 index 00000000..e3323721 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -0,0 +1,58 @@ +/* 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('Signing', function () { + it('Sign a message', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = bigString(100); + context.sign( + 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 SIGNATURE'); + expect(answer.data).to.include('END PGP SIGNATURE'); + expect(answer.data).to.include(data); + context.connection.disconnect(); + done(); + }); + }); + }); + it('Detached sign a message', function (done) { + let prm = Gpgmejs.init(); + prm.then(function (context) { + let data = bigString(100); + context.sign( + data, + inputvalues.encrypt.good.fingerprint, + 'detached' + ).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include(data); + expect(answer.signature).to.be.a('string'); + expect(answer.signature).to.be.a('string'); + context.connection.disconnect(); + done(); + }); + }); + }); + +}); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 5de70a6b..ebecf4fb 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -30,9 +30,6 @@ expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); done(); - }, function(errorr){ - expect(error).to.be.undefined; - done(); }); }); }); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 9c2a6428..07df5def 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -25,7 +25,7 @@ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from "./Errors" -import { GPGME_Message } from "./Message"; +import { GPGME_Message, createMessage } from "./Message"; /** * A Connection handles the nativeMessaging interaction. @@ -34,18 +34,42 @@ export class Connection{ constructor(){ this.connect(); - let me = this; } /** - * (Simple) Connection check. - * @returns {Boolean} true if the onDisconnect event has not been fired. - * Please note that the event listener of the port takes some time - * (5 ms seems enough) to react after the port is created. Then this will - * return undefined + * 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" */ - get isConnected(){ - return this._isConnected; + 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); + }); + }); + } } /** @@ -54,6 +78,7 @@ export class Connection{ disconnect() { if (this._connection){ this._connection.disconnect(); + this._connection = null; } } @@ -61,17 +86,8 @@ export class Connection{ * Opens a nativeMessaging port. */ connect(){ - if (this._isConnected === true){ - gpgme_error('CONN_ALREADY_CONNECTED'); - } else { - this._isConnected = true; + if (!this._connection){ this._connection = chrome.runtime.connectNative('gpgmejson'); - let me = this; - this._connection.onDisconnect.addListener( - function(){ - me._isConnected = false; - } - ); } } @@ -82,8 +98,8 @@ export class Connection{ * information. */ post(message){ - if (!this.isConnected){ - return Promise.reject(gpgme_error('CONN_DISCONNECTED')); + if (!this._connection) { + } if (!message || !message instanceof GPGME_Message){ return Promise.reject(gpgme_error('PARAM_WRONG'), message); @@ -199,7 +215,7 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = []; } - this._response.push(msg[key]); + this._response[key].push(msg[key]); } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index bfe3a2f4..7e98f319 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -25,10 +25,6 @@ const err_list = { + ' established.', type: 'error' }, - 'CONN_DISCONNECTED': { - msg:'Connection with the nativeMessaging host was lost.', - type: 'error' - }, 'CONN_EMPTY_GPG_ANSWER':{ msg: 'The nativeMessaging answer was empty.', type: 'error' diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 4596035a..80792f77 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -36,10 +36,7 @@ export class GPGME_Keyring { } get connection(){ if (this._connection instanceof Connection){ - if (this._connection.isConnected){ - return this._connection; - } - return gpgme_error('CONN_DISCONNECTED'); + return this._connection; } return gpgme_error('CONN_NO_CONNECT'); } @@ -51,36 +48,35 @@ export class GPGME_Keyring { * */ getKeys(pattern, include_secret){ - let msg = createMessage('listkeys'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - if (pattern && typeof(pattern) === 'string'){ - msg.setParameter('pattern', pattern); - } - if (include_secret){ - msg.setParameter('with-secret', true); - } let me = this; - - this.connection.post(msg).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; + return new Promise(function(resolve, reject) { + let msg; + msg = createMessage('listkeys'); + if (pattern && typeof(pattern) === 'string'){ + msg.setParameter('pattern', pattern); } - for (let i=0; i < fpr_list.length; i++){ - let newKey = new GPGME_Key(fpr_list[i], me._connection); - if (newKey instanceof GPGME_Key){ - resultset.push(newKey); - } + if (include_secret){ + msg.setParameter('with-secret', true); } - return Promise.resolve(resultset); - }, function(error){ - //TODO error handling + me.connection.post(msg).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], me._connection); + if (newKey instanceof GPGME_Key){ + resultset.push(newKey); + } + } + resolve(resultset); + }, function(error){ + reject(error); + }); }); } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 1e76655e..c182c175 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -44,13 +44,7 @@ export class GpgME { } get connection(){ - if (this._connection){ - if (this._connection.isConnected === true){ - return this._connection; - } - return undefined; - } - return undefined; + return this._connection; } set Keyring(keyring){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 8527b3f3..7f969fee 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -34,20 +34,16 @@ function init(config){ } return new Promise(function(resolve, reject){ let connection = new Connection; - // TODO: Delayed reaction is ugly. We need to listen to the port's - // event listener in isConnected, but in some cases this takes some - // time (<5ms) to disconnect if there is no successfull connection. - let delayedreaction = function(){ - if (connection === undefined) { + connection.checkConnection(false).then( + function(result){ + if (result === true) { + resolve(new GpgME(connection, _conf)); + } else { + reject(gpgme_error('CONN_NO_CONNECT')); + } + }, function(error){ reject(gpgme_error('CONN_NO_CONNECT')); - } - if (connection.isConnected === true){ - resolve(new GpgME(connection, _conf)); - } else { - reject(gpgme_error('CONN_NO_CONNECT')); - } - }; - setTimeout(delayedreaction, 5); + }); }); } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index c437d599..06b2b23a 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -39,34 +39,29 @@ function unittests (){ it('Connecting', function(done) { let conn0 = new Connection; - let delayed = function(){ - expect(conn0.isConnected).to.be.true; - expect(conn0.connect).to.be.a('function'); + 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(); - }; - setTimeout(delayed, 5); + }); }); it('Disconnecting', function(done) { let conn0 = new Connection; - let delayed = function(){ - conn0.disconnect(); // TODO fails! - expect(conn0.isConnected).to.be.false; - done(); - }; - setTimeout(delayed, 5); + 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(); + }); + }); }); - - // broken - // it('Connect info still only available after a delay', function(done){ - // // if false, all delayed connections can be refactored - // let conn0 = new Connection; - // expect(conn0.isConnected).to.be.undefined; - // // - // }) }); describe('Error Object handling', function(){ @@ -181,14 +176,17 @@ function unittests (){ // TODO not implemented yet: Further Key functionality }); - it('Key can use the connection', function(){ + it('Key can use the connection', function(done){ let conn = new Connection; let key = createKey(hp.validFingerprint, conn); - - expect(key.connection.isConnected).to.be.true; - - key.connection.disconnect(); - expect(key.connection.isConnected).to.be.false; + key.connection.checkConnection(false).then(function(result){ + expect(result).to.be.true; + key.connection.disconnect(); + key.connection.checkConnection(false).then(function(result2){ + expect(result2).to.be.false; + done(); + }); + }); }); it('createKey returns error if parameters are wrong', function(){ @@ -232,12 +230,15 @@ function unittests (){ it('Keyring should return errors if not connected', function(){ let keyring = new GPGME_Keyring; - expect(keyring).to.be.an.instanceof(GPGME_Keyring); expect(keyring.connection).to.be.an.instanceof(Error); expect(keyring.connection.code).to.equal('CONN_NO_CONNECT'); - expect(keyring.getKeys).to.be.an.instanceof(Error); - expect(keyring.getkeys.code).to.equal('CONN_NO_CONNECT'); + // not yet implemented: + // keyring.getKeys().then( + // function(result){}, + //function(reject){ + // expect(reject).to.be.an.instanceof(Error); + // done(); }); //TODO not yet implemented: // getKeys(pattern, include_secret) //note: pattern can be null -- cgit v1.2.3 From 7a73d88aba106d571f121dc3230864c81a76e5db Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 25 May 2018 19:02:18 +0200 Subject: js: implement Key handling (1) -- * Keys can now be queried for information. Onne version queries gnug directly (asynchronous Promise in javascript terms), the cached version refreshes on demand. * Small fixes: src/Connection.js joins answers that stay json properly now --- lang/js/src/Connection.js | 8 +- lang/js/src/Errors.js | 4 + lang/js/src/Helpers.js | 5 +- lang/js/src/Key.js | 433 ++++++++++++++++++++++++++----------- lang/js/src/permittedOperations.js | 95 ++++---- lang/js/unittest_inputvalues.js | 14 +- lang/js/unittests.js | 67 +++++- 7 files changed, 448 insertions(+), 178 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 07df5def..3b442622 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -215,7 +215,13 @@ class Answer{ if (!this._response.hasOwnProperty(key)){ this._response[key] = []; } - this._response[key].push(msg[key]); + if (Array.isArray(msg[key])) { + for (let i=0; i< msg[key].length; i++) { + this._response[key].push(msg[key][i]); + } + } else { + this._response[key].push(msg[key][i]); + } } else { return gpgme_error('CONN_UNEXPECTED_ANSWER'); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 7e98f319..fa8a4efe 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -67,6 +67,10 @@ const err_list = { msg:'Key object is invalid', type: 'error' }, + 'KEY_NOKEY': { + msg:'This key does not exist in GPG', + type: 'error' + }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index fd0e7200..b26f40fb 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -90,10 +90,11 @@ function hextest(key, len){ export function isFingerprint(string){ return hextest(string, 40); }; + /** - * TODO no usage; check if the input is a valid Hex string with a length of 16 + * check if the input is a valid Hex string with a length of 16 */ -function isLongId(string){ +export function isLongId(string){ return hextest(string, 16); }; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 075a190e..7d3d82b1 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -26,13 +26,19 @@ * */ -import { isFingerprint } from './Helpers' +import { isFingerprint, isLongId } from './Helpers' import { gpgme_error } from './Errors' import { createMessage } from './Message'; import { permittedOperations } from './permittedOperations'; import { Connection } from './Connection'; - +/** + * Validates the fingerprint, and checks for tha availability of a connection. + * If both are available, a Key will be returned. + * @param {String} fingerprint + * @param {Object} parent Either a Connection, or the invoking object with a + * Connection (e.g. Keyring) + */ export function createKey(fingerprint, parent){ if (!isFingerprint(fingerprint)){ return gpgme_error('PARAM_WRONG'); @@ -47,6 +53,9 @@ export function createKey(fingerprint, parent){ } } +/** + * Representing the Keys as stored in GPG + */ export class GPGME_Key { constructor(fingerprint, connection){ @@ -63,7 +72,7 @@ export class GPGME_Key { } get connection(){ - if (!this._fingerprint){ + if (!this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } if (!this._connection instanceof Connection){ @@ -74,171 +83,345 @@ export class GPGME_Key { } set fingerprint(fpr){ - if (isFingerprint(fpr) === true && !this._fingerprint){ - this._fingerprint = fpr; + if (isFingerprint(fpr) === true) { + if (this._data === undefined) { + this._data = {fingerprint: fpr}; + } else { + if (this._data.fingerprint === undefined){ + this._data.fingerprint = fpr; + } + } } } get fingerprint(){ - if (!this._fingerprint){ + if (!this._data || !this._data.fingerprint){ return gpgme_error('KEY_INVALID'); } - return this._fingerprint; + return this._data.fingerprint; } /** - * hasSecret returns true if a secret subkey is included in this Key + * + * @param {Object} data Bulk set data for this key, with the Object as sent + * by gpgme-json. + * @returns {GPGME_Key|GPGME_Error} The Key object itself after values have + * been set */ - get hasSecret(){ - return this.checkKey('secret'); - } - - get isRevoked(){ - return this.checkKey('revoked'); - } - - get isExpired(){ - return this.checkKey('expired'); - } - - get isDisabled(){ - return this.checkKey('disabled'); - } - - get isInvalid(){ - return this.checkKey('invalid'); - } - - get canEncrypt(){ - return this.checkKey('can_encrypt'); - } + setKeydata(data){ + if (this._data === undefined) { + this._data = {}; + } + if ( + typeof(data) !== 'object') { + return gpgme_error('KEY_INVALID'); + } + if (!this._data.fingerprint && isFingerprint(data.fingerprint)){ + if (data.fingerprint !== this.fingerprint){ + return gpgme_error('KEY_INVALID'); + } + this._data.fingerprint = data.fingerprint; + } else if (this._data.fingerprint !== data.fingerprint){ + return gpgme_error('KEY_INVALID'); + } - get canSign(){ - return this.checkKey('can_sign'); - } + let booleans = ['expired', 'disabled','invalid','can_encrypt', + 'can_sign','can_certify','can_authenticate','secret', + 'is_qualified']; + for (let b =0; b < booleans.length; b++) { + if ( + !data.hasOwnProperty(booleans[b]) || + typeof(data[booleans[b]]) !== 'boolean' + ){ + return gpgme_error('KEY_INVALID'); + } + this._data[booleans[b]] = data[booleans[b]]; + } + if (typeof(data.protocol) !== 'string'){ + return gpgme_error('KEY_INVALID'); + } + // TODO check valid protocols? + this._data.protocol = data.protocol; - get canCertify(){ - return this.checkKey('can_certify'); - } + if (typeof(data.owner_trust) !== 'string'){ + return gpgme_error('KEY_INVALID'); + } + // TODO check valid values? + this._data.owner_trust = data.owner_trust; - get canAuthenticate(){ - return this.checkKey('can_authenticate'); - } + // TODO: what about origin ? + if (!Number.isInteger(data.last_update)){ + return gpgme_error('KEY_INVALID'); + } + this._data.last_update = data.last_update; - get isQualified(){ - return this.checkKey('is_qualified'); - } + this._data.subkeys = []; + if (data.hasOwnProperty('subkeys')){ + if (!Array.isArray(data.subkeys)){ + return gpgme_error('KEY_INVALID'); + } + for (let i=0; i< data.subkeys.length; i++) { + this._data.subkeys.push( + new GPGME_Subkey(data.subkeys[i])); + } + } - get armored(){ - let msg = createMessage ('export_key'); - msg.setParameter('armor', true); - if (msg instanceof Error){ - return gpgme_error('KEY_INVALID'); + this._data.userids = []; + if (data.hasOwnProperty('userids')){ + if (!Array.isArray(data.userids)){ + return gpgme_error('KEY_INVALID'); + } + for (let i=0; i< data.userids.length; i++) { + this._data.userids.push( + new GPGME_UserId(data.userids[i])); + } } - this.connection.post(msg).then(function(result){ - return result.data; - }); - // TODO return value not yet checked. Should result in an armored block - // in correct encoding + return this; } /** - * TODO returns true if this is the default key used to sign + * Query any property of the Key list + * (TODO: armor not in here, might be unexpected) + * @param {String} property Key property to be retreived + * @param {*} cached (optional) if false, the data will be directly queried + * from gnupg. + * @returns {*|Promise<*>} the value, or if not cached, a Promise + * resolving on the value */ - get isDefault(){ - throw('NOT_YET_IMPLEMENTED'); + get(property, cached=true) { + if (cached === false) { + let me = this; + return new Promise(function(resolve, reject) { + me.refreshKey().then(function(key){ + resolve(key.get(property, true)); + }, function(error){ + reject(error); + }); + }); + } else { + if (!this._data.hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); + } else { + return (this._data[property]); + } + } } /** - * get the Key's subkeys as GPGME_Key objects - * @returns {Array} + * 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 */ - 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} The user ids associated with this key + * + * @param {String} property Information to request + * @returns {String | Number} + * TODO: date properties are numbers with Date in seconds */ - get userIds(){ - return this.checkKey('uids'); + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } + } +} + +class GPGME_UserId { + + constructor(data){ + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++) { + this.setProperty(keys[i], data[keys[i]]); + } + } + + setProperty(property, value){ + if (!this._data){ + this._data = {}; + } + if (validUserIdProperties.hasOwnProperty(property)){ + if (validUserIdProperties[property](value) === true) { + this._data[property] = value; + } + } } /** - * @returns {String} The public key algorithm supported by this subkey + * + * @param {String} property Information to request + * @returns {String | Number} + * TODO: date properties are numbers with Date in seconds */ - get pubkey_algo(){ - return this.checkKey('pubkey_algo'); + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } } +} - /** - * generic function to query gnupg information on a key. - * @param {*} property The gpgme-json property to check. - * TODO: check if Promise.then(return) - */ - checkKey(property){ - if (!this._fingerprint){ - return gpgme_error('KEY_INVALID'); +const validUserIdProperties = { + 'revoked': function(value){ + return typeof(value) === 'boolean'; + }, + 'invalid': function(value){ + return typeof(value) === 'boolean'; + }, + 'uid': function(value){ + if (typeof(value) === 'string' || value === ''){ + return true;; } - return gpgme_error('NOT_YET_IMPLEMENTED'); - // TODO: async is not what is to be ecpected from Key information :( - if (!property || typeof(property) !== 'string' || - !permittedOperations['keyinfo'].hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); - } - let msg = createMessage ('keyinfo'); - if (msg instanceof Error){ - return gpgme_error('PARAM_WRONG'); - } - msg.setParameter('fingerprint', this.fingerprint); - this.connection.post(msg).then(function(result, error){ - if (error){ - return gpgme_error('GNUPG_ERROR',error.msg); - } else if (result.hasOwnProperty(property)){ - return result[property]; - } - else if (property == 'secret'){ - // TBD property undefined means "not true" in case of secret? - return false; - } else { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - }, function(error){ - return gpgme_error('GENERIC_ERROR'); - }); + return false; + }, + 'validity': function(value){ + if (typeof(value) === 'string'){ + return true;; + } + return false; + }, + 'name': function(value){ + if (typeof(value) === 'string' || value === ''){ + return true;; + } + return false; + }, + 'email': function(value){ + if (typeof(value) === 'string' || value === ''){ + return true;; + } + return false; + }, + 'address': function(value){ + if (typeof(value) === 'string' || value === ''){ + return true;; + } + return false; + }, + 'comment': function(value){ + if (typeof(value) === 'string' || value === ''){ + return true;; + } + return false; + }, + 'origin': function(value){ + return Number.isInteger(value); + }, + 'last_update': function(value){ + return Number.isInteger(value); } -}; \ No newline at end of file +}; + +const validSubKeyProperties = { + 'invalid': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_encrypt': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_sign': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_certify': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_authenticate': function(value){ + return typeof(value) === 'boolean'; + }, + 'secret': function(value){ + return typeof(value) === 'boolean'; + }, + 'is_qualified': function(value){ + return typeof(value) === 'boolean'; + }, + 'is_cardkey': function(value){ + return typeof(value) === 'boolean'; + }, + 'is_de_vs': function(value){ + return typeof(value) === 'boolean'; + }, + 'pubkey_algo_name': function(value){ + return typeof(value) === 'string'; + // TODO: check against list of known?[''] + }, + 'pubkey_algo_string': function(value){ + return typeof(value) === 'string'; + // TODO: check against list of known?[''] + }, + 'keyid': function(value){ + return isLongId(value); + }, + 'pubkey_algo': function(value) { + return (Number.isInteger(value) && value >= 0); + }, + 'length': function(value){ + return (Number.isInteger(value) && value > 0); + }, + 'timestamp': function(value){ + return (Number.isInteger(value) && value > 0); + }, + 'expires': function(value){ + return (Number.isInteger(value) && value > 0); + } +} diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index aa02a8bc..42213ec3 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -172,49 +172,57 @@ export const permittedOperations = { } }, - - /** TBD: querying the Key's information (keyinfo) - TBD name: { - required: { - 'fingerprint': { - allowed: ['string'] - }, - }, - answer: { - type: ['TBD'], - data: [], - params: ['hasSecret','isRevoked','isExpired','armored', - 'timestamp','expires','pubkey_algo'], - infos: ['subkeys', 'userIds'] - // {'hasSecret': , - // 'isRevoked': , - // 'isExpired': , - // 'armored': , // armored public Key block - // 'timestamp': , // - // 'expires': , - // 'pubkey_algo': TBD // TBD (optional?), - // 'userIds': Array, - // 'subkeys': Array 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 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:[] + } + } } diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index 3450afd2..ca51f4ae 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -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'] } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 06b2b23a..bb06309d 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -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(){ -- cgit v1.2.3 From d4adbf453d39659eee378b2be1d7125315d76083 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 28 May 2018 16:52:50 +0200 Subject: js: Treat a connection as a gpgme Context -- * After an operation a connection should be disconnected again. The "end of operation" is now assumed to be either an error as answer, or a message not including a "more" * GPGME, GPGME_Key, GPGME_Keyring don't require a connection anymore * Message.js: The Message.post() method will open a connection as required --- .../tests/encryptDecryptTest.js | 7 --- lang/js/BrowserTestExtension/tests/encryptTest.js | 8 --- .../BrowserTestExtension/tests/longRunningTests.js | 1 - lang/js/BrowserTestExtension/tests/signTest.js | 2 - lang/js/BrowserTestExtension/tests/startup.js | 3 - lang/js/src/Connection.js | 14 +++-- lang/js/src/Key.js | 42 ++----------- lang/js/src/Keyring.js | 20 +------ lang/js/src/Message.js | 18 ++++++ lang/js/src/gpgmejs.js | 45 ++++---------- lang/js/src/index.js | 2 +- lang/js/unittests.js | 68 ++++------------------ 12 files changed, 59 insertions(+), 171 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 2fe955e6..f5d2be16 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -34,7 +34,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(inputvalues.encrypt.good.data); - context.connection.disconnect(); done(); }); }); @@ -75,7 +74,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); @@ -108,7 +106,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); @@ -134,7 +131,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); @@ -160,7 +156,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(data).to.equal(data); - context.connection.disconnect(); done(); }); }); @@ -187,7 +182,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); @@ -214,7 +208,6 @@ describe('Encryption and Decryption', function () { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); expect(result.data).to.equal(b64data); - context.connection.disconnect(); done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 521ed276..a16f993c 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -28,7 +28,6 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); - context.connection.disconnect(); done(); }); }); @@ -45,7 +44,6 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); - context.connection.disconnect(); done(); }); }); @@ -62,7 +60,6 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); - context.connection.disconnect(); done(); }); }); @@ -79,7 +76,6 @@ describe('Encryption', function () { expect(answer.data).to.be.a("string"); expect(answer.data).to.include('BEGIN PGP MESSAGE'); expect(answer.data).to.include('END PGP MESSAGE'); - context.connection.disconnect(); done(); }); }); @@ -95,7 +91,6 @@ describe('Encryption', function () { }, function(error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); - context.connection.disconnect(); done(); }); }); @@ -110,7 +105,6 @@ describe('Encryption', function () { }, function (error) { expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('MSG_INCOMPLETE'); - context.connection.disconnect(); done(); }); }); @@ -127,7 +121,6 @@ describe('Encryption', function () { expect(error).to.be.an('Error'); expect(error.code).to.not.be.undefined; expect(error.code).to.equal('GNUPG_ERROR'); - context.connection.disconnect(); done(); }); }); @@ -146,7 +139,6 @@ describe('Encryption', function () { // TODO: there is a 64 MB hard limit at least in chrome at: // chromium//extensions/renderer/messaging_util.cc: // kMaxMessageLength - context.connection.disconnect(); done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index 4e55fd26..5c588f27 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -29,7 +29,6 @@ describe('Long running Encryption/Decryption', function () { } } expect(result.data).to.equal(data); - context.connection.disconnect(); done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js index e3323721..2e5edb30 100644 --- a/lang/js/BrowserTestExtension/tests/signTest.js +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -30,7 +30,6 @@ describe('Signing', function () { expect(answer.data).to.include('BEGIN PGP SIGNATURE'); expect(answer.data).to.include('END PGP SIGNATURE'); expect(answer.data).to.include(data); - context.connection.disconnect(); done(); }); }); @@ -49,7 +48,6 @@ describe('Signing', function () { expect(answer.data).to.include(data); expect(answer.signature).to.be.a('string'); expect(answer.signature).to.be.a('string'); - context.connection.disconnect(); done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index ebecf4fb..7d13ea47 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -23,10 +23,7 @@ let prm = Gpgmejs.init(); prm.then( function(context){ - expect(context.connection).to.not.be.undefined; expect(context).to.be.an('object'); - expect(context.connection).to.be.an('object'); - expect(context.Keyring).to.be.undefined; expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); done(); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 3b442622..3480d811 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -102,9 +102,11 @@ export class Connection{ } if (!message || !message instanceof GPGME_Message){ + this.disconnect(); return Promise.reject(gpgme_error('PARAM_WRONG'), message); } if (message.isComplete !== true){ + this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; @@ -113,25 +115,27 @@ export class Connection{ 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); - } - if (msg.more === true){ + } 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); @@ -140,12 +144,14 @@ export class Connection{ me._connection.postMessage(message.message), function(resolve, reject){ setTimeout(function(){ + me._connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ - return result; + return result; }, function(reject){ if(!reject instanceof Error) { + me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 7d3d82b1..13c99542 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -30,27 +30,16 @@ 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. + * Validates the fingerprint. * @param {String} fingerprint - * @param {Object} parent Either a Connection, or the invoking object with a - * Connection (e.g. Keyring) */ -export function createKey(fingerprint, parent){ +export function createKey(fingerprint){ if (!isFingerprint(fingerprint)){ return gpgme_error('PARAM_WRONG'); } - if ( parent instanceof Connection){ - return new GPGME_Key(fingerprint, parent); - } else if ( parent.hasOwnProperty('connection') && - parent.connection instanceof Connection){ - return new GPGME_Key(fingerprint, parent.connection); - } else { - return gpgme_error('PARAM_WRONG'); - } + else return new GPGME_Key(fingerprint); } /** @@ -58,28 +47,8 @@ export function createKey(fingerprint, parent){ */ export class GPGME_Key { - constructor(fingerprint, connection){ + constructor(fingerprint){ this.fingerprint = fingerprint; - this.connection = connection; - } - - set connection(conn){ - if (this._connection instanceof Connection) { - gpgme_error('CONN_ALREADY_CONNECTED'); - } else if (conn instanceof Connection ) { - this._connection = conn; - } - } - - get connection(){ - if (!this._data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - if (!this._connection instanceof Connection){ - return gpgme_error('CONN_NO_CONNECT'); - } else { - return this._connection; - } } set fingerprint(fpr){ @@ -219,7 +188,8 @@ export class GPGME_Key { let msg = createMessage('keylist'); msg.setParameter('sigs', true); msg.setParameter('keys', me._data.fingerprint); - me.connection.post(msg).then(function(result){ + console.log(msg); + msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeydata(result.keys[0]); resolve(me); diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 80792f77..9abb9ec3 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -22,23 +22,9 @@ import {createMessage} from './Message' import {GPGME_Key} from './Key' import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; -import { Connection } from './Connection'; export class GPGME_Keyring { - constructor(connection){ - this.connection = connection; - } - - set connection(connection){ - if (!this._connection && connection instanceof Connection){ - this._connection = connection; - } - } - get connection(){ - if (this._connection instanceof Connection){ - return this._connection; - } - return gpgme_error('CONN_NO_CONNECT'); + constructor(){ } /** @@ -58,7 +44,7 @@ export class GPGME_Keyring { if (include_secret){ msg.setParameter('with-secret', true); } - me.connection.post(msg).then(function(result){ + msg.post().then(function(result){ let fpr_list = []; let resultset = []; if (!Array.isArray(result.keys)){ @@ -68,7 +54,7 @@ export class GPGME_Keyring { fpr_list = result.keys; } for (let i=0; i < fpr_list.length; i++){ - let newKey = new GPGME_Key(fpr_list[i], me._connection); + let newKey = new GPGME_Key(fpr_list[i]); if (newKey instanceof GPGME_Key){ resultset.push(newKey); } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 932212a6..5664f723 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -19,6 +19,7 @@ */ import { permittedOperations } from './permittedOperations' import { gpgme_error } from './Errors' +import { Connection } from './Connection'; export function createMessage(operation){ if (typeof(operation) !== 'string'){ @@ -193,4 +194,21 @@ export class GPGME_Message { } } + + post(){ + let me = this; + return new Promise(function(resolve, reject) { + if (me.isComplete === true) { + let conn = new Connection; + conn.post(me).then(function(response) { + resolve(response); + }, function(reason) { + reject(gpgme_error('GNUPG_ERROR', reason)); + }); + } + else { + reject(gpgme_error('MSG_INCOMPLETE')); + } + }); + } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index c182c175..88a91a60 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -18,7 +18,6 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import {Connection} from "./Connection" import {GPGME_Message, createMessage} from './Message' import {toKeyIdArray} from "./Helpers" import { gpgme_error } from "./Errors" @@ -29,31 +28,20 @@ export class GpgME { * initializes GpgME by opening a nativeMessaging port * TODO: add configuration */ - constructor(connection){ - this.connection = connection; + constructor(config){ //TODO config not parsed + this._config = config; } - set connection(conn){ - if (this._connection instanceof Connection){ - gpgme_error('CONN_ALREADY_CONNECTED'); - } else if (conn instanceof Connection){ - this._connection = conn; - } else { - gpgme_error('PARAM_WRONG'); - } - } - - get connection(){ - return this._connection; - } - - set Keyring(keyring){ + set Keyring(keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } return this._Keyring; } @@ -81,7 +69,7 @@ export class GpgME { msg.setParameter('throw-keyids', true); }; if (msg.isComplete === true){ - return this.connection.post(msg); + return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } @@ -110,7 +98,7 @@ export class GpgME { return Promise.reject(msg); } putData(msg, data); - return this.connection.post(msg); + return msg.post(); } @@ -135,7 +123,7 @@ export class GpgME { } let me = this; return new Promise(function(resolve,reject) { - me.connection.post(msg).then( function(message) { + msg.post().then( function(message) { if (mode === 'clearsign'){ resolve({ data: message.data} @@ -174,20 +162,7 @@ export class GpgME { // TBD } if (msg.isComplete === true){ - this.connection.post(msg).then(function(success){ - // TODO: it seems that there is always errors coming back: - }, function(error){ - switch (error.msg){ - case 'ERR_NO_ERROR': - return Promise.resolve('okay'); //TBD - default: - return Promise.reject(gpgme_error('TODO') ); // - // INV_VALUE, - // GPG_ERR_NO_PUBKEY, - // GPG_ERR_AMBIGUOUS_NAME, - // GPG_ERR_CONFLICT - } - }); + return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 7f969fee..220a6984 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -37,7 +37,7 @@ function init(config){ connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(new GpgME(connection, _conf)); + resolve(new GpgME(_conf)); } else { reject(gpgme_error('CONN_NO_CONNECT')); } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index bb06309d..9830a2c5 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -47,7 +47,6 @@ 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(); }); @@ -170,15 +169,11 @@ function unittests (){ describe('GPGME_Key', function(){ it('correct Key initialization', function(){ - let conn = new Connection; - let key = createKey(kp.validKeyFingerprint, conn); + let key = createKey(kp.validKeyFingerprint); expect(key).to.be.an.instanceof(GPGME_Key); - expect(key.connection).to.be.an.instanceof(Connection); - conn.disconnect(); }); it('Key has data after a first refresh', function(done) { - let conn = new Connection; - let key = createKey(kp.validKeyFingerprint, conn); + 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'); @@ -192,74 +187,42 @@ function unittests (){ 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); + let key = createKey(kp.validKeyFingerprint); 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); + 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'); - conn.disconnect(); - done(); - }); - }); - - - it('Key can use the connection', function(done){ - let conn = new Connection; - let key = createKey(hp.validFingerprint, conn); - key.connection.checkConnection(false).then(function(result){ - expect(result).to.be.true; - key.connection.disconnect(); - key.connection.checkConnection(false).then(function(result2){ - expect(result2).to.be.false; - conn.disconnect(); done(); - }); }); }); it('createKey returns error if parameters are wrong', function(){ - let conn = new Connection; for (let i=0; i< 4; i++){ - let key0 = createKey(wp.four_invalid_params[i], conn); - + let key0 = createKey(wp.four_invalid_params[i]); expect(key0).to.be.an.instanceof(Error); expect(key0.code).to.equal('PARAM_WRONG'); } - for (let i=0; i< 4; i++){ - let key0 = createKey( - hp.validFingerprint, wp.four_invalid_params[i]); - - expect(key0).to.be.an.instanceof(Error); - expect(key0.code).to.equal('PARAM_WRONG'); - } - conn.disconnect(); }); 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); + 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'); } - conn.disconnect(); }); // TODO: tests for subkeys @@ -269,27 +232,18 @@ function unittests (){ describe('GPGME_Keyring', function(){ - it('correct initialization', function(){ - let conn = new Connection; - let keyring = new GPGME_Keyring(conn); - + it('correct Keyring initialization', function(){ + let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); - expect(keyring.connection).to.be.an.instanceof(Connection); expect(keyring.getKeys).to.be.a('function'); expect(keyring.getSubset).to.be.a('function'); }); - it('Keyring should return errors if not connected', function(){ + it('correct initialization', function(){ let keyring = new GPGME_Keyring; expect(keyring).to.be.an.instanceof(GPGME_Keyring); - expect(keyring.connection).to.be.an.instanceof(Error); - expect(keyring.connection.code).to.equal('CONN_NO_CONNECT'); - // not yet implemented: - // keyring.getKeys().then( - // function(result){}, - //function(reject){ - // expect(reject).to.be.an.instanceof(Error); - // done(); + expect(keyring.getKeys).to.be.a('function'); + expect(keyring.getSubset).to.be.a('function'); }); //TODO not yet implemented: // getKeys(pattern, include_secret) //note: pattern can be null -- cgit v1.2.3 From 53ce2b94bc35243710dec9b7972c7aaaa79dbc75 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 28 May 2018 17:26:56 +0200 Subject: js: Keyring listing keys -- * implementing Keyring methods: - Keyring.getKeys: has an additional option that retrieves the armor and secret state once at the beginning. This is power hungry, but allows for Keys to be used directly (without querying gpgme-json each call) * permittedOperations.js: reflect recent changes in the native counterpart, adding more options * Key: adding two methods for retrieving the armored Key block and for finding out if the Key includes a secret subkey. --- lang/js/BrowserTestExtension/testkey2.pub | 30 ++++++ lang/js/src/Key.js | 86 ++++++++++++--- lang/js/src/Keyring.js | 129 ++++++---------------- lang/js/src/permittedOperations.js | 174 +++++++++++++++++++----------- lang/js/unittest_inputvalues.js | 4 + lang/js/unittests.js | 48 +++++++-- 6 files changed, 286 insertions(+), 185 deletions(-) create mode 100644 lang/js/BrowserTestExtension/testkey2.pub 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': { + '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'] - }, }, + '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': { + required:{ + 'key': { + allowed: ['string'] + } + }, + optional: { + 'protocol': { allowed: ['string'], - // array_allowed: TBD Allow several Keys to be deleted at once? + allowed_data: ['cms', 'openpgp'] }, - optional: { - 'TBD' //Flag to delete secret Key ? - } + // 'secret': { not yet implemented + // allowed: ['boolean'] + // } + + }, answer: { - type ['TBD'], - infos: [''] - // TBD (optional) Some kind of 'ok' if delete was successful. + 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(){ -- cgit v1.2.3 From 332b4adbcc52ccf337cbc1943d5abef500769e10 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 30 May 2018 17:05:54 +0200 Subject: js: more Keyring/Key handling -- * src/Keys.js - made setKeyData more consistent with other methods - added convenience methods (Key.armored, Key.hasSecret) - Added a Key delete function * src/Keyring.js: - added a getkeysArmored which allows for bulk export of public Keys gpgmejs: - removed deleteKey. It is now a method of the Key itself - Encrypt: Added some common options as parameter, and the possibility to set all allowed flags via an additional Object --- lang/js/src/Errors.js | 4 + lang/js/src/Key.js | 210 +++++++++++++++++++++++++++++++++++-------------- lang/js/src/Keyring.js | 32 ++++++-- lang/js/src/gpgmejs.js | 85 ++++++++++---------- 4 files changed, 227 insertions(+), 104 deletions(-) diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index fa8a4efe..3b53eeb4 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -71,6 +71,10 @@ const err_list = { msg:'This key does not exist in GPG', type: 'error' }, + 'KEY_NO_INIT': { + msg:'This property has not been retrieved yet from GPG', + type: 'error' + } // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index f2a16b42..454b1912 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -93,56 +93,31 @@ export class GPGME_Key { } else if (this._data.fingerprint !== data.fingerprint){ return gpgme_error('KEY_INVALID'); } - - let booleans = ['expired', 'disabled','invalid','can_encrypt', - 'can_sign','can_certify','can_authenticate','secret', - 'is_qualified']; - for (let b =0; b < booleans.length; b++) { - if ( - !data.hasOwnProperty(booleans[b]) || - typeof(data[booleans[b]]) !== 'boolean' - ){ + let dataKeys = Object.keys(data); + for (let i=0; i< dataKeys.length; i++){ + if (!validKeyProperties.hasOwnProperty(dataKeys[i])){ return gpgme_error('KEY_INVALID'); } - this._data[booleans[b]] = data[booleans[b]]; - } - if (typeof(data.protocol) !== 'string'){ - return gpgme_error('KEY_INVALID'); - } - // TODO check valid protocols? - this._data.protocol = data.protocol; - - if (typeof(data.owner_trust) !== 'string'){ - return gpgme_error('KEY_INVALID'); - } - // TODO check valid values? - this._data.owner_trust = data.owner_trust; - - // TODO: what about origin ? - if (!Number.isInteger(data.last_update)){ - return gpgme_error('KEY_INVALID'); - } - this._data.last_update = data.last_update; - - this._data.subkeys = []; - if (data.hasOwnProperty('subkeys')){ - if (!Array.isArray(data.subkeys)){ + if (validKeyProperties[dataKeys[i]](data[dataKeys[i]]) !== true ){ return gpgme_error('KEY_INVALID'); } - for (let i=0; i< data.subkeys.length; i++) { - this._data.subkeys.push( - new GPGME_Subkey(data.subkeys[i])); - } - } - - this._data.userids = []; - if (data.hasOwnProperty('userids')){ - if (!Array.isArray(data.userids)){ - return gpgme_error('KEY_INVALID'); - } - for (let i=0; i< data.userids.length; i++) { - this._data.userids.push( - new GPGME_UserId(data.userids[i])); + switch (dataKeys[i]){ + case 'subkeys': + this._data.subkeys = []; + for (let i=0; i< data.subkeys.length; i++) { + this._data.subkeys.push( + new GPGME_Subkey(data.subkeys[i])); + } + break; + case 'userids': + this._data.userids = []; + for (let i=0; i< data.userids.length; i++) { + this._data.userids.push( + new GPGME_UserId(data.userids[i])); + } + break; + default: + this._data[dataKeys[i]] = data[dataKeys[i]]; } } return this; @@ -161,7 +136,9 @@ export class GPGME_Key { if (cached === false) { let me = this; return new Promise(function(resolve, reject) { - if (property === 'armor'){ + if (!validKeyProperties.hasOwnProperty(property)){ + reject('PARAM_WRONG'); + } else if (property === 'armored'){ resolve(me.getArmor()); } else if (property === 'hasSecret'){ resolve(me.getHasSecret()); @@ -173,15 +150,23 @@ export class GPGME_Key { }); } }); - } else { - if (!this._data.hasOwnProperty(property)){ + } else { + if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); + } + if (!this._data.hasOwnProperty(property)){ + return gpgme_error('KEY_NO_INIT'); } else { return (this._data[property]); } } } + get armored () { + return this.get('armored'); + //TODO exception if empty + } + /** * Reloads the Key from gnupg */ @@ -207,15 +192,6 @@ export class GPGME_Key { }); } - /** - * Get the armored block of the non- secret parts of the Key. - * @returns {String} the armored Key block. - * Notice that this may be outdated cached info. Use the async getArmor if - * you need the most current info - */ - - // get armor(){ TODO } - /** * Query the armored block of the non- secret parts of the Key directly * from gpg. @@ -279,6 +255,49 @@ export class GPGME_Key { }) }); } + + /** + * Convenience function to be directly used as properties of the Key + * Notice that these rely on cached info and may be outdated. Use the async + * get(property, false) if you need the most current info + */ + + /** + * @returns {String} The armored public Key block + */ + get armored(){ + return this.get('armored', true); + } + + /** + * @returns {Boolean} If the key is considered a "private Key", + * i.e. owns a secret subkey. + */ + get hasSecret(){ + return this.get('hasSecret', true); + } + + /** + * Deletes the public Key from the GPG Keyring. Note that a deletion of a + * secret key is not supported by the native backend. + * @returns {Boolean} Success if key was deleted, rejects with a GPG error + * otherwise + */ + delete(){ + let me = this; + return new Promise(function(resolve, reject){ + if (!me._data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('delete'); + msg.setParameter('key', me._data.fingerprint); + msg.post().then(function(result){ + resolve(result.success); + }, function(error){ + reject(error); + }) + }); + } } /** @@ -453,3 +472,78 @@ const validSubKeyProperties = { return (Number.isInteger(value) && value > 0); } } +const validKeyProperties = { + //TODO better validation? + 'fingerprint': function(value){ + return isFingerprint(value); + }, + 'armored': function(value){ + return typeof(value === 'string'); + }, + 'revoked': function(value){ + return typeof(value) === 'boolean'; + }, + 'expired': function(value){ + return typeof(value) === 'boolean'; + }, + 'disabled': function(value){ + return typeof(value) === 'boolean'; + }, + 'invalid': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_encrypt': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_sign': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_certify': function(value){ + return typeof(value) === 'boolean'; + }, + 'can_authenticate': function(value){ + return typeof(value) === 'boolean'; + }, + 'secret': function(value){ + return typeof(value) === 'boolean'; + }, + 'is_qualified': function(value){ + return typeof(value) === 'boolean'; + }, + 'protocol': function(value){ + return typeof(value) === 'string'; + //TODO check for implemented ones + }, + 'issuer_serial': function(value){ + return typeof(value) === 'string'; + }, + 'issuer_name': function(value){ + return typeof(value) === 'string'; + }, + 'chain_id': function(value){ + return typeof(value) === 'string'; + }, + 'owner_trust': function(value){ + return typeof(value) === 'string'; + }, + 'last_update': function(value){ + return (Number.isInteger(value)); + //TODO undefined/null possible? + }, + 'origin': function(value){ + return (Number.isInteger(value)); + }, + 'subkeys': function(value){ + return (Array.isArray(value)); + }, + 'userids': function(value){ + return (Array.isArray(value)); + }, + 'tofu': function(value){ + return (Array.isArray(value)); + }, + 'hasSecret': function(value){ + return typeof(value) === 'boolean'; + } + +} diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 7e13dfe2..9081cbe9 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -20,7 +20,7 @@ import {createMessage} from './Message' import {GPGME_Key, createKey} from './Key' -import { isFingerprint } from './Helpers'; +import { isFingerprint, toKeyIdArray } from './Helpers'; import { gpgme_error } from './Errors'; export class GPGME_Keyring { @@ -43,10 +43,10 @@ export class GPGME_Keyring { return new Promise(function(resolve, reject) { let msg; msg = createMessage('keylist'); - if (pattern && typeof(pattern) === 'string'){ + if (pattern !== undefined){ msg.setParameter('keys', pattern); } - msg.setParameter('sigs', true); //TODO do we need this? + msg.setParameter('sigs', true); msg.post().then(function(result){ let resultset = []; let promises = []; @@ -72,10 +72,30 @@ export class GPGME_Keyring { }); }); } -// TODO: - // deleteKey(key, include_secret=false) - // getKeysArmored(pattern) //just dump all armored keys + + /** + * Fetches the armored public Key blocks for all Keys matchin the pattern + * (if no pattern is given, fetches all known to gnupg) + * @param {String|Array} 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!) }; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 88a91a60..39f6a2f0 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -46,28 +46,48 @@ export class GpgME { } /** - * @param {String} data text/data to be encrypted as String + * Encrypt (and optionally sign) a Message + * @param {String|Object} data text/data to be encrypted as String. Also accepts Objects with a getText method * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message + * @param {GPGME_Key|String|Array|Array} secretKeys (optional) Keys used to sign the message + * @param {Boolean} base64 (optional) The data is already considered to be in base64 encoding + * @param {Boolean} armor (optional) Request the output as armored block * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message + * @param {Object} additional use additional gpg options (refer to src/permittedOperations) + * @returns {Promise} Encrypted message: + * data: The encrypted message + * base64: Boolean indicating whether data is base64 encoded. + * @async */ - encrypt(data, publicKeys, base64=false, wildcard=false){ - + encrypt(data, publicKeys, secretKeys, base64=false, armor=true, + wildcard=false, additional = {} + ){ let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg) } - // TODO temporary - msg.setParameter('armor', true); + msg.setParameter('armor', armor); msg.setParameter('always-trust', true); if (base64 === true) { msg.setParameter('base64', true); } let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); + let sigkeys = toKeyIdArray(secretKeys); + if (sigkeys.length > 0) { + msg.setParameter('signing_keys', sigkeys); + } putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); }; + if (additional){ + let additional_Keys = Object.keys(additional); + for (let k = 0; k < additional_Keys.length; k++) { + msg.setParameter(additional_Keys[k], + additional[additional_Keys[k]]); + } + } if (msg.isComplete === true){ return msg.post(); } else { @@ -76,16 +96,17 @@ export class GpgME { } /** - * @param {String} data TODO base64? Message with the encrypted data - * @param {Boolean} base64 (optional) Response should stay base64 + * Decrypt a Message + * @param {String|Object} data text/data to be decrypted. Accepts Strings and Objects with a getText method + * @param {Boolean} base64 (optional) Response is expected to be base64 encoded * @returns {Promise} decrypted message: data: The decrypted data. This may be base64 encoded. base64: Boolean indicating whether data is base64 encoded. mime: A Boolean indicating whether the data is a MIME object. - info: An optional object with extra information. + signatures: Array of signature Objects TODO not yet implemented. + // should be an object that can tell if all signatures are valid etc. * @async */ - decrypt(data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); @@ -99,10 +120,22 @@ export class GpgME { } putData(msg, data); return msg.post(); - } - sign(data, keys, mode='clearsign', base64=false) { //sender + /** + * Sign a Message + * @param {String|Object} data text/data to be decrypted. Accepts Strings and Objects with a gettext methos + * @param {GPGME_Key|String|Array|Array} keys The key/keys to use for signing + * @param {*} mode The signing mode. Currently supported: + * 'clearsign': (default) The Message is embedded into the signature + * 'detached': The signature is stored separately + * @param {*} base64 input is considered base64 + * @returns {Promise} + * data: The resulting data. In clearsign mode this includes the signature + * signature: The detached signature (if in detached mode) + * @async + */ + sign(data, keys, mode='clearsign', base64=false) { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -139,38 +172,10 @@ export class GpgME { }) }); } - - deleteKey(key, delete_secret = false, no_confirm = false){ - return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); - let msg = createMessage('deletekey'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - let key_arr = toKeyIdArray(key); - if (key_arr.length !== 1){ - return Promise.reject( - gpgme_error('GENERIC_ERROR')); - // TBD should always be ONE key? - } - msg.setParameter('key', key_arr[0]); - if (delete_secret === true){ - msg.setParameter('allow_secret', true); - // TBD - } - if (no_confirm === true){ //TODO: Do we want this hidden deep in the code? - msg.setParameter('delete_force', true); - // TBD - } - if (msg.isComplete === true){ - return msg.post(); - } else { - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); - } - } } /** - * Sets the data of the message + * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter */ -- cgit v1.2.3 From 0356a667c5a8b4fdb4404cebb57475ed3f39ade9 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 6 Jun 2018 11:57:41 +0200 Subject: js: implement import/delete Key, some fixes -- * Keyring.js - implemented importKey: importing one or more armored public key blocks. - implemented deleteKey: deleting a public Key from gpg. * Key.js renamed property Key.armor to Key.armored * Helpers.js: toKeyIDArray does not complain anymore if there are no keys. Not having Keys in e.g. signing keys in encrypt is legitimate and common, the complaints were getting spammy * Errors.js: gpgme_errors now always pass an optional additional message, for easier debugging in minified code * Connection.js: Fix in gpgme-json responses containing objects * eslintrc.json: Start using eslint. A cleanup to conform to it is not done yet * Added further tests for the new functionality --- lang/js/.eslintrc.json | 29 +++++ lang/js/BrowserTestExtension/browsertest.html | 1 + .../BrowserTestExtension/tests/KeyImportExport.js | 83 ++++++++++++ .../tests/encryptDecryptTest.js | 5 +- lang/js/BrowserTestExtension/tests/inputvalues.js | 105 +++++++++++++-- lang/js/src/Connection.js | 5 +- lang/js/src/Errors.js | 8 +- lang/js/src/Helpers.js | 2 - lang/js/src/Key.js | 4 +- lang/js/src/Keyring.js | 144 +++++++++++++++++---- lang/js/unittest_inputvalues.js | 93 +++++++++++-- lang/js/unittests.js | 126 +++++++++++++----- 12 files changed, 511 insertions(+), 94 deletions(-) create mode 100644 lang/js/.eslintrc.json create mode 100644 lang/js/BrowserTestExtension/tests/KeyImportExport.js 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 @@ -18,6 +18,7 @@ + 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 @@ -163,7 +163,6 @@ describe('Encryption and Decryption', function () { }).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(); @@ -177,11 +176,11 @@ describe('Encryption and Decryption', function () { '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(); }); }); 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 @@ -18,11 +18,12 @@ * 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! Добрый день', @@ -45,7 +46,8 @@ var inputvalues = { ] }, 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' } }, @@ -54,14 +56,13 @@ var inputvalues = { 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( @@ -71,7 +72,7 @@ function bigString(length){ 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++){ @@ -83,7 +84,7 @@ function fixedLengthString(megabytes){ } // (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++){ @@ -92,11 +93,13 @@ function bigUint8(megabytes){ 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)]); } @@ -105,12 +108,13 @@ function bigBoringString(megabytes){ // 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){ @@ -118,7 +122,7 @@ function slightlyLessBoringString(megabytes, set){ } 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)]); @@ -127,7 +131,7 @@ function slightlyLessBoringString(megabytes, set){ } // Data encrypted with testKey -var encryptedData = +var encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + '\n' + 'hQEMA6B8jfIUScGEAQgAlANd3uyhmhYLzVcfz4LEqA8tgUC3n719YH0iuKEzG/dv\n' + @@ -141,3 +145,76 @@ var encryptedData = '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' + + '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+sq0WRp3hynn7kVP6QQYvtCZTb21lb25lIEVsc2UgPHNvbWVvbmVlbHNl\n' + + 'QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJb\n' + + 'D705AhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPY3kMEeYCeK\n' + + 'aIUH/2o+Ra+GzxgZrVexXLL+FCSmcu0cxeWfMhL8jd96c6uXIT21qQMRU2jgvnUp\n' + + 'Wdi/BeLKp5lYwywm04PFhmRVxWXLuLArCsDu+CFys+aPeybnjikPBZov6P8/cZV3\n' + + 'cd6zxFvqB9J15HjDMcl/r5v6d4CgSLKlFebrO5WKxHa6zGK9TRMQrqTu1heKHRf6\n' + + '4+Wj+MZmYnPzEQePjiBw/VkJ1Nm37Dd24gKdcN/qJFwEOqvbI5RIjB7xqoDslZk9\n' + + 'sAivBXwF0E9HKqvh4WZZeA7uaWNdGo/cQkD5rab5SdHGNPHLbzoRWScsM8WYtsME\n' + + 'dEMp5iPuG9M63+TD7losAkJ/TlS5AQ0EWw+8rQEIALC8FyZucYmTxeEQFG7ZA3eM\n' + + 'qBWzcIbmcTZeHbQmKlnAC4Ye6AMu/r+OWLPPPmO2PdIAdW0kHyUlbwjRw1IQhfxt\n' + + 'FgfgLuU/YNNv0KAxwYStAjIrjzT4cixuxmElI0d2NRSXog8QRV9JDNZq5+a6zB57\n' + + '6CKoeJOxqJbbTK1eKXBUamOUcFrGf9umyRnRhFqyCc+oqAkPd3mS6c2pajIKkxwV\n' + + 'iAMRG89uGFx4Vu8UT0b+7eBty5XXmbHnd0NfQia89y8mcM7IlL3KvsCl05XfXakD\n' + + 'UwtvF8+dudW750ZefsP9UIN3loFhU+b7kA2uTrP2Vgof1o7nCBeksB0m+60I4vkA\n' + + 'EQEAAYkBPAQYAQgAJhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJbD7ytAhsMBQkD\n' + + 'wmcAAAoJEPY3kMEeYCeKAxUH/jzgbb7dHRwBqPdX/PiBbcxFIyFxioQzIn8AJ2jv\n' + + 'Lx6it5hbzjclZZUNFjr9hxhga4EE/jtY7gm4/bAiVWfXtc4JKCc2OaaOwhiIDOSi\n' + + 'nvvZMHbujRV6IVfmJZxrDLQQjskJvbfGkj5A/fWSwvbLJFgVcK1dJR9w6sukJjxo\n' + + 'RAxBsAa2RqDXAdOj9N/9WKnyrRkRDjE4pNHb+WXr6Kc/yTMbLNp0SVScfVDU5Rd3\n' + + 'YuP/sGkEcTb76ECMFBGCfJo40XualQ0S7DriIE2zQJ7oqLhFXb4QEucW5ucU1HF8\n' + + 'UlxhdyKOG5/dgJ1bOpiY+lT8K0XhOAOV9Kx3EfFU6admRjc=\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 @@ -103,7 +103,7 @@ export class 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(); @@ -221,12 +221,13 @@ class Answer{ 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 { 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 @@ -74,7 +74,7 @@ const err_list = { 'KEY_NO_INIT': { msg:'This property has not been retrieved yet from GPG', type: 'error' - } + }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', @@ -118,7 +118,11 @@ class GPGME_Error extends Error{ 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); } 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 @@ -29,7 +29,6 @@ import { GPGME_Key } from "./Key"; export function toKeyIdArray(input){ if (!input){ - gpgme_error('MSG_NO_KEYS'); return []; } if (!Array.isArray(input)){ @@ -61,7 +60,6 @@ export function toKeyIdArray(input){ } } if (result.length === 0){ - gpgme_error('MSG_NO_KEYS'); return []; } else { return result; 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 @@ -207,7 +207,7 @@ export class GPGME_Key { 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); @@ -280,7 +280,7 @@ export class GPGME_Key { /** * 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(){ 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 @@ -18,9 +18,9 @@ * 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 { @@ -39,10 +39,8 @@ export class GPGME_Keyring { * */ 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); } @@ -50,25 +48,28 @@ export class GPGME_Keyring { 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); }); }); } @@ -80,7 +81,6 @@ export class GPGME_Keyring { * @returns {Promise} Armored Key blocks */ getKeysArmored(pattern) { - if (pattern) return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); @@ -92,10 +92,102 @@ export class GPGME_Keyring { }, 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,5 +1,5 @@ -import {Connection} from "./src/Connection"; -import {createKey} from "./src/Key"; +import {Connection} from './src/Connection'; +import {createKey} from './src/Key'; let conn = new Connection; @@ -23,14 +23,14 @@ export const helper_params = { 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', {}], @@ -38,18 +38,89 @@ export const message_params = { 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' + + '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+sq0WRp3hynn7kVP6QQYvtCZTb21lb25lIEVsc2UgPHNvbWVvbmVlbHNl\n' + + 'QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJb\n' + + 'D705AhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPY3kMEeYCeK\n' + + 'aIUH/2o+Ra+GzxgZrVexXLL+FCSmcu0cxeWfMhL8jd96c6uXIT21qQMRU2jgvnUp\n' + + 'Wdi/BeLKp5lYwywm04PFhmRVxWXLuLArCsDu+CFys+aPeybnjikPBZov6P8/cZV3\n' + + 'cd6zxFvqB9J15HjDMcl/r5v6d4CgSLKlFebrO5WKxHa6zGK9TRMQrqTu1heKHRf6\n' + + '4+Wj+MZmYnPzEQePjiBw/VkJ1Nm37Dd24gKdcN/qJFwEOqvbI5RIjB7xqoDslZk9\n' + + 'sAivBXwF0E9HKqvh4WZZeA7uaWNdGo/cQkD5rab5SdHGNPHLbzoRWScsM8WYtsME\n' + + 'dEMp5iPuG9M63+TD7losAkJ/TlS5AQ0EWw+8rQEIALC8FyZucYmTxeEQFG7ZA3eM\n' + + 'qBWzcIbmcTZeHbQmKlnAC4Ye6AMu/r+OWLPPPmO2PdIAdW0kHyUlbwjRw1IQhfxt\n' + + 'FgfgLuU/YNNv0KAxwYStAjIrjzT4cixuxmElI0d2NRSXog8QRV9JDNZq5+a6zB57\n' + + '6CKoeJOxqJbbTK1eKXBUamOUcFrGf9umyRnRhFqyCc+oqAkPd3mS6c2pajIKkxwV\n' + + 'iAMRG89uGFx4Vu8UT0b+7eBty5XXmbHnd0NfQia89y8mcM7IlL3KvsCl05XfXakD\n' + + 'UwtvF8+dudW750ZefsP9UIN3loFhU+b7kA2uTrP2Vgof1o7nCBeksB0m+60I4vkA\n' + + 'EQEAAYkBPAQYAQgAJhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJbD7ytAhsMBQkD\n' + + 'wmcAAAoJEPY3kMEeYCeKAxUH/jzgbb7dHRwBqPdX/PiBbcxFIyFxioQzIn8AJ2jv\n' + + 'Lx6it5hbzjclZZUNFjr9hxhga4EE/jtY7gm4/bAiVWfXtc4JKCc2OaaOwhiIDOSi\n' + + 'nvvZMHbujRV6IVfmJZxrDLQQjskJvbfGkj5A/fWSwvbLJFgVcK1dJR9w6sukJjxo\n' + + 'RAxBsAa2RqDXAdOj9N/9WKnyrRkRDjE4pNHb+WXr6Kc/yTMbLNp0SVScfVDU5Rd3\n' + + 'YuP/sGkEcTb76ECMFBGCfJo40XualQ0S7DriIE2zQJ7oqLhFXb4QEucW5ucU1HF8\n' + + 'UlxhdyKOG5/dgJ1bOpiY+lT8K0XhOAOV9Kx3EfFU6admRjc=\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 @@ -17,19 +17,19 @@ * 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; @@ -74,12 +74,14 @@ function unittests (){ 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'); @@ -201,7 +203,7 @@ function unittests (){ 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(); @@ -233,7 +235,7 @@ function unittests (){ expect(error).to.be.an.instanceof(Error); expect(error.code).to.equal('KEY_NOKEY'); done(); - }); + }); }); it('createKey returns error if parameters are wrong', function(){ @@ -269,14 +271,74 @@ function unittests (){ 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(){ @@ -330,12 +392,12 @@ function unittests (){ 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'); } }); @@ -343,12 +405,12 @@ function unittests (){ 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'); } }); }); -- cgit v1.2.3 From bfd3799d39df265882deedeee083fd5246a2f35d Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 6 Jun 2018 13:05:53 +0200 Subject: js: code cleanup (eslint) -- * trying to stick to eslint from now on for readability * As some attribution was lost in previous git confusions, I added my name into some of the licence headers --- lang/js/.eslintrc.json | 3 + lang/js/BrowserTestExtension/popup.js | 22 +--- lang/js/BrowserTestExtension/runbrowsertest.js | 5 + lang/js/BrowserTestExtension/rununittests.js | 6 + lang/js/BrowserTestExtension/setup_testing.js | 8 +- .../BrowserTestExtension/tests/KeyImportExport.js | 3 + .../tests/encryptDecryptTest.js | 91 ++++++++------- lang/js/BrowserTestExtension/tests/encryptTest.js | 113 +++++++++--------- lang/js/BrowserTestExtension/tests/inputvalues.js | 9 +- .../BrowserTestExtension/tests/longRunningTests.js | 101 +++++++++++----- lang/js/BrowserTestExtension/tests/signTest.js | 31 +++-- lang/js/BrowserTestExtension/tests/startup.js | 24 ++-- lang/js/CHECKLIST | 19 +-- lang/js/DemoExtension/entry.js | 5 + lang/js/DemoExtension/maindemo.js | 40 ++++--- lang/js/package.json | 4 +- lang/js/src/Config.js | 3 + lang/js/src/Connection.js | 130 ++++++++++----------- lang/js/src/Errors.js | 6 +- lang/js/src/Helpers.js | 29 +++-- lang/js/src/Key.js | 93 +++++++-------- lang/js/src/Keyring.js | 32 +++-- lang/js/src/Message.js | 95 +++++++-------- lang/js/src/gpgmejs.js | 57 +++++---- lang/js/src/index.js | 25 ++-- lang/js/src/permittedOperations.js | 35 +++--- lang/js/unittests.js | 104 ++++++----------- 27 files changed, 595 insertions(+), 498 deletions(-) diff --git a/lang/js/.eslintrc.json b/lang/js/.eslintrc.json index 65253cf3..ad82400b 100644 --- a/lang/js/.eslintrc.json +++ b/lang/js/.eslintrc.json @@ -24,6 +24,9 @@ "error", "always" ], + "no-var": [ + "warn" + ], "max-len": 1 } } \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/popup.js b/lang/js/BrowserTestExtension/popup.js index 12beb1eb..794620b6 100644 --- a/lang/js/BrowserTestExtension/popup.js +++ b/lang/js/BrowserTestExtension/popup.js @@ -16,27 +16,13 @@ * 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+ - */ -/* 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+ + * Author(s): + * Maximilian Krambach */ +/* global chrome */ + document.addEventListener('DOMContentLoaded', function() { chrome.tabs.create({ url: './index.html' diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js index 39bc3fb9..c46eb120 100644 --- a/lang/js/BrowserTestExtension/runbrowsertest.js +++ b/lang/js/BrowserTestExtension/runbrowsertest.js @@ -16,6 +16,11 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ +/* global mocha */ + mocha.run(); diff --git a/lang/js/BrowserTestExtension/rununittests.js b/lang/js/BrowserTestExtension/rununittests.js index f85ed8b5..df31589e 100644 --- a/lang/js/BrowserTestExtension/rununittests.js +++ b/lang/js/BrowserTestExtension/rununittests.js @@ -16,6 +16,12 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ + +/* global Gpgmejs_test, mocha*/ + Gpgmejs_test.unittests(); mocha.run(); diff --git a/lang/js/BrowserTestExtension/setup_testing.js b/lang/js/BrowserTestExtension/setup_testing.js index 7f70d347..52aeac58 100644 --- a/lang/js/BrowserTestExtension/setup_testing.js +++ b/lang/js/BrowserTestExtension/setup_testing.js @@ -16,7 +16,13 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ + +/* global mocha, chai */ + mocha.setup('bdd'); -var expect = chai.expect; +const expect = chai.expect; //eslint-disable-line no-unused-vars chai.config.includeStack = true; \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index e6eb5a30..6b298049 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -16,6 +16,9 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ /* global describe, it, expect, Gpgmejs, ImportablePublicKey */ diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index e5c2f749..a84be27c 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -1,4 +1,3 @@ - /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * @@ -17,23 +16,31 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ +/* global describe, it, expect, Gpgmejs */ +/* global inputvalues, encryptedData, bigString, bigBoringString */ + 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) { + inputvalues.encrypt.good.fingerprint).then( + function (answer) { expect(answer).to.not.be.empty; - expect(answer.data).to.be.a("string"); + 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); + expect(result.data).to.equal( + inputvalues.encrypt.good.data); done(); }); }); @@ -51,7 +58,7 @@ describe('Encryption and Decryption', function () { expect(result.data).to.equal( '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n'); done(); - }); + }); }); }).timeout(3000); @@ -64,7 +71,7 @@ describe('Encryption and Decryption', function () { inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; - expect(answer.data).to.be.a("string"); + expect(answer.data).to.be.a('string'); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( @@ -79,39 +86,41 @@ describe('Encryption and Decryption', function () { }); }); }); - }).timeout(5000); + }).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(); - }); - }); - }); + 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); @@ -121,7 +130,7 @@ describe('Encryption and Decryption', function () { inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; - expect(answer.data).to.be.a("string"); + expect(answer.data).to.be.a('string'); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( @@ -143,10 +152,10 @@ describe('Encryption and Decryption', function () { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, - inputvalues.encrypt.good.fingerprint,).then( + inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; - expect(answer.data).to.be.a("string"); + expect(answer.data).to.be.a('string'); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( @@ -171,7 +180,7 @@ describe('Encryption and Decryption', function () { 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.be.a('string'); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); expect(answer.data).to.include( @@ -196,7 +205,7 @@ describe('Encryption and Decryption', function () { inputvalues.encrypt.good.fingerprint).then( function (answer) { expect(answer).to.not.be.empty; - expect(answer.data).to.be.a("string"); + expect(answer.data).to.be.a('string'); expect(answer.data).to.include( 'BEGIN PGP MESSAGE'); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index a16f993c..2cb4e58b 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -16,7 +16,14 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ + +/* global describe, it, expect, Gpgmejs */ +/* global inputvalues, fixedLengthString */ + describe('Encryption', function () { it('Successful encrypt', function (done) { let prm = Gpgmejs.init(); @@ -24,12 +31,12 @@ describe('Encryption', function () { 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'); - done(); - }); + 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'); + done(); + }); }); }); @@ -40,12 +47,12 @@ describe('Encryption', function () { 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'); - done(); - }); + 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'); + done(); + }); }); }).timeout(10000); @@ -56,12 +63,12 @@ describe('Encryption', function () { 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'); - done(); - }); + 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'); + done(); + }); }); }).timeout(20000); @@ -72,12 +79,12 @@ describe('Encryption', function () { 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'); - done(); - }); + 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'); + done(); + }); }); }).timeout(20000); @@ -87,12 +94,12 @@ describe('Encryption', function () { context.encrypt( inputvalues.encrypt.good.data, null).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an('Error'); - expect(error.code).to.equal('MSG_INCOMPLETE'); - done(); - }); + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); + }); }); }); @@ -101,12 +108,12 @@ describe('Encryption', function () { prm.then(function (context) { context.encrypt( null, inputvalues.encrypt.good.keyid).then(function (answer) { - expect(answer).to.be.undefined; - }, function (error) { - expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('MSG_INCOMPLETE'); - done(); - }); + expect(answer).to.be.undefined; + }, function (error) { + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); + }); }); }); @@ -116,15 +123,15 @@ describe('Encryption', function () { context.encrypt( inputvalues.encrypt.good.data, inputvalues.encrypt.bad.fingerprint).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an('Error'); - expect(error.code).to.not.be.undefined; - expect(error.code).to.equal('GNUPG_ERROR'); - done(); - }); + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.not.be.undefined; + expect(error.code).to.equal('GNUPG_ERROR'); + done(); + }); }); - }).timeout(5000);; + }).timeout(5000); it('Overly large message ( > 65MB) is rejected', function (done) { let prm = Gpgmejs.init(); @@ -132,15 +139,15 @@ describe('Encryption', function () { context.encrypt( fixedLengthString(65), inputvalues.encrypt.good.fingerprint).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an.instanceof(Error); - // expect(error.code).to.equal('GNUPG_ERROR'); - // TODO: there is a 64 MB hard limit at least in chrome at: - // chromium//extensions/renderer/messaging_util.cc: - // kMaxMessageLength - done(); - }); + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + // expect(error.code).to.equal('GNUPG_ERROR'); + // TODO: there is a 64 MB hard limit at least in chrome at: + // chromium//extensions/renderer/messaging_util.cc: + // kMaxMessageLength + done(); + }); }); }).timeout(8000); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 9dc13324..024aad25 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -16,9 +16,12 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -var inputvalues = {// eslint-disable-line no-unused-vars +const inputvalues = {// eslint-disable-line no-unused-vars encrypt: { good:{ data : 'Hello World.', @@ -131,7 +134,7 @@ function slightlyLessBoringString(megabytes, set){ } // Data encrypted with testKey -var encryptedData =// eslint-disable-line no-unused-vars +const encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + '\n' + 'hQEMA6B8jfIUScGEAQgAlANd3uyhmhYLzVcfz4LEqA8tgUC3n719YH0iuKEzG/dv\n' + @@ -146,7 +149,7 @@ var encryptedData =// eslint-disable-line no-unused-vars '=zap6\n' + '-----END PGP MESSAGE-----\n'; -var ImportablePublicKey = {// eslint-disable-line no-unused-vars +const ImportablePublicKey = {// eslint-disable-line no-unused-vars fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A', key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + '\n' + diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index 5c588f27..eefe126d 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -1,39 +1,78 @@ +/* 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+ + * + * Author(s): + * Maximilian Krambach + */ +/* global describe, it, expect, Gpgmejs */ +/* global bigString, inputvalues */ + describe('Long running Encryption/Decryption', function () { for (let i=0; i < 100; i++) { - it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { + it('Successful encrypt/decrypt completely random data ' + + (i+1) + '/100', function (done) { let prm = Gpgmejs.init(); let data = bigString(2*1024*1024); - 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'); - if (result.data.length !== data.length) { - console.log('diff: ' + (result.data.length - data.length)); - for (let i=0; i < result.data.length; i++){ - if (result.data[i] !== data[i]){ - console.log('position: ' + i); - console.log('result : '+ result.data.charCodeAt(i) + result.data[i-2] + result.data[i-1] + result.data[i] + result.data[i+1] + result.data[i+2]); - console.log('original: ' + data.charCodeAt(i) + data[i-2] + data[i-1] + data[i] + data[i+1] + data[i+2]); - break; - } - } + 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'); + if (result.data.length !== data.length) { + // console.log('diff: ' + + // (result.data.length - data.length)); + for (let i=0; i < result.data.length; i++){ + if (result.data[i] !== data[i]){ + // console.log('position: ' + i); + // console.log('result : ' + + // result.data.charCodeAt(i) + + // result.data[i-2] + + // result.data[i-1] + + // result.data[i] + + // result.data[i+1] + + // result.data[i+2]); + // console.log('original: ' + + // data.charCodeAt(i) + + // data[i-2] + + // data[i-1] + + // data[i] + + // data[i+1] + + // data[i+2]); + break; } - expect(result.data).to.equal(data); - done(); - }); - }); - }); + } + } + expect(result.data).to.equal(data); + done(); + }); + }); + }); }).timeout(8000); - }; + } }); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js index 2e5edb30..ffd2d5de 100644 --- a/lang/js/BrowserTestExtension/tests/signTest.js +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -16,7 +16,14 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ + +/* global describe, it, expect, Gpgmejs */ +/* global bigString, inputvalues */ + describe('Signing', function () { it('Sign a message', function (done) { let prm = Gpgmejs.init(); @@ -25,12 +32,12 @@ describe('Signing', function () { context.sign( 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 SIGNATURE'); - expect(answer.data).to.include('END PGP SIGNATURE'); - expect(answer.data).to.include(data); - done(); + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include('BEGIN PGP SIGNATURE'); + expect(answer.data).to.include('END PGP SIGNATURE'); + expect(answer.data).to.include(data); + done(); }); }); }); @@ -43,12 +50,12 @@ describe('Signing', function () { inputvalues.encrypt.good.fingerprint, 'detached' ).then(function (answer) { - expect(answer).to.not.be.empty; - expect(answer.data).to.be.a('string'); - expect(answer.data).to.include(data); - expect(answer.signature).to.be.a('string'); - expect(answer.signature).to.be.a('string'); - done(); + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include(data); + expect(answer.signature).to.be.a('string'); + expect(answer.signature).to.be.a('string'); + done(); }); }); }); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 7d13ea47..d434b6d4 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -16,25 +16,31 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ - describe('GPGME context', function(){ +/* global describe, it, expect, Gpgmejs */ +/* global inputvalues */ + +describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ let prm = Gpgmejs.init(); prm.then( - function(context){ - expect(context).to.be.an('object'); - expect(context.encrypt).to.be.a('function'); - expect(context.decrypt).to.be.a('function'); - done(); - }); + function(context){ + expect(context).to.be.an('object'); + expect(context.encrypt).to.be.a('function'); + expect(context.decrypt).to.be.a('function'); + done(); + }); }); }); describe('GPGME does not start with invalid parameters', function(){ for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ it('Parameter '+ i, function(done){ - let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); + let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); prm.then(function(context){ expect(context).to.be.undefined; done(); @@ -43,6 +49,6 @@ describe('GPGME does not start with invalid parameters', function(){ expect(error.code).to.equal('PARAM_WRONG'); done(); }); - }) + }); } }); \ No newline at end of file diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST index 278f39dd..fe260187 100644 --- a/lang/js/CHECKLIST +++ b/lang/js/CHECKLIST @@ -6,20 +6,25 @@ receiving an answer [*] nativeConnection successfull on Windows, macOS, Linux [X] nativeConnection with delayed, multipart (> 1MB) answer - [*] Message handling (encrypt, decrypt verify, sign) + [x] Message handling (encrypt, decrypt verify, sign) [x] encrypt, decrypt - [ ] verify - [ ] sign + [x] verify + [x] sign [*] Key handling (import/export, modifying, status queries) + [x] Import (not importing secret) + [x] Export (not exporting secret) + [x] status queries + [ ] key generation + [ ] modification [*] Configuration handling [ ] check for completeness Communication with other implementations - - [ ] option to export SECRET Key into localstore used by e.g. mailvelope - + [-] option to export SECRET Key into localstore used by e.g. mailvelope? + current discussion states that this won't be possible due to security + concerns Management: [*] Define the gpgme interface [x] check Permissions (e.g. csp) for the different envs - [X] agree on license + [x] agree on license [*] tests diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js index 62583421..77b96f92 100644 --- a/lang/js/DemoExtension/entry.js +++ b/lang/js/DemoExtension/entry.js @@ -17,7 +17,12 @@ * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * + * Author(s): + * Maximilian Krambach */ + +/* global chrome */ + document.addEventListener('DOMContentLoaded', function() { chrome.tabs.create({ url: './mainui.html' diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index b2cb4c23..5cde1ce8 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -17,39 +17,41 @@ * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * + * Author(s): + * Maximilian Krambach */ +/* global document, Gpgmejs */ + document.addEventListener('DOMContentLoaded', function() { Gpgmejs.init().then(function(gpgmejs){ - document.getElementById("buttonencrypt").addEventListener("click", + document.getElementById('buttonencrypt').addEventListener('click', function(){ let data = document.getElementById('cleartext').value; let keyId = document.getElementById('pubkey').value; gpgmejs.encrypt(data, keyId).then( function(answer){ - console.log(answer); if (answer.data){ - console.log(answer.data); - document.getElementById('answer').value = answer.data; + document.getElementById( + 'answer').value = answer.data; } }, function(errormsg){ alert( errormsg.code + ' ' + errormsg.msg); - }); + }); }); - document.getElementById("buttondecrypt").addEventListener("click", - function(){ - let data = document.getElementById("ciphertext").value; - gpgmejs.decrypt(data).then( - function(answer){ - console.log(answer); - if (answer.data){ - document.getElementById('answer').value = answer.data; - } - }, function(errormsg){ - alert( errormsg.code + ' ' + errormsg.msg); + document.getElementById('buttondecrypt').addEventListener('click', + function(){ + let data = document.getElementById('ciphertext').value; + gpgmejs.decrypt(data).then( + function(answer){ + if (answer.data){ + document.getElementById( + 'answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.code + ' ' + errormsg.msg); + }); }); - }); - }, - function(error){console.log(error)}); + }); }); diff --git a/lang/js/package.json b/lang/js/package.json index be52a554..acf89117 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -1,7 +1,7 @@ { "name": "gpgmejs", - "version": "0.0.1", - "description": "javascript part of a nativeMessaging gnupg integration", + "version": "0.0.1-dev", + "description": "Javascript part of the GPGME nativeMessaging integration", "main": "src/index.js", "private": true, "keywords": [], diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js index e85bbb82..8a3ef491 100644 --- a/lang/js/src/Config.js +++ b/lang/js/src/Config.js @@ -16,6 +16,9 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ export const availableConf = { diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8c4cba7c..e9c0b213 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -16,16 +16,16 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -/** - * 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"; +/* global chrome */ + +import { permittedOperations } from './permittedOperations'; +import { gpgme_error } from './Errors'; +import { GPGME_Message, createMessage } from './Message'; /** * A Connection handles the nativeMessaging interaction. @@ -55,7 +55,7 @@ export class Connection{ return this.post(createMessage('version')); } else { let me = this; - return new Promise(function(resolve,reject) { + return new Promise(function(resolve) { Promise.race([ me.post(createMessage('version')), new Promise(function(resolve, reject){ @@ -63,9 +63,9 @@ export class Connection{ reject(gpgme_error('CONN_TIMEOUT')); }, 500); }) - ]).then(function(result){ - resolve(true); - }, function(reject){ + ]).then(function(){ // success + resolve(true); + }, function(){ // failure resolve(false); }); }); @@ -98,12 +98,10 @@ export class Connection{ * information. */ post(message){ - if (!this._connection) { - - } - if (!message || !message instanceof GPGME_Message){ + if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); - return Promise.reject(gpgme_error('PARAM_WRONG', 'Connection.post')); + return Promise.reject(gpgme_error( + 'PARAM_WRONG', 'Connection.post')); } if (message.isComplete !== true){ this.disconnect(); @@ -114,10 +112,10 @@ export class Connection{ let answer = new Answer(message); let listener = function(msg) { if (!msg){ - me._connection.onMessage.removeListener(listener) + me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); - } else if (msg.type === "error"){ + } else if (msg.type === 'error'){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); reject(gpgme_error('GNUPG_ERROR', msg.msg)); @@ -130,7 +128,7 @@ export class Connection{ } else if (msg.more === true){ me._connection.postMessage({'op': 'getmore'}); } else { - me._connection.onMessage.removeListener(listener) + me._connection.onMessage.removeListener(listener); me._connection.disconnect(); resolve(answer.message); } @@ -148,9 +146,9 @@ export class Connection{ reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ - return result; + return result; }, function(reject){ - if(!reject instanceof Error) { + if(!(reject instanceof Error)) { me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { @@ -159,13 +157,14 @@ export class Connection{ }); } }); - } -}; + } +} /** * 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 + * @param {String} operation The operation, to look up validity of returning + * messages */ class Answer{ @@ -191,49 +190,49 @@ class 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'); + 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] = ''; } - 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]; + 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]; } - //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]); - } + 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] = []; } - //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]); + 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]); } - else { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - break; + } + else { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + break; } } return true; @@ -256,10 +255,11 @@ class Answer{ msg[keys[i]] = this._response[keys[i]]; } else { msg[keys[i]] = decodeURIComponent( - atob(this._response[keys[i]]).split('').map(function(c) { - return '%' + + atob(this._response[keys[i]]).split('').map( + function(c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); + }).join('')); } } else { msg[keys[i]] = this._response[keys[i]]; diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 2f2bfd5c..dabf6a5c 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -16,6 +16,9 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ const err_list = { @@ -102,6 +105,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ + // eslint-disable-next-line no-console console.warn(code + ': ' + err_list[code].msg); } return null; @@ -119,7 +123,7 @@ class GPGME_Error extends Error{ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ - super(err_list[code].msg + "--" + msg); + super(err_list[code].msg + '--' + msg); } else { super(err_list[code].msg); } diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 5064d03e..b01fbc30 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -16,14 +16,18 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -import { gpgme_error } from "./Errors"; -import { GPGME_Key } from "./Key"; + +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 + * from Key objects (openpgp Keys or GPGME_Keys are both expected) + * @param {Object |Array| String|Array} input * @returns {Array} Array of fingerprints. */ @@ -48,7 +52,7 @@ export function toKeyIdArray(input){ fpr = input[i].fingerprint; } else if (input[i].hasOwnProperty('primaryKey') && input[i].primaryKey.hasOwnProperty('getFingerprint')){ - fpr = input[i].primaryKey.getFingerprint(); + fpr = input[i].primaryKey.getFingerprint(); } if (isFingerprint(fpr) === true){ result.push(fpr); @@ -64,7 +68,7 @@ export function toKeyIdArray(input){ } else { return result; } -}; +} /** * check if values are valid hexadecimal values of a specified length @@ -72,7 +76,7 @@ export function toKeyIdArray(input){ * @param {int} len the expected length of the value */ function hextest(key, len){ - if (!key || typeof(key) !== "string"){ + if (!key || typeof(key) !== 'string'){ return false; } if (key.length !== len){ @@ -80,23 +84,18 @@ function hextest(key, len){ } 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 d85c8b6e..8c8726a2 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -16,20 +16,14 @@ * 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 * + * Author(s): + * Maximilian Krambach */ -import { isFingerprint, isLongId } from './Helpers' -import { gpgme_error } from './Errors' +import { isFingerprint, isLongId } from './Helpers'; +import { gpgme_error } from './Errors'; import { createMessage } from './Message'; -import { permittedOperations } from './permittedOperations'; /** * Validates the fingerprint. @@ -44,6 +38,11 @@ export function createKey(fingerprint){ /** * Representing the Keys as stored in GPG + * It allows to query almost all information defined in gpgme Key Objects + * Refer to validKeyProperties for available information, and the gpgme + * documentation on their meaning + * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) + * */ export class GPGME_Key { @@ -102,22 +101,22 @@ export class GPGME_Key { 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]]; + 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; @@ -162,11 +161,6 @@ export class GPGME_Key { } } - get armored () { - return this.get('armored'); - //TODO exception if empty - } - /** * Reloads the Key from gnupg */ @@ -188,7 +182,7 @@ export class GPGME_Key { } }, function (error) { reject(gpgme_error('GNUPG_ERROR'), error); - }) + }); }); } @@ -197,7 +191,7 @@ export class GPGME_Key { * from gpg. * @returns {Promise} */ - getArmor(){ + getArmor(){ let me = this; return new Promise(function(resolve, reject) { if (!me._data.fingerprint){ @@ -249,15 +243,16 @@ export class GPGME_Key { } } } else { - reject(gpgme_error('CONN_UNEXPECTED_ANSWER')) + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); } }, function(error){ - }) + reject(error); + }); }); } /** - * Convenience function to be directly used as properties of the Key + * Convenience functions 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 */ @@ -280,8 +275,8 @@ export class GPGME_Key { /** * Deletes the public Key from the GPG Keyring. Note that a deletion of a * secret key is not supported by the native backend. - * @returns {Promise} Success if key was deleted, rejects with a GPG error - * otherwise + * @returns {Promise} Success if key was deleted, rejects with a + * GPG error otherwise */ delete(){ let me = this; @@ -295,7 +290,7 @@ export class GPGME_Key { resolve(result.success); }, function(error){ reject(error); - }) + }); }); } } @@ -378,37 +373,37 @@ const validUserIdProperties = { }, 'uid': function(value){ if (typeof(value) === 'string' || value === ''){ - return true;; + return true; } return false; }, 'validity': function(value){ if (typeof(value) === 'string'){ - return true;; + return true; } return false; }, 'name': function(value){ if (typeof(value) === 'string' || value === ''){ - return true;; + return true; } return false; }, 'email': function(value){ if (typeof(value) === 'string' || value === ''){ - return true;; + return true; } return false; }, 'address': function(value){ if (typeof(value) === 'string' || value === ''){ - return true;; + return true; } return false; }, 'comment': function(value){ if (typeof(value) === 'string' || value === ''){ - return true;; + return true; } return false; }, @@ -449,8 +444,8 @@ const validSubKeyProperties = { return typeof(value) === 'boolean'; }, 'pubkey_algo_name': function(value){ - return typeof(value) === 'string'; - // TODO: check against list of known?[''] + return typeof(value) === 'string'; + // TODO: check against list of known?[''] }, 'pubkey_algo_string': function(value){ return typeof(value) === 'string'; @@ -471,7 +466,7 @@ const validSubKeyProperties = { 'expires': function(value){ return (Number.isInteger(value) && value > 0); } -} +}; const validKeyProperties = { //TODO better validation? 'fingerprint': function(value){ @@ -546,4 +541,4 @@ const validKeyProperties = { return typeof(value) === 'boolean'; } -} +}; diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index c5e613ec..71585878 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -16,8 +16,12 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ + import {createMessage} from './Message'; import {createKey} from './Key'; import { isFingerprint } from './Helpers'; @@ -99,18 +103,22 @@ export class GPGME_Keyring { /** * - * @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: + * @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 : * '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 + * '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: userIds changed + * Key.changes.signature: signatures changed + * Key.changes.subkey: subkeys changed * // TODO: not yet implemented: Information about Keys that failed * (e.g. malformed Keys, secretKeys are not accepted) */ @@ -125,7 +133,7 @@ export class GPGME_Keyring { msg.post().then(function(response){ let infos = {}; let fprs = []; - for (var res=0; res < response.result[0].imports.length; res++) { + for (let res=0; res < response.result[0].imports.length; res++){ let result = response.result[0].imports[res]; let status = ''; if (result.status === 0){ diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 5664f723..0ddda6c4 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -16,9 +16,13 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -import { permittedOperations } from './permittedOperations' -import { gpgme_error } from './Errors' + +import { permittedOperations } from './permittedOperations'; +import { gpgme_error } from './Errors'; import { Connection } from './Connection'; export function createMessage(operation){ @@ -46,7 +50,7 @@ export class GPGME_Message { } set operation (op){ - if (typeof(op) === "string"){ + if (typeof(op) === 'string'){ if (!this._msg){ this._msg = {}; } @@ -67,10 +71,10 @@ export class GPGME_Message { } get expected() { - if (this._expected === "base64"){ - return this._expected; - } - return "string"; + if (this._expected === 'base64'){ + return this._expected; + } + return 'string'; } /** @@ -98,52 +102,50 @@ export class GPGME_Message { } let checktype = function(val){ switch(typeof(val)){ - case 'string': - if (poparam.allowed.indexOf(typeof(val)) >= 0 + case 'string': + if (poparam.allowed.indexOf(typeof(val)) >= 0 && val.length > 0) { - return true; - } - return gpgme_error('PARAM_WRONG'); - break; - case 'number': - if ( - poparam.allowed.indexOf('number') >= 0 + return true; + } + return gpgme_error('PARAM_WRONG'); + case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 && isNaN(value) === false){ - return true; + return true; + } + return gpgme_error('PARAM_WRONG'); + + case 'boolean': + if (poparam.allowed.indexOf('boolean') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); } - return gpgme_error('PARAM_WRONG'); - break; - case 'boolean': - if (poparam.allowed.indexOf('boolean') >= 0){ + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + if (val.length > 0) { return true; } - return gpgme_error('PARAM_WRONG'); - break; - case 'object': - if (Array.isArray(val)){ - if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); - } - for (let i=0; i < val.length; i++){ - let res = checktype(val[i]); - if (res !== true){ - return res; - } - } - if (val.length > 0) { - return true; - } - } else if (val instanceof Uint8Array){ - if (poparam.allowed.indexOf('Uint8Array') >= 0){ - return true; - } - return gpgme_error('PARAM_WRONG'); - } else { - return gpgme_error('PARAM_WRONG'); + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return true; } - break; - default: return gpgme_error('PARAM_WRONG'); + } else { + return gpgme_error('PARAM_WRONG'); + } + break; + default: + return gpgme_error('PARAM_WRONG'); } }; let typechecked = checktype(value); @@ -173,7 +175,6 @@ export class GPGME_Message { let msg_params = Object.keys(this._msg); for (let i=0; i < reqParams.length; i++){ if (msg_params.indexOf(reqParams[i]) < 0){ - console.log(reqParams[i] + ' missing'); return false; } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 39f6a2f0..cbad9021 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -16,12 +16,16 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -import {GPGME_Message, createMessage} from './Message' -import {toKeyIdArray} from "./Helpers" -import { gpgme_error } from "./Errors" -import { GPGME_Keyring } from "./Keyring"; + +import {GPGME_Message, createMessage} from './Message'; +import {toKeyIdArray} from './Helpers'; +import { gpgme_error } from './Errors'; +import { GPGME_Keyring } from './Keyring'; export class GpgME { /** @@ -32,7 +36,7 @@ export class GpgME { this._config = config; } - set Keyring(keyring){ + set Keyring(keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } @@ -47,13 +51,19 @@ export class GpgME { /** * Encrypt (and optionally sign) a Message - * @param {String|Object} data text/data to be encrypted as String. Also accepts Objects with a getText method - * @param {GPGME_Key|String|Array|Array} publicKeys Keys used to encrypt the message - * @param {GPGME_Key|String|Array|Array} secretKeys (optional) Keys used to sign the message - * @param {Boolean} base64 (optional) The data is already considered to be in base64 encoding + * @param {String|Object} data text/data to be encrypted as String. Also + * accepts Objects with a getText method + * @param {GPGME_Key|String|Array|Array} publicKeys + * Keys used to encrypt the message + * @param {GPGME_Key|String|Array|Array} secretKeys + * (optional) Keys used to sign the message + * @param {Boolean} base64 (optional) The data is already considered to be + * in base64 encoding * @param {Boolean} armor (optional) Request the output as armored block - * @param {Boolean} wildcard (optional) If true, recipient information will not be added to the message - * @param {Object} additional use additional gpg options (refer to src/permittedOperations) + * @param {Boolean} wildcard (optional) If true, recipient information will + * not be added to the message + * @param {Object} additional use additional gpg options + * (refer to src/permittedOperations) * @returns {Promise} Encrypted message: * data: The encrypted message * base64: Boolean indicating whether data is base64 encoded. @@ -64,7 +74,7 @@ export class GpgME { ){ let msg = createMessage('encrypt'); if (msg instanceof Error){ - return Promise.reject(msg) + return Promise.reject(msg); } msg.setParameter('armor', armor); msg.setParameter('always-trust', true); @@ -80,7 +90,7 @@ export class GpgME { putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); - }; + } if (additional){ let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { @@ -97,14 +107,16 @@ export class GpgME { /** * Decrypt a Message - * @param {String|Object} data text/data to be decrypted. Accepts Strings and Objects with a getText method - * @param {Boolean} base64 (optional) Response is expected to be base64 encoded + * @param {String|Object} data text/data to be decrypted. Accepts Strings + * and Objects with a getText method + * @param {Boolean} base64 (optional) Response is expected to be base64 + * encoded * @returns {Promise} decrypted message: data: The decrypted data. This may be base64 encoded. base64: Boolean indicating whether data is base64 encoded. mime: A Boolean indicating whether the data is a MIME object. signatures: Array of signature Objects TODO not yet implemented. - // should be an object that can tell if all signatures are valid etc. + // should be an object that can tell if all signatures are valid . * @async */ decrypt(data, base64=false){ @@ -124,14 +136,16 @@ export class GpgME { /** * Sign a Message - * @param {String|Object} data text/data to be decrypted. Accepts Strings and Objects with a gettext methos - * @param {GPGME_Key|String|Array|Array} keys The key/keys to use for signing + * @param {String|Object} data text/data to be decrypted. Accepts Strings + * and Objects with a gettext methos + * @param {GPGME_Key|String|Array|Array} keys The + * key/keys to use for signing * @param {*} mode The signing mode. Currently supported: * 'clearsign': (default) The Message is embedded into the signature * 'detached': The signature is stored separately * @param {*} base64 input is considered base64 * @returns {Promise} - * data: The resulting data. In clearsign mode this includes the signature + * data: The resulting data. Includes the signature in clearsign mode * signature: The detached signature (if in detached mode) * @async */ @@ -154,7 +168,6 @@ export class GpgME { if (mode === 'detached') { msg.expected = 'base64'; } - let me = this; return new Promise(function(resolve,reject) { msg.post().then( function(message) { if (mode === 'clearsign'){ @@ -169,7 +182,7 @@ export class GpgME { } }, function(error){ reject(error); - }) + }); }); } } @@ -180,7 +193,7 @@ export class GpgME { * @param {*} data The data to enter */ function putData(message, data){ - if (!message || !message instanceof GPGME_Message ) { + if (!message || !(message instanceof GPGME_Message) ) { return gpgme_error('PARAM_WRONG'); } if (!data){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 220a6984..1b13ec4a 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -16,16 +16,21 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ -import { GpgME } from "./gpgmejs"; -import { gpgme_error } from "./Errors"; -import { Connection } from "./Connection"; -import { defaultConf, availableConf } from "./Config"; + +import { GpgME } from './gpgmejs'; +import { gpgme_error } from './Errors'; +import { Connection } from './Connection'; +import { defaultConf, availableConf } from './Config'; /** * Initializes a nativeMessaging Connection and returns a GPGMEjs object - * @param {Object} config Configuration. See Config.js for available parameters. Still TODO + * @param {Object} config Configuration. See Config.js for available parameters. + * Still TODO */ function init(config){ let _conf = parseconfiguration(config); @@ -41,16 +46,16 @@ function init(config){ } else { reject(gpgme_error('CONN_NO_CONNECT')); } - }, function(error){ + }, function(){ //unspecific connection error. Should not happen reject(gpgme_error('CONN_NO_CONNECT')); - }); + }); }); } function parseconfiguration(rawconfig = {}){ if ( typeof(rawconfig) !== 'object'){ return gpgme_error('PARAM_WRONG'); - }; + } let result_config = {}; let conf_keys = Object.keys(rawconfig); @@ -75,8 +80,8 @@ function parseconfiguration(rawconfig = {}){ } } return result_config; -}; +} export default { init: init -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index e4f9bd22..445a40cc 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -16,9 +16,12 @@ * 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+ + * + * Author(s): + * Maximilian Krambach */ - /** +/** * Definition of the possible interactions with gpgme-json. * operation: required: Array @@ -41,7 +44,7 @@ infos: Array<*> arbitrary information that may result in a list } } - */ +*/ export const permittedOperations = { encrypt: { @@ -65,7 +68,7 @@ export const permittedOperations = { array_allowed: true }, 'chunksize': { - allowed: ['number'] + allowed: ['number'] }, 'base64': { allowed: ['boolean'] @@ -154,7 +157,8 @@ export const permittedOperations = { }, 'mode': { allowed: ['string'], - allowed_data: ['detached', 'clearsign'] // TODO 'opaque' not used + allowed_data: ['detached', 'clearsign'] + // TODO 'opaque' is not used, but available on native app }, 'base64': { allowed: ['boolean'] @@ -166,14 +170,17 @@ export const permittedOperations = { answer: { type: ['signature', 'ciphertext'], data: ['data'], // Unless armor mode is used a Base64 encoded binary - // signature. In armor mode a string with an armored - // OpenPGP or a PEM message. + // signature. In armor mode a string with an armored + // OpenPGP or a PEM message. params: ['base64'] } }, + // note: For the meaning of the optional keylist flags, refer to + // https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html keylist:{ required: {}, + optional: { 'protocol': { allowed: ['string'], @@ -182,8 +189,6 @@ export const permittedOperations = { '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'] }, @@ -305,11 +310,6 @@ export const permittedOperations = { infos: [] } }, - /** - *TBD get armored secret different treatment from keyinfo! - * TBD key modification? - - */ version: { required: {}, @@ -321,4 +321,11 @@ export const permittedOperations = { params:[] } } -} + + /** + * TBD handling of secrets + * TBD key modification? + * TBD: key generation + */ + +}; diff --git a/lang/js/unittests.js b/lang/js/unittests.js index d1118448..ce1dd0c3 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -32,7 +32,7 @@ import { GPGME_Keyring } from './src/Keyring'; import {GPGME_Message, createMessage} from './src/Message'; mocha.setup('bdd'); -var expect = chai.expect; +const expect = chai.expect; chai.config.includeStack = true; function unittests (){ @@ -266,78 +266,48 @@ function unittests (){ 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('armored')).to.be.a('string'); - expect(result[0].get('armored')).to.include( - '-----END PGP PUBLIC KEY BLOCK-----'); - done(); - }); - }); + 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('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('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(); - }); + 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(){ -- cgit v1.2.3 From 7a072270ac031152ee034df0f5b6ef5e8bf7d394 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 6 Jun 2018 15:29:21 +0200 Subject: js: change Keyinfo timestamps into javascript date -- * src/Key.js --- lang/js/src/Key.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 8c8726a2..5986254e 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -115,6 +115,9 @@ export class GPGME_Key { new GPGME_UserId(data.userids[i])); } break; + case 'last_update': + this._data[dataKeys[i]] = new Date( data[dataKeys[i]] * 1000 ); + break; default: this._data[dataKeys[i]] = data[dataKeys[i]]; } @@ -124,7 +127,6 @@ export class GPGME_Key { /** * 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. @@ -313,7 +315,11 @@ class GPGME_Subkey { } if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { - this._data[property] = value; + if (property === 'timestamp' || property === 'expires'){ + this._data[property] = new Date(value * 1000); + } else { + this._data[property] = value; + } } } } @@ -346,9 +352,14 @@ class GPGME_UserId { } if (validUserIdProperties.hasOwnProperty(property)){ if (validUserIdProperties[property](value) === true) { - this._data[property] = value; + if (property === 'last_update'){ + this._data[property] = new Date(value*1000); + } else { + this._data[property] = value; + } } } + } /** -- cgit v1.2.3 From c072675f3f2d734297a348c6de810148fb1424a2 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 8 Jun 2018 17:54:58 +0200 Subject: js: change chunksize handling and decoding -- * the nativeApp now sends all data in one base64-encoded string, which needs reassembly, but in a much easier way now. * there are some new performance problems now, especially with decrypting data --- .../tests/encryptDecryptTest.js | 31 +---- .../BrowserTestExtension/tests/longRunningTests.js | 38 +++--- lang/js/src/Connection.js | 143 +++++++++------------ lang/js/src/Message.js | 49 +++++-- lang/js/src/gpgmejs.js | 22 ++-- lang/js/src/permittedOperations.js | 81 ++++++------ lang/js/unittests.js | 3 +- 7 files changed, 170 insertions(+), 197 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index a84be27c..bd72c1d2 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -152,7 +152,7 @@ describe('Encryption and Decryption', function () { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt(b64data, - inputvalues.encrypt.good.fingerprint).then( + inputvalues.encrypt.good.fingerprint, true).then( function (answer) { expect(answer).to.not.be.empty; expect(answer.data).to.be.a('string'); @@ -185,33 +185,7 @@ describe('Encryption and Decryption', function () { '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); - - 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( + context.decrypt(answer.data).then( function (result) { expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); @@ -222,5 +196,4 @@ describe('Encryption and Decryption', function () { }); }).timeout(3000); - }); diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index eefe126d..e148d1cf 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -24,7 +24,7 @@ /* global bigString, inputvalues */ describe('Long running Encryption/Decryption', function () { - for (let i=0; i < 100; i++) { + for (let i=0; i < 101; i++) { it('Successful encrypt/decrypt completely random data ' + (i+1) + '/100', function (done) { let prm = Gpgmejs.init(); @@ -43,30 +43,32 @@ describe('Long running Encryption/Decryption', function () { function(result){ expect(result).to.not.be.empty; expect(result.data).to.be.a('string'); + /* if (result.data.length !== data.length) { - // console.log('diff: ' + - // (result.data.length - data.length)); + console.log('diff: ' + + (result.data.length - data.length)); for (let i=0; i < result.data.length; i++){ if (result.data[i] !== data[i]){ - // console.log('position: ' + i); - // console.log('result : ' + - // result.data.charCodeAt(i) + - // result.data[i-2] + - // result.data[i-1] + - // result.data[i] + - // result.data[i+1] + - // result.data[i+2]); - // console.log('original: ' + - // data.charCodeAt(i) + - // data[i-2] + - // data[i-1] + - // data[i] + - // data[i+1] + - // data[i+2]); + console.log('position: ' + i); + console.log('result : ' + + result.data.charCodeAt(i) + + result.data[i-2] + + result.data[i-1] + + result.data[i] + + result.data[i+1] + + result.data[i+2]); + console.log('original: ' + + data.charCodeAt(i) + + data[i-2] + + data[i-1] + + data[i] + + data[i+1] + + data[i+2]); break; } } } + */ expect(result.data).to.equal(data); done(); }); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index e9c0b213..f399b22b 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -108,6 +108,7 @@ export class Connection{ return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } let me = this; + let chunksize = message.chunksize; return new Promise(function(resolve, reject){ let answer = new Answer(message); let listener = function(msg) { @@ -115,22 +116,27 @@ export class Connection{ 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); + let answer_result = answer.collect(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); + if (msg.more === true){ + me._connection.postMessage({ + 'op': 'getmore', + 'chunksize': chunksize + }); + } else { + me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); + if (answer.message instanceof Error){ + reject(answer.message); + } else { + resolve(answer.message); + } + } } } }; @@ -170,19 +176,32 @@ class Answer{ constructor(message){ this.operation = message.operation; - this.expected = message.expected; + this.expect = message.expect; } - /** - * 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 = {}; + collect(msg){ + if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (this._responseb64 === undefined){ + //this._responseb64 = [msg.response]; + this._responseb64 = msg.response; + return true; + } else { + //this._responseb64.push(msg.response); + this._responseb64 += msg.response; + return true; } - let messageKeys = Object.keys(msg); + } + + get message(){ + if (this._responseb64 === undefined){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + // let _decodedResponse = JSON.parse(atob(this._responseb64.join(''))); + let _decodedResponse = JSON.parse(atob(this._responseb64)); + let _response = {}; + let messageKeys = Object.keys(_decodedResponse); let poa = permittedOperations[this.operation].answer; if (messageKeys.length === 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); @@ -191,80 +210,42 @@ class Answer{ let key = messageKeys[i]; switch (key) { case 'type': - if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){ + if (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); + } else if (poa.type.indexOf(_decodedResponse.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } break; - case 'more': + case 'base64': 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]); - } + case 'msg': + if (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); } - //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]); - } + break; + default: + if (!poa.data.hasOwnProperty(key)){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - else { + if( typeof(_decodedResponse[key]) !== poa.data[key] ){ 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( + if (_decodedResponse.base64 === true + && poa.data[key] === 'string' + && this.expect === undefined + ){ + _response[key] = decodeURIComponent( + atob(_decodedResponse[key]).split('').map( function(c) { return '%' + - ('00' + c.charCodeAt(0).toString(16)).slice(-2); + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); + } else { + _response[key] = _decodedResponse[key]; } - } else { - msg[keys[i]] = this._response[keys[i]]; + break; } } - return msg; + return _response; } } diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 0ddda6c4..7ccf7efc 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -46,7 +46,6 @@ export class GPGME_Message { constructor(operation){ this.operation = operation; - this._expected = 'string'; } set operation (op){ @@ -59,24 +58,50 @@ export class GPGME_Message { } } } - get operation(){ return this._msg.op; } - set expected(string){ - if (string === 'base64'){ - this._expected = 'base64'; + /** + * Set the maximum size of responses from gpgme in bytes. Values allowed + * range from 10kB to 1MB. The lower limit is arbitrary, the upper limit + * fixed by browsers' nativeMessaging specifications + */ + set chunksize(value){ + if ( + Number.isInteger(value) && + value > 10 * 1024 && + value <= 1024 * 1024 + ){ + this._chunksize = value; + } + } + get chunksize(){ + if (this._chunksize === undefined){ + return 1024 * 1023; + } else { + return this._chunksize; } } - get expected() { - if (this._expected === 'base64'){ - return this._expected; + /** + * If expect is set to 'base64', the response is expected to be base64 + * encoded binary + */ + set expect(value){ + if (value ==='base64'){ + this._expect = value; + } + } + + get expect(){ + if ( this._expect === 'base64'){ + return this._expect; } - return 'string'; + return undefined; } + /** * Sets a parameter for the message. Note that the operation has to be set * first, to be able to check if the parameter is permittted @@ -188,6 +213,7 @@ export class GPGME_Message { */ get message(){ if (this.isComplete === true){ + this._msg.chunksize = this.chunksize; return this._msg; } else { @@ -201,10 +227,13 @@ export class GPGME_Message { return new Promise(function(resolve, reject) { if (me.isComplete === true) { let conn = new Connection; + if (me._msg.chunksize === undefined){ + me._msg.chunksize = 1023*1024; + } conn.post(me).then(function(response) { resolve(response); }, function(reason) { - reject(gpgme_error('GNUPG_ERROR', reason)); + reject(reason); }); } else { diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index cbad9021..09bca7f9 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -57,8 +57,8 @@ export class GpgME { * Keys used to encrypt the message * @param {GPGME_Key|String|Array|Array} secretKeys * (optional) Keys used to sign the message - * @param {Boolean} base64 (optional) The data is already considered to be - * in base64 encoding + * @param {Boolean} base64 (optional) The data will be interpreted as + * base64 encoded data * @param {Boolean} armor (optional) Request the output as armored block * @param {Boolean} wildcard (optional) If true, recipient information will * not be added to the message @@ -109,24 +109,20 @@ export class GpgME { * Decrypt a Message * @param {String|Object} data text/data to be decrypted. Accepts Strings * and Objects with a getText method - * @param {Boolean} base64 (optional) Response is expected to be base64 - * encoded * @returns {Promise} decrypted message: - data: The decrypted data. This may be base64 encoded. + data: The decrypted data. base64: Boolean indicating whether data is base64 encoded. mime: A Boolean indicating whether the data is a MIME object. signatures: Array of signature Objects TODO not yet implemented. - // should be an object that can tell if all signatures are valid . + // should be an object that can tell if all signatures are valid. * @async */ - decrypt(data, base64=false){ + decrypt(data){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); - if (base64 === true){ - msg.expected = 'base64'; - } + if (msg instanceof Error){ return Promise.reject(msg); } @@ -165,10 +161,10 @@ export class GpgME { } msg.setParameter('mode', mode); putData(msg, data); - if (mode === 'detached') { - msg.expected = 'base64'; - } return new Promise(function(resolve,reject) { + if (mode ==='detached'){ + msg.expect= 'base64'; + } msg.post().then( function(message) { if (mode === 'clearsign'){ resolve({ diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 445a40cc..6ac33af9 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -37,11 +37,8 @@ 5000 ms would be too short answer: type: The payload property of the answer. May be - partial and in need of concatenation - params: Array Information that do not change throughout - the message - infos: Array<*> arbitrary information that may result in a list + data: + the properties expected and their type, eg: {'data':'string'} } } */ @@ -67,9 +64,6 @@ export const permittedOperations = { allowed: ['string'], array_allowed: true }, - 'chunksize': { - allowed: ['number'] - }, 'base64': { allowed: ['boolean'] }, @@ -101,9 +95,10 @@ export const permittedOperations = { }, answer: { type: ['ciphertext'], - data: ['data'], - params: ['base64'], - infos: [] + data: { + 'data': 'string', + 'base64':'boolean' + } } }, @@ -119,18 +114,18 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - 'chunksize': { - allowed: ['number'], - }, 'base64': { allowed: ['boolean'] } }, answer: { type: ['plaintext'], - data: ['data'], - params: ['base64', 'mime'], - infos: ['signatures'] + data: { + 'data': 'string', + 'base64': 'boolean', + 'mime': 'boolean', + 'signatures': 'object' + } } }, @@ -149,9 +144,6 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - 'chunksize': { - allowed: ['number'], - }, 'sender': { allowed: ['string'], }, @@ -169,10 +161,11 @@ export const permittedOperations = { }, answer: { type: ['signature', 'ciphertext'], - data: ['data'], // Unless armor mode is used a Base64 encoded binary - // signature. In armor mode a string with an armored - // OpenPGP or a PEM message. - params: ['base64'] + data: { + 'data': 'string', + 'base64':'boolean' + } + } }, @@ -186,9 +179,6 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - 'chunksize': { - allowed: ['number'], - }, 'secret': { allowed: ['boolean'] }, @@ -220,9 +210,10 @@ export const permittedOperations = { }, answer: { type: ['keys'], - data: [], - params: ['base64'], - infos: ['keys'] + data: { + 'base64': 'boolean', + 'keys': 'object' + } } }, @@ -233,9 +224,6 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - 'chunksize': { - allowed: ['number'], - }, 'keys': { allowed: ['string'], array_allowed: true @@ -259,8 +247,10 @@ export const permittedOperations = { }, answer: { type: ['keys'], - data: ['data'], - params: ['base64'] + data: { + 'data': 'string', + 'base64': 'boolean' + } } }, @@ -280,10 +270,10 @@ export const permittedOperations = { }, }, answer: { - infos: ['result'], type: [], - data: [], - params: [] + data: { + 'result': 'Object' + } } }, @@ -299,15 +289,15 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - // 'secret': { not yet implemented + // 'secret': { not implemented // allowed: ['boolean'] // } }, answer: { - data: [], - params:['success'], - infos: [] + data: { + 'success': 'boolean' + } } }, @@ -316,9 +306,10 @@ export const permittedOperations = { optional: {}, answer: { type: [''], - data: ['gpgme'], - infos: ['info'], - params:[] + data: { + 'gpgme': 'string', + 'info': 'object' + } } } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index ce1dd0c3..169e8ebc 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -339,7 +339,8 @@ function unittests (){ test0.setParameter('keys', hp.validFingerprints); expect(test0.message).to.not.be.null; - expect(test0.message).to.have.keys('op', 'data', 'keys'); + expect(test0.message).to.have.keys('op', 'data', 'keys', + 'chunksize'); expect(test0.message.op).to.equal('encrypt'); expect(test0.message.data).to.equal( mp.valid_encrypt_data); -- cgit v1.2.3 From e97e6c06e950cfad424e120f4f3752b594214c94 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 11 Jun 2018 12:08:50 +0200 Subject: js: Add key creation to Keyring -- * src/Keyring.js: Added method generateKey for new Keys Still TODO: Key length and some further testing. Automated testing does not work in this case, and gpgmejs will not be able to delete test keys again. * src/permittedOperations.js Added new method's definitions according to gpgme-json --- lang/js/src/Keyring.js | 60 +++++++++++++++++++++++++++++++++++++- lang/js/src/permittedOperations.js | 23 +++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 71585878..0d4e3c52 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -197,5 +197,63 @@ export class GPGME_Keyring { } } - // generateKey + /** + * Generates a new Key pair directly in gpg, and returns a GPGME_Key + * representing that Key. Please note that due to security concerns, secret + * Keys can not be _deleted_ from inside gpgmejs. + * + * @param {String} userId The user Id, e.g. "Foo Bar " + * @param {*} algo (optional) algorithm to be used. See + * {@link supportedKeyAlgos } below for supported values. + * @param {Number} keyLength (optional) TODO + * @param {Date} expires (optional) Expiration date. If not set, expiration + * will be set to 'never' + * + * @returns{Promise} + */ + generateKey(userId, algo = 'default', keyLength, expires){ + if ( + typeof(userId) !== 'string' || + supportedKeyAlgos.indexOf(algo) < 0 || + (expires && !(expires instanceof Date)) + // TODO keylength + // TODO check for completeness of algos + ){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('createkey'); + msg.setParameter('userid', userId); + msg.setParameter('algo', algo); + if (expires){ + msg.setParameter('expires', + Math.floor(expires.valueOf()/1000)); + } + // TODO append keylength to algo + msg.post().then(function(response){ + me.getKeys(response.fingerprint, true).then( + // TODO make prepare_sync (second parameter) optional here. + function(result){ + resolve(result); + }, function(error){ + reject(error); + }); + }, function(error) { + reject(error); + }); + }); + } } + +/** + * A list of algorithms supported for key generation. + */ +const supportedKeyAlgos = [ + 'default', + 'rsa', + 'dsa', + 'elg', + 'ed25519', + 'cv25519' +]; \ No newline at end of file diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 6ac33af9..91612ada 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -311,12 +311,31 @@ export const permittedOperations = { 'info': 'object' } } - } + }, + createkey: { + pinentry: true, + required: { + userid: { + allowed: ['string'] + } + }, + optional: { + algo: { + allowed: ['string'] + }, + expires: { + allowed: ['number'], + } + }, + answer: { + type: [''], + data: {'fingerprint': 'string'} + } + } /** * TBD handling of secrets * TBD key modification? - * TBD: key generation */ }; -- cgit v1.2.3 From e154554e9a48a08219649a58be0b641c561e1748 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 11 Jun 2018 15:10:43 +0200 Subject: js: removed config -- * There is no use for a configuration at the moment, and it seems improbable that this use will arise. --- lang/js/BrowserTestExtension/tests/inputvalues.js | 8 ---- lang/js/BrowserTestExtension/tests/startup.js | 17 --------- lang/js/src/Config.js | 34 ----------------- lang/js/src/gpgmejs.js | 4 +- lang/js/src/index.js | 46 +++-------------------- 5 files changed, 7 insertions(+), 102 deletions(-) delete mode 100644 lang/js/src/Config.js diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 024aad25..1c701904 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -53,14 +53,6 @@ const inputvalues = {// eslint-disable-line no-unused-vars // bogus fingerprint) fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' } - }, - init: { - // some parameters - invalid_startups: [ - {all_passwords: true}, - 'openpgpmode', - {api_style:'frankenstein'} - ] } }; diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index d434b6d4..dae94025 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -22,7 +22,6 @@ */ /* global describe, it, expect, Gpgmejs */ -/* global inputvalues */ describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ @@ -36,19 +35,3 @@ describe('GPGME context', function(){ }); }); }); - -describe('GPGME does not start with invalid parameters', function(){ - for (let i=0; i < inputvalues.init.invalid_startups.length; i++){ - it('Parameter '+ i, function(done){ - let prm = Gpgmejs.init(inputvalues.init.invalid_startups[i]); - prm.then(function(context){ - expect(context).to.be.undefined; - done(); - }, function(error){ - expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('PARAM_WRONG'); - done(); - }); - }); - } -}); \ No newline at end of file diff --git a/lang/js/src/Config.js b/lang/js/src/Config.js deleted file mode 100644 index 8a3ef491..00000000 --- a/lang/js/src/Config.js +++ /dev/null @@ -1,34 +0,0 @@ -/* 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+ - * - * Author(s): - * Maximilian Krambach - */ - -export const availableConf = { - null_expire_is_never: [true, false], - // cachedKeys: Some Key info will not be queried on each invocation, - // manual refresh by Key.refresh() - cachedKeys: [true, false] -}; - -export const defaultConf = { - null_expire_is_never: false, - cachedKeys: false -}; \ No newline at end of file diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 09bca7f9..7fa7643c 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -30,10 +30,8 @@ import { GPGME_Keyring } from './Keyring'; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port - * TODO: add configuration */ - constructor(config){ //TODO config not parsed - this._config = config; + constructor(){ } set Keyring(keyring){ diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 1b13ec4a..6db28733 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -25,24 +25,20 @@ import { GpgME } from './gpgmejs'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; -import { defaultConf, availableConf } from './Config'; /** - * Initializes a nativeMessaging Connection and returns a GPGMEjs object - * @param {Object} config Configuration. See Config.js for available parameters. - * Still TODO + * Tests nativeMessaging once and returns a GpgME object if successful. + * @returns {GpgME | Error} + * + * @async */ -function init(config){ - let _conf = parseconfiguration(config); - if (_conf instanceof Error){ - return Promise.reject(_conf); - } +function init(){ return new Promise(function(resolve, reject){ let connection = new Connection; connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(new GpgME(_conf)); + resolve(new GpgME()); } else { reject(gpgme_error('CONN_NO_CONNECT')); } @@ -52,36 +48,6 @@ function init(config){ }); } -function parseconfiguration(rawconfig = {}){ - if ( typeof(rawconfig) !== 'object'){ - return gpgme_error('PARAM_WRONG'); - } - let result_config = {}; - let conf_keys = Object.keys(rawconfig); - - for (let i=0; i < conf_keys.length; i++){ - - if (availableConf.hasOwnProperty(conf_keys[i])){ - let value = rawconfig[conf_keys[i]]; - if (availableConf[conf_keys[i]].indexOf(value) < 0){ - return gpgme_error('PARAM_WRONG'); - } else { - result_config[conf_keys[i]] = value; - } - } - else { - return gpgme_error('PARAM_WRONG'); - } - } - let default_keys = Object.keys(defaultConf); - for (let j=0; j < default_keys.length; j++){ - if (!result_config.hasOwnProperty(default_keys[j])){ - result_config[default_keys[j]] = defaultConf[default_keys[j]]; - } - } - return result_config; -} - export default { init: init }; \ No newline at end of file -- cgit v1.2.3 From d0fc4ded58f4a6a86c5ee0a36a3d3c669e244d0d Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 13 Jun 2018 11:49:37 +0200 Subject: js: less confusing icons for test/Demo extension -- * The current test icon was just a generic pin. Changed that by the gnupg lock symbol with 'Demo'/'Tests' written on it. Original taken from gnupg artwork/icons/lock-wing.svg. --- lang/js/BrowserTestExtension/testicon.png | Bin 16192 -> 2697 bytes lang/js/DemoExtension/testicon.png | Bin 16192 -> 2670 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/lang/js/BrowserTestExtension/testicon.png b/lang/js/BrowserTestExtension/testicon.png index 12c3f5df..a98463de 100644 Binary files a/lang/js/BrowserTestExtension/testicon.png and b/lang/js/BrowserTestExtension/testicon.png differ diff --git a/lang/js/DemoExtension/testicon.png b/lang/js/DemoExtension/testicon.png index 12c3f5df..84284e0b 100644 Binary files a/lang/js/DemoExtension/testicon.png and b/lang/js/DemoExtension/testicon.png differ -- cgit v1.2.3 From aed402c5d572b60246f1f8e57ae269f8c91b0b7c Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 13 Jun 2018 15:22:03 +0200 Subject: js: getDefaultKey and verify fix -- * DemoExtension/maindemo.js - added a Demo for retrieving the default signing key * src/Errors.js - add a new Error if no default key can be determined * src/Key.js added documentation and a TODO marker for hasSecret. * src/Keyring.js implemented getDefaultKey * src/permittedOperations.js: Added missing entry for verify, added config_opt --- lang/js/CHECKLIST | 5 ++-- lang/js/DemoExtension/maindemo.js | 14 ++++++++-- lang/js/DemoExtension/mainui.html | 11 ++++++++ lang/js/src/Errors.js | 5 ++++ lang/js/src/Key.js | 8 ++++++ lang/js/src/Keyring.js | 56 +++++++++++++++++++++++++++++++++++++- lang/js/src/permittedOperations.js | 54 +++++++++++++++++++++++++++++++++++- 7 files changed, 147 insertions(+), 6 deletions(-) diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST index fe260187..2e70dff1 100644 --- a/lang/js/CHECKLIST +++ b/lang/js/CHECKLIST @@ -13,10 +13,11 @@ receiving an answer [*] Key handling (import/export, modifying, status queries) [x] Import (not importing secret) [x] Export (not exporting secret) - [x] status queries + [*] status queries + [ ] getHasSecret [ ] key generation [ ] modification - [*] Configuration handling + [x] Configuration handling [ ] check for completeness Communication with other implementations diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index 5cde1ce8..67b811f6 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -36,7 +36,7 @@ document.addEventListener('DOMContentLoaded', function() { 'answer').value = answer.data; } }, function(errormsg){ - alert( errormsg.code + ' ' + errormsg.msg); + alert( errormsg.message); }); }); @@ -50,8 +50,18 @@ document.addEventListener('DOMContentLoaded', function() { 'answer').value = answer.data; } }, function(errormsg){ - alert( errormsg.code + ' ' + errormsg.msg); + alert(errormsg.message); }); }); + + document.getElementById('getdefaultkey').addEventListener('click', + function(){ + gpgmejs.Keyring.getDefaultKey().then(function(answer){ + document.getElementById('defaultkey').innerHtml = + answer.fingerprint; + }, function(errormsg){ + alert(errormsg.message); + }); + }); }); }); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html index 76b8a221..91be7bbc 100644 --- a/lang/js/DemoExtension/mainui.html +++ b/lang/js/DemoExtension/mainui.html @@ -29,5 +29,16 @@

    Result data:

    + +
    +
      +
    • + Default Key: +
      + +
    • + + +
    diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index dabf6a5c..73e74382 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -78,6 +78,11 @@ const err_list = { msg:'This property has not been retrieved yet from GPG', type: 'error' }, + 'KEY_NO_DEFAULT': { + msg:'A default key could not be established. Please check yout gpg ' + + 'configuration', + type: 'error' + }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 5986254e..3e4f1c78 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -192,6 +192,7 @@ export class GPGME_Key { * Query the armored block of the non- secret parts of the Key directly * from gpg. * @returns {Promise} + * @async */ getArmor(){ let me = this; @@ -211,6 +212,13 @@ export class GPGME_Key { }); } + /** + * Find out if the Key includes a secret part + * @returns {Promise} + * + * @async + */ + // TODO: Does not work yet, result is always false getHasSecret(){ let me = this; return new Promise(function(resolve, reject) { diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 0d4e3c52..e07a5934 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -99,7 +99,61 @@ export class GPGME_Keyring { }); } - // getDefaultKey() Big TODO + /** + * Returns the Key to be used by default for signing operations, + * looking up the gpg configuration, or returning the first key that + * contains a secret key. + * @returns {Promise} + * + * @async + * TODO: getHasSecret always returns false at this moment, so this fucntion + * still does not fully work as intended. + * + */ + getDefaultKey() { + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('config_opt'); + msg.setParameter('component', 'gpg'); + msg.setParameter('option', 'default-key'); + msg.post().then(function(response){ + if (response.value !== undefined + && response.value.hasOwnProperty('string') + && typeof(response.value.string) === 'string' + ){ + me.getKeys(response.value.string,true).then(function(keys){ + if(keys.length === 1){ + resolve(keys[0]); + } else { + reject(gpgme_error('KEY_NO_DEFAULT')); + } + }, function(error){ + reject(error); + }); + } else { + // TODO: this is overly 'expensive' in communication + // and probably performance, too + me.getKeys(null,true).then(function(keys){ + for (let i=0; i < keys.length; i++){ + console.log(keys[i]); + console.log(keys[i].get('hasSecret')); + if (keys[i].get('hasSecret') === true){ + resolve(keys[i]); + break; + } + if (i === keys.length -1){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } + } + }, function(error){ + reject(error); + }); + } + }, function(error){ + reject(error); + }); + }); + } /** * diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 91612ada..044ae4af 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -314,7 +314,7 @@ export const permittedOperations = { }, createkey: { - pinentry: true, + pinentry: true, required: { userid: { allowed: ['string'] @@ -332,7 +332,59 @@ export const permittedOperations = { type: [''], data: {'fingerprint': 'string'} } + }, + + verify: { + required: { + data: { + allowed: ['string'] + } + }, + optional: { + 'protocol': { + allowed: ['string'], + allowed_data: ['cms', 'openpgp'] + }, + 'signature': { + allowed: ['string'] + }, + 'base64':{ + allowed: ['boolean'] + } + }, + answer: { + type: ['plaintext'], + data:{ + data: 'string', + base64:'boolean', + info: 'object' + // file_name: Optional string of the plaintext file name. + // is_mime: Boolean if the messages claims it is MIME. + // signatures: Array of signatures + } + } + }, + + config_opt: { + required: { + 'component':{ + allowed: ['string'], + // allowed_data: ['gpg'] // TODO check all available + }, + 'option': { + allowed: ['string'], + // allowed_data: ['default-key'] // TODO check all available + } + }, + optional: {}, + answer: { + type: [], + data: { + option: 'object' + } + } } + /** * TBD handling of secrets * TBD key modification? -- cgit v1.2.3 From 3c783bd09ce54b0d50dc3bea201e70e4fcbbf6a3 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 14 Jun 2018 12:15:51 +0200 Subject: js: add verify and signature parsing -- * src/gpgmejs.js: - Added verify method - Added verification results in decrypt (if signatures are present in the message) - Added a base64 option to decrypt * src/Signature.js: Convenience class for verification results. Used for e.g. converting timestamps to javascript time, quick overall validity checks * src/Keyring.js: removed debug code * src/Errors.js add two new Signature errors --- lang/js/src/Errors.js | 8 ++ lang/js/src/Keyring.js | 2 - lang/js/src/Signature.js | 193 +++++++++++++++++++++++++++++++++++++++++++++++ lang/js/src/gpgmejs.js | 138 +++++++++++++++++++++++++++++++-- 4 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 lang/js/src/Signature.js diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 73e74382..a8cd8b56 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -83,6 +83,14 @@ const err_list = { 'configuration', type: 'error' }, + 'SIG_WRONG': { + msg:'A malformed signature was created', + type: 'error' + }, + 'SIG_NO_SIGS': { + msg:'There were no signatures found', + type: 'error' + }, // generic 'PARAM_WRONG':{ msg: 'Invalid parameter was found', diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index e07a5934..451f936a 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -135,8 +135,6 @@ export class GPGME_Keyring { // and probably performance, too me.getKeys(null,true).then(function(keys){ for (let i=0; i < keys.length; i++){ - console.log(keys[i]); - console.log(keys[i].get('hasSecret')); if (keys[i].get('hasSecret') === true){ resolve(keys[i]); break; diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js new file mode 100644 index 00000000..d7d05983 --- /dev/null +++ b/lang/js/src/Signature.js @@ -0,0 +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+ + * + * Author(s): + * Maximilian Krambach + */ + +/** + * Validates a signature object and returns + * @param {Object} sigObject Object as returned by gpgme-json. The definition + * of the expected values are to be found in the constants 'expKeys', 'expSum', + * 'expNote' in this file. + * @returns {GPGME_Signature} Signature Object + */ + +import { gpgme_error } from './Errors'; + +export function createSignature(sigObject){ + if ( + typeof(sigObject) !=='object' || + !sigObject.hasOwnProperty('summary') || + !sigObject.hasOwnProperty('fingerpprint') || + !sigObject.hasOwnProperty('timestamp') + //TODO check if timestamp is mandatory in specification + ){ + return gpgme_error('SIG_WRONG'); + } + let keys = Object.keys(sigObject); + for (let i=0; i< keys.length; i++){ + if ( typeof(sigObject[keys[i]]) !== expKeys[keys[i]] ){ + return gpgme_error('SIG_WRONG'); + } + } + let sumkeys = Object.keys(sigObject.summary); + for (let i=0; i< sumkeys.length; i++){ + if ( typeof(sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){ + return gpgme_error('SIG_WRONG'); + } + } + if (sigObject.hasOwnProperty('notations')){ + if (!Array.isArray(sigObject.notations)){ + return gpgme_error('SIG_WRONG'); + } + for (let i=0; i < sigObject.notations.length; i++){ + let notation = sigObject.notations[i]; + let notekeys = Object.keys(notation); + for (let j=0; j < notekeys.length; j++){ + if ( typeof(notation[notekeys[j]]) !== expNote[notekeys[j]] ){ + return gpgme_error('SIG_WRONG'); + } + } + } + } + return new GPGME_Signature(sigObject); +} + + +/** + * Representing the details of a signature. It is supposed to be read-only. The + * full details as given by gpgme-json can be accessed from the _rawSigObject. + * ) + */ +class GPGME_Signature { + constructor(sigObject){ + this._rawSigObject = sigObject; + } + + /** + * The signatures' fingerprint + */ + get fingerprint(){ + return this._rawSigObject.fingerprint; + } + + /** + * The expiration of this Signature as Javascript date, or null if + * signature does not expire + * @returns {Date | null} + */ + get expiration(){ + if (!this._rawSigObject.exp_timestamp){ + return null; + } + return new Date(this._rawSigObject.exp_timestamp* 1000); + } + + /** + * The creation date of this Signature in Javascript Date + * @returns {Date} + */ + get timestamp(){ + return new Date(this._rawSigObject.timestamp* 1000); + } + + /** + * The overall validity of the key. If false, errorDetails may contain + * additional information + */ + get valid() { + if (this._rawSigObject.valid === true){ + return true; + } else { + return false; + } + } + + /** + * gives more information on non-valid signatures. Refer to the gpgme docs + * https://www.gnupg.org/documentation/manuals/gpgme/Verify.html for + * details on the values + * @returns {Object} Object with boolean properties + */ + get errorDetails(){ + let properties = ['revoked', 'key-expired', 'sig-expired', + 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', + 'sys-error']; + let result = {}; + for (let i=0; i< properties.length; i++){ + if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = this._rawSigObject[properties[i]]; + } + } + return result; + } + +} + +/** + * Keys and their value's type for the signature Object + */ +const expKeys = { + 'wrong_key_usage': 'boolean', + 'chain_model': 'boolean', + 'summary': 'object', + 'is_de_vs': 'boolean', + 'status_string':'string', + 'fingerprint':'string', + 'validity_string': 'string', + 'pubkey_algo_name':'string', + 'hash_algo_name':'string', + 'pka_address':'string', + 'status_code':'number', + 'timestamp':'number', + 'exp_timestamp':'number', + 'pka_trust':'number', + 'validity':'number', + 'validity_reason':'number', + 'notations': 'object' +}; + +/** + * Keys and their value's type for the summary + */ +const expSum = { + 'valid': 'boolean', + 'green': 'boolean', + 'red': 'boolean', + 'revoked': 'boolean', + 'key-expired': 'boolean', + 'sig-expired': 'boolean', + 'key-missing': 'boolean', + 'crl-missing': 'boolean', + 'crl-too-old': 'boolean', + 'bad-policy': 'boolean', + 'sys-error': 'boolean' +}; + +/** + * Keys and their value's type for notations objects + */ +const expNote = { + 'human_readable': 'boolean', + 'critical':'boolean', + 'name': 'string', + 'value': 'string', + 'flags': 'number' +}; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 7fa7643c..a0f7e968 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -26,6 +26,7 @@ import {GPGME_Message, createMessage} from './Message'; import {toKeyIdArray} from './Helpers'; import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; +import { createSignature } from './Signature'; export class GpgME { /** @@ -107,15 +108,28 @@ export class GpgME { * Decrypt a Message * @param {String|Object} data text/data to be decrypted. Accepts Strings * and Objects with a getText method - * @returns {Promise} decrypted message: - data: The decrypted data. - base64: Boolean indicating whether data is base64 encoded. - mime: A Boolean indicating whether the data is a MIME object. - signatures: Array of signature Objects TODO not yet implemented. - // should be an object that can tell if all signatures are valid. + * @param {Boolean} base64 (optional) false if the data is an armored block, + * true if it is base64 encoded binary data + * @returns {Promise} result: Decrypted Message and information + * @returns {String} result.data: The decrypted data. + * @returns {Boolean} result.base64: indicating whether data is base64 + * encoded. + * @returns {Boolean} result.is_mime: Indicating whether the data is a MIME + * object. + * @returns {String} result.file_name: The optional original file name + * @returns {Object} message.signatures Verification details for signatures: + * @returns {Boolean} message.signatures.all_valid: true if all signatures + * are valid + * @returns {Number} message.signatures.count: Number of signatures found + * @returns {Number} message.signatures.failures Number of invalid + * signatures + * @returns {Array} message.signatures.signatures. Two arrays + * (good & bad) of {@link GPGME_Signature} objects, offering further + * information. + * * @async */ - decrypt(data){ + decrypt(data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -124,8 +138,32 @@ export class GpgME { if (msg instanceof Error){ return Promise.reject(msg); } + if (base64 === true){ + msg.setParameter('base64', true); + } putData(msg, data); - return msg.post(); + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function(result){ + let _result = {data: result.data}; + _result.base64 = result.base64 ? true: false; + _result.is_mime = result.mime ? true: false; + if (result.file_name){ + _result.file_name = result.file_name; + } + if ( + result.hasOwnProperty('signatures') && + Array.isArray(result.signatures) + ) { + _result.signatures = collectSignatures(result.signatures); + } + resolve(_result); + }, function(error){ + reject(error); + }); + }); } /** @@ -179,6 +217,59 @@ export class GpgME { }); }); } + + /** + * Verifies data. + * @param {String|Object} data text/data to be verified. Accepts Strings + * and Objects with a gettext method + * @param {String} (optional) A detached signature. If not present, opaque + * mode is assumed + * @param {Boolean} (optional) Data and signature are base64 encoded + * // TODO verify if signature really is assumed to be base64 + * @returns {Promise} result: + * @returns {Boolean} result.data: The verified data + * @returns {Boolean} result.is_mime: The message claims it is MIME + * @returns {String} result.file_name: The optional filename of the message + * @returns {Boolean} result.all_valid: true if all signatures are valid + * @returns {Number} result.count: Number of signatures found + * @returns {Number} result.failures Number of unsuccessful signatures + * @returns {Array} result.signatures. Two arrays (good & bad) of + * {@link GPGME_Signature} objects, offering further information. + */ + verify(data, signature, base64 = false){ + let msg = createMessage('verify'); + let dt = this.putData(msg, data); + if (dt instanceof Error){ + return Promise.reject(dt); + } + if (signature){ + if (typeof(signature)!== 'string'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } else { + msg.setParameter('signature', signature); + } + } + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function (message){ + if (!message.info.signatures){ + reject(gpgme_error('SIG_NO_SIGS')); + } else { + let _result = collectSignatures(message.info.signatures); + _result.is_mime = message.info.is_mime? true: false; + if (message.info.filename){ + _result.file_name = message.info.filename; + } + _result.data = message.data; + resolve(_result); + } + }, function(error){ + reject(error); + }); + }); + } } /** @@ -209,3 +300,34 @@ function putData(message, data){ return gpgme_error('PARAM_WRONG'); } } + +function collectSignatures(sigs){ + if (!Array.isArray(sigs)){ + return gpgme_error('SIG_NO_SIGS'); + } + let summary = { + all_valid: false, + count: sigs.length, + failures: 0, + signatures: { + good: [], + bad: [], + } + }; + for (let i=0; i< sigs.length; i++){ + let sigObj = createSignature(sigs[i]); + if (sigObj instanceof Error){ + return gpgme_error('SIG_WRONG'); + } + if (sigObj.valid !== true){ + summary.failures += 1; + summary.signatures.bad.push(sigObj); + } else { + summary.signatures.good.push(sigObj); + } + } + if (summary.failures === 0){ + summary.all_valid = true; + } + return summary; +} \ No newline at end of file -- cgit v1.2.3 From 3cd428ba442f43e470b977e27e18ff52567baba5 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 14 Jun 2018 14:50:25 +0200 Subject: js: import result feedback -- * src/Keyring.js: Changed and documented the import result feedback towards the javascript side --- lang/js/src/Keyring.js | 54 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 451f936a..43d257d2 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -155,26 +155,36 @@ export class GPGME_Keyring { /** * - * @param {String} armored Armored Key block of the Kex(s) to be imported + * @param {String} armored Armored Key block of the Key(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 : - * '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: userIds changed - * Key.changes.signature: signatures changed - * Key.changes.subkey: subkeys changed - * // TODO: not yet implemented: Information about Keys that failed - * (e.g. malformed Keys, secretKeys are not accepted) + * + * @returns {Promise} result: A summary and an array of Keys + * considered + * + * @returns result.summary: Numerical summary of the result. See the + * feedbackValues variable for available values and the gnupg documentation + * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html + * for details on their meaning. + * @returns {Array} result.Keys: Array of objects containing: + * @returns {GPGME_Key} Key.key The resulting key + * @returns {String} Key.status: + * '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. + * @returns {Boolean} Key.changes.userId: userIds changed + * @returns {Boolean} Key.changes.signature: signatures changed + * @returns {Boolean} Key.changes.subkey: subkeys changed */ importKey(armored, prepare_sync) { + let feedbackValues = ['considered', 'no_user_id', 'imported', + 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', + 'new_signatures', 'new_revocations', 'secret_read', + 'secret_imported', 'secret_unchanged', 'skipped_new_keys', + 'not_imported', 'skipped_v3_keys']; if (!armored || typeof(armored) !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } @@ -185,8 +195,8 @@ export class GPGME_Keyring { msg.post().then(function(response){ let infos = {}; let fprs = []; - for (let res=0; res < response.result[0].imports.length; res++){ - let result = response.result[0].imports[res]; + for (let res=0; res Date: Tue, 19 Jun 2018 09:26:01 +0200 Subject: js: getDefaultKey and GenerateKey improvements -- * src/Keyring.js: added more options for key generation. * src/Key.js: GetDefaultKey now relies on the info associated with the key, as the approach of relying on a secret subkey did not work as intended * DemoExtension: Added a button for retrieval of the subkey, to test this functionality. --- lang/js/DemoExtension/maindemo.js | 2 +- lang/js/src/Key.js | 31 ++++++++----------------------- lang/js/src/Keyring.js | 22 ++++++++++------------ 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index 67b811f6..6230c3f0 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -57,7 +57,7 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('getdefaultkey').addEventListener('click', function(){ gpgmejs.Keyring.getDefaultKey().then(function(answer){ - document.getElementById('defaultkey').innerHtml = + document.getElementById('defaultkey').textContent = answer.fingerprint; }, function(errormsg){ alert(errormsg.message); diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 3e4f1c78..88c2b92f 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -218,7 +218,6 @@ export class GPGME_Key { * * @async */ - // TODO: Does not work yet, result is always false getHasSecret(){ let me = this; return new Promise(function(resolve, reject) { @@ -230,31 +229,17 @@ export class GPGME_Key { msg.setParameter('secret', true); msg.post().then(function(result){ me._data.hasSecret = null; - if (result.keys === undefined || result.keys.length < 1) { + if ( + result.keys && + result.keys.length === 1 && + result.keys[0].secret === true + ) { + me._data.hasSecret = true; + resolve(true); + } else { 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){ reject(error); }); diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 43d257d2..8bec1cea 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -273,21 +273,18 @@ export class GPGME_Keyring { * Keys can not be _deleted_ from inside gpgmejs. * * @param {String} userId The user Id, e.g. "Foo Bar " - * @param {*} algo (optional) algorithm to be used. See - * {@link supportedKeyAlgos } below for supported values. - * @param {Number} keyLength (optional) TODO + * @param {*} algo (optional) algorithm (and optionally key size to be + * used. See {@link supportedKeyAlgos } below for supported values. * @param {Date} expires (optional) Expiration date. If not set, expiration * will be set to 'never' * * @returns{Promise} */ - generateKey(userId, algo = 'default', keyLength, expires){ + generateKey(userId, algo = 'default', expires){ if ( typeof(userId) !== 'string' || supportedKeyAlgos.indexOf(algo) < 0 || (expires && !(expires instanceof Date)) - // TODO keylength - // TODO check for completeness of algos ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } @@ -295,12 +292,11 @@ export class GPGME_Keyring { return new Promise(function(resolve, reject){ let msg = createMessage('createkey'); msg.setParameter('userid', userId); - msg.setParameter('algo', algo); + msg.setParameter('algo', algo ); if (expires){ msg.setParameter('expires', Math.floor(expires.valueOf()/1000)); } - // TODO append keylength to algo msg.post().then(function(response){ me.getKeys(response.fingerprint, true).then( // TODO make prepare_sync (second parameter) optional here. @@ -321,9 +317,11 @@ export class GPGME_Keyring { */ const supportedKeyAlgos = [ 'default', - 'rsa', - 'dsa', - 'elg', + 'rsa', 'rsa2048', 'rsa3072', 'rsa4096', + 'dsa', 'dsa2048', 'dsa3072', 'dsa4096', + 'elg', 'elg2048', 'elg3072', 'elg4096', 'ed25519', - 'cv25519' + 'cv25519', + 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', + 'NIST P-256', 'NIST P-384', 'NIST P-521' ]; \ No newline at end of file -- cgit v1.2.3 From d27703ea4f0eed950cddf0157dc78bcb5d8d1c65 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 19 Jun 2018 16:40:40 +0200 Subject: Prepare build system for gpgme-js and dist it * configure.ac: Add js as language. * lang/Makefile.am: Add js as dist language. * lang/js/BrowserTestExtension/Makefile.am, lang/js/DemoExtension/Makefile.am, lang/js/Makefile.am, lang/js/src/Makefile.am: Populate EXTRA_DIST variables. -- There is no actual build done yet as there seems to be no way to build it with debian stable tools. This needs clarification. --- configure.ac | 27 +++++++++++++++++++- lang/Makefile.am | 2 +- lang/js/BrowserTestExtension/Makefile.am | 42 ++++++++++++++++++++++++++++++++ lang/js/DemoExtension/Makefile.am | 27 ++++++++++++++++++++ lang/js/Makefile.am | 34 ++++++++++++++++++++++++++ lang/js/src/Makefile.am | 30 +++++++++++++++++++++++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 lang/js/BrowserTestExtension/Makefile.am create mode 100644 lang/js/DemoExtension/Makefile.am create mode 100644 lang/js/Makefile.am create mode 100644 lang/js/src/Makefile.am diff --git a/configure.ac b/configure.ac index a1da1e3e..69d7132f 100644 --- a/configure.ac +++ b/configure.ac @@ -187,7 +187,7 @@ have_w64_system=no have_macos_system=no build_w32_glib=no build_w32_qt=no -available_languages="cl cpp python python2 python3 qt" +available_languages="cl cpp python python2 python3 qt js" default_languages="cl cpp python qt" case "${host}" in x86_64-*mingw32*) @@ -479,6 +479,28 @@ fi AC_SUBST(ENABLED_LANGUAGES, $enabled_languages) +# The javascript bindings +LIST_MEMBER("js", $enabled_languages) +if test "$found" = "1"; then + AC_CHECK_PROGS([NPX], [npx]) + if test -z "$NPX"; then + if test "$explicit_languages" = "1"; then + AC_MSG_ERROR([[ +*** +*** Node.js (npx) is required for the JavaScript binding. +***]]) + else + AC_MSG_WARN([ +*** +*** Node.js (npx) not found - JavaScript binding will not be built. +***]) + enabled_languages=$(echo $enabled_languages | sed 's/js//') + fi + fi +fi +AM_CONDITIONAL([BUILD_JS_BINDINGS], + [test -n "$NPX"]) + # # Provide information about the build. # @@ -899,6 +921,9 @@ AC_CONFIG_FILES(lang/qt/tests/Makefile) AC_CONFIG_FILES(lang/qt/src/qgpgme_version.h) AC_CONFIG_FILES([lang/Makefile lang/cl/Makefile lang/cl/gpgme.asd]) AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([lang/qt/doc/Doxyfile])]) +AC_CONFIG_FILES([lang/js/Makefile lang/js/src/Makefile + lang/js/BrowserTestExtension/Makefile + lang/js/DemoExtension/Makefile]) AC_CONFIG_FILES(lang/qt/doc/Makefile) AC_CONFIG_FILES([lang/python/Makefile lang/python/version.py diff --git a/lang/Makefile.am b/lang/Makefile.am index fd3ce4eb..1bf73316 100644 --- a/lang/Makefile.am +++ b/lang/Makefile.am @@ -18,6 +18,6 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA SUBDIRS = $(ENABLED_LANGUAGES) -DIST_SUBDIRS = cl cpp qt python +DIST_SUBDIRS = cl cpp qt python js EXTRA_DIST = README diff --git a/lang/js/BrowserTestExtension/Makefile.am b/lang/js/BrowserTestExtension/Makefile.am new file mode 100644 index 00000000..340d7ad9 --- /dev/null +++ b/lang/js/BrowserTestExtension/Makefile.am @@ -0,0 +1,42 @@ +# Makefile.am for GPGME-JS. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of GPGME. +# +# GPGME-CL is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GPGME-CL 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = browsertest.html \ + index.html \ + longTests.html \ + Makefile.am \ + manifest.json \ + popup.html \ + popup.js \ + runbrowsertest.js \ + rununittests.js \ + setup_testing.js \ + testicon.png \ + testkey2.pub \ + testkey.pub \ + testkey.sec \ + tests/encryptDecryptTest.js \ + tests/encryptTest.js \ + tests/inputvalues.js \ + tests/KeyImportExport.js \ + tests/longRunningTests.js \ + tests/signTest.js \ + tests/startup.js \ + unittests.html diff --git a/lang/js/DemoExtension/Makefile.am b/lang/js/DemoExtension/Makefile.am new file mode 100644 index 00000000..e93d0f66 --- /dev/null +++ b/lang/js/DemoExtension/Makefile.am @@ -0,0 +1,27 @@ +# Makefile.am for GPGME-JS. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of GPGME-CL. +# +# GPGME-CL is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GPGME-CL 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = manifest.json \ + popup.html \ + entry.js \ + maindemo.js \ + mainui.html \ + testicon.png \ + ui.css diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am new file mode 100644 index 00000000..4b5c7172 --- /dev/null +++ b/lang/js/Makefile.am @@ -0,0 +1,34 @@ +# Makefile.am for GPGME-JS. +# Copyright (C) 2018 2018 Bundesamt für Sicherheit in der Informationstechnik +# +# This file is part of GPGME-JS. +# +# GPGME-JS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GPGME-JS 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +SUBDIRS = src BrowserTestExtension DemoExtension + +EXTRA_DIST = build_extensions.sh \ + CHECKLIST \ + CHECKLIST_build \ + .eslintrc.json \ + Makefile.am \ + package.json \ + README \ + README_testing \ + unittest_inputvalues.js \ + unittests.js \ + webpack.conf.js \ + webpack.conf_unittests.js diff --git a/lang/js/src/Makefile.am b/lang/js/src/Makefile.am new file mode 100644 index 00000000..dd330d50 --- /dev/null +++ b/lang/js/src/Makefile.am @@ -0,0 +1,30 @@ +# Makefile.am for GPGME-JS. +# Copyright (C) 2018 Intevation GmbH +# +# This file is part of GPGME. +# +# GPGME-CL is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GPGME-CL 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA + +EXTRA_DIST = Connection.js \ + Errors.js \ + gpgmejs.js \ + Helpers.js \ + index.js \ + Key.js \ + Keyring.js \ + Message.js \ + permittedOperations.js \ + Signature.js -- cgit v1.2.3 From 88e7f8ec2ef3d90ca014b0bdb246f4d99f82abc8 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 20 Jun 2018 13:42:19 +0200 Subject: js: Demoextension update -- * src/Signature: typo * src/gpgmejs.js: fixed wrong scope in verification * right now verify does not succeed in the DemoExtension. This is probably a problem in conversion or line ending. --- lang/js/DemoExtension/maindemo.js | 41 +++++++++++++++++++++++-- lang/js/DemoExtension/mainui.html | 63 +++++++++++++++++++-------------------- lang/js/DemoExtension/ui.css | 23 ++++++++++++++ lang/js/src/Signature.js | 6 ++-- lang/js/src/gpgmejs.js | 6 ++-- 5 files changed, 99 insertions(+), 40 deletions(-) diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index 6230c3f0..d0127c73 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -27,7 +27,7 @@ document.addEventListener('DOMContentLoaded', function() { Gpgmejs.init().then(function(gpgmejs){ document.getElementById('buttonencrypt').addEventListener('click', function(){ - let data = document.getElementById('cleartext').value; + let data = document.getElementById('inputtext').value; let keyId = document.getElementById('pubkey').value; gpgmejs.encrypt(data, keyId).then( function(answer){ @@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('buttondecrypt').addEventListener('click', function(){ - let data = document.getElementById('ciphertext').value; + let data = document.getElementById('inputtext').value; gpgmejs.decrypt(data).then( function(answer){ if (answer.data){ @@ -57,11 +57,46 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('getdefaultkey').addEventListener('click', function(){ gpgmejs.Keyring.getDefaultKey().then(function(answer){ - document.getElementById('defaultkey').textContent = + document.getElementById('pubkey').value = answer.fingerprint; }, function(errormsg){ alert(errormsg.message); }); }); + + document.getElementById('signtext').addEventListener('click', + function(){ + let data = document.getElementById('inputtext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.sign(data, keyId).then( + function(answer){ + if (answer.data){ + document.getElementById( + 'answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.message); + }); + }); + + document.getElementById('verifytext').addEventListener('click', + function(){ + let data = document.getElementById('inputtext').value; + gpgmejs.verify(data).then( + function(answer){ + let vals = ''; + if (answer.all_valid === true){ + vals = 'Success! '; + } else { + vals = 'Failure! '; + } + vals = vals + (answer.count - answer.failures) + 'of ' + + answer.count + ' signature(s) were successfully ' + + 'verified.\n\n' + answer.data; + document.getElementById('answer').value = vals; + }, function(errormsg){ + alert( errormsg.message); + }); + }); }); }); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html index 91be7bbc..b6390363 100644 --- a/lang/js/DemoExtension/mainui.html +++ b/lang/js/DemoExtension/mainui.html @@ -7,38 +7,37 @@ -
      -
    • - Text: - -
    • -
    • - Public key ID: - -
    • -
    -
    -
    -
      -
    • - Encrypted armored Text: - -
    • -
    -
    -
    -

    Result data:

    - +
    -
    -
      -
    • - Default Key: -
      - -
    • +
      +
        +
      • + Input + +
      • +
      • + Fingerprint of Key to use: +
      • +
        + + +
      +
      +
      +
        +
      • + Result + +
      • +
      +
      +
    +
    +
    +
    +
    +
    - - - +
    + diff --git a/lang/js/DemoExtension/ui.css b/lang/js/DemoExtension/ui.css index 9c88698b..16dfb5ae 100644 --- a/lang/js/DemoExtension/ui.css +++ b/lang/js/DemoExtension/ui.css @@ -8,3 +8,26 @@ ul li span { width: 120px; margin-top: 6px; } + +div .left { + float: left; + align-items: stretch; + width: 40%; +} +div .center { + width: 50%; + align-content: space-between; +} + +div .center button { + align-self: stretch; +} +div .right { + float: right; + align-items: stretch; + width: 40%; +} + +div .bottom { + clear:both; +} \ No newline at end of file diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index d7d05983..a07fc4d1 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -35,7 +35,7 @@ export function createSignature(sigObject){ if ( typeof(sigObject) !=='object' || !sigObject.hasOwnProperty('summary') || - !sigObject.hasOwnProperty('fingerpprint') || + !sigObject.hasOwnProperty('fingerprint') || !sigObject.hasOwnProperty('timestamp') //TODO check if timestamp is mandatory in specification ){ @@ -67,6 +67,7 @@ export function createSignature(sigObject){ } } } + console.log('sig created'); return new GPGME_Signature(sigObject); } @@ -178,7 +179,8 @@ const expSum = { 'crl-missing': 'boolean', 'crl-too-old': 'boolean', 'bad-policy': 'boolean', - 'sys-error': 'boolean' + 'sys-error': 'boolean', + 'sigsum': 'object' }; /** diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index a0f7e968..c2a6b8b6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -168,7 +168,7 @@ export class GpgME { /** * Sign a Message - * @param {String|Object} data text/data to be decrypted. Accepts Strings + * @param {String|Object} data text/data to be signed. Accepts Strings * and Objects with a gettext methos * @param {GPGME_Key|String|Array|Array} keys The * key/keys to use for signing @@ -238,7 +238,7 @@ export class GpgME { */ verify(data, signature, base64 = false){ let msg = createMessage('verify'); - let dt = this.putData(msg, data); + let dt = putData(msg, data); if (dt instanceof Error){ return Promise.reject(dt); } @@ -317,7 +317,7 @@ function collectSignatures(sigs){ for (let i=0; i< sigs.length; i++){ let sigObj = createSignature(sigs[i]); if (sigObj instanceof Error){ - return gpgme_error('SIG_WRONG'); + return gpgme_error(sigObj); } if (sigObj.valid !== true){ summary.failures += 1; -- cgit v1.2.3 From a52ec87d406379f1a6acd8d4f34605a4bac8683b Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 3 Jul 2018 12:41:49 +0200 Subject: js: fixing Key import/export test -- * BrowserTestExtension: - The KeyImport/Export test had some errors, which have now been fixed - The secret key used for the test examples is now placed more prominently, and a clarification added that decrypt tests will not work if this key is not imported. * permittedOperations.js: typo Thanks to rrenkert@intevation.de for the fixes --- lang/js/BrowserTestExtension/index.html | 73 ++++++++++++++++++++++ .../BrowserTestExtension/tests/KeyImportExport.js | 25 ++++---- lang/js/src/permittedOperations.js | 2 +- 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html index 05d413ba..7f8d97de 100644 --- a/lang/js/BrowserTestExtension/index.html +++ b/lang/js/BrowserTestExtension/index.html @@ -23,6 +23,15 @@ The functionality tests, to be found in gpgme/lang/js/BrowserTestExtension, check the overall functionality of the standard packaged version of gpgmejs. +

    +

    + Most tests rely on a test gpg key to be available in gpg, which can be + found at the bottom of this page, or as "testkey.sec" in the + BrowserTestExtension's directory. Please import this key to your tested + gpg installation, or adapt the input defined in tests/inputvalues.js + if you want to use different values. +

    +

    +
    +

    + + + +

    diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index 6b298049..33e6bd29 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -19,6 +19,7 @@ * * Author(s): * Maximilian Krambach + * Raimund Renkert */ /* global describe, it, expect, Gpgmejs, ImportablePublicKey */ @@ -53,27 +54,27 @@ describe('Key importing', function () { 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( + expect(result.Keys[0]).to.not.be.undefined; + expect(result.Keys[0].key).to.be.an('object'); + expect(result.Keys[0].key.fingerprint).to.equal( ImportablePublicKey.fingerprint); - expect(result[0].status).to.equal('newkey'); + expect(result.Keys[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( + expect(res.Keys[0]).to.not.be.undefined; + expect(res.Keys[0].key).to.be.an('object'); + expect(res.Keys[0].key.fingerprint).to.equal( ImportablePublicKey.fingerprint); - expect(res[0].status).to.equal( + expect(res.Keys[0].status).to.equal( 'change'); expect( - res[0].changes.userId).to.be.true; + res.Keys[0].changes.userId).to.be.true; expect( - res[0].changes.subkey).to.be.false; + res.Keys[0].changes.subkey).to.be.false; expect( - res[0].changes.signature).to.be.true; - res[0].key.delete().then(function(result){ + res.Keys[0].changes.signature).to.be.true; + res.Keys[0].key.delete().then(function(result){ expect(result).to.be.true; done(); }); diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 044ae4af..312eaa52 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -272,7 +272,7 @@ export const permittedOperations = { answer: { type: [], data: { - 'result': 'Object' + 'result': 'object' } } }, -- cgit v1.2.3 From 1105fc87a3bd3e1152aff578b7b84871558418e6 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 4 Jul 2018 12:11:35 +0200 Subject: js: add Key lookup -- * src/Keyring.js: getKeys() now has the option "search", which will trigger a remote lookup (as configured in gpg) for the string given as pattern. * src/permittedOperations: make use of the new 'locate' option in keylist * DemoExtension: Add a button for lookup, to demonstrate the functionality --- lang/js/DemoExtension/maindemo.js | 17 +++++++++++++++++ lang/js/DemoExtension/mainui.html | 10 +++++++--- lang/js/src/Keyring.js | 7 ++++++- lang/js/src/permittedOperations.js | 3 +++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index d0127c73..4cae934e 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -98,5 +98,22 @@ document.addEventListener('DOMContentLoaded', function() { alert( errormsg.message); }); }); + document.getElementById('searchkey').addEventListener('click', + function(){ + let data = document.getElementById('inputtext').value; + gpgmejs.Keyring.getKeys(data, true, true).then(function(keys){ + if (keys.length === 1){ + document.getElementById( + 'pubkey').value = keys[0].fingerprint; + } else if (keys.length > 1) { + alert('The pattern was not unambigious enough for a Key. ' + + keys.length + ' Keys were found'); + } else { + alert('No keys found'); + } + }, function(errormsg){ + alert( errormsg.message); + }); + }); }); }); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html index b6390363..c773c9b9 100644 --- a/lang/js/DemoExtension/mainui.html +++ b/lang/js/DemoExtension/mainui.html @@ -17,9 +17,13 @@
  • Fingerprint of Key to use: -
  • -
    - + +   + diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 8bec1cea..0d7643f0 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -39,16 +39,21 @@ export class GPGME_Keyring { * inmediately. This allows for full synchronous use. If set to false, * these will initially only be available as Promises in getArmor() and * getHasSecret() + * @param {Boolean} search (optional) retrieve the Keys from servers with + * the method(s) defined in gnupg (e.g. WKD/HKP lookup) * @returns {Promise.>} * */ - getKeys(pattern, prepare_sync){ + getKeys(pattern, prepare_sync, search){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); + if (search === true){ + msg.setParameter('locate', true); + } msg.post().then(function(result){ let resultset = []; let promises = []; diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 312eaa52..e7f53965 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -188,6 +188,9 @@ export const permittedOperations = { 'local':{ allowed: ['boolean'] }, + 'locate': { + allowed: ['boolean'] + }, 'sigs':{ allowed: ['boolean'] }, -- cgit v1.2.3 From 1919fa41b6da4dfd4f69e776caa6e6b1883eb208 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 4 Jul 2018 13:38:54 +0200 Subject: js: Add jsdoc, update webpack-cli dependency -- * package.json: - the old webpack-cli version depended on two packages with vulnerabilities, set to minimum version 3.0.8 to fix this (nodesecurity.io/advisories/157, nodesecurity.io/advisories/612) - added License identifier * README: Updated documentation * jsdoc.conf: Added a configuration file for jsdoc * some minor documentation changes, indentations --- lang/js/README | 102 ++++++++++++++++++++++++-------------- lang/js/README_testing | 14 ------ lang/js/jsdoc.conf | 24 +++++++++ lang/js/package.json | 7 +-- lang/js/src/Connection.js | 7 +-- lang/js/src/Keyring.js | 6 +-- lang/js/webpack.conf.js | 21 ++++---- lang/js/webpack.conf_unittests.js | 20 ++++---- 8 files changed, 123 insertions(+), 78 deletions(-) delete mode 100644 lang/js/README_testing create mode 100644 lang/js/jsdoc.conf diff --git a/lang/js/README b/lang/js/README index b597adb2..86d2616a 100644 --- a/lang/js/README +++ b/lang/js/README @@ -3,50 +3,80 @@ of gnupg in browsers, with the help of nativeMessaging. Installation ------------- -gpgmejs uses webpack, and thus depends on nodejs for building. Webpack can be -installed by running -`npm install webpack webpack-cli --save-dev`. +gpgmejs uses webpack, and thus depends on nodejs for building. All dependencies +will be installed (in a local subdirectory) with the command `npm install`. To create a current version of the package, the command is `npx webpack --config webpack.conf.js`. If you want a more debuggable (i.e. not minified) build, just change the mode in webpack.conf.js. -Demo WebExtension: -As soon as a bundled webpack is in dist/ -the gpgmejs folder can just be included in the extensions tab of the browser in -questions (extension debug mode needs to be active). For chrome, selecting the -folder is sufficient, for firefox, the manifest.json needs to be selected. +Demo and Test WebExtension: +--------------------------- + +The Demo Extension shows simple examples of the usage of gpgmejs. + +The BrowsertestExtension runs more intensive tests (using the mocha and chai +frameworks). Tests from BrowserTestExtension/tests will be run against the +gpgmejs.bundle.js itself. They aim to test the outward facing functionality +and API. + +Unittests as defined in ./unittests.js will be bundled in +gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, +which mostly are not exported. + +. The file `build_extension.sh` may serve as a pointer on how to +build and assemble these two Extensions and their dependencies. It can directly +be used in most linux systems. + +The resulting folders can just be included in the extensions tab of the browser +in questions (extension debug mode needs to be active). For chrome, selecting +the folder is sufficient, for firefox, the manifest.json needs to be selected. Please note that it is just for demonstration/debug purposes! +For the Extensions to successfully communicate with gpgme-json, a manifest file +is needed. + +- `~/.config/chromium/NativeMessagingHosts/gpgmejson.json` + In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json' is needed, with the following content: -(The path to the native app gpgme-json may need adaption) - -Chromium: -~/.config/chromium/NativeMessagingHosts/gpgmejson.json - -{ - "name": "gpgmejson", - "description": "This is a test application for gpgmejs", - "path": "/usr/bin/gpgme-json", - "type": "stdio", - "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] -} -The ExtensionIdentifier can be seen on the chrome://extensions page, and -changes on each reinstallation. Note the slashes in allowed_origins. - - -Firefox: -~/.mozilla/native-messaging-hosts/gpgmejson.json -{ - "name": "gpgmejson", - "description": "This is a test application for gpgmejs", - "path": "/usr/bin/gpgme-json", - "type": "stdio", - "allowed_extensions": ["ExtensionIdentifier@temporary-addon"] -} -The ExtensionIdentifier can be seen as Extension ID on the about:addons page if -addon-debugging is active. In firefox, the temporary addon is removed once -firefox exits, and the identifier will need to be changed more often. +- For Chrome/Chromium: + ``` + { + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] + } + ``` + The usual path for Linux is similar to: + `~/.config/chromium/NativeMessagingHosts/gpgmejson.json` for + For Windows, the path to the manifest needs to be placed in + `HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\gpgmejson` + + - For firefox: + ``` + { + "name": "gpgmejson", + "description": "This is a test application for gpgmejs", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_extensions": ["ExtensionIdentifier@temporary-addon"] + } + ``` + + The ExtensionIdentifier can be seen as Extension ID on the about:addons page + if addon-debugging is active. In firefox, the temporary addon is removed once + firefox exits, and the identifier will need to be changed more often. + + The manifest for linux is usually placed at: + `~/.mozilla/native-messaging-hosts/gpgmejson.json` + +Documentation +------------- + +The documentation can be built by jsdoc. It currently uses the command +`./node_modules/.bin/jsdoc -c jsdoc.conf`. diff --git a/lang/js/README_testing b/lang/js/README_testing deleted file mode 100644 index b61ca1a6..00000000 --- a/lang/js/README_testing +++ /dev/null @@ -1,14 +0,0 @@ -Test extension: - -The test extension contains tests with mocha and chai. It will be packed as an -extra extension (see build_extension.sh). - -Tests from BrowserTestExtension/tests will be run against the gpgmejs.bundle.js -itself. They aim to test the outward facing functionality and API. - -Unittests as defined in ./unittests.js will be bundled in -gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, -which mostly are not exported. - -The BrowserExtension can be installed the same way as the DemoExtension -(see README). \ No newline at end of file diff --git a/lang/js/jsdoc.conf b/lang/js/jsdoc.conf new file mode 100644 index 00000000..12ae35e9 --- /dev/null +++ b/lang/js/jsdoc.conf @@ -0,0 +1,24 @@ +{ + "tags": { + "allowUnknownTags": false, + "dictionaries": ["jsdoc"] + }, + "source": { + "include": ["./src"], + "includePattern": ".+\\.js(doc|x)?$", + "excludePattern": "(^|\\/|\\\\)_" + }, + "opts":{ + "destination": "./doc/", + "recurse": true + }, + "sourceType": "module", + "plugins": [], + "templates": { + "cleverLinks": false, + "monospaceLinks": false, + "default": { + "outputSourceFiles": true + } + } +} \ No newline at end of file diff --git a/lang/js/package.json b/lang/js/package.json index acf89117..54af2982 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -6,11 +6,12 @@ "private": true, "keywords": [], "author": "", - "license": "", + "license": "LGPL-2.1+", "devDependencies": { "webpack": "^4.5.0", - "webpack-cli": "^2.0.14", + "webpack-cli": "^3.0.8", "chai": "^4.1.2", - "mocha": "^5.1.1" + "mocha": "^5.1.1", + "jsdoc": "^3.5.5" } } diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index f399b22b..945932e9 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -40,9 +40,10 @@ export class Connection{ * 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. + * @returns {Promise} result + * @returns {String} result.gpgme Version number of gpgme + * @returns {Array} result.info Further information about the + * backends. * Example: * "protocol": "OpenPGP", * "fname": "/usr/bin/gpg", diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 0d7643f0..358757b0 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -110,10 +110,10 @@ export class GPGME_Keyring { * contains a secret key. * @returns {Promise} * - * @async + * * TODO: getHasSecret always returns false at this moment, so this fucntion * still does not fully work as intended. - * + * * @async */ getDefaultKey() { let me = this; @@ -283,7 +283,7 @@ export class GPGME_Keyring { * @param {Date} expires (optional) Expiration date. If not set, expiration * will be set to 'never' * - * @returns{Promise} + * @return {Promise} */ generateKey(userId, algo = 'default', expires){ if ( diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js index b2ad9098..19f3bbda 100644 --- a/lang/js/webpack.conf.js +++ b/lang/js/webpack.conf.js @@ -19,17 +19,18 @@ * * This is the configuration file for building the gpgmejs-Library with webpack */ +/* global require, module, __dirname */ const path = require('path'); module.exports = { - entry: './src/index.js', - // mode: 'development', - mode: 'production', - output: { - path: path.resolve(__dirname, 'build'), - filename: 'gpgmejs.bundle.js', - libraryTarget: 'var', - libraryExport: 'default', - library: 'Gpgmejs' - } + entry: './src/index.js', + // mode: 'development', + mode: 'production', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'gpgmejs.bundle.js', + libraryTarget: 'var', + libraryExport: 'default', + library: 'Gpgmejs' + } }; diff --git a/lang/js/webpack.conf_unittests.js b/lang/js/webpack.conf_unittests.js index 4b903be6..c3c87f39 100644 --- a/lang/js/webpack.conf_unittests.js +++ b/lang/js/webpack.conf_unittests.js @@ -19,16 +19,18 @@ * * This is the configuration file for building the gpgmejs-Library with webpack */ +/* global require, module, __dirname */ + const path = require('path'); module.exports = { - entry: './unittests.js', - mode: 'production', - output: { - path: path.resolve(__dirname, 'build'), - filename: 'gpgmejs_unittests.bundle.js', - libraryTarget: 'var', - libraryExport: 'default', - library: 'Gpgmejs_test' - } + entry: './unittests.js', + mode: 'production', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'gpgmejs_unittests.bundle.js', + libraryTarget: 'var', + libraryExport: 'default', + library: 'Gpgmejs_test' + } }; -- cgit v1.2.3 From 10f2106404f01e7bb369fc66a597875fb455cd27 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 4 Jul 2018 15:46:45 +0200 Subject: js: properly reject pgp message without signature -- * A verify at gpgme-json does not fail if there is a valid pgp message that does not include a signature. Instead, the answer will be devoid of signatures. In javascript, the SIG_NO_SIG error should be reported here, but wasn't. --- lang/js/src/gpgmejs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index c2a6b8b6..f49361dc 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -254,7 +254,7 @@ export class GpgME { } return new Promise(function(resolve, reject){ msg.post().then(function (message){ - if (!message.info.signatures){ + if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { let _result = collectSignatures(message.info.signatures); -- cgit v1.2.3 From 67b6fa5a2948deed6a914c638f923fb9ad2eac66 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 9 Jul 2018 11:24:46 +0200 Subject: js: reduce request spam at getKeys() -- * Don't make a secret-Key request for each Key retrieved, use one request for all of them instead, and assemble the info later. This should reduce the traffic with large Keyrings. The bulk retrieval for the public armored Keys for each of these Keys is still up to discussion * unittests: disabled assertion for the armored key (as it currently doesn't work) * encryptTest: clarified the mechanism/reason of rejection for Messages >64 MB. This is still a TODO, as this error comes from a different place (the browser itself) and behaves different from the other errors. --- lang/js/BrowserTestExtension/tests/encryptTest.js | 5 ++- lang/js/src/Keyring.js | 52 +++++++++++++++-------- lang/js/unittests.js | 14 +++--- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 2cb4e58b..1114125e 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -133,7 +133,7 @@ describe('Encryption', function () { }); }).timeout(5000); - it('Overly large message ( > 65MB) is rejected', function (done) { + it('Overly large message ( > 64MB) is rejected', function (done) { let prm = Gpgmejs.init(); prm.then(function (context) { context.encrypt( @@ -142,10 +142,11 @@ describe('Encryption', function () { expect(answer).to.be.undefined; }, function(error){ expect(error).to.be.an.instanceof(Error); - // expect(error.code).to.equal('GNUPG_ERROR'); // TODO: there is a 64 MB hard limit at least in chrome at: // chromium//extensions/renderer/messaging_util.cc: // kMaxMessageLength + // The error will be a browser error, not from gnupg or from + // this library done(); }); }); diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 358757b0..09c43f73 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -56,28 +56,46 @@ export class GPGME_Keyring { } msg.post().then(function(result){ let resultset = []; - let promises = []; 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); - } - if (promises.length > 0) { - Promise.all(promises).then(function() { - resolve(resultset); - }, function(error){ - reject(error); - }); + let secondrequest; + if (prepare_sync === true) { + secondrequest = function() { + msg.setParameter('secret', true); + return msg.post(); + }; } else { - resolve(resultset); + secondrequest = function() { + return Promise.resolve(true); + }; } + secondrequest().then(function(answer) { + for (let i=0; i < result.keys.length; i++){ + if (prepare_sync === true){ + result.keys[i].hasSecret = false; + if (answer && answer.keys) { + for (let j=0; j < answer.keys.length; j++ ){ + if (result.keys[i].fingerprint === + answer.keys[j].fingerprint + ) { + if (answer.keys[j].secret === true){ + result.keys[i].hasSecret = true; + } + break; + } + } + // TODO getArmor() to be used in sync + } + } + let k = createKey(result.keys[i].fingerprint); + k.setKeyData(result.keys[i]); + resultset.push(k); + } + resolve(resultset); + }, function(error){ + reject(error); + }); } }); }); diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 169e8ebc..04e15ef3 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -272,9 +272,9 @@ function unittests (){ 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('armored')).to.be.a('string'); - expect(result[0].get('armored')).to.include( - '-----END PGP PUBLIC KEY BLOCK-----'); + expect(result[0].get('hasSecret')).to.be.a('boolean'); + // expect(result[0].get('armored')).to.include( + // '-----END PGP PUBLIC KEY BLOCK-----'); done(); }); } @@ -287,9 +287,11 @@ function unittests (){ 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-----'); + expect(result[0].get('hasSecret')).to.be.a('boolean'); + // TODO: preparing sync for armored is still in discussion + // expect(result[0].get('armored')).to.be.a('string'); + // expect(result[0].get('armored')).to.include( + // '-----END PGP PUBLIC KEY BLOCK-----'); done(); } ); -- cgit v1.2.3 From 8964627f6ad7c407785a9fa5cb508c7c28be0d60 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 9 Jul 2018 11:57:01 +0200 Subject: js: fix verify result reporting -- * src/Signature.js: searching for overall validity in the "summary" property * BrowsertestExtension: Added two verify tests --- lang/js/BrowserTestExtension/browsertest.html | 1 + lang/js/BrowserTestExtension/tests/verifyTest.js | 86 ++++++++++++++++++++++++ lang/js/src/Signature.js | 3 +- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/verifyTest.js diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index f3d7a406..de8cd41a 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -18,6 +18,7 @@ + diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js new file mode 100644 index 00000000..bf0f0c0f --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -0,0 +1,86 @@ +/* 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+ + * + * Author(s): + * Maximilian Krambach + */ + +/* global describe, it, expect, bigString, inputvalues, Gpgmejs */ + +let verifyData = { + signedMessage: '-----BEGIN PGP SIGNED MESSAGE-----\n' + + 'Hash: SHA256\n' + + '\n' + + 'Matschige Münsteraner Marshmallows\n' + + '-----BEGIN PGP SIGNATURE-----\n' + + '\n' + + 'iQEzBAEBCAAdFiEE34YHmHCyv9oBiN3shwTx6WpaVdQFAlsqWxYACgkQhwTx6Wpa\n' + + 'VdRaTQf9Fj8agQzbE6DtonewZVGzj1KmjjpyAypnDldY21lrN8zIaQ+aKqRVkVrV\n' + + '5A/MeUfoHh0b/9G1Co4LOuNjGS14GRNlFvPtxeA2mCwlk7kgP2i6ekbHdEXWcG9c\n' + + 'gSbzdJ3EgfVCFNkC/yhldXSLOJZ7oyiGEteDpi8dDSa9dIprT++sQ4kRuR8jPrIi\n' + + 'UUY+DltG3it7PybcTFfQm53I0mtnpFsizzCmgyJAkfG5fwVL3uWwbYGofD049PSu\n' + + '6IEkSY74r8JbAbkCOiF/ln40RYGSwM0Ta5rrb3A3MixZNL/a1r17oljkaWz8e8VT\n' + + 'N7NUgBHwbIQ4e3RLuUU8fF3ICCGDOw==\n' + + '=oGai\n' + + '-----END PGP SIGNATURE-----\n' +}; + +describe('Verify data', function () { + it('Successful verify message', function (done) { + let message = verifyData.signedMessage; + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.verify(message).then(function(result){ + expect(result.data).to.be.a('string'); + expect(result.all_valid).to.be.true; + expect(result.count).to.equal(1); + expect(result.signatures.good).to.be.an('array'); + expect(result.signatures.good.length).to.equal(1); + expect(result.signatures.good[0].fingerprint) + .to.be.a('string'); + expect(result.signatures.good[0].valid).to.be.true; + done(); + }); + }); + }); + + it('Encrypt-Sign-Verify random message', function (done) { + let message = bigString(2000); + let fpr = inputvalues.encrypt.good.fingerprint; + let prm = Gpgmejs.init(); + prm.then(function (context) { + context.encrypt(message, fpr).then(function(message_enc){ + context.sign(message_enc.data, fpr).then(function(message_encsign){ + context.verify(message_encsign.data).then(function(result){ + expect(result.data).to.equal(message_enc.data); + expect(result.data).to.be.a('string'); + expect(result.all_valid).to.be.true; + expect(result.count).to.equal(1); + expect(result.signatures.good).to.be.an('array'); + expect(result.signatures.good.length).to.equal(1); + expect(result.signatures.good[0].fingerprint) + .to.equal(fpr); + expect(result.signatures.good[0].valid).to.be.true; + done(); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index a07fc4d1..c3c511a8 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -67,7 +67,6 @@ export function createSignature(sigObject){ } } } - console.log('sig created'); return new GPGME_Signature(sigObject); } @@ -114,7 +113,7 @@ class GPGME_Signature { * additional information */ get valid() { - if (this._rawSigObject.valid === true){ + if (this._rawSigObject.summary.valid === true){ return true; } else { return false; -- cgit v1.2.3 From 4015f5b4983c8a4590aa71776880d8bc42c7918d Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 10 Jul 2018 14:32:26 +0200 Subject: js: documentation -- * Fixed errors: - src/Message.js post(): Set chunksize to defined default value instead of hardcoded - src/Keys.js: added getHasSecret() to refreshKey operation. * Reviewed and updated the documentation * non-documentation changes which do not affect functionality: - src/Errors: disabled a console.warn that is only useful for debugging - helpers.js: renamed "string" to "value" in isFingerprint and isLongId to avoid confusion - src/Keyring: prepare_sync, search are both explicitly set to false by default --- lang/js/README | 29 +++++--- lang/js/src/Connection.js | 58 +++++++++++---- lang/js/src/Errors.js | 18 ++++- lang/js/src/Helpers.js | 34 ++++++--- lang/js/src/Key.js | 145 +++++++++++++++++++++++++++--------- lang/js/src/Keyring.js | 132 ++++++++++++++++++++------------- lang/js/src/Message.js | 52 ++++++++----- lang/js/src/Signature.js | 29 ++++---- lang/js/src/gpgmejs.js | 146 +++++++++++++++++++++++-------------- lang/js/src/index.js | 4 +- lang/js/src/permittedOperations.js | 43 ++++++----- 11 files changed, 456 insertions(+), 234 deletions(-) diff --git a/lang/js/README b/lang/js/README index 86d2616a..b7cd3d72 100644 --- a/lang/js/README +++ b/lang/js/README @@ -1,10 +1,17 @@ -gpgmejs, as contained in this directory, is a javascript library for direct use +gpgme.js, as contained in this directory, is a javascript library for direct use of gnupg in browsers, with the help of nativeMessaging. -Installation -------------- -gpgmejs uses webpack, and thus depends on nodejs for building. All dependencies -will be installed (in a local subdirectory) with the command `npm install`. +Prerequisites: +-------------- +gpgme.js will make use of the application gpgme-json, which is distributed with +gpgme. Gpgme-json needs to be installed; it will further need to accept the +browser extension in the manifest file. + +Building gpgme.js +----------------- +gpgme.js uses webpack, and thus depends on nodejs for building. All +dependencies will be installed (in a local subdirectory) with the command +`npm install`. To create a current version of the package, the command is `npx webpack --config webpack.conf.js`. @@ -14,7 +21,7 @@ in webpack.conf.js. Demo and Test WebExtension: --------------------------- -The Demo Extension shows simple examples of the usage of gpgmejs. +The Demo Extension shows simple examples of the usage of gpgme.js. The BrowsertestExtension runs more intensive tests (using the mocha and chai frameworks). Tests from BrowserTestExtension/tests will be run against the @@ -22,11 +29,11 @@ gpgmejs.bundle.js itself. They aim to test the outward facing functionality and API. Unittests as defined in ./unittests.js will be bundled in -gpgmejs_unittests.bundle.js, and test the separate components of gpgmejs, +gpgmejs_unittests.bundle.js, and test the separate components of gpgme.js, which mostly are not exported. -. The file `build_extension.sh` may serve as a pointer on how to -build and assemble these two Extensions and their dependencies. It can directly +The file `build_extension.sh` may serve as a pointer on how to build and +assemble these two Extensions and their dependencies. It can directly be used in most linux systems. The resulting folders can just be included in the extensions tab of the browser @@ -46,7 +53,7 @@ is needed, with the following content: ``` { "name": "gpgmejson", - "description": "This is a test application for gpgmejs", + "description": "This is a test application for gpgme.js", "path": "/usr/bin/gpgme-json", "type": "stdio", "allowed_origins": ["chrome-extension://ExtensionIdentifier/"] @@ -61,7 +68,7 @@ is needed, with the following content: ``` { "name": "gpgmejson", - "description": "This is a test application for gpgmejs", + "description": "This is a test application for gpgme.js", "path": "/usr/bin/gpgme-json", "type": "stdio", "allowed_extensions": ["ExtensionIdentifier@temporary-addon"] diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 945932e9..ef54dd64 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -28,7 +28,12 @@ import { gpgme_error } from './Errors'; import { GPGME_Message, createMessage } from './Message'; /** - * A Connection handles the nativeMessaging interaction. + * A Connection handles the nativeMessaging interaction via a port. As the + * protocol only allows up to 1MB of message sent from the nativeApp to the + * browser, the connection will stay open until all parts of a communication + * are finished. For a new request, a new port will open, to avoid mixing + * contexts. + * @class */ export class Connection{ @@ -37,19 +42,26 @@ export class Connection{ } /** - * Retrieves the information about the backend. - * @param {Boolean} details (optional) If set to false, the promise will - * just return a connection status - * @returns {Promise} result - * @returns {String} result.gpgme Version number of gpgme - * @returns {Array} result.info Further information about the - * backends. - * Example: + * @typedef {Object} backEndDetails + * @property {String} gpgme Version number of gpgme + * @property {Array} info Further information about the backend + * and the used applications (Example: + * { * "protocol": "OpenPGP", * "fname": "/usr/bin/gpg", * "version": "2.2.6", * "req_version": "1.4.0", * "homedir": "default" + * } + */ + + /** + * Retrieves the information about the backend. + * @param {Boolean} details (optional) If set to false, the promise will + * just return if a connection was successful. + * @returns {Promise|Promise} Details from the + * backend + * @async */ checkConnection(details = true){ if (details === true) { @@ -74,7 +86,7 @@ export class Connection{ } /** - * Immediately closes the open port. + * Immediately closes an open port. */ disconnect() { if (this._connection){ @@ -93,10 +105,13 @@ export class Connection{ } /** - * Sends a message and resolves with the answer. + * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It resolves + * with the completed answer after all parts have been received and + * reassembled, or rejects with an {@link GPGME_Error}. + * * @param {GPGME_Message} message - * @returns {Promise} the gnupg answer, or rejection with error - * information. + * @returns {Promise} The collected answer + * @async */ post(message){ if (!message || !(message instanceof GPGME_Message)){ @@ -170,16 +185,25 @@ export class Connection{ /** * 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 + * @protected */ class Answer{ + /** + * @param {GPGME_Message} message + */ constructor(message){ this.operation = message.operation; this.expect = message.expect; } + /** + * Adds incoming base64 encoded data to the existing response + * @param {*} msg base64 encoded data. + * @returns {Boolean} + * + * @private + */ collect(msg){ if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); @@ -195,6 +219,10 @@ class Answer{ } } + /** + * Returns the base64 encoded answer data with the content verified against + * {@link permittedOperations}. + */ get message(){ if (this._responseb64 === undefined){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index a8cd8b56..cb5c94c2 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -21,6 +21,9 @@ * Maximilian Krambach */ +/** + * Listing of all possible error codes and messages of a {@link GPGME_Error}. + */ const err_list = { // Connection 'CONN_NO_CONNECT': { @@ -107,10 +110,11 @@ const err_list = { }; /** - * Checks the given error code and returns an error object with some - * information about meaning and origin + * Checks the given error code and returns an {@link GPGME_Error} 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' + * @returns {GPGME_Error} */ export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ @@ -119,7 +123,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ } if (err_list[code].type === 'warning'){ // eslint-disable-next-line no-console - console.warn(code + ': ' + err_list[code].msg); + // console.warn(code + ': ' + err_list[code].msg); } return null; } else if (code === 'GNUPG_ERROR'){ @@ -130,6 +134,14 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ } } +/** + * An error class with additional info about the origin of the error, as string + * @property {String} code Short description of origin and type of the error + * @property {String} msg Additional info + * @class + * @protected + * @extends Error + */ class GPGME_Error extends Error{ constructor(code, msg=''){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index b01fbc30..0fd14994 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -26,11 +26,11 @@ import { GPGME_Key } from './Key'; /** * Tries to return an array of fingerprints, either from input fingerprints or - * from Key objects (openpgp Keys or GPGME_Keys are both expected) - * @param {Object |Array| String|Array} input - * @returns {Array} Array of fingerprints. + * from Key objects (openpgp Keys or GPGME_Keys are both accepted). + * + * @param {Object | Array | String | Array} input + * @returns {Array} Array of fingerprints, or an empty array */ - export function toKeyIdArray(input){ if (!input){ return []; @@ -44,6 +44,8 @@ export function toKeyIdArray(input){ if (isFingerprint(input[i]) === true){ result.push(input[i]); } else { + // MSG_NOT_A_FPR is just a console warning if warning enabled + // in src/Errors.js gpgme_error('MSG_NOT_A_FPR'); } } else if (typeof(input[i]) === 'object'){ @@ -71,9 +73,11 @@ export function toKeyIdArray(input){ } /** - * check if values are valid hexadecimal values of a specified length - * @param {*} key input value. + * Check if values are valid hexadecimal values of a specified length + * @param {String} key input value. * @param {int} len the expected length of the value + * @returns {Boolean} true if value passes test + * @private */ function hextest(key, len){ if (!key || typeof(key) !== 'string'){ @@ -87,15 +91,21 @@ function hextest(key, len){ } /** - * check if the input is a valid Hex string with a length of 40 + * check if the input is a valid Fingerprint + * (Hex string with a length of 40 characters) + * @param {String} value to check + * @returns {Boolean} true if value passes test */ -export function isFingerprint(string){ - return hextest(string, 40); +export function isFingerprint(value){ + return hextest(value, 40); } /** - * check if the input is a valid Hex string with a length of 16 + * check if the input is a valid gnupg long ID (Hex string with a length of 16 + * characters) + * @param {String} value to check + * @returns {Boolean} true if value passes test */ -export function isLongId(string){ - return hextest(string, 16); +export function isLongId(value){ + return hextest(value, 16); } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 88c2b92f..30f507c1 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -26,8 +26,9 @@ import { gpgme_error } from './Errors'; import { createMessage } from './Message'; /** - * Validates the fingerprint. + * Validates the given fingerprint and creates a new {@link GPGME_Key} * @param {String} fingerprint + * @returns {GPGME_Key|GPGME_Error} */ export function createKey(fingerprint){ if (!isFingerprint(fingerprint)){ @@ -37,12 +38,13 @@ export function createKey(fingerprint){ } /** - * Representing the Keys as stored in GPG + * Represents the Keys as stored in the gnupg backend * It allows to query almost all information defined in gpgme Key Objects - * Refer to validKeyProperties for available information, and the gpgme + * Refer to {@link validKeyProperties} for available information, and the gpgme * documentation on their meaning * (https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html) * + * @class */ export class GPGME_Key { @@ -62,6 +64,9 @@ export class GPGME_Key { } } + /** + * @returns {String} The fingerprint defining this Key + */ get fingerprint(){ if (!this._data || !this._data.fingerprint){ return gpgme_error('KEY_INVALID'); @@ -70,11 +75,11 @@ export class GPGME_Key { } /** - * - * @param {Object} data Bulk set data for this key, with the Object as sent + * @param {Object} data Bulk set the data for this key, with an Object sent * by gpgme-json. - * @returns {GPGME_Key|GPGME_Error} The Key object itself after values have - * been set + * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, an + * error if something went wrong + * @private */ setKeyData(data){ if (this._data === undefined) { @@ -126,12 +131,17 @@ export class GPGME_Key { } /** - * Query any property of the Key list - * @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 + * Query any property of the Key listed in {@link validKeyProperties} + * @param {String} property property to be retreived + * @param {Boolean} cached (optional) if false, the data will be directly + * queried from gnupg, and the operation will be asynchronous. Else, the + * data will be fetched from the state of the initialization of the Key. + * The cached mode may contain outdated information, but can be used as + * synchronous operation, where the backend is not expected to change Keys + * during a session. The key still can be reloaded by invoking + * {@link refreshKey}. + * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). + * If 'cached' is true, the value will be resolved as a Promise. */ get(property, cached=true) { if (cached === false) { @@ -164,7 +174,12 @@ export class GPGME_Key { } /** - * Reloads the Key from gnupg + * Reloads the Key information from gnupg. This is only useful if you use + * the GPGME_Keys cached. Note that this is a performance hungry operation. + * If you desire more than a few refreshs, it may be advisable to run + * {@link Keyring.getKeys} instead. + * @returns {Promise} + * @async */ refreshKey() { let me = this; @@ -178,7 +193,12 @@ export class GPGME_Key { msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeyData(result.keys[0]); - resolve(me); + me.getHasSecret().then(function(){ + //TODO retrieve armored Key + resolve(me); + }, function(error){ + reject(error); + }); } else { reject(gpgme_error('KEY_NOKEY')); } @@ -189,9 +209,9 @@ export class GPGME_Key { } /** - * Query the armored block of the non- secret parts of the Key directly - * from gpg. - * @returns {Promise} + * Query the armored block of the Key directly from gnupg. Please note that + * this will not get you any export of the secret/private parts of a Key + * @returns {Promise} * @async */ getArmor(){ @@ -213,9 +233,12 @@ export class GPGME_Key { } /** - * Find out if the Key includes a secret part - * @returns {Promise} - * + * Find out if the Key includes a secret part. Note that this is a rather + * nonperformant operation, as it needs to query gnupg twice. If you want + * this inforrmation about more than a few Keys, it may be advisable to run + * {@link Keyring.getKeys} instead. + * @returns {Promise} True if a secret/private Key is + * available in the gnupg Keyring * @async */ getHasSecret(){ @@ -253,25 +276,30 @@ export class GPGME_Key { */ /** - * @returns {String} The armored public Key block + * Property for the export of armored Key. If the armored Key is not + * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. + * Running {@link refreshKey} may help in this case. + * @returns {String|GPGME_Error} 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. + * Property indicating if the Key possesses a private/secret part. If this + * information is not yet cached, it returns an {@link GPGME_Error} with + * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. + * @returns {Boolean} If the Key has a secret subkey. */ get hasSecret(){ return this.get('hasSecret', true); } /** - * Deletes the public Key from the GPG Keyring. Note that a deletion of a + * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a * secret key is not supported by the native backend. - * @returns {Promise} Success if key was deleted, rejects with a - * GPG error otherwise + * @returns {Promise} Success if key was deleted, + * rejects with a GPG error otherwise. */ delete(){ let me = this; @@ -291,10 +319,17 @@ export class GPGME_Key { } /** - * The subkeys of a Key. Currently, they cannot be refreshed separately + * Representing a subkey of a Key. + * @class + * @protected */ class GPGME_Subkey { + /** + * Initializes with the json data sent by gpgme-json + * @param {Object} data + * @private + */ constructor(data){ let keys = Object.keys(data); for (let i=0; i< keys.length; i++) { @@ -302,6 +337,13 @@ class GPGME_Subkey { } } + /** + * Validates a subkey property against {@link validSubKeyProperties} and + * sets it if validation is successful + * @param {String} property + * @param {*} value + * @param private + */ setProperty(property, value){ if (!this._data){ this._data = {}; @@ -318,10 +360,9 @@ class GPGME_Subkey { } /** - * + * Fetches any information about this subkey * @param {String} property Information to request - * @returns {String | Number} - * TODO: date properties are numbers with Date in seconds + * @returns {String | Number | Date} */ get(property) { if (this._data.hasOwnProperty(property)){ @@ -330,15 +371,31 @@ class GPGME_Subkey { } } +/** + * Representing user attributes associated with a Key or subkey + * @class + * @protected + */ class GPGME_UserId { + /** + * Initializes with the json data sent by gpgme-json + * @param {Object} data + * @private + */ constructor(data){ let keys = Object.keys(data); for (let i=0; i< keys.length; i++) { this.setProperty(keys[i], data[keys[i]]); } } - + /** + * Validates a subkey property against {@link validUserIdProperties} and + * sets it if validation is successful + * @param {String} property + * @param {*} value + * @param private + */ setProperty(property, value){ if (!this._data){ this._data = {}; @@ -356,10 +413,9 @@ class GPGME_UserId { } /** - * + * Fetches information about the user * @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)){ @@ -368,6 +424,13 @@ class GPGME_UserId { } } +/** + * Validation definition for userIds. Each valid userId property is represented + * as a key- Value pair, with their value being a validation function to check + * against + * @protected + * @const + */ const validUserIdProperties = { 'revoked': function(value){ return typeof(value) === 'boolean'; @@ -419,6 +482,12 @@ const validUserIdProperties = { } }; +/** + * Validation definition for subKeys. Each valid userId property is represented + * as a key-value pair, with the value being a validation function + * @protected + * @const + */ const validSubKeyProperties = { 'invalid': function(value){ return typeof(value) === 'boolean'; @@ -471,8 +540,14 @@ const validSubKeyProperties = { return (Number.isInteger(value) && value > 0); } }; + +/** + * Validation definition for Keys. Each valid Key property is represented + * as a key-value pair, with their value being a validation function + * @protected + * @const + */ const validKeyProperties = { - //TODO better validation? 'fingerprint': function(value){ return isFingerprint(value); }, diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 09c43f73..a0bdfcb2 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -27,24 +27,30 @@ import {createKey} from './Key'; import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; +/** + * This class offers access to the gnupg keyring + */ 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() - * @param {Boolean} search (optional) retrieve the Keys from servers with - * the method(s) defined in gnupg (e.g. WKD/HKP lookup) - * @returns {Promise.>} + * Queries Keys (all Keys or a subset) from gnupg. * + * @param {String | Array} pattern (optional) A pattern to search + * for in userIds or KeyIds. + * @param {Boolean} prepare_sync (optional) if set to true, the 'hasSecret' + * and 'armored' properties will be fetched for the Keys as well. These + * require additional calls to gnupg, resulting in a performance hungry + * operation. Calling them here enables direct, synchronous use of these + * properties for all keys, without having to resort to a refresh() first. + * @param {Boolean} search (optional) retrieve Keys from external servers + * with the method(s) defined in gnupg (e.g. WKD/HKP lookup) + * @returns {Promise.|GPGME_Error>} + * @static + * @async */ - getKeys(pattern, prepare_sync, search){ + getKeys(pattern, prepare_sync=false, search=false){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined){ @@ -102,10 +108,15 @@ export class GPGME_Keyring { } /** - * 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 + * Fetches the armored public Key blocks for all Keys matching the pattern + * (if no pattern is given, fetches all keys known to gnupg). Note that the + * result may be one big armored block, instead of several smaller armored + * blocks + * @param {String|Array} pattern (optional) The Pattern to search + * for + * @returns {Promise} Armored Key blocks + * @static + * @async */ getKeysArmored(pattern) { return new Promise(function(resolve, reject) { @@ -123,15 +134,14 @@ export class GPGME_Keyring { } /** - * Returns the Key to be used by default for signing operations, - * looking up the gpg configuration, or returning the first key that - * contains a secret key. - * @returns {Promise} - * + * Returns the Key used by default in gnupg. + * (a.k.a. 'primary Key or 'main key'). + * It looks up the gpg configuration if set, or the first key that contains + * a secret key. * - * TODO: getHasSecret always returns false at this moment, so this fucntion - * still does not fully work as intended. - * * @async + * @returns {Promise} + * @async + * @static */ getDefaultKey() { let me = this; @@ -177,30 +187,40 @@ export class GPGME_Keyring { } /** + * @typedef {Object} importResult The result of a Key update + * @property {Object} summary Numerical summary of the result. See the + * feedbackValues variable for available Keys values and the gnupg + * documentation. + * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html + * for details on their meaning. + * @property {Array} Keys Array of Object containing + * GPGME_Keys with additional import information * + */ + + /** + * @typedef {Object} importedKeyResult + * @property {GPGME_Key} key The resulting key + * @property {String} status: + * '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. + * @property {Boolean} changes.userId Changes in userIds + * @property {Boolean} changes.signature Changes in signatures + * @property {Boolean} changes.subkey Changes in subkeys + */ + + /** + * Import an armored Key block into gnupg. Note that this currently will + * not succeed on private Key blocks. * @param {String} armored Armored Key block of the Key(s) to be imported * into gnupg * @param {Boolean} prepare_sync prepare the keys for synched use - * (see getKeys()). - * - * @returns {Promise} result: A summary and an array of Keys - * considered - * - * @returns result.summary: Numerical summary of the result. See the - * feedbackValues variable for available values and the gnupg documentation - * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html - * for details on their meaning. - * @returns {Array} result.Keys: Array of objects containing: - * @returns {GPGME_Key} Key.key The resulting key - * @returns {String} Key.status: - * '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. - * @returns {Boolean} Key.changes.userId: userIds changed - * @returns {Boolean} Key.changes.signature: signatures changed - * @returns {Boolean} Key.changes.subkey: subkeys changed + * (see {@link getKeys}). + * @returns {Promise} A summary and Keys considered. + * @async + * @static */ importKey(armored, prepare_sync) { let feedbackValues = ['considered', 'no_user_id', 'imported', @@ -283,25 +303,36 @@ export class GPGME_Keyring { } + /** + * Convenience function for deleting a Key. See {@link Key.delete} for + * further information about the return values. + * @param {String} fingerprint + * @returns {Promise} + * @async + * @static + */ deleteKey(fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); - key.delete(); + return key.delete(); + } else { + return Promise.reject(gpgme_error('KEY_INVALID')); } } /** * Generates a new Key pair directly in gpg, and returns a GPGME_Key * representing that Key. Please note that due to security concerns, secret - * Keys can not be _deleted_ from inside gpgmejs. + * Keys can not be deleted or exported from inside gpgme.js. * - * @param {String} userId The user Id, e.g. "Foo Bar " - * @param {*} algo (optional) algorithm (and optionally key size to be - * used. See {@link supportedKeyAlgos } below for supported values. + * @param {String} userId The user Id, e.g. 'Foo Bar ' + * @param {String} algo (optional) algorithm (and optionally key size) to + * be used. See {@link supportedKeyAlgos} below for supported values. * @param {Date} expires (optional) Expiration date. If not set, expiration * will be set to 'never' * - * @return {Promise} + * @return {Promise} + * @async */ generateKey(userId, algo = 'default', expires){ if ( @@ -336,7 +367,8 @@ export class GPGME_Keyring { } /** - * A list of algorithms supported for key generation. + * List of algorithms supported for key generation. Please refer to the gnupg + * documentation for details */ const supportedKeyAlgos = [ 'default', diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 7ccf7efc..2e38142c 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -25,6 +25,12 @@ import { permittedOperations } from './permittedOperations'; import { gpgme_error } from './Errors'; import { Connection } from './Connection'; +/** + * Initializes a message for gnupg, validating the message's purpose with + * {@link permittedOperations} first + * @param {String} operation + * @returns {GPGME_Message|GPGME_Error} The Message object + */ export function createMessage(operation){ if (typeof(operation) !== 'string'){ return gpgme_error('PARAM_WRONG'); @@ -37,12 +43,13 @@ export function createMessage(operation){ } /** - * Prepares a communication request. It checks operations and parameters in - * ./permittedOperations. - * @param {String} operation + * A Message collects, validates and handles all information required to + * successfully establish a meaningful communication with gpgme-json via + * {@link Connection.post}. The definition on which communication is available + * can be found in {@link permittedOperations}. + * @class */ export class GPGME_Message { - //TODO getter constructor(operation){ this.operation = operation; @@ -63,9 +70,13 @@ export class GPGME_Message { } /** - * Set the maximum size of responses from gpgme in bytes. Values allowed - * range from 10kB to 1MB. The lower limit is arbitrary, the upper limit - * fixed by browsers' nativeMessaging specifications + * The maximum size of responses from gpgme in bytes. As of July 2018, + * most browsers will only accept answers up to 1 MB of size. Everything + * above that threshold will not pass through nativeMessaging; answers that + * are larger need to be sent in parts. The lower limit is set to 10 KB. + * Messages smaller than the threshold will not encounter problems, larger + * messages will be received in chunks. + * If the value is not explicitly specified, 1023 KB is used. */ set chunksize(value){ if ( @@ -85,8 +96,9 @@ export class GPGME_Message { } /** - * If expect is set to 'base64', the response is expected to be base64 - * encoded binary + * Expect indicates which format data is expected to be in. It currently + * only supports 'base64', indicating that the response data from gnupg are + * expected to be base64 encoded binary */ set expect(value){ if (value ==='base64'){ @@ -103,13 +115,13 @@ export class GPGME_Message { /** - * Sets a parameter for the message. Note that the operation has to be set - * first, to be able to check if the parameter is permittted + * Sets a parameter for the message. It validates with + * {@link permittedOperations} * @param {String} param Parameter to set - * @param {any} value Value to set //TODO: Some type checking + * @param {any} value Value to set * @returns {Boolean} If the parameter was set successfully */ - setParameter(param,value){ + setParameter( param,value ){ if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } @@ -125,6 +137,7 @@ export class GPGME_Message { } else { return gpgme_error('PARAM_WRONG'); } + // check incoming value for correctness let checktype = function(val){ switch(typeof(val)){ case 'string': @@ -187,9 +200,9 @@ export class GPGME_Message { } /** - * Check if the message has the minimum requirements to be sent, according - * to the definitions in permittedOperations - * @returns {Boolean} + * Check if the message has the minimum requirements to be sent, that is + * all 'required' parameters according to {@link permittedOperations}. + * @returns {Boolean} true if message is complete. */ get isComplete(){ if (!this._msg.op){ @@ -222,13 +235,18 @@ export class GPGME_Message { } + /** + * Sends the Message via nativeMessaging and resolves with the answer. + * @returns {Promise} + * @async + */ post(){ let me = this; return new Promise(function(resolve, reject) { if (me.isComplete === true) { let conn = new Connection; if (me._msg.chunksize === undefined){ - me._msg.chunksize = 1023*1024; + me._msg.chunksize = me.chunksize; } conn.post(me).then(function(response) { resolve(response); diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index c3c511a8..191e00ab 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -20,17 +20,16 @@ * Author(s): * Maximilian Krambach */ +import { gpgme_error } from './Errors'; /** - * Validates a signature object and returns + * Validates an object containing a signature, as sent by the nativeMessaging + * interface * @param {Object} sigObject Object as returned by gpgme-json. The definition - * of the expected values are to be found in the constants 'expKeys', 'expSum', - * 'expNote' in this file. - * @returns {GPGME_Signature} Signature Object + * of the expected values are to be found in {@link expKeys}, {@link expSum}, + * {@link expNote}. + * @returns {GPGME_Signature|GPGME_Error} Signature Object */ - -import { gpgme_error } from './Errors'; - export function createSignature(sigObject){ if ( typeof(sigObject) !=='object' || @@ -72,18 +71,20 @@ export function createSignature(sigObject){ /** - * Representing the details of a signature. It is supposed to be read-only. The - * full details as given by gpgme-json can be accessed from the _rawSigObject. - * ) + * Representing the details of a signature. The full details as given by + * gpgme-json can be read from the _rawSigObject. + * + * Note to reviewers: This class should be read only except via + * {@link createSignature} + * @protected + * @class */ class GPGME_Signature { + constructor(sigObject){ this._rawSigObject = sigObject; } - /** - * The signatures' fingerprint - */ get fingerprint(){ return this._rawSigObject.fingerprint; } @@ -105,7 +106,7 @@ class GPGME_Signature { * @returns {Date} */ get timestamp(){ - return new Date(this._rawSigObject.timestamp* 1000); + return new Date(this._rawSigObject.timestamp * 1000); } /** diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index f49361dc..3f6dc947 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -28,10 +28,62 @@ import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; import { createSignature } from './Signature'; +/** + * @typedef {Object} decrypt_result + * @property {String} data The decrypted data + * @property {Boolean} base64 indicating whether data is base64 encoded. + * @property {Boolean} is_mime (optional) the data claims to be a MIME + * object. + * @property {String} file_name (optional) the original file name + * @property {signatureDetails} signatures Verification details for + * signatures + */ + +/** + * @typedef {Object} signatureDetails + * @property {Boolean} all_valid Summary if all signatures are fully valid + * @property {Number} count Number of signatures found + * @property {Number} failures Number of invalid signatures + * @property {Array} signatures.good All valid signatures + * @property {Array} signatures.bad All invalid signatures + */ + +/** + * @typedef {Object} encrypt_result The result of an encrypt operation + * @property {String} data The encrypted message + * @property {Boolean} base64 Indicating whether data is base64 encoded. + */ + +/** + * @typedef { GPGME_Key | String | Object } inputKeys + * Accepts different identifiers of a gnupg Key that can be parsed by + * {@link toKeyIdArray}. Expected inputs are: One or an array of + * GPGME_Keys; one or an array of fingerprint strings; one or an array of + * openpgpjs Key objects. + */ + +/** + * @typedef {Object} signResult The result of a signing operation + * @property {String} data The resulting data. Includes the signature in + * clearsign mode + * @property {String} signature The detached signature (if in detached mode) + */ + +/** @typedef {Object} verifyResult The result of a verification + * @property {Boolean} data: The verified data + * @property {Boolean} is_mime (optional) the data claims to be a MIME + * object. + * @property {String} file_name (optional) the original file name + * @property {signatureDetails} signatures Verification details for + * signatures + */ + +/** + * The main entry point for gpgme.js. + * @class + */ export class GpgME { - /** - * initializes GpgME by opening a nativeMessaging port - */ + constructor(){ } @@ -41,6 +93,9 @@ export class GpgME { } } + /** + * Accesses the {@link GPGME_Keyring}. + */ get Keyring(){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; @@ -49,23 +104,23 @@ export class GpgME { } /** - * Encrypt (and optionally sign) a Message + * Encrypt (and optionally sign) data * @param {String|Object} data text/data to be encrypted as String. Also * accepts Objects with a getText method - * @param {GPGME_Key|String|Array|Array} publicKeys + * @param {inputKeys} publicKeys * Keys used to encrypt the message - * @param {GPGME_Key|String|Array|Array} secretKeys - * (optional) Keys used to sign the message + * @param {inputKeys} secretKeys (optional) Keys used to sign the message. + * If Keys are present, the operation requested is assumed to be 'encrypt + * and sign' * @param {Boolean} base64 (optional) The data will be interpreted as - * base64 encoded data - * @param {Boolean} armor (optional) Request the output as armored block + * base64 encoded data. + * @param {Boolean} armor (optional) Request the output as armored block. * @param {Boolean} wildcard (optional) If true, recipient information will - * not be added to the message - * @param {Object} additional use additional gpg options - * (refer to src/permittedOperations) - * @returns {Promise} Encrypted message: - * data: The encrypted message - * base64: Boolean indicating whether data is base64 encoded. + * not be added to the message. + * @param {Object} additional use additional valid gpg options as defined + * in {@link permittedOperations} + * @returns {Promise} Object containing the encrypted + * message and additional info. * @async */ encrypt(data, publicKeys, secretKeys, base64=false, armor=true, @@ -105,28 +160,12 @@ export class GpgME { } /** - * Decrypt a Message + * Decrypts a Message * @param {String|Object} data text/data to be decrypted. Accepts Strings * and Objects with a getText method * @param {Boolean} base64 (optional) false if the data is an armored block, * true if it is base64 encoded binary data - * @returns {Promise} result: Decrypted Message and information - * @returns {String} result.data: The decrypted data. - * @returns {Boolean} result.base64: indicating whether data is base64 - * encoded. - * @returns {Boolean} result.is_mime: Indicating whether the data is a MIME - * object. - * @returns {String} result.file_name: The optional original file name - * @returns {Object} message.signatures Verification details for signatures: - * @returns {Boolean} message.signatures.all_valid: true if all signatures - * are valid - * @returns {Number} message.signatures.count: Number of signatures found - * @returns {Number} message.signatures.failures Number of invalid - * signatures - * @returns {Array} message.signatures.signatures. Two arrays - * (good & bad) of {@link GPGME_Signature} objects, offering further - * information. - * + * @returns {Promise} Decrypted Message and information * @async */ decrypt(data, base64=false){ @@ -169,16 +208,13 @@ export class GpgME { /** * Sign a Message * @param {String|Object} data text/data to be signed. Accepts Strings - * and Objects with a gettext methos - * @param {GPGME_Key|String|Array|Array} keys The - * key/keys to use for signing - * @param {*} mode The signing mode. Currently supported: - * 'clearsign': (default) The Message is embedded into the signature - * 'detached': The signature is stored separately - * @param {*} base64 input is considered base64 - * @returns {Promise} - * data: The resulting data. Includes the signature in clearsign mode - * signature: The detached signature (if in detached mode) + * and Objects with a getText method. + * @param {inputKeys} keys The key/keys to use for signing + * @param {String} mode The signing mode. Currently supported: + * 'clearsign':The Message is embedded into the signature; + * 'detached': The signature is stored separately + * @param {Boolean} base64 input is considered base64 + * @returns {Promise} * @async */ sign(data, keys, mode='clearsign', base64=false) { @@ -221,20 +257,12 @@ export class GpgME { /** * Verifies data. * @param {String|Object} data text/data to be verified. Accepts Strings - * and Objects with a gettext method + * and Objects with a getText method * @param {String} (optional) A detached signature. If not present, opaque * mode is assumed * @param {Boolean} (optional) Data and signature are base64 encoded - * // TODO verify if signature really is assumed to be base64 - * @returns {Promise} result: - * @returns {Boolean} result.data: The verified data - * @returns {Boolean} result.is_mime: The message claims it is MIME - * @returns {String} result.file_name: The optional filename of the message - * @returns {Boolean} result.all_valid: true if all signatures are valid - * @returns {Number} result.count: Number of signatures found - * @returns {Number} result.failures Number of unsuccessful signatures - * @returns {Array} result.signatures. Two arrays (good & bad) of - * {@link GPGME_Signature} objects, offering further information. + * @returns {Promise} + *@async */ verify(data, signature, base64 = false){ let msg = createMessage('verify'); @@ -275,7 +303,10 @@ export class GpgME { /** * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set - * @param {*} data The data to enter + * @param { String| Object } data The data to enter. Expects either a string of + * data, or an object with a getText method + * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise + * @private */ function putData(message, data){ if (!message || !(message instanceof GPGME_Message) ) { @@ -301,6 +332,11 @@ function putData(message, data){ } } +/** + * Parses, validates and converts incoming objects into signatures. + * @param {Array} sigs + * @returns {signatureDetails} Details about the signatures + */ function collectSignatures(sigs){ if (!Array.isArray(sigs)){ return gpgme_error('SIG_NO_SIGS'); diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 6db28733..dc613fc7 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -27,8 +27,8 @@ import { gpgme_error } from './Errors'; import { Connection } from './Connection'; /** - * Tests nativeMessaging once and returns a GpgME object if successful. - * @returns {GpgME | Error} + * Initializes gpgme.js by testing the nativeMessaging connection once. + * @returns {Promise | GPGME_Error} * * @async */ diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index e7f53965..0b9c891f 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -22,27 +22,30 @@ */ /** - * Definition of the possible interactions with gpgme-json. - * operation: - required: Array - name The name of the property - allowed: Array of allowed types. Currently accepted values: - ['number', 'string', 'boolean', 'Uint8Array'] - array_allowed: Boolean. If the value can be an array of the above - allowed_data: If present, restricts to the given value - optional: Array - see 'required', with these parameters not being mandatory for a - complete message - pinentry: boolean If a pinentry dialog is expected, and a timeout of - 5000 ms would be too short - answer: - type: - the properties expected and their type, eg: {'data':'string'} - } - } -*/ + * @typedef {Object} messageProperty + * A message Property is defined by it's key. + * @property {Array} allowed Array of allowed types. + * Currently accepted values are 'number', 'string', 'boolean'. + * @property {Boolean} array_allowed If the value can be an array of types + * defined in allowed + * @property {} allowed_data (optional) restricts to the given values + */ +/** + * Definition of the possible interactions with gpgme-json. + * @param {Object} operation Each operation is named by a key and contains + * the following properties: + * @property {messageProperty} required An object with all required parameters + * @property {messageProperty} optional An object with all optional parameters + * @property {Boolean} pinentry (optional) If true, a password dialog is + * expected, thus a connection tuimeout is not advisable + * @property {Object} answer The definition on what to expect as answer, if the + * answer is not an error + * @property {Array} answer.type the type(s) as reported by gpgme-json. + * @property {Object} answer.data key-value combinations of expected properties + * of an answer and their type ('boolean', 'string', object) + @const +*/ export const permittedOperations = { encrypt: { pinentry: true, //TODO only with signing_keys -- cgit v1.2.3 From 30bb5490466119b66eeac255d71fb7bdc79149fa Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 12 Jul 2018 11:36:55 +0200 Subject: js: add with-sec-fprs to getKeysArmored -- * Reflects the changes made to gpgme-json in commit 6cc842c9aa76d19448141e5117ac59452d7a1ff3. - getKeysArmored now returns an object with property 'armored' being the exported armored block, and an (optional) array of fingerprint strings for those keys that can be used in sign/encrypt operations as property 'secret_fprs'. With this, extensions such as mailvelope will be able to bulk fetch all necessary key information in one request. --- .../BrowserTestExtension/tests/KeyImportExport.js | 29 ++++++++++++++++-- lang/js/src/Keyring.js | 34 +++++++++++++++++----- lang/js/src/permittedOperations.js | 12 ++++---- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index 33e6bd29..4a53c7a6 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -22,7 +22,7 @@ * Raimund Renkert */ -/* global describe, it, expect, Gpgmejs, ImportablePublicKey */ +/* global describe, it, expect, Gpgmejs, ImportablePublicKey, inputvalues */ describe('Key importing', function () { it('Prepare test Key (deleting it from gnupg, if present)', function(done){ @@ -83,5 +83,30 @@ describe('Key importing', function () { }); }); }); - + it('exporting armored Key with getKeysArmored', function (done) { + let prm = Gpgmejs.init(); + const fpr = inputvalues.encrypt.good.fingerprint; + prm.then(function (context) { + context.Keyring.getKeysArmored(fpr).then(function(result){ + expect(result).to.be.an('object'); + expect(result.armored).to.be.a('string'); + expect(result.secret_fprs).to.be.undefined; + done(); + }); + }); + }); + it('exporting armored Key (including secret fingerprints) with ' + + 'getKeysArmored', function (done) { + let prm = Gpgmejs.init(); + const fpr = inputvalues.encrypt.good.fingerprint; + prm.then(function (context) { + context.Keyring.getKeysArmored(fpr, true).then(function(result){ + expect(result).to.be.an('object'); + expect(result.armored).to.be.a('string'); + expect(result.secret_fprs).to.be.an('array'); + expect(result.secret_fprs[0]).to.equal(fpr); + done(); + }); + }); + }); }); \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index a0bdfcb2..7a33be98 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -107,26 +107,46 @@ export class GPGME_Keyring { }); } + /** + * @typedef {Object} exportResult The result of a getKeysArmored operation. + * @property {String} armored The public Key(s) as armored block. Note that + * the result is one armored block, and not a block per key. + * @property {Array} secret_fprs (optional) list of fingerprints + * for those Keys that also have a secret Key available in gnupg. The + * secret key will not be exported, but the fingerprint can be used in + * operations needing a secret key. + */ + /** * Fetches the armored public Key blocks for all Keys matching the pattern - * (if no pattern is given, fetches all keys known to gnupg). Note that the - * result may be one big armored block, instead of several smaller armored - * blocks + * (if no pattern is given, fetches all keys known to gnupg). * @param {String|Array} pattern (optional) The Pattern to search * for - * @returns {Promise} Armored Key blocks + * @param {Boolean} with_secret_fpr (optional) also return a list of + * fingerprints for the keys that have a secret key available + * @returns {Promise} Object containing the + * armored Key(s) and additional information. * @static * @async */ - getKeysArmored(pattern) { + getKeysArmored(pattern, with_secret_fpr) { return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); + if (with_secret_fpr === true) { + msg.setParameter('with-sec-fprs', true); + } if (pattern !== undefined){ msg.setParameter('keys', pattern); } - msg.post().then(function(result){ - resolve(result.data); + msg.post().then(function(answer){ + const result = {armored: answer.data}; + if (with_secret_fpr === true + && answer.hasOwnProperty('sec-fprs') + ) { + result.secret_fprs = answer['sec-fprs']; + } + resolve(result); }, function(error){ reject(error); }); diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 0b9c891f..b5e91579 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -246,7 +246,10 @@ export const permittedOperations = { 'raw': { allowed: ['boolean'] }, - 'pkcs12':{ + 'pkcs12': { + allowed: ['boolean'] + }, + 'with-sec-fprs': { allowed: ['boolean'] } // secret: not yet implemented @@ -255,7 +258,8 @@ export const permittedOperations = { type: ['keys'], data: { 'data': 'string', - 'base64': 'boolean' + 'base64': 'boolean', + 'sec-fprs': 'object' } } }, @@ -295,10 +299,6 @@ export const permittedOperations = { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, - // 'secret': { not implemented - // allowed: ['boolean'] - // } - }, answer: { data: { -- cgit v1.2.3 From 5213a599fea0da64560f935dffbf6b27a39d4850 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 12 Jul 2018 11:48:17 +0200 Subject: js: allow optional Key retrieve pattern to be null -- * src/Keyring.js: If the optional "pattern" parameter is not to be used, but another, following parameter is, null is more of a convention in javascript, thus both null and undefined are interpreted as "this parameter is not meant to be set". --- lang/js/src/Keyring.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 7a33be98..cefc8123 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -53,7 +53,7 @@ export class GPGME_Keyring { getKeys(pattern, prepare_sync=false, search=false){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); - if (pattern !== undefined){ + if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); @@ -136,7 +136,7 @@ export class GPGME_Keyring { if (with_secret_fpr === true) { msg.setParameter('with-sec-fprs', true); } - if (pattern !== undefined){ + if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); } msg.post().then(function(answer){ -- cgit v1.2.3 From ce0379d999039131c86dc0bb83e9b8edfee1c7d4 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 17 Jul 2018 11:07:49 +0200 Subject: js: fix getkeys with locate option -- * src/Keyring.js: As locate will not work with the "secret" option, the first message cannot be reused, thus a new one must be created here --- lang/js/src/Keyring.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index cefc8123..f2a71389 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -68,8 +68,10 @@ export class GPGME_Keyring { let secondrequest; if (prepare_sync === true) { secondrequest = function() { - msg.setParameter('secret', true); - return msg.post(); + let msg2 = createMessage('keylist'); + msg2.setParameter('keys', pattern); + msg2.setParameter('secret', true); + return msg2.post(); }; } else { secondrequest = function() { -- cgit v1.2.3 From 50da3ff2fddf0d35541f798313e881fef3cff869 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 18 Jul 2018 13:43:47 +0200 Subject: js: typo in comment -- * Documentation typo that caused jsdoc to exit with an error --- lang/js/src/permittedOperations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index b5e91579..1c28ab81 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -28,7 +28,7 @@ * Currently accepted values are 'number', 'string', 'boolean'. * @property {Boolean} array_allowed If the value can be an array of types * defined in allowed - * @property {} allowed_data (optional) restricts to the given values + * @property {Array<*>} allowed_data (optional) restricts to the given values */ /** -- cgit v1.2.3 From a965e3e0b89521ad4f3898a8483161624c2e5848 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 20 Jul 2018 10:59:57 +0200 Subject: js: repair BrowserTextExtension test -- * the signed message to verify was signed by a wrong test key --- lang/js/BrowserTestExtension/tests/verifyTest.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js index bf0f0c0f..e8e2c76a 100644 --- a/lang/js/BrowserTestExtension/tests/verifyTest.js +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -30,14 +30,14 @@ let verifyData = { 'Matschige Münsteraner Marshmallows\n' + '-----BEGIN PGP SIGNATURE-----\n' + '\n' + - 'iQEzBAEBCAAdFiEE34YHmHCyv9oBiN3shwTx6WpaVdQFAlsqWxYACgkQhwTx6Wpa\n' + - 'VdRaTQf9Fj8agQzbE6DtonewZVGzj1KmjjpyAypnDldY21lrN8zIaQ+aKqRVkVrV\n' + - '5A/MeUfoHh0b/9G1Co4LOuNjGS14GRNlFvPtxeA2mCwlk7kgP2i6ekbHdEXWcG9c\n' + - 'gSbzdJ3EgfVCFNkC/yhldXSLOJZ7oyiGEteDpi8dDSa9dIprT++sQ4kRuR8jPrIi\n' + - 'UUY+DltG3it7PybcTFfQm53I0mtnpFsizzCmgyJAkfG5fwVL3uWwbYGofD049PSu\n' + - '6IEkSY74r8JbAbkCOiF/ln40RYGSwM0Ta5rrb3A3MixZNL/a1r17oljkaWz8e8VT\n' + - 'N7NUgBHwbIQ4e3RLuUU8fF3ICCGDOw==\n' + - '=oGai\n' + + 'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + + 'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + + 'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + + 'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + + 'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + + 'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + + 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + + '=ioB6\n' + '-----END PGP SIGNATURE-----\n' }; -- cgit v1.2.3 From 040b1ed40ada7c9ce095dec696406aea730e3766 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Jul 2018 14:50:54 +0200 Subject: js: Fix wrong encoding in received error messages -- * The libgpg error strings arrive in the browser in a different encoding than used by browsers. Escaping and then decoding it should cover most languages in the supported browsers. --- lang/js/src/Connection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index ef54dd64..d89fa724 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -240,7 +240,8 @@ class Answer{ switch (key) { case 'type': if (_decodedResponse.type === 'error'){ - return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); + return (gpgme_error('GNUPG_ERROR', + decodeURIComponent(escape(_decodedResponse.msg)))); } else if (poa.type.indexOf(_decodedResponse.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } -- cgit v1.2.3 From 4b343c4e339862a5faf8dd20590a3c4592fb6abb Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 24 Jul 2018 14:56:33 +0200 Subject: js: include armored Key in import callback -- * The import answer now also directly contains the armored Key as Key property, without need to refresh the Key object created in the answer. This allows for direct comparision of input and output. * BrowserTestExtension: added test for that import callback --- .../BrowserTestExtension/tests/KeyImportExport.js | 26 ++++++++++++++++++++++ lang/js/src/Key.js | 9 +++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index 4a53c7a6..ed307b32 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -83,6 +83,32 @@ describe('Key importing', function () { }); }); }); + + it('Import result feedback', function(done){ + 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).to.be.an('object'); + expect(result.Keys[0]).to.be.an('object'); + expect(result.Keys[0].key.fingerprint).to.equal( + ImportablePublicKey.fingerprint); + expect(result.Keys[0].status).to.equal('newkey'); + result.Keys[0].key.getArmor().then(function(armor){ + expect(armor).to.be.a('string'); + result.Keys[0].key.delete().then(function(){ + done(); + }); + }); + }); + }); + }); + }); + it('exporting armored Key with getKeysArmored', function (done) { let prm = Gpgmejs.init(); const fpr = inputvalues.encrypt.good.fingerprint; diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 30f507c1..b024a77b 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -141,7 +141,7 @@ export class GPGME_Key { * during a session. The key still can be reloaded by invoking * {@link refreshKey}. * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). - * If 'cached' is true, the value will be resolved as a Promise. + * If 'cached' is false, the value will be resolved as a Promise. */ get(property, cached=true) { if (cached === false) { @@ -194,8 +194,11 @@ export class GPGME_Key { if (result.keys.length === 1){ me.setKeyData(result.keys[0]); me.getHasSecret().then(function(){ - //TODO retrieve armored Key - resolve(me); + me.getArmor().then(function(){ + resolve(me); + }, function(error){ + reject(error); + }); }, function(error){ reject(error); }); -- cgit v1.2.3 From b18b96fb364711025d1e5fa9f135ee682dd0558a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Jul 2018 11:20:33 +0200 Subject: js: clean up test extension -- Tests will now run with one instance of gpgmejs each block, which reduces overhead. Readability is (hopefully) improved), some negative tests are added. There is still a performance problem in base64 encoding/decoding, which causes some tests to fail due to time out. --- .../BrowserTestExtension/tests/KeyImportExport.js | 169 +++++++------- .../tests/encryptDecryptTest.js | 255 +++++++++------------ lang/js/BrowserTestExtension/tests/encryptTest.js | 157 +++++-------- lang/js/BrowserTestExtension/tests/inputvalues.js | 56 ++++- .../BrowserTestExtension/tests/longRunningTests.js | 76 +++--- lang/js/BrowserTestExtension/tests/signTest.js | 58 ++--- lang/js/BrowserTestExtension/tests/verifyTest.js | 99 ++++---- 7 files changed, 417 insertions(+), 453 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index ed307b32..d2fa2d38 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -22,18 +22,25 @@ * Raimund Renkert */ -/* global describe, it, expect, Gpgmejs, ImportablePublicKey, inputvalues */ +/* global describe, it, expect, before, afterEach, Gpgmejs*/ +/* global ImportablePublicKey, inputvalues */ 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( + const fpr = ImportablePublicKey.fingerprint; + const pubKey = ImportablePublicKey.key; + const changedKey = ImportablePublicKey.keyChangedUserId; + + let context = null; + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + context.Keyring.getKeys(fpr).then( function(result){ if (result.length === 1) { - result[0].delete().then(function(result){ - expect(result).to.be.true; + result[0].delete().then(function(){ + done(); + },function(){ done(); }); } else { @@ -43,76 +50,79 @@ describe('Key importing', function () { }); }); - 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.Keys[0]).to.not.be.undefined; - expect(result.Keys[0].key).to.be.an('object'); - expect(result.Keys[0].key.fingerprint).to.equal( - ImportablePublicKey.fingerprint); - expect(result.Keys[0].status).to.equal('newkey'); - context.Keyring.importKey( - ImportablePublicKey.keyChangedUserId,true) - .then(function(res){ - expect(res.Keys[0]).to.not.be.undefined; - expect(res.Keys[0].key).to.be.an('object'); - expect(res.Keys[0].key.fingerprint).to.equal( - ImportablePublicKey.fingerprint); - expect(res.Keys[0].status).to.equal( - 'change'); - expect( - res.Keys[0].changes.userId).to.be.true; - expect( - res.Keys[0].changes.subkey).to.be.false; - expect( - res.Keys[0].changes.signature).to.be.true; - res.Keys[0].key.delete().then(function(result){ - expect(result).to.be.true; - done(); - }); - }); - }); + afterEach(function(done){ + // delete the test key if still present + context.Keyring.getKeys(fpr).then( + function(result){ + if (result.length === 1) { + result[0].delete().then(function(){ + done(); + },function(){ + done(); + }); + } else { + done(); + } + }); + }); + + it('Importing Key', function (done) { + context.Keyring.getKeys(fpr).then(function(result){ + expect(result).to.be.an('array'); + expect(result.length).to.equal(0); + context.Keyring.importKey(pubKey).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(fpr); + expect(result[0].status).to.equal('newkey'); + done(); + }); + }); + }); + + it('Updating Key', function(done){ + context.Keyring.importKey(pubKey) + .then(function(result){ + expect(result[0].key).to.not.be.undefined; + expect(result[0].status).to.equal('newkey'); + context.Keyring.importKey(changedKey).then(function(res){ + expect(res[0].key).to.be.an('object'); + expect(res[0].key.fingerprint).to.equal(fpr); + 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; + done(); }); + }); + }); + + it('Deleting Key', function(done) { + context.Keyring.importKey(pubKey).then(function(result){ + expect(result[0].key).to.be.an('object'); + expect(result[0].key.fingerprint).to.equal(fpr); + result[0].key.delete().then(function(result){ + expect(result).to.be.true; + done(); + }); }); }); it('Import result feedback', function(done){ - 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).to.be.an('object'); - expect(result.Keys[0]).to.be.an('object'); - expect(result.Keys[0].key.fingerprint).to.equal( - ImportablePublicKey.fingerprint); - expect(result.Keys[0].status).to.equal('newkey'); - result.Keys[0].key.getArmor().then(function(armor){ - expect(armor).to.be.a('string'); - result.Keys[0].key.delete().then(function(){ - done(); - }); - }); - }); - }); + context.Keyring.importKey(pubKey, true).then(function(result){ + expect(result).to.be.an('object'); + expect(result.Keys[0]).to.be.an('object'); + expect(result.Keys[0].key.fingerprint).to.equal(fpr); + expect(result.Keys[0].status).to.equal('newkey'); + result.Keys[0].key.getArmor().then(function(armor){ + expect(armor).to.be.a('string'); + done(); + }); }); }); it('exporting armored Key with getKeysArmored', function (done) { - let prm = Gpgmejs.init(); - const fpr = inputvalues.encrypt.good.fingerprint; - prm.then(function (context) { + context.Keyring.importKey(pubKey).then(function(){ context.Keyring.getKeysArmored(fpr).then(function(result){ expect(result).to.be.an('object'); expect(result.armored).to.be.a('string'); @@ -121,18 +131,15 @@ describe('Key importing', function () { }); }); }); - it('exporting armored Key (including secret fingerprints) with ' - + 'getKeysArmored', function (done) { - let prm = Gpgmejs.init(); - const fpr = inputvalues.encrypt.good.fingerprint; - prm.then(function (context) { - context.Keyring.getKeysArmored(fpr, true).then(function(result){ - expect(result).to.be.an('object'); - expect(result.armored).to.be.a('string'); - expect(result.secret_fprs).to.be.an('array'); - expect(result.secret_fprs[0]).to.equal(fpr); - done(); - }); + + it('Exporting Key (including secret fingerprints)', function (done) { + const key_secret = inputvalues.encrypt.good.fingerprint; + context.Keyring.getKeysArmored(key_secret, true).then(function(result){ + expect(result).to.be.an('object'); + expect(result.armored).to.be.a('string'); + expect(result.secret_fprs).to.be.an('array'); + expect(result.secret_fprs[0]).to.equal(key_secret); + done(); }); }); }); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index bd72c1d2..80b293d2 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -21,152 +21,101 @@ * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, expect, before, Gpgmejs */ /* global inputvalues, encryptedData, bigString, bigBoringString */ describe('Encryption and Decryption', function () { + let context = null; + let good_fpr = inputvalues.encrypt.good.fingerprint; + + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); + 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(); - }); - }); + let data = inputvalues.encrypt.good.data; + context.encrypt(data, good_fpr).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(); - }); + 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); + it('Trailing whitespace and different line endings', function (done) { + const data = 'Keks. \rKeks \n Keks \r\n'; + context.encrypt(data, good_fpr).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'); - 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(); - }); - }); + 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); - } + }); + }).timeout(5000); 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(); - }); - }); + context.encrypt(data, good_fpr).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, 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( - function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - expect(data).to.equal(data); - done(); - }); + context.encrypt(b64data, good_fpr, 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( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(data).to.equal(data); + done(); }); }); }).timeout(3000); @@ -174,26 +123,48 @@ describe('Encryption and Decryption', function () { it('Random data, input as base64', function (done) { 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( - function (result) { - expect(result).to.not.be.empty; - expect(result.data).to.be.a('string'); - expect(result.data).to.equal(b64data); - done(); - }); + context.encrypt(b64data, good_fpr, 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( + function (result) { + expect(result).to.not.be.empty; + expect(result.data).to.be.a('string'); + expect(result.data).to.equal(b64data); + done(); }); }); }).timeout(3000); + 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 data = ''; + for (let i=0; i < 34 * 1024; i++){ + data += input; + } + context.encrypt(data,good_fpr).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); + } }); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 1114125e..3ead8153 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -21,134 +21,91 @@ * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, expect, before, Gpgmejs */ /* global inputvalues, fixedLengthString */ describe('Encryption', function () { - it('Successful encrypt', 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'); - done(); - }); + let context = null; + const good_fpr = inputvalues.encrypt.good.fingerprint; + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); }); }); - it('Successful encrypt 5 MB', function (done) { - let prm = Gpgmejs.init(); - let data = fixedLengthString(5); - 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'); - done(); - }); - }); - }).timeout(10000); - - it('Successful encrypt 20 MB', function (done) { - let prm = Gpgmejs.init(); - let data = fixedLengthString(20); - 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'); - done(); - }); + it('Successful encrypt', function (done) { + const data = inputvalues.encrypt.good.data; + context.encrypt(data, good_fpr).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'); + done(); }); - }).timeout(20000); + }); - it('Successful encrypt 50 MB', function (done) { - let prm = Gpgmejs.init(); - let data = fixedLengthString(50); - prm.then(function (context) { - context.encrypt( - data, - inputvalues.encrypt.good.fingerprint).then(function (answer) { + const sizes = [5,20,50]; + for (let i=0; i < sizes.length; i++) { + it('Successful encrypt a ' + sizes[i] + 'MB message', function (done) { + const data = fixedLengthString(sizes[i]); + context.encrypt(data, good_fpr).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'); done(); }); - }); - }).timeout(20000); + }).timeout(20000); + } it('Sending encryption without keys fails', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.encrypt( - inputvalues.encrypt.good.data, - null).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an('Error'); - expect(error.code).to.equal('MSG_INCOMPLETE'); - done(); - }); + const data = inputvalues.encrypt.good.data; + context.encrypt(data,null).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); }); }); it('Sending encryption without data fails', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.encrypt( - null, inputvalues.encrypt.good.keyid).then(function (answer) { - expect(answer).to.be.undefined; - }, function (error) { - expect(error).to.be.an.instanceof(Error); - expect(error.code).to.equal('MSG_INCOMPLETE'); - done(); - }); + context.encrypt(null, good_fpr).then(function (answer) { + expect(answer).to.be.undefined; + }, function (error) { + expect(error).to.be.an.instanceof(Error); + expect(error.code).to.equal('MSG_INCOMPLETE'); + done(); }); }); it('Sending encryption with non existing keys fails', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.encrypt( - inputvalues.encrypt.good.data, - inputvalues.encrypt.bad.fingerprint).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an('Error'); - expect(error.code).to.not.be.undefined; - expect(error.code).to.equal('GNUPG_ERROR'); - done(); - }); + const data = inputvalues.encrypt.good.data; + const bad_fpr = inputvalues.encrypt.bad.fingerprint; + context.encrypt(data, bad_fpr).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an('Error'); + expect(error.code).to.not.be.undefined; + expect(error.code).to.equal('GNUPG_ERROR'); + done(); }); }).timeout(5000); it('Overly large message ( > 64MB) is rejected', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.encrypt( - fixedLengthString(65), - inputvalues.encrypt.good.fingerprint).then(function (answer) { - expect(answer).to.be.undefined; - }, function(error){ - expect(error).to.be.an.instanceof(Error); - // TODO: there is a 64 MB hard limit at least in chrome at: - // chromium//extensions/renderer/messaging_util.cc: - // kMaxMessageLength - // The error will be a browser error, not from gnupg or from - // this library - done(); - }); + const data = fixedLengthString(65); + context.encrypt(data, good_fpr).then(function (answer) { + expect(answer).to.be.undefined; + }, function(error){ + expect(error).to.be.an.instanceof(Error); + // TODO: there is a 64 MB hard limit at least in chrome at: + // chromium//extensions/renderer/messaging_util.cc: + // kMaxMessageLength + // The error will be a browser error, not from gnupg or from + // this library + done(); }); }).timeout(8000); diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 1c701904..5289eab7 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -53,6 +53,39 @@ const inputvalues = {// eslint-disable-line no-unused-vars // bogus fingerprint) fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A' } + }, + + signedMessage: { + good: '-----BEGIN PGP SIGNED MESSAGE-----\n' + + 'Hash: SHA256\n' + + '\n' + + 'Matschige Münsteraner Marshmallows\n' + + '-----BEGIN PGP SIGNATURE-----\n' + + '\n' + + 'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + + 'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + + 'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + + 'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + + 'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + + 'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + + 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + + '=ioB6\n' + + '-----END PGP SIGNATURE-----\n', + bad: '-----BEGIN PGP SIGNED MESSAGE-----\n' + + 'Hash: SHA256\n' + + '\n' + + 'Matschige Münchener Marshmallows\n' + + '-----BEGIN PGP SIGNATURE-----\n' + + '\n' + + 'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + + 'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + + 'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + + 'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + + 'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + + 'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + + 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + + '=ioB6\n' + + '-----END PGP SIGNATURE-----\n', } }; @@ -83,7 +116,7 @@ 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); + uint[i] = Math.floor(Math.random() * 256); } return uint; } @@ -125,6 +158,27 @@ function slightlyLessBoringString(megabytes, set){ return string.join(''); } +// Take a gpg looking string and destroy it a bit by changing random values +// eslint-disable-next-line no-unused-vars +function destroylegitimateGpg(string, mutations=5){ + const allowed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/\n'; + for (let i=0; i < mutations.length; i++){ + // leave the first and last 35 chars (header/footer) intact + let position = Math.floor(Math.random() *(string.length - 70)) + 35; + let str0 = string.substring(0,position - 1); + let str1 = string.substring(position, position + 1); + let str2 = string.substring(position +1); + let success = false; + while (!success){ + let newchar = Math.floor(Math.random() * allowed.length); + if (newchar !== str1){ + string = str0 + newchar + str2; + success = true; + } + } + } +} + // Data encrypted with testKey const encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index e148d1cf..03f0390c 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -20,61 +20,37 @@ * Author(s): * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, before, expect, Gpgmejs */ /* global bigString, inputvalues */ describe('Long running Encryption/Decryption', function () { + let context = null; + const good_fpr = inputvalues.encrypt.good.fingerprint; + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); + for (let i=0; i < 101; i++) { - it('Successful encrypt/decrypt completely random data ' + - (i+1) + '/100', function (done) { - let prm = Gpgmejs.init(); - let data = bigString(2*1024*1024); - 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'); - /* - if (result.data.length !== data.length) { - console.log('diff: ' + - (result.data.length - data.length)); - for (let i=0; i < result.data.length; i++){ - if (result.data[i] !== data[i]){ - console.log('position: ' + i); - console.log('result : ' + - result.data.charCodeAt(i) + - result.data[i-2] + - result.data[i-1] + - result.data[i] + - result.data[i+1] + - result.data[i+2]); - console.log('original: ' + - data.charCodeAt(i) + - data[i-2] + - data[i-1] + - data[i] + - data[i+1] + - data[i+2]); - break; - } - } - } - */ - expect(result.data).to.equal(data); - done(); - }); - }); + it('Successful encrypt/decrypt completely random data ' + + (i+1) + '/100', function (done) { + const data = bigString(2*1024*1024); + context.encrypt(data,good_fpr).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(8000); + }).timeout(15000); } }); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js index ffd2d5de..2763dadf 100644 --- a/lang/js/BrowserTestExtension/tests/signTest.js +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -21,42 +21,42 @@ * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, expect, before, Gpgmejs */ /* global bigString, inputvalues */ describe('Signing', function () { + let context = null; + const good_fpr = inputvalues.encrypt.good.fingerprint; + + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); + it('Sign a message', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - let data = bigString(100); - context.sign( - 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 SIGNATURE'); - expect(answer.data).to.include('END PGP SIGNATURE'); - expect(answer.data).to.include(data); - done(); - }); + const data = bigString(100); + context.sign(data, good_fpr).then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include('BEGIN PGP SIGNATURE'); + expect(answer.data).to.include('END PGP SIGNATURE'); + expect(answer.data).to.include(data); + done(); }); }); + it('Detached sign a message', function (done) { - let prm = Gpgmejs.init(); - prm.then(function (context) { - let data = bigString(100); - context.sign( - data, - inputvalues.encrypt.good.fingerprint, - 'detached' - ).then(function (answer) { - expect(answer).to.not.be.empty; - expect(answer.data).to.be.a('string'); - expect(answer.data).to.include(data); - expect(answer.signature).to.be.a('string'); - expect(answer.signature).to.be.a('string'); - done(); - }); + const data = bigString(100); + context.sign(data,good_fpr, 'detached').then(function (answer) { + expect(answer).to.not.be.empty; + expect(answer.data).to.be.a('string'); + expect(answer.data).to.include(data); + expect(answer.signature).to.be.a('string'); + expect(answer.signature).to.be.a('string'); + done(); }); }); diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js index e8e2c76a..1617e2dc 100644 --- a/lang/js/BrowserTestExtension/tests/verifyTest.js +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -21,64 +21,63 @@ * Maximilian Krambach */ -/* global describe, it, expect, bigString, inputvalues, Gpgmejs */ +/* global describe, it, expect, before, bigString, inputvalues, Gpgmejs */ -let verifyData = { - signedMessage: '-----BEGIN PGP SIGNED MESSAGE-----\n' + - 'Hash: SHA256\n' + - '\n' + - 'Matschige Münsteraner Marshmallows\n' + - '-----BEGIN PGP SIGNATURE-----\n' + - '\n' + - 'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' + - 'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' + - 'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' + - 'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' + - 'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' + - 'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + - 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + - '=ioB6\n' + - '-----END PGP SIGNATURE-----\n' -}; -describe('Verify data', function () { + +describe('Verifying data', function () { + let context = null; + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); it('Successful verify message', function (done) { - let message = verifyData.signedMessage; - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.verify(message).then(function(result){ - expect(result.data).to.be.a('string'); - expect(result.all_valid).to.be.true; - expect(result.count).to.equal(1); - expect(result.signatures.good).to.be.an('array'); - expect(result.signatures.good.length).to.equal(1); - expect(result.signatures.good[0].fingerprint) - .to.be.a('string'); - expect(result.signatures.good[0].valid).to.be.true; - done(); - }); + const message = inputvalues.signedMessage.good; + context.verify(message).then(function(result){ + expect(result.data).to.be.a('string'); + expect(result.all_valid).to.be.true; + expect(result.count).to.equal(1); + expect(result.signatures.good).to.be.an('array'); + expect(result.signatures.good.length).to.equal(1); + expect(result.signatures.good[0].fingerprint).to.be.a('string'); + expect(result.signatures.good[0].valid).to.be.true; + done(); + }); + }); + + it('Successfully recognize changed cleartext', function (done) { + const message = inputvalues.signedMessage.bad; + context.verify(message).then(function(result){ + expect(result.data).to.be.a('string'); + expect(result.all_valid).to.be.false; + expect(result.count).to.equal(1); + expect(result.signatures.bad).to.be.an('array'); + expect(result.signatures.bad.length).to.equal(1); + expect(result.signatures.bad[0].fingerprint).to.be.a('string'); + expect(result.signatures.bad[0].valid).to.be.false; + done(); }); }); it('Encrypt-Sign-Verify random message', function (done) { - let message = bigString(2000); + const message = bigString(2000); let fpr = inputvalues.encrypt.good.fingerprint; - let prm = Gpgmejs.init(); - prm.then(function (context) { - context.encrypt(message, fpr).then(function(message_enc){ - context.sign(message_enc.data, fpr).then(function(message_encsign){ - context.verify(message_encsign.data).then(function(result){ - expect(result.data).to.equal(message_enc.data); - expect(result.data).to.be.a('string'); - expect(result.all_valid).to.be.true; - expect(result.count).to.equal(1); - expect(result.signatures.good).to.be.an('array'); - expect(result.signatures.good.length).to.equal(1); - expect(result.signatures.good[0].fingerprint) - .to.equal(fpr); - expect(result.signatures.good[0].valid).to.be.true; - done(); - }); + context.encrypt(message, fpr).then(function(message_enc){ + context.sign(message_enc.data, fpr).then(function(message_encsign){ + context.verify(message_encsign.data).then(function(result){ + expect(result.data).to.equal(message_enc.data); + expect(result.data).to.be.a('string'); + expect(result.all_valid).to.be.true; + expect(result.count).to.equal(1); + expect(result.signatures.good).to.be.an('array'); + expect(result.signatures.good.length).to.equal(1); + expect( + result.signatures.good[0].fingerprint).to.equal(fpr); + expect(result.signatures.good[0].valid).to.be.true; + done(); }); }); }); -- cgit v1.2.3 From 94ee0988d4eaac27785de6efb7c19ca9976e1e9c Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Jul 2018 20:36:21 +0200 Subject: js: change the write access for js class methods -- * src/ [Connection, Error, Key, Keyring, MEssage, Signature, gpgmejs]: Functions and values that are not meant to be overwritten are now moved into their constructors, thus eliminating the possibility of overwrites after initialization. * Key: The mode of use (synchronous cached, or async promises) ivs now determined at initialization of that Key. The property Key.isAsync reflects this state. * unittests: fixed old Key syntax for testing. * Message.js isComplete is now a method and not a getter anymore. * Added some startup tests. --- lang/js/BrowserTestExtension/tests/inputvalues.js | 25 +- lang/js/BrowserTestExtension/tests/startup.js | 15 +- lang/js/src/Connection.js | 123 +++++----- lang/js/src/Errors.js | 12 +- lang/js/src/Key.js | 287 ++++++++++------------ lang/js/src/Keyring.js | 26 +- lang/js/src/Message.js | 125 ++++------ lang/js/src/Signature.js | 74 ++++-- lang/js/src/gpgmejs.js | 77 ++++-- lang/js/unittest_inputvalues.js | 11 +- lang/js/unittests.js | 22 +- 11 files changed, 403 insertions(+), 394 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 5289eab7..9d956b69 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -86,7 +86,9 @@ const inputvalues = {// eslint-disable-line no-unused-vars 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + '=ioB6\n' + '-----END PGP SIGNATURE-----\n', - } + }, + + someInputParameter: 'bad string' }; // (Pseudo-)Random String covering all of utf8. @@ -158,27 +160,6 @@ function slightlyLessBoringString(megabytes, set){ return string.join(''); } -// Take a gpg looking string and destroy it a bit by changing random values -// eslint-disable-next-line no-unused-vars -function destroylegitimateGpg(string, mutations=5){ - const allowed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/\n'; - for (let i=0; i < mutations.length; i++){ - // leave the first and last 35 chars (header/footer) intact - let position = Math.floor(Math.random() *(string.length - 70)) + 35; - let str0 = string.substring(0,position - 1); - let str1 = string.substring(position, position + 1); - let str2 = string.substring(position +1); - let success = false; - while (!success){ - let newchar = Math.floor(Math.random() * allowed.length); - if (newchar !== str1){ - string = str0 + newchar + str2; - success = true; - } - } - } -} - // Data encrypted with testKey const encryptedData =// eslint-disable-line no-unused-vars '-----BEGIN PGP MESSAGE-----\n' + diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index dae94025..1e2784d9 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -21,17 +21,28 @@ * Maximilian Krambach */ -/* global describe, it, expect, Gpgmejs */ +/* global describe, it, expect, Gpgmejs, inputvalues */ describe('GPGME context', function(){ it('Starting a GpgME instance', function(done){ let prm = Gpgmejs.init(); + const input = inputvalues.someInputParameter; prm.then( function(context){ expect(context).to.be.an('object'); expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); + expect(context.sign).to.be.a('function'); + expect(context.verify).to.be.a('function'); + context.Keyring = input; + expect(context.Keyring).to.be.an('object'); + expect(context.Keyring).to.not.equal(input); + expect(context._Keyring).to.equal(context.Keyring); + expect(context.Keyring.getKeys).to.be.a('function'); + expect(context.Keyring.getDefaultKey).to.be.a('function'); + expect(context.Keyring.importKey).to.be.a('function'); + expect(context.Keyring.generateKey).to.be.a('function'); done(); }); }); -}); +}); \ No newline at end of file diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index d89fa724..d482667e 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -38,8 +38,19 @@ import { GPGME_Message, createMessage } from './Message'; export class Connection{ constructor(){ - this.connect(); - } + let _connection = chrome.runtime.connectNative('gpgmejson'); + + + /** + * Immediately closes an open port. + */ + this.disconnect = function () { + if (_connection){ + _connection.disconnect(); + _connection = null; + } + }; + /** * @typedef {Object} backEndDetails @@ -63,14 +74,15 @@ export class Connection{ * backend * @async */ - checkConnection(details = true){ + this.checkConnection = function(details = true){ + const msg = createMessage('version'); if (details === true) { - return this.post(createMessage('version')); + return this.post(msg); } else { let me = this; return new Promise(function(resolve) { Promise.race([ - me.post(createMessage('version')), + me.post(msg), new Promise(function(resolve, reject){ setTimeout(function(){ reject(gpgme_error('CONN_TIMEOUT')); @@ -83,26 +95,7 @@ export class Connection{ }); }); } - } - - /** - * Immediately closes an 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 {@link GPGME_Message} via tghe nativeMessaging port. It resolves @@ -113,65 +106,65 @@ export class Connection{ * @returns {Promise} The collected answer * @async */ - post(message){ + this.post = function (message){ if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); return Promise.reject(gpgme_error( 'PARAM_WRONG', 'Connection.post')); } - if (message.isComplete !== true){ + if (message.isComplete() !== true){ this.disconnect(); return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } - let me = this; let chunksize = message.chunksize; 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(); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else { let answer_result = answer.collect(msg); if (answer_result !== true){ - me._connection.onMessage.removeListener(listener); - me._connection.disconnect(); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); reject(answer_result); } else { if (msg.more === true){ - me._connection.postMessage({ + _connection.postMessage({ 'op': 'getmore', 'chunksize': chunksize }); } else { - me._connection.onMessage.removeListener(listener); - me._connection.disconnect(); - if (answer.message instanceof Error){ - reject(answer.message); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); + const message = answer.getMessage(); + if (message instanceof Error){ + reject(message); } else { - resolve(answer.message); + resolve(message); } } } } }; - me._connection.onMessage.addListener(listener); + _connection.onMessage.addListener(listener); if (permittedOperations[message.operation].pinentry){ - return me._connection.postMessage(message.message); + return _connection.postMessage(message.message); } else { return Promise.race([ - me._connection.postMessage(message.message), + _connection.postMessage(message.message), function(resolve, reject){ setTimeout(function(){ - me._connection.disconnect(); + _connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); }]).then(function(result){ return result; }, function(reject){ if(!(reject instanceof Error)) { - me._connection.disconnect(); + _connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { return reject; @@ -179,7 +172,8 @@ export class Connection{ }); } }); - } + }; +} } /** @@ -193,10 +187,16 @@ class Answer{ * @param {GPGME_Message} message */ constructor(message){ - this.operation = message.operation; - this.expect = message.expect; - } + const operation = message.operation; + const expect = message.expect; + let response_b64 = null; + this.getOperation = function(){ + return operation; + }; + this.getExpect = function(){ + return expect; + }; /** * Adds incoming base64 encoded data to the existing response * @param {*} msg base64 encoded data. @@ -204,34 +204,30 @@ class Answer{ * * @private */ - collect(msg){ + this.collect = function (msg){ if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - if (this._responseb64 === undefined){ - //this._responseb64 = [msg.response]; - this._responseb64 = msg.response; + if (response_b64 === null){ + response_b64 = msg.response; return true; } else { - //this._responseb64.push(msg.response); - this._responseb64 += msg.response; + response_b64 += msg.response; return true; } - } - - /** + }; + /** * Returns the base64 encoded answer data with the content verified against * {@link permittedOperations}. */ - get message(){ - if (this._responseb64 === undefined){ + this.getMessage = function (){ + if (response_b64 === undefined){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - // let _decodedResponse = JSON.parse(atob(this._responseb64.join(''))); - let _decodedResponse = JSON.parse(atob(this._responseb64)); + let _decodedResponse = JSON.parse(atob(response_b64)); let _response = {}; let messageKeys = Object.keys(_decodedResponse); - let poa = permittedOperations[this.operation].answer; + let poa = permittedOperations[this.getOperation()].answer; if (messageKeys.length === 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } @@ -262,7 +258,7 @@ class Answer{ } if (_decodedResponse.base64 === true && poa.data[key] === 'string' - && this.expect === undefined + && this.getExpect() === undefined ){ _response[key] = decodeURIComponent( atob(_decodedResponse[key]).split('').map( @@ -277,5 +273,6 @@ class Answer{ } } return _response; - } + }; +} } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index cb5c94c2..0cf1af19 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -143,7 +143,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ * @extends Error */ class GPGME_Error extends Error{ - constructor(code, msg=''){ + constructor(code = 'GENERIC_ERROR', msg=''){ if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ @@ -155,12 +155,12 @@ class GPGME_Error extends Error{ } else { super(err_list['GENERIC_ERROR'].msg); } - this.code = code || 'GENERIC_ERROR'; - } - set code(value){ - this._code = value; + this.getCode = function(){ + return code; + }; } + get code(){ - return this._code; + return this.getCode(); } } \ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index b024a77b..a7f7dd26 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -28,13 +28,16 @@ import { createMessage } from './Message'; /** * Validates the given fingerprint and creates a new {@link GPGME_Key} * @param {String} fingerprint + * @param {Boolean} async If True, Key properties (except fingerprint) will be + * queried from gnupg on each call, making the operation up-to-date, the + * answers will be Promises, and the performance will likely suffer * @returns {GPGME_Key|GPGME_Error} */ -export function createKey(fingerprint){ - if (!isFingerprint(fingerprint)){ +export function createKey(fingerprint, async = false){ + if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } - else return new GPGME_Key(fingerprint); + else return new GPGME_Key(fingerprint, async); } /** @@ -48,31 +51,30 @@ export function createKey(fingerprint){ */ export class GPGME_Key { - constructor(fingerprint){ - this.fingerprint = fingerprint; - } + constructor(fingerprint, async){ - 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; - } + /** + * @property {Boolean} If true, most answers will be asynchronous + */ + this.isAsync = async; + + let _data = {fingerprint: fingerprint}; + this.getFingerprint = function(){ + if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ + return gpgme_error('KEY_INVALID'); } - } - } + return _data.fingerprint; + }; /** - * @returns {String} The fingerprint defining this Key + * Property indicating if the Key possesses a private/secret part. If this + * information is not yet cached, it returns an {@link GPGME_Error} with + * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. + * @returns {Boolean} If the Key has a secret subkey. */ - get fingerprint(){ - if (!this._data || !this._data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - return this._data.fingerprint; - } + this.hasSecret= function (){ + return this.get('hasSecret', true); + }; /** * @param {Object} data Bulk set the data for this key, with an Object sent @@ -81,97 +83,89 @@ export class GPGME_Key { * error if something went wrong * @private */ - setKeyData(data){ - if (this._data === undefined) { - this._data = {}; + this.setKeyData = function (data){ + if (typeof(data) !== 'object') { + return gpgme_error('KEY_INVALID'); } - if ( - typeof(data) !== 'object') { + if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ 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){ + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++){ + if (!validKeyProperties.hasOwnProperty(keys[i])){ 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]){ + //running the defined validation function + if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ + return gpgme_error('KEY_INVALID'); + } + switch (keys[i]){ case 'subkeys': - this._data.subkeys = []; + _data.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { - this._data.subkeys.push( + _data.subkeys.push( new GPGME_Subkey(data.subkeys[i])); } break; case 'userids': - this._data.userids = []; + _data.userids = []; for (let i=0; i< data.userids.length; i++) { - this._data.userids.push( + _data.userids.push( new GPGME_UserId(data.userids[i])); } break; case 'last_update': - this._data[dataKeys[i]] = new Date( data[dataKeys[i]] * 1000 ); + _data[keys[i]] = new Date( data[keys[i]] * 1000 ); break; default: - this._data[dataKeys[i]] = data[dataKeys[i]]; + _data[keys[i]] = data[keys[i]]; } } return this; - } + }; /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @param {Boolean} cached (optional) if false, the data will be directly - * queried from gnupg, and the operation will be asynchronous. Else, the - * data will be fetched from the state of the initialization of the Key. - * The cached mode may contain outdated information, but can be used as - * synchronous operation, where the backend is not expected to change Keys - * during a session. The key still can be reloaded by invoking - * {@link refreshKey}. * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). * If 'cached' is false, the value will be resolved as a Promise. */ - get(property, cached=true) { - if (cached === false) { + this.get = function(property) { + if (this.isAsync === true) { let me = this; return new Promise(function(resolve, reject) { - if (!validKeyProperties.hasOwnProperty(property)){ - reject('PARAM_WRONG'); - } else if (property === 'armored'){ + if (property === 'armored'){ resolve(me.getArmor()); } else if (property === 'hasSecret'){ resolve(me.getHasSecret()); - } else { - me.refreshKey().then(function(key){ - resolve(key.get(property, true)); + } else if (validKeyProperties.hasOwnProperty(property)){ + let msg = createMessage('keylist'); + msg.setParameter('keys', _data.fingerprint); + msg.post().then(function(result){ + if (result.keys && result.keys.length === 1 && + result.keys[0].hasOwnProperty(property)){ + resolve(result.keys[0][property]); + } else { + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); + } }, function(error){ - reject(error); + reject(gpgme_error(error)); }); + } else { + reject(gpgme_error('PARAM_WRONG')); } }); } else { if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); } - if (!this._data.hasOwnProperty(property)){ + if (!_data.hasOwnProperty(property)){ return gpgme_error('KEY_NO_INIT'); } else { - return (this._data[property]); + return (_data[property]); } } - } + }; /** * Reloads the Key information from gnupg. This is only useful if you use @@ -181,15 +175,15 @@ export class GPGME_Key { * @returns {Promise} * @async */ - refreshKey() { + this.refreshKey = function() { let me = this; return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); msg.setParameter('sigs', true); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ if (result.keys.length === 1){ me.setKeyData(result.keys[0]); @@ -209,7 +203,7 @@ export class GPGME_Key { reject(gpgme_error('GNUPG_ERROR'), error); }); }); - } + }; /** * Query the armored block of the Key directly from gnupg. Please note that @@ -217,23 +211,22 @@ export class GPGME_Key { * @returns {Promise} * @async */ - getArmor(){ - let me = this; + this.getArmor = function(){ return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('export'); msg.setParameter('armor', true); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ - me._data.armored = result.data; + _data.armored = result.data; resolve(result.data); }, function(error){ reject(error); }); }); - } + }; /** * Find out if the Key includes a secret part. Note that this is a rather @@ -244,59 +237,32 @@ export class GPGME_Key { * available in the gnupg Keyring * @async */ - getHasSecret(){ - let me = this; + this.getHasSecret = function (){ return new Promise(function(resolve, reject) { - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('keylist'); - msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('keys', _data.fingerprint); msg.setParameter('secret', true); msg.post().then(function(result){ - me._data.hasSecret = null; + _data.hasSecret = null; if ( result.keys && result.keys.length === 1 && result.keys[0].secret === true ) { - me._data.hasSecret = true; + _data.hasSecret = true; resolve(true); } else { - me._data.hasSecret = false; + _data.hasSecret = false; resolve(false); } }, function(error){ reject(error); }); }); - } - - /** - * Convenience functions 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 - */ - - /** - * Property for the export of armored Key. If the armored Key is not - * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. - * Running {@link refreshKey} may help in this case. - * @returns {String|GPGME_Error} The armored public Key block. - */ - get armored(){ - return this.get('armored', true); - } - - /** - * Property indicating if the Key possesses a private/secret part. If this - * information is not yet cached, it returns an {@link GPGME_Error} with - * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. - * @returns {Boolean} If the Key has a secret subkey. - */ - get hasSecret(){ - return this.get('hasSecret', true); - } + }; /** * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a @@ -304,20 +270,37 @@ export class GPGME_Key { * @returns {Promise} Success if key was deleted, * rejects with a GPG error otherwise. */ - delete(){ - let me = this; + this.delete= function (){ return new Promise(function(resolve, reject){ - if (!me._data.fingerprint){ + if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } let msg = createMessage('delete'); - msg.setParameter('key', me._data.fingerprint); + msg.setParameter('key', _data.fingerprint); msg.post().then(function(result){ resolve(result.success); }, function(error){ reject(error); }); }); + }; + } + + /** + * @returns {String} The fingerprint defining this Key + */ + get fingerprint(){ + return this.getFingerprint(); + } + + /** + * Property for the export of armored Key. If the armored Key is not + * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. + * Running {@link refreshKey} may help in this case. + * @returns {String|GPGME_Error} The armored public Key block. + */ + get armored(){ + return this.get('armored', true); } } @@ -334,44 +317,45 @@ class GPGME_Subkey { * @private */ constructor(data){ + let _data = {}; let keys = Object.keys(data); - for (let i=0; i< keys.length; i++) { - this.setProperty(keys[i], data[keys[i]]); - } - } - /** + /** * Validates a subkey property against {@link validSubKeyProperties} and * sets it if validation is successful * @param {String} property * @param {*} value * @param private */ - setProperty(property, value){ - if (!this._data){ - this._data = {}; - } + const setProperty = function (property, value){ if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ - this._data[property] = new Date(value * 1000); + _data[property] = new Date(value * 1000); } else { - this._data[property] = value; + _data[property] = value; } } } + }; + for (let i=0; i< keys.length; i++) { + setProperty(keys[i], data[keys[i]]); } + + /** * Fetches any information about this subkey * @param {String} property Information to request * @returns {String | Number | Date} */ - get(property) { - if (this._data.hasOwnProperty(property)){ - return (this._data[property]); + this.get = function(property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); } - } + }; +} + } /** @@ -387,11 +371,23 @@ class GPGME_UserId { * @private */ constructor(data){ + let _data = {}; let keys = Object.keys(data); + const setProperty = function(property, value){ + if (validUserIdProperties.hasOwnProperty(property)){ + if (validUserIdProperties[property](value) === true) { + if (property === 'last_update'){ + _data[property] = new Date(value*1000); + } else { + _data[property] = value; + } + } + } + }; for (let i=0; i< keys.length; i++) { - this.setProperty(keys[i], data[keys[i]]); + setProperty(keys[i], data[keys[i]]); } - } + /** * Validates a subkey property against {@link validUserIdProperties} and * sets it if validation is successful @@ -399,34 +395,21 @@ class GPGME_UserId { * @param {*} value * @param private */ - setProperty(property, value){ - if (!this._data){ - this._data = {}; - } - if (validUserIdProperties.hasOwnProperty(property)){ - if (validUserIdProperties[property](value) === true) { - if (property === 'last_update'){ - this._data[property] = new Date(value*1000); - } else { - this._data[property] = value; - } - } - } - } /** * Fetches information about the user * @param {String} property Information to request * @returns {String | Number} */ - get(property) { - if (this._data.hasOwnProperty(property)){ - return (this._data[property]); + this.get = function (property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); } - } + }; } +} /** * Validation definition for userIds. Each valid userId property is represented * as a key- Value pair, with their value being a validation function to check diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index f2a71389..d863fe73 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -32,7 +32,6 @@ import { gpgme_error } from './Errors'; */ export class GPGME_Keyring { constructor(){ - } /** * Queries Keys (all Keys or a subset) from gnupg. @@ -50,7 +49,7 @@ export class GPGME_Keyring { * @static * @async */ - getKeys(pattern, prepare_sync=false, search=false){ + this.getKeys = function(pattern, prepare_sync=false, search=false){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined && pattern !== null){ @@ -107,7 +106,7 @@ export class GPGME_Keyring { } }); }); - } + }; /** * @typedef {Object} exportResult The result of a getKeysArmored operation. @@ -131,7 +130,7 @@ export class GPGME_Keyring { * @static * @async */ - getKeysArmored(pattern, with_secret_fpr) { + this.getKeysArmored = function(pattern, with_secret_fpr) { return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); @@ -153,7 +152,7 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; /** * Returns the Key used by default in gnupg. @@ -165,7 +164,7 @@ export class GPGME_Keyring { * @async * @static */ - getDefaultKey() { + this.getDefaultKey = function() { let me = this; return new Promise(function(resolve, reject){ let msg = createMessage('config_opt'); @@ -206,7 +205,7 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; /** * @typedef {Object} importResult The result of a Key update @@ -244,7 +243,7 @@ export class GPGME_Keyring { * @async * @static */ - importKey(armored, prepare_sync) { + this.importKey = function (armored, prepare_sync) { let feedbackValues = ['considered', 'no_user_id', 'imported', 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', 'new_signatures', 'new_revocations', 'secret_read', @@ -323,7 +322,7 @@ export class GPGME_Keyring { }); - } + }; /** * Convenience function for deleting a Key. See {@link Key.delete} for @@ -333,14 +332,14 @@ export class GPGME_Keyring { * @async * @static */ - deleteKey(fingerprint){ + this.deleteKey = function(fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); return key.delete(); } else { return Promise.reject(gpgme_error('KEY_INVALID')); } - } + }; /** * Generates a new Key pair directly in gpg, and returns a GPGME_Key @@ -356,7 +355,7 @@ export class GPGME_Keyring { * @return {Promise} * @async */ - generateKey(userId, algo = 'default', expires){ + this.generateKey = function (userId, algo = 'default', expires){ if ( typeof(userId) !== 'string' || supportedKeyAlgos.indexOf(algo) < 0 || @@ -385,7 +384,8 @@ export class GPGME_Keyring { reject(error); }); }); - } + }; +} } /** diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 2e38142c..cf9f84ef 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -52,22 +52,14 @@ export function createMessage(operation){ export class GPGME_Message { constructor(operation){ - this.operation = operation; - } + let _msg = { + op: operation, + chunksize: 1023* 1024 + }; - set operation (op){ - if (typeof(op) === 'string'){ - if (!this._msg){ - this._msg = {}; - } - if (!this._msg.op & permittedOperations.hasOwnProperty(op)){ - this._msg.op = op; - } - } - } - get operation(){ - return this._msg.op; - } + this.getOperation = function(){ + return _msg.op; + }; /** * The maximum size of responses from gpgme in bytes. As of July 2018, @@ -78,41 +70,23 @@ export class GPGME_Message { * messages will be received in chunks. * If the value is not explicitly specified, 1023 KB is used. */ - set chunksize(value){ + this.setChunksize = function (value){ if ( Number.isInteger(value) && value > 10 * 1024 && value <= 1024 * 1024 ){ - this._chunksize = value; - } - } - get chunksize(){ - if (this._chunksize === undefined){ - return 1024 * 1023; - } else { - return this._chunksize; + _msg.chunksize = value; } - } + }; - /** - * Expect indicates which format data is expected to be in. It currently - * only supports 'base64', indicating that the response data from gnupg are - * expected to be base64 encoded binary - */ - set expect(value){ - if (value ==='base64'){ - this._expect = value; - } - } - - get expect(){ - if ( this._expect === 'base64'){ - return this._expect; - } - return undefined; - } + this.getMsg = function(){ + return _msg; + }; + this.getChunksize= function() { + return _msg.chunksize; + }; /** * Sets a parameter for the message. It validates with @@ -121,11 +95,11 @@ export class GPGME_Message { * @param {any} value Value to set * @returns {Boolean} If the parameter was set successfully */ - setParameter( param,value ){ + this.setParameter = function ( param,value ){ if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } - let po = permittedOperations[this._msg.op]; + let po = permittedOperations[_msg.op]; if (!po){ return gpgme_error('MSG_WRONG_OP'); } @@ -195,59 +169,42 @@ export class GPGME_Message { return gpgme_error('PARAM_WRONG'); } } - this._msg[param] = value; + _msg[param] = value; return true; - } + }; + + /** * Check if the message has the minimum requirements to be sent, that is * all 'required' parameters according to {@link permittedOperations}. * @returns {Boolean} true if message is complete. */ - get isComplete(){ - if (!this._msg.op){ + this.isComplete = function(){ + if (!_msg.op){ return false; } let reqParams = Object.keys( - permittedOperations[this._msg.op].required); - let msg_params = Object.keys(this._msg); + permittedOperations[_msg.op].required); + let msg_params = Object.keys(_msg); for (let i=0; i < reqParams.length; i++){ if (msg_params.indexOf(reqParams[i]) < 0){ return false; } } return true; - } - - /** - * Returns the prepared message with parameters and completeness checked - * @returns {Object|null} Object to be posted to gnupg, or null if - * incomplete - */ - get message(){ - if (this.isComplete === true){ - this._msg.chunksize = this.chunksize; - return this._msg; - } - else { - return null; - } - - } - + }; /** * Sends the Message via nativeMessaging and resolves with the answer. * @returns {Promise} * @async */ - post(){ + this.post = function(){ let me = this; return new Promise(function(resolve, reject) { - if (me.isComplete === true) { + if (me.isComplete() === true) { + let conn = new Connection; - if (me._msg.chunksize === undefined){ - me._msg.chunksize = me.chunksize; - } conn.post(me).then(function(response) { resolve(response); }, function(reason) { @@ -258,5 +215,27 @@ export class GPGME_Message { reject(gpgme_error('MSG_INCOMPLETE')); } }); + }; +} + + /** + * Returns the prepared message with parameters and completeness checked + * @returns {Object|null} Object to be posted to gnupg, or null if + * incomplete + */ + get message(){ + if (this.isComplete() === true){ + return this.getMsg(); + } + else { + return null; + } } + +get operation(){ + return this.getOperation(); +} +get chunksize(){ + return this.getChunksize(); +} } diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index 191e00ab..ff4278ad 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -82,44 +82,47 @@ export function createSignature(sigObject){ class GPGME_Signature { constructor(sigObject){ - this._rawSigObject = sigObject; - } + let _rawSigObject = sigObject; - get fingerprint(){ - return this._rawSigObject.fingerprint; - } + this.getFingerprint = function(){ + if (!_rawSigObject.fingerprint){ + return gpgme_error('SIG_WRONG'); + } else { + return _rawSigObject.fingerprint; + } + }; /** * The expiration of this Signature as Javascript date, or null if * signature does not expire * @returns {Date | null} */ - get expiration(){ - if (!this._rawSigObject.exp_timestamp){ + this.getExpiration = function(){ + if (!_rawSigObject.exp_timestamp){ return null; } - return new Date(this._rawSigObject.exp_timestamp* 1000); - } + return new Date(_rawSigObject.exp_timestamp* 1000); + }; /** * The creation date of this Signature in Javascript Date * @returns {Date} */ - get timestamp(){ - return new Date(this._rawSigObject.timestamp * 1000); - } + this.getTimestamp= function (){ + return new Date(_rawSigObject.timestamp * 1000); + }; /** * The overall validity of the key. If false, errorDetails may contain * additional information */ - get valid() { - if (this._rawSigObject.summary.valid === true){ + this.getValid= function() { + if (_rawSigObject.summary.valid === true){ return true; } else { return false; } - } + }; /** * gives more information on non-valid signatures. Refer to the gpgme docs @@ -127,19 +130,54 @@ class GPGME_Signature { * details on the values * @returns {Object} Object with boolean properties */ - get errorDetails(){ + this.getErrorDetails = function (){ let properties = ['revoked', 'key-expired', 'sig-expired', 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', 'sys-error']; let result = {}; for (let i=0; i< properties.length; i++){ - if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ - result[properties[i]] = this._rawSigObject[properties[i]]; + if ( _rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = _rawSigObject[properties[i]]; } } return result; + }; +} + + /** + * Convenience getter for {@link getFingerprint} + */ + get fingerprint(){ + return this.getFingerprint(); } + /** + * Convenience getter for {@link getExpiration} + */ + get expiration(){ + return this.getExpiration(); + } + + /** + * Convenience getter for {@link getTimeStamp} + */ + get timestamp(){ + return this.getTimestamp(); + } + + /** + * Convenience getter for {@link getValid} + */ + get valid(){ + return this.getValid(); + } + + /** + * Convenience getter for {@link getErrorDetails} + */ + get errorDetails(){ + return this.getErrorDetails(); + } } /** diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 3f6dc947..f587e854 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -22,8 +22,8 @@ */ -import {GPGME_Message, createMessage} from './Message'; -import {toKeyIdArray} from './Helpers'; +import { GPGME_Message, createMessage } from './Message'; +import { toKeyIdArray } from './Helpers'; import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; import { createSignature } from './Signature'; @@ -85,23 +85,27 @@ import { createSignature } from './Signature'; export class GpgME { constructor(){ - } + let _Keyring = null; - set Keyring(keyring){ - if (keyring && keyring instanceof GPGME_Keyring){ - this._Keyring = keyring; - } - } + /** + * Sets a new Keyring to be used + * @param {GPGME_Keyring} keyring + */ + this.setKeyring = function(keyring){ + if (keyring && keyring instanceof GPGME_Keyring){ + _Keyring = keyring; + } + }; - /** - * Accesses the {@link GPGME_Keyring}. - */ - get Keyring(){ - if (!this._Keyring){ - this._Keyring = new GPGME_Keyring; - } - return this._Keyring; - } + /** + * Accesses the {@link GPGME_Keyring}. + */ + this.getKeyring = function(){ + if (!_Keyring){ + _Keyring = new GPGME_Keyring; + } + return _Keyring; + }; /** * Encrypt (and optionally sign) data @@ -123,8 +127,8 @@ export class GpgME { * message and additional info. * @async */ - encrypt(data, publicKeys, secretKeys, base64=false, armor=true, - wildcard=false, additional = {} + this.encrypt = function (data, publicKeys, secretKeys, base64=false, + armor=true, wildcard=false, additional = {} ){ let msg = createMessage('encrypt'); if (msg instanceof Error){ @@ -152,12 +156,12 @@ export class GpgME { additional[additional_Keys[k]]); } } - if (msg.isComplete === true){ + if (msg.isComplete() === true){ return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } - } + }; /** * Decrypts a Message @@ -168,7 +172,7 @@ export class GpgME { * @returns {Promise} Decrypted Message and information * @async */ - decrypt(data, base64=false){ + this.decrypt = function (data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -203,7 +207,7 @@ export class GpgME { reject(error); }); }); - } + }; /** * Sign a Message @@ -217,7 +221,7 @@ export class GpgME { * @returns {Promise} * @async */ - sign(data, keys, mode='clearsign', base64=false) { + this.sign = function (data, keys, mode='clearsign', base64=false) { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } @@ -252,7 +256,7 @@ export class GpgME { reject(error); }); }); - } + }; /** * Verifies data. @@ -264,7 +268,7 @@ export class GpgME { * @returns {Promise} *@async */ - verify(data, signature, base64 = false){ + this.verify= function (data, signature, base64 = false){ let msg = createMessage('verify'); let dt = putData(msg, data); if (dt instanceof Error){ @@ -297,6 +301,25 @@ export class GpgME { reject(error); }); }); + }; +} + + /** + * setter for {@link setKeyring}. + * @param {GPGME_Keyring} keyring A Keyring to use + */ + set Keyring(keyring){ + this.setKeyring(keyring); + } + + /** + * Accesses the {@link GPGME_Keyring}. + */ + get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } + return this._Keyring; } } @@ -309,7 +332,7 @@ export class GpgME { * @private */ function putData(message, data){ - if (!message || !(message instanceof GPGME_Message) ) { + if (!message || !message instanceof GPGME_Message) { return gpgme_error('PARAM_WRONG'); } if (!data){ diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index 07147bac..02bb5329 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -1,12 +1,9 @@ -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), + createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), 'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'], validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', @@ -15,11 +12,11 @@ export const helper_params = { invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], invalidKeyArray: {curiosity:'uncat'}, invalidKeyArray_OneBad: [ - createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), + createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'], invalidErrorCode: 'Please type in all your passwords.', - validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', conn), + validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', true), valid_openpgplike: { primaryKey: { getFingerprint: function(){ return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} @@ -36,7 +33,7 @@ export const message_params = { invalid_param_names: [22,'dance', {}], validparam_name_0: 'mime', invalid_values_0: [2134, 'All your passwords', - createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08', conn), null] + createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null] } }; diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 04e15ef3..6228993b 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -194,16 +194,16 @@ function unittests (){ }); it('Non-cached key async data retrieval', function (done){ - let key = createKey(kp.validKeyFingerprint); - key.get('can_authenticate',false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('can_authenticate').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('armored', false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('armored').then(function(result){ expect(result).to.be.a('string'); expect(result).to.include('KEY BLOCK-----'); done(); @@ -211,17 +211,17 @@ function unittests (){ }); it('Non-cached key async hasSecret', function (done){ - let key = createKey(kp.validKeyFingerprint); - key.get('hasSecret', false).then(function(result){ + let key = createKey(kp.validKeyFingerprint, true); + key.get('hasSecret').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); + let key = createKey(kp.validFingerprintNoSecret, true); expect(key).to.be.an.instanceof(GPGME_Key); - key.get('hasSecret', false).then(function(result){ + key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); expect(result).to.equal(false); done(); @@ -317,7 +317,7 @@ function unittests (){ let test0 = createMessage('encrypt'); expect(test0).to.be.an.instanceof(GPGME_Message); - expect(test0.isComplete).to.be.false; + expect(test0.isComplete()).to.be.false; }); it('Message is complete after setting mandatory data', function(){ @@ -325,14 +325,14 @@ function unittests (){ test0.setParameter('data', mp.valid_encrypt_data); test0.setParameter('keys', hp.validFingerprints); - expect(test0.isComplete).to.be.true; + 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; + expect(test0.isComplete()).to.be.false; }); it('Complete Message contains the data that was set', function(){ -- cgit v1.2.3 From 522121ea7e105acc22795b1997ca500c7b227b4f Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 27 Jul 2018 20:56:11 +0200 Subject: js: fix indentaion -- * doing the indentation changes that became neccesary in the last commit. --- lang/js/src/Connection.js | 372 +++++++++++++------------- lang/js/src/Key.js | 493 +++++++++++++++++----------------- lang/js/src/Keyring.js | 653 +++++++++++++++++++++++----------------------- lang/js/src/Message.js | 283 ++++++++++---------- lang/js/src/Signature.js | 116 ++++---- lang/js/src/gpgmejs.js | 371 +++++++++++++------------- 6 files changed, 1150 insertions(+), 1138 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index d482667e..561a5b70 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -52,128 +52,128 @@ export class Connection{ }; - /** - * @typedef {Object} backEndDetails - * @property {String} gpgme Version number of gpgme - * @property {Array} info Further information about the backend - * and the used applications (Example: - * { - * "protocol": "OpenPGP", - * "fname": "/usr/bin/gpg", - * "version": "2.2.6", - * "req_version": "1.4.0", - * "homedir": "default" - * } - */ + /** + * @typedef {Object} backEndDetails + * @property {String} gpgme Version number of gpgme + * @property {Array} info Further information about the backend + * and the used applications (Example: + * { + * "protocol": "OpenPGP", + * "fname": "/usr/bin/gpg", + * "version": "2.2.6", + * "req_version": "1.4.0", + * "homedir": "default" + * } + */ - /** - * Retrieves the information about the backend. - * @param {Boolean} details (optional) If set to false, the promise will - * just return if a connection was successful. - * @returns {Promise|Promise} Details from the - * backend - * @async - */ - this.checkConnection = function(details = true){ - const msg = createMessage('version'); - if (details === true) { - return this.post(msg); - } else { - let me = this; - return new Promise(function(resolve) { - Promise.race([ - me.post(msg), - new Promise(function(resolve, reject){ - setTimeout(function(){ - reject(gpgme_error('CONN_TIMEOUT')); - }, 500); - }) - ]).then(function(){ // success - resolve(true); - }, function(){ // failure - resolve(false); + /** + * Retrieves the information about the backend. + * @param {Boolean} details (optional) If set to false, the promise will + * just return if a connection was successful. + * @returns {Promise|Promise} Details from the + * backend + * @async + */ + this.checkConnection = function(details = true){ + const msg = createMessage('version'); + if (details === true) { + return this.post(msg); + } else { + let me = this; + return new Promise(function(resolve) { + Promise.race([ + me.post(msg), + new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(gpgme_error('CONN_TIMEOUT')); + }, 500); + }) + ]).then(function(){ // success + resolve(true); + }, function(){ // failure + resolve(false); + }); }); - }); - } - }; + } + }; - /** - * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It resolves - * with the completed answer after all parts have been received and - * reassembled, or rejects with an {@link GPGME_Error}. - * - * @param {GPGME_Message} message - * @returns {Promise} The collected answer - * @async - */ - this.post = function (message){ - if (!message || !(message instanceof GPGME_Message)){ - this.disconnect(); - return Promise.reject(gpgme_error( - 'PARAM_WRONG', 'Connection.post')); - } - if (message.isComplete() !== true){ - this.disconnect(); - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); - } - let chunksize = message.chunksize; - return new Promise(function(resolve, reject){ - let answer = new Answer(message); - let listener = function(msg) { - if (!msg){ - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); - } else { - let answer_result = answer.collect(msg); - if (answer_result !== true){ + /** + * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It + * resolves with the completed answer after all parts have been + * received and reassembled, or rejects with an {@link GPGME_Error}. + * + * @param {GPGME_Message} message + * @returns {Promise} The collected answer + * @async + */ + this.post = function (message){ + if (!message || !(message instanceof GPGME_Message)){ + this.disconnect(); + return Promise.reject(gpgme_error( + 'PARAM_WRONG', 'Connection.post')); + } + if (message.isComplete() !== true){ + this.disconnect(); + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + let chunksize = message.chunksize; + return new Promise(function(resolve, reject){ + let answer = new Answer(message); + let listener = function(msg) { + if (!msg){ _connection.onMessage.removeListener(listener); _connection.disconnect(); - reject(answer_result); + reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); } else { - if (msg.more === true){ - _connection.postMessage({ - 'op': 'getmore', - 'chunksize': chunksize - }); - } else { + let answer_result = answer.collect(msg); + if (answer_result !== true){ _connection.onMessage.removeListener(listener); _connection.disconnect(); - const message = answer.getMessage(); - if (message instanceof Error){ - reject(message); + reject(answer_result); + } else { + if (msg.more === true){ + _connection.postMessage({ + 'op': 'getmore', + 'chunksize': chunksize + }); } else { - resolve(message); + _connection.onMessage.removeListener(listener); + _connection.disconnect(); + const message = answer.getMessage(); + if (message instanceof Error){ + reject(message); + } else { + resolve(message); + } } } } - } - }; - _connection.onMessage.addListener(listener); - if (permittedOperations[message.operation].pinentry){ - return _connection.postMessage(message.message); - } else { - return Promise.race([ - _connection.postMessage(message.message), - function(resolve, reject){ - setTimeout(function(){ + }; + _connection.onMessage.addListener(listener); + if (permittedOperations[message.operation].pinentry){ + return _connection.postMessage(message.message); + } else { + return Promise.race([ + _connection.postMessage(message.message), + function(resolve, reject){ + setTimeout(function(){ + _connection.disconnect(); + reject(gpgme_error('CONN_TIMEOUT')); + }, 5000); + }]).then(function(result){ + return result; + }, function(reject){ + if(!(reject instanceof Error)) { _connection.disconnect(); - reject(gpgme_error('CONN_TIMEOUT')); - }, 5000); - }]).then(function(result){ - return result; - }, function(reject){ - if(!(reject instanceof Error)) { - _connection.disconnect(); - return gpgme_error('GNUPG_ERROR', reject); - } else { - return reject; - } - }); - } - }); - }; -} + return gpgme_error('GNUPG_ERROR', reject); + } else { + return reject; + } + }); + } + }); + }; + } } /** @@ -197,82 +197,84 @@ class Answer{ this.getExpect = function(){ return expect; }; - /** - * Adds incoming base64 encoded data to the existing response - * @param {*} msg base64 encoded data. - * @returns {Boolean} - * - * @private - */ - this.collect = function (msg){ - if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (response_b64 === null){ - response_b64 = msg.response; - return true; - } else { - response_b64 += msg.response; - return true; - } - }; - /** - * Returns the base64 encoded answer data with the content verified against - * {@link permittedOperations}. - */ - this.getMessage = function (){ - if (response_b64 === undefined){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - let _decodedResponse = JSON.parse(atob(response_b64)); - let _response = {}; - let messageKeys = Object.keys(_decodedResponse); - let poa = permittedOperations[this.getOperation()].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 (_decodedResponse.type === 'error'){ - return (gpgme_error('GNUPG_ERROR', - decodeURIComponent(escape(_decodedResponse.msg)))); - } else if (poa.type.indexOf(_decodedResponse.type) < 0){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - break; - case 'base64': - break; - case 'msg': - if (_decodedResponse.type === 'error'){ - return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); - } - break; - default: - if (!poa.data.hasOwnProperty(key)){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if( typeof(_decodedResponse[key]) !== poa.data[key] ){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (_decodedResponse.base64 === true - && poa.data[key] === 'string' - && this.getExpect() === undefined - ){ - _response[key] = decodeURIComponent( - atob(_decodedResponse[key]).split('').map( - function(c) { - return '%' + - ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - } else { - _response[key] = _decodedResponse[key]; + + /** + * Adds incoming base64 encoded data to the existing response + * @param {*} msg base64 encoded data. + * @returns {Boolean} + * + * @private + */ + this.collect = function (msg){ + if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (response_b64 === null){ + response_b64 = msg.response; + return true; + } else { + response_b64 += msg.response; + return true; + } + }; + /** + * Returns the base64 encoded answer data with the content verified + * against {@link permittedOperations}. + */ + this.getMessage = function (){ + if (response_b64 === undefined){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + let _decodedResponse = JSON.parse(atob(response_b64)); + let _response = {}; + let messageKeys = Object.keys(_decodedResponse); + let poa = permittedOperations[this.getOperation()].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 (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', + decodeURIComponent(escape(_decodedResponse.msg)))); + } else if (poa.type.indexOf(_decodedResponse.type) < 0){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + break; + case 'base64': + break; + case 'msg': + if (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', + _decodedResponse.msg)); + } + break; + default: + if (!poa.data.hasOwnProperty(key)){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if( typeof(_decodedResponse[key]) !== poa.data[key] ){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (_decodedResponse.base64 === true + && poa.data[key] === 'string' + && this.getExpect() === undefined + ){ + _response[key] = decodeURIComponent( + atob(_decodedResponse[key]).split('').map( + function(c) { + return '%' + + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + } else { + _response[key] = _decodedResponse[key]; + } + break; } - break; } - } - return _response; - }; -} + return _response; + }; + } } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index a7f7dd26..d5873a70 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -66,224 +66,226 @@ export class GPGME_Key { return _data.fingerprint; }; - /** - * Property indicating if the Key possesses a private/secret part. If this - * information is not yet cached, it returns an {@link GPGME_Error} with - * code 'KEY_NO_INIT'. Running {@link refreshKey} may help in this case. - * @returns {Boolean} If the Key has a secret subkey. - */ - this.hasSecret= function (){ - return this.get('hasSecret', true); - }; + /** + * Property indicating if the Key possesses a private/secret part. If + * this information is not yet cached, it returns an + * {@link GPGME_Error} with code 'KEY_NO_INIT'. Running + * {@link refreshKey} may help in this case. + * @returns {Boolean} If the Key has a secret subkey. + */ + this.hasSecret= function (){ + return this.get('hasSecret', true); + }; - /** - * @param {Object} data Bulk set the data for this key, with an Object sent - * by gpgme-json. - * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, an - * error if something went wrong - * @private - */ - this.setKeyData = function (data){ - if (typeof(data) !== 'object') { - return gpgme_error('KEY_INVALID'); - } - if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - let keys = Object.keys(data); - for (let i=0; i< keys.length; i++){ - if (!validKeyProperties.hasOwnProperty(keys[i])){ - return gpgme_error('KEY_INVALID'); - } - //running the defined validation function - if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ - return gpgme_error('KEY_INVALID'); - } - switch (keys[i]){ - case 'subkeys': - _data.subkeys = []; - for (let i=0; i< data.subkeys.length; i++) { - _data.subkeys.push( - new GPGME_Subkey(data.subkeys[i])); + /** + * @param {Object} data Bulk set the data for this key, with an Object + * sent by gpgme-json. + * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, + * an error if something went wrong. + * @private + */ + this.setKeyData = function (data){ + if (typeof(data) !== 'object') { + return gpgme_error('KEY_INVALID'); + } + if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ + return gpgme_error('KEY_INVALID'); + } + let keys = Object.keys(data); + for (let i=0; i< keys.length; i++){ + if (!validKeyProperties.hasOwnProperty(keys[i])){ + return gpgme_error('KEY_INVALID'); } - break; - case 'userids': - _data.userids = []; - for (let i=0; i< data.userids.length; i++) { - _data.userids.push( - new GPGME_UserId(data.userids[i])); + //running the defined validation function + if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ + return gpgme_error('KEY_INVALID'); } - break; - case 'last_update': - _data[keys[i]] = new Date( data[keys[i]] * 1000 ); - break; - default: + switch (keys[i]){ + case 'subkeys': + _data.subkeys = []; + for (let i=0; i< data.subkeys.length; i++) { + _data.subkeys.push( + new GPGME_Subkey(data.subkeys[i])); + } + break; + case 'userids': + _data.userids = []; + for (let i=0; i< data.userids.length; i++) { + _data.userids.push( + new GPGME_UserId(data.userids[i])); + } + break; + case 'last_update': + _data[keys[i]] = new Date( data[keys[i]] * 1000 ); + break; + default: _data[keys[i]] = data[keys[i]]; - } - } - return this; - }; - - /** - * Query any property of the Key listed in {@link validKeyProperties} - * @param {String} property property to be retreived - * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). - * If 'cached' is false, the value will be resolved as a Promise. - */ - this.get = function(property) { - if (this.isAsync === true) { - let me = this; - return new Promise(function(resolve, reject) { - if (property === 'armored'){ - resolve(me.getArmor()); - } else if (property === 'hasSecret'){ - resolve(me.getHasSecret()); - } else if (validKeyProperties.hasOwnProperty(property)){ - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys && result.keys.length === 1 && - result.keys[0].hasOwnProperty(property)){ - resolve(result.keys[0][property]); - } else { - reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); - } - }, function(error){ - reject(gpgme_error(error)); - }); - } else { - reject(gpgme_error('PARAM_WRONG')); } - }); - } else { - if (!validKeyProperties.hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); } - if (!_data.hasOwnProperty(property)){ - return gpgme_error('KEY_NO_INIT'); + return this; + }; + + /** + * Query any property of the Key listed in {@link validKeyProperties} + * @param {String} property property to be retreived + * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). + * If 'cached' is false, the value will be resolved as a Promise. + */ + this.get = function(property) { + if (this.isAsync === true) { + let me = this; + return new Promise(function(resolve, reject) { + if (property === 'armored'){ + resolve(me.getArmor()); + } else if (property === 'hasSecret'){ + resolve(me.getHasSecret()); + } else if (validKeyProperties.hasOwnProperty(property)){ + let msg = createMessage('keylist'); + msg.setParameter('keys', _data.fingerprint); + msg.post().then(function(result){ + if (result.keys && result.keys.length === 1 && + result.keys[0].hasOwnProperty(property)){ + resolve(result.keys[0][property]); + } else { + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); + } + }, function(error){ + reject(gpgme_error(error)); + }); + } else { + reject(gpgme_error('PARAM_WRONG')); + } + }); } else { + if (!validKeyProperties.hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); + } + if (!_data.hasOwnProperty(property)){ + return gpgme_error('KEY_NO_INIT'); + } else { return (_data[property]); + } } - } - }; + }; - /** - * Reloads the Key information from gnupg. This is only useful if you use - * the GPGME_Keys cached. Note that this is a performance hungry operation. - * If you desire more than a few refreshs, it may be advisable to run - * {@link Keyring.getKeys} instead. - * @returns {Promise} - * @async - */ - this.refreshKey = function() { - let me = this; - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('keylist'); - msg.setParameter('sigs', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys.length === 1){ - me.setKeyData(result.keys[0]); - me.getHasSecret().then(function(){ - me.getArmor().then(function(){ - resolve(me); + /** + * Reloads the Key information from gnupg. This is only useful if you + * use the GPGME_Keys cached. Note that this is a performance hungry + * operation. If you desire more than a few refreshs, it may be + * advisable to run {@link Keyring.getKeys} instead. + * @returns {Promise} + * @async + */ + this.refreshKey = function() { + let me = this; + return new Promise(function(resolve, reject) { + if (!_data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('keylist'); + msg.setParameter('sigs', true); + msg.setParameter('keys', _data.fingerprint); + msg.post().then(function(result){ + if (result.keys.length === 1){ + me.setKeyData(result.keys[0]); + me.getHasSecret().then(function(){ + me.getArmor().then(function(){ + resolve(me); + }, function(error){ + reject(error); + }); }, function(error){ reject(error); }); - }, function(error){ - reject(error); - }); - } else { - reject(gpgme_error('KEY_NOKEY')); - } - }, function (error) { - reject(gpgme_error('GNUPG_ERROR'), error); + } else { + reject(gpgme_error('KEY_NOKEY')); + } + }, function (error) { + reject(gpgme_error('GNUPG_ERROR'), error); + }); }); - }); - }; + }; - /** - * Query the armored block of the Key directly from gnupg. Please note that - * this will not get you any export of the secret/private parts of a Key - * @returns {Promise} - * @async - */ - this.getArmor = function(){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('export'); - msg.setParameter('armor', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - _data.armored = result.data; - resolve(result.data); - }, function(error){ - reject(error); + /** + * Query the armored block of the Key directly from gnupg. Please note + * that this will not get you any export of the secret/private parts of + * a Key + * @returns {Promise} + * @async + */ + this.getArmor = function(){ + return new Promise(function(resolve, reject) { + if (!_data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('export'); + msg.setParameter('armor', true); + msg.setParameter('keys', _data.fingerprint); + msg.post().then(function(result){ + _data.armored = result.data; + resolve(result.data); + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * Find out if the Key includes a secret part. Note that this is a rather - * nonperformant operation, as it needs to query gnupg twice. If you want - * this inforrmation about more than a few Keys, it may be advisable to run - * {@link Keyring.getKeys} instead. - * @returns {Promise} True if a secret/private Key is - * available in the gnupg Keyring - * @async - */ - this.getHasSecret = function (){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.setParameter('secret', true); - msg.post().then(function(result){ - _data.hasSecret = null; - if ( - result.keys && - result.keys.length === 1 && - result.keys[0].secret === true - ) { - _data.hasSecret = true; - resolve(true); - } else { - _data.hasSecret = false; - resolve(false); + /** + * Find out if the Key includes a secret part. Note that this is a + * rather nonperformant operation, as it needs to query gnupg twice. + * If you want this inforrmation about more than a few Keys, it may be + * advisable to run {@link Keyring.getKeys} instead. + * @returns {Promise} True if a secret/private Key + * is available in the gnupg Keyring + * @async + */ + this.getHasSecret = function (){ + return new Promise(function(resolve, reject) { + if (!_data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); } - }, function(error){ - reject(error); + let msg = createMessage('keylist'); + msg.setParameter('keys', _data.fingerprint); + msg.setParameter('secret', true); + msg.post().then(function(result){ + _data.hasSecret = null; + if ( + result.keys && + result.keys.length === 1 && + result.keys[0].secret === true + ) { + _data.hasSecret = true; + resolve(true); + } else { + _data.hasSecret = false; + resolve(false); + } + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * Deletes the (public) Key from the GPG Keyring. Note that a deletion of a - * secret key is not supported by the native backend. - * @returns {Promise} Success if key was deleted, - * rejects with a GPG error otherwise. - */ - this.delete= function (){ - return new Promise(function(resolve, reject){ - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('delete'); - msg.setParameter('key', _data.fingerprint); - msg.post().then(function(result){ - resolve(result.success); - }, function(error){ - reject(error); + /** + * Deletes the (public) Key from the GPG Keyring. Note that a deletion + * of a secret key is not supported by the native backend. + * @returns {Promise} Success if key was deleted, + * rejects with a GPG error otherwise. + */ + this.delete= function (){ + return new Promise(function(resolve, reject){ + if (!_data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } + let msg = createMessage('delete'); + msg.setParameter('key', _data.fingerprint); + msg.post().then(function(result){ + resolve(result.success); + }, function(error){ + reject(error); + }); }); - }); - }; + }; } /** @@ -320,42 +322,39 @@ class GPGME_Subkey { let _data = {}; let keys = Object.keys(data); - /** - * Validates a subkey property against {@link validSubKeyProperties} and - * sets it if validation is successful - * @param {String} property - * @param {*} value - * @param private - */ - const setProperty = function (property, value){ - if (validSubKeyProperties.hasOwnProperty(property)){ - if (validSubKeyProperties[property](value) === true) { - if (property === 'timestamp' || property === 'expires'){ - _data[property] = new Date(value * 1000); - } else { - _data[property] = value; + /** + * Validates a subkey property against {@link validSubKeyProperties} and + * sets it if validation is successful + * @param {String} property + * @param {*} value + * @param private + */ + const setProperty = function (property, value){ + if (validSubKeyProperties.hasOwnProperty(property)){ + if (validSubKeyProperties[property](value) === true) { + if (property === 'timestamp' || property === 'expires'){ + _data[property] = new Date(value * 1000); + } else { + _data[property] = value; + } } } + }; + for (let i=0; i< keys.length; i++) { + setProperty(keys[i], data[keys[i]]); } - }; - for (let i=0; i< keys.length; i++) { - setProperty(keys[i], data[keys[i]]); - } - - - - /** - * Fetches any information about this subkey - * @param {String} property Information to request - * @returns {String | Number | Date} - */ - this.get = function(property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; -} + /** + * Fetches any information about this subkey + * @param {String} property Information to request + * @returns {String | Number | Date} + */ + this.get = function(property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); + } + }; + } } /** @@ -388,28 +387,28 @@ class GPGME_UserId { setProperty(keys[i], data[keys[i]]); } - /** - * Validates a subkey property against {@link validUserIdProperties} and - * sets it if validation is successful - * @param {String} property - * @param {*} value - * @param private - */ + /** + * Validates a subkey property against {@link validUserIdProperties} and + * sets it if validation is successful + * @param {String} property + * @param {*} value + * @param private + */ - /** - * Fetches information about the user - * @param {String} property Information to request - * @returns {String | Number} - */ - this.get = function (property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; + /** + * Fetches information about the user + * @param {String} property Information to request + * @returns {String | Number} + */ + this.get = function (property) { + if (_data.hasOwnProperty(property)){ + return (_data[property]); + } + }; + } } -} /** * Validation definition for userIds. Each valid userId property is represented * as a key- Value pair, with their value being a validation function to check diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d863fe73..31c4f92b 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -33,359 +33,368 @@ import { gpgme_error } from './Errors'; export class GPGME_Keyring { constructor(){ - /** - * Queries Keys (all Keys or a subset) from gnupg. - * - * @param {String | Array} pattern (optional) A pattern to search - * for in userIds or KeyIds. - * @param {Boolean} prepare_sync (optional) if set to true, the 'hasSecret' - * and 'armored' properties will be fetched for the Keys as well. These - * require additional calls to gnupg, resulting in a performance hungry - * operation. Calling them here enables direct, synchronous use of these - * properties for all keys, without having to resort to a refresh() first. - * @param {Boolean} search (optional) retrieve Keys from external servers - * with the method(s) defined in gnupg (e.g. WKD/HKP lookup) - * @returns {Promise.|GPGME_Error>} - * @static - * @async - */ - this.getKeys = function(pattern, prepare_sync=false, search=false){ - return new Promise(function(resolve, reject) { - let msg = createMessage('keylist'); - if (pattern !== undefined && pattern !== null){ - msg.setParameter('keys', pattern); - } - msg.setParameter('sigs', true); - if (search === true){ - msg.setParameter('locate', true); - } - msg.post().then(function(result){ - let resultset = []; - if (result.keys.length === 0){ - resolve([]); - } else { - let secondrequest; - if (prepare_sync === true) { - secondrequest = function() { - let msg2 = createMessage('keylist'); - msg2.setParameter('keys', pattern); - msg2.setParameter('secret', true); - return msg2.post(); - }; + /** + * Queries Keys (all Keys or a subset) from gnupg. + * + * @param {String | Array} pattern (optional) A pattern to + * search for in userIds or KeyIds. + * @param {Boolean} prepare_sync (optional) if set to true, the + * 'hasSecret' and 'armored' properties will be fetched for the Keys as + * well. These require additional calls to gnupg, resulting in a + * performance hungry operation. Calling them here enables direct, + * synchronous use of these properties for all keys, without having to + * resort to a refresh() first. + * @param {Boolean} search (optional) retrieve Keys from external + * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) + * @returns {Promise.|GPGME_Error>} + * @static + * @async + */ + this.getKeys = function(pattern, prepare_sync=false, search=false){ + return new Promise(function(resolve, reject) { + let msg = createMessage('keylist'); + if (pattern !== undefined && pattern !== null){ + msg.setParameter('keys', pattern); + } + msg.setParameter('sigs', true); + if (search === true){ + msg.setParameter('locate', true); + } + msg.post().then(function(result){ + let resultset = []; + if (result.keys.length === 0){ + resolve([]); } else { - secondrequest = function() { - return Promise.resolve(true); - }; - } - secondrequest().then(function(answer) { - for (let i=0; i < result.keys.length; i++){ - if (prepare_sync === true){ - result.keys[i].hasSecret = false; - if (answer && answer.keys) { - for (let j=0; j < answer.keys.length; j++ ){ - if (result.keys[i].fingerprint === - answer.keys[j].fingerprint - ) { - if (answer.keys[j].secret === true){ - result.keys[i].hasSecret = true; + let secondrequest; + if (prepare_sync === true) { + secondrequest = function() { + let msg2 = createMessage('keylist'); + msg2.setParameter('keys', pattern); + msg2.setParameter('secret', true); + return msg2.post(); + }; + } else { + secondrequest = function() { + return Promise.resolve(true); + }; + } + secondrequest().then(function(answer) { + for (let i=0; i < result.keys.length; i++){ + if (prepare_sync === true){ + result.keys[i].hasSecret = false; + if (answer && answer.keys) { + for (let j=0; + j < answer.keys.length; j++ ){ + const a = answer.keys[j]; + const b = result.keys[i]; + if ( + a.fingerprint === b.fingerprint + ) { + if (a.secret === true){ + a.hasSecret = true; + } + break; } - break; } + // TODO getArmor() to be used in sync } - // TODO getArmor() to be used in sync } + let k = createKey(result.keys[i].fingerprint); + k.setKeyData(result.keys[i]); + resultset.push(k); } - let k = createKey(result.keys[i].fingerprint); - k.setKeyData(result.keys[i]); - resultset.push(k); - } - resolve(resultset); - }, function(error){ - reject(error); - }); - } + resolve(resultset); + }, function(error){ + reject(error); + }); + } + }); }); - }); - }; + }; - /** - * @typedef {Object} exportResult The result of a getKeysArmored operation. - * @property {String} armored The public Key(s) as armored block. Note that - * the result is one armored block, and not a block per key. - * @property {Array} secret_fprs (optional) list of fingerprints - * for those Keys that also have a secret Key available in gnupg. The - * secret key will not be exported, but the fingerprint can be used in - * operations needing a secret key. - */ + /** + * @typedef {Object} exportResult The result of a getKeysArmored + * operation. + * @property {String} armored The public Key(s) as armored block. Note + * that the result is one armored block, and not a block per key. + * @property {Array} secret_fprs (optional) list of + * fingerprints for those Keys that also have a secret Key available in + * gnupg. The secret key will not be exported, but the fingerprint can + * be used in operations needing a secret key. + */ - /** - * Fetches the armored public Key blocks for all Keys matching the pattern - * (if no pattern is given, fetches all keys known to gnupg). - * @param {String|Array} pattern (optional) The Pattern to search - * for - * @param {Boolean} with_secret_fpr (optional) also return a list of - * fingerprints for the keys that have a secret key available - * @returns {Promise} Object containing the - * armored Key(s) and additional information. - * @static - * @async - */ - this.getKeysArmored = function(pattern, with_secret_fpr) { - return new Promise(function(resolve, reject) { - let msg = createMessage('export'); - msg.setParameter('armor', true); - if (with_secret_fpr === true) { - msg.setParameter('with-sec-fprs', true); - } - if (pattern !== undefined && pattern !== null){ - msg.setParameter('keys', pattern); - } - msg.post().then(function(answer){ - const result = {armored: answer.data}; - if (with_secret_fpr === true - && answer.hasOwnProperty('sec-fprs') - ) { - result.secret_fprs = answer['sec-fprs']; + /** + * Fetches the armored public Key blocks for all Keys matching the + * pattern (if no pattern is given, fetches all keys known to gnupg). + * @param {String|Array} pattern (optional) The Pattern to + * search for + * @param {Boolean} with_secret_fpr (optional) also return a list of + * fingerprints for the keys that have a secret key available + * @returns {Promise} Object containing the + * armored Key(s) and additional information. + * @static + * @async + */ + this.getKeysArmored = function(pattern, with_secret_fpr) { + return new Promise(function(resolve, reject) { + let msg = createMessage('export'); + msg.setParameter('armor', true); + if (with_secret_fpr === true) { + msg.setParameter('with-sec-fprs', true); } - resolve(result); - }, function(error){ - reject(error); + if (pattern !== undefined && pattern !== null){ + msg.setParameter('keys', pattern); + } + msg.post().then(function(answer){ + const result = {armored: answer.data}; + if (with_secret_fpr === true + && answer.hasOwnProperty('sec-fprs') + ) { + result.secret_fprs = answer['sec-fprs']; + } + resolve(result); + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * Returns the Key used by default in gnupg. - * (a.k.a. 'primary Key or 'main key'). - * It looks up the gpg configuration if set, or the first key that contains - * a secret key. - * - * @returns {Promise} - * @async - * @static - */ - this.getDefaultKey = function() { - let me = this; - return new Promise(function(resolve, reject){ - let msg = createMessage('config_opt'); - msg.setParameter('component', 'gpg'); - msg.setParameter('option', 'default-key'); - msg.post().then(function(response){ - if (response.value !== undefined - && response.value.hasOwnProperty('string') - && typeof(response.value.string) === 'string' - ){ - me.getKeys(response.value.string,true).then(function(keys){ - if(keys.length === 1){ - resolve(keys[0]); - } else { - reject(gpgme_error('KEY_NO_DEFAULT')); - } - }, function(error){ - reject(error); - }); - } else { - // TODO: this is overly 'expensive' in communication - // and probably performance, too - me.getKeys(null,true).then(function(keys){ - for (let i=0; i < keys.length; i++){ - if (keys[i].get('hasSecret') === true){ - resolve(keys[i]); - break; - } - if (i === keys.length -1){ - reject(gpgme_error('KEY_NO_DEFAULT')); + /** + * Returns the Key used by default in gnupg. + * (a.k.a. 'primary Key or 'main key'). + * It looks up the gpg configuration if set, or the first key that + * contains a secret key. + * + * @returns {Promise} + * @async + * @static + */ + this.getDefaultKey = function() { + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('config_opt'); + msg.setParameter('component', 'gpg'); + msg.setParameter('option', 'default-key'); + msg.post().then(function(response){ + if (response.value !== undefined + && response.value.hasOwnProperty('string') + && typeof(response.value.string) === 'string' + ){ + me.getKeys(response.value.string,true).then( + function(keys){ + if(keys.length === 1){ + resolve(keys[0]); + } else { + reject(gpgme_error('KEY_NO_DEFAULT')); + } + }, function(error){ + reject(error); + }); + } else { + // TODO: this is overly 'expensive' in communication + // and probably performance, too + me.getKeys(null,true).then(function(keys){ + for (let i=0; i < keys.length; i++){ + if (keys[i].get('hasSecret') === true){ + resolve(keys[i]); + break; + } + if (i === keys.length -1){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } } - } - }, function(error){ - reject(error); - }); - } - }, function(error){ - reject(error); + }, function(error){ + reject(error); + }); + } + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * @typedef {Object} importResult The result of a Key update - * @property {Object} summary Numerical summary of the result. See the - * feedbackValues variable for available Keys values and the gnupg - * documentation. - * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html - * for details on their meaning. - * @property {Array} Keys Array of Object containing - * GPGME_Keys with additional import information - * - */ + /** + * @typedef {Object} importResult The result of a Key update + * @property {Object} summary Numerical summary of the result. See the + * feedbackValues variable for available Keys values and the gnupg + * documentation. + * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html + * for details on their meaning. + * @property {Array} Keys Array of Object containing + * GPGME_Keys with additional import information + * + */ - /** - * @typedef {Object} importedKeyResult - * @property {GPGME_Key} key The resulting key - * @property {String} status: - * '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. - * @property {Boolean} changes.userId Changes in userIds - * @property {Boolean} changes.signature Changes in signatures - * @property {Boolean} changes.subkey Changes in subkeys - */ + /** + * @typedef {Object} importedKeyResult + * @property {GPGME_Key} key The resulting key + * @property {String} status: + * '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. + * @property {Boolean} changes.userId Changes in userIds + * @property {Boolean} changes.signature Changes in signatures + * @property {Boolean} changes.subkey Changes in subkeys + */ - /** - * Import an armored Key block into gnupg. Note that this currently will - * not succeed on private Key blocks. - * @param {String} armored Armored Key block of the Key(s) to be imported - * into gnupg - * @param {Boolean} prepare_sync prepare the keys for synched use - * (see {@link getKeys}). - * @returns {Promise} A summary and Keys considered. - * @async - * @static - */ - this.importKey = function (armored, prepare_sync) { - let feedbackValues = ['considered', 'no_user_id', 'imported', - 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', - 'new_signatures', 'new_revocations', 'secret_read', - 'secret_imported', 'secret_unchanged', 'skipped_new_keys', - 'not_imported', 'skipped_v3_keys']; - 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 (let res=0; res} A summary and Keys considered. + * @async + * @static + */ + this.importKey = function (armored, prepare_sync) { + let feedbackValues = ['considered', 'no_user_id', 'imported', + 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', + 'new_signatures', 'new_revocations', 'secret_read', + 'secret_imported', 'secret_unchanged', 'skipped_new_keys', + 'not_imported', 'skipped_v3_keys']; + 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 (let res=0; res} - * @async - * @static - */ - this.deleteKey = function(fingerprint){ - if (isFingerprint(fingerprint) === true) { - let key = createKey(fingerprint); - return key.delete(); - } else { - return Promise.reject(gpgme_error('KEY_INVALID')); - } - }; + /** + * Convenience function for deleting a Key. See {@link Key.delete} for + * further information about the return values. + * @param {String} fingerprint + * @returns {Promise} + * @async + * @static + */ + this.deleteKey = function(fingerprint){ + if (isFingerprint(fingerprint) === true) { + let key = createKey(fingerprint); + return key.delete(); + } else { + return Promise.reject(gpgme_error('KEY_INVALID')); + } + }; - /** - * Generates a new Key pair directly in gpg, and returns a GPGME_Key - * representing that Key. Please note that due to security concerns, secret - * Keys can not be deleted or exported from inside gpgme.js. - * - * @param {String} userId The user Id, e.g. 'Foo Bar ' - * @param {String} algo (optional) algorithm (and optionally key size) to - * be used. See {@link supportedKeyAlgos} below for supported values. - * @param {Date} expires (optional) Expiration date. If not set, expiration - * will be set to 'never' - * - * @return {Promise} - * @async - */ - this.generateKey = function (userId, algo = 'default', expires){ - if ( - typeof(userId) !== 'string' || - supportedKeyAlgos.indexOf(algo) < 0 || - (expires && !(expires instanceof Date)) - ){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } - let me = this; - return new Promise(function(resolve, reject){ - let msg = createMessage('createkey'); - msg.setParameter('userid', userId); - msg.setParameter('algo', algo ); - if (expires){ - msg.setParameter('expires', - Math.floor(expires.valueOf()/1000)); + /** + * Generates a new Key pair directly in gpg, and returns a GPGME_Key + * representing that Key. Please note that due to security concerns, + * secret Keys can not be deleted or exported from inside gpgme.js. + * + * @param {String} userId The user Id, e.g. 'Foo Bar ' + * @param {String} algo (optional) algorithm (and optionally key size) + * to be used. See {@link supportedKeyAlgos} below for supported + * values. + * @param {Date} expires (optional) Expiration date. If not set, + * expiration will be set to 'never' + * + * @return {Promise} + * @async + */ + this.generateKey = function (userId, algo = 'default', expires){ + if ( + typeof(userId) !== 'string' || + supportedKeyAlgos.indexOf(algo) < 0 || + (expires && !(expires instanceof Date)) + ){ + return Promise.reject(gpgme_error('PARAM_WRONG')); } - msg.post().then(function(response){ - me.getKeys(response.fingerprint, true).then( - // TODO make prepare_sync (second parameter) optional here. - function(result){ - resolve(result); - }, function(error){ - reject(error); - }); - }, function(error) { - reject(error); + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('createkey'); + msg.setParameter('userid', userId); + msg.setParameter('algo', algo ); + if (expires){ + msg.setParameter('expires', + Math.floor(expires.valueOf()/1000)); + } + msg.post().then(function(response){ + me.getKeys(response.fingerprint, true).then( + // TODO prepare_sync? + function(result){ + resolve(result); + }, function(error){ + reject(error); + }); + }, function(error) { + reject(error); + }); }); - }); - }; -} + }; + } } /** diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index cf9f84ef..c0b6ed57 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -61,162 +61,162 @@ export class GPGME_Message { return _msg.op; }; - /** - * The maximum size of responses from gpgme in bytes. As of July 2018, - * most browsers will only accept answers up to 1 MB of size. Everything - * above that threshold will not pass through nativeMessaging; answers that - * are larger need to be sent in parts. The lower limit is set to 10 KB. - * Messages smaller than the threshold will not encounter problems, larger - * messages will be received in chunks. - * If the value is not explicitly specified, 1023 KB is used. - */ - this.setChunksize = function (value){ - if ( - Number.isInteger(value) && - value > 10 * 1024 && - value <= 1024 * 1024 - ){ - _msg.chunksize = value; - } - }; + /** + * The maximum size of responses from gpgme in bytes. As of July 2018, + * most browsers will only accept answers up to 1 MB of size. + * Everything above that threshold will not pass through + * nativeMessaging; answers that are larger need to be sent in parts. + * The lower limit is set to 10 KB. Messages smaller than the threshold + * will not encounter problems, larger messages will be received in + * chunks. If the value is not explicitly specified, 1023 KB is used. + */ + this.setChunksize = function (value){ + if ( + Number.isInteger(value) && + value > 10 * 1024 && + value <= 1024 * 1024 + ){ + _msg.chunksize = value; + } + }; - this.getMsg = function(){ - return _msg; - }; + this.getMsg = function(){ + return _msg; + }; - this.getChunksize= function() { - return _msg.chunksize; - }; + this.getChunksize= function() { + return _msg.chunksize; + }; - /** - * Sets a parameter for the message. It validates with - * {@link permittedOperations} - * @param {String} param Parameter to set - * @param {any} value Value to set - * @returns {Boolean} If the parameter was set successfully - */ - this.setParameter = function ( param,value ){ - if (!param || typeof(param) !== 'string'){ - return gpgme_error('PARAM_WRONG'); - } - let po = permittedOperations[_msg.op]; - if (!po){ - return gpgme_error('MSG_WRONG_OP'); - } - let poparam = null; - if (po.required.hasOwnProperty(param)){ - poparam = po.required[param]; - } else if (po.optional.hasOwnProperty(param)){ - poparam = po.optional[param]; - } else { - return gpgme_error('PARAM_WRONG'); - } - // check incoming value for correctness - let checktype = function(val){ - switch(typeof(val)){ - case 'string': - if (poparam.allowed.indexOf(typeof(val)) >= 0 - && val.length > 0) { - return true; - } - return gpgme_error('PARAM_WRONG'); - case 'number': - if ( - poparam.allowed.indexOf('number') >= 0 - && isNaN(value) === false){ - return true; - } + /** + * Sets a parameter for the message. It validates with + * {@link permittedOperations} + * @param {String} param Parameter to set + * @param {any} value Value to set + * @returns {Boolean} If the parameter was set successfully + */ + this.setParameter = function ( param,value ){ + if (!param || typeof(param) !== 'string'){ return gpgme_error('PARAM_WRONG'); - - case 'boolean': - if (poparam.allowed.indexOf('boolean') >= 0){ - return true; - } + } + let po = permittedOperations[_msg.op]; + if (!po){ + return gpgme_error('MSG_WRONG_OP'); + } + let poparam = null; + if (po.required.hasOwnProperty(param)){ + poparam = po.required[param]; + } else if (po.optional.hasOwnProperty(param)){ + poparam = po.optional[param]; + } else { return gpgme_error('PARAM_WRONG'); - case 'object': - if (Array.isArray(val)){ - if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); - } - for (let i=0; i < val.length; i++){ - let res = checktype(val[i]); - if (res !== true){ - return res; - } + } + // check incoming value for correctness + let checktype = function(val){ + switch(typeof(val)){ + case 'string': + if (poparam.allowed.indexOf(typeof(val)) >= 0 + && val.length > 0) { + return true; } - if (val.length > 0) { + return gpgme_error('PARAM_WRONG'); + case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 + && isNaN(value) === false){ return true; } - } else if (val instanceof Uint8Array){ - if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return gpgme_error('PARAM_WRONG'); + + case 'boolean': + if (poparam.allowed.indexOf('boolean') >= 0){ return true; } return gpgme_error('PARAM_WRONG'); - } else { + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); + } + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + if (val.length > 0) { + return true; + } + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + } else { + return gpgme_error('PARAM_WRONG'); + } + break; + default: + return gpgme_error('PARAM_WRONG'); + } + }; + let typechecked = checktype(value); + if (typechecked !== true){ + return typechecked; + } + if (poparam.hasOwnProperty('allowed_data')){ + if (poparam.allowed_data.indexOf(value) < 0){ return gpgme_error('PARAM_WRONG'); } - break; - default: - return gpgme_error('PARAM_WRONG'); } + _msg[param] = value; + return true; }; - let typechecked = checktype(value); - if (typechecked !== true){ - return typechecked; - } - if (poparam.hasOwnProperty('allowed_data')){ - if (poparam.allowed_data.indexOf(value) < 0){ - return gpgme_error('PARAM_WRONG'); - } - } - _msg[param] = value; - return true; - }; - /** - * Check if the message has the minimum requirements to be sent, that is - * all 'required' parameters according to {@link permittedOperations}. - * @returns {Boolean} true if message is complete. - */ - this.isComplete = function(){ - if (!_msg.op){ - return false; - } - let reqParams = Object.keys( - permittedOperations[_msg.op].required); - let msg_params = Object.keys(_msg); - for (let i=0; i < reqParams.length; i++){ - if (msg_params.indexOf(reqParams[i]) < 0){ + /** + * Check if the message has the minimum requirements to be sent, that is + * all 'required' parameters according to {@link permittedOperations}. + * @returns {Boolean} true if message is complete. + */ + this.isComplete = function(){ + if (!_msg.op){ return false; } - } - return true; - }; - /** - * Sends the Message via nativeMessaging and resolves with the answer. - * @returns {Promise} - * @async - */ - this.post = function(){ - let me = this; - return new Promise(function(resolve, reject) { - if (me.isComplete() === true) { - - let conn = new Connection; - conn.post(me).then(function(response) { - resolve(response); - }, function(reason) { - reject(reason); - }); - } - else { - reject(gpgme_error('MSG_INCOMPLETE')); + let reqParams = Object.keys( + permittedOperations[_msg.op].required); + let msg_params = Object.keys(_msg); + for (let i=0; i < reqParams.length; i++){ + if (msg_params.indexOf(reqParams[i]) < 0){ + return false; + } } - }); - }; -} + return true; + }; + /** + * Sends the Message via nativeMessaging and resolves with the answer. + * @returns {Promise} + * @async + */ + this.post = function(){ + let me = this; + return new Promise(function(resolve, reject) { + if (me.isComplete() === true) { + + let conn = new Connection; + conn.post(me).then(function(response) { + resolve(response); + }, function(reason) { + reject(reason); + }); + } + else { + reject(gpgme_error('MSG_INCOMPLETE')); + } + }); + }; + } /** * Returns the prepared message with parameters and completeness checked @@ -231,11 +231,10 @@ export class GPGME_Message { return null; } } - -get operation(){ - return this.getOperation(); -} -get chunksize(){ - return this.getChunksize(); -} + get operation(){ + return this.getOperation(); + } + get chunksize(){ + return this.getChunksize(); + } } diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index ff4278ad..0ee58e94 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -84,65 +84,65 @@ class GPGME_Signature { constructor(sigObject){ let _rawSigObject = sigObject; - this.getFingerprint = function(){ - if (!_rawSigObject.fingerprint){ - return gpgme_error('SIG_WRONG'); - } else { - return _rawSigObject.fingerprint; - } - }; - - /** - * The expiration of this Signature as Javascript date, or null if - * signature does not expire - * @returns {Date | null} - */ - this.getExpiration = function(){ - if (!_rawSigObject.exp_timestamp){ - return null; - } - return new Date(_rawSigObject.exp_timestamp* 1000); - }; - - /** - * The creation date of this Signature in Javascript Date - * @returns {Date} - */ - this.getTimestamp= function (){ - return new Date(_rawSigObject.timestamp * 1000); - }; - - /** - * The overall validity of the key. If false, errorDetails may contain - * additional information - */ - this.getValid= function() { - if (_rawSigObject.summary.valid === true){ - return true; - } else { - return false; - } - }; - - /** - * gives more information on non-valid signatures. Refer to the gpgme docs - * https://www.gnupg.org/documentation/manuals/gpgme/Verify.html for - * details on the values - * @returns {Object} Object with boolean properties - */ - this.getErrorDetails = function (){ - let properties = ['revoked', 'key-expired', 'sig-expired', - 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', - 'sys-error']; - let result = {}; - for (let i=0; i< properties.length; i++){ - if ( _rawSigObject.hasOwnProperty(properties[i]) ){ - result[properties[i]] = _rawSigObject[properties[i]]; + this.getFingerprint = function(){ + if (!_rawSigObject.fingerprint){ + return gpgme_error('SIG_WRONG'); + } else { + return _rawSigObject.fingerprint; } - } - return result; - }; -} + }; + + /** + * The expiration of this Signature as Javascript date, or null if + * signature does not expire + * @returns {Date | null} + */ + this.getExpiration = function(){ + if (!_rawSigObject.exp_timestamp){ + return null; + } + return new Date(_rawSigObject.exp_timestamp* 1000); + }; + + /** + * The creation date of this Signature in Javascript Date + * @returns {Date} + */ + this.getTimestamp= function (){ + return new Date(_rawSigObject.timestamp * 1000); + }; + + /** + * The overall validity of the key. If false, errorDetails may contain + * additional information. + */ + this.getValid= function() { + if (_rawSigObject.summary.valid === true){ + return true; + } else { + return false; + } + }; + + /** + * gives more information on non-valid signatures. Refer to the gpgme + * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html + * for details on the values. + * @returns {Object} Object with boolean properties + */ + this.getErrorDetails = function (){ + let properties = ['revoked', 'key-expired', 'sig-expired', + 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', + 'sys-error']; + let result = {}; + for (let i=0; i< properties.length; i++){ + if ( _rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = _rawSigObject[properties[i]]; + } + } + return result; + }; + } /** * Convenience getter for {@link getFingerprint} diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index f587e854..720490d6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -107,204 +107,207 @@ export class GpgME { return _Keyring; }; - /** - * Encrypt (and optionally sign) data - * @param {String|Object} data text/data to be encrypted as String. Also - * accepts Objects with a getText method - * @param {inputKeys} publicKeys - * Keys used to encrypt the message - * @param {inputKeys} secretKeys (optional) Keys used to sign the message. - * If Keys are present, the operation requested is assumed to be 'encrypt - * and sign' - * @param {Boolean} base64 (optional) The data will be interpreted as - * base64 encoded data. - * @param {Boolean} armor (optional) Request the output as armored block. - * @param {Boolean} wildcard (optional) If true, recipient information will - * not be added to the message. - * @param {Object} additional use additional valid gpg options as defined - * in {@link permittedOperations} - * @returns {Promise} Object containing the encrypted - * message and additional info. - * @async - */ - this.encrypt = function (data, publicKeys, secretKeys, base64=false, - armor=true, wildcard=false, additional = {} - ){ - let msg = createMessage('encrypt'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - msg.setParameter('armor', armor); - msg.setParameter('always-trust', true); - if (base64 === true) { - msg.setParameter('base64', true); - } - let pubkeys = toKeyIdArray(publicKeys); - msg.setParameter('keys', pubkeys); - let sigkeys = toKeyIdArray(secretKeys); - if (sigkeys.length > 0) { - msg.setParameter('signing_keys', sigkeys); - } - putData(msg, data); - if (wildcard === true){ - msg.setParameter('throw-keyids', true); - } - if (additional){ - let additional_Keys = Object.keys(additional); - for (let k = 0; k < additional_Keys.length; k++) { - msg.setParameter(additional_Keys[k], - additional[additional_Keys[k]]); + /** + * Encrypt (and optionally sign) data + * @param {String|Object} data text/data to be encrypted as String. Also + * accepts Objects with a getText method + * @param {inputKeys} publicKeys + * Keys used to encrypt the message + * @param {inputKeys} secretKeys (optional) Keys used to sign the + * message. If Keys are present, the operation requested is assumed + * to be 'encrypt and sign' + * @param {Boolean} base64 (optional) The data will be interpreted as + * base64 encoded data. + * @param {Boolean} armor (optional) Request the output as armored + * block. + * @param {Boolean} wildcard (optional) If true, recipient information + * will not be added to the message. + * @param {Object} additional use additional valid gpg options as + * defined in {@link permittedOperations} + * @returns {Promise} Object containing the encrypted + * message and additional info. + * @async + */ + this.encrypt = function (data, publicKeys, secretKeys, base64=false, + armor=true, wildcard=false, additional = {} + ){ + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg); } - } - if (msg.isComplete() === true){ - return msg.post(); - } else { - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); - } - }; + msg.setParameter('armor', armor); + msg.setParameter('always-trust', true); + if (base64 === true) { + msg.setParameter('base64', true); + } + let pubkeys = toKeyIdArray(publicKeys); + msg.setParameter('keys', pubkeys); + let sigkeys = toKeyIdArray(secretKeys); + if (sigkeys.length > 0) { + msg.setParameter('signing_keys', sigkeys); + } + putData(msg, data); + if (wildcard === true){ + msg.setParameter('throw-keyids', true); + } + if (additional){ + let additional_Keys = Object.keys(additional); + for (let k = 0; k < additional_Keys.length; k++) { + msg.setParameter(additional_Keys[k], + additional[additional_Keys[k]]); + } + } + if (msg.isComplete() === true){ + return msg.post(); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + }; - /** - * Decrypts a Message - * @param {String|Object} data text/data to be decrypted. Accepts Strings - * and Objects with a getText method - * @param {Boolean} base64 (optional) false if the data is an armored block, - * true if it is base64 encoded binary data - * @returns {Promise} Decrypted Message and information - * @async - */ - this.decrypt = function (data, base64=false){ - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let msg = createMessage('decrypt'); + /** + * Decrypts a Message + * @param {String|Object} data text/data to be decrypted. Accepts + * Strings and Objects with a getText method + * @param {Boolean} base64 (optional) false if the data is an armored + * block, true if it is base64 encoded binary data + * @returns {Promise} Decrypted Message and information + * @async + */ + this.decrypt = function (data, base64=false){ + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let msg = createMessage('decrypt'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - if (base64 === true){ - msg.setParameter('base64', true); - } - putData(msg, data); - if (base64 === true){ - msg.setParameter('base64', true); - } - return new Promise(function(resolve, reject){ - msg.post().then(function(result){ - let _result = {data: result.data}; - _result.base64 = result.base64 ? true: false; - _result.is_mime = result.mime ? true: false; - if (result.file_name){ - _result.file_name = result.file_name; - } - if ( - result.hasOwnProperty('signatures') && - Array.isArray(result.signatures) - ) { - _result.signatures = collectSignatures(result.signatures); - } - resolve(_result); - }, function(error){ - reject(error); + if (msg instanceof Error){ + return Promise.reject(msg); + } + if (base64 === true){ + msg.setParameter('base64', true); + } + putData(msg, data); + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function(result){ + let _result = {data: result.data}; + _result.base64 = result.base64 ? true: false; + _result.is_mime = result.mime ? true: false; + if (result.file_name){ + _result.file_name = result.file_name; + } + if ( + result.hasOwnProperty('signatures') && + Array.isArray(result.signatures) + ) { + _result.signatures = collectSignatures( + result.signatures); + } + resolve(_result); + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * Sign a Message - * @param {String|Object} data text/data to be signed. Accepts Strings - * and Objects with a getText method. - * @param {inputKeys} keys The key/keys to use for signing - * @param {String} mode The signing mode. Currently supported: - * 'clearsign':The Message is embedded into the signature; - * 'detached': The signature is stored separately - * @param {Boolean} base64 input is considered base64 - * @returns {Promise} - * @async - */ - this.sign = function (data, keys, mode='clearsign', base64=false) { - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let key_arr = toKeyIdArray(keys); - if (key_arr.length === 0){ - return Promise.reject(gpgme_error('MSG_NO_KEYS')); - } - let msg = createMessage('sign'); + /** + * Sign a Message + * @param {String|Object} data text/data to be signed. Accepts Strings + * and Objects with a getText method. + * @param {inputKeys} keys The key/keys to use for signing + * @param {String} mode The signing mode. Currently supported: + * 'clearsign':The Message is embedded into the signature; + * 'detached': The signature is stored separately + * @param {Boolean} base64 input is considered base64 + * @returns {Promise} + * @async + */ + this.sign = function (data, keys, mode='clearsign', base64=false) { + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let key_arr = toKeyIdArray(keys); + if (key_arr.length === 0){ + return Promise.reject(gpgme_error('MSG_NO_KEYS')); + } + let msg = createMessage('sign'); - msg.setParameter('keys', key_arr); - if (base64 === true){ - msg.setParameter('base64', true); - } - msg.setParameter('mode', mode); - putData(msg, data); - return new Promise(function(resolve,reject) { - if (mode ==='detached'){ - msg.expect= 'base64'; + msg.setParameter('keys', key_arr); + if (base64 === true){ + msg.setParameter('base64', true); } - msg.post().then( function(message) { - if (mode === 'clearsign'){ - resolve({ - data: message.data} - ); - } else if (mode === 'detached') { - resolve({ - data: data, - signature: message.data - }); + msg.setParameter('mode', mode); + putData(msg, data); + return new Promise(function(resolve,reject) { + if (mode ==='detached'){ + msg.expect= 'base64'; } - }, function(error){ - reject(error); + msg.post().then( function(message) { + if (mode === 'clearsign'){ + resolve({ + data: message.data} + ); + } else if (mode === 'detached') { + resolve({ + data: data, + signature: message.data + }); + } + }, function(error){ + reject(error); + }); }); - }); - }; + }; - /** - * Verifies data. - * @param {String|Object} data text/data to be verified. Accepts Strings - * and Objects with a getText method - * @param {String} (optional) A detached signature. If not present, opaque - * mode is assumed - * @param {Boolean} (optional) Data and signature are base64 encoded - * @returns {Promise} - *@async - */ - this.verify= function (data, signature, base64 = false){ - let msg = createMessage('verify'); - let dt = putData(msg, data); - if (dt instanceof Error){ - return Promise.reject(dt); - } - if (signature){ - if (typeof(signature)!== 'string'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); - } else { - msg.setParameter('signature', signature); + /** + * Verifies data. + * @param {String|Object} data text/data to be verified. Accepts Strings + * and Objects with a getText method + * @param {String} (optional) A detached signature. If not present, + * opaque mode is assumed + * @param {Boolean} (optional) Data and signature are base64 encoded + * @returns {Promise} + *@async + */ + this.verify= function (data, signature, base64 = false){ + let msg = createMessage('verify'); + let dt = putData(msg, data); + if (dt instanceof Error){ + return Promise.reject(dt); } - } - if (base64 === true){ - msg.setParameter('base64', true); - } - return new Promise(function(resolve, reject){ - msg.post().then(function (message){ - if (!message.info || !message.info.signatures){ - reject(gpgme_error('SIG_NO_SIGS')); + if (signature){ + if (typeof(signature)!== 'string'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); } else { - let _result = collectSignatures(message.info.signatures); - _result.is_mime = message.info.is_mime? true: false; - if (message.info.filename){ - _result.file_name = message.info.filename; - } - _result.data = message.data; - resolve(_result); + msg.setParameter('signature', signature); } - }, function(error){ - reject(error); + } + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function (message){ + if (!message.info || !message.info.signatures){ + reject(gpgme_error('SIG_NO_SIGS')); + } else { + let _result = collectSignatures( + message.info.signatures); + _result.is_mime = message.info.is_mime? true: false; + if (message.info.filename){ + _result.file_name = message.info.filename; + } + _result.data = message.data; + resolve(_result); + } + }, function(error){ + reject(error); + }); }); - }); - }; -} + }; + } - /** + /** * setter for {@link setKeyring}. * @param {GPGME_Keyring} keyring A Keyring to use */ @@ -332,7 +335,7 @@ export class GpgME { * @private */ function putData(message, data){ - if (!message || !message instanceof GPGME_Message) { + if (!message || !(message instanceof GPGME_Message)) { return gpgme_error('PARAM_WRONG'); } if (!data){ -- cgit v1.2.3 From e16a87e83910ebb6bfdc4148369165f121f0997e Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 30 Jul 2018 12:31:27 +0200 Subject: js: Making objects inmutable -- * An Object.freeze should stop any malicious third party from changing objects' methods once the objects are instantiated (see unittest for an approach that would have worked before) - An initialized gpgmejs- object doesn't have a '_Keyring' property anymore (it still has its 'Keyring') - The internal expect='base64' needed to be turned into a method. --- lang/js/BrowserTestExtension/tests/startup.js | 1 - lang/js/src/Connection.js | 9 +++++---- lang/js/src/Errors.js | 6 +++--- lang/js/src/Key.js | 10 +++++----- lang/js/src/Message.js | 14 ++++++++++++-- lang/js/src/Signature.js | 2 +- lang/js/src/gpgmejs.js | 9 +++------ lang/js/src/index.js | 4 ++-- lang/js/unittests.js | 16 ++++++++++++++++ 9 files changed, 47 insertions(+), 24 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 1e2784d9..63358aa9 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -37,7 +37,6 @@ describe('GPGME context', function(){ context.Keyring = input; expect(context.Keyring).to.be.an('object'); expect(context.Keyring).to.not.equal(input); - expect(context._Keyring).to.equal(context.Keyring); expect(context.Keyring.getKeys).to.be.a('function'); expect(context.Keyring.getDefaultKey).to.be.a('function'); expect(context.Keyring.importKey).to.be.a('function'); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 561a5b70..b0105757 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -118,7 +118,7 @@ export class Connection{ } let chunksize = message.chunksize; return new Promise(function(resolve, reject){ - let answer = new Answer(message); + let answer = Object.freeze(new Answer(message)); let listener = function(msg) { if (!msg){ _connection.onMessage.removeListener(listener); @@ -188,14 +188,15 @@ class Answer{ */ constructor(message){ const operation = message.operation; - const expect = message.expect; + const expected = message.getExpect(); let response_b64 = null; this.getOperation = function(){ return operation; }; + this.getExpect = function(){ - return expect; + return expected; }; /** @@ -260,7 +261,7 @@ class Answer{ } if (_decodedResponse.base64 === true && poa.data[key] === 'string' - && this.getExpect() === undefined + && this.getExpect() !== 'base64' ){ _response[key] = decodeURIComponent( atob(_decodedResponse[key]).split('').map( diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 0cf1af19..39e3a74a 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -119,7 +119,7 @@ const err_list = { export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ - return new GPGME_Error(code); + return Object.freeze(new GPGME_Error(code)); } if (err_list[code].type === 'warning'){ // eslint-disable-next-line no-console @@ -127,10 +127,10 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ } return null; } else if (code === 'GNUPG_ERROR'){ - return new GPGME_Error(code, info); + return Object.freeze(new GPGME_Error(code, info)); } else { - return new GPGME_Error('GENERIC_ERROR'); + return Object.freeze(new GPGME_Error('GENERIC_ERROR')); } } diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index d5873a70..f431a283 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -37,7 +37,7 @@ export function createKey(fingerprint, async = false){ if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } - else return new GPGME_Key(fingerprint, async); + else return Object.freeze(new GPGME_Key(fingerprint, async)); } /** @@ -104,15 +104,15 @@ export class GPGME_Key { case 'subkeys': _data.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { - _data.subkeys.push( - new GPGME_Subkey(data.subkeys[i])); + _data.subkeys.push(Object.freeze( + new GPGME_Subkey(data.subkeys[i]))); } break; case 'userids': _data.userids = []; for (let i=0; i< data.userids.length; i++) { - _data.userids.push( - new GPGME_UserId(data.userids[i])); + _data.userids.push(Object.freeze( + new GPGME_UserId(data.userids[i]))); } break; case 'last_update': diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index c0b6ed57..e2c07344 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -36,7 +36,7 @@ export function createMessage(operation){ return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ - return new GPGME_Message(operation); + return Object.freeze(new GPGME_Message(operation)); } else { return gpgme_error('MSG_WRONG_OP'); } @@ -56,11 +56,21 @@ export class GPGME_Message { op: operation, chunksize: 1023* 1024 }; + let expected = null; this.getOperation = function(){ return _msg.op; }; + this.setExpect = function(value){ + if (value === 'base64'){ + expected = value; + } + }; + this.getExpect = function(){ + return expected; + }; + /** * The maximum size of responses from gpgme in bytes. As of July 2018, * most browsers will only accept answers up to 1 MB of size. @@ -204,7 +214,7 @@ export class GPGME_Message { return new Promise(function(resolve, reject) { if (me.isComplete() === true) { - let conn = new Connection; + let conn = Object.freeze(new Connection); conn.post(me).then(function(response) { resolve(response); }, function(reason) { diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index 0ee58e94..55131b01 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -66,7 +66,7 @@ export function createSignature(sigObject){ } } } - return new GPGME_Signature(sigObject); + return Object.freeze(new GPGME_Signature(sigObject)); } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 720490d6..9154979d 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -102,7 +102,7 @@ export class GpgME { */ this.getKeyring = function(){ if (!_Keyring){ - _Keyring = new GPGME_Keyring; + _Keyring = Object.freeze(new GPGME_Keyring); } return _Keyring; }; @@ -241,7 +241,7 @@ export class GpgME { putData(msg, data); return new Promise(function(resolve,reject) { if (mode ==='detached'){ - msg.expect= 'base64'; + msg.setExpect('base64'); } msg.post().then( function(message) { if (mode === 'clearsign'){ @@ -319,10 +319,7 @@ export class GpgME { * Accesses the {@link GPGME_Keyring}. */ get Keyring(){ - if (!this._Keyring){ - this._Keyring = new GPGME_Keyring; - } - return this._Keyring; + return this.getKeyring(); } } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index dc613fc7..2fed95f9 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -34,11 +34,11 @@ import { Connection } from './Connection'; */ function init(){ return new Promise(function(resolve, reject){ - let connection = new Connection; + let connection = Object.freeze(new Connection); connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(new GpgME()); + resolve(Object.freeze(new GpgME())); } else { reject(gpgme_error('CONN_NO_CONNECT')); } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 6228993b..3304b1eb 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -253,6 +253,22 @@ function unittests (){ expect(key.fingerprint.code).to.equal('KEY_INVALID'); } }); + + it('Overwriting getFingerprint does not work', function(){ + const evilFunction = function(){ + return 'bad Data'; + }; + let key = createKey(kp.validKeyFingerprint, true); + expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + try { + key.getFingerprint = evilFunction; + } + catch(e) { + expect(e).to.be.an.instanceof(TypeError); + } + expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + expect(key.getFingerprint).to.not.equal(evilFunction); + }); // TODO: tests for subkeys // TODO: tests for userids // TODO: some invalid tests for key/keyring -- cgit v1.2.3 From 9d247b7fd5edd11fb5710a057baec671276f5034 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 31 Jul 2018 16:54:43 +0200 Subject: js: Fix Key.hasSecret answer -- * The comparision result between Keyring and Keyring with secrets was set to the wrong Object which was not returned at all. --- lang/js/src/Key.js | 2 +- lang/js/src/Keyring.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index f431a283..88c49d3f 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -74,7 +74,7 @@ export class GPGME_Key { * @returns {Boolean} If the Key has a secret subkey. */ this.hasSecret= function (){ - return this.get('hasSecret', true); + return this.get('hasSecret'); }; /** diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 31c4f92b..8715a47d 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -46,7 +46,7 @@ export class GPGME_Keyring { * resort to a refresh() first. * @param {Boolean} search (optional) retrieve Keys from external * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) - * @returns {Promise.|GPGME_Error>} + * @returns {Promise>} * @static * @async */ @@ -79,9 +79,9 @@ export class GPGME_Keyring { }; } secondrequest().then(function(answer) { - for (let i=0; i < result.keys.length; i++){ + for (let i=0; i < answer.keys.length; i++){ if (prepare_sync === true){ - result.keys[i].hasSecret = false; + result.keys[i].hasSecret = undefined; if (answer && answer.keys) { for (let j=0; j < answer.keys.length; j++ ){ @@ -91,7 +91,9 @@ export class GPGME_Keyring { a.fingerprint === b.fingerprint ) { if (a.secret === true){ - a.hasSecret = true; + b.hasSecret = true; + } else { + b.hasSecret = false; } break; } -- cgit v1.2.3 From 6313a2de9ee84a9321292f775e4d6c790486d3dc Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 31 Jul 2018 17:35:52 +0200 Subject: js: fix confusion about loop in last commit -- * The aim is to iterate through the results of the first request (all keys), and then add the propert 'hasSecret' to those that are in the second request (secret Keysring) as well. I messed this up in a recent change, and it escaped testing. --- lang/js/src/Keyring.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 8715a47d..d25216c6 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -79,9 +79,8 @@ export class GPGME_Keyring { }; } secondrequest().then(function(answer) { - for (let i=0; i < answer.keys.length; i++){ + for (let i=0; i < result.keys.length; i++){ if (prepare_sync === true){ - result.keys[i].hasSecret = undefined; if (answer && answer.keys) { for (let j=0; j < answer.keys.length; j++ ){ -- cgit v1.2.3 From 68a012deb3b501d7417778be12c88bd475a37cb5 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 1 Aug 2018 12:51:12 +0200 Subject: js: make init export immutable -- * src/index.js: The export now uses a freezed Object, which does not allow for simply overwriting the init method by e.g. a third-party library. * BrowsertestExtension: Added some tests trying if decryption of bad data properly fails --- lang/js/BrowserTestExtension/browsertest.html | 1 + lang/js/BrowserTestExtension/tests/decryptTest.js | 62 +++++++++++++++++++++++ lang/js/BrowserTestExtension/tests/inputvalues.js | 36 +++++++++++++ lang/js/src/index.js | 7 ++- 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/decryptTest.js diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index de8cd41a..a20cfe19 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -19,6 +19,7 @@ + diff --git a/lang/js/BrowserTestExtension/tests/decryptTest.js b/lang/js/BrowserTestExtension/tests/decryptTest.js new file mode 100644 index 00000000..c6b3a3c5 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/decryptTest.js @@ -0,0 +1,62 @@ +/* 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+ + * + * Author(s): + * Maximilian Krambach + */ + +/* global describe, it, before, expect, Gpgmejs */ +/* global bigString, inputvalues, sabotageMsg*/ + +describe('Decryption', function () { + let context = null; + const good_fpr = inputvalues.encrypt.good.fingerprint; + + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); + + it('Decryption of random string fails', function (done) { + let data = bigString(20 * 1024); + context.decrypt(data).then( + function(){}, + function(error){ + expect(error).to.be.an('error'); + expect(error.code).to.equal('GNUPG_ERROR'); + done(); + }); + }); + + it('Decryption of slightly corrupted message fails', function (done) { + const data = bigString(10000); + context.encrypt(data, good_fpr).then(function(enc){ + context.decrypt(sabotageMsg(enc.data)).then( + function(){}, + function(error){ + expect(error).to.be.an('error'); + expect(error.code).to.equal('GNUPG_ERROR'); + done(); + }); + }); + }).timeout(5000); +}); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 9d956b69..1e8f1544 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -248,3 +248,39 @@ const ImportablePublicKey = {// eslint-disable-line no-unused-vars '=9WZ7\n' + '-----END PGP PUBLIC KEY BLOCK-----\n' }; + +/** + * Changes base64 encoded gpg messages + * @param {String} msg input message + * @param {Number} rate of changes as percentage of message length. + * @param {[Number, Number]} p begin and end of the message left untouched (to + * preserve) header/footer + */ +// eslint-disable-next-line no-unused-vars +function sabotageMsg(msg, rate = 0.01, p= [35,35]){ + const iterations = Math.floor(Math.random() * msg.length * rate) + 1; + const base64_set = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'; + for (let i=0; i < iterations; i++){ + let str0, str1, str2; + const chosePosition = function(){ + let position = + Math.floor( Math.random() * (msg.length - p[0] + p[1])) + + p[0]; + str1 = msg.substring(position,position+1); + if (str1 === '\n'){ + chosePosition(); + } else { + str0 = msg.substring(0,position); + str2 = msg.substring(position +1); + } + }; + chosePosition(); + let new1 = function(){ + let n = base64_set[Math.floor(Math.random() * 64)]; + return (n === str1) ? new1() : n; + }; + msg = str0.concat(new1()).concat(str2); + } + return msg; +} diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 2fed95f9..51f07538 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -34,7 +34,7 @@ import { Connection } from './Connection'; */ function init(){ return new Promise(function(resolve, reject){ - let connection = Object.freeze(new Connection); + const connection = Object.freeze(new Connection); connection.checkConnection(false).then( function(result){ if (result === true) { @@ -48,6 +48,5 @@ function init(){ }); } -export default { - init: init -}; \ No newline at end of file +const exportvalue = Object.freeze({init:init}); +export default exportvalue; \ No newline at end of file -- cgit v1.2.3 From 622db0d1de665dfd93c991cd2d517078b04b3a13 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 11:25:50 +0200 Subject: js: consistently return uppercase fingerprint -- * src/Key.js: the fingerprint returned by a Key is now always upper case hex, even if the constructor had lower case input. This is to be more consistent with gpgme and to be more readable and reliable in comparisions. --- lang/js/BrowserTestExtension/browsertest.html | 1 + lang/js/BrowserTestExtension/tests/KeyInfos.js | 46 +++++++++++++++++++++++ lang/js/BrowserTestExtension/tests/inputvalues.js | 1 + lang/js/src/Key.js | 5 ++- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 lang/js/BrowserTestExtension/tests/KeyInfos.js diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html index a20cfe19..0d3e2936 100644 --- a/lang/js/BrowserTestExtension/browsertest.html +++ b/lang/js/BrowserTestExtension/browsertest.html @@ -15,6 +15,7 @@ + diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js new file mode 100644 index 00000000..03773a44 --- /dev/null +++ b/lang/js/BrowserTestExtension/tests/KeyInfos.js @@ -0,0 +1,46 @@ +/* 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+ + * + * Author(s): + * Maximilian Krambach + */ + +/* global describe, it, expect, before, Gpgmejs */ +/* global inputvalues, fixedLengthString */ + +describe('Key information', function () { + let context = null; + before(function(done){ + const prm = Gpgmejs.init(); + prm.then(function(gpgmejs){ + context = gpgmejs; + done(); + }); + }); + + it('A fingerprint is consistently returned upper case hex', function(done){ + const mixedCase = inputvalues.encrypt.good.fingerprint_mixedcase; + context.Keyring.getKeys(mixedCase).then(function(result){ + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].fingerprint).to.equal(mixedCase.toUpperCase()); + done(); + }); + }); +}); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 1e8f1544..7dda47cd 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -28,6 +28,7 @@ const inputvalues = {// eslint-disable-line no-unused-vars // Fingerprint of a key that has been imported to gnupg // (i.e. see testkey.pub; testkey.sec) fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05', + fingerprint_mixedcase: 'D41735B91236fdb882048C5A2301635eeFF0Cb05', data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день', // used for checking encoding consistency in > 2MB messages. diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 88c49d3f..eeb27035 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -58,7 +58,7 @@ export class GPGME_Key { */ this.isAsync = async; - let _data = {fingerprint: fingerprint}; + let _data = {fingerprint: fingerprint.toUpperCase()}; this.getFingerprint = function(){ if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ return gpgme_error('KEY_INVALID'); @@ -88,7 +88,8 @@ export class GPGME_Key { if (typeof(data) !== 'object') { return gpgme_error('KEY_INVALID'); } - if (!data.fingerprint || data.fingerprint !== _data.fingerprint){ + if (!data.fingerprint || + data.fingerprint.toUpperCase() !== _data.fingerprint){ return gpgme_error('KEY_INVALID'); } let keys = Object.keys(data); -- cgit v1.2.3 From aeb065acc91a22b6548ebf0a558951ed26398214 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 11:29:10 +0200 Subject: js: simplify getDefaultKey -- * src/Keyring.js: In case no default Key is set in configuration, only Keys reported as having a secret part should be considered for default Keys, avoiding some extra requests. --- lang/js/src/Keyring.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d25216c6..c4b89b2e 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -192,17 +192,13 @@ export class GPGME_Keyring { reject(error); }); } else { - // TODO: this is overly 'expensive' in communication - // and probably performance, too - me.getKeys(null,true).then(function(keys){ - for (let i=0; i < keys.length; i++){ - if (keys[i].get('hasSecret') === true){ - resolve(keys[i]); - break; - } - if (i === keys.length -1){ - reject(gpgme_error('KEY_NO_DEFAULT')); - } + let msg = createMessage('keylist'); + msg.setParameter('secret', true); + msg.post().then(function(result){ + if (result.keys.length === 0){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } else { + resolve(result.keys[0]); } }, function(error){ reject(error); -- cgit v1.2.3 From 715cdc0d7d5bc8d39ff3cc49774c59e5db01c1b6 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 12:03:30 +0200 Subject: js: get default key fixes -- * src/Keyring.js: The answer was not parsed correctly, so a config was being ignored. * If no config is set, we return the first non-invalid key with a secret, instead of the first key (which may be e.g. an expired one) --- lang/js/src/Keyring.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index c4b89b2e..9fdd53b9 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -176,12 +176,13 @@ export class GPGME_Keyring { let msg = createMessage('config_opt'); msg.setParameter('component', 'gpg'); msg.setParameter('option', 'default-key'); - msg.post().then(function(response){ - if (response.value !== undefined - && response.value.hasOwnProperty('string') - && typeof(response.value.string) === 'string' - ){ - me.getKeys(response.value.string,true).then( + msg.post().then(function(resp){ + if (resp.option !== undefined + && resp.option.hasOwnProperty('value') + && resp.option.value.length === 1 + && resp.option.value[0].hasOwnProperty('string') + && typeof(resp.option.value[0].string) === 'string'){ + me.getKeys(resp.option.value[0].string, true).then( function(keys){ if(keys.length === 1){ resolve(keys[0]); @@ -198,7 +199,14 @@ export class GPGME_Keyring { if (result.keys.length === 0){ reject(gpgme_error('KEY_NO_DEFAULT')); } else { - resolve(result.keys[0]); + for (let i=0; i< result.keys.length; i++ ) { + if (result.keys[i].get('invalid') === false) { + resolve(result.keys[i]); + break; + } else if (i === result.keys.length - 1){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } + } } }, function(error){ reject(error); -- cgit v1.2.3 From 43cff5136459c5bca4dca66772eb815f5761c6cd Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 12:13:10 +0200 Subject: js: wrong object assumed in recent commit -- * src/Keyring.js I wrongly assumed an object to be a GPGME_Key, it was the raw answer from nativeMessaging instead. Now it returns a GPGME_Key again. --- lang/js/src/Keyring.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 9fdd53b9..93923c6f 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -200,8 +200,11 @@ export class GPGME_Keyring { reject(gpgme_error('KEY_NO_DEFAULT')); } else { for (let i=0; i< result.keys.length; i++ ) { - if (result.keys[i].get('invalid') === false) { - resolve(result.keys[i]); + if (result.keys[i].invalid === false) { + let k = createKey( + result.keys[i].fingerprint); + k.setKeyData(result.keys[i]); + resolve(k); break; } else if (i === result.keys.length - 1){ reject(gpgme_error('KEY_NO_DEFAULT')); -- cgit v1.2.3 From ea43158d4043b01058afd7411c84aa38b61c2009 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 14:40:53 +0200 Subject: js: avoid async getters -- * src/Key.js get armored was returning a promise on async keys. As getters should not do that, it returns an error in this case. --- lang/js/src/Key.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index eeb27035..aa419053 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -303,7 +303,11 @@ export class GPGME_Key { * @returns {String|GPGME_Error} The armored public Key block. */ get armored(){ - return this.get('armored', true); + if (this.isAsync === true){ + return gpgme_error('KEY_NO_INIT'); + } else { + return this.get('armored'); + } } } @@ -388,15 +392,6 @@ class GPGME_UserId { setProperty(keys[i], data[keys[i]]); } - /** - * Validates a subkey property against {@link validUserIdProperties} and - * sets it if validation is successful - * @param {String} property - * @param {*} value - * @param private - */ - - /** * Fetches information about the user * @param {String} property Information to request -- cgit v1.2.3 From d65a392670888f86071ca629266ec14b7afb0e46 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 17:07:29 +0200 Subject: js: fix import feedback -- * src/Keyring.js For Key imports without prepare_sync the import feedback was lacking the summary --- lang/js/src/Keyring.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 93923c6f..eb4f60f5 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -327,7 +327,15 @@ export class GPGME_Keyring { status: infos[fprs[i]].status }); } - resolve(resultset); + let summary = {}; + for (let i=0; i < feedbackValues.length; i++ ){ + summary[feedbackValues[i]] = + response[feedbackValues[i]]; + } + resolve({ + Keys:resultset, + summary:summary + }); } }, function(error){ -- cgit v1.2.3 From 90cb4a684211fe5630f209ba61510e8be3129eae Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 16 Aug 2018 17:58:11 +0200 Subject: js: importKey feedback refactor -- * src/Keyring.js: An empty result should no longer cause an error, the import feedback summary has been refactored slightly * Browsertests to reflect import feedback change --- .../BrowserTestExtension/tests/KeyImportExport.js | 34 ++++++++++++---------- lang/js/src/Keyring.js | 31 +++++++++----------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index d2fa2d38..6d0ba106 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -71,10 +71,13 @@ describe('Key importing', function () { expect(result).to.be.an('array'); expect(result.length).to.equal(0); context.Keyring.importKey(pubKey).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(fpr); - expect(result[0].status).to.equal('newkey'); + expect(result.Keys).to.be.an('array'); + expect(result.Keys[0]).to.not.be.undefined; + expect(result.Keys[0].key).to.be.an('object'); + expect(result.Keys[0].key.fingerprint).to.equal(fpr); + expect(result.Keys[0].status).to.equal('newkey'); + expect(result.summary.considered).to.equal(1); + expect(result.summary.imported).to.equal(1); done(); }); }); @@ -83,15 +86,16 @@ describe('Key importing', function () { it('Updating Key', function(done){ context.Keyring.importKey(pubKey) .then(function(result){ - expect(result[0].key).to.not.be.undefined; - expect(result[0].status).to.equal('newkey'); + expect(result.Keys[0].key).to.not.be.undefined; + expect(result.Keys[0].status).to.equal('newkey'); context.Keyring.importKey(changedKey).then(function(res){ - expect(res[0].key).to.be.an('object'); - expect(res[0].key.fingerprint).to.equal(fpr); - 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; + expect(res.Keys[0].key).to.be.an('object'); + expect(res.Keys[0].key.fingerprint).to.equal(fpr); + expect(res.Keys[0].status).to.equal('change'); + expect(res.Keys[0].changes.userId).to.be.true; + expect(res.Keys[0].changes.subkey).to.be.false; + expect(res.Keys[0].changes.signature).to.be.true; + expect(res.summary.considered).to.equal(1); done(); }); }); @@ -99,9 +103,9 @@ describe('Key importing', function () { it('Deleting Key', function(done) { context.Keyring.importKey(pubKey).then(function(result){ - expect(result[0].key).to.be.an('object'); - expect(result[0].key.fingerprint).to.equal(fpr); - result[0].key.delete().then(function(result){ + expect(result.Keys[0].key).to.be.an('object'); + expect(result.Keys[0].key.fingerprint).to.equal(fpr); + result.Keys[0].key.delete().then(function(result){ expect(result).to.be.true; done(); }); diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index eb4f60f5..a6787986 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -274,6 +274,17 @@ export class GPGME_Keyring { msg.post().then(function(response){ let infos = {}; let fprs = []; + let summary = {}; + for (let i=0; i < feedbackValues.length; i++ ){ + summary[feedbackValues[i]] = + response.result[feedbackValues[i]]; + } + if (!response.result.hasOwnProperty('imports') || + response.result.imports.length === 0 + ){ + resolve({Keys:[],summary: summary}); + return; + } for (let res=0; res Date: Fri, 17 Aug 2018 14:40:27 +0200 Subject: js: disallow bulk set data on key from outside -- * src/Key.js Key class is not exported anymore, as it should not be used directly anywhere. setKeyData is no more a method of the Key, (optional) data are now validated and set on Key creation and on updates, both from within this module, thus no longer exposing setKeyData to the outside. * createKey now gained an optional parameter which allows to set Key data at this point. --- lang/js/src/Helpers.js | 3 +- lang/js/src/Key.js | 142 ++++++++++++++++++++++++++++--------------------- lang/js/src/Keyring.js | 11 ++-- 3 files changed, 89 insertions(+), 67 deletions(-) diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index 0fd14994..accc2af5 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -22,7 +22,6 @@ */ import { gpgme_error } from './Errors'; -import { GPGME_Key } from './Key'; /** * Tries to return an array of fingerprints, either from input fingerprints or @@ -50,7 +49,7 @@ export function toKeyIdArray(input){ } } else if (typeof(input[i]) === 'object'){ let fpr = ''; - if (input[i] instanceof GPGME_Key){ + if (input[i].hasOwnProperty('fingerprint')){ fpr = input[i].fingerprint; } else if (input[i].hasOwnProperty('primaryKey') && input[i].primaryKey.hasOwnProperty('getFingerprint')){ diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index aa419053..8d7fd948 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -31,13 +31,22 @@ import { createMessage } from './Message'; * @param {Boolean} async If True, Key properties (except fingerprint) will be * queried from gnupg on each call, making the operation up-to-date, the * answers will be Promises, and the performance will likely suffer - * @returns {GPGME_Key|GPGME_Error} + * @param {Object} data additional initial properties this Key will have. Needs + * a full object as delivered by gpgme-json + * @returns {Object|GPGME_Error} The verified and updated data */ -export function createKey(fingerprint, async = false){ +export function createKey(fingerprint, async = false, data){ if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } - else return Object.freeze(new GPGME_Key(fingerprint, async)); + if (data !== undefined){ + data = validateKeyData(data); + } + if (data instanceof Error){ + return gpgme_error('KEY_INVALID'); + } else { + return Object.freeze(new GPGME_Key(fingerprint, async, data)); + } } /** @@ -49,9 +58,9 @@ export function createKey(fingerprint, async = false){ * * @class */ -export class GPGME_Key { +class GPGME_Key { - constructor(fingerprint, async){ + constructor(fingerprint, async, data){ /** * @property {Boolean} If true, most answers will be asynchronous @@ -59,6 +68,11 @@ export class GPGME_Key { this.isAsync = async; let _data = {fingerprint: fingerprint.toUpperCase()}; + if (data !== undefined + && data.fingerprint.toUpperCase() === _data.fingerprint + ) { + _data = data; + } this.getFingerprint = function(){ if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ return gpgme_error('KEY_INVALID'); @@ -77,54 +91,6 @@ export class GPGME_Key { return this.get('hasSecret'); }; - /** - * @param {Object} data Bulk set the data for this key, with an Object - * sent by gpgme-json. - * @returns {GPGME_Key|GPGME_Error} Itself after values have been set, - * an error if something went wrong. - * @private - */ - this.setKeyData = function (data){ - if (typeof(data) !== 'object') { - return gpgme_error('KEY_INVALID'); - } - if (!data.fingerprint || - data.fingerprint.toUpperCase() !== _data.fingerprint){ - return gpgme_error('KEY_INVALID'); - } - let keys = Object.keys(data); - for (let i=0; i< keys.length; i++){ - if (!validKeyProperties.hasOwnProperty(keys[i])){ - return gpgme_error('KEY_INVALID'); - } - //running the defined validation function - if (validKeyProperties[keys[i]](data[keys[i]]) !== true ){ - return gpgme_error('KEY_INVALID'); - } - switch (keys[i]){ - case 'subkeys': - _data.subkeys = []; - for (let i=0; i< data.subkeys.length; i++) { - _data.subkeys.push(Object.freeze( - new GPGME_Subkey(data.subkeys[i]))); - } - break; - case 'userids': - _data.userids = []; - for (let i=0; i< data.userids.length; i++) { - _data.userids.push(Object.freeze( - new GPGME_UserId(data.userids[i]))); - } - break; - case 'last_update': - _data[keys[i]] = new Date( data[keys[i]] * 1000 ); - break; - default: - _data[keys[i]] = data[keys[i]]; - } - } - return this; - }; /** * Query any property of the Key listed in {@link validKeyProperties} @@ -188,16 +154,22 @@ export class GPGME_Key { msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ if (result.keys.length === 1){ - me.setKeyData(result.keys[0]); - me.getHasSecret().then(function(){ - me.getArmor().then(function(){ - resolve(me); + const newdata = validateKeyData( + _data.fingerprint, result.keys[0]); + if (newdata instanceof Error){ + reject(gpgme_error('KEY_INVALID')); + } else { + _data = newdata; + me.getHasSecret().then(function(){ + me.getArmor().then(function(){ + resolve(me); + }, function(error){ + reject(error); + }); }, function(error){ reject(error); }); - }, function(error){ - reject(error); - }); + } } else { reject(gpgme_error('KEY_NOKEY')); } @@ -602,3 +574,53 @@ const validKeyProperties = { } }; + +/** +* sets the Key data in bulk. It can only be used from inside a Key, either +* during construction or on a refresh callback. +* @param {Object} key the original internal key data. +* @param {Object} data Bulk set the data for this key, with an Object structure +* as sent by gpgme-json. +* @returns {Object|GPGME_Error} the changed data after values have been set, +* an error if something went wrong. +* @private +*/ +function validateKeyData(data){ + const key = {}; + if ( typeof(data) !== 'object' + || !data.fingerprint){ + return gpgme_error('KEY_INVALID'); + } + let props = Object.keys(data); + for (let i=0; i< props.length; i++){ + if (!validKeyProperties.hasOwnProperty(props[i])){ + return gpgme_error('KEY_INVALID'); + } + // running the defined validation function + if (validKeyProperties[props[i]](data[props[i]]) !== true ){ + return gpgme_error('KEY_INVALID'); + } + switch (props[i]){ + case 'subkeys': + key.subkeys = []; + for (let i=0; i< data.subkeys.length; i++) { + key.subkeys.push(Object.freeze( + new GPGME_Subkey(data.subkeys[i]))); + } + break; + case 'userids': + key.userids = []; + for (let i=0; i< data.userids.length; i++) { + key.userids.push(Object.freeze( + new GPGME_UserId(data.userids[i]))); + } + break; + case 'last_update': + key[props[i]] = new Date( data[props[i]] * 1000 ); + break; + default: + key[props[i]] = data[props[i]]; + } + } + return key; +} \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index a6787986..43ab96c8 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -100,8 +100,8 @@ export class GPGME_Keyring { // TODO getArmor() to be used in sync } } - let k = createKey(result.keys[i].fingerprint); - k.setKeyData(result.keys[i]); + let k = createKey(result.keys[i].fingerprint, + !prepare_sync, result.keys[i]); resultset.push(k); } resolve(resultset); @@ -170,7 +170,7 @@ export class GPGME_Keyring { * @async * @static */ - this.getDefaultKey = function() { + this.getDefaultKey = function(prepare_sync = false) { let me = this; return new Promise(function(resolve, reject){ let msg = createMessage('config_opt'); @@ -202,8 +202,9 @@ export class GPGME_Keyring { for (let i=0; i< result.keys.length; i++ ) { if (result.keys[i].invalid === false) { let k = createKey( - result.keys[i].fingerprint); - k.setKeyData(result.keys[i]); + result.keys[i].fingerprint, + !prepare_sync, + result.keys[i]); resolve(k); break; } else if (i === result.keys.length - 1){ -- cgit v1.2.3 From ad39d54d192864b54a155bf5f94d5b6bb3e8612a Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 16:57:41 +0200 Subject: js: removed Key.armor property in synchronous use -- * src/Key.js The synchronous mode for a Key does not offer an armor/ armored property anymore. This frees up a lot of performance issues, also the armored expoort is expected to change quite often, so a cached version is not advisable. * hasSecret/getHasSecret is now refactored, to reflect their uses. With get('hasSecret') there is a method that fetches the result. * src/Key.js also some refactoring --- lang/js/src/Errors.js | 4 + lang/js/src/Key.js | 218 ++++++++++++++++++++++++++++++------------------- lang/js/src/Keyring.js | 12 ++- lang/js/unittests.js | 4 - 4 files changed, 145 insertions(+), 93 deletions(-) diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 39e3a74a..b22eca73 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -81,6 +81,10 @@ const err_list = { msg:'This property has not been retrieved yet from GPG', type: 'error' }, + 'KEY_ASYNC_ONLY': { + msg: 'This property cannot be used in synchronous calls', + type: 'error' + }, 'KEY_NO_DEFAULT': { msg:'A default key could not be established. Please check yout gpg ' + 'configuration', diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 8d7fd948..5d0c8160 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -80,55 +80,31 @@ class GPGME_Key { return _data.fingerprint; }; - /** - * Property indicating if the Key possesses a private/secret part. If - * this information is not yet cached, it returns an - * {@link GPGME_Error} with code 'KEY_NO_INIT'. Running - * {@link refreshKey} may help in this case. - * @returns {Boolean} If the Key has a secret subkey. - */ - this.hasSecret= function (){ - return this.get('hasSecret'); - }; - - /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @returns {*|Promise<*>} the value (Boolean, String, Array, Object). - * If 'cached' is false, the value will be resolved as a Promise. + * @returns {Boolean| String | Date | Array | Object |GPGME_Error} + * the value of the property. If the Key is set to Async, the value + * will be fetched from gnupg and resolved as a Promise. If Key is not + * async, the armored property is not available (it can still be + * retrieved asynchronously by {@link Key.getArmor}) */ this.get = function(property) { if (this.isAsync === true) { - let me = this; - return new Promise(function(resolve, reject) { - if (property === 'armored'){ - resolve(me.getArmor()); - } else if (property === 'hasSecret'){ - resolve(me.getHasSecret()); - } else if (validKeyProperties.hasOwnProperty(property)){ - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys && result.keys.length === 1 && - result.keys[0].hasOwnProperty(property)){ - resolve(result.keys[0][property]); - } else { - reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); - } - }, function(error){ - reject(gpgme_error(error)); - }); - } else { - reject(gpgme_error('PARAM_WRONG')); - } - }); + switch (property){ + case 'armored': + return this.getArmor(); + case 'hasSecret': + return this.getGnupgSecretState(); + default: + return getGnupgState(property); + } } else { + if (property === 'armored') { + return gpgme_error('KEY_ASYNC_ONLY'); + } if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); - } - if (!_data.hasOwnProperty(property)){ - return gpgme_error('KEY_NO_INIT'); } else { return (_data[property]); } @@ -160,7 +136,7 @@ class GPGME_Key { reject(gpgme_error('KEY_INVALID')); } else { _data = newdata; - me.getHasSecret().then(function(){ + me.getGnupgSecretState().then(function(){ me.getArmor().then(function(){ resolve(me); }, function(error){ @@ -195,7 +171,6 @@ class GPGME_Key { msg.setParameter('armor', true); msg.setParameter('keys', _data.fingerprint); msg.post().then(function(result){ - _data.armored = result.data; resolve(result.data); }, function(error){ reject(error); @@ -205,37 +180,38 @@ class GPGME_Key { /** * Find out if the Key includes a secret part. Note that this is a - * rather nonperformant operation, as it needs to query gnupg twice. + * rather nonperformant operation. * If you want this inforrmation about more than a few Keys, it may be * advisable to run {@link Keyring.getKeys} instead. * @returns {Promise} True if a secret/private Key * is available in the gnupg Keyring * @async */ - this.getHasSecret = function (){ + this.getGnupgSecretState = function (){ return new Promise(function(resolve, reject) { if (!_data.fingerprint){ reject(gpgme_error('KEY_INVALID')); + } else { + let msg = createMessage('keylist'); + msg.setParameter('keys', _data.fingerprint); + msg.setParameter('secret', true); + msg.post().then(function(result){ + _data.hasSecret = null; + if ( + result.keys && + result.keys.length === 1 && + result.keys[0].secret === true + ) { + _data.hasSecret = true; + resolve(true); + } else { + _data.hasSecret = false; + resolve(false); + } + }, function(error){ + reject(error); + }); } - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.setParameter('secret', true); - msg.post().then(function(result){ - _data.hasSecret = null; - if ( - result.keys && - result.keys.length === 1 && - result.keys[0].secret === true - ) { - _data.hasSecret = true; - resolve(true); - } else { - _data.hasSecret = false; - resolve(false); - } - }, function(error){ - reject(error); - }); }); }; @@ -262,25 +238,11 @@ class GPGME_Key { } /** - * @returns {String} The fingerprint defining this Key + * @returns {String} The fingerprint defining this Key. Convenience getter */ get fingerprint(){ return this.getFingerprint(); } - - /** - * Property for the export of armored Key. If the armored Key is not - * cached, it returns an {@link GPGME_Error} with code 'KEY_NO_INIT'. - * Running {@link refreshKey} may help in this case. - * @returns {String|GPGME_Error} The armored public Key block. - */ - get armored(){ - if (this.isAsync === true){ - return gpgme_error('KEY_NO_INIT'); - } else { - return this.get('armored'); - } - } } /** @@ -496,7 +458,31 @@ const validSubKeyProperties = { /** * Validation definition for Keys. Each valid Key property is represented - * as a key-value pair, with their value being a validation function + * as a key-value pair, with their value being a validation function. For + * details on the meanings, please refer to the gpgme documentation + * https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#Key-objects + * @param {String} fingerprint + * @param {Boolean} revoked + * @param {Boolean} expired + * @param {Boolean} disabled + * @param {Boolean} invalid + * @param {Boolean} can_encrypt + * @param {Boolean} can_sign + * @param {Boolean} can_certify + * @param {Boolean} can_authenticate + * @param {Boolean} secret + * @param {Boolean}is_qualified + * @param {String} protocol + * @param {String} issuer_serial + * @param {String} issuer_name + * @param {Boolean} chain_id + * @param {String} owner_trust + * @param {Date} last_update + * @param {String} origin + * @param {Array} subkeys + * @param {Array} userids + * @param {Array} tofu + * @param {Boolean} hasSecret * @protected * @const */ @@ -504,9 +490,6 @@ const validKeyProperties = { 'fingerprint': function(value){ return isFingerprint(value); }, - 'armored': function(value){ - return typeof(value === 'string'); - }, 'revoked': function(value){ return typeof(value) === 'boolean'; }, @@ -623,4 +606,75 @@ function validateKeyData(data){ } } return key; +} + +/** + * Fetches and sets properties from gnupg + * @param {String} fingerprint + * @param {String} property to search for. + * @private + * @async + */ +function getGnupgState (fingerprint, property){ + return new Promise(function(resolve, reject) { + if (!isFingerprint(fingerprint)) { + reject(gpgme_error('KEY_INVALID')); + } else { + let msg = createMessage('keylist'); + msg.setParameter('keys', fingerprint); + msg.post().then(function(result){ + if (!result.keys || result.keys.length !== 1){ + reject(gpgme_error('KEY_INVALID')); + } else { + const key = result.keys[0]; + let result; + switch (property){ + case 'subkeys': + result = []; + if (key.subkeys.length){ + for (let i=0; i < key.subkeys.length; i++) { + result.push(Object.freeze( + new GPGME_Subkey(key.subkeys[i]))); + } + } + resolve(result); + break; + case 'userids': + result = []; + if (key.userids.length){ + for (let i=0; i< key.userids.length; i++) { + result.push(Object.freeze( + new GPGME_UserId(key.userids[i]))); + } + } + resolve(result); + break; + case 'last_update': + if (key.last_update === undefined){ + reject(gpgme_error('CONN_UNEXPECTED_ANSWER')); + } else if (key.last_update !== null){ + resolve(new Date( key.last_update * 1000)); + } else { + resolve(null); + } + break; + default: + if (!validKeyProperties.hasOwnProperty(property)){ + reject(gpgme_error('PARAM_WRONG')); + } else { + if (key.hasOwnProperty(property)){ + resolve(key[property]); + } else { + reject(gpgme_error( + 'CONN_UNEXPECTED_ANSWER')); + } + } + break; + } + } + }, function(error){ + reject(gpgme_error(error)); + }); + } + }); } \ No newline at end of file diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 43ab96c8..766bab15 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -38,12 +38,11 @@ export class GPGME_Keyring { * * @param {String | Array} pattern (optional) A pattern to * search for in userIds or KeyIds. - * @param {Boolean} prepare_sync (optional) if set to true, the - * 'hasSecret' and 'armored' properties will be fetched for the Keys as - * well. These require additional calls to gnupg, resulting in a - * performance hungry operation. Calling them here enables direct, - * synchronous use of these properties for all keys, without having to - * resort to a refresh() first. + * @param {Boolean} prepare_sync (optional) if set to true, most data + * (with the exception of armored Key blocks) will be cached for the + * Keys. This enables direct, synchronous use of these properties for + * all keys. It does not check for changes on the backend. The cached + * information can be updated with the {@link Key.refresh} method. * @param {Boolean} search (optional) retrieve Keys from external * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) * @returns {Promise>} @@ -97,7 +96,6 @@ export class GPGME_Keyring { break; } } - // TODO getArmor() to be used in sync } } let k = createKey(result.keys[i].fingerprint, diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 3304b1eb..25023bcb 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -304,10 +304,6 @@ function unittests (){ expect(result).to.be.an('array'); expect(result[0]).to.be.an.instanceof(GPGME_Key); expect(result[0].get('hasSecret')).to.be.a('boolean'); - // TODO: preparing sync for armored is still in discussion - // expect(result[0].get('armored')).to.be.a('string'); - // expect(result[0].get('armored')).to.include( - // '-----END PGP PUBLIC KEY BLOCK-----'); done(); } ); -- cgit v1.2.3 From 3fb094a9b8c320fc10e537a9bb5fab34807f4e52 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 17:14:51 +0200 Subject: js: small documentation fix -- --- lang/js/src/Key.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 5d0c8160..ea6fd883 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -179,12 +179,13 @@ class GPGME_Key { }; /** - * Find out if the Key includes a secret part. Note that this is a - * rather nonperformant operation. - * If you want this inforrmation about more than a few Keys, it may be - * advisable to run {@link Keyring.getKeys} instead. - * @returns {Promise} True if a secret/private Key - * is available in the gnupg Keyring + * Find out if the Key is part of a Key pair including public and + * private key(s). If you want this information about more than a few + * Keys in synchronous mode, it may be advisable to run + * {@link Keyring.getKeys} instead, as it performs faster in bulk + * querying this state. + * @returns {Promise} True if a private Key is + * available in the gnupg Keyring. * @async */ this.getGnupgSecretState = function (){ -- cgit v1.2.3 From 8e87790db3499b1625fd65f3272192df47b5dfd0 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 17:20:35 +0200 Subject: js: don't expire new keys if no date is set -- * src/Keyring.js A new Key without expiration is documented as 'never expire' here, and should behave accordingly. This requires sending '0' here. --- lang/js/src/Keyring.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 766bab15..d18fb649 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -390,6 +390,8 @@ export class GPGME_Keyring { if (expires){ msg.setParameter('expires', Math.floor(expires.valueOf()/1000)); + } else { + msg.setParameter('expires', 0); } msg.post().then(function(response){ me.getKeys(response.fingerprint, true).then( -- cgit v1.2.3 From 5b0f8230b2172bffcb3a3b629a75c9cf1a50a3d8 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 17:44:06 +0200 Subject: js: decrypt callback is_mime fix -- * src/gpgmejs: is_mime should report its' counterpart. Also, file_name is not optional in specification. We'll send null if there is no file_name --- lang/js/src/gpgmejs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 9154979d..18164366 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -193,9 +193,11 @@ export class GpgME { msg.post().then(function(result){ let _result = {data: result.data}; _result.base64 = result.base64 ? true: false; - _result.is_mime = result.mime ? true: false; + _result.is_mime = result.is_mime ? true: false; if (result.file_name){ _result.file_name = result.file_name; + } else { + _result.file_name = null; } if ( result.hasOwnProperty('signatures') && -- cgit v1.2.3 From fff365ffc583ef87ac585df2ac84fd8586202b8e Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 17:55:11 +0200 Subject: js: expect additional 'info' to arrive on decrypt, too -- * src/permittedOperations.js: if decrypt includes a verification, this info needs to pass, too. --- lang/js/src/permittedOperations.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 1c28ab81..676ebaf0 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -166,7 +166,8 @@ export const permittedOperations = { type: ['signature', 'ciphertext'], data: { 'data': 'string', - 'base64':'boolean' + 'base64':'boolean', + 'info': 'object' } } -- cgit v1.2.3 From 3cbafb97ec4d94c1b9a2232a74f19f432ba67384 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 18:25:57 +0200 Subject: js: correct decrypt result info (2) -- * the permittedOperation from last commit ended up in the wrong place. sign does not return an additional 'info' object. --- lang/js/src/permittedOperations.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 676ebaf0..f9145dab 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -127,7 +127,8 @@ export const permittedOperations = { 'data': 'string', 'base64': 'boolean', 'mime': 'boolean', - 'signatures': 'object' + 'signatures': 'object', + 'info': 'object' } } }, @@ -166,8 +167,7 @@ export const permittedOperations = { type: ['signature', 'ciphertext'], data: { 'data': 'string', - 'base64':'boolean', - 'info': 'object' + 'base64':'boolean' } } -- cgit v1.2.3 From 74684f24c663af12c88b196fecd5f44863b893e4 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 17 Aug 2018 19:20:35 +0200 Subject: js: decode arriving gpg message strings -- * Arriving strings (i.e. user id names, error messages) are not always in javascript encoding. This is an attempt to go through the whole gpgme answer (with the exception of payload data) and to fix the encoding of these --- lang/js/BrowserTestExtension/tests/KeyInfos.js | 13 ++++++++- lang/js/BrowserTestExtension/tests/inputvalues.js | 35 ++++++++++++++++++++++- lang/js/src/Connection.js | 5 ++-- lang/js/src/Helpers.js | 27 +++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js index 03773a44..1829f227 100644 --- a/lang/js/BrowserTestExtension/tests/KeyInfos.js +++ b/lang/js/BrowserTestExtension/tests/KeyInfos.js @@ -22,7 +22,7 @@ */ /* global describe, it, expect, before, Gpgmejs */ -/* global inputvalues, fixedLengthString */ +/* global inputvalues*/ describe('Key information', function () { let context = null; @@ -43,4 +43,15 @@ describe('Key information', function () { done(); }); }); + + it('A userId keeps their encoding', function(done){ + context.Keyring.importKey(inputvalues.publicKeyNonAscii.key, true) + .then(function(result){ + expect(result.Keys[0]).to.be.an('object'); + const user = result.Keys[0].key.get('userids')[0]; + expect(user.get('name')).to.equal( + inputvalues.publicKeyNonAscii.userid); + done(); + }); + }); }); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index 7dda47cd..b33d985b 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -89,7 +89,40 @@ const inputvalues = {// eslint-disable-line no-unused-vars '-----END PGP SIGNATURE-----\n', }, - someInputParameter: 'bad string' + someInputParameter: 'bad string', + + publicKeyNonAscii: { + userid: 'Müller €uro', + key: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + '\n' + + 'mQENBFt2/VIBCADIWBIMxExZlHda4XIVnM9nsIfUYLebJSC/krEriyWgzytU8/fQ\n' + + 'S05cfnYx7RXvOOq4k8aa7mu80ovg3q77idXauLreAUwng4Njw0nMxWq/vtoMiZ60\n' + + '9f8EmfthZophhkQF2HIPHyqXMDZzMLWv4oTr2UJ9BKudL1XtbK51y9TbiyfQygBl\n' + + '8bl+zrOo70/dN6aunvuo6Hlu5cEzkj2QrzZlqTdfG5qv6KVEMut1eAbxZAmvSnna\n' + + 'R4wqiRCT3/eRXGJbDL/8GaCEYkwi9FBrimjOTV0MpcLNwAU4aGfDxMUsxML9xJ+/\n' + + '/6GFxzYf7Lmk5UhvoewR58uQkHkTVPjZ9hXZABEBAAG0KE3DvGxsZXIg4oKsdXJv\n' + + 'IDxtdWVsbGVyZXVyb0BleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQQVNixp3XT/DuGT\n' + + 'F4MFmkL4L5UZdAUCW3b9UgIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIX\n' + + 'gAAKCRAFmkL4L5UZdAhiCACowW1aC8DYGtJyAaBO2MqWhyw1wVCbQN9uFsQZPydY\n' + + 'v3BEbCDrRc0HyfV1PVoRQsgkiNMes1S2tz2IMJoEOTMaz3WjPM8yK0dDbo5sfx/o\n' + + '/XaXeKhyYNqRkz2dPzorg1sHyHe0ki/HoQiANEJ8mByMtlwnPWlhnINAX+27eL17\n' + + 'JC8juhBYUchqoIBAl+ajYKSThdLzrUkcL7QfJjZb3pPytJSTTdFc0rD6ERDbfXXc\n' + + '/vnE2SDYme+XXn7H5tNe67tPM8M96vbp+uM+n2t/z96C+Pqb6GJFMBa35PM+/qQO\n' + + 'yr0I2oaQnTecx2AfBXGZvd81wMYikAJ9rAOWyMQZHJWouQENBFt2/VIBCADXCvKD\n' + + '3wRWCOzRWtLTs7hpAjCDxp6niPkwxKuUf9r/sUPmn0pWdZHYlbPDev9psN9bnJ+C\n' + + '+wzzPZ1zgSYKIAN0IMoh0L7BRAoau7VWQ3Q7hP6HIbdzOTEGyklSoh9pIh6IlwZZ\n' + + 'XfPlFlnn7FeH1UeA711E174SUpDRKYSfT+mFObQUuQewGi9QC3gBsz5MPLQQLzML\n' + + 'yimIOT+8i64fHHSKChw5ZDckBffej31/YHPQ7+JsWFV+G/6xDfbwnaFZFAUwo+1L\n' + + '4w9UiMyCNkIWCkulzJ2Hbz66xzFMi/8zMYxr08Af+PpsXaWTQHAa5V4GNJSInDEB\n' + + '7gy/CGLcY90EozoDABEBAAGJATwEGAEIACYWIQQVNixp3XT/DuGTF4MFmkL4L5UZ\n' + + 'dAUCW3b9UgIbDAUJA8JnAAAKCRAFmkL4L5UZdPqoB/9kpqxqa82k7JMcq7UiwQY7\n' + + 'CdqCUPKF88ciOWKBpZmpl8V7zgM7kEXwmM6ocHcznXi8xM7eOfDIJcBeqFVIE4OT\n' + + '63OCMuvZICM9Kiu48wLNAw5W/YGAOBH7ySQzZM2XrtvwfFtJ3lR00t5f4FVtriA5\n' + + '47BjYYG5tTdJc8HwEHs045S99xKCWqwuDgO9qskIi6iPePUkuhpaVBLuEj2Goku6\n' + + 'i8aql/vKYQS67L7UHJiEbjLe+wP9k3FvWUFTx39lAubsDzb4Abhe+qRqs2TKD7Go\n' + + 'k35ZriRIYllmx4c9KyWL7Mvzcp+84Sq9LeMfsN4JstBDJ7jn6g19SjO5dmtxSuP0\n' + + '=zZSJ\n' + + '-----END PGP PUBLIC KEY BLOCK-----\n' + } }; // (Pseudo-)Random String covering all of utf8. diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index b0105757..8d381f15 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -26,6 +26,7 @@ import { permittedOperations } from './permittedOperations'; import { gpgme_error } from './Errors'; import { GPGME_Message, createMessage } from './Message'; +import { decode } from './Helpers'; /** * A Connection handles the nativeMessaging interaction via a port. As the @@ -239,7 +240,7 @@ class Answer{ case 'type': if (_decodedResponse.type === 'error'){ return (gpgme_error('GNUPG_ERROR', - decodeURIComponent(escape(_decodedResponse.msg)))); + decode(_decodedResponse.msg))); } else if (poa.type.indexOf(_decodedResponse.type) < 0){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } @@ -270,7 +271,7 @@ class Answer{ ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } else { - _response[key] = _decodedResponse[key]; + _response[key] = decode(_decodedResponse[key]); } break; } diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js index accc2af5..379015f2 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -108,3 +108,30 @@ export function isFingerprint(value){ export function isLongId(value){ return hextest(value, 16); } + +/** + * Recursively decodes input (utf8) to output (utf-16; javascript) strings + * @param {Object | Array | String} property + */ +export function decode(property){ + if (typeof property === 'string'){ + return decodeURIComponent(escape(property)); + } else if (Array.isArray(property)){ + let res = []; + for (let arr=0; arr < property.length; arr++){ + res.push(decode(property[arr])); + } + return res; + } else if (typeof property === 'object'){ + const keys = Object.keys(property); + if (keys.length){ + let res = {}; + for (let k=0; k < keys.length; k++ ){ + res[keys[k]] = decode(property[keys[k]]); + } + return res; + } + return property; + } + return property; +} \ No newline at end of file -- cgit v1.2.3 From 1954d27be86b8e4eb801ca6ddcb670f8cfb149f5 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 20 Aug 2018 12:12:43 +0200 Subject: js: revert changes to class read/write restriction -- * undoes 94ee0988d4eaac27785de6efb7c19ca9976e1e9c and e16a87e83910ebb6bfdc4148369165f121f0997e. I do not fully understand why my approach was bad, but I am not in a position to argue. This revert was requested to me after a review, and I'm doing it in the assumption that more experienced people know better than me. * unittests: Also changed some outdated tests that stopped working since 754e799d35fd62d7a979452f44342934659908c7 (as GPGME_Key is not exported, one cannot check for instanceof in the tests anymore) --- lang/js/src/Connection.js | 415 ++++++++++++++-------------- lang/js/src/Errors.js | 13 +- lang/js/src/Key.js | 386 +++++++++++++------------- lang/js/src/Keyring.js | 675 +++++++++++++++++++++++----------------------- lang/js/src/Message.js | 321 +++++++++++----------- lang/js/src/Signature.js | 116 +++----- lang/js/src/gpgmejs.js | 425 ++++++++++++++--------------- lang/js/src/index.js | 6 +- lang/js/unittests.js | 62 ++--- 9 files changed, 1164 insertions(+), 1255 deletions(-) diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index 8d381f15..a60fd215 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -39,144 +39,146 @@ import { decode } from './Helpers'; export class Connection{ constructor(){ - let _connection = chrome.runtime.connectNative('gpgmejson'); - + this._connection = chrome.runtime.connectNative('gpgmejson'); + } - /** - * Immediately closes an open port. - */ - this.disconnect = function () { - if (_connection){ - _connection.disconnect(); - _connection = null; - } - }; + /** + * Immediately closes an open port. + */ + disconnect() { + if (this._connection){ + this._connection.disconnect(); + this._connection = null; + } + } - /** - * @typedef {Object} backEndDetails - * @property {String} gpgme Version number of gpgme - * @property {Array} info Further information about the backend - * and the used applications (Example: - * { - * "protocol": "OpenPGP", - * "fname": "/usr/bin/gpg", - * "version": "2.2.6", - * "req_version": "1.4.0", - * "homedir": "default" - * } - */ + /** + * @typedef {Object} backEndDetails + * @property {String} gpgme Version number of gpgme + * @property {Array} info Further information about the backend + * and the used applications (Example: + * { + * "protocol": "OpenPGP", + * "fname": "/usr/bin/gpg", + * "version": "2.2.6", + * "req_version": "1.4.0", + * "homedir": "default" + * } + */ - /** - * Retrieves the information about the backend. - * @param {Boolean} details (optional) If set to false, the promise will - * just return if a connection was successful. - * @returns {Promise|Promise} Details from the - * backend - * @async - */ - this.checkConnection = function(details = true){ - const msg = createMessage('version'); - if (details === true) { - return this.post(msg); - } else { - let me = this; - return new Promise(function(resolve) { - Promise.race([ - me.post(msg), - new Promise(function(resolve, reject){ - setTimeout(function(){ - reject(gpgme_error('CONN_TIMEOUT')); - }, 500); - }) - ]).then(function(){ // success - resolve(true); - }, function(){ // failure - resolve(false); - }); + /** + * Retrieves the information about the backend. + * @param {Boolean} details (optional) If set to false, the promise will + * just return if a connection was successful. + * @returns {Promise|Promise} Details from the + * backend + * @async + */ + checkConnection (details = true){ + const msg = createMessage('version'); + if (details === true) { + return this.post(msg); + } else { + let me = this; + return new Promise(function(resolve) { + Promise.race([ + me.post(msg), + new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(gpgme_error('CONN_TIMEOUT')); + }, 500); + }) + ]).then(function(){ // success + resolve(true); + }, function(){ // failure + resolve(false); }); - } - }; + }); + } + } - /** - * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It - * resolves with the completed answer after all parts have been - * received and reassembled, or rejects with an {@link GPGME_Error}. - * - * @param {GPGME_Message} message - * @returns {Promise} The collected answer - * @async - */ - this.post = function (message){ - if (!message || !(message instanceof GPGME_Message)){ - this.disconnect(); - return Promise.reject(gpgme_error( - 'PARAM_WRONG', 'Connection.post')); - } - if (message.isComplete() !== true){ - this.disconnect(); - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); - } - let chunksize = message.chunksize; - return new Promise(function(resolve, reject){ - let answer = Object.freeze(new Answer(message)); - let listener = function(msg) { - if (!msg){ - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - reject(gpgme_error('CONN_EMPTY_GPG_ANSWER')); + /** + * Sends a {@link GPGME_Message} via tghe nativeMessaging port. It + * resolves with the completed answer after all parts have been + * received and reassembled, or rejects with an {@link GPGME_Error}. + * + * @param {GPGME_Message} message + * @returns {Promise} The collected answer + * @async + */ + post(message){ + if (!message || !(message instanceof GPGME_Message)){ + this.disconnect(); + return Promise.reject(gpgme_error( + 'PARAM_WRONG', 'Connection.post')); + } + if (message.isComplete() !== true){ + this.disconnect(); + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + let chunksize = message.chunksize; + const 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 { + let answer_result = answer.collect(msg); + if (answer_result !== true){ + me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); + reject(answer_result); } else { - let answer_result = answer.collect(msg); - if (answer_result !== true){ - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - reject(answer_result); + if (msg.more === true){ + me._connection.postMessage({ + 'op': 'getmore', + 'chunksize': chunksize + }); } else { - if (msg.more === true){ - _connection.postMessage({ - 'op': 'getmore', - 'chunksize': chunksize - }); + me._connection.onMessage.removeListener(listener); + me._connection.disconnect(); + const message = answer.getMessage(); + if (message instanceof Error){ + reject(message); } else { - _connection.onMessage.removeListener(listener); - _connection.disconnect(); - const message = answer.getMessage(); - if (message instanceof Error){ - reject(message); - } else { - resolve(message); - } + resolve(message); } } } - }; - _connection.onMessage.addListener(listener); - if (permittedOperations[message.operation].pinentry){ - return _connection.postMessage(message.message); - } else { - return Promise.race([ - _connection.postMessage(message.message), - function(resolve, reject){ - setTimeout(function(){ - _connection.disconnect(); - reject(gpgme_error('CONN_TIMEOUT')); - }, 5000); - }]).then(function(result){ - return result; - }, function(reject){ - if(!(reject instanceof Error)) { - _connection.disconnect(); - return gpgme_error('GNUPG_ERROR', reject); - } else { - return reject; - } - }); } - }); - }; + }; + 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. @@ -188,95 +190,94 @@ class Answer{ * @param {GPGME_Message} message */ constructor(message){ - const operation = message.operation; - const expected = message.getExpect(); - let response_b64 = null; + this._operation = message.operation; + this._expected = message.expected; + this._response_b64 = null; + } - this.getOperation = function(){ - return operation; - }; + get operation (){ + return this._operation; + } - this.getExpect = function(){ - return expected; - }; + get expected (){ + return this._expected; + } - /** - * Adds incoming base64 encoded data to the existing response - * @param {*} msg base64 encoded data. - * @returns {Boolean} - * - * @private - */ - this.collect = function (msg){ - if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (response_b64 === null){ - response_b64 = msg.response; - return true; - } else { - response_b64 += msg.response; - return true; - } - }; - /** - * Returns the base64 encoded answer data with the content verified - * against {@link permittedOperations}. - */ - this.getMessage = function (){ - if (response_b64 === undefined){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - let _decodedResponse = JSON.parse(atob(response_b64)); - let _response = {}; - let messageKeys = Object.keys(_decodedResponse); - let poa = permittedOperations[this.getOperation()].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 (_decodedResponse.type === 'error'){ - return (gpgme_error('GNUPG_ERROR', - decode(_decodedResponse.msg))); - } else if (poa.type.indexOf(_decodedResponse.type) < 0){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - break; - case 'base64': - break; - case 'msg': - if (_decodedResponse.type === 'error'){ - return (gpgme_error('GNUPG_ERROR', - _decodedResponse.msg)); - } - break; - default: - if (!poa.data.hasOwnProperty(key)){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if( typeof(_decodedResponse[key]) !== poa.data[key] ){ - return gpgme_error('CONN_UNEXPECTED_ANSWER'); - } - if (_decodedResponse.base64 === true - && poa.data[key] === 'string' - && this.getExpect() !== 'base64' - ){ - _response[key] = decodeURIComponent( - atob(_decodedResponse[key]).split('').map( - function(c) { - return '%' + - ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - } else { - _response[key] = decode(_decodedResponse[key]); - } - break; + /** + * Adds incoming base64 encoded data to the existing response + * @param {*} msg base64 encoded data. + * @returns {Boolean} + * + * @private + */ + collect (msg){ + if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (!this._response_b64){ + this._response_b64 = msg.response; + return true; + } else { + this._response_b64 += msg.response; + return true; + } + } + /** + * Returns the base64 encoded answer data with the content verified + * against {@link permittedOperations}. + */ + getMessage(){ + if (this._response_b64 === null){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + let _decodedResponse = JSON.parse(atob(this._response_b64)); + let _response = {}; + let messageKeys = Object.keys(_decodedResponse); + 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 (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', + decode(_decodedResponse.msg))); + } else if (poa.type.indexOf(_decodedResponse.type) < 0){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + break; + case 'base64': + break; + case 'msg': + if (_decodedResponse.type === 'error'){ + return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg)); + } + break; + default: + if (!poa.data.hasOwnProperty(key)){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if( typeof(_decodedResponse[key]) !== poa.data[key] ){ + return gpgme_error('CONN_UNEXPECTED_ANSWER'); + } + if (_decodedResponse.base64 === true + && poa.data[key] === 'string' + && this.expected !== 'base64' + ){ + _response[key] = decodeURIComponent( + atob(_decodedResponse[key]).split('').map( + function(c) { + return '%' + + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + } else { + _response[key] = decode(_decodedResponse[key]); } + break; } - return _response; - }; + } + return _response; } } diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index b22eca73..2a35bc5e 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -123,7 +123,7 @@ const err_list = { export function gpgme_error(code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ - return Object.freeze(new GPGME_Error(code)); + return new GPGME_Error(code); } if (err_list[code].type === 'warning'){ // eslint-disable-next-line no-console @@ -131,10 +131,10 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ } return null; } else if (code === 'GNUPG_ERROR'){ - return Object.freeze(new GPGME_Error(code, info)); + return new GPGME_Error(code, info); } else { - return Object.freeze(new GPGME_Error('GENERIC_ERROR')); + return new GPGME_Error('GENERIC_ERROR'); } } @@ -148,6 +148,7 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ */ class GPGME_Error extends Error{ constructor(code = 'GENERIC_ERROR', msg=''){ + if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ @@ -159,12 +160,10 @@ class GPGME_Error extends Error{ } else { super(err_list['GENERIC_ERROR'].msg); } - this.getCode = function(){ - return code; - }; + this._code = code; } get code(){ - return this.getCode(); + return this._code; } } \ No newline at end of file diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index ea6fd883..37ec7f9d 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -40,12 +40,12 @@ export function createKey(fingerprint, async = false, data){ return gpgme_error('PARAM_WRONG'); } if (data !== undefined){ - data = validateKeyData(data); + data = validateKeyData(fingerprint, data); } if (data instanceof Error){ return gpgme_error('KEY_INVALID'); } else { - return Object.freeze(new GPGME_Key(fingerprint, async, data)); + return new GPGME_Key(fingerprint, async, data); } } @@ -65,184 +65,181 @@ class GPGME_Key { /** * @property {Boolean} If true, most answers will be asynchronous */ - this.isAsync = async; + this._async = async; - let _data = {fingerprint: fingerprint.toUpperCase()}; + this._data = {fingerprint: fingerprint.toUpperCase()}; if (data !== undefined - && data.fingerprint.toUpperCase() === _data.fingerprint + && data.fingerprint.toUpperCase() === this._data.fingerprint ) { - _data = data; + this._data = data; } - this.getFingerprint = function(){ - if (!_data.fingerprint || !isFingerprint(_data.fingerprint)){ - return gpgme_error('KEY_INVALID'); - } - return _data.fingerprint; - }; + } - /** - * Query any property of the Key listed in {@link validKeyProperties} - * @param {String} property property to be retreived - * @returns {Boolean| String | Date | Array | Object |GPGME_Error} - * the value of the property. If the Key is set to Async, the value - * will be fetched from gnupg and resolved as a Promise. If Key is not - * async, the armored property is not available (it can still be - * retrieved asynchronously by {@link Key.getArmor}) - */ - this.get = function(property) { - if (this.isAsync === true) { - switch (property){ - case 'armored': - return this.getArmor(); - case 'hasSecret': - return this.getGnupgSecretState(); - default: - return getGnupgState(property); - } + /** + * Query any property of the Key listed in {@link validKeyProperties} + * @param {String} property property to be retreived + * @returns {Boolean| String | Date | Array | Object |GPGME_Error} + * the value of the property. If the Key is set to Async, the value + * will be fetched from gnupg and resolved as a Promise. If Key is not + * async, the armored property is not available (it can still be + * retrieved asynchronously by {@link Key.getArmor}) + */ + get(property) { + if (this._async === true) { + switch (property){ + case 'armored': + return this.getArmor(); + case 'hasSecret': + return this.getGnupgSecretState(); + default: + return getGnupgState(this.fingerprint, property); + } + } else { + if (property === 'armored') { + return gpgme_error('KEY_ASYNC_ONLY'); + } + if (!validKeyProperties.hasOwnProperty(property)){ + return gpgme_error('PARAM_WRONG'); } else { - if (property === 'armored') { - return gpgme_error('KEY_ASYNC_ONLY'); - } - if (!validKeyProperties.hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); - } else { - return (_data[property]); - } + return (this._data[property]); } - }; + } + } - /** - * Reloads the Key information from gnupg. This is only useful if you - * use the GPGME_Keys cached. Note that this is a performance hungry - * operation. If you desire more than a few refreshs, it may be - * advisable to run {@link Keyring.getKeys} instead. - * @returns {Promise} - * @async - */ - this.refreshKey = function() { - let me = this; - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('keylist'); - msg.setParameter('sigs', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - if (result.keys.length === 1){ - const newdata = validateKeyData( - _data.fingerprint, result.keys[0]); - if (newdata instanceof Error){ - reject(gpgme_error('KEY_INVALID')); - } else { - _data = newdata; - me.getGnupgSecretState().then(function(){ - me.getArmor().then(function(){ - resolve(me); - }, function(error){ - reject(error); - }); + /** + * Reloads the Key information from gnupg. This is only useful if you + * use the GPGME_Keys cached. Note that this is a performance hungry + * operation. If you desire more than a few refreshs, it may be + * advisable to run {@link Keyring.getKeys} instead. + * @returns {Promise} + * @async + */ + 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){ + const newdata = validateKeyData( + me._data.fingerprint, result.keys[0]); + if (newdata instanceof Error){ + reject(gpgme_error('KEY_INVALID')); + } else { + me._data = newdata; + me.getGnupgSecretState().then(function(){ + me.getArmor().then(function(){ + resolve(me); }, function(error){ reject(error); }); - } - } else { - reject(gpgme_error('KEY_NOKEY')); + }, function(error){ + reject(error); + }); } - }, function (error) { - reject(gpgme_error('GNUPG_ERROR'), error); - }); - }); - }; - - /** - * Query the armored block of the Key directly from gnupg. Please note - * that this will not get you any export of the secret/private parts of - * a Key - * @returns {Promise} - * @async - */ - this.getArmor = function(){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); + } else { + reject(gpgme_error('KEY_NOKEY')); } - let msg = createMessage('export'); - msg.setParameter('armor', true); - msg.setParameter('keys', _data.fingerprint); - msg.post().then(function(result){ - resolve(result.data); - }, function(error){ - reject(error); - }); + }, function (error) { + reject(gpgme_error('GNUPG_ERROR'), error); }); - }; + }); + } - /** - * Find out if the Key is part of a Key pair including public and - * private key(s). If you want this information about more than a few - * Keys in synchronous mode, it may be advisable to run - * {@link Keyring.getKeys} instead, as it performs faster in bulk - * querying this state. - * @returns {Promise} True if a private Key is - * available in the gnupg Keyring. - * @async - */ - this.getGnupgSecretState = function (){ - return new Promise(function(resolve, reject) { - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } else { - let msg = createMessage('keylist'); - msg.setParameter('keys', _data.fingerprint); - msg.setParameter('secret', true); - msg.post().then(function(result){ - _data.hasSecret = null; - if ( - result.keys && - result.keys.length === 1 && - result.keys[0].secret === true - ) { - _data.hasSecret = true; - resolve(true); - } else { - _data.hasSecret = false; - resolve(false); - } - }, function(error){ - reject(error); - }); - } + /** + * Query the armored block of the Key directly from gnupg. Please note + * that this will not get you any export of the secret/private parts of + * a Key + * @returns {Promise} + * @async + */ + getArmor() { + const 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){ + resolve(result.data); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Deletes the (public) Key from the GPG Keyring. Note that a deletion - * of a secret key is not supported by the native backend. - * @returns {Promise} Success if key was deleted, - * rejects with a GPG error otherwise. - */ - this.delete= function (){ - return new Promise(function(resolve, reject){ - if (!_data.fingerprint){ - reject(gpgme_error('KEY_INVALID')); - } - let msg = createMessage('delete'); - msg.setParameter('key', _data.fingerprint); + /** + * Find out if the Key is part of a Key pair including public and + * private key(s). If you want this information about more than a few + * Keys in synchronous mode, it may be advisable to run + * {@link Keyring.getKeys} instead, as it performs faster in bulk + * querying this state. + * @returns {Promise} True if a private Key is + * available in the gnupg Keyring. + * @async + */ + getGnupgSecretState (){ + const me = this; + return new Promise(function(resolve, reject) { + if (!me._data.fingerprint){ + reject(gpgme_error('KEY_INVALID')); + } else { + let msg = createMessage('keylist'); + msg.setParameter('keys', me._data.fingerprint); + msg.setParameter('secret', true); msg.post().then(function(result){ - resolve(result.success); + me._data.hasSecret = null; + if ( + result.keys && + result.keys.length === 1 && + result.keys[0].secret === true + ) { + me._data.hasSecret = true; + resolve(true); + } else { + me._data.hasSecret = false; + resolve(false); + } }, function(error){ reject(error); }); + } + }); + } + + /** + * Deletes the (public) Key from the GPG Keyring. Note that a deletion + * of a secret key is not supported by the native backend. + * @returns {Promise} Success if key was deleted, + * rejects with a GPG error otherwise. + */ + delete(){ + const 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); }); - }; + }); } /** * @returns {String} The fingerprint defining this Key. Convenience getter */ get fingerprint(){ - return this.getFingerprint(); + return this._data.fingerprint; } } @@ -259,8 +256,9 @@ class GPGME_Subkey { * @private */ constructor(data){ - let _data = {}; + this._data = {}; let keys = Object.keys(data); + const me = this; /** * Validates a subkey property against {@link validSubKeyProperties} and @@ -273,9 +271,9 @@ class GPGME_Subkey { if (validSubKeyProperties.hasOwnProperty(property)){ if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ - _data[property] = new Date(value * 1000); + me._data[property] = new Date(value * 1000); } else { - _data[property] = value; + me._data[property] = value; } } } @@ -283,18 +281,19 @@ class GPGME_Subkey { for (let i=0; i< keys.length; i++) { setProperty(keys[i], data[keys[i]]); } + } - /** - * Fetches any information about this subkey - * @param {String} property Information to request - * @returns {String | Number | Date} - */ - this.get = function(property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; + /** + * Fetches any information about this subkey + * @param {String} property Information to request + * @returns {String | Number | Date} + */ + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } } + } /** @@ -310,15 +309,16 @@ class GPGME_UserId { * @private */ constructor(data){ - let _data = {}; + this._data = {}; + const me = this; let keys = Object.keys(data); const setProperty = function(property, value){ if (validUserIdProperties.hasOwnProperty(property)){ if (validUserIdProperties[property](value) === true) { if (property === 'last_update'){ - _data[property] = new Date(value*1000); + me._data[property] = new Date(value*1000); } else { - _data[property] = value; + me._data[property] = value; } } } @@ -326,18 +326,19 @@ class GPGME_UserId { for (let i=0; i< keys.length; i++) { setProperty(keys[i], data[keys[i]]); } + } - /** - * Fetches information about the user - * @param {String} property Information to request - * @returns {String | Number} - */ - this.get = function (property) { - if (_data.hasOwnProperty(property)){ - return (_data[property]); - } - }; + /** + * Fetches information about the user + * @param {String} property Information to request + * @returns {String | Number} + */ + get(property) { + if (this._data.hasOwnProperty(property)){ + return (this._data[property]); + } } + } /** @@ -569,10 +570,11 @@ const validKeyProperties = { * an error if something went wrong. * @private */ -function validateKeyData(data){ +function validateKeyData(fingerprint, data){ const key = {}; - if ( typeof(data) !== 'object' - || !data.fingerprint){ + if (!fingerprint || typeof(data) !== 'object' || !data.fingerprint + || fingerprint !== data.fingerprint.toUpperCase() + ){ return gpgme_error('KEY_INVALID'); } let props = Object.keys(data); @@ -588,15 +590,15 @@ function validateKeyData(data){ case 'subkeys': key.subkeys = []; for (let i=0; i< data.subkeys.length; i++) { - key.subkeys.push(Object.freeze( - new GPGME_Subkey(data.subkeys[i]))); + key.subkeys.push( + new GPGME_Subkey(data.subkeys[i])); } break; case 'userids': key.userids = []; for (let i=0; i< data.userids.length; i++) { - key.userids.push(Object.freeze( - new GPGME_UserId(data.userids[i]))); + key.userids.push( + new GPGME_UserId(data.userids[i])); } break; case 'last_update': @@ -623,19 +625,19 @@ function getGnupgState (fingerprint, property){ } else { let msg = createMessage('keylist'); msg.setParameter('keys', fingerprint); - msg.post().then(function(result){ - if (!result.keys || result.keys.length !== 1){ + msg.post().then(function(res){ + if (!res.keys || res.keys.length !== 1){ reject(gpgme_error('KEY_INVALID')); } else { - const key = result.keys[0]; + const key = res.keys[0]; let result; switch (property){ case 'subkeys': result = []; if (key.subkeys.length){ for (let i=0; i < key.subkeys.length; i++) { - result.push(Object.freeze( - new GPGME_Subkey(key.subkeys[i]))); + result.push( + new GPGME_Subkey(key.subkeys[i])); } } resolve(result); @@ -644,8 +646,8 @@ function getGnupgState (fingerprint, property){ result = []; if (key.userids.length){ for (let i=0; i< key.userids.length; i++) { - result.push(Object.freeze( - new GPGME_UserId(key.userids[i]))); + result.push( + new GPGME_UserId(key.userids[i])); } } resolve(result); diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index d18fb649..de21736e 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -32,383 +32,384 @@ import { gpgme_error } from './Errors'; */ export class GPGME_Keyring { constructor(){ + } - /** - * Queries Keys (all Keys or a subset) from gnupg. - * - * @param {String | Array} pattern (optional) A pattern to - * search for in userIds or KeyIds. - * @param {Boolean} prepare_sync (optional) if set to true, most data - * (with the exception of armored Key blocks) will be cached for the - * Keys. This enables direct, synchronous use of these properties for - * all keys. It does not check for changes on the backend. The cached - * information can be updated with the {@link Key.refresh} method. - * @param {Boolean} search (optional) retrieve Keys from external - * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) - * @returns {Promise>} - * @static - * @async - */ - this.getKeys = function(pattern, prepare_sync=false, search=false){ - return new Promise(function(resolve, reject) { - let msg = createMessage('keylist'); - if (pattern !== undefined && pattern !== null){ - msg.setParameter('keys', pattern); - } - msg.setParameter('sigs', true); - if (search === true){ - msg.setParameter('locate', true); - } - msg.post().then(function(result){ - let resultset = []; - if (result.keys.length === 0){ - resolve([]); + /** + * Queries Keys (all Keys or a subset) from gnupg. + * + * @param {String | Array} pattern (optional) A pattern to + * search for in userIds or KeyIds. + * @param {Boolean} prepare_sync (optional) if set to true, most data + * (with the exception of armored Key blocks) will be cached for the + * Keys. This enables direct, synchronous use of these properties for + * all keys. It does not check for changes on the backend. The cached + * information can be updated with the {@link Key.refresh} method. + * @param {Boolean} search (optional) retrieve Keys from external + * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) + * @returns {Promise>} + * @static + * @async + */ + getKeys (pattern, prepare_sync=false, search=false){ + return new Promise(function(resolve, reject) { + let msg = createMessage('keylist'); + if (pattern !== undefined && pattern !== null){ + msg.setParameter('keys', pattern); + } + msg.setParameter('sigs', true); + if (search === true){ + msg.setParameter('locate', true); + } + msg.post().then(function(result){ + let resultset = []; + if (result.keys.length === 0){ + resolve([]); + } else { + let secondrequest; + if (prepare_sync === true) { + secondrequest = function() { + let msg2 = createMessage('keylist'); + msg2.setParameter('keys', pattern); + msg2.setParameter('secret', true); + return msg2.post(); + }; } else { - let secondrequest; - if (prepare_sync === true) { - secondrequest = function() { - let msg2 = createMessage('keylist'); - msg2.setParameter('keys', pattern); - msg2.setParameter('secret', true); - return msg2.post(); - }; - } else { - secondrequest = function() { - return Promise.resolve(true); - }; - } - secondrequest().then(function(answer) { - for (let i=0; i < result.keys.length; i++){ - if (prepare_sync === true){ - if (answer && answer.keys) { - for (let j=0; - j < answer.keys.length; j++ ){ - const a = answer.keys[j]; - const b = result.keys[i]; - if ( - a.fingerprint === b.fingerprint - ) { - if (a.secret === true){ - b.hasSecret = true; - } else { - b.hasSecret = false; - } - break; + secondrequest = function() { + return Promise.resolve(true); + }; + } + secondrequest().then(function(answer) { + for (let i=0; i < result.keys.length; i++){ + if (prepare_sync === true){ + if (answer && answer.keys) { + for (let j=0; + j < answer.keys.length; j++ ){ + const a = answer.keys[j]; + const b = result.keys[i]; + if ( + a.fingerprint === b.fingerprint + ) { + if (a.secret === true){ + b.hasSecret = true; + } else { + b.hasSecret = false; } + break; } } } - let k = createKey(result.keys[i].fingerprint, - !prepare_sync, result.keys[i]); - resultset.push(k); } - resolve(resultset); - }, function(error){ - reject(error); - }); - } - }); + let k = createKey(result.keys[i].fingerprint, + !prepare_sync, result.keys[i]); + resultset.push(k); + } + resolve(resultset); + }, function(error){ + reject(error); + }); + } }); - }; + }); + } - /** - * @typedef {Object} exportResult The result of a getKeysArmored - * operation. - * @property {String} armored The public Key(s) as armored block. Note - * that the result is one armored block, and not a block per key. - * @property {Array} secret_fprs (optional) list of - * fingerprints for those Keys that also have a secret Key available in - * gnupg. The secret key will not be exported, but the fingerprint can - * be used in operations needing a secret key. - */ + /** + * @typedef {Object} exportResult The result of a getKeysArmored + * operation. + * @property {String} armored The public Key(s) as armored block. Note + * that the result is one armored block, and not a block per key. + * @property {Array} secret_fprs (optional) list of + * fingerprints for those Keys that also have a secret Key available in + * gnupg. The secret key will not be exported, but the fingerprint can + * be used in operations needing a secret key. + */ - /** - * Fetches the armored public Key blocks for all Keys matching the - * pattern (if no pattern is given, fetches all keys known to gnupg). - * @param {String|Array} pattern (optional) The Pattern to - * search for - * @param {Boolean} with_secret_fpr (optional) also return a list of - * fingerprints for the keys that have a secret key available - * @returns {Promise} Object containing the - * armored Key(s) and additional information. - * @static - * @async - */ - this.getKeysArmored = function(pattern, with_secret_fpr) { - return new Promise(function(resolve, reject) { - let msg = createMessage('export'); - msg.setParameter('armor', true); - if (with_secret_fpr === true) { - msg.setParameter('with-sec-fprs', true); - } - if (pattern !== undefined && pattern !== null){ - msg.setParameter('keys', pattern); + /** + * Fetches the armored public Key blocks for all Keys matching the + * pattern (if no pattern is given, fetches all keys known to gnupg). + * @param {String|Array} pattern (optional) The Pattern to + * search for + * @param {Boolean} with_secret_fpr (optional) also return a list of + * fingerprints for the keys that have a secret key available + * @returns {Promise} Object containing the + * armored Key(s) and additional information. + * @static + * @async + */ + getKeysArmored (pattern, with_secret_fpr) { + return new Promise(function(resolve, reject) { + let msg = createMessage('export'); + msg.setParameter('armor', true); + if (with_secret_fpr === true) { + msg.setParameter('with-sec-fprs', true); + } + if (pattern !== undefined && pattern !== null){ + msg.setParameter('keys', pattern); + } + msg.post().then(function(answer){ + const result = {armored: answer.data}; + if (with_secret_fpr === true + && answer.hasOwnProperty('sec-fprs') + ) { + result.secret_fprs = answer['sec-fprs']; } - msg.post().then(function(answer){ - const result = {armored: answer.data}; - if (with_secret_fpr === true - && answer.hasOwnProperty('sec-fprs') - ) { - result.secret_fprs = answer['sec-fprs']; - } - resolve(result); - }, function(error){ - reject(error); - }); + resolve(result); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Returns the Key used by default in gnupg. - * (a.k.a. 'primary Key or 'main key'). - * It looks up the gpg configuration if set, or the first key that - * contains a secret key. - * - * @returns {Promise} - * @async - * @static - */ - this.getDefaultKey = function(prepare_sync = false) { - let me = this; - return new Promise(function(resolve, reject){ - let msg = createMessage('config_opt'); - msg.setParameter('component', 'gpg'); - msg.setParameter('option', 'default-key'); - msg.post().then(function(resp){ - if (resp.option !== undefined - && resp.option.hasOwnProperty('value') - && resp.option.value.length === 1 - && resp.option.value[0].hasOwnProperty('string') - && typeof(resp.option.value[0].string) === 'string'){ - me.getKeys(resp.option.value[0].string, true).then( - function(keys){ - if(keys.length === 1){ - resolve(keys[0]); - } else { - reject(gpgme_error('KEY_NO_DEFAULT')); - } - }, function(error){ - reject(error); - }); - } else { - let msg = createMessage('keylist'); - msg.setParameter('secret', true); - msg.post().then(function(result){ - if (result.keys.length === 0){ - reject(gpgme_error('KEY_NO_DEFAULT')); + /** + * Returns the Key used by default in gnupg. + * (a.k.a. 'primary Key or 'main key'). + * It looks up the gpg configuration if set, or the first key that + * contains a secret key. + * + * @returns {Promise} + * @async + * @static + */ + getDefaultKey(prepare_sync = false) { + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('config_opt'); + msg.setParameter('component', 'gpg'); + msg.setParameter('option', 'default-key'); + msg.post().then(function(resp){ + if (resp.option !== undefined + && resp.option.hasOwnProperty('value') + && resp.option.value.length === 1 + && resp.option.value[0].hasOwnProperty('string') + && typeof(resp.option.value[0].string) === 'string'){ + me.getKeys(resp.option.value[0].string, true).then( + function(keys){ + if(keys.length === 1){ + resolve(keys[0]); } else { - for (let i=0; i< result.keys.length; i++ ) { - if (result.keys[i].invalid === false) { - let k = createKey( - result.keys[i].fingerprint, - !prepare_sync, - result.keys[i]); - resolve(k); - break; - } else if (i === result.keys.length - 1){ - reject(gpgme_error('KEY_NO_DEFAULT')); - } - } + reject(gpgme_error('KEY_NO_DEFAULT')); } }, function(error){ reject(error); }); - } - }, function(error){ - reject(error); - }); + } else { + let msg = createMessage('keylist'); + msg.setParameter('secret', true); + msg.post().then(function(result){ + if (result.keys.length === 0){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } else { + for (let i=0; i< result.keys.length; i++ ) { + if (result.keys[i].invalid === false) { + let k = createKey( + result.keys[i].fingerprint, + !prepare_sync, + result.keys[i]); + resolve(k); + break; + } else if (i === result.keys.length - 1){ + reject(gpgme_error('KEY_NO_DEFAULT')); + } + } + } + }, function(error){ + reject(error); + }); + } + }, function(error){ + reject(error); }); - }; + }); + } - /** - * @typedef {Object} importResult The result of a Key update - * @property {Object} summary Numerical summary of the result. See the - * feedbackValues variable for available Keys values and the gnupg - * documentation. - * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html - * for details on their meaning. - * @property {Array} Keys Array of Object containing - * GPGME_Keys with additional import information - * - */ + /** + * @typedef {Object} importResult The result of a Key update + * @property {Object} summary Numerical summary of the result. See the + * feedbackValues variable for available Keys values and the gnupg + * documentation. + * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html + * for details on their meaning. + * @property {Array} Keys Array of Object containing + * GPGME_Keys with additional import information + * + */ - /** - * @typedef {Object} importedKeyResult - * @property {GPGME_Key} key The resulting key - * @property {String} status: - * '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. - * @property {Boolean} changes.userId Changes in userIds - * @property {Boolean} changes.signature Changes in signatures - * @property {Boolean} changes.subkey Changes in subkeys - */ + /** + * @typedef {Object} importedKeyResult + * @property {GPGME_Key} key The resulting key + * @property {String} status: + * '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. + * @property {Boolean} changes.userId Changes in userIds + * @property {Boolean} changes.signature Changes in signatures + * @property {Boolean} changes.subkey Changes in subkeys + */ - /** - * Import an armored Key block into gnupg. Note that this currently - * will not succeed on private Key blocks. - * @param {String} armored Armored Key block of the Key(s) to be - * imported into gnupg - * @param {Boolean} prepare_sync prepare the keys for synched use - * (see {@link getKeys}). - * @returns {Promise} A summary and Keys considered. - * @async - * @static - */ - this.importKey = function (armored, prepare_sync) { - let feedbackValues = ['considered', 'no_user_id', 'imported', - 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', - 'new_signatures', 'new_revocations', 'secret_read', - 'secret_imported', 'secret_unchanged', 'skipped_new_keys', - 'not_imported', 'skipped_v3_keys']; - 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 = []; - let summary = {}; - for (let i=0; i < feedbackValues.length; i++ ){ - summary[feedbackValues[i]] = - response.result[feedbackValues[i]]; - } - if (!response.result.hasOwnProperty('imports') || - response.result.imports.length === 0 - ){ - resolve({Keys:[],summary: summary}); - return; + /** + * Import an armored Key block into gnupg. Note that this currently + * will not succeed on private Key blocks. + * @param {String} armored Armored Key block of the Key(s) to be + * imported into gnupg + * @param {Boolean} prepare_sync prepare the keys for synched use + * (see {@link getKeys}). + * @returns {Promise} A summary and Keys considered. + * @async + * @static + */ + importKey (armored, prepare_sync) { + let feedbackValues = ['considered', 'no_user_id', 'imported', + 'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', + 'new_signatures', 'new_revocations', 'secret_read', + 'secret_imported', 'secret_unchanged', 'skipped_new_keys', + 'not_imported', 'skipped_v3_keys']; + 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 = []; + let summary = {}; + for (let i=0; i < feedbackValues.length; i++ ){ + summary[feedbackValues[i]] = + response.result[feedbackValues[i]]; + } + if (!response.result.hasOwnProperty('imports') || + response.result.imports.length === 0 + ){ + resolve({Keys:[],summary: summary}); + return; + } + for (let res=0; res} - * @async - * @static - */ - this.deleteKey = function(fingerprint){ - if (isFingerprint(fingerprint) === true) { - let key = createKey(fingerprint); - return key.delete(); - } else { - return Promise.reject(gpgme_error('KEY_INVALID')); - } - }; + /** + * Convenience function for deleting a Key. See {@link Key.delete} for + * further information about the return values. + * @param {String} fingerprint + * @returns {Promise} + * @async + * @static + */ + deleteKey(fingerprint){ + if (isFingerprint(fingerprint) === true) { + let key = createKey(fingerprint); + return key.delete(); + } else { + return Promise.reject(gpgme_error('KEY_INVALID')); + } + } - /** - * Generates a new Key pair directly in gpg, and returns a GPGME_Key - * representing that Key. Please note that due to security concerns, - * secret Keys can not be deleted or exported from inside gpgme.js. - * - * @param {String} userId The user Id, e.g. 'Foo Bar ' - * @param {String} algo (optional) algorithm (and optionally key size) - * to be used. See {@link supportedKeyAlgos} below for supported - * values. - * @param {Date} expires (optional) Expiration date. If not set, - * expiration will be set to 'never' - * - * @return {Promise} - * @async - */ - this.generateKey = function (userId, algo = 'default', expires){ - if ( - typeof(userId) !== 'string' || - supportedKeyAlgos.indexOf(algo) < 0 || - (expires && !(expires instanceof Date)) - ){ - return Promise.reject(gpgme_error('PARAM_WRONG')); + /** + * Generates a new Key pair directly in gpg, and returns a GPGME_Key + * representing that Key. Please note that due to security concerns, + * secret Keys can not be deleted or exported from inside gpgme.js. + * + * @param {String} userId The user Id, e.g. 'Foo Bar ' + * @param {String} algo (optional) algorithm (and optionally key size) + * to be used. See {@link supportedKeyAlgos} below for supported + * values. + * @param {Date} expires (optional) Expiration date. If not set, + * expiration will be set to 'never' + * + * @return {Promise} + * @async + */ + generateKey(userId, algo = 'default', expires){ + if ( + typeof(userId) !== 'string' || + supportedKeyAlgos.indexOf(algo) < 0 || + (expires && !(expires instanceof Date)) + ){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('createkey'); + msg.setParameter('userid', userId); + msg.setParameter('algo', algo ); + if (expires){ + msg.setParameter('expires', + Math.floor(expires.valueOf()/1000)); + } else { + msg.setParameter('expires', 0); } - let me = this; - return new Promise(function(resolve, reject){ - let msg = createMessage('createkey'); - msg.setParameter('userid', userId); - msg.setParameter('algo', algo ); - if (expires){ - msg.setParameter('expires', - Math.floor(expires.valueOf()/1000)); - } else { - msg.setParameter('expires', 0); - } - msg.post().then(function(response){ - me.getKeys(response.fingerprint, true).then( - // TODO prepare_sync? - function(result){ - resolve(result); - }, function(error){ - reject(error); - }); - }, function(error) { - reject(error); - }); + msg.post().then(function(response){ + me.getKeys(response.fingerprint, true).then( + // TODO prepare_sync? + function(result){ + resolve(result); + }, function(error){ + reject(error); + }); + }, function(error) { + reject(error); }); - }; + }); } } + /** * List of algorithms supported for key generation. Please refer to the gnupg * documentation for details diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index e2c07344..2134fe99 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -36,7 +36,7 @@ export function createMessage(operation){ return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ - return Object.freeze(new GPGME_Message(operation)); + return new GPGME_Message(operation); } else { return gpgme_error('MSG_WRONG_OP'); } @@ -52,199 +52,188 @@ export function createMessage(operation){ export class GPGME_Message { constructor(operation){ - let _msg = { + this._msg = { op: operation, chunksize: 1023* 1024 }; - let expected = null; + this._expected = null; + } - this.getOperation = function(){ - return _msg.op; - }; + get operation(){ + return this._msg.op; + } - this.setExpect = function(value){ - if (value === 'base64'){ - expected = value; - } - }; - this.getExpect = function(){ - return expected; - }; + set expected (value){ + if (value === 'base64'){ + this._expected = value; + } + } - /** - * The maximum size of responses from gpgme in bytes. As of July 2018, - * most browsers will only accept answers up to 1 MB of size. - * Everything above that threshold will not pass through - * nativeMessaging; answers that are larger need to be sent in parts. - * The lower limit is set to 10 KB. Messages smaller than the threshold - * will not encounter problems, larger messages will be received in - * chunks. If the value is not explicitly specified, 1023 KB is used. - */ - this.setChunksize = function (value){ - if ( - Number.isInteger(value) && - value > 10 * 1024 && - value <= 1024 * 1024 - ){ - _msg.chunksize = value; - } - }; + get expected() { + return this._expected; + } + /** + * The maximum size of responses from gpgme in bytes. As of July 2018, + * most browsers will only accept answers up to 1 MB of size. + * Everything above that threshold will not pass through + * nativeMessaging; answers that are larger need to be sent in parts. + * The lower limit is set to 10 KB. Messages smaller than the threshold + * will not encounter problems, larger messages will be received in + * chunks. If the value is not explicitly specified, 1023 KB is used. + */ + set chunksize(value){ + if ( + Number.isInteger(value) && + value > 10 * 1024 && + value <= 1024 * 1024 + ){ + this._msg.chunksize = value; + } + } - this.getMsg = function(){ - return _msg; - }; + get chunksize(){ + return this._msg.chunksize; + } - this.getChunksize= function() { - return _msg.chunksize; - }; + /** + * Returns the prepared message with parameters and completeness checked + * @returns {Object|null} Object to be posted to gnupg, or null if + * incomplete + */ + get message() { + if (this.isComplete() === true){ + return this._msg; + } else { + return null; + } + } - /** - * Sets a parameter for the message. It validates with - * {@link permittedOperations} - * @param {String} param Parameter to set - * @param {any} value Value to set - * @returns {Boolean} If the parameter was set successfully - */ - this.setParameter = function ( param,value ){ - if (!param || typeof(param) !== 'string'){ + /** + * Sets a parameter for the message. It validates with + * {@link permittedOperations} + * @param {String} param Parameter to set + * @param {any} value Value to set + * @returns {Boolean} If the parameter was set successfully + */ + setParameter ( param,value ){ + if (!param || typeof(param) !== 'string'){ + return gpgme_error('PARAM_WRONG'); + } + let po = permittedOperations[this._msg.op]; + if (!po){ + return gpgme_error('MSG_WRONG_OP'); + } + let poparam = null; + if (po.required.hasOwnProperty(param)){ + poparam = po.required[param]; + } else if (po.optional.hasOwnProperty(param)){ + poparam = po.optional[param]; + } else { + return gpgme_error('PARAM_WRONG'); + } + // check incoming value for correctness + let checktype = function(val){ + switch(typeof(val)){ + case 'string': + if (poparam.allowed.indexOf(typeof(val)) >= 0 + && val.length > 0) { + return true; + } return gpgme_error('PARAM_WRONG'); - } - let po = permittedOperations[_msg.op]; - if (!po){ - return gpgme_error('MSG_WRONG_OP'); - } - let poparam = null; - if (po.required.hasOwnProperty(param)){ - poparam = po.required[param]; - } else if (po.optional.hasOwnProperty(param)){ - poparam = po.optional[param]; - } else { + case 'number': + if ( + poparam.allowed.indexOf('number') >= 0 + && isNaN(value) === false){ + return true; + } return gpgme_error('PARAM_WRONG'); - } - // check incoming value for correctness - let checktype = function(val){ - switch(typeof(val)){ - case 'string': - if (poparam.allowed.indexOf(typeof(val)) >= 0 - && val.length > 0) { - return true; + + case 'boolean': + if (poparam.allowed.indexOf('boolean') >= 0){ + return true; + } + return gpgme_error('PARAM_WRONG'); + case 'object': + if (Array.isArray(val)){ + if (poparam.array_allowed !== true){ + return gpgme_error('PARAM_WRONG'); } - return gpgme_error('PARAM_WRONG'); - case 'number': - if ( - poparam.allowed.indexOf('number') >= 0 - && isNaN(value) === false){ + for (let i=0; i < val.length; i++){ + let res = checktype(val[i]); + if (res !== true){ + return res; + } + } + if (val.length > 0) { return true; } - return gpgme_error('PARAM_WRONG'); - - case 'boolean': - if (poparam.allowed.indexOf('boolean') >= 0){ + } else if (val instanceof Uint8Array){ + if (poparam.allowed.indexOf('Uint8Array') >= 0){ return true; } return gpgme_error('PARAM_WRONG'); - case 'object': - if (Array.isArray(val)){ - if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); - } - for (let i=0; i < val.length; i++){ - let res = checktype(val[i]); - if (res !== true){ - return res; - } - } - if (val.length > 0) { - return true; - } - } else if (val instanceof Uint8Array){ - if (poparam.allowed.indexOf('Uint8Array') >= 0){ - return true; - } - return gpgme_error('PARAM_WRONG'); - } else { - return gpgme_error('PARAM_WRONG'); - } - break; - default: - return gpgme_error('PARAM_WRONG'); - } - }; - let typechecked = checktype(value); - if (typechecked !== true){ - return typechecked; - } - if (poparam.hasOwnProperty('allowed_data')){ - if (poparam.allowed_data.indexOf(value) < 0){ + } else { return gpgme_error('PARAM_WRONG'); } + break; + default: + return gpgme_error('PARAM_WRONG'); } - _msg[param] = value; - return true; }; - - - - /** - * Check if the message has the minimum requirements to be sent, that is - * all 'required' parameters according to {@link permittedOperations}. - * @returns {Boolean} true if message is complete. - */ - this.isComplete = function(){ - if (!_msg.op){ - return false; - } - let reqParams = Object.keys( - permittedOperations[_msg.op].required); - let msg_params = Object.keys(_msg); - for (let i=0; i < reqParams.length; i++){ - if (msg_params.indexOf(reqParams[i]) < 0){ - return false; - } + let typechecked = checktype(value); + if (typechecked !== true){ + return typechecked; + } + if (poparam.hasOwnProperty('allowed_data')){ + if (poparam.allowed_data.indexOf(value) < 0){ + return gpgme_error('PARAM_WRONG'); } - return true; - }; - /** - * Sends the Message via nativeMessaging and resolves with the answer. - * @returns {Promise} - * @async - */ - this.post = function(){ - let me = this; - return new Promise(function(resolve, reject) { - if (me.isComplete() === true) { - - let conn = Object.freeze(new Connection); - conn.post(me).then(function(response) { - resolve(response); - }, function(reason) { - reject(reason); - }); - } - else { - reject(gpgme_error('MSG_INCOMPLETE')); - } - }); - }; + } + this._msg[param] = value; + return true; } + /** - * Returns the prepared message with parameters and completeness checked - * @returns {Object|null} Object to be posted to gnupg, or null if - * incomplete + * Check if the message has the minimum requirements to be sent, that is + * all 'required' parameters according to {@link permittedOperations}. + * @returns {Boolean} true if message is complete. */ - get message(){ - if (this.isComplete() === true){ - return this.getMsg(); + isComplete(){ + if (!this._msg.op){ + return false; } - else { - return null; + let reqParams = Object.keys( + permittedOperations[this._msg.op].required); + let msg_params = Object.keys(this._msg); + for (let i=0; i < reqParams.length; i++){ + if (msg_params.indexOf(reqParams[i]) < 0){ + return false; + } } + return true; } - get operation(){ - return this.getOperation(); - } - get chunksize(){ - return this.getChunksize(); + /** + * Sends the Message via nativeMessaging and resolves with the answer. + * @returns {Promise} + * @async + */ + post (){ + let me = this; + return new Promise(function(resolve, reject) { + if (me.isComplete() === true) { + + let conn = new Connection; + conn.post(me).then(function(response) { + resolve(response); + }, function(reason) { + reject(reason); + }); + } + else { + reject(gpgme_error('MSG_INCOMPLETE')); + } + }); } + } diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index 55131b01..65365772 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -66,7 +66,7 @@ export function createSignature(sigObject){ } } } - return Object.freeze(new GPGME_Signature(sigObject)); + return new GPGME_Signature(sigObject); } @@ -82,101 +82,65 @@ export function createSignature(sigObject){ class GPGME_Signature { constructor(sigObject){ - let _rawSigObject = sigObject; - - this.getFingerprint = function(){ - if (!_rawSigObject.fingerprint){ - return gpgme_error('SIG_WRONG'); - } else { - return _rawSigObject.fingerprint; - } - }; - - /** - * The expiration of this Signature as Javascript date, or null if - * signature does not expire - * @returns {Date | null} - */ - this.getExpiration = function(){ - if (!_rawSigObject.exp_timestamp){ - return null; - } - return new Date(_rawSigObject.exp_timestamp* 1000); - }; - - /** - * The creation date of this Signature in Javascript Date - * @returns {Date} - */ - this.getTimestamp= function (){ - return new Date(_rawSigObject.timestamp * 1000); - }; - - /** - * The overall validity of the key. If false, errorDetails may contain - * additional information. - */ - this.getValid= function() { - if (_rawSigObject.summary.valid === true){ - return true; - } else { - return false; - } - }; - - /** - * gives more information on non-valid signatures. Refer to the gpgme - * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html - * for details on the values. - * @returns {Object} Object with boolean properties - */ - this.getErrorDetails = function (){ - let properties = ['revoked', 'key-expired', 'sig-expired', - 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', - 'sys-error']; - let result = {}; - for (let i=0; i< properties.length; i++){ - if ( _rawSigObject.hasOwnProperty(properties[i]) ){ - result[properties[i]] = _rawSigObject[properties[i]]; - } - } - return result; - }; + this._rawSigObject = sigObject; } - - /** - * Convenience getter for {@link getFingerprint} - */ get fingerprint(){ - return this.getFingerprint(); + if (!this._rawSigObject.fingerprint){ + return gpgme_error('SIG_WRONG'); + } else { + return this._rawSigObject.fingerprint; + } } /** - * Convenience getter for {@link getExpiration} + * The expiration of this Signature as Javascript date, or null if + * signature does not expire + * @returns {Date | null} */ get expiration(){ - return this.getExpiration(); + if (!this._rawSigObject.exp_timestamp){ + return null; + } + return new Date(this._rawSigObject.exp_timestamp* 1000); } /** - * Convenience getter for {@link getTimeStamp} + * The creation date of this Signature in Javascript Date + * @returns {Date} */ - get timestamp(){ - return this.getTimestamp(); + get timestamp (){ + return new Date(this._rawSigObject.timestamp * 1000); } /** - * Convenience getter for {@link getValid} + * The overall validity of the key. If false, errorDetails may contain + * additional information. */ - get valid(){ - return this.getValid(); + get valid () { + if (this._rawSigObject.summary.valid === true){ + return true; + } else { + return false; + } } /** - * Convenience getter for {@link getErrorDetails} + * gives more information on non-valid signatures. Refer to the gpgme + * docs https://www.gnupg.org/documentation/manuals/gpgme/Verify.html + * for details on the values. + * @returns {Object} Object with boolean properties */ get errorDetails(){ - return this.getErrorDetails(); + let properties = ['revoked', 'key-expired', 'sig-expired', + 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', + 'sys-error']; + let result = {}; + for (let i=0; i< properties.length; i++){ + if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ + result[properties[i]] = this._rawSigObject[properties[i]]; + } + } + return result; } } diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 18164366..4aa51759 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -85,243 +85,226 @@ import { createSignature } from './Signature'; export class GpgME { constructor(){ - let _Keyring = null; - - /** - * Sets a new Keyring to be used - * @param {GPGME_Keyring} keyring - */ - this.setKeyring = function(keyring){ - if (keyring && keyring instanceof GPGME_Keyring){ - _Keyring = keyring; - } - }; + this._Keyring = null; + } - /** - * Accesses the {@link GPGME_Keyring}. - */ - this.getKeyring = function(){ - if (!_Keyring){ - _Keyring = Object.freeze(new GPGME_Keyring); - } - return _Keyring; - }; + /** + * setter for {@link setKeyring}. + * @param {GPGME_Keyring} keyring A Keyring to use + */ + set Keyring(keyring){ + if (keyring && keyring instanceof GPGME_Keyring){ + this._Keyring = keyring; + } + } + /** + * Accesses the {@link GPGME_Keyring}. + */ + get Keyring(){ + if (!this._Keyring){ + this._Keyring = new GPGME_Keyring; + } + return this._Keyring; + } - /** - * Encrypt (and optionally sign) data - * @param {String|Object} data text/data to be encrypted as String. Also - * accepts Objects with a getText method - * @param {inputKeys} publicKeys - * Keys used to encrypt the message - * @param {inputKeys} secretKeys (optional) Keys used to sign the - * message. If Keys are present, the operation requested is assumed - * to be 'encrypt and sign' - * @param {Boolean} base64 (optional) The data will be interpreted as - * base64 encoded data. - * @param {Boolean} armor (optional) Request the output as armored - * block. - * @param {Boolean} wildcard (optional) If true, recipient information - * will not be added to the message. - * @param {Object} additional use additional valid gpg options as - * defined in {@link permittedOperations} - * @returns {Promise} Object containing the encrypted - * message and additional info. - * @async - */ - this.encrypt = function (data, publicKeys, secretKeys, base64=false, - armor=true, wildcard=false, additional = {} - ){ - let msg = createMessage('encrypt'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - msg.setParameter('armor', armor); - msg.setParameter('always-trust', true); - if (base64 === true) { - msg.setParameter('base64', true); - } - let pubkeys = toKeyIdArray(publicKeys); - msg.setParameter('keys', pubkeys); - let sigkeys = toKeyIdArray(secretKeys); - if (sigkeys.length > 0) { - msg.setParameter('signing_keys', sigkeys); - } - putData(msg, data); - if (wildcard === true){ - msg.setParameter('throw-keyids', true); - } - if (additional){ - let additional_Keys = Object.keys(additional); - for (let k = 0; k < additional_Keys.length; k++) { - msg.setParameter(additional_Keys[k], - additional[additional_Keys[k]]); - } - } - if (msg.isComplete() === true){ - return msg.post(); - } else { - return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + /** + * Encrypt (and optionally sign) data + * @param {String|Object} data text/data to be encrypted as String. Also + * accepts Objects with a getText method + * @param {inputKeys} publicKeys + * Keys used to encrypt the message + * @param {inputKeys} secretKeys (optional) Keys used to sign the + * message. If Keys are present, the operation requested is assumed + * to be 'encrypt and sign' + * @param {Boolean} base64 (optional) The data will be interpreted as + * base64 encoded data. + * @param {Boolean} armor (optional) Request the output as armored + * block. + * @param {Boolean} wildcard (optional) If true, recipient information + * will not be added to the message. + * @param {Object} additional use additional valid gpg options as + * defined in {@link permittedOperations} + * @returns {Promise} Object containing the encrypted + * message and additional info. + * @async + */ + encrypt (data, publicKeys, secretKeys, base64=false, armor=true, + wildcard=false, additional = {}){ + let msg = createMessage('encrypt'); + if (msg instanceof Error){ + return Promise.reject(msg); + } + msg.setParameter('armor', armor); + msg.setParameter('always-trust', true); + if (base64 === true) { + msg.setParameter('base64', true); + } + let pubkeys = toKeyIdArray(publicKeys); + msg.setParameter('keys', pubkeys); + let sigkeys = toKeyIdArray(secretKeys); + if (sigkeys.length > 0) { + msg.setParameter('signing_keys', sigkeys); + } + putData(msg, data); + if (wildcard === true){ + msg.setParameter('throw-keyids', true); + } + if (additional){ + let additional_Keys = Object.keys(additional); + for (let k = 0; k < additional_Keys.length; k++) { + msg.setParameter(additional_Keys[k], + additional[additional_Keys[k]]); } - }; + } + if (msg.isComplete() === true){ + return msg.post(); + } else { + return Promise.reject(gpgme_error('MSG_INCOMPLETE')); + } + } - /** - * Decrypts a Message - * @param {String|Object} data text/data to be decrypted. Accepts - * Strings and Objects with a getText method - * @param {Boolean} base64 (optional) false if the data is an armored - * block, true if it is base64 encoded binary data - * @returns {Promise} Decrypted Message and information - * @async - */ - this.decrypt = function (data, base64=false){ - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let msg = createMessage('decrypt'); + /** + * Decrypts a Message + * @param {String|Object} data text/data to be decrypted. Accepts + * Strings and Objects with a getText method + * @param {Boolean} base64 (optional) false if the data is an armored + * block, true if it is base64 encoded binary data + * @returns {Promise} Decrypted Message and information + * @async + */ + decrypt (data, base64=false){ + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let msg = createMessage('decrypt'); - if (msg instanceof Error){ - return Promise.reject(msg); - } - if (base64 === true){ - msg.setParameter('base64', true); - } - putData(msg, data); - if (base64 === true){ - msg.setParameter('base64', true); - } - return new Promise(function(resolve, reject){ - msg.post().then(function(result){ - let _result = {data: result.data}; - _result.base64 = result.base64 ? true: false; - _result.is_mime = result.is_mime ? true: false; - if (result.file_name){ - _result.file_name = result.file_name; - } else { - _result.file_name = null; - } - if ( - result.hasOwnProperty('signatures') && - Array.isArray(result.signatures) - ) { - _result.signatures = collectSignatures( - result.signatures); - } - resolve(_result); - }, function(error){ - reject(error); - }); + if (msg instanceof Error){ + return Promise.reject(msg); + } + if (base64 === true){ + msg.setParameter('base64', true); + } + putData(msg, data); + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function(result){ + let _result = {data: result.data}; + _result.base64 = result.base64 ? true: false; + _result.is_mime = result.is_mime ? true: false; + if (result.file_name){ + _result.file_name = result.file_name; + } else { + _result.file_name = null; + } + if ( + result.hasOwnProperty('signatures') && + Array.isArray(result.signatures) + ) { + _result.signatures = collectSignatures( + result.signatures); + } + resolve(_result); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Sign a Message - * @param {String|Object} data text/data to be signed. Accepts Strings - * and Objects with a getText method. - * @param {inputKeys} keys The key/keys to use for signing - * @param {String} mode The signing mode. Currently supported: - * 'clearsign':The Message is embedded into the signature; - * 'detached': The signature is stored separately - * @param {Boolean} base64 input is considered base64 - * @returns {Promise} - * @async - */ - this.sign = function (data, keys, mode='clearsign', base64=false) { - if (data === undefined){ - return Promise.reject(gpgme_error('MSG_EMPTY')); - } - let key_arr = toKeyIdArray(keys); - if (key_arr.length === 0){ - return Promise.reject(gpgme_error('MSG_NO_KEYS')); - } - let msg = createMessage('sign'); + /** + * Sign a Message + * @param {String|Object} data text/data to be signed. Accepts Strings + * and Objects with a getText method. + * @param {inputKeys} keys The key/keys to use for signing + * @param {String} mode The signing mode. Currently supported: + * 'clearsign':The Message is embedded into the signature; + * 'detached': The signature is stored separately + * @param {Boolean} base64 input is considered base64 + * @returns {Promise} + * @async + */ + sign (data, keys, mode='clearsign', base64=false) { + if (data === undefined){ + return Promise.reject(gpgme_error('MSG_EMPTY')); + } + let key_arr = toKeyIdArray(keys); + if (key_arr.length === 0){ + return Promise.reject(gpgme_error('MSG_NO_KEYS')); + } + let msg = createMessage('sign'); - msg.setParameter('keys', key_arr); - if (base64 === true){ - msg.setParameter('base64', true); + msg.setParameter('keys', key_arr); + if (base64 === true){ + msg.setParameter('base64', true); + } + msg.setParameter('mode', mode); + putData(msg, data); + return new Promise(function(resolve,reject) { + if (mode ==='detached'){ + msg.expected ='base64'; } - msg.setParameter('mode', mode); - putData(msg, data); - return new Promise(function(resolve,reject) { - if (mode ==='detached'){ - msg.setExpect('base64'); + msg.post().then( function(message) { + if (mode === 'clearsign'){ + resolve({ + data: message.data} + ); + } else if (mode === 'detached') { + resolve({ + data: data, + signature: message.data + }); } - msg.post().then( function(message) { - if (mode === 'clearsign'){ - resolve({ - data: message.data} - ); - } else if (mode === 'detached') { - resolve({ - data: data, - signature: message.data - }); - } - }, function(error){ - reject(error); - }); + }, function(error){ + reject(error); }); - }; + }); + } - /** - * Verifies data. - * @param {String|Object} data text/data to be verified. Accepts Strings - * and Objects with a getText method - * @param {String} (optional) A detached signature. If not present, - * opaque mode is assumed - * @param {Boolean} (optional) Data and signature are base64 encoded - * @returns {Promise} - *@async - */ - this.verify= function (data, signature, base64 = false){ - let msg = createMessage('verify'); - let dt = putData(msg, data); - if (dt instanceof Error){ - return Promise.reject(dt); + /** + * Verifies data. + * @param {String|Object} data text/data to be verified. Accepts Strings + * and Objects with a getText method + * @param {String} (optional) A detached signature. If not present, + * opaque mode is assumed + * @param {Boolean} (optional) Data and signature are base64 encoded + * @returns {Promise} + *@async + */ + verify (data, signature, base64 = false){ + let msg = createMessage('verify'); + let dt = putData(msg, data); + if (dt instanceof Error){ + return Promise.reject(dt); + } + if (signature){ + if (typeof(signature)!== 'string'){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } else { + msg.setParameter('signature', signature); } - if (signature){ - if (typeof(signature)!== 'string'){ - return Promise.reject(gpgme_error('PARAM_WRONG')); + } + if (base64 === true){ + msg.setParameter('base64', true); + } + return new Promise(function(resolve, reject){ + msg.post().then(function (message){ + if (!message.info || !message.info.signatures){ + reject(gpgme_error('SIG_NO_SIGS')); } else { - msg.setParameter('signature', signature); - } - } - if (base64 === true){ - msg.setParameter('base64', true); - } - return new Promise(function(resolve, reject){ - msg.post().then(function (message){ - if (!message.info || !message.info.signatures){ - reject(gpgme_error('SIG_NO_SIGS')); - } else { - let _result = collectSignatures( - message.info.signatures); - _result.is_mime = message.info.is_mime? true: false; - if (message.info.filename){ - _result.file_name = message.info.filename; - } - _result.data = message.data; - resolve(_result); + let _result = collectSignatures( + message.info.signatures); + _result.is_mime = message.info.is_mime? true: false; + if (message.info.filename){ + _result.file_name = message.info.filename; } - }, function(error){ - reject(error); - }); + _result.data = message.data; + resolve(_result); + } + }, function(error){ + reject(error); }); - }; - } - - /** - * setter for {@link setKeyring}. - * @param {GPGME_Keyring} keyring A Keyring to use - */ - set Keyring(keyring){ - this.setKeyring(keyring); - } - - /** - * Accesses the {@link GPGME_Keyring}. - */ - get Keyring(){ - return this.getKeyring(); + }); } } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index 51f07538..ad4b05b0 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -34,11 +34,11 @@ import { Connection } from './Connection'; */ function init(){ return new Promise(function(resolve, reject){ - const connection = Object.freeze(new Connection); + const connection = new Connection; connection.checkConnection(false).then( function(result){ if (result === true) { - resolve(Object.freeze(new GpgME())); + resolve(new GpgME()); } else { reject(gpgme_error('CONN_NO_CONNECT')); } @@ -48,5 +48,5 @@ function init(){ }); } -const exportvalue = Object.freeze({init:init}); +const exportvalue = {init:init}; export default exportvalue; \ No newline at end of file diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 25023bcb..47eeabf2 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -27,7 +27,7 @@ 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 { createKey } from './src/Key'; import { GPGME_Keyring } from './src/Keyring'; import {GPGME_Message, createMessage} from './src/Message'; @@ -116,14 +116,6 @@ function unittests (){ 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); @@ -169,15 +161,9 @@ function unittests (){ }); 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]); @@ -220,7 +206,6 @@ function unittests (){ it('Non-cached key async hasSecret (no secret in Key)', function (done){ let key = createKey(kp.validFingerprintNoSecret, true); - expect(key).to.be.an.instanceof(GPGME_Key); key.get('hasSecret').then(function(result){ expect(result).to.be.a('boolean'); expect(result).to.equal(false); @@ -246,32 +231,21 @@ function unittests (){ } }); - 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'); - } - }); - - it('Overwriting getFingerprint does not work', function(){ - const evilFunction = function(){ - return 'bad Data'; - }; - let key = createKey(kp.validKeyFingerprint, true); - expect(key.fingerprint).to.equal(kp.validKeyFingerprint); - try { - key.getFingerprint = evilFunction; - } - catch(e) { - expect(e).to.be.an.instanceof(TypeError); - } - expect(key.fingerprint).to.equal(kp.validKeyFingerprint); - expect(key.getFingerprint).to.not.equal(evilFunction); - }); - // TODO: tests for subkeys - // TODO: tests for userids - // TODO: some invalid tests for key/keyring + // it('Overwriting getFingerprint does not work', function(){ + // const evilFunction = function(){ + // return 'bad Data'; + // }; + // let key = createKey(kp.validKeyFingerprint, true); + // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + // try { + // key.getFingerprint = evilFunction; + // } + // catch(e) { + // expect(e).to.be.an.instanceof(TypeError); + // } + // expect(key.fingerprint).to.equal(kp.validKeyFingerprint); + // expect(key.getFingerprint).to.not.equal(evilFunction); + // }); }); describe('GPGME_Keyring', function(){ @@ -287,10 +261,7 @@ function unittests (){ 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('hasSecret')).to.be.a('boolean'); - // expect(result[0].get('armored')).to.include( - // '-----END PGP PUBLIC KEY BLOCK-----'); done(); }); } @@ -302,7 +273,6 @@ function unittests (){ 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('hasSecret')).to.be.a('boolean'); done(); } -- cgit v1.2.3 From dd32daad0bb21e3d5567326d0b2e548ff8510431 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 20 Aug 2018 15:12:01 +0200 Subject: js: add and apply eslint rules -- * mainly spacing, see .eslintrc.json for details --- lang/js/.eslintrc.json | 19 +- .../BrowserTestExtension/tests/KeyImportExport.js | 46 ++-- lang/js/BrowserTestExtension/tests/KeyInfos.js | 12 +- lang/js/BrowserTestExtension/tests/decryptTest.js | 14 +- .../tests/encryptDecryptTest.js | 6 +- lang/js/BrowserTestExtension/tests/encryptTest.js | 10 +- lang/js/BrowserTestExtension/tests/inputvalues.js | 16 +- .../BrowserTestExtension/tests/longRunningTests.js | 6 +- lang/js/BrowserTestExtension/tests/signTest.js | 4 +- lang/js/BrowserTestExtension/tests/startup.js | 6 +- lang/js/BrowserTestExtension/tests/verifyTest.js | 14 +- lang/js/DemoExtension/entry.js | 2 +- lang/js/DemoExtension/maindemo.js | 40 ++-- lang/js/src/Connection.js | 40 ++-- lang/js/src/Errors.js | 8 +- lang/js/src/Helpers.js | 16 +- lang/js/src/Key.js | 243 +++++++++++---------- lang/js/src/Keyring.js | 83 ++++--- lang/js/src/Message.js | 32 +-- lang/js/src/Signature.js | 23 +- lang/js/src/gpgmejs.js | 40 ++-- lang/js/src/index.js | 10 +- lang/js/src/permittedOperations.js | 12 +- lang/js/unittest_inputvalues.js | 8 +- lang/js/unittests.js | 108 ++++----- 25 files changed, 421 insertions(+), 397 deletions(-) diff --git a/lang/js/.eslintrc.json b/lang/js/.eslintrc.json index ad82400b..dc3be2e4 100644 --- a/lang/js/.eslintrc.json +++ b/lang/js/.eslintrc.json @@ -27,6 +27,23 @@ "no-var": [ "warn" ], - "max-len": 1 + "max-len": 1, + "default-case": 2, + "no-invalid-this": 2, + "no-lone-blocks": 1, + "no-self-compare": 2, + "radix": 2, + "no-use-before-define": ["error", { + "functions": false, + "classes": false, + "variables": true + }], + "no-useless-constructor": 1, + "space-before-function-paren": ["error", "always"], + "keyword-spacing": 2, + "spaced-comment": 1, + "space-unary-ops": 2, + "object-curly-spacing": ["error", "always"], + "array-bracket-spacing": ["error", "never"] } } \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js index 6d0ba106..f52b790a 100644 --- a/lang/js/BrowserTestExtension/tests/KeyImportExport.js +++ b/lang/js/BrowserTestExtension/tests/KeyImportExport.js @@ -31,16 +31,16 @@ describe('Key importing', function () { const changedKey = ImportablePublicKey.keyChangedUserId; let context = null; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; context.Keyring.getKeys(fpr).then( - function(result){ + function (result){ if (result.length === 1) { - result[0].delete().then(function(){ + result[0].delete().then(function (){ done(); - },function(){ + },function (){ done(); }); } else { @@ -50,14 +50,14 @@ describe('Key importing', function () { }); }); - afterEach(function(done){ + afterEach(function (done){ // delete the test key if still present context.Keyring.getKeys(fpr).then( - function(result){ + function (result){ if (result.length === 1) { - result[0].delete().then(function(){ + result[0].delete().then(function (){ done(); - },function(){ + },function (){ done(); }); } else { @@ -67,10 +67,10 @@ describe('Key importing', function () { }); it('Importing Key', function (done) { - context.Keyring.getKeys(fpr).then(function(result){ + context.Keyring.getKeys(fpr).then(function (result){ expect(result).to.be.an('array'); expect(result.length).to.equal(0); - context.Keyring.importKey(pubKey).then(function(result){ + context.Keyring.importKey(pubKey).then(function (result){ expect(result.Keys).to.be.an('array'); expect(result.Keys[0]).to.not.be.undefined; expect(result.Keys[0].key).to.be.an('object'); @@ -83,12 +83,12 @@ describe('Key importing', function () { }); }); - it('Updating Key', function(done){ + it('Updating Key', function (done){ context.Keyring.importKey(pubKey) - .then(function(result){ + .then(function (result){ expect(result.Keys[0].key).to.not.be.undefined; expect(result.Keys[0].status).to.equal('newkey'); - context.Keyring.importKey(changedKey).then(function(res){ + context.Keyring.importKey(changedKey).then(function (res){ expect(res.Keys[0].key).to.be.an('object'); expect(res.Keys[0].key.fingerprint).to.equal(fpr); expect(res.Keys[0].status).to.equal('change'); @@ -101,24 +101,24 @@ describe('Key importing', function () { }); }); - it('Deleting Key', function(done) { - context.Keyring.importKey(pubKey).then(function(result){ + it('Deleting Key', function (done) { + context.Keyring.importKey(pubKey).then(function (result){ expect(result.Keys[0].key).to.be.an('object'); expect(result.Keys[0].key.fingerprint).to.equal(fpr); - result.Keys[0].key.delete().then(function(result){ + result.Keys[0].key.delete().then(function (result){ expect(result).to.be.true; done(); }); }); }); - it('Import result feedback', function(done){ - context.Keyring.importKey(pubKey, true).then(function(result){ + it('Import result feedback', function (done){ + context.Keyring.importKey(pubKey, true).then(function (result){ expect(result).to.be.an('object'); expect(result.Keys[0]).to.be.an('object'); expect(result.Keys[0].key.fingerprint).to.equal(fpr); expect(result.Keys[0].status).to.equal('newkey'); - result.Keys[0].key.getArmor().then(function(armor){ + result.Keys[0].key.getArmor().then(function (armor){ expect(armor).to.be.a('string'); done(); }); @@ -126,8 +126,8 @@ describe('Key importing', function () { }); it('exporting armored Key with getKeysArmored', function (done) { - context.Keyring.importKey(pubKey).then(function(){ - context.Keyring.getKeysArmored(fpr).then(function(result){ + context.Keyring.importKey(pubKey).then(function (){ + context.Keyring.getKeysArmored(fpr).then(function (result){ expect(result).to.be.an('object'); expect(result.armored).to.be.a('string'); expect(result.secret_fprs).to.be.undefined; @@ -138,7 +138,7 @@ describe('Key importing', function () { it('Exporting Key (including secret fingerprints)', function (done) { const key_secret = inputvalues.encrypt.good.fingerprint; - context.Keyring.getKeysArmored(key_secret, true).then(function(result){ + context.Keyring.getKeysArmored(key_secret, true).then(function (result){ expect(result).to.be.an('object'); expect(result.armored).to.be.a('string'); expect(result.secret_fprs).to.be.an('array'); diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js index 1829f227..e1caabe1 100644 --- a/lang/js/BrowserTestExtension/tests/KeyInfos.js +++ b/lang/js/BrowserTestExtension/tests/KeyInfos.js @@ -26,17 +26,17 @@ describe('Key information', function () { let context = null; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); }); - it('A fingerprint is consistently returned upper case hex', function(done){ + it('A fingerprint is consistently returned upper case hex', function (done){ const mixedCase = inputvalues.encrypt.good.fingerprint_mixedcase; - context.Keyring.getKeys(mixedCase).then(function(result){ + context.Keyring.getKeys(mixedCase).then(function (result){ expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].fingerprint).to.equal(mixedCase.toUpperCase()); @@ -44,9 +44,9 @@ describe('Key information', function () { }); }); - it('A userId keeps their encoding', function(done){ + it('A userId keeps their encoding', function (done){ context.Keyring.importKey(inputvalues.publicKeyNonAscii.key, true) - .then(function(result){ + .then(function (result){ expect(result.Keys[0]).to.be.an('object'); const user = result.Keys[0].key.get('userids')[0]; expect(user.get('name')).to.equal( diff --git a/lang/js/BrowserTestExtension/tests/decryptTest.js b/lang/js/BrowserTestExtension/tests/decryptTest.js index c6b3a3c5..a3f48daa 100644 --- a/lang/js/BrowserTestExtension/tests/decryptTest.js +++ b/lang/js/BrowserTestExtension/tests/decryptTest.js @@ -28,9 +28,9 @@ describe('Decryption', function () { let context = null; const good_fpr = inputvalues.encrypt.good.fingerprint; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); @@ -39,8 +39,8 @@ describe('Decryption', function () { it('Decryption of random string fails', function (done) { let data = bigString(20 * 1024); context.decrypt(data).then( - function(){}, - function(error){ + function (){}, + function (error){ expect(error).to.be.an('error'); expect(error.code).to.equal('GNUPG_ERROR'); done(); @@ -49,10 +49,10 @@ describe('Decryption', function () { it('Decryption of slightly corrupted message fails', function (done) { const data = bigString(10000); - context.encrypt(data, good_fpr).then(function(enc){ + context.encrypt(data, good_fpr).then(function (enc){ context.decrypt(sabotageMsg(enc.data)).then( - function(){}, - function(error){ + function (){}, + function (error){ expect(error).to.be.an('error'); expect(error.code).to.equal('GNUPG_ERROR'); done(); diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js index 80b293d2..28c98d98 100644 --- a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js @@ -24,13 +24,13 @@ /* global describe, it, expect, before, Gpgmejs */ /* global inputvalues, encryptedData, bigString, bigBoringString */ -describe('Encryption and Decryption', function () { +describe('Encryption and Decryption', function (){ let context = null; let good_fpr = inputvalues.encrypt.good.fingerprint; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js index 3ead8153..a242af5f 100644 --- a/lang/js/BrowserTestExtension/tests/encryptTest.js +++ b/lang/js/BrowserTestExtension/tests/encryptTest.js @@ -27,9 +27,9 @@ describe('Encryption', function () { let context = null; const good_fpr = inputvalues.encrypt.good.fingerprint; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); @@ -64,7 +64,7 @@ describe('Encryption', function () { const data = inputvalues.encrypt.good.data; context.encrypt(data,null).then(function (answer) { expect(answer).to.be.undefined; - }, function(error){ + }, function (error){ expect(error).to.be.an('Error'); expect(error.code).to.equal('MSG_INCOMPLETE'); done(); @@ -86,7 +86,7 @@ describe('Encryption', function () { const bad_fpr = inputvalues.encrypt.bad.fingerprint; context.encrypt(data, bad_fpr).then(function (answer) { expect(answer).to.be.undefined; - }, function(error){ + }, function (error){ expect(error).to.be.an('Error'); expect(error.code).to.not.be.undefined; expect(error.code).to.equal('GNUPG_ERROR'); @@ -98,7 +98,7 @@ describe('Encryption', function () { const data = fixedLengthString(65); context.encrypt(data, good_fpr).then(function (answer) { expect(answer).to.be.undefined; - }, function(error){ + }, function (error){ expect(error).to.be.an.instanceof(Error); // TODO: there is a 64 MB hard limit at least in chrome at: // chromium//extensions/renderer/messaging_util.cc: diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index b33d985b..f84ac955 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -126,7 +126,7 @@ const inputvalues = {// eslint-disable-line no-unused-vars }; // (Pseudo-)Random String covering all of utf8. -function bigString(length){// eslint-disable-line no-unused-vars +function bigString (length){// eslint-disable-line no-unused-vars let arr = []; for (let i= 0; i < length; i++){ arr.push(String.fromCharCode( @@ -136,7 +136,7 @@ function bigString(length){// eslint-disable-line no-unused-vars return arr.join(''); } -function fixedLengthString(megabytes){// eslint-disable-line no-unused-vars +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++){ @@ -148,7 +148,7 @@ function fixedLengthString(megabytes){// eslint-disable-line no-unused-vars } // (Pseudo-)Random Uint8Array, given size in Megabytes -function bigUint8(megabytes){// eslint-disable-line no-unused-vars +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++){ @@ -159,7 +159,7 @@ function bigUint8(megabytes){// eslint-disable-line no-unused-vars // (Pseudo-)Random string with very limited charset // (ascii only, no control chars) -function bigBoringString(megabytes){// eslint-disable-line no-unused-vars +function bigBoringString (megabytes){// eslint-disable-line no-unused-vars let maxlength = 1024 * 1024 * megabytes; let string = []; let chars = @@ -173,7 +173,7 @@ function bigBoringString(megabytes){// eslint-disable-line no-unused-vars // 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){ +function slightlyLessBoringString (megabytes, set){ let maxlength = 1024 * 1024 * megabytes; let string = []; let chars = ''; @@ -291,13 +291,13 @@ const ImportablePublicKey = {// eslint-disable-line no-unused-vars * preserve) header/footer */ // eslint-disable-next-line no-unused-vars -function sabotageMsg(msg, rate = 0.01, p= [35,35]){ +function sabotageMsg (msg, rate = 0.01, p= [35,35]){ const iterations = Math.floor(Math.random() * msg.length * rate) + 1; const base64_set = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'; for (let i=0; i < iterations; i++){ let str0, str1, str2; - const chosePosition = function(){ + const chosePosition = function (){ let position = Math.floor( Math.random() * (msg.length - p[0] + p[1])) + p[0]; @@ -310,7 +310,7 @@ function sabotageMsg(msg, rate = 0.01, p= [35,35]){ } }; chosePosition(); - let new1 = function(){ + let new1 = function (){ let n = base64_set[Math.floor(Math.random() * 64)]; return (n === str1) ? new1() : n; }; diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js index 03f0390c..240a6b93 100644 --- a/lang/js/BrowserTestExtension/tests/longRunningTests.js +++ b/lang/js/BrowserTestExtension/tests/longRunningTests.js @@ -26,9 +26,9 @@ describe('Long running Encryption/Decryption', function () { let context = null; const good_fpr = inputvalues.encrypt.good.fingerprint; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); @@ -43,7 +43,7 @@ describe('Long running Encryption/Decryption', function () { 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){ + 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); diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js index 2763dadf..f5bd9c1d 100644 --- a/lang/js/BrowserTestExtension/tests/signTest.js +++ b/lang/js/BrowserTestExtension/tests/signTest.js @@ -28,9 +28,9 @@ describe('Signing', function () { let context = null; const good_fpr = inputvalues.encrypt.good.fingerprint; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js index 63358aa9..cf5b0999 100644 --- a/lang/js/BrowserTestExtension/tests/startup.js +++ b/lang/js/BrowserTestExtension/tests/startup.js @@ -23,12 +23,12 @@ /* global describe, it, expect, Gpgmejs, inputvalues */ -describe('GPGME context', function(){ - it('Starting a GpgME instance', function(done){ +describe('GPGME context', function (){ + it('Starting a GpgME instance', function (done){ let prm = Gpgmejs.init(); const input = inputvalues.someInputParameter; prm.then( - function(context){ + function (context){ expect(context).to.be.an('object'); expect(context.encrypt).to.be.a('function'); expect(context.decrypt).to.be.a('function'); diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js index 1617e2dc..82aaf564 100644 --- a/lang/js/BrowserTestExtension/tests/verifyTest.js +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -27,16 +27,16 @@ describe('Verifying data', function () { let context = null; - before(function(done){ + before(function (done){ const prm = Gpgmejs.init(); - prm.then(function(gpgmejs){ + prm.then(function (gpgmejs){ context = gpgmejs; done(); }); }); it('Successful verify message', function (done) { const message = inputvalues.signedMessage.good; - context.verify(message).then(function(result){ + context.verify(message).then(function (result){ expect(result.data).to.be.a('string'); expect(result.all_valid).to.be.true; expect(result.count).to.equal(1); @@ -50,7 +50,7 @@ describe('Verifying data', function () { it('Successfully recognize changed cleartext', function (done) { const message = inputvalues.signedMessage.bad; - context.verify(message).then(function(result){ + context.verify(message).then(function (result){ expect(result.data).to.be.a('string'); expect(result.all_valid).to.be.false; expect(result.count).to.equal(1); @@ -65,9 +65,9 @@ describe('Verifying data', function () { it('Encrypt-Sign-Verify random message', function (done) { const message = bigString(2000); let fpr = inputvalues.encrypt.good.fingerprint; - context.encrypt(message, fpr).then(function(message_enc){ - context.sign(message_enc.data, fpr).then(function(message_encsign){ - context.verify(message_encsign.data).then(function(result){ + context.encrypt(message, fpr).then(function (message_enc){ + context.sign(message_enc.data, fpr).then(function (message_encsign){ + context.verify(message_encsign.data).then(function (result){ expect(result.data).to.equal(message_enc.data); expect(result.data).to.be.a('string'); expect(result.all_valid).to.be.true; diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js index 77b96f92..fd261a0b 100644 --- a/lang/js/DemoExtension/entry.js +++ b/lang/js/DemoExtension/entry.js @@ -23,7 +23,7 @@ /* global chrome */ -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { chrome.tabs.create({ url: './mainui.html' }); diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index 4cae934e..8d190852 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -23,67 +23,67 @@ /* global document, Gpgmejs */ -document.addEventListener('DOMContentLoaded', function() { - Gpgmejs.init().then(function(gpgmejs){ +document.addEventListener('DOMContentLoaded', function () { + Gpgmejs.init().then(function (gpgmejs){ document.getElementById('buttonencrypt').addEventListener('click', - function(){ + function (){ let data = document.getElementById('inputtext').value; let keyId = document.getElementById('pubkey').value; gpgmejs.encrypt(data, keyId).then( - function(answer){ + function (answer){ if (answer.data){ document.getElementById( 'answer').value = answer.data; } - }, function(errormsg){ + }, function (errormsg){ alert( errormsg.message); }); }); document.getElementById('buttondecrypt').addEventListener('click', - function(){ + function (){ let data = document.getElementById('inputtext').value; gpgmejs.decrypt(data).then( - function(answer){ + function (answer){ if (answer.data){ document.getElementById( 'answer').value = answer.data; } - }, function(errormsg){ + }, function (errormsg){ alert(errormsg.message); }); }); document.getElementById('getdefaultkey').addEventListener('click', - function(){ - gpgmejs.Keyring.getDefaultKey().then(function(answer){ + function (){ + gpgmejs.Keyring.getDefaultKey().then(function (answer){ document.getElementById('pubkey').value = answer.fingerprint; - }, function(errormsg){ + }, function (errormsg){ alert(errormsg.message); }); }); document.getElementById('signtext').addEventListener('click', - function(){ + function (){ let data = document.getElementById('inputtext').value; let keyId = document.getElementById('pubkey').value; gpgmejs.sign(data, keyId).then( - function(answer){ + function (answer){ if (answer.data){ document.getElementById( 'answer').value = answer.data; } - }, function(errormsg){ + }, function (errormsg){ alert( errormsg.message); }); }); document.getElementById('verifytext').addEventListener('click', - function(){ + function (){ let data = document.getElementById('inputtext').value; gpgmejs.verify(data).then( - function(answer){ + function (answer){ let vals = ''; if (answer.all_valid === true){ vals = 'Success! '; @@ -94,14 +94,14 @@ document.addEventListener('DOMContentLoaded', function() { + answer.count + ' signature(s) were successfully ' + 'verified.\n\n' + answer.data; document.getElementById('answer').value = vals; - }, function(errormsg){ + }, function (errormsg){ alert( errormsg.message); }); }); document.getElementById('searchkey').addEventListener('click', - function(){ + function (){ let data = document.getElementById('inputtext').value; - gpgmejs.Keyring.getKeys(data, true, true).then(function(keys){ + gpgmejs.Keyring.getKeys(data, true, true).then(function (keys){ if (keys.length === 1){ document.getElementById( 'pubkey').value = keys[0].fingerprint; @@ -111,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() { } else { alert('No keys found'); } - }, function(errormsg){ + }, function (errormsg){ alert( errormsg.message); }); }); diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js index a60fd215..928ac681 100644 --- a/lang/js/src/Connection.js +++ b/lang/js/src/Connection.js @@ -38,14 +38,14 @@ import { decode } from './Helpers'; */ export class Connection{ - constructor(){ + constructor (){ this._connection = chrome.runtime.connectNative('gpgmejson'); } /** * Immediately closes an open port. */ - disconnect() { + disconnect () { if (this._connection){ this._connection.disconnect(); this._connection = null; @@ -81,17 +81,17 @@ export class Connection{ return this.post(msg); } else { let me = this; - return new Promise(function(resolve) { + return new Promise(function (resolve) { Promise.race([ me.post(msg), - new Promise(function(resolve, reject){ - setTimeout(function(){ + new Promise(function (resolve, reject){ + setTimeout(function (){ reject(gpgme_error('CONN_TIMEOUT')); }, 500); }) - ]).then(function(){ // success + ]).then(function (){ // success resolve(true); - }, function(){ // failure + }, function (){ // failure resolve(false); }); }); @@ -107,7 +107,7 @@ export class Connection{ * @returns {Promise} The collected answer * @async */ - post(message){ + post (message){ if (!message || !(message instanceof GPGME_Message)){ this.disconnect(); return Promise.reject(gpgme_error( @@ -119,9 +119,9 @@ export class Connection{ } let chunksize = message.chunksize; const me = this; - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject){ let answer = new Answer(message); - let listener = function(msg) { + let listener = function (msg) { if (!msg){ me._connection.onMessage.removeListener(listener); me._connection.disconnect(); @@ -157,16 +157,16 @@ export class Connection{ } else { return Promise.race([ me._connection.postMessage(message.message), - function(resolve, reject){ - setTimeout(function(){ + function (resolve, reject){ + setTimeout(function (){ me._connection.disconnect(); reject(gpgme_error('CONN_TIMEOUT')); }, 5000); } - ]).then(function(result){ + ]).then(function (result){ return result; - }, function(reject){ - if(!(reject instanceof Error)) { + }, function (reject){ + if (!(reject instanceof Error)) { me._connection.disconnect(); return gpgme_error('GNUPG_ERROR', reject); } else { @@ -189,7 +189,7 @@ class Answer{ /** * @param {GPGME_Message} message */ - constructor(message){ + constructor (message){ this._operation = message.operation; this._expected = message.expected; this._response_b64 = null; @@ -211,7 +211,7 @@ class Answer{ * @private */ collect (msg){ - if (typeof(msg) !== 'object' || !msg.hasOwnProperty('response')) { + if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) { return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (!this._response_b64){ @@ -226,7 +226,7 @@ class Answer{ * Returns the base64 encoded answer data with the content verified * against {@link permittedOperations}. */ - getMessage(){ + getMessage (){ if (this._response_b64 === null){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } @@ -259,7 +259,7 @@ class Answer{ if (!poa.data.hasOwnProperty(key)){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } - if( typeof(_decodedResponse[key]) !== poa.data[key] ){ + if ( typeof (_decodedResponse[key]) !== poa.data[key] ){ return gpgme_error('CONN_UNEXPECTED_ANSWER'); } if (_decodedResponse.base64 === true @@ -268,7 +268,7 @@ class Answer{ ){ _response[key] = decodeURIComponent( atob(_decodedResponse[key]).split('').map( - function(c) { + function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 2a35bc5e..53e7bcd7 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -120,7 +120,7 @@ const err_list = { * @param {*} info Error message passed through if code is 'GNUPG_ERROR' * @returns {GPGME_Error} */ -export function gpgme_error(code = 'GENERIC_ERROR', info){ +export function gpgme_error (code = 'GENERIC_ERROR', info){ if (err_list.hasOwnProperty(code)){ if (err_list[code].type === 'error'){ return new GPGME_Error(code); @@ -147,9 +147,9 @@ export function gpgme_error(code = 'GENERIC_ERROR', info){ * @extends Error */ class GPGME_Error extends Error{ - constructor(code = 'GENERIC_ERROR', msg=''){ + constructor (code = 'GENERIC_ERROR', msg=''){ - if (code === 'GNUPG_ERROR' && typeof(msg) === 'string'){ + if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){ super(msg); } else if (err_list.hasOwnProperty(code)){ if (msg){ @@ -163,7 +163,7 @@ class GPGME_Error extends Error{ this._code = code; } - get code(){ + 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 379015f2..ba4277ab 100644 --- a/lang/js/src/Helpers.js +++ b/lang/js/src/Helpers.js @@ -30,7 +30,7 @@ import { gpgme_error } from './Errors'; * @param {Object | Array | String | Array} input * @returns {Array} Array of fingerprints, or an empty array */ -export function toKeyIdArray(input){ +export function toKeyIdArray (input){ if (!input){ return []; } @@ -39,7 +39,7 @@ export function toKeyIdArray(input){ } let result = []; for (let i=0; i < input.length; i++){ - if (typeof(input[i]) === 'string'){ + if (typeof (input[i]) === 'string'){ if (isFingerprint(input[i]) === true){ result.push(input[i]); } else { @@ -47,7 +47,7 @@ export function toKeyIdArray(input){ // in src/Errors.js gpgme_error('MSG_NOT_A_FPR'); } - } else if (typeof(input[i]) === 'object'){ + } else if (typeof (input[i]) === 'object'){ let fpr = ''; if (input[i].hasOwnProperty('fingerprint')){ fpr = input[i].fingerprint; @@ -78,8 +78,8 @@ export function toKeyIdArray(input){ * @returns {Boolean} true if value passes test * @private */ -function hextest(key, len){ - if (!key || typeof(key) !== 'string'){ +function hextest (key, len){ + if (!key || typeof (key) !== 'string'){ return false; } if (key.length !== len){ @@ -95,7 +95,7 @@ function hextest(key, len){ * @param {String} value to check * @returns {Boolean} true if value passes test */ -export function isFingerprint(value){ +export function isFingerprint (value){ return hextest(value, 40); } @@ -105,7 +105,7 @@ export function isFingerprint(value){ * @param {String} value to check * @returns {Boolean} true if value passes test */ -export function isLongId(value){ +export function isLongId (value){ return hextest(value, 16); } @@ -113,7 +113,7 @@ export function isLongId(value){ * Recursively decodes input (utf8) to output (utf-16; javascript) strings * @param {Object | Array | String} property */ -export function decode(property){ +export function decode (property){ if (typeof property === 'string'){ return decodeURIComponent(escape(property)); } else if (Array.isArray(property)){ diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 37ec7f9d..2800ae9a 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -35,8 +35,8 @@ import { createMessage } from './Message'; * a full object as delivered by gpgme-json * @returns {Object|GPGME_Error} The verified and updated data */ -export function createKey(fingerprint, async = false, data){ - if (!isFingerprint(fingerprint) || typeof(async) !== 'boolean'){ +export function createKey (fingerprint, async = false, data){ + if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){ return gpgme_error('PARAM_WRONG'); } if (data !== undefined){ @@ -60,14 +60,14 @@ export function createKey(fingerprint, async = false, data){ */ class GPGME_Key { - constructor(fingerprint, async, data){ + constructor (fingerprint, async, data){ /** * @property {Boolean} If true, most answers will be asynchronous */ this._async = async; - this._data = {fingerprint: fingerprint.toUpperCase()}; + this._data = { fingerprint: fingerprint.toUpperCase() }; if (data !== undefined && data.fingerprint.toUpperCase() === this._data.fingerprint ) { @@ -84,7 +84,7 @@ class GPGME_Key { * async, the armored property is not available (it can still be * retrieved asynchronously by {@link Key.getArmor}) */ - get(property) { + get (property) { if (this._async === true) { switch (property){ case 'armored': @@ -98,6 +98,7 @@ class GPGME_Key { if (property === 'armored') { return gpgme_error('KEY_ASYNC_ONLY'); } + // eslint-disable-next-line no-use-before-define if (!validKeyProperties.hasOwnProperty(property)){ return gpgme_error('PARAM_WRONG'); } else { @@ -114,16 +115,16 @@ class GPGME_Key { * @returns {Promise} * @async */ - refreshKey() { + refreshKey () { let me = this; - return new Promise(function(resolve, reject) { + 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){ + msg.post().then(function (result){ if (result.keys.length === 1){ const newdata = validateKeyData( me._data.fingerprint, result.keys[0]); @@ -131,13 +132,13 @@ class GPGME_Key { reject(gpgme_error('KEY_INVALID')); } else { me._data = newdata; - me.getGnupgSecretState().then(function(){ - me.getArmor().then(function(){ + me.getGnupgSecretState().then(function (){ + me.getArmor().then(function (){ resolve(me); - }, function(error){ + }, function (error){ reject(error); }); - }, function(error){ + }, function (error){ reject(error); }); } @@ -157,18 +158,18 @@ class GPGME_Key { * @returns {Promise} * @async */ - getArmor() { + getArmor () { const me = this; - return new Promise(function(resolve, reject) { + 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){ + msg.post().then(function (result){ resolve(result.data); - }, function(error){ + }, function (error){ reject(error); }); }); @@ -186,14 +187,14 @@ class GPGME_Key { */ getGnupgSecretState (){ const me = this; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { if (!me._data.fingerprint){ reject(gpgme_error('KEY_INVALID')); } else { let msg = createMessage('keylist'); msg.setParameter('keys', me._data.fingerprint); msg.setParameter('secret', true); - msg.post().then(function(result){ + msg.post().then(function (result){ me._data.hasSecret = null; if ( result.keys && @@ -206,7 +207,7 @@ class GPGME_Key { me._data.hasSecret = false; resolve(false); } - }, function(error){ + }, function (error){ reject(error); }); } @@ -219,17 +220,17 @@ class GPGME_Key { * @returns {Promise} Success if key was deleted, * rejects with a GPG error otherwise. */ - delete(){ + delete (){ const me = this; - return new Promise(function(resolve, reject){ + 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){ + msg.post().then(function (result){ resolve(result.success); - }, function(error){ + }, function (error){ reject(error); }); }); @@ -238,7 +239,7 @@ class GPGME_Key { /** * @returns {String} The fingerprint defining this Key. Convenience getter */ - get fingerprint(){ + get fingerprint (){ return this._data.fingerprint; } } @@ -255,7 +256,7 @@ class GPGME_Subkey { * @param {Object} data * @private */ - constructor(data){ + constructor (data){ this._data = {}; let keys = Object.keys(data); const me = this; @@ -268,7 +269,9 @@ class GPGME_Subkey { * @param private */ const setProperty = function (property, value){ + // eslint-disable-next-line no-use-before-define if (validSubKeyProperties.hasOwnProperty(property)){ + // eslint-disable-next-line no-use-before-define if (validSubKeyProperties[property](value) === true) { if (property === 'timestamp' || property === 'expires'){ me._data[property] = new Date(value * 1000); @@ -288,7 +291,7 @@ class GPGME_Subkey { * @param {String} property Information to request * @returns {String | Number | Date} */ - get(property) { + get (property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } @@ -308,12 +311,14 @@ class GPGME_UserId { * @param {Object} data * @private */ - constructor(data){ + constructor (data){ this._data = {}; const me = this; let keys = Object.keys(data); - const setProperty = function(property, value){ + const setProperty = function (property, value){ + // eslint-disable-next-line no-use-before-define if (validUserIdProperties.hasOwnProperty(property)){ + // eslint-disable-next-line no-use-before-define if (validUserIdProperties[property](value) === true) { if (property === 'last_update'){ me._data[property] = new Date(value*1000); @@ -333,7 +338,7 @@ class GPGME_UserId { * @param {String} property Information to request * @returns {String | Number} */ - get(property) { + get (property) { if (this._data.hasOwnProperty(property)){ return (this._data[property]); } @@ -349,52 +354,52 @@ class GPGME_UserId { * @const */ const validUserIdProperties = { - 'revoked': function(value){ - return typeof(value) === 'boolean'; + 'revoked': function (value){ + return typeof (value) === 'boolean'; }, - 'invalid': function(value){ - return typeof(value) === 'boolean'; + 'invalid': function (value){ + return typeof (value) === 'boolean'; }, - 'uid': function(value){ - if (typeof(value) === 'string' || value === ''){ + 'uid': function (value){ + if (typeof (value) === 'string' || value === ''){ return true; } return false; }, - 'validity': function(value){ - if (typeof(value) === 'string'){ + 'validity': function (value){ + if (typeof (value) === 'string'){ return true; } return false; }, - 'name': function(value){ - if (typeof(value) === 'string' || value === ''){ + 'name': function (value){ + if (typeof (value) === 'string' || value === ''){ return true; } return false; }, - 'email': function(value){ - if (typeof(value) === 'string' || value === ''){ + 'email': function (value){ + if (typeof (value) === 'string' || value === ''){ return true; } return false; }, - 'address': function(value){ - if (typeof(value) === 'string' || value === ''){ + 'address': function (value){ + if (typeof (value) === 'string' || value === ''){ return true; } return false; }, - 'comment': function(value){ - if (typeof(value) === 'string' || value === ''){ + 'comment': function (value){ + if (typeof (value) === 'string' || value === ''){ return true; } return false; }, - 'origin': function(value){ + 'origin': function (value){ return Number.isInteger(value); }, - 'last_update': function(value){ + 'last_update': function (value){ return Number.isInteger(value); } }; @@ -406,54 +411,54 @@ const validUserIdProperties = { * @const */ const validSubKeyProperties = { - 'invalid': function(value){ - return typeof(value) === 'boolean'; + 'invalid': function (value){ + return typeof (value) === 'boolean'; }, - 'can_encrypt': function(value){ - return typeof(value) === 'boolean'; + 'can_encrypt': function (value){ + return typeof (value) === 'boolean'; }, - 'can_sign': function(value){ - return typeof(value) === 'boolean'; + 'can_sign': function (value){ + return typeof (value) === 'boolean'; }, - 'can_certify': function(value){ - return typeof(value) === 'boolean'; + 'can_certify': function (value){ + return typeof (value) === 'boolean'; }, - 'can_authenticate': function(value){ - return typeof(value) === 'boolean'; + 'can_authenticate': function (value){ + return typeof (value) === 'boolean'; }, - 'secret': function(value){ - return typeof(value) === 'boolean'; + 'secret': function (value){ + return typeof (value) === 'boolean'; }, - 'is_qualified': function(value){ - return typeof(value) === 'boolean'; + 'is_qualified': function (value){ + return typeof (value) === 'boolean'; }, - 'is_cardkey': function(value){ - return typeof(value) === 'boolean'; + 'is_cardkey': function (value){ + return typeof (value) === 'boolean'; }, - 'is_de_vs': function(value){ - return typeof(value) === 'boolean'; + 'is_de_vs': function (value){ + return typeof (value) === 'boolean'; }, - 'pubkey_algo_name': function(value){ - return typeof(value) === 'string'; + 'pubkey_algo_name': function (value){ + return typeof (value) === 'string'; // TODO: check against list of known?[''] }, - 'pubkey_algo_string': function(value){ - return typeof(value) === 'string'; + 'pubkey_algo_string': function (value){ + return typeof (value) === 'string'; // TODO: check against list of known?[''] }, - 'keyid': function(value){ + 'keyid': function (value){ return isLongId(value); }, - 'pubkey_algo': function(value) { + 'pubkey_algo': function (value) { return (Number.isInteger(value) && value >= 0); }, - 'length': function(value){ + 'length': function (value){ return (Number.isInteger(value) && value > 0); }, - 'timestamp': function(value){ + 'timestamp': function (value){ return (Number.isInteger(value) && value > 0); }, - 'expires': function(value){ + 'expires': function (value){ return (Number.isInteger(value) && value > 0); } }; @@ -489,73 +494,73 @@ const validSubKeyProperties = { * @const */ const validKeyProperties = { - 'fingerprint': function(value){ + 'fingerprint': function (value){ return isFingerprint(value); }, - 'revoked': function(value){ - return typeof(value) === 'boolean'; + 'revoked': function (value){ + return typeof (value) === 'boolean'; }, - 'expired': function(value){ - return typeof(value) === 'boolean'; + 'expired': function (value){ + return typeof (value) === 'boolean'; }, - 'disabled': function(value){ - return typeof(value) === 'boolean'; + 'disabled': function (value){ + return typeof (value) === 'boolean'; }, - 'invalid': function(value){ - return typeof(value) === 'boolean'; + 'invalid': function (value){ + return typeof (value) === 'boolean'; }, - 'can_encrypt': function(value){ - return typeof(value) === 'boolean'; + 'can_encrypt': function (value){ + return typeof (value) === 'boolean'; }, - 'can_sign': function(value){ - return typeof(value) === 'boolean'; + 'can_sign': function (value){ + return typeof (value) === 'boolean'; }, - 'can_certify': function(value){ - return typeof(value) === 'boolean'; + 'can_certify': function (value){ + return typeof (value) === 'boolean'; }, - 'can_authenticate': function(value){ - return typeof(value) === 'boolean'; + 'can_authenticate': function (value){ + return typeof (value) === 'boolean'; }, - 'secret': function(value){ - return typeof(value) === 'boolean'; + 'secret': function (value){ + return typeof (value) === 'boolean'; }, - 'is_qualified': 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 + 'protocol': function (value){ + return typeof (value) === 'string'; + // TODO check for implemented ones }, - 'issuer_serial': function(value){ - return typeof(value) === 'string'; + 'issuer_serial': function (value){ + return typeof (value) === 'string'; }, - 'issuer_name': function(value){ - return typeof(value) === 'string'; + 'issuer_name': function (value){ + return typeof (value) === 'string'; }, - 'chain_id': function(value){ - return typeof(value) === 'string'; + 'chain_id': function (value){ + return typeof (value) === 'string'; }, - 'owner_trust': function(value){ - return typeof(value) === 'string'; + 'owner_trust': function (value){ + return typeof (value) === 'string'; }, - 'last_update': function(value){ + 'last_update': function (value){ return (Number.isInteger(value)); - //TODO undefined/null possible? + // TODO undefined/null possible? }, - 'origin': function(value){ + 'origin': function (value){ return (Number.isInteger(value)); }, - 'subkeys': function(value){ + 'subkeys': function (value){ return (Array.isArray(value)); }, - 'userids': function(value){ + 'userids': function (value){ return (Array.isArray(value)); }, - 'tofu': function(value){ + 'tofu': function (value){ return (Array.isArray(value)); }, - 'hasSecret': function(value){ - return typeof(value) === 'boolean'; + 'hasSecret': function (value){ + return typeof (value) === 'boolean'; } }; @@ -570,9 +575,9 @@ const validKeyProperties = { * an error if something went wrong. * @private */ -function validateKeyData(fingerprint, data){ +function validateKeyData (fingerprint, data){ const key = {}; - if (!fingerprint || typeof(data) !== 'object' || !data.fingerprint + if (!fingerprint || typeof (data) !== 'object' || !data.fingerprint || fingerprint !== data.fingerprint.toUpperCase() ){ return gpgme_error('KEY_INVALID'); @@ -619,13 +624,13 @@ function validateKeyData(fingerprint, data){ * @async */ function getGnupgState (fingerprint, property){ - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { if (!isFingerprint(fingerprint)) { reject(gpgme_error('KEY_INVALID')); } else { let msg = createMessage('keylist'); msg.setParameter('keys', fingerprint); - msg.post().then(function(res){ + msg.post().then(function (res){ if (!res.keys || res.keys.length !== 1){ reject(gpgme_error('KEY_INVALID')); } else { @@ -675,7 +680,7 @@ function getGnupgState (fingerprint, property){ break; } } - }, function(error){ + }, function (error){ reject(gpgme_error(error)); }); } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index de21736e..90d267db 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -22,8 +22,8 @@ */ -import {createMessage} from './Message'; -import {createKey} from './Key'; +import { createMessage } from './Message'; +import { createKey } from './Key'; import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; @@ -31,8 +31,6 @@ import { gpgme_error } from './Errors'; * This class offers access to the gnupg keyring */ export class GPGME_Keyring { - constructor(){ - } /** * Queries Keys (all Keys or a subset) from gnupg. @@ -51,7 +49,7 @@ export class GPGME_Keyring { * @async */ getKeys (pattern, prepare_sync=false, search=false){ - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); @@ -60,25 +58,25 @@ export class GPGME_Keyring { if (search === true){ msg.setParameter('locate', true); } - msg.post().then(function(result){ + msg.post().then(function (result){ let resultset = []; if (result.keys.length === 0){ resolve([]); } else { let secondrequest; if (prepare_sync === true) { - secondrequest = function() { + secondrequest = function () { let msg2 = createMessage('keylist'); msg2.setParameter('keys', pattern); msg2.setParameter('secret', true); return msg2.post(); }; } else { - secondrequest = function() { + secondrequest = function () { return Promise.resolve(true); }; } - secondrequest().then(function(answer) { + secondrequest().then(function (answer) { for (let i=0; i < result.keys.length; i++){ if (prepare_sync === true){ if (answer && answer.keys) { @@ -104,7 +102,7 @@ export class GPGME_Keyring { resultset.push(k); } resolve(resultset); - }, function(error){ + }, function (error){ reject(error); }); } @@ -136,7 +134,7 @@ export class GPGME_Keyring { * @async */ getKeysArmored (pattern, with_secret_fpr) { - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); if (with_secret_fpr === true) { @@ -145,15 +143,15 @@ export class GPGME_Keyring { if (pattern !== undefined && pattern !== null){ msg.setParameter('keys', pattern); } - msg.post().then(function(answer){ - const result = {armored: answer.data}; + msg.post().then(function (answer){ + const result = { armored: answer.data }; if (with_secret_fpr === true && answer.hasOwnProperty('sec-fprs') ) { result.secret_fprs = answer['sec-fprs']; } resolve(result); - }, function(error){ + }, function (error){ reject(error); }); }); @@ -169,32 +167,32 @@ export class GPGME_Keyring { * @async * @static */ - getDefaultKey(prepare_sync = false) { + getDefaultKey (prepare_sync = false) { let me = this; - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject){ let msg = createMessage('config_opt'); msg.setParameter('component', 'gpg'); msg.setParameter('option', 'default-key'); - msg.post().then(function(resp){ + msg.post().then(function (resp){ if (resp.option !== undefined && resp.option.hasOwnProperty('value') && resp.option.value.length === 1 && resp.option.value[0].hasOwnProperty('string') - && typeof(resp.option.value[0].string) === 'string'){ + && typeof (resp.option.value[0].string) === 'string'){ me.getKeys(resp.option.value[0].string, true).then( - function(keys){ - if(keys.length === 1){ + function (keys){ + if (keys.length === 1){ resolve(keys[0]); } else { reject(gpgme_error('KEY_NO_DEFAULT')); } - }, function(error){ + }, function (error){ reject(error); }); } else { let msg = createMessage('keylist'); msg.setParameter('secret', true); - msg.post().then(function(result){ + msg.post().then(function (result){ if (result.keys.length === 0){ reject(gpgme_error('KEY_NO_DEFAULT')); } else { @@ -211,11 +209,11 @@ export class GPGME_Keyring { } } } - }, function(error){ + }, function (error){ reject(error); }); } - }, function(error){ + }, function (error){ reject(error); }); }); @@ -264,14 +262,14 @@ export class GPGME_Keyring { 'new_signatures', 'new_revocations', 'secret_read', 'secret_imported', 'secret_unchanged', 'skipped_new_keys', 'not_imported', 'skipped_v3_keys']; - if (!armored || typeof(armored) !== 'string'){ + if (!armored || typeof (armored) !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let me = this; - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject){ let msg = createMessage('import'); msg.setParameter('data', armored); - msg.post().then(function(response){ + msg.post().then(function (response){ let infos = {}; let fprs = []; let summary = {}; @@ -282,7 +280,7 @@ export class GPGME_Keyring { if (!response.result.hasOwnProperty('imports') || response.result.imports.length === 0 ){ - resolve({Keys:[],summary: summary}); + resolve({ Keys:[],summary: summary }); return; } for (let res=0; res} * @async */ - generateKey(userId, algo = 'default', expires){ + generateKey (userId, algo = 'default', expires){ if ( - typeof(userId) !== 'string' || + typeof (userId) !== 'string' || + // eslint-disable-next-line no-use-before-define supportedKeyAlgos.indexOf(algo) < 0 || (expires && !(expires instanceof Date)) ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let me = this; - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject){ let msg = createMessage('createkey'); msg.setParameter('userid', userId); msg.setParameter('algo', algo ); @@ -394,15 +393,15 @@ export class GPGME_Keyring { } else { msg.setParameter('expires', 0); } - msg.post().then(function(response){ + msg.post().then(function (response){ me.getKeys(response.fingerprint, true).then( // TODO prepare_sync? - function(result){ + function (result){ resolve(result); - }, function(error){ + }, function (error){ reject(error); }); - }, function(error) { + }, function (error) { reject(error); }); }); diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 2134fe99..1ba2b658 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -31,8 +31,8 @@ import { Connection } from './Connection'; * @param {String} operation * @returns {GPGME_Message|GPGME_Error} The Message object */ -export function createMessage(operation){ - if (typeof(operation) !== 'string'){ +export function createMessage (operation){ + if (typeof (operation) !== 'string'){ return gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ @@ -51,7 +51,7 @@ export function createMessage(operation){ */ export class GPGME_Message { - constructor(operation){ + constructor (operation){ this._msg = { op: operation, chunksize: 1023* 1024 @@ -59,7 +59,7 @@ export class GPGME_Message { this._expected = null; } - get operation(){ + get operation (){ return this._msg.op; } @@ -69,7 +69,7 @@ export class GPGME_Message { } } - get expected() { + get expected () { return this._expected; } /** @@ -81,7 +81,7 @@ export class GPGME_Message { * will not encounter problems, larger messages will be received in * chunks. If the value is not explicitly specified, 1023 KB is used. */ - set chunksize(value){ + set chunksize (value){ if ( Number.isInteger(value) && value > 10 * 1024 && @@ -91,7 +91,7 @@ export class GPGME_Message { } } - get chunksize(){ + get chunksize (){ return this._msg.chunksize; } @@ -100,7 +100,7 @@ export class GPGME_Message { * @returns {Object|null} Object to be posted to gnupg, or null if * incomplete */ - get message() { + get message () { if (this.isComplete() === true){ return this._msg; } else { @@ -116,7 +116,7 @@ export class GPGME_Message { * @returns {Boolean} If the parameter was set successfully */ setParameter ( param,value ){ - if (!param || typeof(param) !== 'string'){ + if (!param || typeof (param) !== 'string'){ return gpgme_error('PARAM_WRONG'); } let po = permittedOperations[this._msg.op]; @@ -132,10 +132,10 @@ export class GPGME_Message { return gpgme_error('PARAM_WRONG'); } // check incoming value for correctness - let checktype = function(val){ - switch(typeof(val)){ + let checktype = function (val){ + switch (typeof (val)){ case 'string': - if (poparam.allowed.indexOf(typeof(val)) >= 0 + if (poparam.allowed.indexOf(typeof (val)) >= 0 && val.length > 0) { return true; } @@ -199,7 +199,7 @@ export class GPGME_Message { * all 'required' parameters according to {@link permittedOperations}. * @returns {Boolean} true if message is complete. */ - isComplete(){ + isComplete (){ if (!this._msg.op){ return false; } @@ -220,13 +220,13 @@ export class GPGME_Message { */ post (){ let me = this; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { if (me.isComplete() === true) { let conn = new Connection; - conn.post(me).then(function(response) { + conn.post(me).then(function (response) { resolve(response); - }, function(reason) { + }, function (reason) { reject(reason); }); } diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index 65365772..a6539048 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -30,25 +30,27 @@ import { gpgme_error } from './Errors'; * {@link expNote}. * @returns {GPGME_Signature|GPGME_Error} Signature Object */ -export function createSignature(sigObject){ +export function createSignature (sigObject){ if ( - typeof(sigObject) !=='object' || + typeof (sigObject) !=='object' || !sigObject.hasOwnProperty('summary') || !sigObject.hasOwnProperty('fingerprint') || !sigObject.hasOwnProperty('timestamp') - //TODO check if timestamp is mandatory in specification + // TODO check if timestamp is mandatory in specification ){ return gpgme_error('SIG_WRONG'); } let keys = Object.keys(sigObject); for (let i=0; i< keys.length; i++){ - if ( typeof(sigObject[keys[i]]) !== expKeys[keys[i]] ){ + // eslint-disable-next-line no-use-before-define + if ( typeof (sigObject[keys[i]]) !== expKeys[keys[i]] ){ return gpgme_error('SIG_WRONG'); } } let sumkeys = Object.keys(sigObject.summary); for (let i=0; i< sumkeys.length; i++){ - if ( typeof(sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){ + // eslint-disable-next-line no-use-before-define + if ( typeof (sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){ return gpgme_error('SIG_WRONG'); } } @@ -60,7 +62,8 @@ export function createSignature(sigObject){ let notation = sigObject.notations[i]; let notekeys = Object.keys(notation); for (let j=0; j < notekeys.length; j++){ - if ( typeof(notation[notekeys[j]]) !== expNote[notekeys[j]] ){ + // eslint-disable-next-line no-use-before-define + if ( typeof (notation[notekeys[j]]) !== expNote[notekeys[j]] ){ return gpgme_error('SIG_WRONG'); } } @@ -81,10 +84,10 @@ export function createSignature(sigObject){ */ class GPGME_Signature { - constructor(sigObject){ + constructor (sigObject){ this._rawSigObject = sigObject; } - get fingerprint(){ + get fingerprint (){ if (!this._rawSigObject.fingerprint){ return gpgme_error('SIG_WRONG'); } else { @@ -97,7 +100,7 @@ class GPGME_Signature { * signature does not expire * @returns {Date | null} */ - get expiration(){ + get expiration (){ if (!this._rawSigObject.exp_timestamp){ return null; } @@ -130,7 +133,7 @@ class GPGME_Signature { * for details on the values. * @returns {Object} Object with boolean properties */ - get errorDetails(){ + get errorDetails (){ let properties = ['revoked', 'key-expired', 'sig-expired', 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', 'sys-error']; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 4aa51759..9a0925b0 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -84,7 +84,7 @@ import { createSignature } from './Signature'; */ export class GpgME { - constructor(){ + constructor (){ this._Keyring = null; } @@ -92,7 +92,7 @@ export class GpgME { * setter for {@link setKeyring}. * @param {GPGME_Keyring} keyring A Keyring to use */ - set Keyring(keyring){ + set Keyring (keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } @@ -100,7 +100,7 @@ export class GpgME { /** * Accesses the {@link GPGME_Keyring}. */ - get Keyring(){ + get Keyring (){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; } @@ -188,9 +188,9 @@ export class GpgME { if (base64 === true){ msg.setParameter('base64', true); } - return new Promise(function(resolve, reject){ - msg.post().then(function(result){ - let _result = {data: result.data}; + return new Promise(function (resolve, reject){ + msg.post().then(function (result){ + let _result = { data: result.data }; _result.base64 = result.base64 ? true: false; _result.is_mime = result.is_mime ? true: false; if (result.file_name){ @@ -206,7 +206,7 @@ export class GpgME { result.signatures); } resolve(_result); - }, function(error){ + }, function (error){ reject(error); }); }); @@ -240,14 +240,14 @@ export class GpgME { } msg.setParameter('mode', mode); putData(msg, data); - return new Promise(function(resolve,reject) { + return new Promise(function (resolve,reject) { if (mode ==='detached'){ msg.expected ='base64'; } - msg.post().then( function(message) { + msg.post().then( function (message) { if (mode === 'clearsign'){ resolve({ - data: message.data} + data: message.data } ); } else if (mode === 'detached') { resolve({ @@ -255,7 +255,7 @@ export class GpgME { signature: message.data }); } - }, function(error){ + }, function (error){ reject(error); }); }); @@ -278,7 +278,7 @@ export class GpgME { return Promise.reject(dt); } if (signature){ - if (typeof(signature)!== 'string'){ + if (typeof (signature)!== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } else { msg.setParameter('signature', signature); @@ -287,7 +287,7 @@ export class GpgME { if (base64 === true){ msg.setParameter('base64', true); } - return new Promise(function(resolve, reject){ + return new Promise(function (resolve, reject){ msg.post().then(function (message){ if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); @@ -301,7 +301,7 @@ export class GpgME { _result.data = message.data; resolve(_result); } - }, function(error){ + }, function (error){ reject(error); }); }); @@ -316,20 +316,20 @@ export class GpgME { * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise * @private */ -function putData(message, data){ +function putData (message, data){ if (!message || !(message instanceof GPGME_Message)) { return gpgme_error('PARAM_WRONG'); } if (!data){ return gpgme_error('PARAM_WRONG'); - } else if (typeof(data) === 'string') { + } else if (typeof (data) === 'string') { message.setParameter('data', data); } else if ( - typeof(data) === 'object' && - typeof(data.getText) === 'function' + typeof (data) === 'object' && + typeof (data.getText) === 'function' ){ let txt = data.getText(); - if (typeof(txt) === 'string'){ + if (typeof (txt) === 'string'){ message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); @@ -345,7 +345,7 @@ function putData(message, data){ * @param {Array} sigs * @returns {signatureDetails} Details about the signatures */ -function collectSignatures(sigs){ +function collectSignatures (sigs){ if (!Array.isArray(sigs)){ return gpgme_error('SIG_NO_SIGS'); } diff --git a/lang/js/src/index.js b/lang/js/src/index.js index ad4b05b0..cf6e2d03 100644 --- a/lang/js/src/index.js +++ b/lang/js/src/index.js @@ -32,21 +32,21 @@ import { Connection } from './Connection'; * * @async */ -function init(){ - return new Promise(function(resolve, reject){ +function init (){ + return new Promise(function (resolve, reject){ const connection = new Connection; connection.checkConnection(false).then( - function(result){ + function (result){ if (result === true) { resolve(new GpgME()); } else { reject(gpgme_error('CONN_NO_CONNECT')); } - }, function(){ //unspecific connection error. Should not happen + }, function (){ // unspecific connection error. Should not happen reject(gpgme_error('CONN_NO_CONNECT')); }); }); } -const exportvalue = {init:init}; +const exportvalue = { init:init }; export default exportvalue; \ No newline at end of file diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index f9145dab..48ff7fa7 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -48,7 +48,7 @@ */ export const permittedOperations = { encrypt: { - pinentry: true, //TODO only with signing_keys + pinentry: true, // TODO only with signing_keys required: { 'keys': { allowed: ['string'], @@ -137,7 +137,7 @@ export const permittedOperations = { pinentry: true, required: { 'data': { - allowed: ['string']}, + allowed: ['string'] }, 'keys': { allowed: ['string'], array_allowed: true @@ -337,7 +337,7 @@ export const permittedOperations = { }, answer: { type: [''], - data: {'fingerprint': 'string'} + data: { 'fingerprint': 'string' } } }, @@ -365,9 +365,9 @@ export const permittedOperations = { data: 'string', base64:'boolean', info: 'object' - // file_name: Optional string of the plaintext file name. - // is_mime: Boolean if the messages claims it is MIME. - // signatures: Array of signatures + // info.file_name: Optional string of the plaintext file name. + // info.is_mime: Boolean if the messages claims it is MIME. + // info.signatures: Array of signatures } } }, diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js index 02bb5329..659ef85c 100644 --- a/lang/js/unittest_inputvalues.js +++ b/lang/js/unittest_inputvalues.js @@ -1,4 +1,4 @@ -import {createKey} from './src/Key'; +import { createKey } from './src/Key'; export const helper_params = { validLongId: '0A0A0A0A0A0A0A0A', @@ -9,8 +9,8 @@ export const helper_params = { validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A', '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'], invalidLongId: '9A9A7A7A8A9A9A7A7A8A', - invalidFingerprints: [{hello:'World'}, ['kekekeke'], new Uint32Array(40)], - invalidKeyArray: {curiosity:'uncat'}, + invalidFingerprints: [{ hello:'World' }, ['kekekeke'], new Uint32Array(40)], + invalidKeyArray: { curiosity:'uncat' }, invalidKeyArray_OneBad: [ createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'), 'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A', @@ -18,7 +18,7 @@ export const helper_params = { invalidErrorCode: 'Please type in all your passwords.', validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', true), valid_openpgplike: { primaryKey: { - getFingerprint: function(){ + getFingerprint: function (){ return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';} } } diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 47eeabf2..0abc1061 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -18,8 +18,8 @@ * SPDX-License-Identifier: LGPL-2.1+ */ -import './node_modules/mocha/mocha'; /*global mocha, it, describe*/ -import './node_modules/chai/chai';/*global chai*/ +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'; @@ -29,18 +29,18 @@ import { gpgme_error } from './src/Errors'; import { toKeyIdArray , isFingerprint } from './src/Helpers'; import { createKey } from './src/Key'; import { GPGME_Keyring } from './src/Keyring'; -import {GPGME_Message, createMessage} from './src/Message'; +import { GPGME_Message, createMessage } from './src/Message'; mocha.setup('bdd'); const expect = chai.expect; chai.config.includeStack = true; function unittests (){ - describe('Connection testing', function(){ + describe('Connection testing', function (){ - it('Connecting', function(done) { + it('Connecting', function (done) { let conn0 = new Connection; - conn0.checkConnection().then(function(answer) { + 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'); @@ -52,12 +52,12 @@ function unittests (){ }); - it('Disconnecting', function(done) { + it('Disconnecting', function (done) { let conn0 = new Connection; - conn0.checkConnection(false).then(function(answer) { + conn0.checkConnection(false).then(function (answer) { expect(answer).to.be.true; conn0.disconnect(); - conn0.checkConnection(false).then(function(result) { + conn0.checkConnection(false).then(function (result) { expect(result).to.be.false; done(); }); @@ -65,9 +65,9 @@ function unittests (){ }); }); - describe('Error Object handling', function(){ + describe('Error Object handling', function (){ // TODO: new GPGME_Error codes - it('check the Timeout error', function(){ + it('check the Timeout error', function (){ let test0 = gpgme_error('CONN_TIMEOUT'); expect(test0).to.be.an.instanceof(Error); @@ -75,7 +75,7 @@ function unittests (){ }); it('Error Object returns generic code if code is not listed', - function(){ + function (){ let test0 = gpgme_error(hp.invalidErrorCode); expect(test0).to.be.an.instanceof(Error); @@ -83,22 +83,22 @@ function unittests (){ } ); - it('Warnings like PARAM_IGNORED should not return errors', function(){ + 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(){ + describe('Fingerprint checking', function (){ - it('isFingerprint(): valid Fingerprint', function(){ + it('isFingerprint(): valid Fingerprint', function (){ let test0 = isFingerprint(hp.validFingerprint); expect(test0).to.be.true; }); - it('isFingerprint(): invalid Fingerprints', function(){ + it('isFingerprint(): invalid Fingerprints', function (){ for (let i=0; i < hp.invalidFingerprints.length; i++){ let test0 = isFingerprint(hp.invalidFingerprints[i]); @@ -107,16 +107,16 @@ function unittests (){ }); }); - describe('toKeyIdArray() (converting input to fingerprint)', function(){ + describe('toKeyIdArray() (converting input to fingerprint)', function (){ - it('Correct fingerprint string', function(){ + it('Correct fingerprint string', function (){ let test0 = toKeyIdArray(hp.validFingerprint); expect(test0).to.be.an('array'); expect(test0).to.include(hp.validFingerprint); }); - it('openpgpjs-like object', function(){ + it('openpgpjs-like object', function (){ let test0 = toKeyIdArray(hp.valid_openpgplike); expect(test0).to.be.an('array').with.lengthOf(1); @@ -124,33 +124,33 @@ function unittests (){ hp.valid_openpgplike.primaryKey.getFingerprint()); }); - it('Array of valid inputs', function(){ + 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('Incorrect inputs', function (){ - it('valid Long ID', function(){ + it('valid Long ID', function (){ let test0 = toKeyIdArray(hp.validLongId); expect(test0).to.be.empty; }); - it('invalidFingerprint', function(){ + it('invalidFingerprint', function (){ let test0 = toKeyIdArray(hp.invalidFingerprint); expect(test0).to.be.empty; }); - it('invalidKeyArray', function(){ + it('invalidKeyArray', function (){ let test0 = toKeyIdArray(hp.invalidKeyArray); expect(test0).to.be.empty; }); - it('Partially invalid array', function(){ + it('Partially invalid array', function (){ let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad); expect(test0).to.be.an('array'); @@ -160,10 +160,10 @@ function unittests (){ }); }); - describe('GPGME_Key', function(){ - it('Key has data after a first refresh', function(done) { + describe('GPGME_Key', function (){ + it('Key has data after a first refresh', function (done) { let key = createKey(kp.validKeyFingerprint); - key.refreshKey().then(function(key2){ + key.refreshKey().then(function (key2){ expect(key2.get).to.be.a('function'); for (let i=0; i < kp.validKeyProperties.length; i++) { let prop = key2.get(kp.validKeyProperties[i]); @@ -181,7 +181,7 @@ function unittests (){ it('Non-cached key async data retrieval', function (done){ let key = createKey(kp.validKeyFingerprint, true); - key.get('can_authenticate').then(function(result){ + key.get('can_authenticate').then(function (result){ expect(result).to.be.a('boolean'); done(); }); @@ -189,7 +189,7 @@ function unittests (){ it('Non-cached key async armored Key', function (done){ let key = createKey(kp.validKeyFingerprint, true); - key.get('armored').then(function(result){ + key.get('armored').then(function (result){ expect(result).to.be.a('string'); expect(result).to.include('KEY BLOCK-----'); done(); @@ -198,7 +198,7 @@ function unittests (){ it('Non-cached key async hasSecret', function (done){ let key = createKey(kp.validKeyFingerprint, true); - key.get('hasSecret').then(function(result){ + key.get('hasSecret').then(function (result){ expect(result).to.be.a('boolean'); done(); }); @@ -206,24 +206,24 @@ function unittests (){ it('Non-cached key async hasSecret (no secret in Key)', function (done){ let key = createKey(kp.validFingerprintNoSecret, true); - key.get('hasSecret').then(function(result){ + key.get('hasSecret').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) { + it('Querying non-existing Key returns an error', function (done) { let key = createKey(kp.invalidKeyFingerprint); - key.refreshKey().then(function(){}, - function(error){ + 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(){ + 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); @@ -248,18 +248,18 @@ function unittests (){ // }); }); - describe('GPGME_Keyring', function(){ + describe('GPGME_Keyring', function (){ - it('correct Keyring initialization', 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){ + function (done){ let keyring = new GPGME_Keyring; - keyring.getKeys(null, true).then(function(result){ + keyring.getKeys(null, true).then(function (result){ expect(result).to.be.an('array'); expect(result[0].get('hasSecret')).to.be.a('boolean'); done(); @@ -268,10 +268,10 @@ function unittests (){ ); it('Loading specific Key from Keyring, to be used synchronously', - function(done){ + function (done){ let keyring = new GPGME_Keyring; keyring.getKeys(kp.validKeyFingerprint, true).then( - function(result){ + function (result){ expect(result).to.be.an('array'); expect(result[0].get('hasSecret')).to.be.a('boolean'); done(); @@ -280,10 +280,10 @@ function unittests (){ } ); - it('Querying non-existing Key from Keyring', function(done){ + it('Querying non-existing Key from Keyring', function (done){ let keyring = new GPGME_Keyring; keyring.getKeys(kp.invalidKeyFingerprint, true).then( - function(result){ + function (result){ expect(result).to.be.an('array'); expect(result.length).to.equal(0); done(); @@ -293,16 +293,16 @@ function unittests (){ }); - describe('GPGME_Message', function(){ + describe('GPGME_Message', function (){ - it('creating encrypt 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(){ + 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); @@ -310,14 +310,14 @@ function unittests (){ expect(test0.isComplete()).to.be.true; }); - it('Message is not complete after mandatory data is empty', function(){ + 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(){ + 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); @@ -330,20 +330,20 @@ function unittests (){ mp.valid_encrypt_data); }); - it ('Not accepting non-allowed operation', function(){ + 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(){ + 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(){ + 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++){ @@ -356,7 +356,7 @@ function unittests (){ } }); - it('Not accepting wrong parameter value', function(){ + 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++){ @@ -372,4 +372,4 @@ function unittests (){ } -export default {unittests}; \ No newline at end of file +export default { unittests }; \ No newline at end of file -- cgit v1.2.3 From 91c2362550f787cc28d764c0e571e911c740f74f Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 20 Aug 2018 17:46:29 +0200 Subject: js: set expiry date on generateKey -- * on the javascript side a Date is expected, gpggme-json expects seconds from 'now' --- lang/js/src/Keyring.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 90d267db..7d9b370a 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -388,8 +388,9 @@ export class GPGME_Keyring { msg.setParameter('userid', userId); msg.setParameter('algo', algo ); if (expires){ + const now = new Date(); msg.setParameter('expires', - Math.floor(expires.valueOf()/1000)); + Math.floor((expires - now) /1000)); } else { msg.setParameter('expires', 0); } -- cgit v1.2.3 From d77a1c887d6a5e892329534c94f95eaf8bb88492 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 20 Aug 2018 18:05:34 +0200 Subject: js: add option "subkey-algo" to generateKey -- * The option was recently added to gpgme-json; this reflects this on javascript side --- lang/js/src/Keyring.js | 13 +++++++++++-- lang/js/src/permittedOperations.js | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 7d9b370a..81a047ca 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -366,14 +366,16 @@ export class GPGME_Keyring { * @param {String} userId The user Id, e.g. 'Foo Bar ' * @param {String} algo (optional) algorithm (and optionally key size) * to be used. See {@link supportedKeyAlgos} below for supported - * values. + * values. If ommitted, 'default' is used. * @param {Date} expires (optional) Expiration date. If not set, * expiration will be set to 'never' + * @param {String} subkey_algo (optional) algorithm of the encryption + * subkey. If ommited the same as algo is used. * * @return {Promise} * @async */ - generateKey (userId, algo = 'default', expires){ + generateKey (userId, algo = 'default', expires, subkey_algo){ if ( typeof (userId) !== 'string' || // eslint-disable-next-line no-use-before-define @@ -382,11 +384,18 @@ export class GPGME_Keyring { ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } + // eslint-disable-next-line no-use-before-define + if (subkey_algo && supportedKeyAlgos.indexOf(subkey_algo) < 0 ){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } let me = this; return new Promise(function (resolve, reject){ let msg = createMessage('createkey'); msg.setParameter('userid', userId); msg.setParameter('algo', algo ); + if (subkey_algo) { + msg.setParameter('subkey-algo', subkey_algo ); + } if (expires){ const now = new Date(); msg.setParameter('expires', diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 48ff7fa7..3142725c 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -331,6 +331,9 @@ export const permittedOperations = { algo: { allowed: ['string'] }, + 'subkey-algo': { + allowed: ['string'] + }, expires: { allowed: ['number'], } -- cgit v1.2.3 From 8b8c009dee8ae5493e7f888ee518468dbfcf5375 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 21 Aug 2018 11:42:11 +0200 Subject: js: set expiry of generatedKey to seconds from now -- * src/Keyring.js: Changed key ecpiration from Date to seconds from creation, as in gpgme. The Date parameter used before was due to a misunderstanding in documentation and requests from potential users. --- lang/js/src/Keyring.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 81a047ca..ab0144ef 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -367,8 +367,8 @@ export class GPGME_Keyring { * @param {String} algo (optional) algorithm (and optionally key size) * to be used. See {@link supportedKeyAlgos} below for supported * values. If ommitted, 'default' is used. - * @param {Date} expires (optional) Expiration date. If not set, - * expiration will be set to 'never' + * @param {Number} expires (optional) Expiration time in seconds from now. + * If not set or set to 0, expiration will be 'never' * @param {String} subkey_algo (optional) algorithm of the encryption * subkey. If ommited the same as algo is used. * @@ -380,7 +380,7 @@ export class GPGME_Keyring { typeof (userId) !== 'string' || // eslint-disable-next-line no-use-before-define supportedKeyAlgos.indexOf(algo) < 0 || - (expires && !(expires instanceof Date)) + (expires && !( Number.isInteger(expires) || expires < 0 )) ){ return Promise.reject(gpgme_error('PARAM_WRONG')); } @@ -397,9 +397,7 @@ export class GPGME_Keyring { msg.setParameter('subkey-algo', subkey_algo ); } if (expires){ - const now = new Date(); - msg.setParameter('expires', - Math.floor((expires - now) /1000)); + msg.setParameter('expires', expires); } else { msg.setParameter('expires', 0); } -- cgit v1.2.3 From fe3de5b86b4a25f5b23cf1af2fd1809ef6c087a0 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 21 Aug 2018 12:49:22 +0200 Subject: Remove js as language from configure.ac * configure.ac: Remove js language. -- It does not make much sense to integrate gpgme-js into the GPGME build system. gpgme-js will be distrbuted by it's users as part of the web extensions / their distribution as that is the JavaScript way. So they can use their tools etc. to compile gpgme-js JavaScript style, which is documented in the lang/js folder. --- configure.ac | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/configure.ac b/configure.ac index 69d7132f..65f1ef90 100644 --- a/configure.ac +++ b/configure.ac @@ -187,7 +187,7 @@ have_w64_system=no have_macos_system=no build_w32_glib=no build_w32_qt=no -available_languages="cl cpp python python2 python3 qt js" +available_languages="cl cpp python python2 python3 qt" default_languages="cl cpp python qt" case "${host}" in x86_64-*mingw32*) @@ -479,28 +479,6 @@ fi AC_SUBST(ENABLED_LANGUAGES, $enabled_languages) -# The javascript bindings -LIST_MEMBER("js", $enabled_languages) -if test "$found" = "1"; then - AC_CHECK_PROGS([NPX], [npx]) - if test -z "$NPX"; then - if test "$explicit_languages" = "1"; then - AC_MSG_ERROR([[ -*** -*** Node.js (npx) is required for the JavaScript binding. -***]]) - else - AC_MSG_WARN([ -*** -*** Node.js (npx) not found - JavaScript binding will not be built. -***]) - enabled_languages=$(echo $enabled_languages | sed 's/js//') - fi - fi -fi -AM_CONDITIONAL([BUILD_JS_BINDINGS], - [test -n "$NPX"]) - # # Provide information about the build. # -- cgit v1.2.3 From 605eb8a8bfcb12141d7cc5626e75af812cda6c75 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 21 Aug 2018 13:26:01 +0200 Subject: js: Improve README * lang/js/README: Clarify structure at the beginning. --- lang/js/README | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/lang/js/README b/lang/js/README index b7cd3d72..fd95cc45 100644 --- a/lang/js/README +++ b/lang/js/README @@ -1,16 +1,41 @@ -gpgme.js, as contained in this directory, is a javascript library for direct use -of gnupg in browsers, with the help of nativeMessaging. +gpgme.js - JavaScript for GPGME +------------------------------- +Initially developed for integration with the Mailvelope Web Extension. + +Overview +-------- + +gpgme.js is a javascript library for direct use of GnuPG in browsers. +It interacts with GPGME through nativeMessaging and gpgme-json. + +It is meant to be distributed directly by its downstream users in +their extension package. As such it is not integrated in the +autotools build system. See build instructions below. + + +gpgme-json +---------- + +gpgme-json (see core src/gpgme-json.c) the json to GPGME bridge is +required as native messaging backend for gpgme.js to work. +It needs to be installed and registered as native messaging +backend with the browser. + +See gpgme-mozilla.json and gpgme-chrome.json examples in +the top level doc/examples as example manifests. + +Any web extension using gpgme.js will need to be whitelisted in the manifest +file by its id. + +Distributors are encouraged to create manifest packages for their +distributions. -Prerequisites: --------------- -gpgme.js will make use of the application gpgme-json, which is distributed with -gpgme. Gpgme-json needs to be installed; it will further need to accept the -browser extension in the manifest file. Building gpgme.js ----------------- -gpgme.js uses webpack, and thus depends on nodejs for building. All -dependencies will be installed (in a local subdirectory) with the command + +gpgme.js uses webpack, and thus depends on Node.js for building. +All dependencies will be installed (in a local subdirectory) with the command `npm install`. To create a current version of the package, the command is @@ -18,6 +43,7 @@ To create a current version of the package, the command is If you want a more debuggable (i.e. not minified) build, just change the mode in webpack.conf.js. + Demo and Test WebExtension: --------------------------- @@ -82,6 +108,7 @@ is needed, with the following content: The manifest for linux is usually placed at: `~/.mozilla/native-messaging-hosts/gpgmejson.json` + Documentation ------------- -- cgit v1.2.3 From 9608996d88549b60da490e5eeb41db023f97a038 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 21 Aug 2018 13:24:08 +0200 Subject: Add example manifests for gpgme-json * doc/examples/gpgme-chrome.json, doc/examples/gpgme-mozilla.json: New. * doc/Makefile.am (EXTRA_DIST): Include them. -- The id contained in the examples is the ID of Mailvelope. --- doc/Makefile.am | 3 ++- doc/examples/gpgme-chrome.json | 9 +++++++++ doc/examples/gpgme-mozilla.json | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 doc/examples/gpgme-chrome.json create mode 100644 doc/examples/gpgme-mozilla.json diff --git a/doc/Makefile.am b/doc/Makefile.am index 905f9534..a592f795 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -23,7 +23,8 @@ DISTCLEANFILES = gpgme.tmp CLEANFILES = mkdefsinc defs.inc EXTRA_DIST = module-overview.sk HACKING DCO ChangeLog-2011 \ - mkdefsinc.c defsincdate + mkdefsinc.c defsincdate \ + examples/gpgme-mozilla.json examples/gpgme-chrome.json BUILT_SOURCES = defsincdate defs.inc diff --git a/doc/examples/gpgme-chrome.json b/doc/examples/gpgme-chrome.json new file mode 100644 index 00000000..d250ecbe --- /dev/null +++ b/doc/examples/gpgme-chrome.json @@ -0,0 +1,9 @@ +{ + "name": "gpgmejson", + "description": "Integration with GnuPG", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://kajibbejlbohfaggdiogboambcijhkke/" + ] +} diff --git a/doc/examples/gpgme-mozilla.json b/doc/examples/gpgme-mozilla.json new file mode 100644 index 00000000..493b3983 --- /dev/null +++ b/doc/examples/gpgme-mozilla.json @@ -0,0 +1,9 @@ +{ + "name": "gpgmejson", + "description": "Integration with GnuPG", + "path": "/usr/bin/gpgme-json", + "type": "stdio", + "allowed_extensions": [ + "jid1-AQqSMBYb0a8ADg@jetpack" + ] +} -- cgit v1.2.3 From 738a8e6f950af08305c082d59a91d3d5d45800fa Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 21 Aug 2018 13:56:45 +0200 Subject: js: Update extra_dist files * lang/js/BrowserTestExtension/Makefile.am, lang/js/Makefile.am (EXTRA_DIST): Update. --- lang/js/BrowserTestExtension/Makefile.am | 3 +++ lang/js/Makefile.am | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lang/js/BrowserTestExtension/Makefile.am b/lang/js/BrowserTestExtension/Makefile.am index 340d7ad9..6153f7d7 100644 --- a/lang/js/BrowserTestExtension/Makefile.am +++ b/lang/js/BrowserTestExtension/Makefile.am @@ -32,11 +32,14 @@ EXTRA_DIST = browsertest.html \ testkey2.pub \ testkey.pub \ testkey.sec \ + tests/decryptTest.js \ tests/encryptDecryptTest.js \ tests/encryptTest.js \ tests/inputvalues.js \ tests/KeyImportExport.js \ + tests/KeyInfos.js \ tests/longRunningTests.js \ tests/signTest.js \ tests/startup.js \ + tests/verifyTest.js \ unittests.html diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am index 4b5c7172..daa3d1ed 100644 --- a/lang/js/Makefile.am +++ b/lang/js/Makefile.am @@ -23,11 +23,10 @@ SUBDIRS = src BrowserTestExtension DemoExtension EXTRA_DIST = build_extensions.sh \ CHECKLIST \ CHECKLIST_build \ + jsdoc.conf \ .eslintrc.json \ - Makefile.am \ package.json \ README \ - README_testing \ unittest_inputvalues.js \ unittests.js \ webpack.conf.js \ -- cgit v1.2.3 From 0036b9bc493f0482cc7c4619867649481393163e Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 21 Aug 2018 13:58:06 +0200 Subject: js: Fix library name mentioned in js Makefiles -- --- lang/js/BrowserTestExtension/Makefile.am | 6 +++--- lang/js/DemoExtension/Makefile.am | 8 ++++---- lang/js/Makefile.am | 10 +++++----- lang/js/src/Makefile.am | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lang/js/BrowserTestExtension/Makefile.am b/lang/js/BrowserTestExtension/Makefile.am index 6153f7d7..8f0a4f93 100644 --- a/lang/js/BrowserTestExtension/Makefile.am +++ b/lang/js/BrowserTestExtension/Makefile.am @@ -1,14 +1,14 @@ -# Makefile.am for GPGME-JS. +# Makefile.am for gpgme.js. # Copyright (C) 2018 Intevation GmbH # # This file is part of GPGME. # -# GPGME-CL is free software; you can redistribute it and/or modify it +# gpgme.js is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # -# GPGME-CL is distributed in the hope that it will be useful, +# gpgme.js 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. diff --git a/lang/js/DemoExtension/Makefile.am b/lang/js/DemoExtension/Makefile.am index e93d0f66..d6e87fde 100644 --- a/lang/js/DemoExtension/Makefile.am +++ b/lang/js/DemoExtension/Makefile.am @@ -1,14 +1,14 @@ -# Makefile.am for GPGME-JS. +# Makefile.am for gpgme.js. # Copyright (C) 2018 Intevation GmbH # -# This file is part of GPGME-CL. +# This file is part of gpgme.js. # -# GPGME-CL is free software; you can redistribute it and/or modify it +# gpgme.js is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # -# GPGME-CL is distributed in the hope that it will be useful, +# gpgme.js 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. diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am index daa3d1ed..40a1c4fd 100644 --- a/lang/js/Makefile.am +++ b/lang/js/Makefile.am @@ -1,14 +1,14 @@ -# Makefile.am for GPGME-JS. -# Copyright (C) 2018 2018 Bundesamt für Sicherheit in der Informationstechnik +# Makefile.am for gpgme.js. +# Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik # -# This file is part of GPGME-JS. +# This file is part of gpgme.js. # -# GPGME-JS is free software; you can redistribute it and/or modify it +# gpgme.js is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # -# GPGME-JS is distributed in the hope that it will be useful, +# gpgme.js 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. diff --git a/lang/js/src/Makefile.am b/lang/js/src/Makefile.am index dd330d50..dc58fd31 100644 --- a/lang/js/src/Makefile.am +++ b/lang/js/src/Makefile.am @@ -1,14 +1,14 @@ -# Makefile.am for GPGME-JS. +# Makefile.am for gpgme.js. # Copyright (C) 2018 Intevation GmbH # # This file is part of GPGME. # -# GPGME-CL is free software; you can redistribute it and/or modify it +# gpgme.js is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # -# GPGME-CL is distributed in the hope that it will be useful, +# gpgme.js 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. -- cgit v1.2.3 From 9dd6c67cd5ae8bf394c4c257d6d16907018761c2 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 21 Aug 2018 14:37:50 +0200 Subject: js: remove outdated checklists -- * They are heavily outdated and offer no more useful information --- lang/js/CHECKLIST | 31 ------------------------------- lang/js/CHECKLIST_build | 3 --- lang/js/Makefile.am | 2 -- 3 files changed, 36 deletions(-) delete mode 100644 lang/js/CHECKLIST delete mode 100644 lang/js/CHECKLIST_build diff --git a/lang/js/CHECKLIST b/lang/js/CHECKLIST deleted file mode 100644 index 2e70dff1..00000000 --- a/lang/js/CHECKLIST +++ /dev/null @@ -1,31 +0,0 @@ -NativeConnection: - - [X] nativeConnection: successfully sending an encrypt request, -receiving an answer - [X] nativeConnection successfull on Chromium, chrome and firefox - [*] nativeConnection successfull on Windows, macOS, Linux - [X] nativeConnection with delayed, multipart (> 1MB) answer - - [x] Message handling (encrypt, decrypt verify, sign) - [x] encrypt, decrypt - [x] verify - [x] sign - [*] Key handling (import/export, modifying, status queries) - [x] Import (not importing secret) - [x] Export (not exporting secret) - [*] status queries - [ ] getHasSecret - [ ] key generation - [ ] modification - [x] Configuration handling - [ ] check for completeness - -Communication with other implementations - [-] option to export SECRET Key into localstore used by e.g. mailvelope? - current discussion states that this won't be possible due to security - concerns -Management: - [*] Define the gpgme interface - [x] check Permissions (e.g. csp) for the different envs - [x] agree on license - [*] tests diff --git a/lang/js/CHECKLIST_build b/lang/js/CHECKLIST_build deleted file mode 100644 index a7c8d08d..00000000 --- a/lang/js/CHECKLIST_build +++ /dev/null @@ -1,3 +0,0 @@ -- Checklist for build/install: - -browsers' manifests (see README) need allowedextension added, and the path set diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am index 40a1c4fd..4464730f 100644 --- a/lang/js/Makefile.am +++ b/lang/js/Makefile.am @@ -21,8 +21,6 @@ SUBDIRS = src BrowserTestExtension DemoExtension EXTRA_DIST = build_extensions.sh \ - CHECKLIST \ - CHECKLIST_build \ jsdoc.conf \ .eslintrc.json \ package.json \ -- cgit v1.2.3 From 6d720137dd9564931bf313a7e7078e63fb00287c Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Tue, 21 Aug 2018 15:26:17 +0200 Subject: js: update decrypt/verify results -- * src/gpgmejs.js: Decrypt now parses additional optional dec_info information, as well as any verify information, if present * src/permittedOperations: Now decrypt also expect the new return object dec_inf (containing info such as is_mime and file_name) --- lang/js/BrowserTestExtension/tests/decryptTest.js | 16 ++++++++++++++++ lang/js/BrowserTestExtension/tests/inputvalues.js | 22 ++++++++++++++++++++-- lang/js/src/gpgmejs.js | 19 +++++++++++-------- lang/js/src/permittedOperations.js | 4 ++-- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/decryptTest.js b/lang/js/BrowserTestExtension/tests/decryptTest.js index a3f48daa..ea887491 100644 --- a/lang/js/BrowserTestExtension/tests/decryptTest.js +++ b/lang/js/BrowserTestExtension/tests/decryptTest.js @@ -59,4 +59,20 @@ describe('Decryption', function () { }); }); }).timeout(5000); + + + it('decrypt/verify operations return proper information', function (done){ + const data = inputvalues.encryptSignedMessage; + context.decrypt(data).then(function (result){ + expect(result).to.be.an('object'); + expect(result.signatures).to.be.an('object'); + expect(result.signatures.all_valid).to.be.true; + expect(result.signatures.count).to.equal(1); + expect(result.signatures.signatures.good).to.be.an('array'); + expect( + result.signatures.signatures.good[0].fingerprint).to.equal( + good_fpr); + done(); + }); + }); }); \ No newline at end of file diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js index f84ac955..5c2abf36 100644 --- a/lang/js/BrowserTestExtension/tests/inputvalues.js +++ b/lang/js/BrowserTestExtension/tests/inputvalues.js @@ -86,9 +86,27 @@ const inputvalues = {// eslint-disable-line no-unused-vars 'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' + 'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' + '=ioB6\n' + - '-----END PGP SIGNATURE-----\n', + '-----END PGP SIGNATURE-----\n' }, - + encryptSignedMessage: '-----BEGIN PGP MESSAGE-----\n'+ + '\n'+ + 'hQEMA6B8jfIUScGEAQf/bmQ+xNMGTjPvQCktkxR4Svt2dVNVdSzKsCmvSv24QOQF\n'+ + 'yBMK5w51S/6DTdiZI12IYD7hjvkr9NqxXXupjrVKwqEVpg4Pkwckac0OcElJIBsL\n'+ + '3htr4iYsr8dhSgSS4BO0azcu4wZQTXy5v2P7yYPECMEagNEXnW+tE7sHLCq8Ysqz\n'+ + 'LVxG0R0IUijKeEd3xQC2Tt20e1Z1j5tnqaPhE/9Smqf5OjSUDqpXxvRnSNRk/zEs\n'+ + 'cGVgCF+cv68nUJM9lwEAbBQChplwL6ebnhunC6DsRCxnjLHVyKm127hmhSiMGC0e\n'+ + 'Ns31mGeP1dxpDv6Gi2/oKmq67vG3i4fKeckj7bt30tLA1wH0Qn5Mn6Tzxzve0W0q\n'+ + 'Ghqn9PY9qNK8EkrkzqaFk9dzu5tfSbaJBLS/uIhX2Wj70EMEBbFSkN0qlgOfLgGw\n'+ + '5mwRvCgj4nvV1ByFhnx7uwgQixvOwLH4JLKvwCQpJm+O2R0eC7M6CzR/b9iL/oaO\n'+ + 'JTkoD9hcLhxF7j+3ZYg7rbNwofuHST097vFjzItsucb0jHOzjlkCqbhdczICILTa\n'+ + 'H76Q6YGdMLyG9a3s4yZUMruaeQyWGeXlryzLDvdEoSgoD5YrolsFOM+Z2apbzVs2\n'+ + 'k5CltwtanjjWGnpAqSyr49C6CSU8G1QHpNygx5frtAS8bojR2ovB9OJp2wUklDvC\n'+ + 'LtU7dLpTY/BIvfB1vzwcW/aNgmPadNHX8mAzlqTQJjeLoo69Wp804t+u36sgfd/J\n'+ + 'ser7vdJJUm+86Q9csefItvFmHhqjMg5XXHoa8WZWJOHIQMxZkaIwKAzcEt/oEOdJ\n'+ + 'rbVNVabhTdbmS5I1ok16wg5jMF07ZDM7nXWMcQNjwT646XKP+pp2N6YQROVidNXj\n'+ + 'COyRyiXE/csr\n'+ + '=Ik7G\n'+ + '-----END PGP MESSAGE-----\n', someInputParameter: 'bad string', publicKeyNonAscii: { diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 9a0925b0..3be5cdd5 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -192,18 +192,21 @@ export class GpgME { msg.post().then(function (result){ let _result = { data: result.data }; _result.base64 = result.base64 ? true: false; - _result.is_mime = result.is_mime ? true: false; - if (result.file_name){ - _result.file_name = result.file_name; - } else { + if (result.hasOwnProperty('dec_info')){ + _result.is_mime = result.dec_info.is_mime ? true: false; + if (result.dec_info.file_name) { + _result.file_name = result.dec_info.file_name; + } + } + if (!result.file_name) { _result.file_name = null; } - if ( - result.hasOwnProperty('signatures') && - Array.isArray(result.signatures) + if (result.hasOwnProperty('info') + && result.info.hasOwnProperty('signatures') + && Array.isArray(result.info.signatures) ) { _result.signatures = collectSignatures( - result.signatures); + result.info.signatures); } resolve(_result); }, function (error){ diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 3142725c..6c05fc6c 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -127,8 +127,8 @@ export const permittedOperations = { 'data': 'string', 'base64': 'boolean', 'mime': 'boolean', - 'signatures': 'object', - 'info': 'object' + 'info': 'object', + 'dec_info': 'object' } } }, -- cgit v1.2.3 From 93f674d33d4dacb115398196a7218c28323fd708 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 22 Aug 2018 12:18:55 +0200 Subject: js: throw errors in sync functions -- * synchronous functions should throw errors if something goes wrong, Promises should reject. This commit changes some error cases that returned Error objects instead of throwing them - src/Key.js: createKey() and sync Key.get() throw errors - src/Error.js: Exporting the list of errors to be able to test and compare against these strings - src/Keyring.js: Setting a null value in pattern is not useful, and now caused an error with the new changes. - src/Message.js: createMessage and Message.setParameter now throw errors --- lang/js/src/Errors.js | 2 +- lang/js/src/Key.js | 12 +++++------ lang/js/src/Keyring.js | 4 +++- lang/js/src/Message.js | 26 ++++++++++++------------ lang/js/src/gpgmejs.js | 39 ++++++++++++++++++++++-------------- lang/js/unittests.js | 54 +++++++++++++++++++++++++++----------------------- 6 files changed, 76 insertions(+), 61 deletions(-) diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js index 53e7bcd7..73418028 100644 --- a/lang/js/src/Errors.js +++ b/lang/js/src/Errors.js @@ -24,7 +24,7 @@ /** * Listing of all possible error codes and messages of a {@link GPGME_Error}. */ -const err_list = { +export const err_list = { // Connection 'CONN_NO_CONNECT': { msg:'Connection with the nativeMessaging host could not be' diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js index 2800ae9a..d0f87eda 100644 --- a/lang/js/src/Key.js +++ b/lang/js/src/Key.js @@ -33,17 +33,17 @@ import { createMessage } from './Message'; * answers will be Promises, and the performance will likely suffer * @param {Object} data additional initial properties this Key will have. Needs * a full object as delivered by gpgme-json - * @returns {Object|GPGME_Error} The verified and updated data + * @returns {Object} The verified and updated data */ export function createKey (fingerprint, async = false, data){ if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } if (data !== undefined){ data = validateKeyData(fingerprint, data); } if (data instanceof Error){ - return gpgme_error('KEY_INVALID'); + throw gpgme_error('KEY_INVALID'); } else { return new GPGME_Key(fingerprint, async, data); } @@ -78,7 +78,7 @@ class GPGME_Key { /** * Query any property of the Key listed in {@link validKeyProperties} * @param {String} property property to be retreived - * @returns {Boolean| String | Date | Array | Object |GPGME_Error} + * @returns {Boolean| String | Date | Array | Object} * the value of the property. If the Key is set to Async, the value * will be fetched from gnupg and resolved as a Promise. If Key is not * async, the armored property is not available (it can still be @@ -96,11 +96,11 @@ class GPGME_Key { } } else { if (property === 'armored') { - return gpgme_error('KEY_ASYNC_ONLY'); + throw gpgme_error('KEY_ASYNC_ONLY'); } // eslint-disable-next-line no-use-before-define if (!validKeyProperties.hasOwnProperty(property)){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } else { return (this._data[property]); } diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index ab0144ef..cb053ba1 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -67,7 +67,9 @@ export class GPGME_Keyring { if (prepare_sync === true) { secondrequest = function () { let msg2 = createMessage('keylist'); - msg2.setParameter('keys', pattern); + if (pattern){ + msg2.setParameter('keys', pattern); + } msg2.setParameter('secret', true); return msg2.post(); }; diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js index 1ba2b658..b83caf6d 100644 --- a/lang/js/src/Message.js +++ b/lang/js/src/Message.js @@ -29,16 +29,16 @@ import { Connection } from './Connection'; * Initializes a message for gnupg, validating the message's purpose with * {@link permittedOperations} first * @param {String} operation - * @returns {GPGME_Message|GPGME_Error} The Message object + * @returns {GPGME_Message} The Message object */ export function createMessage (operation){ if (typeof (operation) !== 'string'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } if (permittedOperations.hasOwnProperty(operation)){ return new GPGME_Message(operation); } else { - return gpgme_error('MSG_WRONG_OP'); + throw gpgme_error('MSG_WRONG_OP'); } } @@ -117,11 +117,11 @@ export class GPGME_Message { */ setParameter ( param,value ){ if (!param || typeof (param) !== 'string'){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } let po = permittedOperations[this._msg.op]; if (!po){ - return gpgme_error('MSG_WRONG_OP'); + throw gpgme_error('MSG_WRONG_OP'); } let poparam = null; if (po.required.hasOwnProperty(param)){ @@ -129,7 +129,7 @@ export class GPGME_Message { } else if (po.optional.hasOwnProperty(param)){ poparam = po.optional[param]; } else { - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } // check incoming value for correctness let checktype = function (val){ @@ -139,24 +139,24 @@ export class GPGME_Message { && val.length > 0) { return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'number': if ( poparam.allowed.indexOf('number') >= 0 && isNaN(value) === false){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'boolean': if (poparam.allowed.indexOf('boolean') >= 0){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); case 'object': if (Array.isArray(val)){ if (poparam.array_allowed !== true){ - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } for (let i=0; i < val.length; i++){ let res = checktype(val[i]); @@ -171,13 +171,13 @@ export class GPGME_Message { if (poparam.allowed.indexOf('Uint8Array') >= 0){ return true; } - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } else { - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } break; default: - return gpgme_error('PARAM_WRONG'); + throw gpgme_error('PARAM_WRONG'); } }; let typechecked = checktype(value); diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 3be5cdd5..2886c6f6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -152,8 +152,13 @@ export class GpgME { if (additional){ let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { - msg.setParameter(additional_Keys[k], - additional[additional_Keys[k]]); + try { + msg.setParameter(additional_Keys[k], + additional[additional_Keys[k]]); + } + catch (error){ + return Promise.reject(error); + } } } if (msg.isComplete() === true){ @@ -185,9 +190,6 @@ export class GpgME { msg.setParameter('base64', true); } putData(msg, data); - if (base64 === true){ - msg.setParameter('base64', true); - } return new Promise(function (resolve, reject){ msg.post().then(function (result){ let _result = { data: result.data }; @@ -208,7 +210,11 @@ export class GpgME { _result.signatures = collectSignatures( result.info.signatures); } - resolve(_result); + if (_result.signatures instanceof Error){ + reject(_result.signatures); + } else { + resolve(_result); + } }, function (error){ reject(error); }); @@ -295,14 +301,17 @@ export class GpgME { if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { - let _result = collectSignatures( - message.info.signatures); - _result.is_mime = message.info.is_mime? true: false; - if (message.info.filename){ - _result.file_name = message.info.filename; + let _result = collectSignatures(message.info.signatures); + if (_result instanceof Error){ + reject(_result.signatures); + } else { + _result.is_mime = message.info.is_mime? true: false; + if (message.info.filename){ + _result.file_name = message.info.filename; + } + _result.data = message.data; + resolve(_result); } - _result.data = message.data; - resolve(_result); } }, function (error){ reject(error); @@ -363,8 +372,8 @@ function collectSignatures (sigs){ }; for (let i=0; i< sigs.length; i++){ let sigObj = createSignature(sigs[i]); - if (sigObj instanceof Error){ - return gpgme_error(sigObj); + if (sigObj instanceof Error) { + return gpgme_error('SIG_WRONG'); } if (sigObj.valid !== true){ summary.failures += 1; diff --git a/lang/js/unittests.js b/lang/js/unittests.js index 0abc1061..212effd3 100644 --- a/lang/js/unittests.js +++ b/lang/js/unittests.js @@ -25,7 +25,7 @@ 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 { gpgme_error, err_list } from './src/Errors'; import { toKeyIdArray , isFingerprint } from './src/Helpers'; import { createKey } from './src/Key'; import { GPGME_Keyring } from './src/Keyring'; @@ -225,9 +225,12 @@ function unittests (){ 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'); + expect(function (){ + createKey(wp.four_invalid_params[i]); + }).to.throw( + err_list.PARAM_WRONG.msg + ); + } }); @@ -312,9 +315,12 @@ function unittests (){ 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; + expect(function (){ + test0.setParameter('data', ''); + }).to.throw( + err_list.PARAM_WRONG.msg); }); it('Complete Message contains the data that was set', function (){ @@ -331,28 +337,27 @@ function unittests (){ }); 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'); + expect(function () { + createMessage(mp.invalid_op_action); + }).to.throw( + err_list.MSG_WRONG_OP.msg); }); 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'); + expect(function () { + createMessage(mp.invalid_op_type); + }).to.throw( + err_list.PARAM_WRONG.msg); }); 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'); - - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + expect(function (){ + test0.setParameter( + mp.invalid_param_test.invalid_param_names[i], + 'Somevalue');} + ).to.throw(err_list.PARAM_WRONG.msg); } }); @@ -360,12 +365,11 @@ function unittests (){ 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]); - - expect(ret).to.be.an.instanceof(Error); - expect(ret.code).to.equal('PARAM_WRONG'); + expect(function (){ + test0.setParameter( + mp.invalid_param_test.validparam_name_0, + mp.invalid_param_test.invalid_values_0[j]); + }).to.throw(err_list.PARAM_WRONG.msg); } }); }); -- cgit v1.2.3 From d8fd4aad8a93f7745c63814b3779469b610a8db0 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Wed, 22 Aug 2018 12:44:05 +0200 Subject: js: changed verify signature result infos -- * the resulting information of verify now are as documented, and the same as in a decrypt callback --- lang/js/BrowserTestExtension/tests/verifyTest.js | 43 +++++++++++++----------- lang/js/src/gpgmejs.js | 6 ++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js index 82aaf564..5788ed51 100644 --- a/lang/js/BrowserTestExtension/tests/verifyTest.js +++ b/lang/js/BrowserTestExtension/tests/verifyTest.js @@ -38,12 +38,12 @@ describe('Verifying data', function () { const message = inputvalues.signedMessage.good; context.verify(message).then(function (result){ expect(result.data).to.be.a('string'); - expect(result.all_valid).to.be.true; - expect(result.count).to.equal(1); - expect(result.signatures.good).to.be.an('array'); - expect(result.signatures.good.length).to.equal(1); - expect(result.signatures.good[0].fingerprint).to.be.a('string'); - expect(result.signatures.good[0].valid).to.be.true; + expect(result.signatures.all_valid).to.be.true; + expect(result.signatures.count).to.equal(1); + expect(result.signatures.signatures.good).to.be.an('array'); + expect(result.signatures.signatures.good.length).to.equal(1); + expect(result.signatures.signatures.good[0].fingerprint).to.be.a('string'); + expect(result.signatures.signatures.good[0].valid).to.be.true; done(); }); }); @@ -52,12 +52,14 @@ describe('Verifying data', function () { const message = inputvalues.signedMessage.bad; context.verify(message).then(function (result){ expect(result.data).to.be.a('string'); - expect(result.all_valid).to.be.false; - expect(result.count).to.equal(1); - expect(result.signatures.bad).to.be.an('array'); - expect(result.signatures.bad.length).to.equal(1); - expect(result.signatures.bad[0].fingerprint).to.be.a('string'); - expect(result.signatures.bad[0].valid).to.be.false; + expect(result.signatures.all_valid).to.be.false; + expect(result.signatures.count).to.equal(1); + expect(result.signatures.signatures.bad).to.be.an('array'); + expect(result.signatures.signatures.bad.length).to.equal(1); + expect(result.signatures.signatures.bad[0].fingerprint) + .to.be.a('string'); + expect(result.signatures.signatures.bad[0].valid) + .to.be.false; done(); }); }); @@ -70,13 +72,16 @@ describe('Verifying data', function () { context.verify(message_encsign.data).then(function (result){ expect(result.data).to.equal(message_enc.data); expect(result.data).to.be.a('string'); - expect(result.all_valid).to.be.true; - expect(result.count).to.equal(1); - expect(result.signatures.good).to.be.an('array'); - expect(result.signatures.good.length).to.equal(1); - expect( - result.signatures.good[0].fingerprint).to.equal(fpr); - expect(result.signatures.good[0].valid).to.be.true; + expect(result.signatures.all_valid).to.be.true; + expect(result.signatures.count).to.equal(1); + expect(result.signatures.signatures.good) + .to.be.an('array'); + expect(result.signatures.signatures.good.length) + .to.equal(1); + expect(result.signatures.signatures.good[0].fingerprint) + .to.equal(fpr); + expect(result.signatures.signatures.good[0].valid) + .to.be.true; done(); }); }); diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index 2886c6f6..7692298f 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -301,8 +301,10 @@ export class GpgME { if (!message.info || !message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { - let _result = collectSignatures(message.info.signatures); - if (_result instanceof Error){ + let _result = { + signatures: collectSignatures(message.info.signatures) + }; + if (_result.signatures instanceof Error){ reject(_result.signatures); } else { _result.is_mime = message.info.is_mime? true: false; -- cgit v1.2.3