refactor(noise): add comments, restyle code

This commit is contained in:
s1fr0 2022-04-06 23:43:15 +02:00
parent a56f3c22a4
commit 3b5b4aa0e9
No known key found for this signature in database
GPG Key ID: 2C041D60117BFF46
3 changed files with 179 additions and 137 deletions

View File

@ -4,7 +4,9 @@ import
testutils/unittests,
std/random,
stew/byteutils,
../../waku/v2/node/waku_payload,
../../waku/v2/protocol/waku_noise/noise,
../../waku/v2/protocol/waku_message,
../test_helpers
procSuite "Waku Noise":
@ -43,7 +45,7 @@ procSuite "Waku Noise":
check:
plaintext.toBytes() == decryptedCiphertext
test "Encrypt and decrypt Noise public keys":
test "Noise public keys: encrypt and decrypt a public key":
let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -55,7 +57,7 @@ procSuite "Waku Noise":
check:
noisePublicKey == decryptedPk
test "Decrypt unencrypted public key":
test "Noise public keys: decrypt an unencrypted public key":
let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -66,7 +68,7 @@ procSuite "Waku Noise":
check:
noisePublicKey == decryptedPk
test "Encrypt encrypted public key":
test "Noise public keys: encrypt an encrypted public key":
let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -78,7 +80,7 @@ procSuite "Waku Noise":
check:
encryptedPk == encryptedPk2
test "Encrypt, decrypt and decrypt public key":
test "Noise public keys: encrypt, decrypt and decrypt a public key":
let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -91,7 +93,7 @@ procSuite "Waku Noise":
check:
decryptedPk == decryptedPk2
test "Serialize and deserialize unencrypted public key":
test "Noise public keys: serialize and deserialize an unencrypted public key":
let
noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -101,7 +103,7 @@ procSuite "Waku Noise":
check:
noisePublicKey == deserializedNoisePublicKey
test "Encrypt, serialize, deserialize and decrypt public key":
test "Noise public keys: encrypt, serialize, deserialize and decrypt a public key":
let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[])
@ -115,41 +117,44 @@ procSuite "Waku Noise":
check:
noisePublicKey == decryptedPk
test "Encode/decode PayloadV2 to byte sequence":
let
test "PayloadV2: serialize/deserialize PayloadV2 to byte sequence":
let
payload2: PayloadV2 = randomPayloadV2(rng[])
serializedPayload = serializePayloadV2(payload2)
check:
serializedPayload.isOk()
let deserializedPayload = deserializePayloadV2(serializedPayload.get())
check:
deserializedPayload.isOk()
payload2 == deserializedPayload.get()
test "PayloadV2: Encode/Decode a Waku Message (version 2) to a PayloadV2":
# We encode to a WakuMessage a random PayloadV2
let
payload2 = randomPayloadV2(rng[])
encoded_payload = encodeV2(payload2)
decoded_payload = decodeV2(encoded_payload.get())
msg = encodePayloadV2(payload2)
check:
payload2 == decoded_payload.get()
check:
msg.isOk()
# We create ProtoBuffer from WakuMessage
let pb = msg.get().encode()
test "Encode/Decode Waku2 payload (version 2) - ChaChaPoly Keyinfo":
# Encoding
let
version = 2'u32
payload = randomPayloadV2(rng[])
encodedPayload = encodePayloadV2(payload)
# We decode the WakuMessage from the ProtoBuffer
let msgFromPb = WakuMessage.init(pb.buffer)
check:
msgFromPb.isOk()
check encodedPayload.isOk()
let
msg = WakuMessage(payload: encodedPayload.get(), version: version)
pb = msg.encode()
# Decoding
let msgDecoded = WakuMessage.init(pb.buffer)
check msgDecoded.isOk()
let
cipherState = randomChaChaPolyCipherState(rng[])
keyInfo = KeyInfo(kind: ChaChaPolyEncryption, cs: cipherState)
decoded = decodePayloadV2(msgDecoded.get(), keyInfo)
let decoded = decodePayloadV2(msgFromPb.get())
check:
decoded.isOk()
decoded.get() == payload
#TODO: add encrypt payload with ChaChaPoly
payload2 == decoded.get()

View File

@ -7,16 +7,12 @@ import
../protocol/waku_message,
../protocol/waku_noise/noise
import libp2p/crypto/[chacha20poly1305, curve25519]
export whisper_types, keys, options
type
KeyKind* = enum
Symmetric
Asymmetric
ChaChaPolyEncryption
None
KeyInfo* = object
@ -25,8 +21,6 @@ type
symKey*: SymKey
of Asymmetric:
privKey*: PrivateKey
of ChaChaPolyEncryption:
cs*: ChaChaPolyCipherState
of None:
discard
@ -86,27 +80,34 @@ proc encode*(payload: Payload, version: uint32, rng: var BrHmacDrbgContext):
return err("Unsupported WakuMessage version")
proc decodePayloadV2*(message: WakuMessage, keyInfo: KeyInfo):
WakuResult[PayloadV2] =
# Decodes a WakuMessage to a PayloadV2
# Currently, this is just a wrapper over deserializePayloadV2 and encryption/decryption is done on top (no KeyInfo)
proc decodePayloadV2*(message: WakuMessage): WakuResult[PayloadV2]
{.raises: [Defect, NoiseMalformedHandshake, NoisePublicKeyError].} =
# We check message version (only 2 is supported in this proc)
case message.version
of 2:
case keyInfo.kind
of ChaChaPolyEncryption:
let decoded = decodeV2(message.payload)#, keyInfo.cs)
if decoded.isSome():
return ok(decoded.get())
else:
return err("Couldn't decrypt using ChaChaPoly Cipher State")
# We attempt to decode the WakuMessage payload
let deserializedPayload2 = deserializePayloadV2(message.payload)
if deserializedPayload2.isOk():
return ok(deserializedPayload2.get())
else:
discard
return err("Failed to decode WakuMessage")
else:
return err("Key info doesn't match v2 payloads")
return err("Wrong message version while decoding payload")
proc encodePayloadV2*(payload: PayloadV2):
WakuResult[seq[byte]] =
let encoded = encodeV2(payload)
if encoded.isSome():
return ok(encoded.get())
else:
return err("Couldn't encode the payload")
# Encodes a PayloadV2 to a WakuMessage
# Currently, this is just a wrapper over serializePayloadV2 and encryption/decryption is done on top (no KeyInfo)
proc encodePayloadV2*(payload2: PayloadV2): WakuResult[WakuMessage]
{.raises: [Defect, NoiseMalformedHandshake, NoisePublicKeyError].} =
# We attempt to encode the PayloadV2
let serializedPayload2 = serializePayloadV2(payload2)
if not serializedPayload2.isOk():
return err("Failed to encode PayloadV2")
# If successful, we create and return a WakuMessage
let msg = WakuMessage(payload: serializedPayload2.get(), version: 2)
return ok(msg)

View File

@ -7,18 +7,14 @@
{.push raises: [Defect].}
import std/[oids, options, tables]
import std/[options, tables]
import chronos
import chronicles
import bearssl
import strutils
import stew/[endians2]
import stew/[results, endians2]
import nimcrypto/[utils, sha2, hmac]
import libp2p/stream/[connection]
import libp2p/peerid
import libp2p/peerinfo
import libp2p/protobuf/minprotobuf
import libp2p/utility
import libp2p/errors
import libp2p/crypto/[crypto, chacha20poly1305, curve25519]
@ -51,6 +47,7 @@ type
# This follows https://rfc.vac.dev/spec/35/#public-keys-serialization
# pk contains the X coordinate of the public key, if unencrypted (this implies flag = 0)
# or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1)
# Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves
NoisePublicKey* = object
flag: uint8
pk: seq[byte]
@ -66,6 +63,15 @@ type
nonce: ChaChaPolyNonce
ad: seq[byte]
# PayloadV2 defines an object for Waku payloads with version 2 as in
# https://rfc.vac.dev/spec/35/#public-keys-serialization
# It contains a protocol ID field, the handshake message (for Noise handshakes) and
# a transport message (for Noise handshakes and ChaChaPoly encryptions)
PayloadV2* = object
protocolId: uint8
handshakeMessage: seq[NoisePublicKey]
transportMessage: seq[byte]
# Some useful error types
NoiseError* = object of LPError
NoiseHandshakeError* = object of NoiseError
@ -86,7 +92,7 @@ proc randomSeqByte*(rng: var BrHmacDrbgContext, size: int): seq[byte] =
brHmacDrbgGenerate(rng, output)
return output
# Generate random Curve25519 (public, private) key pairs
# Generate random (public, private) Elliptic Curve key pairs
proc genKeyPair*(rng: var BrHmacDrbgContext): KeyPair =
var keyPair: KeyPair
keyPair.privateKey = EllipticCurveKey.random(rng)
@ -253,108 +259,138 @@ proc decryptNoisePublicKey*(cs: ChaChaPolyCipherState, noisePublicKey: NoisePubl
#################################################################
# Payload functions
type
PayloadV2* = object
protocol_id: uint8
handshake_message: seq[NoisePublicKey]
transport_message: seq[byte]
# Payload encoding/decoding procedures
# Checks equality between two PayloadsV2 objects
proc `==`(p1, p2: PayloadV2): bool =
result = (p1.protocol_id == p2.protocol_id) and
(p1.handshake_message == p2.handshake_message) and
(p1.transport_message == p2.transport_message)
return (p1.protocolId == p2.protocolId) and
(p1.handshakeMessage == p2.handshakeMessage) and
(p1.transportMessage == p2.transportMessage)
# Generates a random PayloadV2
proc randomPayloadV2*(rng: var BrHmacDrbgContext): PayloadV2 =
var protocol_id = newSeq[byte](1)
brHmacDrbgGenerate(rng, protocol_id)
result.protocol_id = protocol_id[0].uint8
result.handshake_message = @[genNoisePublicKey(rng), genNoisePublicKey(rng), genNoisePublicKey(rng)]
result.transport_message = newSeq[byte](128)
brHmacDrbgGenerate(rng, result.transport_message)
var payload2: PayloadV2
# To generate a random protocol id, we generate a random 1-byte long sequence, and we convert the first element to uint8
payload2.protocolId = randomSeqByte(rng, 1)[0].uint8
# We set the handshake message to three unencrypted random Noise Public Keys
payload2.handshakeMessage = @[genNoisePublicKey(rng), genNoisePublicKey(rng), genNoisePublicKey(rng)]
# We set the transport message to a random 128-bytes long sequence
payload2.transportMessage = randomSeqByte(rng, 128)
return payload2
proc encodeV2*(self: PayloadV2): Option[seq[byte]] =
# Serializes a PayloadV2 object to a byte sequences according to https://rfc.vac.dev/spec/35/.
# The results can be passed to the payload field of a WakuMessage https://rfc.vac.dev/spec/14/
proc serializePayloadV2*(self: PayloadV2): Result[seq[byte], cstring]
{.raises: [Defect, NoiseMalformedHandshake, NoisePublicKeyError].} =
#We collect public keys contained in the handshake message
var
ser_handshake_message_len: int = 0
ser_handshake_message = newSeqOfCap[byte](256)
ser_pk: seq[byte]
for pk in self.handshake_message:
ser_pk = serializeNoisePublicKey(pk)
ser_handshake_message_len += ser_pk.len
ser_handshake_message.add ser_pk
# 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)
# However, to ease public keys length addition operation, we declare it as int and later cast to uit8
serializedHandshakeMessageLen: int = 0
# This variables will store the concatenation of the serializations of all public keys in the handshake message
serializedHandshakeMessage = newSeqOfCap[byte](256)
# A variable to store the currently processed public key serialization
serializedPk: seq[byte]
# For each public key in the handshake message
for pk in self.handshakeMessage:
# We serialize the public key
serializedPk = serializeNoisePublicKey(pk)
# We sum its serialized length to the total
serializedHandshakeMessageLen += serializedPk.len
# We add its serialization to the concatenation of all serialized public keys in the handshake message
serializedHandshakeMessage.add serializedPk
# If we are processing more than 256 byte, we return an error
if serializedHandshakeMessageLen > 256:
debug "PayloadV2 malformed: too many public keys contained in the handshake message"
raise newException(NoiseMalformedHandshake, "Too many public keys in handshake message")
# We get the transport message byte length
let transportMessageLen = self.transportMessage.len
#RFC: handshake-message-len is 1 byte
if ser_handshake_message_len > 256:
debug "Payload malformed: too many public keys contained in the handshake message"
return none(seq[byte])
let transport_message_len = self.transport_message.len
#let transport_message_len_len = ceil(log(transport_message_len, 8)).int
var payload = newSeqOfCap[byte](1 + #self.protocol_id.len +
1 + #ser_handshake_message_len
ser_handshake_message_len +
8 + #transport_message_len
transport_message_len #self.transport_message
# The output payload as in https://rfc.vac.dev/spec/35/. We concatenate all the PayloadV2 as
# payload = ( protocolId || serializedHandshakeMessageLen || serializedHandshakeMessage || transportMessageLen || transportMessage)
# We declare it as a byte sequence of length accordingly to the PayloadV2 information read
var payload = newSeqOfCap[byte](1 + # 1 byte for protocol ID
1 + # 1 byte for length of serializedHandshakeMessage field
serializedHandshakeMessageLen + # serializedHandshakeMessageLen bytes for serializedHandshakeMessage
8 + # 8 bytes for transportMessageLen
transportMessageLen # transportMessageLen bytes for transportMessage
)
payload.add self.protocol_id.byte
payload.add ser_handshake_message_len.byte
payload.add ser_handshake_message
payload.add toBytesLE(transport_message_len.uint64)
payload.add self.transport_message
# We concatenate all the data
# The protocol ID (1 byte) and handshake message length (1 byte) can be directly casted to byte to allow direct copy to the payload byte sequence
payload.add self.protocolId.byte
payload.add serializedHandshakeMessageLen.byte
payload.add serializedHandshakeMessage
# The transport message length is converted from uint64 to bytes in Little-Endian
payload.add toBytesLE(transportMessageLen.uint64)
payload.add self.transportMessage
return some(payload)
return ok(payload)
#Decode Noise handshake payload
proc decodeV2*(payload: seq[byte]): Option[PayloadV2] =
var res: PayloadV2
# Deserializes a byte sequence to a PayloadV2 object according to https://rfc.vac.dev/spec/35/.
proc deserializePayloadV2*(payload: seq[byte]): Result[PayloadV2, cstring]
{.raises: [Defect, NoiseMalformedHandshake, NoisePublicKeyError].} =
# The output PayloadV2
var payload2: PayloadV2
# i is the read input buffer position index
var i: uint64 = 0
res.protocol_id = payload[i].uint8
# We start reading the Protocol ID
payload2.protocolId = payload[i].uint8
i+=1
var handshake_message_len = payload[i].uint64
# We read the Handshake Message lenght (1 byte)
var handshakeMessageLen = payload[i].uint64
if handshakeMessageLen > 256:
debug "Payload malformed: too many public keys contained in the handshake message"
raise newException(NoiseMalformedHandshake, "Too many public keys in handshake message")
i+=1
var handshake_message: seq[NoisePublicKey]
var
# We now read for handshakeMessageLen bytes the buffer and we deserialize each (encrypted/unencrypted) public key read
var
# In handshakeMessage we accumulates the deserialized Noise Public keys read
handshakeMessage: seq[NoisePublicKey]
flag: byte
pk_len: uint64
pkLen: uint64
written: uint64 = 0
while written != handshake_message_len:
#Note that flag can be used to add support to multiple Elliptic Curve arithmetics..
# We read the buffer until handshakeMessageLen are read
while written != handshakeMessageLen:
# We obtain the current Noise Public key encryption flag
flag = payload[i]
# If the key is unencrypted, we only read the X coordinate of the EC public key and we deserialize into a Noise Public Key
if flag == 0:
pk_len = 1 + Curve25519Key.len
handshake_message.add intoNoisePublicKey(payload[i..<i+pk_len])
i += pk_len
written += pk_len
if flag == 1:
pk_len = 1 + Curve25519Key.len + ChaChaPolyTag.len
handshake_message.add intoNoisePublicKey(payload[i..<i+pk_len])
i += pk_len
written += pk_len
pkLen = 1 + EllipticCurveKey.len
handshake_message.add intoNoisePublicKey(payload[i..<i+pkLen])
i += pkLen
written += pkLen
# If the key is encrypted, we only read the encrypted X coordinate and the authorization tag, and we deserialize into a Noise Public Key
elif flag == 1:
pkLen = 1 + EllipticCurveKey.len + ChaChaPolyTag.len
handshakeMessage.add intoNoisePublicKey(payload[i..<i+pkLen])
i += pkLen
written += pkLen
else:
raise newException(NoisePublicKeyError, "Invalid flag for Noise public key")
res.handshake_message = handshake_message
# We save in the output PayloadV2 the read handshake message
payload2.handshakeMessage = handshakeMessage
let transport_message_len = fromBytesLE(uint64, payload[i..(i+8-1)])
# We read the transport message length (8 bytes) and we convert to uint64 in Little Endian
let transportMessageLen = fromBytesLE(uint64, payload[i..(i+8-1)])
i+=8
res.transport_message = payload[i..i+transport_message_len-1]
i+=transport_message_len
# We read the transport message (handshakeMessage bytes)
payload2.transportMessage = payload[i..i+transportMessageLen-1]
i+=transportMessageLen
return some(res)
return ok(payload2)