mirror of https://github.com/waku-org/js-waku.git
Add symmetric encryption support to Waku Message
This commit is contained in:
parent
56c30059b2
commit
25fccb4c9a
|
@ -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 () {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue