Skip to content

Commit

Permalink
Merge branch 'pilip/chunkfix2' into 'master'
Browse files Browse the repository at this point in the history
pilip/chunkfix2

See merge request Tanker/sdk-js!94
  • Loading branch information
Jérémy Tellaa committed Dec 17, 2018
2 parents 0f7edf9 + 96bc6ef commit 19d4b29
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
24 changes: 16 additions & 8 deletions packages/core/src/DataProtection/ChunkEncryptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { tcrypto, aead, random, utils } from '@tanker/crypto';

import { ChunkIndexOutOfRange, ChunkNotFound, DecryptFailed, InvalidArgument, InvalidSeal } from '../errors';
import { type EncryptionOptions, validateEncryptionOptions } from './EncryptionOptions';
import { isShareWithOptionsEmpty } from './ShareWithOptions';
import * as Serialize from '../Blocks/Serialize';

const currentSealVersion = 3;
Expand Down Expand Up @@ -164,10 +165,12 @@ export function getChunkKeys(seal: Uint8Array): Array<?Uint8Array> {
export default class ChunkEncryptor {
encryptor: EncryptorInterface;
chunkKeys: Array<?Uint8Array>;
defaultShareWithSelf: bool;

constructor(encryptor: EncryptorInterface, chunkKeys: Array<?Uint8Array>) {
Object.defineProperty(this, 'encryptor', { value: encryptor });
this.chunkKeys = chunkKeys;
constructor(options: { encryptor: EncryptorInterface, chunkKeys: Array<?Uint8Array>, defaultShareWithSelf: bool }) {
Object.defineProperty(this, 'encryptor', { value: options.encryptor });
this.chunkKeys = options.chunkKeys;
this.defaultShareWithSelf = options.defaultShareWithSelf;
}

get length(): number {
Expand Down Expand Up @@ -267,9 +270,14 @@ export default class ChunkEncryptor {
if (!validateEncryptionOptions(options))
throw new InvalidArgument('options', '{ shareWithUsers?: Array<String>, shareWithGroups?: Array<String> }', options);

const opts = { shareWithSelf: this.defaultShareWithSelf, ...options };

if (opts.shareWithSelf === false && isShareWithOptionsEmpty(opts))
throw new InvalidArgument('options.shareWith*', 'options.shareWithUsers or options.shareWithGroups must contain recipients when options.shareWithSelf === false', opts);

const seal = Seal.build(this.chunkKeys);
const serializedSeal = seal.serialize();
const encryptedData = await this.encryptor.encryptData(serializedSeal, options);
const encryptedData = await this.encryptor.encryptData(serializedSeal, opts);
return encryptedData;
}

Expand Down Expand Up @@ -367,13 +375,13 @@ export default class ChunkEncryptor {
// END OF DEPRECATED SINCE 1.6.0 SECTION
}

export async function makeChunkEncryptor(encryptor: EncryptorInterface, seal?: Uint8Array): Promise<ChunkEncryptor> {
export async function makeChunkEncryptor(options: { encryptor: EncryptorInterface, seal?: Uint8Array, defaultShareWithSelf: bool }): Promise<ChunkEncryptor> {
let chunkKeys: Array<?Uint8Array> = [];

if (seal) {
const clearSeal = await encryptor.decryptData(seal);
if (options.seal) {
const clearSeal = await options.encryptor.decryptData(options.seal);
chunkKeys = getChunkKeys(clearSeal);
}

return new ChunkEncryptor(encryptor, chunkKeys);
return new ChunkEncryptor({ encryptor: options.encryptor, chunkKeys, defaultShareWithSelf: options.defaultShareWithSelf });
}
3 changes: 2 additions & 1 deletion packages/core/src/DataProtection/DataProtector.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UserAccessor from '../Users/UserAccessor';
import { type User, getLastUserPublicKey } from '../Users/User';
import { type ExternalGroup } from '../Groups/types';
import { NATURE_KIND, type NatureKind } from '../Blocks/Nature';
import { DEVICE_TYPE } from '../Unlock/unlock';
import { decryptData } from './decrypt';
import { encryptData } from './encrypt';
import { type EncryptionOptions } from './EncryptionOptions';
Expand Down Expand Up @@ -167,6 +168,6 @@ export default class DataProtector {
encryptData: (data, options) => this.encryptAndShareData(data, options),
decryptData: (encryptedData) => this.decryptData(encryptedData)
};
return makeChunkEncryptor(encryptor, seal);
return makeChunkEncryptor({ encryptor, seal, defaultShareWithSelf: (this._localUser.deviceType === DEVICE_TYPE.client_device) });
}
}
6 changes: 3 additions & 3 deletions packages/core/src/__tests__/ChunkEncryptor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ let clearText;
describe('ChunkEncryptor', () => {
beforeEach(async () => {
fakeEncryptor = new FakeEncryptor();
chunkEncryptor = await makeChunkEncryptor(fakeEncryptor);
chunkEncryptor = await makeChunkEncryptor({ encryptor: fakeEncryptor, defaultShareWithSelf: true });
clearText = 'initial message';
});

Expand Down Expand Up @@ -154,7 +154,7 @@ describe('ChunkEncryptor', () => {
await chunkEncryptor.encrypt(clearText, 7);
await chunkEncryptor.encrypt(clearText, 11);
const seal = await chunkEncryptor.seal();
const newEnc = await makeChunkEncryptor(fakeEncryptor, seal);
const newEnc = await makeChunkEncryptor({ encryptor: fakeEncryptor, seal, defaultShareWithSelf: true });
expect(newEnc).to.deep.equal(chunkEncryptor);
});

Expand All @@ -171,7 +171,7 @@ describe('ChunkEncryptor', () => {
arr.splice(5, 1);
const encCorruptedSeal = await fakeEncryptor.encryptData(new Uint8Array(arr));

await expect(makeChunkEncryptor(fakeEncryptor, encCorruptedSeal))
await expect(makeChunkEncryptor({ encryptor: fakeEncryptor, seal: encCorruptedSeal, defaultShareWithSelf: true }))
.to.be.rejectedWith(errors.InvalidSeal);
});

Expand Down
41 changes: 41 additions & 0 deletions packages/functional-tests/src/chunkEncryptor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import uuid from 'uuid';
import { errors } from '@tanker/core';
import { utils } from '@tanker/crypto';
import { expect } from './chai';

Expand Down Expand Up @@ -29,6 +30,46 @@ const generateChunkEncryptorTests = (args: TestArgs) => {
]);
});

it('can decrypt data encrypted for self', async () => {
const letterContents = ['Harder', 'Better', 'Faster', 'Stronger'];

const encryptor = await args.aliceLaptop.makeChunkEncryptor();
const encryptedChunks = [];
for (const word of letterContents)
encryptedChunks.push(await encryptor.encryptData(utils.fromString(word)));

const encryptedSeal = await encryptor.seal();

const decryptor = await args.aliceLaptop.makeChunkEncryptor(encryptedSeal);
const decryptedChunks = await Promise.all(encryptedChunks.map((c, idx) => decryptor.decryptData(c, idx)));

expect(decryptedChunks.map(utils.toString)).to.deep.equal(letterContents);
});

it('cannot decrypt data encrypted and not shared with self', async () => {
const letterContents = ['Harder', 'Better', 'Faster', 'Stronger'];

const encryptor = await args.aliceLaptop.makeChunkEncryptor();
const encryptedChunks = [];
for (const word of letterContents)
encryptedChunks.push(await encryptor.encryptData(utils.fromString(word)));

const encryptedSeal = await encryptor.seal({ shareWithUsers: [bobId], shareWithSelf: false });

await expect(args.aliceLaptop.makeChunkEncryptor(encryptedSeal)).to.be.rejectedWith(errors.ResourceNotFound);
});

it('cannot seal and share with no one (including myself)', async () => {
const letterContents = ['Harder', 'Better', 'Faster', 'Stronger'];

const encryptor = await args.aliceLaptop.makeChunkEncryptor();
const encryptedChunks = [];
for (const word of letterContents)
encryptedChunks.push(await encryptor.encryptData(utils.fromString(word)));

await expect(encryptor.seal({ shareWithSelf: false })).to.be.rejectedWith(errors.InvalidArgument);
});

[
{ prop: 'shareWithUsers', title: 'shares a chunked resource' },
{ prop: 'shareWith', title: 'shares a chunked resource with deprecated option' },
Expand Down

0 comments on commit 19d4b29

Please sign in to comment.