Skip to content

Commit

Permalink
Merge branch 'feat/encrypt_padding' into 'master'
Browse files Browse the repository at this point in the history
Feat/encrypt padding

See merge request TankerHQ/sdk-react-native!87
  • Loading branch information
JMounier committed Jul 7, 2022
2 parents b7e225b + 22f494e commit d40126a
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package com.tankerclientreactnative

import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import io.tanker.api.EncryptionOptions
import io.tanker.api.Padding

private fun convertPaddingStep(paddingStep: Int): Padding =
when (paddingStep) {
0 -> Padding.auto
1 -> Padding.off
else -> Padding.step(paddingStep)
}

fun EncryptionOptions(json: ReadableMap?): EncryptionOptions {
val options = EncryptionOptions()
Expand All @@ -23,5 +33,9 @@ fun EncryptionOptions(json: ReadableMap?): EncryptionOptions {
}
if (json.hasKey("shareWithSelf"))
options.shareWithSelf(json.getBoolean("shareWithSelf"))

if (json.hasKey("paddingStep"))
options.paddingStep(convertPaddingStep(json.getInt("paddingStep")))

return options
}
95 changes: 87 additions & 8 deletions example/src/test_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@

import { expect } from 'chai';
import type { Tanker } from '@tanker/client-react-native';
import { Padding } from '@tanker/client-react-native';
import { describe, beforeEach, afterEach, it } from './framework';
import { createIdentity, getPublicIdentity } from './admin';
import { InvalidArgument } from '@tanker/errors';
import { createTanker, clearTankerDataDirs } from './tests';
import { createTanker, clearTankerDataDirs, getPaddedSize } from './tests';

const simpleEncryptionOverhead = 17;
const simpleEncryptionPaddedOverhead = simpleEncryptionOverhead + 1;

export const encryptionTests = () => {
describe('Encryption tests', () => {
Expand All @@ -23,7 +27,7 @@ export const encryptionTests = () => {
await clearTankerDataDirs();
});

it('can roundtrip a basic encrypt', async () => {
it('roundtrips a basic encrypt', async () => {
const plaintext = 'foo';
const encrypted = await tanker.encrypt(plaintext);
const decrypted = await tanker.decrypt(encrypted);
Expand All @@ -38,7 +42,7 @@ export const encryptionTests = () => {
);
});

it('can use encryption options to share', async () => {
it('uses encryption options to share', async () => {
const other = await createTanker();
const otherPrivIdent = await createIdentity();
await other.start(otherPrivIdent);
Expand All @@ -60,14 +64,14 @@ export const encryptionTests = () => {
expect(decrypted).eq(plaintext);
});

it('can roundtrip with encryptData', async () => {
it('roundtrips with encryptData', async () => {
const plaindata = 'dW5kZXIgY29vbCBtb29ubGlnaHQ=';
const encrypted = await tanker.encryptData(plaindata);
const decrypted = await tanker.decryptData(encrypted);
expect(decrypted).eq(plaindata);
});

it('cannot pass a non-base64 plaintext to encryptData', async () => {
it('fails to pass a non-base64 plaintext to encryptData', async () => {
// NOTE: Android cannot be told to reject unpadded Base64 (though if there is padding, it must be correct),
// that's why we explicitely pick a plaintext with a space (invalid charset) for this test
const plaintext = 'plain text';
Expand All @@ -87,20 +91,20 @@ export const encryptionTests = () => {
expect(decrypted).eq(plaintext);
});

it('can get the resourceId for a small encrypted data', async () => {
it('retrieves the resourceId for a small encrypted data', async () => {
const encrypted = await tanker.encrypt('Principalities');
const resId = await tanker.getResourceId(encrypted);
expect(resId).is.not.empty;
});

it('can get the resourceId for a longer encrypted data', async () => {
it('retrieves the resourceId for a longer encrypted data', async () => {
const plaintext = `Heap dump: 0x${'41'.repeat(1025)}`;
const encrypted = await tanker.encrypt(plaintext);
const resId = await tanker.getResourceId(encrypted);
expect(resId).is.not.empty;
});

it('can share encrypted data', async () => {
it('shares encrypted data', async () => {
const other = await createTanker();
const otherPrivIdent = await createIdentity();
await other.start(otherPrivIdent);
Expand All @@ -117,5 +121,80 @@ export const encryptionTests = () => {
await other.stop();
expect(decrypted).eq(plaintext);
});

it('encrypts with auto padding by default', async () => {
const plaintext = 'my clear data is clear!';
const lengthWithPadme = 24;

const encrypted = await tanker.encrypt(plaintext);

const paddedSize = getPaddedSize(
encrypted,
simpleEncryptionPaddedOverhead
);
expect(paddedSize).eq(lengthWithPadme);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with auto padding', async () => {
const plaintext = 'my clear data is clear!';
const lengthWithPadme = 24;

const options = { paddingStep: Padding.AUTO };
const encrypted = await tanker.encrypt(plaintext, options);

const paddedSize = getPaddedSize(
encrypted,
simpleEncryptionPaddedOverhead
);
expect(paddedSize).eq(lengthWithPadme);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with no padding', async () => {
const plaintext = 'This is the text to pad.';

const options = { paddingStep: Padding.OFF };
const encrypted = await tanker.encrypt(plaintext, options);

const paddedSize = getPaddedSize(encrypted, simpleEncryptionOverhead);
expect(paddedSize).to.equal(plaintext.length);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with a padding step', async () => {
const plaintext = 'my clear data is clear';

const step = 13;
const options = { paddingStep: step };
const encrypted = await tanker.encrypt(plaintext, options);

const paddedSize = getPaddedSize(
encrypted,
simpleEncryptionPaddedOverhead
);
expect(paddedSize % step).to.equal(0);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('fails to provide a bad paddingStep parameter', async () => {
const plaintext = 'unused';
await Promise.all(
[-1, 0, 1, 2.42, 'a random string', null].map(async (x) => {
await expect(
// @ts-expect-error
tanker.encrypt(plaintext, { paddingStep: x })
).eventually.rejectedWith(InvalidArgument);
})
);
});
});
};
97 changes: 91 additions & 6 deletions example/src/test_encryption_session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

import { expect } from 'chai';
import type { Tanker } from '@tanker/client-react-native';
import { Padding } from '@tanker/client-react-native';
import { describe, beforeEach, afterEach, it } from './framework';
import { createIdentity, getPublicIdentity } from './admin';
import { InvalidArgument } from '@tanker/errors';
import { createTanker, clearTankerDataDirs } from './tests';
import { createTanker, clearTankerDataDirs, getPaddedSize } from './tests';
import type { EncryptionSession } from '../../src/encryptionSessionWrapper';

const encryptionSessionOverhead = 57;
const encryptionSessionPaddedOverhead = encryptionSessionOverhead + 1;

export const encryptionSessionTests = () => {
describe('Encryption session tests', () => {
let tanker: Tanker;
Expand All @@ -27,7 +31,7 @@ export const encryptionSessionTests = () => {
await clearTankerDataDirs();
});

it('can get the resource ID', async () => {
it('retrieves the resource ID', async () => {
const encrypted = await session.encrypt('less than three');
const resId = await tanker.getResourceId(encrypted);
const sessResId = session.resourceId;
Expand All @@ -36,14 +40,14 @@ export const encryptionSessionTests = () => {
expect(sessResId).eq(resId);
});

it('can roundtrip a basic encrypt', async () => {
it('roundtrips a basic encrypt', async () => {
const plaintext = 'foo';
const encrypted = await session.encrypt(plaintext);
const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('can use encryption options to share', async () => {
it('uses encryption options to share', async () => {
const other = await createTanker();
const otherPrivIdent = await createIdentity();
await other.start(otherPrivIdent);
Expand All @@ -67,18 +71,99 @@ export const encryptionSessionTests = () => {
expect(decrypted).eq(plaintext);
});

it('can roundtrip with encryptData', async () => {
it('roundtrips with encryptData', async () => {
const plaindata = 'dW5kZXIgY29vbCBtb29ubGlnaHQ=';
const encrypted = await session.encryptData(plaindata);
const decrypted = await tanker.decryptData(encrypted);
expect(decrypted).eq(plaindata);
});

it('cannot pass a non-base64 plaintext to encryptData', async () => {
it('fails to pass a non-base64 plaintext to encryptData', async () => {
const plaintext = 'plain text';
await expect(session.encryptData(plaintext)).eventually.rejectedWith(
InvalidArgument
);
});

it('encrypts with auto padding by default', async () => {
const plaintext = 'my clear data is clear!';
const lengthWithPadme = 24;

const encrypted = await session.encrypt(plaintext);

const paddedSize = getPaddedSize(
encrypted,
encryptionSessionPaddedOverhead
);
expect(paddedSize).eq(lengthWithPadme);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with auto padding', async () => {
const aliceSession = await tanker.createEncryptionSession({
paddingStep: Padding.AUTO,
});

const plaintext = 'my clear data is clear!';
const lengthWithPadme = 24;

const encrypted = await aliceSession.encrypt(plaintext);

const paddedSize = getPaddedSize(
encrypted,
encryptionSessionPaddedOverhead
);
expect(paddedSize).eq(lengthWithPadme);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with no padding', async () => {
const aliceSession = await tanker.createEncryptionSession({
paddingStep: Padding.OFF,
});

const plaintext = 'my clear data is clear!';
const encrypted = await aliceSession.encrypt(plaintext);

const paddedSize = getPaddedSize(encrypted, encryptionSessionOverhead);
expect(paddedSize).eq(plaintext.length);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('encrypts with a padding step', async () => {
const step = 13;
const aliceSession = await tanker.createEncryptionSession({
paddingStep: step,
});

const plaintext = 'my clear data is clear!';
const encrypted = await aliceSession.encrypt(plaintext);

const paddedSize = getPaddedSize(
encrypted,
encryptionSessionPaddedOverhead
);
expect(paddedSize % step).eq(0);

const decrypted = await tanker.decrypt(encrypted);
expect(decrypted).eq(plaintext);
});

it('fails to createEncryptionSession with a bad paddingStep', async () => {
await Promise.all(
[-1, 0, 1, 2.42, 'a random string', null].map(async (x) => {
await expect(
// @ts-expect-error
tanker.createEncryptionSession({ paddingStep: x })
).eventually.rejectedWith(InvalidArgument);
})
);
});
});
};
12 changes: 12 additions & 0 deletions example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ export async function clearTankerDataDirs(): Promise<void> {
}
pathsToClear = [];
}

export const getPaddedSize = (
b64Encrypted: string,
encryptionOverhead: number
) => {
const length = b64Encrypted.length;

let b64PaddingBytes = 0;
while (b64Encrypted[length - b64PaddingBytes - 1] === '=') b64PaddingBytes++;

return length * (3 / 4) - b64PaddingBytes - encryptionOverhead;
};
7 changes: 7 additions & 0 deletions ios/Utils+Private.m
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,20 @@
NSArray<NSString*>* shareWithUsers = optionsDict[@"shareWithUsers"];
NSArray<NSString*>* shareWithGroups = optionsDict[@"shareWithGroups"];
NSNumber* shareWithSelf = optionsDict[@"shareWithSelf"];
NSNumber* paddingStep = optionsDict[@"paddingStep"];

if (shareWithUsers)
ret.shareWithUsers = shareWithUsers;
if (shareWithGroups)
ret.shareWithGroups = shareWithGroups;
if (shareWithSelf)
ret.shareWithSelf = shareWithSelf.boolValue;
switch ([paddingStep intValue])
{
case 0: ret.paddingStep = [TKRPadding automatic]; break;
case 1: ret.paddingStep = [TKRPadding off]; break;
default: ret.paddingStep = [TKRPadding step: paddingStep.unsignedIntValue];
}
return ret;
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
"typescript": "^4.1.3"
},
"dependencies": {
"@tanker/errors": "^2.12.0"
"@tanker/errors": "^2.31.0",
"@tanker/types": "^2.31.0"
},
"peerDependencies": {
"react": "*",
Expand Down
Loading

0 comments on commit d40126a

Please sign in to comment.