diff --git a/src/util/crypto.js b/src/util/crypto.js index a7e29fa..0c1fb56 100644 --- a/src/util/crypto.js +++ b/src/util/crypto.js @@ -79,11 +79,31 @@ function parse(payload) { } } -function reconstruct(shards, title, passphrase, encodedNonce) { - var encryptedSecret = SECRETS.combine(shards); +function reconstruct(shardObjects, passphrase) { + var shardData = shardObjects.map(shard => shard.data); + + var shardsRequirements = shardObjects.map(shard => shard.requiredShards); + if (!shardsRequirements.every(r => r===shardsRequirements[0])) { + throw "Mismatching min shards requirement among shards!" + } + if (shardObjects.length < shardsRequirements[0]) { + throw `Not enough shards, need ${shardsRequirements[0]} but only ${shardObjects.length} provided` + } + + var nonces = shardObjects.map(shard => shard.nonce); + if (!nonces.every(n => n===nonces[0])) { + throw "Nonces mismatch among shards!" + } + + var titles = shardObjects.map((shard) => shard.title); + if (!titles.every(t => t===titles[0])) { + throw "Titles mismatch among shards!" + } + + var encryptedSecret = SECRETS.combine(shardData); var secret = dehexify(encryptedSecret); - var nonce = dehexify(encodedNonce); - var salt = hashString(title); + var nonce = dehexify(nonces[0]); + var salt = hashString(titles[0]); return uint8ArrayToStr(decrypt(secret, salt, passphrase, nonce)); } diff --git a/src/views/Combine.vue b/src/views/Combine.vue index 1dd8a9e..c748824 100644 --- a/src/views/Combine.vue +++ b/src/views/Combine.vue @@ -68,9 +68,7 @@ export default { if (!this.passphrase) { return; } this.passphrase = this.passphrase.split(" ").filter(el => el).join('-'); var shards = Array.from(this.shards); - var nonce = shards[0].nonce; - var dataShards = shards.map((shard) => shard.data) - this.recoveredSecret = crypto.reconstruct(dataShards, this.title, this.passphrase, nonce); + this.recoveredSecret = crypto.reconstruct(shards, this.passphrase); } }, mounted: function() { diff --git a/tests/unit/crypto.spec.js b/tests/unit/crypto.spec.js index 6b2e20c..5a3c8a8 100644 --- a/tests/unit/crypto.spec.js +++ b/tests/unit/crypto.spec.js @@ -5,7 +5,7 @@ import crypto from "../../src/util/crypto"; test('encodes-decodes empty values', () => { var shards = crypto.share('', '', '', 3, 2); var parsed = shards.map(s => crypto.parse(s)); - var reconstructed = crypto.reconstruct(parsed.map(p => p.data), parsed[0].title, '', parsed[0].nonce); + var reconstructed = crypto.reconstruct(parsed, ''); expect(reconstructed).toBe(''); }) @@ -23,18 +23,18 @@ test('works with any 2 shards out of 3', () => { var shards = crypto.share('Secret message', 'Secret title', 'correct-horse-battery-staple', 3, 2); var parsed = shards.map(s => crypto.parse(s)); [ - [parsed[0].data, parsed[1].data], - [parsed[0].data, parsed[2].data], - [parsed[1].data, parsed[2].data] - ].forEach(twoShards => { - expect(crypto.reconstruct(twoShards, 'Secret title', 'correct-horse-battery-staple', parsed[0].nonce)).toBe('Secret message') + + [0, 1], [0, 2], [1, 2], + [1, 0], [2, 0], [2, 1] + ].forEach(([i, j]) => { + expect(crypto.reconstruct([parsed[i], parsed[j]], 'correct-horse-battery-staple')).toBe('Secret message') }); }) test('fails with incorrect password', () => { var shards = crypto.share('Secret message', 'Secret title', 'correct-horse-battery-staple', 5, 3); var parsed = shards.map(s => crypto.parse(s)); - var reconstructed = crypto.reconstruct(parsed.map(p => p.data), parsed[0].title, '', parsed[0].nonce); + var reconstructed = crypto.reconstruct(parsed, ''); expect(reconstructed).not.toBe('Secret message'); expect(reconstructed).toBe(''); }) @@ -46,21 +46,38 @@ test('works with unicode strings', () => { expect(message.title).toBe('Это секрет') }); - var reconstructed = crypto.reconstruct(parsed.map(p => p.data), parsed[0].title, 'correct-horse-battery-staple', parsed[0].nonce); + var reconstructed = crypto.reconstruct(parsed, 'correct-horse-battery-staple'); expect(reconstructed).toBe('Текст сообщения'); }) -test('reconstructs the reference example', () => { +test('reconstructs the reference example of v0 serialization', () => { var shards = [ - {"t":"Very secret info","r":2,"d":"803c12929ba469d720a63fc8ca6f6ef1cc441f8f0b830ea04f8a484169ec800e4a7","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}, - {"t":"Very secret info","r":2,"d":"801b4196813bb664141169f939414fec30c7f343326d8df45e557b25b0249fd43e2","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}, - {"t":"Very secret info","r":2,"d":"80275318760b66ee5a1d7430dbf8769fda05e9e1ff7447eaa78539fbed006f0390b","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"} - ]; + '{"t":"Very secret info","r":2,"d":"803c12929ba469d720a63fc8ca6f6ef1cc441f8f0b830ea04f8a484169ec800e4a7","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', + '{"t":"Very secret info","r":2,"d":"801b4196813bb664141169f939414fec30c7f343326d8df45e557b25b0249fd43e2","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}', + '{"t":"Very secret info","r":2,"d":"80275318760b66ee5a1d7430dbf8769fda05e9e1ff7447eaa78539fbed006f0390b","n":"29abbb2c509adedb470f6d3fd3d083362b8c1a9283adf987"}' + ].map(s => crypto.parse(s)); [ [0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1] ].forEach(([i,j]) => { - expect(crypto.reconstruct([shards[i].d, shards[j].d], 'Very secret info', 'amazing-daring-panda-horror', shards[i].n)).toBe('Any questions?') + expect(crypto.reconstruct([shards[i], shards[j]], 'amazing-daring-panda-horror')).toBe('Any questions?') }); }) + +test('throws an error if nonces mismatch', () => { + var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 3, 2).map(s => crypto.parse(s)); + shards[0].nonce = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Nonces mismatch/) +}) + +test('throws an error if titles mismatch', () => { + var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 3, 2).map(s => crypto.parse(s)); + shards[0].title = "Completely different title" + expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Titles mismatch/) +}) + +test("doesn't work if less shards than necessary are supplied", () => { + var shards = crypto.share('Message', 'Title', 'correct-horse-battery-staple', 5, 3).map(s => crypto.parse(s)); + expect(() => crypto.reconstruct([shards[0], shards[1]], 'correct-horse-battery-staple')).toThrow(/Not enough shards/) +})