e32f43a37f793309e64fd40b27a67e19f303fff6
[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 { GPGMEJS_Error } from './Errors'
32
33
34  export class GpgME_openpgpmode {
35
36     constructor(connection){
37         this.initGpgME(connection);
38     }
39
40     get Keyring(){
41         if (this._keyring){
42             return this._keyring;
43         }
44         return undefined;
45     }
46
47     initGpgME(connection){
48         this._GpgME = new GpgME(connection);
49         this._Keyring = new GPGME_Keyring_openpgpmode(connection);
50     }
51
52     get GpgME(){
53         if (this._GpGME){
54             return this._GpGME;
55         }
56     }
57
58     /**
59      * Encrypt Message
60      * Supported:
61      * @param  {String|Uint8Array} data
62      * @param  {Key|Array<Key>} publicKeys
63      * @param  {Boolean} wildcard
64      * TODO:
65      * @param  {Key|Array<Key>} privateKeys
66      * @param  {String} filename
67      * @param  {module:enums.compression} compression
68      * @param  {Boolean} armor
69      * @param  {Boolean} detached
70      * unsupported:
71      * @param  {String|Array<String>} passwords
72      * @param  {Object} sessionKey
73      * @param  {Signature} signature
74      * @param  {Boolean} returnSessionKey
75      *
76      * @returns {Promise<Object>}
77      *      {data: ASCII armored message,
78      *      signature: detached signature if 'detached' is true
79      *      }
80     * @async
81     * @static
82     */
83     encrypt({data = '', publicKeys = '', privateKeys, passwords, sessionKey,
84         filename, compression, armor=true, detached=false, signature=null,
85         returnSessionKey=null, wildcard=false, date=null}) {
86         if (passwords !== undefined
87             || sessionKey !== undefined
88             || signature !== null
89             || returnSessionKey !== null
90             || date !== null){
91             return Promise.reject(GPMGEJS_Error('NOT_IMPLEMENTED'));
92         }
93         if ( privateKeys
94             || filename
95             || compression
96             || armor === false
97             || detached == true){
98                 return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED'));
99         }
100         return this.GpgME.encrypt(data, translateKeyInput(publicKeys), wildcard);
101     }
102
103     /** Decrypt Message
104     * supported
105     * TODO: @param {Message} message TODO: for now it accepts an armored string only
106     * Unsupported:
107     * @param  {String|Array<String>} passwords
108     * @param  {Object|Array<Object>} sessionKeys
109     * @param  {Date} date
110
111     * TODO
112     * @param  {Key|Array<Key>} privateKey
113     * @param  {Key|Array<Key>} publicKeys
114     * @param  {String} format                    (optional) return data format either as 'utf8' or 'binary'
115     * @param  {Signature} signature              (optional) detached signature for verification
116
117     * @returns {Promise<Object>}             decrypted and verified message in the form:
118     *                                         { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
119     * @async
120     * @static
121     */
122     decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null, date}) {
123         if (passwords !== undefined
124             || sessionKeys
125             || date){
126             return Promise.reject(GPGMEJS_Error('NOT_IMPLEMENTED'));
127         }
128         if ( privateKeys
129             || publicKeys
130             || format !== 'utf8'
131             || signature
132         ){
133             return Promise.reject(GPGMEJS_Error('NOT_YET_IMPLEMENTED'));
134         }
135         return this.GpgME.decrypt(message);
136         // TODO: translate between:
137         // openpgp:
138         // { data:Uint8Array|String,
139         //      filename:String,
140         //      signatures:[{ keyid:String, valid:Boolean }] }
141         // and gnupg:
142         // data:   The decrypted data.  This may be base64 encoded.
143         // base64: Boolean indicating whether data is base64 encoded.
144         // mime:   A Boolean indicating whether the data is a MIME object.
145         // info:   An optional object with extra information.
146     }
147 }
148
149 /**
150  * Translation layer offering basic Keyring API to be used in Mailvelope.
151  * It may still be changed/expanded/merged with GPGME_Keyring
152  */
153 class GPGME_Keyring_openpgpmode {
154     constructor(connection){
155         this._gpgme_keyring = new GPGME_Keyring(connection);
156     }
157
158     /**
159      * Returns a GPGME_Key Object for each Key in the gnupg Keyring. This
160      * includes keys openpgpjs considers 'private' (usable for signing), with
161      * the difference that Key.armored will NOT contain any secret information.
162      * Please also note that a GPGME_Key does not offer full openpgpjs- Key
163      * compatibility.
164      * @returns {Array<GPGME_Key_openpgpmode>}
165      * //TODO: Check if IsDefault is also always hasSecret
166      * TODO Check if async is required
167      */
168     getPublicKeys(){
169         return translateKeys(
170             this._gpgme_keyring.getKeys(null, true));
171     }
172
173     /**
174      * Returns the Default Key used for crypto operation in gnupg.
175      * Please note that the armored property does not contained secret key blocks,
176      * despite secret blocks being part of the key itself.
177      * @returns {Promise <GPGME_Key>}
178      */
179     getDefaultKey(){
180         this._gpgme_keyring.getSubset({defaultKey: true}).then(function(result){
181             if (result.length === 1){
182                 return Promise.resolve(
183                     translateKeys(result)[0]);
184             }
185             else {
186                 // TODO: Can there be "no default key"?
187                 // TODO: Can there be several default keys?
188                 return GPGMEJS_Error('TODO');
189             }
190         });
191     }
192
193     /**
194      * Deletes a Key
195      * @param {Object} Object identifying key
196      * @param {String} key.fingerprint - fingerprint of the to be deleted key
197      * @param {Boolean} key.secret - indicator if private key should be deleted as well
198
199      * @returns {Promise.<Array.<undefined>, Error>} TBD: Not sure what is wanted
200      TODO @throws {Error} error.code = ‘KEY_NOT_EXIST’ - there is no key for the given fingerprint
201      TODO @throws {Error} error.code = ‘NO_SECRET_KEY’ - secret indicator set, but no secret key exists
202      */
203     deleteKey(key){
204         if (typeof(key) !== "object"){
205             return Promise.reject(GPGMEJS_Error('PARAM_WRONG'));
206         }
207         if ( !key.fingerprint || ! isFingerprint(key.fingerprint)){
208             return Promise.reject(GPGMEJS_Error('PARAM_WRONG'));
209         }
210         let key_to_delete = new GPGME_Key(key.fingerprint);
211         return key_to_delete.deleteKey(key.secret);
212     }
213 }
214
215 /**
216  * TODO error handling.
217  * Offers the Key information as the openpgpmode wants
218  */
219 class GPGME_Key_openpgpmode {
220     constructor(value){
221         this.init = value;
222     }
223
224     set init (value){
225         if (!this._GPGME_Key && value instanceof GPGME_Key){
226             this._GPGME_Key = value;
227         } else if (!this._GPGME_Key && isFingerprint(fpr)){
228             this._GPGME_Key = new GPGME_Key;
229         }
230     }
231
232     get fingerprint(){
233         return this._GPGME_Key.fingerprint;
234     }
235
236     get armor(){
237         return this._GPGME_Key.armored;
238     }
239
240     get secret(){
241         return this._GPGME_Key.hasSecret;
242     }
243
244     get default(){
245         return this._GPGME_Key.isDefault;
246     }
247 }
248
249 /**
250  * creates GPGME_Key_openpgpmode from GPGME_Keys
251  */
252 function translateKeys(input){
253     if (!Array.isArray(input)){
254         input = [input];
255     }
256     let resultset;
257     for (let i=0; i< input.length; i++){
258         resultset.push(new GPGME_Key_openpgpmode(input[i]));
259     }
260     return resultset;
261 }