diff --git a/CHANGELOG.md b/CHANGELOG.md index 629bbd0..e5b7b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## 2.0.0 + +Signature related functions have changed to be asynchronous and to accept async +functions as key retrieving functions + ## 1.3.0 asymmetric encryption and signatures diff --git a/README.md b/README.md index f8d9c5e..c820763 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @sapphirecode/crypto-helper -version: 1.3.x +version: 2.0.x simple functions for cryptography @@ -29,11 +29,11 @@ const check = crypto.checksum('foo'); // returns a sha 256 hex // jwt like object signing const signed = crypto.sign_object({foo: 'bar'}, 'secret'); -const info = crypto.get_signature_info(signed); // returns an object with iat (issued at), key_info and data -const dec = crypto.decode_signed(signed); // decode a signed object without verifying the signature -const ver = crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted. -const ver_info = crypto.verify_signature_get_info(signed, 'secret', 10000); // verify a signature and get signature information like iat and key_info -const ver_func = crypto.verify_signature( +const info = await crypto.get_signature_info(signed); // returns an object with iat (issued at), key_info and data +const dec = await crypto.decode_signed(signed); // decode a signed object without verifying the signature +const ver = await crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted. +const ver_info = await crypto.verify_signature_get_info(signed, 'secret', 10000); // verify a signature and get signature information like iat and key_info +const ver_func = await crypto.verify_signature( signed, (signature_info) => 'secret', 10000 diff --git a/lib/signatures.js b/lib/signatures.js index 4614e89..6d69854 100644 --- a/lib/signatures.js +++ b/lib/signatures.js @@ -26,7 +26,14 @@ function sign_object (obj, key, key_info = null) { return res; } -function parse_signature (str, key = null) { +/** + * 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 = {}; @@ -46,7 +53,7 @@ function parse_signature (str, key = null) { } if (key !== null) { - const string_key = typeof key === 'string' ? key : key (res.json); + 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) @@ -59,18 +66,20 @@ function parse_signature (str, key = null) { * verify a signed object and return its info and contents * * @param {string} str string to verify - * @param {string|(Object)=>string} key used key - * @param {number|(Object)=>number} timeout timeout (optional) - * @returns {any} returns object if successful, else null + * @param {string|((Object)=>string|Promise)} key used key + * @param {number|((Object)=>number|Promise)} timeout timeout (optional) + * @returns {Promise} returns object if successful, else null */ -function verify_signature_get_info (str, key, timeout = 0) { +async function verify_signature_get_info (str, key, timeout = 0) { if (typeof str !== 'string') return null; - const { json, token, hash, is_rsa } = parse_signature (str, key); + 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 : timeout (json); + const num_timeout = typeof timeout === 'number' + ? timeout + : await timeout (json); if (num_timeout === 0 || time <= num_timeout) return json; return null; @@ -80,12 +89,12 @@ function verify_signature_get_info (str, key, timeout = 0) { * verify a signed object and return its contents * * @param {string} str string to verify - * @param {string|(Object)=>string} key used key - * @param {number|(Object)=>number} timeout timeout (optional) - * @returns {any} returns object if successful, else null + * @param {string|((Object)=>string|Promise)} key used key + * @param {number|((Object)=>number|Promise)} timeout timeout (optional) + * @returns {Promise} returns object if successful, else null */ -function verify_signature (str, key, timeout = 0) { - const res = verify_signature_get_info (str, key, timeout); +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; @@ -95,12 +104,12 @@ function verify_signature (str, key, timeout = 0) { * get a signed object info and data * * @param {string} str string to decode - * @returns {any} data + * @returns {Promise} data */ -function get_signature_info (str) { +async function get_signature_info (str) { if (typeof str !== 'string') return null; - const { json } = parse_signature (str); + const { json } = await parse_signature (str); return json; } @@ -108,10 +117,10 @@ function get_signature_info (str) { * decode a signed object without verifying the signature * * @param {string} str string to decode - * @returns {any} object + * @returns {Promise} object */ -function decode_signed (str) { - const info = get_signature_info (str); +async function decode_signed (str) { + const info = await get_signature_info (str); if (info) return info.obj; return null; diff --git a/package.json b/package.json index 3d31896..597862e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sapphirecode/crypto-helper", - "version": "1.3.0", + "version": "2.0.0", "main": "index.js", "author": { "name": "Timo Hocker", diff --git a/test/spec/encryption.js b/test/spec/encryption.js index 244d6b7..b4c59df 100644 --- a/test/spec/encryption.js +++ b/test/spec/encryption.js @@ -94,7 +94,7 @@ describe ('encryption', () => { }) .toThrowError ( // eslint-disable-next-line max-len - 'error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt' + /bad decrypt/ui ); }); diff --git a/test/spec/rsa.js b/test/spec/rsa.js index 5daf971..d67adfb 100644 --- a/test/spec/rsa.js +++ b/test/spec/rsa.js @@ -19,7 +19,7 @@ describe ('rsa', () => { it ('should throw on too small key size', async () => { await expectAsync (crypto.generate_keypair (16)) .toBeRejectedWithError ( - 'error:0408F078:rsa routines:pkey_rsa_ctrl:key size too small' + /key size too small/ui ); }); diff --git a/test/spec/signatures.js b/test/spec/signatures.js index 81e36e2..65dbf6f 100644 --- a/test/spec/signatures.js +++ b/test/spec/signatures.js @@ -18,26 +18,28 @@ describe ('signatures', () => { .uninstall (); }); - it ('sign_object', () => { + it ('sign_object', async () => { const obj = { foo: 'bar' }; - expect (() => { - const str = crypto.sign_object (obj, 'baz'); + await expectAsync ((async () => { + const str = await crypto.sign_object (obj, 'baz'); expect (typeof str) .toEqual ('string'); - }).not.toThrow (); + }) ()) + .toBeResolved (); }); it ('sign_object with rsa key', async () => { const obj = { foo: 'bar' }; const k = await rsa.generate_keypair (); - expect (() => { - const str = crypto.sign_object (obj, k.private_key); + await expectAsync ((async () => { + const str = await crypto.sign_object (obj, k.private_key); expect (typeof str) .toEqual ('string'); - }).not.toThrow (); + }) ()) + .toBeResolved (); }); - it ('verify_signature with rsa key', () => { + it ('verify_signature with rsa key', async () => { const obj = { foo: 'bar' }; const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt' + '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X' @@ -54,12 +56,12 @@ describe ('signatures', () => { + 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n' + '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n' + '-----END RSA PUBLIC KEY-----\n'; - const ver = crypto.verify_signature (str, key); + const ver = await crypto.verify_signature (str, key); expect (ver) .toEqual (obj); }); - it ('verify_signature reject with rsa key', () => { + it ('verify_signature reject with rsa key', async () => { const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt' + '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X' + 'LjRUkc2M8M84LmWLg76EvcY2pw6HwsFvCUoZYcKAJp3vkp9MQVrVYdHKMPkBjQKyy2V' @@ -75,179 +77,185 @@ describe ('signatures', () => { + 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n' + '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n' + '-----END RSA PUBLIC KEY-----\n'; - const ver = crypto.verify_signature (str, key); + const ver = await crypto.verify_signature (str, key); expect (ver) .toBeNull (); }); - it ('should sign object with key info', () => { + it ('should sign object with key info', async () => { const obj = { foo: 'bar' }; - expect (() => { + await expectAsync ((async () => { const str = crypto.sign_object (obj, 'baz', 'baz'); - const res = crypto.get_signature_info (str); + const res = await crypto.get_signature_info (str); expect (res.key_info) .toEqual ('baz'); - }).not.toThrow (); + }) ()) + .toBeResolved (); }); - it ('should sign object with custom properties', () => { + it ('should sign object with custom properties', async () => { const obj = { foo: 'bar' }; - expect (() => { + await expectAsync ((async () => { const str = crypto.sign_object (obj, 'baz', { bar: 'baz' }); - const res = crypto.get_signature_info (str); + const res = await crypto.get_signature_info (str); expect (res.bar) .toEqual ('baz'); - }).not.toThrow (); + }) ()) + .toBeResolved (); }); - it ('should sign object with custom override properties', () => { + it ('should sign object with custom override properties', async () => { const obj = { foo: 'bar' }; - expect (() => { + await expectAsync ((async () => { const str = crypto.sign_object (obj, 'baz', { iat: 'baz' }); - const res = crypto.get_signature_info (str); + const res = await crypto.get_signature_info (str); expect (res.iat) .toEqual ('baz'); - }).not.toThrow (); + }) ()) + .toBeResolved (); }); - it ('decode_signed', () => { + it ('decode_signed', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); - const dec = crypto.decode_signed (str); + const dec = await crypto.decode_signed (str); expect (obj) .toEqual (dec); }); - it ('verify_signature', () => { + it ('verify_signature', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); - const dec = crypto.verify_signature (str, 'baz'); + const dec = await crypto.verify_signature (str, 'baz'); expect (obj) .toEqual (dec); }); - it ('should verify and return all info', () => { + it ('should verify and return all info', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz', { iat: 'baz' }); - const dec = crypto.verify_signature_get_info (str, 'baz'); + const dec = await crypto.verify_signature_get_info (str, 'baz'); expect (dec.obj) .toEqual (obj); expect (dec.iat) .toEqual ('baz'); }); - it ('should verify signature using function retrieved key', () => { + it ('should verify signature using function retrieved key', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); - const dec = crypto.verify_signature (str, () => 'baz'); + const dec = await crypto.verify_signature (str, () => 'baz'); expect (obj) .toEqual (dec); }); - it ('should verify signature using function retrieved timeout 0', () => { - const obj = { foo: 'bar' }; - const str = crypto.sign_object (obj, 'baz'); - const dec = crypto.verify_signature (str, 'baz', () => 0); - expect (obj) - .toEqual (dec); - }); + it ( + 'should verify signature using function retrieved timeout 0', + async () => { + const obj = { foo: 'bar' }; + const str = crypto.sign_object (obj, 'baz'); + const dec = await crypto.verify_signature (str, 'baz', () => 0); + expect (obj) + .toEqual (dec); + } + ); - it ('should reject tampered signatures', () => { + it ('should reject tampered signatures', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); - const dec = crypto.verify_signature (str, 'foo'); + const dec = await crypto.verify_signature (str, 'foo'); expect (dec) .toEqual (null); }); - it ('should return null on invalid input', () => { - const ver = crypto.verify_signature (null, 'foo'); + it ('should return null on invalid input', async () => { + const ver = await crypto.verify_signature (null, 'foo'); expect (ver) .toEqual (null); - const dec = crypto.decode_signed (null, 'foo'); + const dec = await crypto.decode_signed (null); expect (dec) .toEqual (null); }); - it ('should not fail verification if timeout unspecified', () => { + it ('should not fail verification if timeout unspecified', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); jasmine.clock () .tick (36e5); - const dec = crypto.verify_signature (str, 'baz'); + const dec = await crypto.verify_signature (str, 'baz'); expect (obj) .toEqual (dec); }); - it ('should reject old signatures', () => { + it ('should reject old signatures', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); jasmine.clock () .tick (50); - const dec = crypto.verify_signature (str, 'baz', 1); + const dec = await crypto.verify_signature (str, 'baz', 1); expect (dec) .toEqual (null); }); - it ('should not reject valid signatures', () => { + it ('should not reject valid signatures', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); jasmine.clock () .tick (50); - const dec = crypto.verify_signature (str, 'baz', 100); + const dec = await crypto.verify_signature (str, 'baz', 100); expect (obj) .toEqual (dec); }); - it ('should verify signature using function retrieved timeout', () => { + it ('should verify signature using function retrieved timeout', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz', { to: 100 }); jasmine.clock () .tick (50); - const dec = crypto.verify_signature (str, 'baz', (info) => info.to); + const dec = await crypto.verify_signature (str, 'baz', (info) => info.to); expect (obj) .toEqual (dec); }); - it ('verify_signature on almost timed out packet', () => { + it ('verify_signature on almost timed out packet', async () => { const obj = { foo: 'bar' }; const str = crypto.sign_object (obj, 'baz'); jasmine.clock () .tick (10); - const dec = crypto.verify_signature (str, 'baz', 10); + const dec = await crypto.verify_signature (str, 'baz', 10); expect (obj) .toEqual (dec); }); - it ('should decode problematic token', () => { + it ('should decode problematic token', async () => { // eslint-disable-next-line max-len const str = 'wEJbzvUywiaiGWZUG6CtCXNkNmRGyVoi9icytpTe4gZhsb8Gk.5PZbhGL525mdV7EmYomTwUei6qULpLaZwSXy92eaUDNgbyXPHsr9dfUCeEBpTqmzuq3VtmmV43epUyWRoHocAsV3.2'; - const obj = crypto.decode_signed (str); + const obj = await crypto.decode_signed (str); expect (obj) .toEqual ({ id: 1 }); }); - it ('should automatically reencode b64 tokens', () => { + it ('should automatically reencode b64 tokens', async () => { // eslint-disable-next-line max-len const str = 'eyJpYXQiOjE1ODE0NDAwMTIyODgsIm9iaiI6eyJpZCI6MX19.24ZOsWrnfkNe%2FbM0r7DaVJMqE2bfn2aAM%2BZSzWeSf31OCTlXXNWD34RBL2X5v3UliYQ4IIsLNBFbaW9texPHug%3D%3D'; - const obj = crypto.decode_signed (str); + const obj = await crypto.decode_signed (str); expect (obj) .toEqual ({ id: 1 }); }); - it ('verify_signature on b64 string', () => { + it ('verify_signature on b64 string', async () => { // eslint-disable-next-line max-len const str = 'eyJpYXQiOjE2MDkzNDQ4MDMyMjcsIm9iaiI6eyJpZCI6MX19.N762xuMaNbT%2Fqb0uTKST68BZgSnmNxXaHl4GY7iAKqaDDEwZn3biYfg5DgJ45QgPZrndchczDjUqLkyXoqw4KQ%3D%3D'; - const obj = crypto.verify_signature (str, 'baz'); + const obj = await crypto.verify_signature (str, 'baz'); expect (obj) .toEqual ({ id: 1 }); });