Blame lang/js/src/Connection.js

Packit Service 30b792
/* gpgme.js - Javascript integration for gpgme
Packit Service 30b792
 * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
Packit Service 30b792
 *
Packit Service 30b792
 * This file is part of GPGME.
Packit Service 30b792
 *
Packit Service 30b792
 * GPGME is free software; you can redistribute it and/or modify it
Packit Service 30b792
 * under the terms of the GNU Lesser General Public License as
Packit Service 30b792
 * published by the Free Software Foundation; either version 2.1 of
Packit Service 30b792
 * the License, or (at your option) any later version.
Packit Service 30b792
 *
Packit Service 30b792
 * GPGME is distributed in the hope that it will be useful, but
Packit Service 30b792
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 30b792
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 30b792
 * Lesser General Public License for more details.
Packit Service 30b792
 *
Packit Service 30b792
 * You should have received a copy of the GNU Lesser General Public
Packit Service 30b792
 * License along with this program; if not, see <https://www.gnu.org/licenses/>.
Packit Service 30b792
 * SPDX-License-Identifier: LGPL-2.1+
Packit Service 30b792
 *
Packit Service 30b792
 * Author(s):
Packit Service 30b792
 *     Maximilian Krambach <mkrambach@intevation.de>
Packit Service 30b792
 */
Packit Service 30b792
Packit Service 30b792
/* global chrome */
Packit Service 30b792
Packit Service 30b792
import { permittedOperations } from './permittedOperations';
Packit Service 30b792
import { gpgme_error } from './Errors';
Packit Service 30b792
import { GPGME_Message, createMessage } from './Message';
Packit Service 30b792
import { decode, atobArray, Utf8ArrayToStr } from './Helpers';
Packit Service 30b792
Packit Service 30b792
/**
Packit Service 30b792
 * A Connection handles the nativeMessaging interaction via a port. As the
Packit Service 30b792
 * protocol only allows up to 1MB of message sent from the nativeApp to the
Packit Service 30b792
 * browser, the connection will stay open until all parts of a communication
Packit Service 30b792
 * are finished. For a new request, a new port will open, to avoid mixing
Packit Service 30b792
 * contexts.
Packit Service 30b792
 * @class
Packit Service 30b792
 * @private
Packit Service 30b792
 */
Packit Service 30b792
export class Connection{
Packit Service 30b792
Packit Service 30b792
    constructor (){
Packit Service 30b792
        this._connection = chrome.runtime.connectNative('gpgmejson');
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
     * Immediately closes an open port.
Packit Service 30b792
     */
Packit Service 30b792
    disconnect () {
Packit Service 30b792
        if (this._connection){
Packit Service 30b792
            this._connection.disconnect();
Packit Service 30b792
            this._connection = null;
Packit Service 30b792
        }
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
    * @typedef {Object} backEndDetails
Packit Service 30b792
    * @property {String} gpgme Version number of gpgme
Packit Service 30b792
    * @property {Array<Object>} info Further information about the backend
Packit Service 30b792
    * and the used applications (Example:
Packit Service 30b792
    * 
Packit Service 30b792
    * {
Packit Service 30b792
    *          "protocol":     "OpenPGP",
Packit Service 30b792
    *          "fname":        "/usr/bin/gpg",
Packit Service 30b792
    *          "version":      "2.2.6",
Packit Service 30b792
    *          "req_version":  "1.4.0",
Packit Service 30b792
    *          "homedir":      "default"
Packit Service 30b792
    * }
Packit Service 30b792
    * 
Packit Service 30b792
    */
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
     * Retrieves the information about the backend.
Packit Service 30b792
     * @param {Boolean} details (optional) If set to false, the promise will
Packit Service 30b792
     *  just return if a connection was successful.
Packit Service 30b792
     * @param {Number} timeout (optional)
Packit Service 30b792
     * @returns {Promise<backEndDetails>|Promise<Boolean>} Details from the
Packit Service 30b792
     * backend
Packit Service 30b792
     * @async
Packit Service 30b792
     */
Packit Service 30b792
    checkConnection (details = true, timeout = 1000){
Packit Service 30b792
        if (typeof timeout !== 'number' && timeout <= 0) {
Packit Service 30b792
            timeout = 1000;
Packit Service 30b792
        }
Packit Service 30b792
        const msg = createMessage('version');
Packit Service 30b792
        if (details === true) {
Packit Service 30b792
            return this.post(msg);
Packit Service 30b792
        } else {
Packit Service 30b792
            let me = this;
Packit Service 30b792
            return new Promise(function (resolve) {
Packit Service 30b792
                Promise.race([
Packit Service 30b792
                    me.post(msg),
Packit Service 30b792
                    new Promise(function (resolve, reject){
Packit Service 30b792
                        setTimeout(function (){
Packit Service 30b792
                            reject(gpgme_error('CONN_TIMEOUT'));
Packit Service 30b792
                        }, timeout);
Packit Service 30b792
                    })
Packit Service 30b792
                ]).then(function (){ // success
Packit Service 30b792
                    resolve(true);
Packit Service 30b792
                }, function (){ // failure
Packit Service 30b792
                    resolve(false);
Packit Service 30b792
                });
Packit Service 30b792
            });
Packit Service 30b792
        }
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
     * Sends a {@link GPGME_Message} via the nativeMessaging port. It
Packit Service 30b792
     * resolves with the completed answer after all parts have been
Packit Service 30b792
     * received and reassembled, or rejects with an {@link GPGME_Error}.
Packit Service 30b792
     *
Packit Service 30b792
     * @param {GPGME_Message} message
Packit Service 30b792
     * @returns {Promise<*>} The collected answer, depending on the messages'
Packit Service 30b792
     * operation
Packit Service 30b792
     * @private
Packit Service 30b792
     * @async
Packit Service 30b792
     */
Packit Service 30b792
    post (message){
Packit Service 30b792
        if (!message || !(message instanceof GPGME_Message)){
Packit Service 30b792
            this.disconnect();
Packit Service 30b792
            return Promise.reject(gpgme_error(
Packit Service 30b792
                'PARAM_WRONG', 'Connection.post'));
Packit Service 30b792
        }
Packit Service 30b792
        if (message.isComplete() !== true){
Packit Service 30b792
            this.disconnect();
Packit Service 30b792
            return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
Packit Service 30b792
        }
Packit Service 30b792
        let chunksize = message.chunksize;
Packit Service 30b792
        const me = this;
Packit Service 30b792
        return new Promise(function (resolve, reject){
Packit Service 30b792
            let answer = new Answer(message);
Packit Service 30b792
            let listener = function (msg) {
Packit Service 30b792
                if (!msg){
Packit Service 30b792
                    me._connection.onMessage.removeListener(listener);
Packit Service 30b792
                    me._connection.disconnect();
Packit Service 30b792
                    reject(gpgme_error('CONN_EMPTY_GPG_ANSWER'));
Packit Service 30b792
                } else {
Packit Service 30b792
                    let answer_result = answer.collect(msg);
Packit Service 30b792
                    if (answer_result !== true){
Packit Service 30b792
                        me._connection.onMessage.removeListener(listener);
Packit Service 30b792
                        me._connection.disconnect();
Packit Service 30b792
                        reject(answer_result);
Packit Service 30b792
                    } else {
Packit Service 30b792
                        if (msg.more === true){
Packit Service 30b792
                            me._connection.postMessage({
Packit Service 30b792
                                'op': 'getmore',
Packit Service 30b792
                                'chunksize': chunksize
Packit Service 30b792
                            });
Packit Service 30b792
                        } else {
Packit Service 30b792
                            me._connection.onMessage.removeListener(listener);
Packit Service 30b792
                            me._connection.disconnect();
Packit Service 30b792
                            const message = answer.getMessage();
Packit Service 30b792
                            if (message instanceof Error){
Packit Service 30b792
                                reject(message);
Packit Service 30b792
                            } else {
Packit Service 30b792
                                resolve(message);
Packit Service 30b792
                            }
Packit Service 30b792
                        }
Packit Service 30b792
                    }
Packit Service 30b792
                }
Packit Service 30b792
            };
Packit Service 30b792
            me._connection.onMessage.addListener(listener);
Packit Service 30b792
            if (permittedOperations[message.operation].pinentry){
Packit Service 30b792
                return me._connection.postMessage(message.message);
Packit Service 30b792
            } else {
Packit Service 30b792
                return Promise.race([
Packit Service 30b792
                    me._connection.postMessage(message.message),
Packit Service 30b792
                    function (resolve, reject){
Packit Service 30b792
                        setTimeout(function (){
Packit Service 30b792
                            me._connection.disconnect();
Packit Service 30b792
                            reject(gpgme_error('CONN_TIMEOUT'));
Packit Service 30b792
                        }, 5000);
Packit Service 30b792
                    }
Packit Service 30b792
                ]).then(function (result){
Packit Service 30b792
                    return result;
Packit Service 30b792
                }, function (reject){
Packit Service 30b792
                    if (!(reject instanceof Error)) {
Packit Service 30b792
                        me._connection.disconnect();
Packit Service 30b792
                        return gpgme_error('GNUPG_ERROR', reject);
Packit Service 30b792
                    } else {
Packit Service 30b792
                        return reject;
Packit Service 30b792
                    }
Packit Service 30b792
                });
Packit Service 30b792
            }
Packit Service 30b792
        });
Packit Service 30b792
    }
Packit Service 30b792
}
Packit Service 30b792
Packit Service 30b792
Packit Service 30b792
/**
Packit Service 30b792
 * A class for answer objects, checking and processing the return messages of
Packit Service 30b792
 * the nativeMessaging communication.
Packit Service 30b792
 * @private
Packit Service 30b792
 */
Packit Service 30b792
class Answer{
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
     * @param {GPGME_Message} message
Packit Service 30b792
     */
Packit Service 30b792
    constructor (message){
Packit Service 30b792
        this._operation = message.operation;
Packit Service 30b792
        this._expected = message.expected;
Packit Service 30b792
        this._response_b64 = null;
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
    get operation (){
Packit Service 30b792
        return this._operation;
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
    get expected (){
Packit Service 30b792
        return this._expected;
Packit Service 30b792
    }
Packit Service 30b792
Packit Service 30b792
    /**
Packit Service 30b792
     * Adds incoming base64 encoded data to the existing response
Packit Service 30b792
     * @param {*} msg base64 encoded data.
Packit Service 30b792
     * @returns {Boolean}
Packit Service 30b792
     *
Packit Service 30b792
     * @private
Packit Service 30b792
     */
Packit Service 30b792
    collect (msg){
Packit Service 30b792
        if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) {
Packit Service 30b792
            return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
        }
Packit Service 30b792
        if (!this._response_b64){
Packit Service 30b792
            this._response_b64 = msg.response;
Packit Service 30b792
            return true;
Packit Service 30b792
        } else {
Packit Service 30b792
            this._response_b64 += msg.response;
Packit Service 30b792
            return true;
Packit Service 30b792
        }
Packit Service 30b792
    }
Packit Service 30b792
    /**
Packit Service 30b792
     * Decodes and verifies the base64 encoded answer data. Verified against
Packit Service 30b792
     * {@link permittedOperations}.
Packit Service 30b792
     * @returns {Object} The readable gpnupg answer
Packit Service 30b792
     */
Packit Service 30b792
    getMessage (){
Packit Service 30b792
        if (this._response_b64 === null){
Packit Service 30b792
            return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
        }
Packit Service 30b792
        let _decodedResponse = JSON.parse(atob(this._response_b64));
Packit Service 30b792
        let _response = {
Packit Service 30b792
            format: 'ascii'
Packit Service 30b792
        };
Packit Service 30b792
        let messageKeys = Object.keys(_decodedResponse);
Packit Service 30b792
        let poa = permittedOperations[this.operation].answer;
Packit Service 30b792
        if (messageKeys.length === 0){
Packit Service 30b792
            return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
        }
Packit Service 30b792
        for (let i= 0; i < messageKeys.length; i++){
Packit Service 30b792
            let key = messageKeys[i];
Packit Service 30b792
            switch (key) {
Packit Service 30b792
            case 'type': {
Packit Service 30b792
                if (_decodedResponse.type === 'error'){
Packit Service 30b792
                    return (gpgme_error('GNUPG_ERROR',
Packit Service 30b792
                        decode(_decodedResponse.msg)));
Packit Service 30b792
                } else if (poa.type.indexOf(_decodedResponse.type) < 0){
Packit Service 30b792
                    return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
                }
Packit Service 30b792
                break;
Packit Service 30b792
            }
Packit Service 30b792
            case 'base64': {
Packit Service 30b792
                break;
Packit Service 30b792
            }
Packit Service 30b792
            case 'msg': {
Packit Service 30b792
                if (_decodedResponse.type === 'error'){
Packit Service 30b792
                    return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg));
Packit Service 30b792
                }
Packit Service 30b792
                break;
Packit Service 30b792
            }
Packit Service 30b792
            default: {
Packit Service 30b792
                let answerType = null;
Packit Service 30b792
                if (poa.payload && poa.payload.hasOwnProperty(key)){
Packit Service 30b792
                    answerType = 'p';
Packit Service 30b792
                } else if (poa.info && poa.info.hasOwnProperty(key)){
Packit Service 30b792
                    answerType = 'i';
Packit Service 30b792
                }
Packit Service 30b792
                if (answerType !== 'p' && answerType !== 'i'){
Packit Service 30b792
                    return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
                }
Packit Service 30b792
Packit Service 30b792
                if (answerType === 'i') {
Packit Service 30b792
                    if ( typeof (_decodedResponse[key]) !== poa.info[key] ){
Packit Service 30b792
                        return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
                    }
Packit Service 30b792
                    _response[key] = decode(_decodedResponse[key]);
Packit Service 30b792
Packit Service 30b792
                } else if (answerType === 'p') {
Packit Service 30b792
                    if (_decodedResponse.base64 === true
Packit Service 30b792
                        && poa.payload[key] === 'string'
Packit Service 30b792
                    ) {
Packit Service 30b792
                        if (this.expected === 'uint8'){
Packit Service 30b792
                            _response[key] = atobArray(_decodedResponse[key]);
Packit Service 30b792
                            _response.format = 'uint8';
Packit Service 30b792
Packit Service 30b792
                        } else if (this.expected === 'base64'){
Packit Service 30b792
                            _response[key] = _decodedResponse[key];
Packit Service 30b792
                            _response.format = 'base64';
Packit Service 30b792
Packit Service 30b792
                        } else { // no 'expected'
Packit Service 30b792
                            _response[key] = Utf8ArrayToStr(
Packit Service 30b792
                                atobArray(_decodedResponse[key]));
Packit Service 30b792
                            _response.format = 'string';
Packit Service 30b792
                        }
Packit Service 30b792
                    } else if (poa.payload[key] === 'string') {
Packit Service 30b792
                        _response[key] = _decodedResponse[key];
Packit Service 30b792
                    } else {
Packit Service 30b792
                        // fallthrough, should not be reached
Packit Service 30b792
                        // (payload is always string)
Packit Service 30b792
                        return gpgme_error('CONN_UNEXPECTED_ANSWER');
Packit Service 30b792
                    }
Packit Service 30b792
                }
Packit Service 30b792
                break;
Packit Service 30b792
            } }
Packit Service 30b792
        }
Packit Service 30b792
        return _response;
Packit Service 30b792
    }
Packit Service 30b792
}