aboutsummaryrefslogtreecommitdiffstats
path: root/lang/js/src
diff options
context:
space:
mode:
author[email protected] <[email protected]>2018-04-10 09:33:14 +0000
committerWerner Koch <[email protected]>2018-04-10 16:47:59 +0000
commiteef3a509fa5744e5f09ec8084985e6070b78226b (patch)
treed5fe58d4e50c56a2a750bac81d79266a71e5ec01 /lang/js/src
parentqt: Add test for resetting config value (diff)
downloadgpgme-eef3a509fa5744e5f09ec8084985e6070b78226b.tar.gz
gpgme-eef3a509fa5744e5f09ec8084985e6070b78226b.zip
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.
Diffstat (limited to 'lang/js/src')
-rw-r--r--lang/js/src/Connection.js76
-rw-r--r--lang/js/src/gpgmejs.js187
-rw-r--r--lang/js/src/index.js14
3 files changed, 277 insertions, 0 deletions
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';