js: Configuration and Error handling
[gpgme.git] / lang / js / src / gpgmejs_openpgpjs.js
1 /* gpgme.js - Javascript integration for gpgme
2  * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  * SPDX-License-Identifier: LGPL-2.1+
19  */
20
21 /**
22  * This is a compatibility API to be used as openpgpjs syntax.
23  * Non-implemented options will throw an error if set (not null or undefined)
24  * TODO Some info about differences
25  */
26
27  import { GpgME } from "./gpgmejs";
28  import {GPGME_Keyring}  from "./Keyring"
29  import { GPGME_Key } from "./Key";
30  import { isFingerprint } from "./Helpers"
31  import { gpgme_error } from "./Errors"
32
33
34  export class GpgME_openpgpmode {
35
36     constructor(connection, config = {}){
37         this.initGpgME(connection, config);
38     }
39
40     get Keyring(){
41         if (this._keyring){
42             return this._keyring;
43         }
44         return undefined;
45     }
46
47     initGpgME(connection, config = {}){
48         if (connection && typeof(config) ==='object'){
49             this._config = config;
50             if (!this._GPGME){
51                 this._GpgME = new GpgME(connection, config);
52             }
53             if (!this._Keyring){
54                 this._Keyring = new GPGME_Keyring_openpgpmode(connection);
55             }
56         }
57     }
58
59     get GpgME(){
60         if (this._GpGME){
61             return this._GpGME;
62         }
63     }
64
65     /**
66      * Encrypt Message
67      * Supported:
68      * @param  {String|Uint8Array} data
69      * //an openpgp Message also accepted here. TODO: is this wanted?
70      * @param  {Key|Array<Key>} publicKeys
71      * //Strings of Fingerprints
72      * @param  {Boolean} wildcard
73      * TODO:
74      * @param  {Key|Array<Key>} privateKeys // -> encryptsign
75      * @param  {module:enums.compression} compression //TODO accepts integer, if 0 (no compression) it won't compress
76      * @param  {Boolean} armor // TODO base64 switch
77      * @param  {Boolean} detached // --> encryptsign
78      * unsupported:
79      * @param  {String|Array<String>} passwords
80      * @param  {Object} sessionKey
81      * @param  {Signature} signature
82      * @param  {Boolean} returnSessionKey
83      * @param  {String} filename
84      *
85      * Can be set, but will be ignored:
86      *
87      * @returns {Promise<Object>}
88      *      {data: ASCII armored message,
89      *      signature: detached signature if 'detached' is true
90      *      }
91     * @async
92     * @static
93     */
94     encrypt({data = '', publicKeys = '', privateKeys, passwords=null,
95         sessionKey = null, filename, compression, armor=true, detached=false,
96         signature=null, returnSessionKey=null, wildcard=false, date=null}) {
97         if (passwords !== null
98             || sessionKey !== null
99             || signature !== null
100             || returnSessionKey !== null
101             || date !== null
102             ){
103             return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
104         }
105         if ( privateKeys
106             || compression
107             || armor === false
108             || detached == true){
109                 return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
110         }
111         if (filename){
112             if (this._config.unconsidered_params === 'warn'){
113                 GPMGEJS_Error('PARAM_IGNORED');
114             } else if (this._config.unconsidered_params === 'error'){
115                 return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
116             }
117         }
118         return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard);
119     }
120
121     /** Decrypt Message
122     * supported openpgpjs parameters:
123     * @param {Message|Uint8Array|String} message Message object from openpgpjs
124     * Unsupported:
125     * @param  {String|Array<String>} passwords
126     * @param  {Key|Array<Key>} privateKeys
127     * @param  {Object|Array<Object>} sessionKeys
128     * Not yet supported, but planned
129     * @param  {String} format                    (optional) return data format either as 'utf8' or 'binary'
130     * @param  {Signature} signature              (optional) detached signature for verification
131     * Ignored values: can be safely set, but have no effect
132     * @param  {Date} date
133     * @param  {Key|Array<Key>} publicKeys
134     *
135     * @returns {Promise<Object>}             decrypted and verified message in the form:
136     *                                         { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
137     * @async
138     * @static
139     */
140     decrypt({ message, privateKeys, passwords=null, sessionKeys,
141         publicKeys, format='utf8', signature=null, date= null}) {
142         if (passwords !== null || sessionKeys || privateKeys){
143             return Promise.reject(gpgme_error('NOT_IMPLEMENTED'));
144         }
145         if ( format !== 'utf8' || signature){
146             return Promise.reject(gpgme_error('NOT_YET_IMPLEMENTED'));
147         }
148         if (date !== null || publicKeys){
149             if (this._config.unconsidered_params === 'warn'){
150                 GPMGEJS_Error('PARAM_IGNORED');
151             } else if (this._config.unconsidered_params === 'reject'){
152                 return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
153             }
154         }
155         return this.GpgME.decrypt(message);
156         // TODO: translate between:
157         // openpgp:
158         // { data:Uint8Array|String,
159         //      filename:String,
160         //      signatures:[{ keyid:String, valid:Boolean }] }
161         // and gnupg:
162         // data:   The decrypted data.  This may be base64 encoded.
163         // base64: Boolean indicating whether data is base64 encoded.
164         // mime:   A Boolean indicating whether the data is a MIME object.
165         // info:   An optional object with extra information.
166     }
167 }
168
169 /**
170  * Translation layer offering basic Keyring API to be used in Mailvelope.
171  * It may still be changed/expanded/merged with GPGME_Keyring
172  */
173 class GPGME_Keyring_openpgpmode {
174     constructor(connection){
175         this._gpgme_keyring = new GPGME_Keyring(connection);
176     }
177
178     /**
179      * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This
180      * includes keys openpgpjs considers 'private' (usable for signing), with
181      * the difference that Key.armored will NOT contain any secret information.
182      * Please also note that a GPGME_Key does not offer full openpgpjs- Key
183      * compatibility.
184      * @returns {Array<GPGME_Key_openpgpmode>}
185      * //TODO: Check if IsDefault is also always hasSecret
186      * TODO Check if async is required
187      */
188     getPublicKeys(){
189         return translateKeys(
190             this._gpgme_keyring.getKeys(null, true));
191     }
192
193     /**
194      * Returns the Default Key used for crypto operation in gnupg.
195      * Please note that the armored property does not contained secret key blocks,
196      * despite secret blocks being part of the key itself.
197      * @returns {Promise <GPGME_Key>}
198      */
199     getDefaultKey(){
200         this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){
201             if (result.length === 1){
202                 return Promise.resolve(
203                     translateKeys(result)[0]);
204             }
205             else {
206                 // TODO: Can there be "no default key"?
207                 // TODO: Can there be several default keys?
208                 return gpgme_error('TODO');
209             }
210         });
211     }
212
213     /**
214      * Deletes a Key
215      * @param {Object} Object identifying key
216      * @param {String} key.fingerprint - fingerprint of the to be deleted key
217      * @param {Boolean} key.secret - indicator if private key should be deleted as well
218
219      * @returns {Promise.<Array.<undefined>, Error>} TBD: Not sure what is wanted
220      TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint
221      TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists
222      */
223     deleteKey(key){
224         if (typeof(key) !== "object"){
225             return Promise.reject(gpgme_error('PARAM_WRONG'));
226         }
227         if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){
228             return Promise.reject(gpgme_error('PARAM_WRONG'));
229         }
230         let key_to_delete = new GPGME_Key(key.fingerprint);
231         return key_to_delete.deleteKey(key.secret);
232     }
233 }
234
235 /**
236  * TODO error handling.
237  * Offers the Key information as the openpgpmode wants
238  */
239 class GPGME_Key_openpgpmode {
240     constructor(value){
241         this.init = value;
242     }
243
244     set init (value){
245         if (!this._GPGME_Key && value instanceof GPGME_Key){
246             this._GPGME_Key = value;
247         } else if (!this._GPGME_Key && isFingerprint(value)){
248             this._GPGME_Key = new GPGME_Key(value);
249         }
250     }
251
252     get fingerprint(){
253         return this._GPGME_Key.fingerprint;
254     }
255
256     get armor(){
257         return this._GPGME_Key.armored;
258     }
259
260     get secret(){
261         return this._GPGME_Key.hasSecret;
262     }
263
264     get default(){
265         return this._GPGME_Key.isDefault;
266     }
267 }
268
269 /**
270  * creates GPGME_Key_openpgpmode from GPGME_Keys
271  */
272 function translateKeys(input){
273     if (!Array.isArray(input)){
274         input = [input];
275     }
276     let resultset;
277     for (let i=0; i< input.length; i++){
278         resultset.push(new GPGME_Key_openpgpmode(input[i]));
279     }
280     return resultset;
281 }