UNPKG

brave-crypto

Version:

Crypto utils for Brave Browser

254 lines (228 loc) 12.8 kB
const test = require('tape') const crypto = require('../index') const nacl = require('tweetnacl') const toHex = crypto.uint8ToHex const fromHex = crypto.hexToUint8 test('getSeed', (t) => { t.plan(2) t.equal(crypto.getSeed().length, 32) t.equal(crypto.getSeed(666).length, 666) }) test('hmac', (t) => { // https://tools.ietf.org/html/rfc4231#section-4 const keys = [ '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', '4a656665', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '0102030405060708090a0b0c0d0e0f10111213141516171819', '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ] const data = [ '4869205468657265', '7768617420646f2079612077616e7420666f72206e6f7468696e673f', 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', '546573742057697468205472756e636174696f6e', '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374', '5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e' ] const outputs = [ '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854', '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737', 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb', 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd', '415fad6271580a531d4179bc891d87a6', '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598', 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58' ] t.plan(8) outputs.forEach((output, i) => { if (i === 4) { // test case 5 tests truncation to 128 bits t.ok(toHex(crypto.hmac(fromHex(data[i]), fromHex(keys[i]))).startsWith(output)) return } t.equal(output, toHex(crypto.hmac(fromHex(data[i]), fromHex(keys[i])))) }) t.throws(crypto.hmac.bind(null, new Uint8Array(), []), /Uint8Arrays/, 'errors if inputs are wrong type') }) test('hkdf', (t) => { // https://www.kullo.net/blog/hkdf-sha-512-test-vectors/ const results = [{ IKM: '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', salt: '000102030405060708090a0b0c', info: 'f0f1f2f3f4f5f6f7f8f9', L: 42, OKM: '832390086cda71fb47625bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb' }, { IKM: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', salt: '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', info: 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', L: 82, OKM: 'ce6c97192805b346e6161e821ed165673b84f400a2b514b2fe23d84cd189ddf1b695b48cbd1c8388441137b3ce28f16aa64ba33ba466b24df6cfcb021ecff235f6a2056ce3af1de44d572097a8505d9e7a93' }, { IKM: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f', salt: '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf', info: 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff', L: 64, // Same as above but truncated to a multiple of HMAC length. OKM: 'ce6c97192805b346e6161e821ed165673b84f400a2b514b2fe23d84cd189ddf1b695b48cbd1c8388441137b3ce28f16aa64ba33ba466b24df6cfcb021ecff235' }, { IKM: '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', salt: '', info: '', L: 42, OKM: 'f5fa02b18298a72a8c23898a8703472c6eb179dc204c03425c970e3b164bf90fff22d04836d0e2343bac' }, { IKM: '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', salt: '', // "info" : ..., // This field intentionally blank. L: 42, OKM: 'f5fa02b18298a72a8c23898a8703472c6eb179dc204c03425c970e3b164bf90fff22d04836d0e2343bac' }, { IKM: '0b0b0b0b0b0b0b0b0b0b0b', salt: '000102030405060708090a0b0c', info: 'f0f1f2f3f4f5f6f7f8f9', L: 42, OKM: '7413e8997e020610fbf6823f2ce14bff01875db1ca55f68cfcf3954dc8aff53559bd5e3028b080f7c068' }, { IKM: '0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', info: '', L: 42, OKM: '1407d46013d98bc6decefcfee55f0f90b0c7f63d68eb1a80eaf07e953cfc0a3a5240a155d6e4daa965bb' }] t.plan(8) results.forEach((result) => { const hkdf = crypto.getHKDF( fromHex(result.IKM), 'info' in result && fromHex(result.info), result.L, fromHex(result.salt) ) t.equal(toHex(hkdf), result.OKM) }) t.throws(crypto.getHKDF.bind(null, new Uint8Array(1), new Uint8Array(), 16321), /Invalid extract length/, 'error when extract length is too long') }) test('uint8ToHex', (t) => { t.plan(8) t.equal(toHex(new Uint8Array([])), '') t.equal(toHex(new Uint8Array([0])), '00') t.equal(toHex(new Uint8Array([0, 255])), '00ff') t.equal(toHex(new Uint8Array([30, 1, 2, 3])), '1e010203') const buf = new ArrayBuffer(6) for (let i = 0; i < 6; i++) { new Uint8Array(buf)[i] = [42, 30, 1, 2, 3, 73][i] } t.equal(toHex(new Uint8Array(buf, 1, 4)), '1e010203') t.equal(toHex(Buffer.from([30, 1, 2, 3])), '1e010203') t.equal(toHex(Buffer.alloc(3)), '000000') t.throws(toHex.bind(null, 'foo'), /Uint8Array/, 'errors if inputs are wrong type') }) test('hexToUint8', (t) => { t.plan(9) t.deepEqual(fromHex('00'), { 0: 0 }) t.deepEqual(fromHex('1'), { 0: 1 }) t.deepEqual(fromHex(''), {}) t.deepEqual(fromHex('00ff'), { 0: 0, 1: 255 }) t.deepEqual(fromHex('1e010203'), { 0: 30, 1: 1, 2: 2, 3: 3 }) t.deepEqual(fromHex('1E010203'), { 0: 30, 1: 1, 2: 2, 3: 3 }) t.throws(fromHex.bind(null, new Uint8Array(3)), /must be a string/, 'errors if inputs are wrong type') t.throws(fromHex.bind(null, '1e010203g'), /must be hex/, 'errors if input is not hex') t.throws(fromHex.bind(null, '0x1ffb78'), /without the 0x prefix/, 'errors if input has 0x prefix') }) test('key derivation', (t) => { const HKDF_SALT = new Uint8Array([72, 203, 156, 43, 64, 229, 225, 127, 214, 158, 50, 29, 130, 186, 182, 207, 6, 108, 47, 254, 245, 71, 198, 109, 44, 108, 32, 193, 221, 126, 119, 143, 112, 113, 87, 184, 239, 231, 230, 234, 28, 135, 54, 42, 9, 243, 39, 30, 179, 147, 194, 211, 212, 239, 225, 52, 192, 219, 145, 40, 95, 19, 142, 98]) t.plan(5) const key = crypto.deriveSigningKeysFromSeed(fromHex('5bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb'), HKDF_SALT) t.equal('f58ca446f0c33ee7e8e9874466da442b2e764afd77ad46034bdff9e01f9b87d4', toHex(key.publicKey), 'gets pub key') t.equal('b5abda6940984c5153a2ba3653f047f98dfb19e39c3e02f07c8bbb0bd8e8872ef58ca446f0c33ee7e8e9874466da442b2e764afd77ad46034bdff9e01f9b87d4', toHex(key.secretKey), 'gets priv key') const message = Buffer.from('€ 123 ッッッ あ') const signed = nacl.sign(message, key.secretKey) const opened = Buffer.from(nacl.sign.open(signed, key.publicKey)) t.deepEqual(opened, message, 'verification success') signed[0] = 255 t.deepEqual(nacl.sign.open(signed, key.publicKey), null, 'verification failure') t.throws(crypto.deriveSigningKeysFromSeed.bind(null, []), /Uint8Array/, 'error when input is not a Uint8Array') }) const pair = nacl.sign.keyPair.fromSecretKey( Uint8Array.from( Buffer.from('9f8362f87a484a954e6e740c5b4c0e84229139a20aa8ab56ff66586f6a7d29c526b40b8f93fff3d897112f7ebc582b232dbd72517d082fe83cfb30ddce43d1bb', 'hex') ) ) const goodSignature = 'keyId="test-key-ed25519",algorithm="ed25519",headers="foo fizz",signature="lAGT9Bhde3sJp8Z1NTxmViJtG1PSoYnXV9he82z1iu//KXmCrjKYe1JOU34memKIdlxG1yJoeS2hxANRvalrBw=="' test('signing', (t) => { const headers = { foo: 'bar', fizz: 'buzz' } t.plan(6) let signature = crypto.ed25519HttpSign('test-key-ed25519', pair.secretKey, headers) t.equal(signature, goodSignature) // Incorrect header signature = crypto.ed25519HttpSign('test-key-ed25519', pair.secretKey, { ...headers, fizz: 'fizz' }) t.notEqual(signature, goodSignature) // No headers t.throws(crypto.ed25519HttpSign.bind('test-key-ed25519', pair.secretKey), 'headers are required') // No Secret Key t.throws(crypto.ed25519HttpSign.bind('test-key-ed25519', pair.secretKey, headers), 'secret key is required') // No Key ID t.throws(crypto.ed25519HttpSign.bind(null, pair.secretKey, headers), 'key ID is required') const original = crypto.ed25519HttpSign( 'primary', '96aa9ec42242a9a62196281045705196a64e12b15e9160bbb630e38385b82700e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d', { foo: 'bar' } ) t.equal(original, 'keyId="primary",algorithm="ed25519",headers="foo",signature="RbGSX1MttcKCpCkq9nsPGkdJGUZsAU+0TpiXJYkwde+0ZwxEp9dXO3v17DwyGLXjv385253RdGI7URbrI7J6DQ=="') }) test('verification', (t) => { t.plan(10) const headers = { foo: 'bar', fizz: 'buzz', signature: goodSignature } let res = crypto.ed25519HttpVerify(pair.publicKey, headers) t.equal(res.verified, true) // Miss a byte let testKey = pair.publicKey t.throws(crypto.ed25519HttpVerify.bind(testKey.slice(0, 2), headers), 'bad public key size') // Modify a byte testKey = Uint8Array.from(pair.publicKey) testKey[0] = 0 res = crypto.ed25519HttpVerify(testKey, headers) t.equal(res.verified, false) // Miss a header let bad = { foo: 'bar', signature: goodSignature } res = crypto.ed25519HttpVerify(testKey, bad) t.equal(res.verified, false) // Missing part of the signature bad = { ...headers } bad.signature = bad.signature.slice(25, goodSignature.length) t.equal(bad.signature, 'algorithm="ed25519",headers="foo fizz",signature="lAGT9Bhde3sJp8Z1NTxmViJtG1PSoYnXV9he82z1iu//KXmCrjKYe1JOU34memKIdlxG1yJoeS2hxANRvalrBw=="') t.throws(crypto.ed25519HttpVerify.bind(pair.publicKey, bad), 'no keyId was parsed') // Missing signature bad = { ...headers } delete bad.signature t.throws(crypto.ed25519HttpVerify.bind(pair.publicKey, bad), 'header signature is required') // Missing equal const eq = { ...headers } eq.signature = 'keyId="test-key-ed25519",algorithm="ed25519",headers="foo fizz",signature"lAGT9Bhde3sJp8Z1NTxmViJtG1PSoYnXV9he82z1iu//KXmCrjKYe1JOU34memKIdlxG1yJoeS2hxANRvalrBw=="' t.throws(crypto.ed25519HttpVerify.bind(pair.publicKey, bad), 'no ed25519 signature was parsed') // Incorrect algorithm t.throws(crypto.ed25519HttpVerify.bind( 'e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d', { foo: 'bar', signature: 'keyId="primary",algorithm="foobar",headers="foo",signature="RbGSX1MttcKCpCkq9nsPGkdJGUZsAU+0TpiXJYkwde+0ZwxEp9dXO3v17DwyGLXjv385253RdGI7URbrI7J6DQ=="' } ), 'unsupported algorithm, use ed25519') // Original const signature = crypto.ed25519HttpVerify( 'e7876fd5cc3a228dad634816f4ec4b80a258b2a552467e5d26f30003211bc45d', { foo: 'bar', signature: 'keyId="primary",algorithm="ed25519",headers="foo",signature="RbGSX1MttcKCpCkq9nsPGkdJGUZsAU+0TpiXJYkwde+0ZwxEp9dXO3v17DwyGLXjv385253RdGI7URbrI7J6DQ=="' } ) t.deepEqual(signature, { algorithm: 'ed25519', headers: ['foo'], keyId: 'primary', signature: 'RbGSX1MttcKCpCkq9nsPGkdJGUZsAU+0TpiXJYkwde+0ZwxEp9dXO3v17DwyGLXjv385253RdGI7URbrI7J6DQ==', verified: true }) })