From aafdf6157217b60278e7512c19465200e094a010 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Tue, 22 Nov 2022 10:13:57 -0400 Subject: [PATCH] handle secure transfer --- src/codec.ts | 57 ++++++++++++++++++++--- src/handshake.ts | 4 +- src/payload.ts | 8 +++- src/waku-noise-pairing.spec.ts | 85 +++++++++++++++++++++++++++++++--- 4 files changed, 137 insertions(+), 17 deletions(-) diff --git a/src/codec.ts b/src/codec.ts index e70eee1..cac699b 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -32,13 +32,26 @@ export class NoiseHandshakeEncoder implements Encoder { } } -export class MessageV2 extends MessageV0 implements Message { +export class NoiseHandshakeMessage extends MessageV0 implements Message { get payloadV2(): PayloadV2 { return PayloadV2.deserialize(this.payload!); } } -export class NoiseHandshakeDecoder implements Decoder { +export class NoiseSecureMessage extends MessageV0 implements Message { + private readonly _decodedPayload: Uint8Array; + + constructor(proto: proto_message.WakuMessage, decodedPayload: Uint8Array) { + super(proto); + this._decodedPayload = decodedPayload; + } + + get payload(): Uint8Array { + return this._decodedPayload; + } +} + +export class NoiseHandshakeDecoder implements Decoder { constructor(public contentTopic: string) {} decodeProto(bytes: Uint8Array): Promise { @@ -47,7 +60,7 @@ export class NoiseHandshakeDecoder implements Decoder { return Promise.resolve(protoMessage); } - async decode(proto: ProtoMessage): Promise { + async decode(proto: ProtoMessage): Promise { // https://github.com/status-im/js-waku/issues/921 if (proto.version === undefined) { proto.version = 0; @@ -63,7 +76,7 @@ export class NoiseHandshakeDecoder implements Decoder { return; } - return new MessageV2(proto); + return new NoiseHandshakeMessage(proto); } } @@ -82,6 +95,7 @@ export class NoiseSecureTransferEncoder implements Encoder { log("No payload to encrypt, skipping: ", message); return; } + const preparedPayload = this.hsResult.writeMessage(message.payload, this.hsResult.nametagsOutbound); const payload = preparedPayload.serialize(); @@ -95,6 +109,35 @@ export class NoiseSecureTransferEncoder implements Encoder { } } -/* -export class NoiseSecureTransferDecoder implements Decoder<> {} -*/ +export class NoiseSecureTransferDecoder implements Decoder { + constructor(public contentTopic: string, private hsResult: HandshakeResult) {} + + decodeProto(bytes: Uint8Array): Promise { + const protoMessage = proto_message.WakuMessage.decode(bytes); + log("Message decoded", protoMessage); + return Promise.resolve(protoMessage); + } + + async decode(proto: ProtoMessage): Promise { + // https://github.com/status-im/js-waku/issues/921 + if (proto.version === undefined) { + proto.version = 0; + } + + if (proto.version !== Version) { + log("Failed to decode due to incorrect version, expected:", Version, ", actual:", proto.version); + return Promise.resolve(undefined); + } + + if (!proto.payload) { + log("No payload, skipping: ", proto); + return; + } + + const payloadV2 = PayloadV2.deserialize(proto.payload); + + const decryptedPayload = this.hsResult.readMessage(payloadV2, this.hsResult.nametagsInbound); + + return new NoiseSecureMessage(proto, decryptedPayload); + } +} diff --git a/src/handshake.ts b/src/handshake.ts index b944e74..69770c5 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -272,8 +272,8 @@ export class Handshake { hsResult.nametagsOutbound.initNametagsBuffer(); // We store the optional fields rs and h - hsResult.rs = this.hs.rs!; - hsResult.h = this.hs.ss.h; + hsResult.rs = new Uint8Array(this.hs.rs!); + hsResult.h = new Uint8Array(this.hs.ss.h); return hsResult; } diff --git a/src/payload.ts b/src/payload.ts index 9322d43..ee94676 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -23,6 +23,12 @@ export class MessageNametagBuffer { counter = 0; secret?: Uint8Array; + constructor() { + for (let i = 0; i < this.buffer.length; i++) { + this.buffer[i] = new Uint8Array(MessageNametagLength); + } + } + // Initializes the empty Message nametag buffer. The n-th nametag is equal to HKDF( secret || n ) initNametagsBuffer(): void { // We default the counter and buffer fields @@ -44,7 +50,7 @@ export class MessageNametagBuffer { pop(): MessageNametag { // Note that if the input MessageNametagBuffer is set to default, an all 0 messageNametag is returned - const messageNametag = this.buffer[0]; + const messageNametag = new Uint8Array(this.buffer[0]); this.delete(1); return messageNametag; } diff --git a/src/waku-noise-pairing.spec.ts b/src/waku-noise-pairing.spec.ts index 9ab701f..0f8cd49 100644 --- a/src/waku-noise-pairing.spec.ts +++ b/src/waku-noise-pairing.spec.ts @@ -3,11 +3,16 @@ import { randomBytes } from "@stablelib/random"; import { expect } from "chai"; import { equals as uint8ArrayEquals } from "uint8arrays/equals"; -import { NoiseHandshakeDecoder, NoiseHandshakeEncoder } from "./codec"; +import { + NoiseHandshakeDecoder, + NoiseHandshakeEncoder, + NoiseSecureTransferDecoder, + NoiseSecureTransferEncoder, +} from "./codec"; import { commitPublicKey, generateX25519KeyPair } from "./crypto"; import { Handshake } from "./handshake"; import { NoiseHandshakePatterns } from "./patterns"; -import { MessageNametagLength } from "./payload"; +import { MessageNametagBufferSize, MessageNametagLength } from "./payload"; import { NoisePublicKey } from "./publickey"; import { fromQr, toQr } from "./utils"; @@ -211,15 +216,81 @@ describe("Waku Noise Sessions", () => { // Secure Transfer Phase // ========== + // We finalize the handshake to retrieve the Inbound/Outbound Symmetric States + const aliceHSResult = aliceHS.finalizeHandshake(); + const bobHSResult = bobHS.finalizeHandshake(); + + const aliceEncoder = new NoiseSecureTransferEncoder(contentTopic, aliceHSResult); + const bobEncoder = new NoiseSecureTransferEncoder(contentTopic, bobHSResult); + + const aliceDecoder = new NoiseSecureTransferDecoder(contentTopic, aliceHSResult); + const bobDecoder = new NoiseSecureTransferDecoder(contentTopic, bobHSResult); + + // We test read/write of random messages exchanged between Alice and Bob + // Note that we exchange more than the number of messages contained in the nametag buffer to test if they are filled correctly as the communication proceeds + for (let i = 0; i < 10 * MessageNametagBufferSize; i++) { + // Alice writes to Bob + let message = randomBytes(32, rng); + let encodedMsg = await aliceEncoder.encode({ payload: message }); + let readMessageProto = await bobDecoder.decodeProto(encodedMsg!); + let readMessage = await bobDecoder.decode(readMessageProto!); + + expect(uint8ArrayEquals(message, readMessage!.payload)).to.be.true; + + // Bob writes to Alice + message = randomBytes(32, rng); + encodedMsg = await bobEncoder.encode({ payload: message }); + readMessageProto = await aliceDecoder.decodeProto(encodedMsg!); + readMessage = await aliceDecoder.decode(readMessageProto!); + + expect(uint8ArrayEquals(message, readMessage!.payload)).to.be.true; + } + // TODO // TODO // TODO // TODO // TODO - // TODO - // TODO - // TODO - // TODO - // TODO + /* + // We test how nametag buffers help in detecting lost messages + // Alice writes two messages to Bob, but only the second is received + try { + const message = randomBytes(32, rng); + payload2 = aliceHSResult.writeMessage(message, aliceHSResult.nametagsOutbound); + message = randomBytes(32, rng); + payload2 = aliceHSResult.writeMessage(aliceHSResult.nametagsOutbound); + } catch (NoiseSomeMessagesWereLost) { + let readMessage = readMessage( + bobHSResult, + payload2, + (inboundMessageNametagBuffer = bobHSResult.nametagsInbound) + ).get(); + } + + // We adjust bob nametag buffer for next test (i.e. the missed message is correctly recovered) + bobHSResult.nametagsInbound.delete(2); + let message = randomBytes(32, rng); + payload2 = writeMessage(bobHSResult, message, (outboundMessageNametagBuffer = bobHSResult.nametagsOutbound)); + readMessage = readMessage( + aliceHSResult, + payload2, + (inboundMessageNametagBuffer = aliceHSResult.nametagsInbound) + ).get(); + + expect(uint8ArrayEquals(message, readMessage!.payload)).to.be.true; + + // We test if a missing nametag is correctly detected + try { + const message = randomBytes(32, rng); + const payload2 = aliceHSResult.writeMessage(message, aliceHSResult.nametagsOutbound); + bobHSResult.nametagsInbound.delete(1); + } catch (NoiseMessageNametagError) { + let readMessage = readMessage( + bobHSResult, + payload2, + (inboundMessageNametagBuffer = bobHSResult.nametagsInbound) + ).get(); + } + */ }); });