From 20dbd3ff6c40a6e9a55a29db5b58aa430ca9c280 Mon Sep 17 00:00:00 2001 From: s1fr0 Date: Fri, 3 Jun 2022 19:43:43 +0000 Subject: [PATCH] deploy: c259f4cbaae9ba8ae0dc424aee80887e0fe2693d --- tests/v2/test_waku_noise.nim | 452 ++++++++++++- .../vendor/libbacktrace-upstream/libtool | 2 +- waku/v2/protocol/waku_noise/noise.nim | 607 +++++++++++++++++- 3 files changed, 1039 insertions(+), 22 deletions(-) diff --git a/tests/v2/test_waku_noise.nim b/tests/v2/test_waku_noise.nim index 7893ef8a6..b2af86d4a 100644 --- a/tests/v2/test_waku_noise.nim +++ b/tests/v2/test_waku_noise.nim @@ -209,7 +209,6 @@ procSuite "Waku Noise": getNonce(cipherState) == nonce + 1 plaintext == decrypted - # If a Cipher State has no key set, encryptWithAd should return the plaintext without increasing the nonce setCipherStateKey(cipherState, EmptyKey) nonce = getNonce(cipherState) @@ -321,7 +320,7 @@ procSuite "Waku Noise": ######################################## # We generate random input key material and we execute mixKey - var inputKeyMaterial = randomChaChaPolyKey(rng[]) + var inputKeyMaterial = randomSeqByte(rng[], rand(1..128)) mixKey(symmetricState, inputKeyMaterial) # mixKey changes the Symmetric State's chaining key and encryption key of the embedded Cipher State @@ -342,7 +341,7 @@ procSuite "Waku Noise": ######################################## # We generate random input key material and we execute mixKeyAndHash - inputKeyMaterial = randomChaChaPolyKey(rng[]) + inputKeyMaterial = randomSeqByte(rng[], rand(1..128)) mixKeyAndHash(symmetricState, inputKeyMaterial) # mixKeyAndHash executes a mixKey and a mixHash using the input key material @@ -413,3 +412,450 @@ procSuite "Waku Noise": getNonce(cs1) == 0.uint64 getNonce(cs2) == 0.uint64 getKey(cs1) != getKey(cs2) + + test "Noise XX Handhshake and message encryption (extended test)": + + let hsPattern = NoiseHandshakePatterns["XX"] + + # We initialize Alice's and Bob's Handshake State + let aliceStaticKey = genKeyPair(rng[]) + var aliceHS = initialize(hsPattern = hsPattern, staticKey = aliceStaticKey, initiator = true) + + let bobStaticKey = genKeyPair(rng[]) + var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey) + + var + sentTransportMessage: seq[byte] + aliceStep, bobStep: HandshakeStepResult + + # Here the handshake starts + # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user + + ############### + # 1st step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message + # and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + ############### + # 2nd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # At this step, Bob writes and returns a payload + bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() + + # While Alice reads and returns the (decrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() + + check: + aliceStep.transportMessage == sentTransportMessage + + ############### + # 3rd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + # Note that for this handshake pattern, no more message patterns are left for processing + # Another call to stepHandshake would return an empty HandshakeStepResult + # We test that extra calls to stepHandshake do not affect parties' handshake states + # and that the intermediate HandshakeStepResult are empty + let prevAliceHS = aliceHS + let prevBobHS = bobHS + + let bobStep1 = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() + let aliceStep1 = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep1.payload2).get() + let aliceStep2 = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + let bobStep2 = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep2.payload2).get() + + check: + aliceStep1 == default(HandshakeStepResult) + aliceStep2 == default(HandshakeStepResult) + bobStep1 == default(HandshakeStepResult) + bobStep2 == default(HandshakeStepResult) + aliceHS == prevAliceHS + bobHS == prevBobHS + + ######################### + # After Handshake + ######################### + + # We finalize the handshake to retrieve the Inbound/Outbound symmetric states + var aliceHSResult, bobHSResult: HandshakeResult + + aliceHSResult = finalizeHandshake(aliceHS) + bobHSResult = finalizeHandshake(bobHS) + + # We test read/write of random messages exchanged between Alice and Bob + var + payload2: PayloadV2 + message: seq[byte] + readMessage: seq[byte] + + for _ in 0..10: + + # Alice writes to Bob + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(aliceHSResult, message) + readMessage = readMessage(bobHSResult, payload2).get() + + check: + message == readMessage + + # Bob writes to Alice + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(bobHSResult, message) + readMessage = readMessage(aliceHSResult, payload2).get() + + check: + message == readMessage + + test "Noise XXpsk0 Handhshake and message encryption (short test)": + + let hsPattern = NoiseHandshakePatterns["XXpsk0"] + + # We generate a random psk + let psk = randomSeqByte(rng[], 32) + + # We initialize Alice's and Bob's Handshake State + let aliceStaticKey = genKeyPair(rng[]) + var aliceHS = initialize(hsPattern = hsPattern, staticKey = aliceStaticKey, psk = psk, initiator = true) + + let bobStaticKey = genKeyPair(rng[]) + var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey, psk = psk) + + var + sentTransportMessage: seq[byte] + aliceStep, bobStep: HandshakeStepResult + + # Here the handshake starts + # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user + + ############### + # 1st step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message + # and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + ############### + # 2nd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # At this step, Bob writes and returns a payload + bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() + + # While Alice reads and returns the (decrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() + + check: + aliceStep.transportMessage == sentTransportMessage + + ############### + # 3rd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transportMessage alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + # Note that for this handshake pattern, no more message patterns are left for processing + + ######################### + # After Handshake + ######################### + + # We finalize the handshake to retrieve the Inbound/Outbound Symmetric States + var aliceHSResult, bobHSResult: HandshakeResult + + aliceHSResult = finalizeHandshake(aliceHS) + bobHSResult = finalizeHandshake(bobHS) + + # We test read/write of random messages exchanged between Alice and Bob + var + payload2: PayloadV2 + message: seq[byte] + readMessage: seq[byte] + + for _ in 0..10: + + # Alice writes to Bob + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(aliceHSResult, message) + readMessage = readMessage(bobHSResult, payload2).get() + + check: + message == readMessage + + # Bob writes to Alice + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(bobHSResult, message) + readMessage = readMessage(aliceHSResult, payload2).get() + + check: + message == readMessage + + test "Noise K1K1 Handhshake and message encryption (short test)": + + let hsPattern = NoiseHandshakePatterns["K1K1"] + + # We initialize Alice's and Bob's Handshake State + let aliceStaticKey = genKeyPair(rng[]) + let bobStaticKey = genKeyPair(rng[]) + + # This handshake has the following pre-message pattern: + # -> s + # <- s + # ... + # So we define accordingly the sequence of the pre-message public keys + let preMessagePKs: seq[NoisePublicKey] = @[toNoisePublicKey(getPublicKey(aliceStaticKey)), toNoisePublicKey(getPublicKey(bobStaticKey))] + + var aliceHS = initialize(hsPattern = hsPattern, staticKey = aliceStaticKey, preMessagePKs = preMessagePKs, initiator = true) + var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey, preMessagePKs = preMessagePKs) + + var + sentTransportMessage: seq[byte] + aliceStep, bobStep: HandshakeStepResult + + # Here the handshake starts + # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user + + ############### + # 1st step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message + # and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + ############### + # 2nd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # At this step, Bob writes and returns a payload + bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() + + # While Alice reads and returns the (decrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() + + check: + aliceStep.transportMessage == sentTransportMessage + + ############### + # 3rd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # Similarly as in first step, Alice writes a Waku2 payload containing the handshake_message and the (encrypted) transportMessage + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transportMessage alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + # Note that for this handshake pattern, no more message patterns are left for processing + + ######################### + # After Handshake + ######################### + + # We finalize the handshake to retrieve the Inbound/Outbound Symmetric States + var aliceHSResult, bobHSResult: HandshakeResult + + aliceHSResult = finalizeHandshake(aliceHS) + bobHSResult = finalizeHandshake(bobHS) + + # We test read/write of random messages between Alice and Bob + var + payload2: PayloadV2 + message: seq[byte] + readMessage: seq[byte] + + for _ in 0..10: + + # Alice writes to Bob + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(aliceHSResult, message) + readMessage = readMessage(bobHSResult, payload2).get() + + check: + message == readMessage + + # Bob writes to Alice + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(bobHSResult, message) + readMessage = readMessage(aliceHSResult, payload2).get() + + check: + message == readMessage + + + test "Noise XK1 Handhshake and message encryption (short test)": + + let hsPattern = NoiseHandshakePatterns["XK1"] + + # We initialize Alice's and Bob's Handshake State + let aliceStaticKey = genKeyPair(rng[]) + let bobStaticKey = genKeyPair(rng[]) + + # This handshake has the following pre-message pattern: + # <- s + # ... + # So we define accordingly the sequence of the pre-message public keys + let preMessagePKs: seq[NoisePublicKey] = @[toNoisePublicKey(getPublicKey(bobStaticKey))] + + var aliceHS = initialize(hsPattern = hsPattern, staticKey = aliceStaticKey, preMessagePKs = preMessagePKs, initiator = true) + var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey, preMessagePKs = preMessagePKs) + + var + sentTransportMessage: seq[byte] + aliceStep, bobStep: HandshakeStepResult + + # Here the handshake starts + # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user + + ############### + # 1st step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message + # and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + ############### + # 2nd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # At this step, Bob writes and returns a payload + bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() + + # While Alice reads and returns the (decrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() + + check: + aliceStep.transportMessage == sentTransportMessage + + ############### + # 3rd step + ############### + + # We generate a random transport message + sentTransportMessage = randomSeqByte(rng[], 32) + + # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message + aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() + + # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him + bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() + + check: + bobStep.transportMessage == sentTransportMessage + + # Note that for this handshake pattern, no more message patterns are left for processing + + ######################### + # After Handshake + ######################### + + # We finalize the handshake to retrieve the Inbound/Outbound Symmetric States + var aliceHSResult, bobHSResult: HandshakeResult + + aliceHSResult = finalizeHandshake(aliceHS) + bobHSResult = finalizeHandshake(bobHS) + + # We test read/write of random messages exchanged between Alice and Bob + var + payload2: PayloadV2 + message: seq[byte] + readMessage: seq[byte] + + for _ in 0..10: + + # Alice writes to Bob + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(aliceHSResult, message) + readMessage = readMessage(bobHSResult, payload2).get() + + check: + message == readMessage + + # Bob writes to Alice + message = randomSeqByte(rng[], 32) + payload2 = writeMessage(bobHSResult, message) + readMessage = readMessage(aliceHSResult, payload2).get() + + check: + message == readMessage diff --git a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool index fc5b7e3de..95d456335 100755 --- a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool +++ b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (libbacktrace) version-unused -# Libtool was configured on host fv-az133-170: +# Libtool was configured on host fv-az124-778: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/waku/v2/protocol/waku_noise/noise.nim b/waku/v2/protocol/waku_noise/noise.nim index e41c746e7..fa57ad021 100644 --- a/waku/v2/protocol/waku_noise/noise.nim +++ b/waku/v2/protocol/waku_noise/noise.nim @@ -152,13 +152,21 @@ type msgPatternIdx: uint8 psk: seq[byte] + # While processing messages patterns, users either: + # - read (decrypt) the other party's (encrypted) transport message + # - write (encrypt) a message, sent through a PayloadV2 + # These two intermediate results are stored in the HandshakeStepResult data structure + HandshakeStepResult* = object + payload2*: PayloadV2 + transportMessage*: seq[byte] + # When a handshake is complete, the HandhshakeResult will contain the two # Cipher States used to encrypt/decrypt outbound/inbound messages # The recipient static key rs and handshake hash values h are stored to address some possible future applications (channel-binding, session management, etc.). # However, are not required by Noise specifications and are thus optional - HandshakeResult = object - csInbound: CipherState + HandshakeResult* = object csOutbound: CipherState + csInbound: CipherState # Optional fields: rs: EllipticCurveKey h: MDigest[256] @@ -195,7 +203,7 @@ type const # The empty pre message patterns - EmptyPreMessagePattern: seq[PreMessagePattern] = @[] + EmptyPreMessage: seq[PreMessagePattern] = @[] # Supported Noise handshake patterns as defined in https://rfc.vac.dev/spec/35/#specification NoiseHandshakePatterns* = { @@ -215,14 +223,14 @@ const ), "XX": HandshakePattern(name: "Noise_XX_25519_ChaChaPoly_SHA256", - preMessagePatterns: EmptyPreMessagePattern, + preMessagePatterns: EmptyPreMessage, messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e]), MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_s, T_es]), MessagePattern(direction: D_r, tokens: @[T_s, T_se])] ), "XXpsk0": HandshakePattern(name: "Noise_XXpsk0_25519_ChaChaPoly_SHA256", - preMessagePatterns: EmptyPreMessagePattern, + preMessagePatterns: EmptyPreMessage, messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_psk, T_e]), MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_s, T_es]), MessagePattern(direction: D_r, tokens: @[T_s, T_se])] @@ -278,8 +286,8 @@ proc print*(self: HandshakePattern) if self.name != "": stdout.write self.name, ":\n" stdout.flushFile() - #We iterate over pre message patterns, if any - if self.preMessagePatterns != EmptyPreMessagePattern: + # We iterate over pre message patterns, if any + if self.preMessagePatterns != EmptyPreMessage: for pattern in self.preMessagePatterns: stdout.write " ", pattern.direction var first = true @@ -293,7 +301,7 @@ proc print*(self: HandshakePattern) stdout.flushFile() stdout.write " ...\n" stdout.flushFile() - #We iterate over message patterns + # We iterate over message patterns for pattern in self.messagePatterns: stdout.write " ", pattern.direction var first = true @@ -339,7 +347,6 @@ proc dh*(private: EllipticCurveKey, public: EllipticCurveKey): EllipticCurveKey return output - ################################################################# # Noise state machine primitives @@ -506,7 +513,7 @@ proc init*(_: type[SymmetricState], hsPattern: HandshakePattern): SymmetricState # MixKey as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object # Updates a Symmetric state chaining key and symmetric state -proc mixKey*(ss: var SymmetricState, inputKeyMaterial: ChaChaPolyKey) = +proc mixKey*(ss: var SymmetricState, inputKeyMaterial: openArray[byte]) = # We derive two keys using HKDF var tempKeys: array[2, ChaChaPolyKey] sha256.hkdf(ss.ck, inputKeyMaterial, [], tempKeys) @@ -626,7 +633,7 @@ proc encrypt*( # Since ChaChaPoly's library "encrypt" primitive directly changes the input plaintext to the ciphertext, # we copy the plaintext into the ciphertext variable and we pass the latter to encrypt ciphertext.data.add plaintext - #TODO: add padding + # TODO: add padding # ChaChaPoly.encrypt takes as input: the key (k), the nonce (nonce), a data structure for storing the computed authorization tag (tag), # the plaintext (overwritten to ciphertext) (data), the associated data (ad) ChaChaPoly.encrypt(state.k, state.nonce, ciphertext.tag, ciphertext.data, state.ad) @@ -653,7 +660,7 @@ proc decrypt*( # ChaChaPoly.decrypt takes as input: the key (k), the nonce (nonce), a data structure for storing the computed authorization tag (tag), # the ciphertext (overwritten to plaintext) (data), the associated data (ad) ChaChaPoly.decrypt(state.k, state.nonce, tagOut, plaintext, state.ad) - #TODO: add unpadding + # TODO: add unpadding trace "decrypt", tagIn = tagIn, tagOut = tagOut, nonce = state.nonce # We check if the authorization tag computed while decrypting is the same as the input tag if tagIn != tagOut: @@ -687,11 +694,11 @@ proc randomChaChaPolyCipherState*(rng: var BrHmacDrbgContext): ChaChaPolyCipherS proc `==`(k1, k2: NoisePublicKey): bool = return (k1.flag == k2.flag) and (k1.pk == k2.pk) -# Converts a (public, private) Elliptic Curve keypair to an unencrypted Noise public key (only public part) -proc keyPairToNoisePublicKey*(keyPair: KeyPair): NoisePublicKey = +# Converts a public Elliptic Curve key to an unencrypted Noise public key +proc toNoisePublicKey*(publicKey: EllipticCurveKey): NoisePublicKey = var noisePublicKey: NoisePublicKey noisePublicKey.flag = 0 - noisePublicKey.pk = getBytes(keyPair.publicKey) + noisePublicKey.pk = getBytes(publicKey) return noisePublicKey # Generates a random Noise public key @@ -803,7 +810,7 @@ proc randomPayloadV2*(rng: var BrHmacDrbgContext): PayloadV2 = # The output can be then passed to the payload field of a WakuMessage https://rfc.vac.dev/spec/14/ proc serializePayloadV2*(self: PayloadV2): Result[seq[byte], cstring] = - #We collect public keys contained in the handshake message + # We collect public keys contained in the handshake message var # According to https://rfc.vac.dev/spec/35/, the maximum size for the handshake message is 256 bytes, that is # the handshake message length can be represented with 1 byte only. (its length can be stored in 1 byte) @@ -873,7 +880,6 @@ proc deserializePayloadV2*(payload: seq[byte]): Result[PayloadV2, cstring] var handshakeMessageLen = payload[i].uint64 if handshakeMessageLen > uint8.high.uint64: debug "Payload malformed: too many public keys contained in the handshake message" - #raise newException(NoiseMalformedHandshake, "Too many public keys in handshake message") return err("Too many public keys in handshake message") i += 1 @@ -917,4 +923,569 @@ proc deserializePayloadV2*(payload: seq[byte]): Result[PayloadV2, cstring] payload2.transportMessage = payload[i..i+transportMessageLen-1] i += transportMessageLen - return ok(payload2) \ No newline at end of file + return ok(payload2) + + +################################################################# + +# Handshake Processing + +################################# +## Utilities +################################# + +# Based on the message handshake direction and if the user is or not the initiator, returns a boolean tuple telling if the user +# has to read or write the next handshake message +proc getReadingWritingState(hs: HandshakeState, direction: MessageDirection): (bool, bool) = + + var reading, writing : bool + + if hs.initiator and direction == D_r: + # I'm Alice and direction is -> + reading = false + writing = true + elif hs.initiator and direction == D_l: + # I'm Alice and direction is <- + reading = true + writing = false + elif not hs.initiator and direction == D_r: + # I'm Bob and direction is -> + reading = true + writing = false + elif not hs.initiator and direction == D_l: + # I'm Bob and direction is <- + reading = false + writing = true + + return (reading, writing) + +# Checks if a pre-message is valid according to Noise specifications +# http://www.noiseprotocol.org/noise.html#handshake-patterns +proc isValid(msg: seq[PreMessagePattern]): bool = + + var isValid: bool = true + + # Non-empty pre-messages can only have patterns "e", "s", "e,s" in each direction + let allowedPatterns: seq[PreMessagePattern] = @[ PreMessagePattern(direction: D_r, tokens: @[T_s]), + PreMessagePattern(direction: D_r, tokens: @[T_e]), + PreMessagePattern(direction: D_r, tokens: @[T_e, T_s]), + PreMessagePattern(direction: D_l, tokens: @[T_s]), + PreMessagePattern(direction: D_l, tokens: @[T_e]), + PreMessagePattern(direction: D_l, tokens: @[T_e, T_s]) + ] + + # We check if pre message patterns are allowed + for pattern in msg: + if not (pattern in allowedPatterns): + isValid = false + break + + return isValid + +################################# +# Handshake messages processing procedures +################################# + +# Processes pre-message patterns +proc processPreMessagePatternTokens*(hs: var HandshakeState, inPreMessagePKs: seq[NoisePublicKey] = @[]) + {.raises: [Defect, NoiseMalformedHandshake, NoiseHandshakeError, NoisePublicKeyError].} = + + var + # I make a copy of the input pre-message public keys, so that I can easily delete processed ones without using iterators/counters + preMessagePKs = inPreMessagePKs + # Here we store currently processed pre message public key + currPK : NoisePublicKey + + # We retrieve the pre-message patterns to process, if any + # If none, there's nothing to do + if hs.handshakePattern.preMessagePatterns == EmptyPreMessage: + return + + # If not empty, we check that pre-message is valid according to Noise specifications + if isValid(hs.handshakePattern.preMessagePatterns) == false: + raise newException(NoiseMalformedHandshake, "Invalid pre-message in handshake") + + # We iterate over each pattern contained in the pre-message + for messagePattern in hs.handshakePattern.preMessagePatterns: + let + direction = messagePattern.direction + tokens = messagePattern.tokens + + # We get if the user is reading or writing the current pre-message pattern + var (reading, writing) = getReadingWritingState(hs , direction) + + # We process each message pattern token + for token in tokens: + + # We process the pattern token + case token + of T_e: + + # We expect an ephemeral key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs) + if preMessagePKs.len > 0: + currPK = preMessagePKs[0] + else: + raise newException(NoiseHandshakeError, "Noise pre-message read e, expected a public key") + + # If user is reading the "e" token + if reading: + trace "noise pre-message read e" + + # We check if current key is encrypted or not. We assume pre-message public keys are all unencrypted on users' end + if currPK.flag == 0.uint8: + + # Sets re and calls MixHash(re.public_key). + hs.re = intoCurve25519Key(currPK.pk) + hs.ss.mixHash(hs.re) + + else: + raise newException(NoisePublicKeyError, "Noise read e, incorrect encryption flag for pre-message public key") + + # If user is writing the "e" token + elif writing: + + trace "noise pre-message write e" + + # When writing, the user is sending a public key, + # We check that the public part corresponds to the set local key and we call MixHash(e.public_key). + if hs.e.publicKey == intoCurve25519Key(currPK.pk): + hs.ss.mixHash(hs.e.publicKey) + else: + raise newException(NoisePublicKeyError, "Noise pre-message e key doesn't correspond to locally set e key pair") + + # Noise specification: section 9.2 + # In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results + # in a call to MixHash(e.public_key). + # In a PSK handshake, all of these calls are followed by MixKey(e.public_key). + if "psk" in hs.handshakePattern.name: + hs.ss.mixKey(currPK.pk) + + # We delete processed public key + preMessagePKs.delete(0) + + of T_s: + + # We expect a static key, so we attempt to read it (next PK to process will always be at index of preMessagePKs) + if preMessagePKs.len > 0: + currPK = preMessagePKs[0] + else: + raise newException(NoiseHandshakeError, "Noise pre-message read s, expected a public key") + + # If user is reading the "s" token + if reading: + trace "noise pre-message read s" + + # We check if current key is encrypted or not. We assume pre-message public keys are all unencrypted on users' end + if currPK.flag == 0.uint8: + + # Sets re and calls MixHash(re.public_key). + hs.rs = intoCurve25519Key(currPK.pk) + hs.ss.mixHash(hs.rs) + + else: + raise newException(NoisePublicKeyError, "Noise read s, incorrect encryption flag for pre-message public key") + + # If user is writing the "s" token + elif writing: + + trace "noise pre-message write s" + + # If writing, it means that the user is sending a public key, + # We check that the public part corresponds to the set local key and we call MixHash(s.public_key). + if hs.s.publicKey == intoCurve25519Key(currPK.pk): + hs.ss.mixHash(hs.s.publicKey) + else: + raise newException(NoisePublicKeyError, "Noise pre-message s key doesn't correspond to locally set s key pair") + + # Noise specification: section 9.2 + # In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results + # in a call to MixHash(e.public_key). + # In a PSK handshake, all of these calls are followed by MixKey(e.public_key). + if "psk" in hs.handshakePattern.name: + hs.ss.mixKey(currPK.pk) + + # We delete processed public key + preMessagePKs.delete(0) + + else: + + raise newException(NoiseMalformedHandshake, "Invalid Token for pre-message pattern") + +# This procedure encrypts/decrypts the implicit payload attached at the end of every message pattern +proc processMessagePatternPayload(hs: var HandshakeState, transportMessage: seq[byte]): seq[byte] + {.raises: [Defect, NoiseDecryptTagError, NoiseNonceMaxError].} = + + var payload: seq[byte] + + # We retrieve current message pattern (direction + tokens) to process + let direction = hs.handshakePattern.messagePatterns[hs.msgPatternIdx].direction + + # We get if the user is reading or writing the input handshake message + var (reading, writing) = getReadingWritingState(hs, direction) + + # We decrypt the transportMessage, if any + if reading: + payload = hs.ss.decryptAndHash(transportMessage) + elif writing: + payload = hs.ss.encryptAndHash(transportMessage) + + return payload + +# We process an input handshake message according to current handshake state and we return the next handshake step's handshake message +proc processMessagePatternTokens*(rng: var BrHmacDrbgContext, hs: var HandshakeState, inputHandshakeMessage: seq[NoisePublicKey] = @[]): Result[seq[NoisePublicKey], cstring] + {.raises: [Defect, NoiseHandshakeError, NoiseMalformedHandshake, NoisePublicKeyError, NoiseDecryptTagError, NoiseNonceMaxError].} = + + # We retrieve current message pattern (direction + tokens) to process + let + messagePattern = hs.handshakePattern.messagePatterns[hs.msgPatternIdx] + direction = messagePattern.direction + tokens = messagePattern.tokens + + # We get if the user is reading or writing the input handshake message + var (reading, writing) = getReadingWritingState(hs , direction) + + # I make a copy of the handshake message so that I can easily delete processed PKs without using iterators/counters + # (Possibly) non-empty if reading + var inHandshakeMessage = inputHandshakeMessage + + # The party's output public keys + # (Possibly) non-empty if writing + var outHandshakeMessage: seq[NoisePublicKey] = @[] + + # In currPK we store the currently processed public key from the handshake message + var currPK: NoisePublicKey + + # We process each message pattern token + for token in tokens: + + case token + of T_e: + + # If user is reading the "s" token + if reading: + trace "noise read e" + + # We expect an ephemeral key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs) + if inHandshakeMessage.len > 0: + currPK = inHandshakeMessage[0] + else: + raise newException(NoiseHandshakeError, "Noise read e, expected a public key") + + # We check if current key is encrypted or not + # Note: by specification, ephemeral keys should always be unencrypted. But we support encrypted ones. + if currPK.flag == 0.uint8: + + # Unencrypted Public Key + # Sets re and calls MixHash(re.public_key). + hs.re = intoCurve25519Key(currPK.pk) + hs.ss.mixHash(hs.re) + + # The following is out of specification: we call decryptAndHash for encrypted ephemeral keys, similarly as happens for (encrypted) static keys + elif currPK.flag == 1.uint8: + + # Encrypted public key + # Decrypts re, sets re and calls MixHash(re.public_key). + hs.re = intoCurve25519Key(hs.ss.decryptAndHash(currPK.pk)) + + else: + raise newException(NoisePublicKeyError, "Noise read e, incorrect encryption flag for public key") + + # Noise specification: section 9.2 + # In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results + # in a call to MixHash(e.public_key). + # In a PSK handshake, all of these calls are followed by MixKey(e.public_key). + if "psk" in hs.handshakePattern.name: + hs.ss.mixKey(hs.re) + + # We delete processed public key + inHandshakeMessage.delete(0) + + # If user is writing the "e" token + elif writing: + trace "noise write e" + + # We generate a new ephemeral keypair + hs.e = genKeyPair(rng) + + # We update the state + hs.ss.mixHash(hs.e.publicKey) + + # Noise specification: section 9.2 + # In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results + # in a call to MixHash(e.public_key). + # In a PSK handshake, all of these calls are followed by MixKey(e.public_key). + if "psk" in hs.handshakePattern.name: + hs.ss.mixKey(hs.e.publicKey) + + # We add the ephemeral public key to the Waku payload + outHandshakeMessage.add toNoisePublicKey(getPublicKey(hs.e)) + + of T_s: + + # If user is reading the "s" token + if reading: + trace "noise read s" + + # We expect a static key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs) + if inHandshakeMessage.len > 0: + currPK = inHandshakeMessage[0] + else: + raise newException(NoiseHandshakeError, "Noise read s, expected a public key") + + # We check if current key is encrypted or not + if currPK.flag == 0.uint8: + + # Unencrypted Public Key + # Sets re and calls MixHash(re.public_key). + hs.rs = intoCurve25519Key(currPK.pk) + hs.ss.mixHash(hs.rs) + + elif currPK.flag == 1.uint8: + + # Encrypted public key + # Decrypts rs, sets rs and calls MixHash(rs.public_key). + hs.rs = intoCurve25519Key(hs.ss.decryptAndHash(currPK.pk)) + + else: + raise newException(NoisePublicKeyError, "Noise read s, incorrect encryption flag for public key") + + # We delete processed public key + inHandshakeMessage.delete(0) + + # If user is writing the "s" token + elif writing: + + trace "noise write s" + + # If the local static key is not set (the handshake state was not properly initialized), we raise an error + if hs.s == default(KeyPair): + raise newException(NoisePublicKeyError, "Static key not set") + + # We encrypt the public part of the static key in case a key is set in the Cipher State + # That is, encS may either be an encrypted or unencrypted static key. + let encS = hs.ss.encryptAndHash(hs.s.publicKey) + + # We add the (encrypted) static public key to the Waku payload + # Note that encS = (Enc(s) || tag) if encryption key is set, otherwise encS = s. + # We distinguish these two cases by checking length of encryption and we set the proper encryption flag + if encS.len > Curve25519Key.len: + outHandshakeMessage.add NoisePublicKey(flag: 1, pk: encS) + else: + outHandshakeMessage.add NoisePublicKey(flag: 0, pk: encS) + + of T_psk: + + # If user is reading the "psk" token + + trace "noise psk" + + # Calls MixKeyAndHash(psk) + hs.ss.mixKeyAndHash(hs.psk) + + of T_ee: + + # If user is reading the "ee" token + + trace "noise dh ee" + + # If local and/or remote ephemeral keys are not set, we raise an error + if hs.e == default(KeyPair) or hs.re == default(Curve25519Key): + raise newException(NoisePublicKeyError, "Local or remote ephemeral key not set") + + # Calls MixKey(DH(e, re)). + hs.ss.mixKey(dh(hs.e.privateKey, hs.re)) + + of T_es: + + # If user is reading the "es" token + + trace "noise dh es" + + # We check if keys are correctly set. + # If both present, we call MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re)) if responder. + if hs.initiator: + if hs.e == default(KeyPair) or hs.rs == default(Curve25519Key): + raise newException(NoisePublicKeyError, "Local or remote ephemeral/static key not set") + hs.ss.mixKey(dh(hs.e.privateKey, hs.rs)) + else: + if hs.re == default(Curve25519Key) or hs.s == default(KeyPair): + raise newException(NoisePublicKeyError, "Local or remote ephemeral/static key not set") + hs.ss.mixKey(dh(hs.s.privateKey, hs.re)) + + of T_se: + + # If user is reading the "se" token + + trace "noise dh se" + + # We check if keys are correctly set. + # If both present, call MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs)) if responder. + if hs.initiator: + if hs.s == default(KeyPair) or hs.re == default(Curve25519Key): + raise newException(NoiseMalformedHandshake, "Local or remote ephemeral/static key not set") + hs.ss.mixKey(dh(hs.s.privateKey, hs.re)) + else: + if hs.rs == default(Curve25519Key) or hs.e == default(KeyPair): + raise newException(NoiseMalformedHandshake, "Local or remote ephemeral/static key not set") + hs.ss.mixKey(dh(hs.e.privateKey, hs.rs)) + + of T_ss: + + # If user is reading the "ss" token + + trace "noise dh ss" + + # If local and/or remote static keys are not set, we raise an error + if hs.s == default(KeyPair) or hs.rs == default(Curve25519Key): + raise newException(NoiseMalformedHandshake, "Local or remote static key not set") + + # Calls MixKey(DH(s, rs)). + hs.ss.mixKey(dh(hs.s.privateKey, hs.rs)) + + return ok(outHandshakeMessage) + +################################# +## Procedures to progress handshakes between users +################################# + +# Initializes a Handshake State +proc initialize*(hsPattern: HandshakePattern, ephemeralKey: KeyPair = default(KeyPair), staticKey: KeyPair = default(KeyPair), prologue: seq[byte] = @[], psk: seq[byte] = @[], preMessagePKs: seq[NoisePublicKey] = @[], initiator: bool = false): HandshakeState + {.raises: [Defect, NoiseMalformedHandshake, NoiseHandshakeError, NoisePublicKeyError].} = + var hs = HandshakeState.init(hsPattern) + hs.ss.mixHash(prologue) + hs.e = ephemeralKey + hs.s = staticKey + hs.psk = psk + hs.msgPatternIdx = 0 + hs.initiator = initiator + # We process any eventual handshake pre-message pattern by processing pre-message public keys + processPreMessagePatternTokens(hs, preMessagePKs) + return hs + +# Advances 1 step in handshake +# Each user in a handshake alternates writing and reading of handshake messages. +# If the user is writing the handshake message, the transport message (if not empty) has to be passed to transportMessage and readPayloadV2 can be left to its default value +# It the user is reading the handshake message, the read payload v2 has to be passed to readPayloadV2 and the transportMessage can be left to its default values. +proc stepHandshake*(rng: var BrHmacDrbgContext, hs: var HandshakeState, readPayloadV2: PayloadV2 = default(PayloadV2), transportMessage: seq[byte] = @[]): Result[HandshakeStepResult, cstring] + {.raises: [Defect, NoiseHandshakeError, NoiseMalformedHandshake, NoisePublicKeyError, NoiseDecryptTagError, NoiseNonceMaxError].} = + + var hsStepResult: HandshakeStepResult + + # If there are no more message patterns left for processing + # we return an empty HandshakeStepResult + if hs.msgPatternIdx > uint8(hs.handshakePattern.messagePatterns.len - 1): + debug "stepHandshake called more times than the number of message patterns present in handshake" + return ok(hsStepResult) + + # We process the next handshake message pattern + + # We get if the user is reading or writing the input handshake message + let direction = hs.handshakePattern.messagePatterns[hs.msgPatternIdx].direction + var (reading, writing) = getReadingWritingState(hs, direction) + + # If we write an answer at this handshake step + if writing: + # We initialize a payload v2 and we set proper protocol ID (if supported) + try: + hsStepResult.payload2.protocolId = PayloadV2ProtocolIDs[hs.handshakePattern.name] + except: + raise newException(NoiseMalformedHandshake, "Handshake Pattern not supported") + + # We set the handshake and transport message + hsStepResult.payload2.handshakeMessage = processMessagePatternTokens(rng, hs).get() + hsStepResult.payload2.transportMessage = processMessagePatternPayload(hs, transportMessage) + + # If we read an answer during this handshake step + elif reading: + # We process the read public keys and (eventually decrypt) the read transport message + let + readHandshakeMessage = readPayloadV2.handshakeMessage + readTransportMessage = readPayloadV2.transportMessage + + # Since we only read, nothing meanigful (i.e. public keys) is returned + discard processMessagePatternTokens(rng, hs, readHandshakeMessage) + # We retrieve and store the (decrypted) received transport message + hsStepResult.transportMessage = processMessagePatternPayload(hs, readTransportMessage) + + else: + raise newException(NoiseHandshakeError, "Handshake Error: neither writing or reading user") + + # We increase the handshake state message pattern index to progress to next step + hs.msgPatternIdx += 1 + + return ok(hsStepResult) + +# Finalizes the handshake by calling Split and assigning the proper Cipher States to users +proc finalizeHandshake*(hs: var HandshakeState): HandshakeResult = + + var hsResult: HandshakeResult + + ## Noise specification, Section 5: + ## Processing the final handshake message returns two CipherState objects, + ## the first for encrypting transport messages from initiator to responder, + ## and the second for messages in the other direction. + + # We call Split() + let (cs1, cs2) = hs.ss.split() + + # We assign the proper Cipher States + if hs.initiator: + hsResult.csOutbound = cs1 + hsResult.csInbound = cs2 + else: + hsResult.csOutbound = cs2 + hsResult.csInbound = cs1 + + # We store the optional fields rs and h + hsResult.rs = hs.rs + hsResult.h = hs.ss.h + + return hsResult + +################################# +# After-handshake procedures +################################# + +## Noise specification, Section 5: +## Transport messages are then encrypted and decrypted by calling EncryptWithAd() +## and DecryptWithAd() on the relevant CipherState with zero-length associated data. +## If DecryptWithAd() signals an error due to DECRYPT() failure, then the input message is discarded. +## The application may choose to delete the CipherState and terminate the session on such an error, +## or may continue to attempt communications. If EncryptWithAd() or DecryptWithAd() signal an error +## due to nonce exhaustion, then the application must delete the CipherState and terminate the session. + +# Writes an encrypted message using the proper Cipher State +proc writeMessage*(hsr: var HandshakeResult, transportMessage: seq[byte]): PayloadV2 + {.raises: [Defect, NoiseNonceMaxError].} = + + var payload2: PayloadV2 + + # According to 35/WAKU2-NOISE RFC, no Handshake protocol information is sent when exchanging messages + # This correspond to setting protocol-id to 0 + payload2.protocolId = 0.uint8 + # Encryption is done with zero-length associated data as per specification + payload2.transportMessage = encryptWithAd(hsr.csOutbound, @[], transportMessage) + + return payload2 + +# Reads an encrypted message using the proper Cipher State +# Associated data ad for encryption is optional, since the latter is out of scope for Noise +proc readMessage*(hsr: var HandshakeResult, readPayload2: PayloadV2): Result[seq[byte], cstring] + {.raises: [Defect, NoiseDecryptTagError, NoiseNonceMaxError].} = + + # The output decrypted message + var message: seq[byte] + + # According to 35/WAKU2-NOISE RFC, no Handshake protocol information is sent when exchanging messages + if readPayload2.protocolId == 0.uint8: + + # On application level we decide to discard messages which fail decryption, without raising an error + # (this because an attacker may flood the content topic on which messages are exchanged) + try: + # Decryption is done with zero-length associated data as per specification + message = decryptWithAd(hsr.csInbound, @[], readPayload2.transportMessage) + except NoiseDecryptTagError: + debug "A read message failed decryption. Returning empty message as plaintext." + message = @[] + + return ok(message) \ No newline at end of file