/* 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+ */ /** * 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 {GPGME_Keyring} from "./Keyring" import { GPGME_Key } from "./Key"; import { isFingerprint } from "./Helpers" import { gpgme_error } from "./Errors" export class GpgME_openpgpmode { constructor(connection, config = {}){ this.initGpgME(connection, config); } get Keyring(){ if (this._keyring){ return this._keyring; } return undefined; } initGpgME(connection, config = {}){ if (connection && typeof(config) ==='object'){ this._config = config; if (!this._GPGME){ this._GpgME = new GpgME(connection, config); } if (!this._keyring){ this._keyring = new GPGME_Keyring_openpgpmode(connection); } } } /** * Encrypt Message * Supported: * @param {String|Uint8Array} data * //an openpgp Message also accepted here. TODO: is this wanted? * @param {Key|Array} publicKeys * //Strings of Fingerprints * @param {Boolean} wildcard * TODO: * @param {Key|Array} privateKeys // -> encryptsign * @param {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress * @param {Boolean} armor // TODO base64 switch * @param {Boolean} detached // --> encryptsign * unsupported: * @param {String|Array} passwords * @param {Object} sessionKey * @param {Signature} signature * @param {Boolean} returnSessionKey * @param {String} filename * * Can be set, but will be ignored: * * @returns {Promise} * {data: ASCII armored message, * signature: detached signature if 'detached' is true * } * @async * @static */ encrypt({data = '', publicKeys = '', privateKeys, passwords=null, sessionKey = null, filename, compression, armor=true, detached=false, signature=null, returnSessionKey=null, wildcard=false, date=null}) { if (passwords !== null || sessionKey !== null || signature !== null || returnSessionKey !== null || date !== null ){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } if ( privateKeys || compression || armor === false || detached == true){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } if (filename){ if (this._config.unconsidered_params === 'warn'){ GPMGEJS_Error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'error'){ return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED')); } } return this._GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard); } /** Decrypt Message * supported openpgpjs parameters: * @param {Message|Uint8Array|String} message Message object from openpgpjs * Unsupported: * @param {String|Array} passwords * @param {Key|Array} privateKeys * @param {Object|Array} sessionKeys * Not yet supported, but planned * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification * Ignored values: can be safely set, but have no effect * @param {Date} date * @param {Key|Array} publicKeys * * @returns {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @async * @static */ decrypt({ message, privateKeys, passwords=null, sessionKeys, publicKeys, format='utf8', signature=null, date= null}) { if (passwords !== null || sessionKeys || privateKeys){ return Promise.reject(gpgme_error('NOT_IMPLEMENTED')); } if ( format !== 'utf8' || signature){ return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED')); } if (date !== null || publicKeys){ if (this._config.unconsidered_params === 'warn'){ GPMGEJS_Error('PARAM_IGNORED'); } else if (this._config.unconsidered_params === 'reject'){ return Promise.reject(GPMGEJS_Error('NOT_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. } } /** * Translation layer offering basic Keyring API to be used in Mailvelope. * It may still be changed/expanded/merged with GPGME_Keyring */ class GPGME_Keyring_openpgpmode { constructor(connection){ this._gpgme_keyring = new GPGME_Keyring(connection); } /** * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This * includes keys openpgpjs considers 'private' (usable for signing), with * the difference that Key.armored will NOT contain any secret information. * Please also note that a GPGME_Key does not offer full openpgpjs- Key * compatibility. * @returns {Array} * //TODO: Check if IsDefault is also always hasSecret * TODO Check if async is required */ getPublicKeys(){ return translateKeys( this._gpgme_keyring.getKeys(null, true)); } /** * Returns the Default Key used for crypto operation in gnupg. * Please note that the armored property does not contained secret key blocks, * despite secret blocks being part of the key itself. * @returns {Promise } */ getDefaultKey(){ this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){ if (result.length === 1){ return Promise.resolve( translateKeys(result)[0]); } else { // TODO: Can there be "no default key"? // TODO: Can there be several default keys? return gpgme_error('TODO'); } }); } /** * Deletes a Key * @param {Object} Object identifying key * @param {String} key.fingerprint - fingerprint of the to be deleted key * @param {Boolean} key.secret - indicator if private key should be deleted as well * @returns {Promise., Error>} TBD: Not sure what is wanted TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists */ deleteKey(key){ if (typeof(key) !== "object"){ return Promise.reject(gpgme_error('PARAM_WRONG')); } if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){ return Promise.reject(gpgme_error('PARAM_WRONG')); } let key_to_delete = new GPGME_Key(key.fingerprint); return key_to_delete.deleteKey(key.secret); } } /** * TODO error handling. * Offers the Key information as the openpgpmode wants */ class GPGME_Key_openpgpmode { constructor(value){ this.init = value; } set init (value){ if (!this._GPGME_Key && value instanceof GPGME_Key){ this._GPGME_Key = value; } else if (!this._GPGME_Key && isFingerprint(value)){ this._GPGME_Key = new GPGME_Key(value); } } get fingerprint(){ return this._GPGME_Key.fingerprint; } get armor(){ return this._GPGME_Key.armored; } get secret(){ return this._GPGME_Key.hasSecret; } get default(){ return this._GPGME_Key.isDefault; } } /** * creates GPGME_Key_openpgpmode from GPGME_Keys */ function translateKeys(input){ if (!Array.isArray(input)){ input = [input]; } let resultset; for (let i=0; i< input.length; i++){ resultset.push(new GPGME_Key_openpgpmode(input[i])); } return resultset; }