diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js index 71585878..0d4e3c52 100644 --- a/lang/js/src/Keyring.js +++ b/lang/js/src/Keyring.js @@ -1,201 +1,259 @@ /* 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 {createMessage} from './Message'; import {createKey} from './Key'; import { isFingerprint } from './Helpers'; import { gpgme_error } from './Errors'; export class GPGME_Keyring { constructor(){ } /** * @param {String} pattern (optional) pattern A pattern to search for, * in userIds or KeyIds * @param {Boolean} prepare_sync (optional, default true) if set to true, * Key.armor and Key.hasSecret will be called, so they can be used * inmediately. This allows for full synchronous use. If set to false, * these will initially only be available as Promises in getArmor() and * getHasSecret() * @returns {Promise.>} * */ getKeys(pattern, prepare_sync){ return new Promise(function(resolve, reject) { let msg = createMessage('keylist'); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.setParameter('sigs', true); msg.post().then(function(result){ let resultset = []; let promises = []; if (result.keys.length === 0){ resolve([]); } else { for (let i=0; i< result.keys.length; i++){ let k = createKey(result.keys[i].fingerprint); k.setKeyData(result.keys[i]); if (prepare_sync === true){ promises.push(k.getArmor()); promises.push(k.getHasSecret()); } resultset.push(k); } if (promises.length > 0) { Promise.all(promises).then(function() { resolve(resultset); }, function(error){ reject(error); }); } else { resolve(resultset); } } }); }); } /** * Fetches the armored public Key blocks for all Keys matchin the pattern * (if no pattern is given, fetches all known to gnupg) * @param {String|Array} pattern (optional) * @returns {Promise} Armored Key blocks */ getKeysArmored(pattern) { return new Promise(function(resolve, reject) { let msg = createMessage('export'); msg.setParameter('armor', true); if (pattern !== undefined){ msg.setParameter('keys', pattern); } msg.post().then(function(result){ resolve(result.data); }, function(error){ reject(error); }); }); } // getDefaultKey() Big TODO /** * * @param {String} armored Armored Key block of the Kex(s) to be imported * into gnupg * @param {Boolean} prepare_sync prepare the keys for synched use * (see getKeys()). * @returns {Promise>} An array of objects for the Keys * considered: * Key.key : The key itself as a GPGME_Key * Key.status : * 'nochange' if the Key was not changed, * 'newkey' if the Key was imported in gpg, and did not exist * previously, * 'change' if the key existed, but details were updated. For * details, Key.changes is available. * Key.changes.userId: userIds changed * Key.changes.signature: signatures changed * Key.changes.subkey: subkeys changed * // TODO: not yet implemented: Information about Keys that failed * (e.g. malformed Keys, secretKeys are not accepted) */ importKey(armored, prepare_sync) { if (!armored || typeof(armored) !== 'string'){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let me = this; return new Promise(function(resolve, reject){ let msg = createMessage('import'); msg.setParameter('data', armored); msg.post().then(function(response){ let infos = {}; let fprs = []; for (let res=0; res < response.result[0].imports.length; res++){ let result = response.result[0].imports[res]; let status = ''; if (result.status === 0){ status = 'nochange'; } else if ((result.status & 1) === 1){ status = 'newkey'; } else { status = 'change'; } let changes = {}; changes.userId = (result.status & 2) === 2; changes.signature = (result.status & 4) === 4; changes.subkey = (result.status & 8) === 8; //16 new secret key: not implemented fprs.push(result.fingerprint); infos[result.fingerprint] = { changes: changes, status: status }; } let resultset = []; if (prepare_sync === true){ me.getKeys(fprs, true).then(function(result){ for (let i=0; i < result.length; i++) { resultset.push({ key: result[i], changes: infos[result[i].fingerprint].changes, status: infos[result[i].fingerprint].status }); } resolve(resultset); }, function(error){ reject(error); }); } else { for (let i=0; i < fprs.length; i++) { resultset.push({ key: createKey(fprs[i]), changes: infos[fprs[i]].changes, status: infos[fprs[i]].status }); } resolve(resultset); } }, function(error){ reject(error); }); }); } deleteKey(fingerprint){ if (isFingerprint(fingerprint) === true) { let key = createKey(fingerprint); key.delete(); } } - // generateKey + /** + * Generates a new Key pair directly in gpg, and returns a GPGME_Key + * representing that Key. Please note that due to security concerns, secret + * Keys can not be _deleted_ from inside gpgmejs. + * + * @param {String} userId The user Id, e.g. "Foo Bar " + * @param {*} algo (optional) algorithm to be used. See + * {@link supportedKeyAlgos } below for supported values. + * @param {Number} keyLength (optional) TODO + * @param {Date} expires (optional) Expiration date. If not set, expiration + * will be set to 'never' + * + * @returns{Promise} + */ + generateKey(userId, algo = 'default', keyLength, expires){ + if ( + typeof(userId) !== 'string' || + supportedKeyAlgos.indexOf(algo) < 0 || + (expires && !(expires instanceof Date)) + // TODO keylength + // TODO check for completeness of algos + ){ + return Promise.reject(gpgme_error('PARAM_WRONG')); + } + let me = this; + return new Promise(function(resolve, reject){ + let msg = createMessage('createkey'); + msg.setParameter('userid', userId); + msg.setParameter('algo', algo); + if (expires){ + msg.setParameter('expires', + Math.floor(expires.valueOf()/1000)); + } + // TODO append keylength to algo + msg.post().then(function(response){ + me.getKeys(response.fingerprint, true).then( + // TODO make prepare_sync (second parameter) optional here. + function(result){ + resolve(result); + }, function(error){ + reject(error); + }); + }, function(error) { + reject(error); + }); + }); + } } + +/** + * A list of algorithms supported for key generation. + */ +const supportedKeyAlgos = [ + 'default', + 'rsa', + 'dsa', + 'elg', + 'ed25519', + 'cv25519' +]; \ No newline at end of file diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js index 6ac33af9..91612ada 100644 --- a/lang/js/src/permittedOperations.js +++ b/lang/js/src/permittedOperations.js @@ -1,322 +1,341 @@ /* 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 */ /** * Definition of the possible interactions with gpgme-json. * operation: required: Array name The name of the property allowed: Array of allowed types. Currently accepted values: ['number', 'string', 'boolean', 'Uint8Array'] array_allowed: Boolean. If the value can be an array of the above allowed_data: If present, restricts to the given value optional: Array see 'required', with these parameters not being mandatory for a complete message pinentry: boolean If a pinentry dialog is expected, and a timeout of 5000 ms would be too short answer: type: the properties expected and their type, eg: {'data':'string'} } } */ export const permittedOperations = { encrypt: { pinentry: true, //TODO only with signing_keys required: { 'keys': { allowed: ['string'], array_allowed: true }, 'data': { allowed: ['string'] } }, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'signing_keys': { allowed: ['string'], array_allowed: true }, 'base64': { allowed: ['boolean'] }, 'mime': { allowed: ['boolean'] }, 'armor': { allowed: ['boolean'] }, 'always-trust': { allowed: ['boolean'] }, 'no-encrypt-to': { allowed: ['string'], array_allowed: true }, 'no-compress': { allowed: ['boolean'] }, 'throw-keyids': { allowed: ['boolean'] }, 'want-address': { allowed: ['boolean'] }, 'wrap': { allowed: ['boolean'] } }, answer: { type: ['ciphertext'], data: { 'data': 'string', 'base64':'boolean' } } }, decrypt: { pinentry: true, required: { 'data': { allowed: ['string'] } }, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'base64': { allowed: ['boolean'] } }, answer: { type: ['plaintext'], data: { 'data': 'string', 'base64': 'boolean', 'mime': 'boolean', 'signatures': 'object' } } }, sign: { pinentry: true, required: { 'data': { allowed: ['string']}, 'keys': { allowed: ['string'], array_allowed: true } }, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'sender': { allowed: ['string'], }, 'mode': { allowed: ['string'], allowed_data: ['detached', 'clearsign'] // TODO 'opaque' is not used, but available on native app }, 'base64': { allowed: ['boolean'] }, 'armor': { allowed: ['boolean'] }, }, answer: { type: ['signature', 'ciphertext'], data: { 'data': 'string', 'base64':'boolean' } } }, // note: For the meaning of the optional keylist flags, refer to // https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html keylist:{ required: {}, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'secret': { allowed: ['boolean'] }, 'extern': { allowed: ['boolean'] }, 'local':{ allowed: ['boolean'] }, 'sigs':{ allowed: ['boolean'] }, 'notations':{ allowed: ['boolean'] }, 'tofu': { allowed: ['boolean'] }, 'ephemeral': { allowed: ['boolean'] }, 'validate': { allowed: ['boolean'] }, 'keys': { allowed: ['string'], array_allowed: true } }, answer: { type: ['keys'], data: { 'base64': 'boolean', 'keys': 'object' } } }, export: { required: {}, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'keys': { allowed: ['string'], array_allowed: true }, 'armor': { allowed: ['boolean'] }, 'extern': { allowed: ['boolean'] }, 'minimal': { allowed: ['boolean'] }, 'raw': { allowed: ['boolean'] }, 'pkcs12':{ allowed: ['boolean'] } // secret: not yet implemented }, answer: { type: ['keys'], data: { 'data': 'string', 'base64': 'boolean' } } }, import: { required: { 'data': { allowed: ['string'] } }, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, 'base64': { allowed: ['boolean'] }, }, answer: { type: [], data: { 'result': 'Object' } } }, delete: { pinentry: true, required:{ 'key': { allowed: ['string'] } }, optional: { 'protocol': { allowed: ['string'], allowed_data: ['cms', 'openpgp'] }, // 'secret': { not implemented // allowed: ['boolean'] // } }, answer: { data: { 'success': 'boolean' } } }, version: { required: {}, optional: {}, answer: { type: [''], data: { 'gpgme': 'string', 'info': 'object' } } - } + }, + createkey: { + pinentry: true, + required: { + userid: { + allowed: ['string'] + } + }, + optional: { + algo: { + allowed: ['string'] + }, + expires: { + allowed: ['number'], + } + }, + answer: { + type: [''], + data: {'fingerprint': 'string'} + } + } /** * TBD handling of secrets * TBD key modification? - * TBD: key generation */ };