diff options
| author | Andre Heinecke <[email protected]> | 2018-08-22 11:15:35 +0000 | 
|---|---|---|
| committer | Andre Heinecke <[email protected]> | 2018-08-22 11:15:35 +0000 | 
| commit | 59ed27bae14da6c1ba6848b34acfc836846a27bc (patch) | |
| tree | f9efc016f561129c4c02f41bf0e84b566883d6eb /lang/js/src/Keyring.js | |
| parent | json: Add proper decrypt_result_t handling (diff) | |
| parent | js: changed verify signature result infos (diff) | |
| download | gpgme-59ed27bae14da6c1ba6848b34acfc836846a27bc.tar.gz gpgme-59ed27bae14da6c1ba6848b34acfc836846a27bc.zip | |
Merge branch 'javascript-binding'
This adds a new language binding "gpgme.js" to GPGME. It
serves as a bridge between the native-messaging service "gpgme-json"
and JavaScript Applications.
The first user of this binding will be Mailvelope which will
see GnuPG integration in the near future.
GnuPG-Bug-Id: T4107
Diffstat (limited to '')
| -rw-r--r-- | lang/js/src/Keyring.js | 435 | 
1 files changed, 435 insertions, 0 deletions
| diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js new file mode 100644 index 00000000..cb053ba1 --- /dev/null +++ b/lang/js/src/Keyring.js @@ -0,0 +1,435 @@ +/* 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+ + * + * Author(s): + *     Maximilian Krambach <[email protected]> + */ + + +import { createMessage } from './Message'; +import { createKey } from './Key'; +import { isFingerprint } from './Helpers'; +import { gpgme_error } from './Errors'; + +/** + * This class offers access to the gnupg keyring + */ +export class GPGME_Keyring { + +    /** +     * Queries Keys (all Keys or a subset) from gnupg. +     * +     * @param {String | Array<String>} pattern (optional) A pattern to +     * search for in userIds or KeyIds. +     * @param {Boolean} prepare_sync (optional) if set to true, most data +     * (with the exception of armored Key blocks) will be cached for the +     * Keys. This enables direct, synchronous use of these properties for +     * all keys. It does not check for changes on the backend. The cached +     * information can be updated with the {@link Key.refresh} method. +     * @param {Boolean} search (optional) retrieve Keys from external +     * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup) +     * @returns {Promise<Array<GPGME_Key>>} +     * @static +     * @async +     */ +    getKeys (pattern, prepare_sync=false, search=false){ +        return new Promise(function (resolve, reject) { +            let msg = createMessage('keylist'); +            if (pattern !== undefined && pattern !== null){ +                msg.setParameter('keys', pattern); +            } +            msg.setParameter('sigs', true); +            if (search === true){ +                msg.setParameter('locate', true); +            } +            msg.post().then(function (result){ +                let resultset = []; +                if (result.keys.length === 0){ +                    resolve([]); +                } else { +                    let secondrequest; +                    if (prepare_sync === true) { +                        secondrequest = function () { +                            let msg2 = createMessage('keylist'); +                            if (pattern){ +                                msg2.setParameter('keys', pattern); +                            } +                            msg2.setParameter('secret', true); +                            return msg2.post(); +                        }; +                    } else { +                        secondrequest = function () { +                            return Promise.resolve(true); +                        }; +                    } +                    secondrequest().then(function (answer) { +                        for (let i=0; i < result.keys.length; i++){ +                            if (prepare_sync === true){ +                                if (answer && answer.keys) { +                                    for (let j=0; +                                        j < answer.keys.length; j++ ){ +                                        const a = answer.keys[j]; +                                        const b = result.keys[i]; +                                        if ( +                                            a.fingerprint === b.fingerprint +                                        ) { +                                            if (a.secret === true){ +                                                b.hasSecret = true; +                                            } else { +                                                b.hasSecret = false; +                                            } +                                            break; +                                        } +                                    } +                                } +                            } +                            let k = createKey(result.keys[i].fingerprint, +                                !prepare_sync, result.keys[i]); +                            resultset.push(k); +                        } +                        resolve(resultset); +                    }, function (error){ +                        reject(error); +                    }); +                } +            }); +        }); +    } + +    /** +     * @typedef {Object} exportResult The result of a getKeysArmored +     * operation. +     * @property {String} armored The public Key(s) as armored block. Note +     * that the result is one armored block, and not a block per key. +     * @property {Array<String>} secret_fprs (optional) list of +     * fingerprints for those Keys that also have a secret Key available in +     * gnupg. The secret key will not be exported, but the fingerprint can +     * be used in operations needing a secret key. +     */ + +    /** +     * Fetches the armored public Key blocks for all Keys matching the +     * pattern (if no pattern is given, fetches all keys known to gnupg). +     * @param {String|Array<String>} pattern (optional) The Pattern to +     * search for +     * @param {Boolean} with_secret_fpr (optional) also return a list of +     * fingerprints for the keys that have a secret key available +     * @returns {Promise<exportResult|GPGME_Error>} Object containing the +     * armored Key(s) and additional information. +     * @static +     * @async +     */ +    getKeysArmored (pattern, with_secret_fpr) { +        return new Promise(function (resolve, reject) { +            let msg = createMessage('export'); +            msg.setParameter('armor', true); +            if (with_secret_fpr === true) { +                msg.setParameter('with-sec-fprs', true); +            } +            if (pattern !== undefined && pattern !== null){ +                msg.setParameter('keys', pattern); +            } +            msg.post().then(function (answer){ +                const result = { armored: answer.data }; +                if (with_secret_fpr === true +                    && answer.hasOwnProperty('sec-fprs') +                ) { +                    result.secret_fprs = answer['sec-fprs']; +                } +                resolve(result); +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * Returns the Key used by default in gnupg. +     * (a.k.a. 'primary Key or 'main key'). +     * It looks up the gpg configuration if set, or the first key that +     * contains a secret key. +     * +     * @returns {Promise<GPGME_Key|GPGME_Error>} +     * @async +     * @static +     */ +    getDefaultKey (prepare_sync = false) { +        let me = this; +        return new Promise(function (resolve, reject){ +            let msg = createMessage('config_opt'); +            msg.setParameter('component', 'gpg'); +            msg.setParameter('option', 'default-key'); +            msg.post().then(function (resp){ +                if (resp.option !== undefined +                    && resp.option.hasOwnProperty('value') +                    && resp.option.value.length === 1 +                    && resp.option.value[0].hasOwnProperty('string') +                    && typeof (resp.option.value[0].string) === 'string'){ +                    me.getKeys(resp.option.value[0].string, true).then( +                        function (keys){ +                            if (keys.length === 1){ +                                resolve(keys[0]); +                            } else { +                                reject(gpgme_error('KEY_NO_DEFAULT')); +                            } +                        }, function (error){ +                            reject(error); +                        }); +                } else { +                    let msg = createMessage('keylist'); +                    msg.setParameter('secret', true); +                    msg.post().then(function (result){ +                        if (result.keys.length === 0){ +                            reject(gpgme_error('KEY_NO_DEFAULT')); +                        } else { +                            for (let i=0; i< result.keys.length; i++ ) { +                                if (result.keys[i].invalid === false) { +                                    let k = createKey( +                                        result.keys[i].fingerprint, +                                        !prepare_sync, +                                        result.keys[i]); +                                    resolve(k); +                                    break; +                                } else if (i === result.keys.length - 1){ +                                    reject(gpgme_error('KEY_NO_DEFAULT')); +                                } +                            } +                        } +                    }, function (error){ +                        reject(error); +                    }); +                } +            }, function (error){ +                reject(error); +            }); +        }); +    } + +    /** +     * @typedef {Object} importResult The result of a Key update +     * @property {Object} summary Numerical summary of the result. See the +     * feedbackValues variable for available Keys values and the gnupg +     * documentation. +     * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html +     * for details on their meaning. +     * @property {Array<importedKeyResult>} Keys Array of Object containing +     * GPGME_Keys with additional import information +     * +     */ + +    /** +     * @typedef {Object} importedKeyResult +     * @property {GPGME_Key} key The resulting key +     * @property {String} 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. +     * @property {Boolean} changes.userId Changes in userIds +     * @property {Boolean} changes.signature Changes in signatures +     * @property {Boolean} changes.subkey Changes in subkeys +     */ + +    /** +     * Import an armored Key block into gnupg. Note that this currently +     * will not succeed on private Key blocks. +     * @param {String} armored Armored Key block of the Key(s) to be +     * imported into gnupg +     * @param {Boolean} prepare_sync prepare the keys for synched use +     * (see {@link getKeys}). +     * @returns {Promise<importResult>} A summary and Keys considered. +     * @async +     * @static +     */ +    importKey (armored, prepare_sync) { +        let feedbackValues = ['considered', 'no_user_id', 'imported', +            'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys', +            'new_signatures', 'new_revocations', 'secret_read', +            'secret_imported', 'secret_unchanged', 'skipped_new_keys', +            'not_imported', 'skipped_v3_keys']; +        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 = []; +                let summary = {}; +                for (let i=0; i < feedbackValues.length; i++ ){ +                    summary[feedbackValues[i]] = +                        response.result[feedbackValues[i]]; +                } +                if (!response.result.hasOwnProperty('imports') || +                    response.result.imports.length === 0 +                ){ +                    resolve({ Keys:[],summary: summary }); +                    return; +                } +                for (let res=0; res<response.result.imports.length; res++){ +                    let result = response.result.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({ Keys:resultset,summary: summary }); +                    }, 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({ Keys:resultset,summary:summary }); +                } + +            }, function (error){ +                reject(error); +            }); + + +        }); + + +    } + +    /** +     * Convenience function for deleting a Key. See {@link Key.delete} for +     * further information about the return values. +     * @param {String} fingerprint +     * @returns {Promise<Boolean|GPGME_Error>} +     * @async +     * @static +     */ +    deleteKey (fingerprint){ +        if (isFingerprint(fingerprint) === true) { +            let key = createKey(fingerprint); +            return key.delete(); +        } else { +            return Promise.reject(gpgme_error('KEY_INVALID')); +        } +    } + +    /** +     * 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 or exported from inside gpgme.js. +     * +     * @param {String} userId The user Id, e.g. 'Foo Bar <[email protected]>' +     * @param {String} algo (optional) algorithm (and optionally key size) +     * to be used. See {@link supportedKeyAlgos} below for supported +     * values. If ommitted, 'default' is used. +     * @param {Number} expires (optional) Expiration time in seconds from now. +     * If not set or set to 0, expiration will be 'never' +     * @param {String} subkey_algo (optional) algorithm of the encryption +     * subkey. If ommited the same as algo is used. +     * +     * @return {Promise<Key|GPGME_Error>} +     * @async +     */ +    generateKey (userId, algo = 'default', expires, subkey_algo){ +        if ( +            typeof (userId) !== 'string' || +            // eslint-disable-next-line no-use-before-define +            supportedKeyAlgos.indexOf(algo) < 0 || +            (expires && !( Number.isInteger(expires) || expires < 0 )) +        ){ +            return Promise.reject(gpgme_error('PARAM_WRONG')); +        } +        // eslint-disable-next-line no-use-before-define +        if (subkey_algo && supportedKeyAlgos.indexOf(subkey_algo) < 0 ){ +            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 (subkey_algo) { +                msg.setParameter('subkey-algo', subkey_algo ); +            } +            if (expires){ +                msg.setParameter('expires', expires); +            } else { +                msg.setParameter('expires', 0); +            } +            msg.post().then(function (response){ +                me.getKeys(response.fingerprint, true).then( +                    // TODO prepare_sync? +                    function (result){ +                        resolve(result); +                    }, function (error){ +                        reject(error); +                    }); +            }, function (error) { +                reject(error); +            }); +        }); +    } +} + + +/** + * List of algorithms supported for key generation. Please refer to the gnupg + * documentation for details + */ +const supportedKeyAlgos = [ +    'default', +    'rsa', 'rsa2048', 'rsa3072', 'rsa4096', +    'dsa', 'dsa2048', 'dsa3072', 'dsa4096', +    'elg', 'elg2048', 'elg3072', 'elg4096', +    'ed25519', +    'cv25519', +    'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', +    'NIST P-256', 'NIST P-384', 'NIST P-521' +];
\ No newline at end of file | 
