Add symmetric encryption support to Waku Message

This commit is contained in:
Franck Royer 2021-07-14 17:06:23 +10:00
parent 56c30059b2
commit 25fccb4c9a
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
3 changed files with 202 additions and 9 deletions

View File

@ -90,6 +90,49 @@ describe('Waku Message', function () {
) )
); );
}); });
it('Waku message round trip binary encryption [symmetric, no signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, key) => {
const msg = await WakuMessage.fromBytes(payload, {
symKey: key,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [key]);
expect(actual?.payload).to.deep.equal(payload);
}
)
);
});
it('Waku message round trip binary encryption [symmetric, signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, symKey) => {
const sigPubKey = getPublicKey(sigPrivKey);
const msg = await WakuMessage.fromBytes(payload, {
symKey: symKey,
sigPrivKey: sigPrivKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [symKey]);
expect(actual?.payload).to.deep.equal(payload);
expect(actual?.signaturePublicKey).to.deep.equal(sigPubKey);
}
)
);
});
}); });
describe('Interop: Nim', function () { describe('Interop: Nim', function () {

View File

@ -25,10 +25,18 @@ export interface Options {
timestamp?: Date; timestamp?: Date;
/** /**
* Public Key to use to encrypt the messages using ECIES (Asymmetric Encryption). * Public Key to use to encrypt the messages using ECIES (Asymmetric Encryption).
*
* @throws if both `encPublicKey` and `symKey` are passed
*/ */
encPublicKey?: Uint8Array | string; encPublicKey?: Uint8Array | string;
/** /**
* Private key to use to sign the message, `encPublicKey` must be provided as only * Key to use to encrypt the messages using AES (Symmetric Encryption).
*
* @throws if both `encPublicKey` and `symKey` are passed
*/
symKey?: Uint8Array | string;
/**
* Private key to use to sign the message, either `encPublicKey` or `symKey` must be provided as only
* encrypted messages are signed. * encrypted messages are signed.
*/ */
sigPrivKey?: Uint8Array; sigPrivKey?: Uint8Array;
@ -61,24 +69,37 @@ export class WakuMessage {
* *
* If `opts.sigPrivKey` is passed and version 1 is used, the payload is signed * If `opts.sigPrivKey` is passed and version 1 is used, the payload is signed
* before encryption. * before encryption.
*
* @throws if both `opts.encPublicKey` and `opt.symKey` are passed
*/ */
static async fromBytes( static async fromBytes(
payload: Uint8Array, payload: Uint8Array,
opts?: Options opts?: Options
): Promise<WakuMessage> { ): Promise<WakuMessage> {
const { timestamp, contentTopic, encPublicKey, sigPrivKey } = Object.assign( const { timestamp, contentTopic, encPublicKey, symKey, sigPrivKey } =
{ timestamp: new Date(), contentTopic: DefaultContentTopic }, Object.assign(
opts ? opts : {} { timestamp: new Date(), contentTopic: DefaultContentTopic },
); opts ? opts : {}
);
let _payload = payload; let _payload = payload;
let version = DefaultVersion; let version = DefaultVersion;
let sig; let sig;
if (encPublicKey && symKey) {
throw 'Pass either `encPublicKey` or `symKey`, not both.';
}
if (encPublicKey) { if (encPublicKey) {
const enc = version_1.clearEncode(_payload, sigPrivKey); const enc = version_1.clearEncode(_payload, sigPrivKey);
_payload = await version_1.encryptAsymmetric(enc.payload, encPublicKey); _payload = await version_1.encryptAsymmetric(enc.payload, encPublicKey);
sig = enc.sig; sig = enc.sig;
version = 1; version = 1;
} else if (symKey) {
const enc = version_1.clearEncode(_payload, sigPrivKey);
_payload = await version_1.encryptSymmetric(enc.payload, symKey);
sig = enc.sig;
version = 1;
} }
return new WakuMessage( return new WakuMessage(
@ -127,7 +148,6 @@ export class WakuMessage {
if (protoBuf.version === 1 && protoBuf.payload) { if (protoBuf.version === 1 && protoBuf.payload) {
if (decPrivateKeys === undefined) { if (decPrivateKeys === undefined) {
dbg('Payload is encrypted but no private keys have been provided.'); dbg('Payload is encrypted but no private keys have been provided.');
return; return;
} }
@ -135,10 +155,15 @@ export class WakuMessage {
const allResults = await Promise.all( const allResults = await Promise.all(
decPrivateKeys.map(async (privateKey) => { decPrivateKeys.map(async (privateKey) => {
try { try {
return await version_1.decryptAsymmetric(payload, privateKey); return await version_1.decryptSymmetric(payload, privateKey);
} catch (e) { } catch (e) {
dbg('Failed to decrypt asymmetric message', e); dbg('Failed to decrypt message using symmetric encryption', e);
return; try {
return await version_1.decryptAsymmetric(payload, privateKey);
} catch (e) {
dbg('Failed to decrypt message using asymmetric encryption', e);
return;
}
} }
}) })
); );

View File

@ -0,0 +1,125 @@
import { expect } from 'chai';
import fc from 'fast-check';
fc.configureGlobal({
interruptAfterTimeLimit: 1500,
markInterruptAsFailure: true,
numRuns: 10, // Firefox is too slow for 100 (fc default) runs in 2s (mocha default)
});
import { WakuMessage } from '../../lib/waku_message';
import { getPublicKey } from '../../lib/waku_message/version_1';
describe('Waku Message', function () {
it('Waku message round trip binary serialization [clear]', async function () {
await fc.assert(
fc.asyncProperty(fc.string(), async (s) => {
const msg = await WakuMessage.fromUtf8String(s);
const binary = msg.encode();
const actual = await WakuMessage.decode(binary);
expect(actual).to.deep.equal(msg);
})
);
});
it('Payload to utf-8', async function () {
await fc.assert(
fc.asyncProperty(fc.string(), async (s) => {
const msg = await WakuMessage.fromUtf8String(s);
const utf8 = msg.payloadAsUtf8;
return utf8 === s;
})
);
});
it('Waku message round trip binary encryption [asymmetric, no signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, privKey) => {
const publicKey = getPublicKey(privKey);
const msg = await WakuMessage.fromBytes(payload, {
encPublicKey: publicKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [privKey]);
expect(actual?.payload).to.deep.equal(payload);
}
)
);
});
it('Waku message round trip binary encryption [asymmetric, signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, encPrivKey) => {
const sigPubKey = getPublicKey(sigPrivKey);
const encPubKey = getPublicKey(encPrivKey);
const msg = await WakuMessage.fromBytes(payload, {
encPublicKey: encPubKey,
sigPrivKey: sigPrivKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [encPrivKey]);
expect(actual?.payload).to.deep.equal(payload);
expect(actual?.signaturePublicKey).to.deep.equal(sigPubKey);
}
)
);
});
it('Waku message round trip binary encryption [symmetric, no signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, key) => {
const msg = await WakuMessage.fromBytes(payload, {
symKey: key,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [key]);
expect(actual?.payload).to.deep.equal(payload);
}
)
);
});
it('Waku message round trip binary encryption [symmetric, signature]', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, symKey) => {
const sigPubKey = getPublicKey(sigPrivKey);
const msg = await WakuMessage.fromBytes(payload, {
symKey: symKey,
sigPrivKey: sigPrivKey,
});
const wireBytes = msg.encode();
const actual = await WakuMessage.decode(wireBytes, [symKey]);
expect(actual?.payload).to.deep.equal(payload);
expect(actual?.signaturePublicKey).to.deep.equal(sigPubKey);
}
)
);
});
});