2018-04-18 14:38:06 +00:00
|
|
|
|
/* 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";
|
2018-04-23 15:18:46 +00:00
|
|
|
|
import {GPGME_Keyring} from "./Keyring"
|
|
|
|
|
import { GPGME_Key } from "./Key";
|
|
|
|
|
import { isFingerprint } from "./Helpers"
|
2018-04-25 13:59:36 +00:00
|
|
|
|
import { gpgme_error } from "./Errors"
|
2018-04-18 14:38:06 +00:00
|
|
|
|
|
2018-04-24 16:44:30 +00:00
|
|
|
|
|
2018-04-24 17:29:32 +00:00
|
|
|
|
export class GpgME_openpgpmode {
|
2018-04-18 14:38:06 +00:00
|
|
|
|
|
2018-04-25 13:59:36 +00:00
|
|
|
|
constructor(connection, config = {}){
|
|
|
|
|
this.initGpgME(connection, config);
|
2018-04-24 16:44:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get Keyring(){
|
|
|
|
|
if (this._keyring){
|
|
|
|
|
return this._keyring;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-25 13:59:36 +00:00
|
|
|
|
initGpgME(connection, config = {}){
|
|
|
|
|
if (connection && typeof(config) ==='object'){
|
|
|
|
|
this._config = config;
|
|
|
|
|
if (!this._GPGME){
|
|
|
|
|
this._GpgME = new GpgME(connection, config);
|
|
|
|
|
}
|
2018-04-26 15:13:34 +00:00
|
|
|
|
if (!this._keyring){
|
|
|
|
|
this._keyring = new GPGME_Keyring_openpgpmode(connection);
|
2018-04-25 13:59:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-24 16:44:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-18 14:38:06 +00:00
|
|
|
|
/**
|
|
|
|
|
* Encrypt Message
|
|
|
|
|
* Supported:
|
|
|
|
|
* @param {String|Uint8Array} data
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* //an openpgp Message also accepted here. TODO: is this wanted?
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* @param {Key|Array<Key>} publicKeys
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* //Strings of Fingerprints
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* @param {Boolean} wildcard
|
|
|
|
|
* TODO:
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* @param {Key|Array<Key>} 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
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* unsupported:
|
|
|
|
|
* @param {String|Array<String>} passwords
|
|
|
|
|
* @param {Object} sessionKey
|
|
|
|
|
* @param {Signature} signature
|
|
|
|
|
* @param {Boolean} returnSessionKey
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* @param {String} filename
|
|
|
|
|
*
|
|
|
|
|
* Can be set, but will be ignored:
|
2018-04-18 14:38:06 +00:00
|
|
|
|
*
|
|
|
|
|
* @returns {Promise<Object>}
|
|
|
|
|
* {data: ASCII armored message,
|
|
|
|
|
* signature: detached signature if 'detached' is true
|
|
|
|
|
* }
|
|
|
|
|
* @async
|
|
|
|
|
* @static
|
|
|
|
|
*/
|
2018-04-25 13:59:36 +00:00
|
|
|
|
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
|
2018-04-18 14:38:06 +00:00
|
|
|
|
|| signature !== null
|
|
|
|
|
|| returnSessionKey !== null
|
2018-04-25 13:59:36 +00:00
|
|
|
|
|| date !== null
|
|
|
|
|
){
|
2018-04-25 08:54:24 +00:00
|
|
|
|
return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
|
|
|
|
if ( privateKeys
|
|
|
|
|
|| compression
|
|
|
|
|
|| armor === false
|
|
|
|
|
|| detached == true){
|
2018-04-25 13:59:36 +00:00
|
|
|
|
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'));
|
|
|
|
|
}
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-26 15:13:34 +00:00
|
|
|
|
return this._GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard);
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Decrypt Message
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* supported openpgpjs parameters:
|
|
|
|
|
* @param {Message|Uint8Array|String} message Message object from openpgpjs
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* Unsupported:
|
|
|
|
|
* @param {String|Array<String>} passwords
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* @param {Key|Array<Key>} privateKeys
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* @param {Object|Array<Object>} sessionKeys
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* Not yet supported, but planned
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* @param {String} format (optional) return data format either as 'utf8' or 'binary'
|
|
|
|
|
* @param {Signature} signature (optional) detached signature for verification
|
2018-04-25 13:59:36 +00:00
|
|
|
|
* Ignored values: can be safely set, but have no effect
|
|
|
|
|
* @param {Date} date
|
|
|
|
|
* @param {Key|Array<Key>} publicKeys
|
|
|
|
|
*
|
2018-04-18 14:38:06 +00:00
|
|
|
|
* @returns {Promise<Object>} decrypted and verified message in the form:
|
|
|
|
|
* { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
|
|
|
|
|
* @async
|
|
|
|
|
* @static
|
|
|
|
|
*/
|
2018-04-25 13:59:36 +00:00
|
|
|
|
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'));
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-25 13:59:36 +00:00
|
|
|
|
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'));
|
|
|
|
|
}
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-26 15:13:34 +00:00
|
|
|
|
return this._GpgME.decrypt(message);
|
2018-04-18 14:38:06 +00:00
|
|
|
|
// 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.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-04-23 15:18:46 +00:00
|
|
|
|
* Translation layer offering basic Keyring API to be used in Mailvelope.
|
|
|
|
|
* It may still be changed/expanded/merged with GPGME_Keyring
|
2018-04-18 14:38:06 +00:00
|
|
|
|
*/
|
2018-04-24 17:29:32 +00:00
|
|
|
|
class GPGME_Keyring_openpgpmode {
|
2018-04-24 16:44:30 +00:00
|
|
|
|
constructor(connection){
|
|
|
|
|
this._gpgme_keyring = new GPGME_Keyring(connection);
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-23 15:18:46 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2018-04-24 17:29:32 +00:00
|
|
|
|
* @returns {Array<GPGME_Key_openpgpmode>}
|
2018-04-23 15:18:46 +00:00
|
|
|
|
* //TODO: Check if IsDefault is also always hasSecret
|
2018-04-24 17:29:32 +00:00
|
|
|
|
* TODO Check if async is required
|
2018-04-23 15:18:46 +00:00
|
|
|
|
*/
|
|
|
|
|
getPublicKeys(){
|
2018-04-24 17:29:32 +00:00
|
|
|
|
return translateKeys(
|
|
|
|
|
this._gpgme_keyring.getKeys(null, true));
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-23 15:18:46 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 <GPGME_Key>}
|
|
|
|
|
*/
|
|
|
|
|
getDefaultKey(){
|
|
|
|
|
this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){
|
|
|
|
|
if (result.length === 1){
|
2018-04-24 17:29:32 +00:00
|
|
|
|
return Promise.resolve(
|
|
|
|
|
translateKeys(result)[0]);
|
2018-04-23 15:18:46 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// TODO: Can there be "no default key"?
|
|
|
|
|
// TODO: Can there be several default keys?
|
2018-04-25 13:59:36 +00:00
|
|
|
|
return gpgme_error('TODO');
|
2018-04-23 15:18:46 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.<Array.<undefined>, 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"){
|
2018-04-25 13:59:36 +00:00
|
|
|
|
return Promise.reject(gpgme_error('PARAM_WRONG'));
|
2018-04-23 15:18:46 +00:00
|
|
|
|
}
|
|
|
|
|
if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){
|
2018-04-25 13:59:36 +00:00
|
|
|
|
return Promise.reject(gpgme_error('PARAM_WRONG'));
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-23 15:18:46 +00:00
|
|
|
|
let key_to_delete = new GPGME_Key(key.fingerprint);
|
|
|
|
|
return key_to_delete.deleteKey(key.secret);
|
2018-04-18 14:38:06 +00:00
|
|
|
|
}
|
2018-04-23 15:18:46 +00:00
|
|
|
|
}
|
2018-04-24 17:29:32 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
2018-04-25 13:59:36 +00:00
|
|
|
|
} else if (!this._GPGME_Key && isFingerprint(value)){
|
|
|
|
|
this._GPGME_Key = new GPGME_Key(value);
|
2018-04-24 17:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|