diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js index 6230c3f0..d0127c73 100644 --- a/lang/js/DemoExtension/maindemo.js +++ b/lang/js/DemoExtension/maindemo.js @@ -1,67 +1,102 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ /* global document, Gpgmejs */ document.addEventListener('DOMContentLoaded', function() { Gpgmejs.init().then(function(gpgmejs){ document.getElementById('buttonencrypt').addEventListener('click', function(){ - let data = document.getElementById('cleartext').value; + let data = document.getElementById('inputtext').value; let keyId = document.getElementById('pubkey').value; gpgmejs.encrypt(data, keyId).then( function(answer){ if (answer.data){ document.getElementById( 'answer').value = answer.data; } }, function(errormsg){ alert( errormsg.message); }); }); document.getElementById('buttondecrypt').addEventListener('click', function(){ - let data = document.getElementById('ciphertext').value; + let data = document.getElementById('inputtext').value; gpgmejs.decrypt(data).then( function(answer){ if (answer.data){ document.getElementById( 'answer').value = answer.data; } }, function(errormsg){ alert(errormsg.message); }); }); document.getElementById('getdefaultkey').addEventListener('click', function(){ gpgmejs.Keyring.getDefaultKey().then(function(answer){ - document.getElementById('defaultkey').textContent = + document.getElementById('pubkey').value = answer.fingerprint; }, function(errormsg){ alert(errormsg.message); }); }); + + document.getElementById('signtext').addEventListener('click', + function(){ + let data = document.getElementById('inputtext').value; + let keyId = document.getElementById('pubkey').value; + gpgmejs.sign(data, keyId).then( + function(answer){ + if (answer.data){ + document.getElementById( + 'answer').value = answer.data; + } + }, function(errormsg){ + alert( errormsg.message); + }); + }); + + document.getElementById('verifytext').addEventListener('click', + function(){ + let data = document.getElementById('inputtext').value; + gpgmejs.verify(data).then( + function(answer){ + let vals = ''; + if (answer.all_valid === true){ + vals = 'Success! '; + } else { + vals = 'Failure! '; + } + vals = vals + (answer.count - answer.failures) + 'of ' + + answer.count + ' signature(s) were successfully ' + + 'verified.\n\n' + answer.data; + document.getElementById('answer').value = vals; + }, function(errormsg){ + alert( errormsg.message); + }); + }); }); }); diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html index 91be7bbc..b6390363 100644 --- a/lang/js/DemoExtension/mainui.html +++ b/lang/js/DemoExtension/mainui.html @@ -1,44 +1,43 @@ - -
-
- -
-
-

Result data:

- +
-
-
    -
  • - Default Key: -
    - -
  • +
    +
      +
    • + Input + +
    • +
    • + Fingerprint of Key to use: +
    • +
      + + +
    +
    +
    +
      +
    • + Result + +
    • +
    +
    +
+
+
+
+
+
- - - +
+ diff --git a/lang/js/DemoExtension/ui.css b/lang/js/DemoExtension/ui.css index 9c88698b..16dfb5ae 100644 --- a/lang/js/DemoExtension/ui.css +++ b/lang/js/DemoExtension/ui.css @@ -1,10 +1,33 @@ ul { list-style-type: none; padding-left: 0px; } ul li span { float: left; width: 120px; margin-top: 6px; } + +div .left { + float: left; + align-items: stretch; + width: 40%; +} +div .center { + width: 50%; + align-content: space-between; +} + +div .center button { + align-self: stretch; +} +div .right { + float: right; + align-items: stretch; + width: 40%; +} + +div .bottom { + clear:both; +} \ No newline at end of file diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js index d7d05983..a07fc4d1 100644 --- a/lang/js/src/Signature.js +++ b/lang/js/src/Signature.js @@ -1,193 +1,195 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ /** * Validates a signature object and returns * @param {Object} sigObject Object as returned by gpgme-json. The definition * of the expected values are to be found in the constants 'expKeys', 'expSum', * 'expNote' in this file. * @returns {GPGME_Signature} Signature Object */ import { gpgme_error } from './Errors'; export function createSignature(sigObject){ if ( typeof(sigObject) !=='object' || !sigObject.hasOwnProperty('summary') || - !sigObject.hasOwnProperty('fingerpprint') || + !sigObject.hasOwnProperty('fingerprint') || !sigObject.hasOwnProperty('timestamp') //TODO check if timestamp is mandatory in specification ){ return gpgme_error('SIG_WRONG'); } let keys = Object.keys(sigObject); for (let i=0; i< keys.length; i++){ if ( typeof(sigObject[keys[i]]) !== expKeys[keys[i]] ){ return gpgme_error('SIG_WRONG'); } } let sumkeys = Object.keys(sigObject.summary); for (let i=0; i< sumkeys.length; i++){ if ( typeof(sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){ return gpgme_error('SIG_WRONG'); } } if (sigObject.hasOwnProperty('notations')){ if (!Array.isArray(sigObject.notations)){ return gpgme_error('SIG_WRONG'); } for (let i=0; i < sigObject.notations.length; i++){ let notation = sigObject.notations[i]; let notekeys = Object.keys(notation); for (let j=0; j < notekeys.length; j++){ if ( typeof(notation[notekeys[j]]) !== expNote[notekeys[j]] ){ return gpgme_error('SIG_WRONG'); } } } } + console.log('sig created'); return new GPGME_Signature(sigObject); } /** * Representing the details of a signature. It is supposed to be read-only. The * full details as given by gpgme-json can be accessed from the _rawSigObject. * ) */ class GPGME_Signature { constructor(sigObject){ this._rawSigObject = sigObject; } /** * The signatures' fingerprint */ get fingerprint(){ return this._rawSigObject.fingerprint; } /** * The expiration of this Signature as Javascript date, or null if * signature does not expire * @returns {Date | null} */ get expiration(){ if (!this._rawSigObject.exp_timestamp){ return null; } return new Date(this._rawSigObject.exp_timestamp* 1000); } /** * The creation date of this Signature in Javascript Date * @returns {Date} */ get timestamp(){ return new Date(this._rawSigObject.timestamp* 1000); } /** * The overall validity of the key. If false, errorDetails may contain * additional information */ get valid() { if (this._rawSigObject.valid === true){ return true; } else { return false; } } /** * gives more information on non-valid signatures. Refer to the gpgme docs * https://www.gnupg.org/documentation/manuals/gpgme/Verify.html for * details on the values * @returns {Object} Object with boolean properties */ get errorDetails(){ let properties = ['revoked', 'key-expired', 'sig-expired', 'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy', 'sys-error']; let result = {}; for (let i=0; i< properties.length; i++){ if ( this._rawSigObject.hasOwnProperty(properties[i]) ){ result[properties[i]] = this._rawSigObject[properties[i]]; } } return result; } } /** * Keys and their value's type for the signature Object */ const expKeys = { 'wrong_key_usage': 'boolean', 'chain_model': 'boolean', 'summary': 'object', 'is_de_vs': 'boolean', 'status_string':'string', 'fingerprint':'string', 'validity_string': 'string', 'pubkey_algo_name':'string', 'hash_algo_name':'string', 'pka_address':'string', 'status_code':'number', 'timestamp':'number', 'exp_timestamp':'number', 'pka_trust':'number', 'validity':'number', 'validity_reason':'number', 'notations': 'object' }; /** * Keys and their value's type for the summary */ const expSum = { 'valid': 'boolean', 'green': 'boolean', 'red': 'boolean', 'revoked': 'boolean', 'key-expired': 'boolean', 'sig-expired': 'boolean', 'key-missing': 'boolean', 'crl-missing': 'boolean', 'crl-too-old': 'boolean', 'bad-policy': 'boolean', - 'sys-error': 'boolean' + 'sys-error': 'boolean', + 'sigsum': 'object' }; /** * Keys and their value's type for notations objects */ const expNote = { 'human_readable': 'boolean', 'critical':'boolean', 'name': 'string', 'value': 'string', 'flags': 'number' }; diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js index a0f7e968..c2a6b8b6 100644 --- a/lang/js/src/gpgmejs.js +++ b/lang/js/src/gpgmejs.js @@ -1,333 +1,333 @@ /* gpgme.js - Javascript integration for gpgme * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ * * Author(s): * Maximilian Krambach */ import {GPGME_Message, createMessage} from './Message'; import {toKeyIdArray} from './Helpers'; import { gpgme_error } from './Errors'; import { GPGME_Keyring } from './Keyring'; import { createSignature } from './Signature'; export class GpgME { /** * initializes GpgME by opening a nativeMessaging port */ constructor(){ } set Keyring(keyring){ if (keyring && keyring instanceof GPGME_Keyring){ this._Keyring = keyring; } } get Keyring(){ if (!this._Keyring){ this._Keyring = new GPGME_Keyring; } return this._Keyring; } /** * Encrypt (and optionally sign) a Message * @param {String|Object} data text/data to be encrypted as String. Also * accepts Objects with a getText method * @param {GPGME_Key|String|Array|Array} publicKeys * Keys used to encrypt the message * @param {GPGME_Key|String|Array|Array} secretKeys * (optional) Keys used to sign the message * @param {Boolean} base64 (optional) The data will be interpreted as * base64 encoded data * @param {Boolean} armor (optional) Request the output as armored block * @param {Boolean} wildcard (optional) If true, recipient information will * not be added to the message * @param {Object} additional use additional gpg options * (refer to src/permittedOperations) * @returns {Promise} Encrypted message: * data: The encrypted message * base64: Boolean indicating whether data is base64 encoded. * @async */ encrypt(data, publicKeys, secretKeys, base64=false, armor=true, wildcard=false, additional = {} ){ let msg = createMessage('encrypt'); if (msg instanceof Error){ return Promise.reject(msg); } msg.setParameter('armor', armor); msg.setParameter('always-trust', true); if (base64 === true) { msg.setParameter('base64', true); } let pubkeys = toKeyIdArray(publicKeys); msg.setParameter('keys', pubkeys); let sigkeys = toKeyIdArray(secretKeys); if (sigkeys.length > 0) { msg.setParameter('signing_keys', sigkeys); } putData(msg, data); if (wildcard === true){ msg.setParameter('throw-keyids', true); } if (additional){ let additional_Keys = Object.keys(additional); for (let k = 0; k < additional_Keys.length; k++) { msg.setParameter(additional_Keys[k], additional[additional_Keys[k]]); } } if (msg.isComplete === true){ return msg.post(); } else { return Promise.reject(gpgme_error('MSG_INCOMPLETE')); } } /** * Decrypt a Message * @param {String|Object} data text/data to be decrypted. Accepts Strings * and Objects with a getText method * @param {Boolean} base64 (optional) false if the data is an armored block, * true if it is base64 encoded binary data * @returns {Promise} result: Decrypted Message and information * @returns {String} result.data: The decrypted data. * @returns {Boolean} result.base64: indicating whether data is base64 * encoded. * @returns {Boolean} result.is_mime: Indicating whether the data is a MIME * object. * @returns {String} result.file_name: The optional original file name * @returns {Object} message.signatures Verification details for signatures: * @returns {Boolean} message.signatures.all_valid: true if all signatures * are valid * @returns {Number} message.signatures.count: Number of signatures found * @returns {Number} message.signatures.failures Number of invalid * signatures * @returns {Array} message.signatures.signatures. Two arrays * (good & bad) of {@link GPGME_Signature} objects, offering further * information. * * @async */ decrypt(data, base64=false){ if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let msg = createMessage('decrypt'); if (msg instanceof Error){ return Promise.reject(msg); } if (base64 === true){ msg.setParameter('base64', true); } putData(msg, data); if (base64 === true){ msg.setParameter('base64', true); } return new Promise(function(resolve, reject){ msg.post().then(function(result){ let _result = {data: result.data}; _result.base64 = result.base64 ? true: false; _result.is_mime = result.mime ? true: false; if (result.file_name){ _result.file_name = result.file_name; } if ( result.hasOwnProperty('signatures') && Array.isArray(result.signatures) ) { _result.signatures = collectSignatures(result.signatures); } resolve(_result); }, function(error){ reject(error); }); }); } /** * Sign a Message - * @param {String|Object} data text/data to be decrypted. Accepts Strings + * @param {String|Object} data text/data to be signed. Accepts Strings * and Objects with a gettext methos * @param {GPGME_Key|String|Array|Array} keys The * key/keys to use for signing * @param {*} mode The signing mode. Currently supported: * 'clearsign': (default) The Message is embedded into the signature * 'detached': The signature is stored separately * @param {*} base64 input is considered base64 * @returns {Promise} * data: The resulting data. Includes the signature in clearsign mode * signature: The detached signature (if in detached mode) * @async */ sign(data, keys, mode='clearsign', base64=false) { if (data === undefined){ return Promise.reject(gpgme_error('MSG_EMPTY')); } let key_arr = toKeyIdArray(keys); if (key_arr.length === 0){ return Promise.reject(gpgme_error('MSG_NO_KEYS')); } let msg = createMessage('sign'); msg.setParameter('keys', key_arr); if (base64 === true){ msg.setParameter('base64', true); } msg.setParameter('mode', mode); putData(msg, data); return new Promise(function(resolve,reject) { if (mode ==='detached'){ msg.expect= 'base64'; } msg.post().then( function(message) { if (mode === 'clearsign'){ resolve({ data: message.data} ); } else if (mode === 'detached') { resolve({ data: data, signature: message.data }); } }, function(error){ reject(error); }); }); } /** * Verifies data. * @param {String|Object} data text/data to be verified. Accepts Strings * and Objects with a gettext method * @param {String} (optional) A detached signature. If not present, opaque * mode is assumed * @param {Boolean} (optional) Data and signature are base64 encoded * // TODO verify if signature really is assumed to be base64 * @returns {Promise} result: * @returns {Boolean} result.data: The verified data * @returns {Boolean} result.is_mime: The message claims it is MIME * @returns {String} result.file_name: The optional filename of the message * @returns {Boolean} result.all_valid: true if all signatures are valid * @returns {Number} result.count: Number of signatures found * @returns {Number} result.failures Number of unsuccessful signatures * @returns {Array} result.signatures. Two arrays (good & bad) of * {@link GPGME_Signature} objects, offering further information. */ verify(data, signature, base64 = false){ let msg = createMessage('verify'); - let dt = this.putData(msg, data); + let dt = putData(msg, data); if (dt instanceof Error){ return Promise.reject(dt); } if (signature){ if (typeof(signature)!== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } else { msg.setParameter('signature', signature); } } if (base64 === true){ msg.setParameter('base64', true); } return new Promise(function(resolve, reject){ msg.post().then(function (message){ if (!message.info.signatures){ reject(gpgme_error('SIG_NO_SIGS')); } else { let _result = collectSignatures(message.info.signatures); _result.is_mime = message.info.is_mime? true: false; if (message.info.filename){ _result.file_name = message.info.filename; } _result.data = message.data; resolve(_result); } }, function(error){ reject(error); }); }); } } /** * Sets the data of the message, setting flags according on the data type * @param {GPGME_Message} message The message where this data will be set * @param {*} data The data to enter */ function putData(message, data){ if (!message || !(message instanceof GPGME_Message) ) { return gpgme_error('PARAM_WRONG'); } if (!data){ return gpgme_error('PARAM_WRONG'); } else if (typeof(data) === 'string') { message.setParameter('data', data); } else if ( typeof(data) === 'object' && typeof(data.getText) === 'function' ){ let txt = data.getText(); if (typeof(txt) === 'string'){ message.setParameter('data', txt); } else { return gpgme_error('PARAM_WRONG'); } } else { return gpgme_error('PARAM_WRONG'); } } function collectSignatures(sigs){ if (!Array.isArray(sigs)){ return gpgme_error('SIG_NO_SIGS'); } let summary = { all_valid: false, count: sigs.length, failures: 0, signatures: { good: [], bad: [], } }; for (let i=0; i< sigs.length; i++){ let sigObj = createSignature(sigs[i]); if (sigObj instanceof Error){ - return gpgme_error('SIG_WRONG'); + return gpgme_error(sigObj); } if (sigObj.valid !== true){ summary.failures += 1; summary.signatures.bad.push(sigObj); } else { summary.signatures.good.push(sigObj); } } if (summary.failures === 0){ summary.all_valid = true; } return summary; } \ No newline at end of file