Source: secretstore/session.js

/** @module secretstore/session */
"use strict";

const utils = require("../utils.js");

/**
 * Error class, thrown upon a failed SS session http request.
 * 
 * @memberof module:secretstore/session
 * @class
 */
class SecretStoreSessionError extends Error {
    /**
     * @constructor
     * @param {String} message The error message 
     * @param {Object} response The response object
     */
    constructor(message, response) {
        super(message);
        this.response = response;
        this.name = "SecretStoreSessionError";
    }
};

/**
 * Generates server keys.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {Number} threshold Key threshold value. Please consider the guidelines when choosing this value: https://wiki.parity.io/Secret-Store.html#server-key-generation-session
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<String>} The hex-encoded public portion of server key
 */
function generateServerKey(url, serverKeyID, signedServerKeyID, threshold, verbose=true) {

    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/shadow/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID) + "/" + threshold,
            method: 'POST'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}

/**
 * Generating document key by one of the participating nodes. 
 * While it is possible (and more secure, if you’re not trusting the Secret Store nodes) 
 * to run separate server key generation and document key storing sessions, 
 * you can generate both keys simultaneously.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {Number} threshold Key threshold value. Please consider the guidelines when choosing this value: https://wiki.parity.io/Secret-Store.html#server-key-generation-session
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<String>} The hex-encoded document key, encrypted with requester public key (ECIES encryption is used)
 */
function generateServerAndDocumentKey(url, serverKeyID, signedServerKeyID, threshold, verbose=true) {

    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID) + "/" + threshold,
            method: 'POST'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}

/**
 * This session is a preferable way of retrieving previously generated document key.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<Object>} The hex-encoded decrypted_secret, common_point and decrypt_shadows fields
 */
function shadowRetrieveDocumentKey(url, serverKeyID, signedServerKeyID, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/shadow/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID),
            method: 'GET'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(JSON.parse(body));
            }
        });
    });
}

/**
 * The lighter version of the `document key shadow retrieval` session, 
 * which returns final document key (though, encrypted with requester public key) if you have enough trust in Secret Store nodes. 
 * During document key shadow retrieval session, document key is not reconstructed on any node. 
 * But it requires Secret Store client either to have an access to Parity RPCs, or to run some EC calculations to decrypt the document key.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<String>} The hex-encoded document key, encrypted with requester public key (ECIES encryption is used)
 */
function retrieveDocumentKey(url, serverKeyID, signedServerKeyID, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID),
            method: 'GET'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}

/**
 * Schnorr signing session, for computing Schnorr signature of a given message hash.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {String} messageHash The 256-bit hash of the message that needs to be signed
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<String>} The hex-encoded Schnorr signature (serialized as c || s), encrypted with requester public key (ECIES encryption is used)
 */ 
function signSchnorr(url, serverKeyID, signedServerKeyID, messageHash, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/schnorr/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID) + "/" + messageHash,
            method: 'GET'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}

/**
 * ECDSA signing session, for computing ECDSA signature of a given message hash.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID The server key ID
 * @param {String} signedServerKeyID The server key ID signed by SS user
 * @param {String} messageHash The 256-bit hash of the message that needs to be signed
 * @param {Boolean} verbose Whether to console log errors
 * @return {Promise<String>} The hex-encoded ECDSA signature (serialized as r || s || v), encrypted with requester public key (ECIES encryption is used)
 */ 
function signEcdsa(url, serverKeyID, signedServerKeyID, messageHash, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/ecdsa/" + utils.remove0x(serverKeyID) + "/" + utils.remove0x(signedServerKeyID) + "/" + messageHash,
            method: 'GET'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}


/**
 * Binds an externally-generated document key to a server key. Useable after a `server key generation` session.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} serverKeyID Same ID that was used in `server key generation session`
 * @param {String} signedServerKeyID Same server key id, signed by the same entity (author) that has signed the server key id in the `server key generation session`
 * @param {String} commonPoint The hex-encoded common point portion of encrypted document key
 * @param {String} encryptedPoint The hex-encoded encrypted point portion of encrypted document key
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<String>} Empty body of the response if everything was OK
 */
function storeDocumentKey(url, serverKeyID, signedServerKeyID, commonPoint, encryptedPoint, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        var options = {
            url: url + "/shadow/" + utils.remove0x(serverKeyID)
                + "/" + utils.remove0x(signedServerKeyID)
                + "/" + utils.remove0x(commonPoint)
                + "/" + utils.remove0x(encryptedPoint),
            method: 'POST'
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(body);
            }
        });
    });
}


/**
 * Nodes set change session. Requires all added, removed and stable nodes to be online for the duration of the session. 
 * Before starting the session, you’ll need to generate two administrator’s signatures: 
 * `old set` signature and `new set` signature. To generate these signatures, 
 * the Secret Store RPC methods should be used: `serversSetHash` and `signRawHash`.
 * 
 * @memberof module:secretstore/session
 * @param {String} url URL where the SS node is listening for incoming requests
 * @param {String} nodeIDsNewSet node IDs of the `new set`
 * @param {String} signatureOldSet ECDSA signature of all online nodes IDs `keccak(ordered_list(staying + added + removing))`
 * @param {String} signatureNewSet ECDSA signature of nodes IDs, that should stay in the Secret Store after the session ends `keccak(ordered_list(staying + added))`
 * @param {Boolean} verbose Whether to console log errors
 * @returns {Promise<Object>} Unknown
 */
function nodesSetChange(url, nodeIDsNewSet, signatureOldSet, signatureNewSet, verbose=true) {
    return new Promise((resolve, reject) => {
        const request = require('request');

        /*
        let x = '["' + nodeIDsNewSet[0] + '"';
        for(var i = 1; i < nodeIDsNewSet.length; i++) {
            x += ',"' + nodeIDsNewSet[i] + '"';
        }
        x += ']';
        */
        var options = {
            url: url + "/admin/servers_set_change"
                + "/" + utils.remove0x(signatureOldSet)
                + "/" + utils.remove0x(signatureNewSet),
            method: 'POST',
            body: JSON.stringify(nodeIDsNewSet)
            //body: x
        };

        request(options, (error, response, body) => {
            if (error) {
                if (verbose) utils.logError(e);
                reject(error);
            }
            else if (response.statusCode != 200) {
                if (verbose) utils.logFailedResponse(response, body, options);
                var sserror = new SecretStoreSessionError("Request failed.", response);
                reject(sserror);
            }
            else {
                resolve(utils.removeEnclosingDQuotes(body));
            }
        });
    });
}

module.exports = {
    SecretStoreSessionError,
    generateServerKey,
    generateServerAndDocumentKey,
    storeDocumentKey,
    retrieveDocumentKey,
    shadowRetrieveDocumentKey,
    signSchnorr,
    signEcdsa,
    nodesSetChange,
}