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
--
This commit is contained in:
Maximilian Krambach 2018-04-18 16:38:06 +02:00
parent 94f21d9f6b
commit 6ab25e40d9
18 changed files with 894 additions and 313 deletions

View File

@ -3,16 +3,19 @@ NativeConnection:
[X] nativeConnection: successfully sending an encrypt request, [X] nativeConnection: successfully sending an encrypt request,
receiving an answer receiving an answer
[X] nativeConnection successfull on Chromium, chrome and firefox [X] nativeConnection successfull on Chromium, chrome and firefox
[ ] nativeConnection successfull on Windows, macOS, Linux [*] nativeConnection successfull on Windows, macOS, Linux
[ ] nativeConnection with delayed, multipart (> 1MB) answer [*] nativeConnection with delayed, multipart (> 1MB) answer
replicating Openpgpjs API: replicating Openpgpjs API:
[*] Message handling (encrypt, verify, sign) [*] 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 [ ] Configuration handling
[ ] check for completeness [ ] check for completeness
[ ] handling of differences to openpgpjs [*] handling of differences to openpgpjs
Communication with other implementations Communication with other implementations
@ -21,10 +24,5 @@ Communication with other implementations
Management: Management:
[*] Define the gpgme interface [*] Define the gpgme interface
[ ] check Permissions (e.g. csp) for the different envs [ ] check Permissions (e.g. csp) for the different envs
[ ] agree on license [X] agree on license
[ ] tests [ ] tests
Problems:
[X] gpgme-json: interactive mode vs. bytelength; filename
[X] nativeApp chokes on arrays. We will get rid of that bnativeapp anyhow

View File

@ -4,6 +4,6 @@ browsers' manifests (see README) need allowedextension added, and the path set
manifest.json/ csp needs adaption manifest.json/ csp needs adaption
/dist contains a current build which is used by example app. /dist should be built which is used by the example app.
We may either want to update it on every commit, or never at all, but not
inconsistently. csp in manifest.json MUST NOT contain "unsafe-eval" in production!

View File

@ -1,20 +1,28 @@
This is an example app for gpgme-json. gpgmejs, as contained in this directory, is a javascript library for direct use
As of now, it only encrypts a given text. of gnupg in browsers, with the help of nativeMessaging.
Installation 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/ To create a current version of the package, the command is
(the testapplication uses that script at that location). To create a new `npx webpack --config webpack.conf.js`.
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 If you want a more debuggable (i.e. not minified) build, just change the mode
in webpack.conf.js. 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: 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 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 questions (extension debug mode needs to be active). For chrome, selecting the
folder is sufficient, for firefox, the manifest.json needs to be selected. 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' In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json'
is needed, with the following content: 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 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 addon-debugging is active. In firefox, the temporary addon is removed once
firefox exits, and the identifier will need to be changed more often. 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.

View File

@ -4,15 +4,11 @@
"name": "gpgme-json with native Messaging", "name": "gpgme-json with native Messaging",
"description": "This should be able to encrypt a text using gpgme-json", "description": "This should be able to encrypt a text using gpgme-json",
"version": "0.1", "version": "0.1",
"content_security_policy": "default-src 'self' 'unsafe-eval' filesystem", "content_security_policy": "default-src 'self' 'unsafe-eval' filesystem:",
"browser_action": { "browser_action": {
"default_icon": "testicon.png", "default_icon": "testicon.png",
"default_title": "gpgme.js", "default_title": "gpgme.js",
"default_popup": "ui.html" "default_popup": "testapplication_index.html"
}, },
"permissions": ["nativeMessaging", "activeTab"], "permissions": ["nativeMessaging", "activeTab"]
"background": {
"scripts": [ "dist/gpgmejs.bundle.js"]
}
} }

View File

@ -2,7 +2,7 @@
"name": "gpgmejs", "name": "gpgmejs",
"version": "0.0.1", "version": "0.0.1",
"description": "javascript part of a nativeMessaging gnupg integration", "description": "javascript part of a nativeMessaging gnupg integration",
"main": "src/gpgmejs.js", "main": "src/index.js",
"private": true, "private": true,
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@ -11,7 +11,7 @@
"author": "", "author": "",
"license": "", "license": "",
"devDependencies": { "devDependencies": {
"webpack": "^4.3.0", "webpack": "^4.5.0",
"webpack-cli": "^2.0.13" "webpack-cli": "^2.0.14"
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+
*/
/** /**
* A connection port will be opened for each communication between gpgmejs and * 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 * gnupg. It should be alive as long as there are additional messages to be
* expected. * expected.
*/ */
import { permittedOperations } from './permittedOperations'
export function Connection(){ export class Connection{
if (!this.connection){
this.connection = connect(); /**
this._msg = { * Opens and closes a port. Thus, it is made sure that the connection can
'always-trust': true, * be used.
// 'no-encrypt-to': false, * THIS BEHAVIOUR MAY CHANGE!
// 'no-compress': true, * discussion is to keep a port alive as long as the context stays the same
// 'throw-keyids': false, *
// 'wrap': false, * TODO returns nothing, but triggers exceptions if not successfull
'armor': true, */
'base64': false 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
}
/**
* Immediately closes the open port
*/
disconnect() {
if (this._connection){
this._connection.disconnect();
}
}
/**
* 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');
}
// let timeout = 5000; //TODO config
let me = this;
return new Promise(function(resolve, reject){
let answer = new Answer(message.op);
let listener = function(msg) {
if (!msg){
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);
}
}
}; };
this.disconnect = function () { me._connection.onMessage.addListener(listener);
if (this.connection){ me._connection.postMessage(message);
this.connection.disconnect(); //TBD: needs to be aware if there is a pinentry pending
// setTimeout(
// function(){
// me.disconnect();
// reject('TIMEOUT');
// }, timeout);
});
} }
}; };
/** /**
* Sends a message and resolves with the answer. * A class for answer objects, checking and processing the return messages of
* @param {*} operation The interaction requested from gpgme * the nativeMessaging communication
* @param {*} message A json-capable object to pass the operation details. * @param {String} operation The operation, to look up validity of return keys
* TODO: _msg should contain configurable parameters
*/ */
this.post = function(operation, message){ class Answer{
let timeout = 5000;
let me = this; constructor(operation){
if (!message || !operation){ this.operation = operation;
return Promise.reject('no message'); // TBD
} }
let keys = Object.keys(message); /**
for (let i=0; i < keys.length; i++){ *
let property = keys[i]; * @param {Object} msg The message as received with nativeMessaging
me._msg[property] = message[property]; * TODO: "error" and "more" handling are not in here, but in post()
*/
add(msg){
if (this._response === undefined){
this._response = {};
} }
me._msg['op'] = operation; let messageKeys = Object.keys(msg);
// TODO fancier checks if what we want is consistent with submitted content let poa = permittedOperations[this.operation].answer;
return new Promise(function(resolve, reject){ for (let i= 0; i < messageKeys.length; i++){
me.connection.onMessage.addListener(function(msg) { let key = messageKeys[i];
if (!msg){ switch (key) {
reject('empty answer.'); case 'type':
} if ( msg.type !== 'error' && poa.type.indexOf(msg.type) < 0){
if (msg.type === "error"){ console.log( 'unexpected answer type: ' + msg.type);
reject(msg.msg); throw('UNEXPECTED_TYPE');
}
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; 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;
}
}

84
lang/js/src/Helpers.js Normal file
View File

@ -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);
};

109
lang/js/src/Message.js Normal file
View File

@ -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;
}
}
}

View File

@ -1,187 +1,131 @@
import {Connection} from "./Connection" /* 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 encrypt(data, publicKeys, privateKeys, passwords=null, import {Connection} from "./Connection"
sessionKey, filename, compression, armor=true, detached=false, import {GPGME_Message} from './Message'
signature=null, returnSessionKey=false, wildcard=false, date=new Date()){ import {toKeyIdArray} from "./Helpers"
// gpgme_op_encrypt ( <-gpgme doc on this operation
// gpgme_ctx_t ctx, export class GpgME {
// gpgme_key_t recp[], /**
// gpgme_encrypt_flags_t flags, * initial check if connection si successfull. Will throw ERR_NO_CONNECT or
// gpgme_data_t plain, * ERR_NO_CONNECT_RLE (if chrome.runtime.lastError is available) if the
// gpgme_data_t cipher) * connection fails.
// flags: * TODO The connection to the nativeMessaging host will, for now, be closed
// GPGME_ENCRYPT_ALWAYS_TRUST * after each interaction. Session management with gpg_agent is TBD.
// GPGME_ENCRYPT_NO_ENCRYPT_TO * TODO: add configuration
// GPGME_ENCRYPT_NO_COMPRESS */
// GPGME_ENCRYPT_PREPARE constructor(){
// GPGME_ENCRYPT_EXPECT_SIGN let conn = new Connection();
// GPGME_ENCRYPT_SYMMETRIC // this.keyring = new Keyring(); TBD
// GPGME_ENCRYPT_THROW_KEYIDS // TODO config, e.g.
// GPGME_ENCRYPT_WRAP this.configuration = {
if (passwords !== null){ null_expire_is_never: true
throw('Password!'); // TBD };
conn.disconnect();
} }
/**
* @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){
let msg = new GPGME_Message;
msg.operation = 'encrypt';
// TODO temporary
msg.setParameter('armor', true);
msg.setParameter('always-trust', true);
let pubkeys = toKeyIdArray(publicKeys); let pubkeys = toKeyIdArray(publicKeys);
let privkeys = toKeyIdArray(privateKeys); msg.setParameter('keys', pubkeys);
// TODO filename: data is supposed to be empty, file is provided putData(msg, data);
// TODO config compression detached signature if (wildcard === true){msg.setParameter('throw-keyids', true);
// 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?
if (msg.isComplete === true) {
let conn = new Connection(); let conn = new Connection();
if (wildcard){ return (conn.post(msg.message));
// Connection.set('throw-keyids', true); TODO Connection.set not yet existant
} }
return conn.post('encrypt', { else {
'data': data, return Promise.reject('NO_CONNECT');
'keys': publicKeys, //TODO
'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 * @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
*/ */
/** decrypt(data){
* Checks if the submitted value is a keyID.
* TODO: should accept all strings that are accepted as keyID by gnupg if (data === undefined){
* TODO: See if Key becomes an object later on throw('ERR_EMPTY_MSG');
* @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 let msg = new GPGME_Message;
*/ msg.operation = 'decrypt';
function isKeyId(key){ putData(msg, data);
if (!key || typeof(key) !== "string"){ // TODO: needs proper EOL to be decrypted.
return false;
if (msg.isComplete === true){
let conn = new Connection();
return conn.post(msg.message);
}
else {
return Promise.reject('NO_CONNECT');
//TODO
}
} }
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. * Sets the data of the message, converting Uint8Array to base64 and setting
* Filters out those that do not meet the criteria. (TODO: silently for now) * the base64 flag
* @param {*} array Input value. * @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){ function putData(message, data){
let result = []; if (!message || !message instanceof GPGME_Message ) {
if (!array){ throw('NO_MESSAGE_OBJECT');
return result;
} }
if (!Array.isArray(array)){ if (!data){
if (isKeyId(array) === true){ //TODO Debug only! No data is legitimate
return [keyId]; console.log('Warning. no data in message');
} message.setParameter('data', '');
return result; } else if (data instanceof Uint8Array){
} let decoder = new TextDecoder('utf8');
for (let i=0; i < array.length; i++){ message.setParameter('base64', true);
if (isKeyId(array[i]) === true){ message.setParameter ('data', decoder.decode(data));
result.push(array[i]); } else if (typeof(data) === 'string') {
message.setParameter('base64', false);
message.setParameter('data', data);
} else {
throw('ERR_WRONG_TYPE');
} }
} }
return result;
};

View File

@ -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;
}

View File

@ -1,14 +1,23 @@
import * as gpgmejs from'./gpgmejs' /* gpgme.js - Javascript integration for gpgme
export default gpgmejs; * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
/**
* Export each high level api function separately.
* Usage:
* *
* import { encryptMessage } from 'gpgme.js' * This file is part of GPGME.
* encryptMessage(keys, text) *
* 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 {
encrypt, decrypt, sign, verify, import { GpgME as gpgmejs } from "./gpgmejs";
generateKey, reformatKey // import { GpgME_openPGPCompatibility as gpgmejs } from "./gpgmejs_openpgpjs";
} from './gpgmejs'; export default gpgmejs;

View File

@ -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']
}
}
}

25
lang/js/test_index.js Normal file
View File

@ -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'
});
});

View File

@ -1,21 +1,57 @@
/** /* gpgme.js - Javascript integration for gpgme
* Testing nativeMessaging. This is a temporary plugin using the gpgmejs * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
implemetation as contained in src/ *
* 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 buttonclicked(event){
let data = document.getElementById("text0").value; function encryptbuttonclicked(event){
let keyId = document.getElementById("key").value; let data = document.getElementById('cleartext').value;
let enc = Gpgmejs.encrypt(data, [keyId]).then(function(answer){ let keyId = document.getElementById('pubkey').value;
let communication = new Gpgmejs;
let enc = communication.encrypt(data, keyId).then(
function(answer){
console.log(answer); console.log(answer);
console.log(answer.type); if (answer.data){
console.log(answer.data); console.log(answer.data);
alert(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){ }, function(errormsg){
alert('Error: '+ errormsg); alert('Error: '+ errormsg);
}); });
}; };
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.getElementById("button0").addEventListener("click", document.getElementById("buttonencrypt").addEventListener("click",
buttonclicked); encryptbuttonclicked);
document.getElementById("buttondecrypt").addEventListener("click",
decryptbuttonclicked);
}); });

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="test_index.js"></script>
</head>
<body>
</body>
</html>

View File

@ -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>

33
lang/js/ui2.html Normal file
View File

@ -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>

View 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'); const path = require('path');
module.exports = { module.exports = {
@ -8,6 +29,7 @@ module.exports = {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'gpgmejs.bundle.js', filename: 'gpgmejs.bundle.js',
libraryTarget: 'var', libraryTarget: 'var',
libraryExport: 'default',
library: 'Gpgmejs' library: 'Gpgmejs'
} }
}; };