mirror of
https://github.com/waku-org/nwaku.git
synced 2025-02-15 00:16:48 +00:00
deploy: c259f4cbaae9ba8ae0dc424aee80887e0fe2693d
This commit is contained in:
parent
3c7eb36a2c
commit
20dbd3ff6c
@ -209,7 +209,6 @@ procSuite "Waku Noise":
|
|||||||
getNonce(cipherState) == nonce + 1
|
getNonce(cipherState) == nonce + 1
|
||||||
plaintext == decrypted
|
plaintext == decrypted
|
||||||
|
|
||||||
|
|
||||||
# If a Cipher State has no key set, encryptWithAd should return the plaintext without increasing the nonce
|
# If a Cipher State has no key set, encryptWithAd should return the plaintext without increasing the nonce
|
||||||
setCipherStateKey(cipherState, EmptyKey)
|
setCipherStateKey(cipherState, EmptyKey)
|
||||||
nonce = getNonce(cipherState)
|
nonce = getNonce(cipherState)
|
||||||
@ -321,7 +320,7 @@ procSuite "Waku Noise":
|
|||||||
########################################
|
########################################
|
||||||
|
|
||||||
# We generate random input key material and we execute mixKey
|
# 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(symmetricState, inputKeyMaterial)
|
||||||
|
|
||||||
# mixKey changes the Symmetric State's chaining key and encryption key of the embedded Cipher State
|
# 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
|
# We generate random input key material and we execute mixKeyAndHash
|
||||||
inputKeyMaterial = randomChaChaPolyKey(rng[])
|
inputKeyMaterial = randomSeqByte(rng[], rand(1..128))
|
||||||
mixKeyAndHash(symmetricState, inputKeyMaterial)
|
mixKeyAndHash(symmetricState, inputKeyMaterial)
|
||||||
|
|
||||||
# mixKeyAndHash executes a mixKey and a mixHash using the input key material
|
# mixKeyAndHash executes a mixKey and a mixHash using the input key material
|
||||||
@ -413,3 +412,450 @@ procSuite "Waku Noise":
|
|||||||
getNonce(cs1) == 0.uint64
|
getNonce(cs1) == 0.uint64
|
||||||
getNonce(cs2) == 0.uint64
|
getNonce(cs2) == 0.uint64
|
||||||
getKey(cs1) != getKey(cs2)
|
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
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# libtool - Provide generalized library-building support services.
|
# libtool - Provide generalized library-building support services.
|
||||||
# Generated automatically by config.status (libbacktrace) version-unused
|
# 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.
|
# NOTE: Changes made to this file will be lost: look at ltmain.sh.
|
||||||
#
|
#
|
||||||
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
|
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
|
||||||
|
@ -152,13 +152,21 @@ type
|
|||||||
msgPatternIdx: uint8
|
msgPatternIdx: uint8
|
||||||
psk: seq[byte]
|
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
|
# When a handshake is complete, the HandhshakeResult will contain the two
|
||||||
# Cipher States used to encrypt/decrypt outbound/inbound messages
|
# 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.).
|
# 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
|
# However, are not required by Noise specifications and are thus optional
|
||||||
HandshakeResult = object
|
HandshakeResult* = object
|
||||||
csInbound: CipherState
|
|
||||||
csOutbound: CipherState
|
csOutbound: CipherState
|
||||||
|
csInbound: CipherState
|
||||||
# Optional fields:
|
# Optional fields:
|
||||||
rs: EllipticCurveKey
|
rs: EllipticCurveKey
|
||||||
h: MDigest[256]
|
h: MDigest[256]
|
||||||
@ -195,7 +203,7 @@ type
|
|||||||
const
|
const
|
||||||
|
|
||||||
# The empty pre message patterns
|
# 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
|
# Supported Noise handshake patterns as defined in https://rfc.vac.dev/spec/35/#specification
|
||||||
NoiseHandshakePatterns* = {
|
NoiseHandshakePatterns* = {
|
||||||
@ -215,14 +223,14 @@ const
|
|||||||
),
|
),
|
||||||
|
|
||||||
"XX": HandshakePattern(name: "Noise_XX_25519_ChaChaPoly_SHA256",
|
"XX": HandshakePattern(name: "Noise_XX_25519_ChaChaPoly_SHA256",
|
||||||
preMessagePatterns: EmptyPreMessagePattern,
|
preMessagePatterns: EmptyPreMessage,
|
||||||
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e]),
|
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e]),
|
||||||
MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_s, T_es]),
|
MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_s, T_es]),
|
||||||
MessagePattern(direction: D_r, tokens: @[T_s, T_se])]
|
MessagePattern(direction: D_r, tokens: @[T_s, T_se])]
|
||||||
),
|
),
|
||||||
|
|
||||||
"XXpsk0": HandshakePattern(name: "Noise_XXpsk0_25519_ChaChaPoly_SHA256",
|
"XXpsk0": HandshakePattern(name: "Noise_XXpsk0_25519_ChaChaPoly_SHA256",
|
||||||
preMessagePatterns: EmptyPreMessagePattern,
|
preMessagePatterns: EmptyPreMessage,
|
||||||
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_psk, T_e]),
|
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_l, tokens: @[T_e, T_ee, T_s, T_es]),
|
||||||
MessagePattern(direction: D_r, tokens: @[T_s, T_se])]
|
MessagePattern(direction: D_r, tokens: @[T_s, T_se])]
|
||||||
@ -278,8 +286,8 @@ proc print*(self: HandshakePattern)
|
|||||||
if self.name != "":
|
if self.name != "":
|
||||||
stdout.write self.name, ":\n"
|
stdout.write self.name, ":\n"
|
||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
#We iterate over pre message patterns, if any
|
# We iterate over pre message patterns, if any
|
||||||
if self.preMessagePatterns != EmptyPreMessagePattern:
|
if self.preMessagePatterns != EmptyPreMessage:
|
||||||
for pattern in self.preMessagePatterns:
|
for pattern in self.preMessagePatterns:
|
||||||
stdout.write " ", pattern.direction
|
stdout.write " ", pattern.direction
|
||||||
var first = true
|
var first = true
|
||||||
@ -293,7 +301,7 @@ proc print*(self: HandshakePattern)
|
|||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
stdout.write " ...\n"
|
stdout.write " ...\n"
|
||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
#We iterate over message patterns
|
# We iterate over message patterns
|
||||||
for pattern in self.messagePatterns:
|
for pattern in self.messagePatterns:
|
||||||
stdout.write " ", pattern.direction
|
stdout.write " ", pattern.direction
|
||||||
var first = true
|
var first = true
|
||||||
@ -339,7 +347,6 @@ proc dh*(private: EllipticCurveKey, public: EllipticCurveKey): EllipticCurveKey
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
# Noise state machine primitives
|
# 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
|
# MixKey as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||||
# Updates a Symmetric state chaining key and symmetric state
|
# 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
|
# We derive two keys using HKDF
|
||||||
var tempKeys: array[2, ChaChaPolyKey]
|
var tempKeys: array[2, ChaChaPolyKey]
|
||||||
sha256.hkdf(ss.ck, inputKeyMaterial, [], tempKeys)
|
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,
|
# 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
|
# we copy the plaintext into the ciphertext variable and we pass the latter to encrypt
|
||||||
ciphertext.data.add plaintext
|
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),
|
# 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)
|
# the plaintext (overwritten to ciphertext) (data), the associated data (ad)
|
||||||
ChaChaPoly.encrypt(state.k, state.nonce, ciphertext.tag, ciphertext.data, state.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),
|
# 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)
|
# the ciphertext (overwritten to plaintext) (data), the associated data (ad)
|
||||||
ChaChaPoly.decrypt(state.k, state.nonce, tagOut, plaintext, state.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
|
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
|
# We check if the authorization tag computed while decrypting is the same as the input tag
|
||||||
if tagIn != tagOut:
|
if tagIn != tagOut:
|
||||||
@ -687,11 +694,11 @@ proc randomChaChaPolyCipherState*(rng: var BrHmacDrbgContext): ChaChaPolyCipherS
|
|||||||
proc `==`(k1, k2: NoisePublicKey): bool =
|
proc `==`(k1, k2: NoisePublicKey): bool =
|
||||||
return (k1.flag == k2.flag) and (k1.pk == k2.pk)
|
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)
|
# Converts a public Elliptic Curve key to an unencrypted Noise public key
|
||||||
proc keyPairToNoisePublicKey*(keyPair: KeyPair): NoisePublicKey =
|
proc toNoisePublicKey*(publicKey: EllipticCurveKey): NoisePublicKey =
|
||||||
var noisePublicKey: NoisePublicKey
|
var noisePublicKey: NoisePublicKey
|
||||||
noisePublicKey.flag = 0
|
noisePublicKey.flag = 0
|
||||||
noisePublicKey.pk = getBytes(keyPair.publicKey)
|
noisePublicKey.pk = getBytes(publicKey)
|
||||||
return noisePublicKey
|
return noisePublicKey
|
||||||
|
|
||||||
# Generates a random Noise public key
|
# 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/
|
# 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] =
|
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
|
var
|
||||||
# According to https://rfc.vac.dev/spec/35/, the maximum size for the handshake message is 256 bytes, that is
|
# 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)
|
# 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
|
var handshakeMessageLen = payload[i].uint64
|
||||||
if handshakeMessageLen > uint8.high.uint64:
|
if handshakeMessageLen > uint8.high.uint64:
|
||||||
debug "Payload malformed: too many public keys contained in the handshake message"
|
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")
|
return err("Too many public keys in handshake message")
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
@ -917,4 +923,569 @@ proc deserializePayloadV2*(payload: seq[byte]): Result[PayloadV2, cstring]
|
|||||||
payload2.transportMessage = payload[i..i+transportMessageLen-1]
|
payload2.transportMessage = payload[i..i+transportMessageLen-1]
|
||||||
i += transportMessageLen
|
i += transportMessageLen
|
||||||
|
|
||||||
return ok(payload2)
|
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)
|
Loading…
x
Reference in New Issue
Block a user