Home Reference Source

src/security/Crypto.js

import stringify from 'fast-json-stable-stringify';
import {base64Url} from './Base64UrlCodec';
import sha256 from 'fast-sha256';
import nacl from 'tweetnacl';
import Util from '../Util';

/**
 * Class providing static crypto primitives.
 */
class Crypto {
    /**
     * Generates a key pair to use with the Token system.
     *
     * @param {string} keyLevel - 'LOW', 'STANDARD', or 'PRIVILEGED'
     * @param {number} expirationMs - (optional) expiration duration of the key in milliseconds
     * @return {Object} generated key pair
     */
    static generateKeys(keyLevel, expirationMs) {
        const keyPair = nacl.sign.keyPair();
        keyPair.id = base64Url(sha256(keyPair.publicKey)).substring(0, 16);
        keyPair.algorithm = 'ED25519';
        keyPair.level = keyLevel;
        keyPair.privateKey = keyPair.secretKey;
        if (expirationMs)
            keyPair.expiresAtMs = ((new Date()).getTime() + expirationMs).toString();
        delete keyPair.secretKey;
        return keyPair;
    }

    /**
     * Signs a json object and returns the signature
     *
     * @param {Object} json - object to sign
     * @param {Object} keys - keys to sign with
     * @return {string} signature
     */
    static signJson(json, keys) {
        return Crypto.sign(stringify(json), keys);
    }

    /**
     * Signs a string and returns the signature.
     *
     * @param {string} message - message to sign
     * @param {Object} keys - keys to sign with
     * @return {string} signature
     */
    static sign(message, keys) {
        const msg = Util.wrapBuffer(message);
        return base64Url(nacl.sign.detached(msg, keys.privateKey));
    }

    /**
     * Helper function for crypto engine createSigner:
     * returns a signer that uses a key pair.
     *
     * @param {Object} keyPair - such as returned by Token.Crypto.generateKeys
     * @return {Object} signer object
     */
    static createSignerFromKeyPair(keyPair) {
        return {
            sign: async message => {
                return await Crypto.sign(message, keyPair);
            },
            signJson: async json => {
                return await Crypto.signJson(json, keyPair);
            },
            getKeyId: () => keyPair.id,
        };
    }

    /**
     * Verifies a signature on a JSON object. Throws if verification fails.
     *
     * @param {Object} json - JSON object to verify
     * @param {string} signature - signature to verify
     * @param {Uint8Array} publicKey - public key to use for verification
     */
    static verifyJson(json, signature, publicKey) {
        Crypto.verify(stringify(json), signature, publicKey);
    }

    /**
     * Verifies a signature on a string. Throws if verification fails.
     *
     * @param {string} message - string to verify
     * @param {string} signature - signature to verify
     * @param {Uint8Array} publicKey - public key to use for verification
     */
    static verify(message, signature, publicKey) {
        const msg = Util.wrapBuffer(message);
        const sig = Util.bufferKey(signature);
        const result = nacl.sign.detached.verify(msg, sig, publicKey);
        if (!result) {
            throw new Error(
                `Invalid signature ${signature} on message ${message} with pk ${publicKey}`);
        }
    }

    /**
     * Helper function for crypto engine createVerifier:
     * returns a signer that uses a key pair.
     *
     * @param {Object} keyPair - such as returned by Token.Crypto.generateKeys, only public key
     * @return {Object} verifier object
     */
    static createVerifierFromKeyPair(keyPair) {
        return {
            verify: async (message, signature) => {
                return await Crypto.verify(message, signature, keyPair.publicKey);
            },
            verifyJson: async (json, signature) => {
                return await Crypto.verifyJson(json, signature, keyPair.publicKey);
            },
        };
    }
}

export default Crypto;