js: encrypt improvement and decrypt method
authorMaximilian Krambach <maximilian.krambach@intevation.de>
Wed, 18 Apr 2018 14:38:06 +0000 (16:38 +0200)
committerMaximilian Krambach <maximilian.krambach@intevation.de>
Fri, 20 Apr 2018 13:24:13 +0000 (15:24 +0200)
* 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
--

18 files changed:
lang/js/CHECKLIST
lang/js/CHECKLIST_build
lang/js/README
lang/js/manifest.json
lang/js/package.json
lang/js/src/Connection.js
lang/js/src/Helpers.js [new file with mode: 0644]
lang/js/src/Message.js [new file with mode: 0644]
lang/js/src/gpgmejs.js
lang/js/src/gpgmejs_openpgpjs.js [new file with mode: 0644]
lang/js/src/index.js
lang/js/src/permittedOperations.js [new file with mode: 0644]
lang/js/test_index.js [new file with mode: 0644]
lang/js/testapplication.js
lang/js/testapplication_index.html [new file with mode: 0644]
lang/js/ui.html [deleted file]
lang/js/ui2.html [new file with mode: 0644]
lang/js/webpack.conf.js

index 79a35cb..49b1726 100644 (file)
@@ -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
index fa162a1..19eb214 100644 (file)
@@ -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!
index 3ca0743..5dc3f50 100644 (file)
@@ -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.
index 8bb5c58..e5e17aa 100644 (file)
@@ -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"]
 }
index 46b60fd..2b7dd7e 100644 (file)
@@ -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"
   }
 }
index e8fea54..784929e 100644 (file)
+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 <http://www.gnu.org/licenses/>.
+ * 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<Object>} 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 (file)
index 0000000..eeb7a3c
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+/**
+ * Tries to return an array of fingerprints, either from input fingerprints or
+ * from Key objects
+ * @param {String|Array<String>} input Input value.
+ * @returns {Array<String>} 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 (file)
index 0000000..90b554a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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
index dedbf80..8323ac3 100644 (file)
-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 <http://www.gnu.org/licenses/>.
+ * 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<String>|Array<GPGME_Key>} 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<Object>} decrypted message:
+        data:   The decrypted data.  This may be base64 encoded.
+        base64: Boolean indicating whether data is base64 encoded.
+        mime:   A Boolean indicating whether the data is a MIME object.
+        info:   An optional object with extra information.
+    * @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 (file)
index 0000000..1eec4da
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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<Key>} publicKeys
+     * @param  {Boolean} wildcard
+     * TODO:
+     * @param  {Key|Array<Key>} privateKeys
+     * @param  {String} filename
+     * @param  {module:enums.compression} compression
+     * @param  {Boolean} armor
+     * @param  {Boolean} detached
+     * unsupported:
+     * @param  {String|Array<String>} passwords
+     * @param  {Object} sessionKey
+     * @param  {Signature} signature
+     * @param  {Boolean} returnSessionKey
+     *
+     * @returns {Promise<Object>}
+     *      {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<String>} passwords
+    * @param  {Object|Array<Object>} sessionKeys
+    * @param  {Date} date
+
+    * TODO
+    * @param  {Key|Array<Key>} privateKey
+    * @param  {Key|Array<Key>} publicKeys
+    * @param  {String} format                    (optional) return data format either as 'utf8' or 'binary'
+    * @param  {Signature} signature              (optional) detached signature for verification
+
+    * @returns {Promise<Object>}             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
index 02dc919..f70bd2d 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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 (file)
index 0000000..3c11b8e
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+ /**
+  * Definition of the possible interactions with gpgme-json.
+  * operation: <Object>
+      required: Array<String>
+      optional: Array<String>
+      answer: <Object>
+          type: <String< The content type of answer expected
+          data: Array<String> The payload property of the answer. May be
+                partial and in need of concatenation
+          params: Array<String> Information that do not change throughout
+                the message
+          infos: Array<String> 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 (file)
index 0000000..9119d27
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ */
+document.addEventListener('DOMContentLoaded', function() {
+    chrome.tabs.create({
+        url: './ui2.html'
+    });
+});
index d01aca9..97b3552 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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 (file)
index 0000000..866b113
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src="test_index.js"></script>
+    </head>
+    <body>
+    </body>
+</html>
\ No newline at end of file
diff --git a/lang/js/ui.html b/lang/js/ui.html
deleted file mode 100644 (file)
index 9c56c2e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <meta charset="utf-8">
-        <link rel="stylesheet" href="ui.css"/>
-    </head>
-    <body>
-        <!--TODO: replace this mess with require -->
-        <script src="dist/gpgmejs.bundle.js"></script>
-        <script src="testapplication.js"></script>
-       <ul>
-            <li>
-                <span class="label">Text: </span>
-               <input type="text" id='text0' />
-            </li>
-            <li>
-                <span class="label">Public key ID: </span>
-                <input type="text" id="key" value="Your Public Key ID here" />
-            </li>
-        </ul>
-        <button id="button0">Encrypt</button><br>
-        <div id="answer"></div>
-    </body>
-</html>
diff --git a/lang/js/ui2.html b/lang/js/ui2.html
new file mode 100644 (file)
index 0000000..8d0abd9
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link rel="stylesheet" href="ui.css"/>
+        <script src="dist/gpgmejs.bundle.js"></script>
+        <script src="testapplication.js"></script>
+    </head>
+    <body>
+       <ul>
+            <li>
+                <span class="label">Text: </span>
+               <input type="text" id='cleartext' />
+            </li>
+            <li>
+                <span class="label">Public key ID: </span>
+                <input type="text" id="pubkey" value="" />
+            </li>
+    </ul>
+        <button id="buttonencrypt">Encrypt</button><br>
+    <hr>
+    <ul>
+        <li>
+            <span class="label">Encrypted armored Text: </span>
+            <textarea rows="5" cols="65" id="ciphertext" wrap="hard"></textarea>
+        </li>
+    </ul>
+        <button id="buttondecrypt">Decrypt</button><br>
+    <hr>
+    <h3>Result data:</h3>
+    <textarea id="answer" rows="5" cols="65" wrap="hard"></textarea>
+    </body>
+</html>
index 71b7116..7a5392e 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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'
   }
 };