'use strict'; const encoding = require ('@sapphirecode/encoding-helper'); const { hash_sha512 } = require ('./hashing'); const { asym_sign, asym_verify } = require ('./rsa'); /** * sign an object * * @param {any} obj object to sign * @param {string} key key to use * @param {string|Object} key_info key identifier * @returns {string} signed object */ function sign_object (obj, key, key_info = null) { const payload = { iat: Date.now (), obj, ...(typeof key_info === 'object' ? key_info : { key_info }) }; const str = encoding.to_b58 (JSON.stringify (payload)); const is_rsa = (/^-----BEGIN PRIVATE KEY-----/u).test (key); const signature = is_rsa ? asym_sign (str, key) : hash_sha512 (str, key); const token = encoding.to_b58 (signature, 'hex'); const res = `${str}.${token}.2`; return res; } /** * parse a string signature * * @param {string} str string to verify * @param {string|((Object)=>string|Promise)|null} key used key * @returns {Promise} returns object if successful, else null */ async function parse_signature (str, key = null) { let dec = str.split ('.'); const version = dec[2]; const res = {}; switch (version) { case '2': res.json = JSON.parse (encoding.to_utf8 (dec[0], 'base58')); res.token = encoding.to_hex (dec[1], 'base58'); break; default: dec = decodeURIComponent (str) .split ('.'); res.json = JSON.parse ( encoding.to_utf8 (dec[0], 'base64') ); res.token = encoding.to_hex (dec[1], 'base64'); break; } if (key !== null) { const string_key = typeof key === 'string' ? key : await key (res.json); res.is_rsa = (/^-----BEGIN RSA PUBLIC KEY-----/u).test (string_key); res.hash = res.is_rsa ? asym_verify (dec[0], string_key, res.token) : hash_sha512 (dec[0], string_key); } return res; } /** * verify a signed object and return its info and contents * * @param {string} str string to verify * @param {string|((Object)=>string|Promise)} key used key * @param {number|((Object)=>number|Promise)} timeout timeout (optional) * @returns {Promise} returns object if successful, else null */ async function verify_signature_get_info (str, key, timeout = 0) { if (typeof str !== 'string') return null; const { json, token, hash, is_rsa } = await parse_signature (str, key); if (is_rsa ? !hash : (token !== hash)) return null; const time = Date.now () - json.iat; const num_timeout = typeof timeout === 'number' ? timeout : await timeout (json); if (num_timeout === 0 || time <= num_timeout) return json; return null; } /** * verify a signed object and return its contents * * @param {string} str string to verify * @param {string|((Object)=>string|Promise)} key used key * @param {number|((Object)=>number|Promise)} timeout timeout (optional) * @returns {Promise} returns object if successful, else null */ async function verify_signature (str, key, timeout = 0) { const res = await verify_signature_get_info (str, key, timeout); if (res === null) return null; return res.obj; } /** * get a signed object info and data * * @param {string} str string to decode * @returns {Promise} data */ async function get_signature_info (str) { if (typeof str !== 'string') return null; const { json } = await parse_signature (str); return json; } /** * decode a signed object without verifying the signature * * @param {string} str string to decode * @returns {Promise} object */ async function decode_signed (str) { const info = await get_signature_info (str); if (info) return info.obj; return null; } module.exports = { decode_signed, get_signature_info, sign_object, verify_signature, verify_signature_get_info };