nwaku/waku/waku_noise/noise_types.nim

279 lines
12 KiB
Nim

# Waku Noise Protocols for Waku Payload Encryption
## See spec for more details:
## https://github.com/vacp2p/rfc/tree/master/content/docs/rfcs/35
##
## Implementation partially inspired by noise-libp2p:
## https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/noise.nim
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import std/[options, tables]
import chronos
import chronicles
import bearssl
import nimcrypto/[sha2, hmac]
import libp2p/errors
import libp2p/crypto/[crypto, chacha20poly1305, curve25519]
logScope:
topics = "waku noise"
#################################################################
# Constants and data structures
const
# EmptyKey represents a non-initialized ChaChaPolyKey
EmptyKey* = default(ChaChaPolyKey)
# The maximum ChaChaPoly allowed nonce in Noise Handshakes
NonceMax* = uint64.high - 1
# The padding blocksize of a transport message
NoisePaddingBlockSize* = 248
# The default length of a message nametag
MessageNametagLength* = 16
# The default length of the secret to generate Inbound/Outbound nametags buffer
MessageNametagSecretLength* = 32
# The default size of an Inbound/outbound MessageNametagBuffer
MessageNametagBufferSize* = 50
type
#################################
# Elliptic Curve arithemtic
#################################
# Default underlying elliptic curve arithmetic (useful for switching to multiple ECs)
# Current default is Curve25519
EllipticCurve* = Curve25519
EllipticCurveKey* = Curve25519Key
# An EllipticCurveKey (public, private) key pair
KeyPair* = object
privateKey*: EllipticCurveKey
publicKey*: EllipticCurveKey
#################################
# Noise Public Keys
#################################
# A Noise public key is a public key exchanged during Noise handshakes (no private part)
# 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]
#################################
# ChaChaPoly Encryption
#################################
# A ChaChaPoly ciphertext (data) + authorization tag (tag)
ChaChaPolyCiphertext* = object
data*: seq[byte]
tag*: ChaChaPolyTag
# A ChaChaPoly Cipher State containing key (k), nonce (nonce) and associated data (ad)
ChaChaPolyCipherState* = object
k*: ChaChaPolyKey
nonce*: ChaChaPolyNonce
ad*: seq[byte]
#################################
# Noise handshake patterns
#################################
# The Noise tokens appearing in Noise (pre)message patterns
# as in http://www.noiseprotocol.org/noise.html#handshake-pattern-basics
NoiseTokens* = enum
T_e = "e"
T_s = "s"
T_es = "es"
T_ee = "ee"
T_se = "se"
T_ss = "ss"
T_psk = "psk"
# The direction of a (pre)message pattern in canonical form (i.e. Alice-initiated form)
# as in http://www.noiseprotocol.org/noise.html#alice-and-bob
MessageDirection* = enum
D_r = "->"
D_l = "<-"
# The pre message pattern consisting of a message direction and some Noise tokens, if any.
# (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics)
PreMessagePattern* = object
direction*: MessageDirection
tokens*: seq[NoiseTokens]
# The message pattern consisting of a message direction and some Noise tokens
# All Noise tokens are allowed
MessagePattern* = object
direction*: MessageDirection
tokens*: seq[NoiseTokens]
# The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
HandshakePattern* = object
name*: string
preMessagePatterns*: seq[PreMessagePattern]
messagePatterns*: seq[MessagePattern]
#################################
# Noise state machine
#################################
# The Cipher State as in https://noiseprotocol.org/noise.html#the-cipherstate-object
# Contains an encryption key k and a nonce n (used in Noise as a counter)
CipherState* = object
k*: ChaChaPolyKey
n*: uint64
# The Symmetric State as in https://noiseprotocol.org/noise.html#the-symmetricstate-object
# Contains a Cipher State cs, the chaining key ck and the handshake hash value h
SymmetricState* = object
cs*: CipherState
ck*: ChaChaPolyKey
h*: MDigest[256]
# The Handshake State as in https://noiseprotocol.org/noise.html#the-handshakestate-object
# Contains
# - the local and remote ephemeral/static keys e,s,re,rs (if any)
# - the initiator flag (true if the user creating the state is the handshake initiator, false otherwise)
# - the handshakePattern (containing the handshake protocol name, and (pre)message patterns)
# This object is futher extended from specifications by storing:
# - a message pattern index msgPatternIdx indicating the next handshake message pattern to process
# - the user's preshared psk, if any
HandshakeState* = object
s*: KeyPair
e*: KeyPair
rs*: EllipticCurveKey
re*: EllipticCurveKey
ss*: SymmetricState
initiator*: bool
handshakePattern*: HandshakePattern
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
csOutbound*: CipherState
csInbound*: CipherState
# Optional fields:
nametagsInbound*: MessageNametagBuffer
nametagsOutbound*: MessageNametagBuffer
rs*: EllipticCurveKey
h*: MDigest[256]
#################################
# Waku Payload V2
#################################
# PayloadV2 defines an object for Waku payloads with version 2 as in
# https://rfc.vac.dev/spec/35/#public-keys-serialization
# It contains a message nametag, protocol ID field, the handshake message (for Noise handshakes) and
# a transport message (for Noise handshakes and ChaChaPoly encryptions)
MessageNametag* = array[MessageNametagLength, byte]
MessageNametagBuffer* = object
buffer*: array[MessageNametagBufferSize, MessageNametag]
counter*: uint64
secret*: Option[array[MessageNametagSecretLength, byte]]
PayloadV2* = object
messageNametag*: MessageNametag
protocolId*: uint8
handshakeMessage*: seq[NoisePublicKey]
transportMessage*: seq[byte]
#################################
# Some useful error types
#################################
NoiseError* = object of LPError
NoiseHandshakeError* = object of NoiseError
NoiseEmptyChaChaPolyInput* = object of NoiseError
NoiseDecryptTagError* = object of NoiseError
NoiseNonceMaxError* = object of NoiseError
NoisePublicKeyError* = object of NoiseError
NoiseMalformedHandshake* = object of NoiseError
NoiseMessageNametagError* = object of NoiseError
NoiseSomeMessagesWereLost* = object of NoiseError
#################################
# Constants (supported protocols)
#################################
const
# The empty pre message patterns
EmptyPreMessage*: seq[PreMessagePattern] = @[]
# Supported Noise handshake patterns as defined in https://rfc.vac.dev/spec/35/#specification
NoiseHandshakePatterns* = {
"K1K1": HandshakePattern(name: "Noise_K1K1_25519_ChaChaPoly_SHA256",
preMessagePatterns: @[PreMessagePattern(direction: D_r, tokens: @[T_s]),
PreMessagePattern(direction: D_l, tokens: @[T_s])],
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e]),
MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_es]),
MessagePattern(direction: D_r, tokens: @[T_se])]
),
"XK1": HandshakePattern(name: "Noise_XK1_25519_ChaChaPoly_SHA256",
preMessagePatterns: @[PreMessagePattern(direction: D_l, tokens: @[T_s])],
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e]),
MessagePattern(direction: D_l, tokens: @[T_e, T_ee, T_es]),
MessagePattern(direction: D_r, tokens: @[T_s, T_se])]
),
"XX": HandshakePattern(name: "Noise_XX_25519_ChaChaPoly_SHA256",
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: 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])]
),
"WakuPairing": HandshakePattern(name: "Noise_WakuPairing_25519_ChaChaPoly_SHA256",
preMessagePatterns: @[PreMessagePattern(direction: D_l, tokens: @[T_e])],
messagePatterns: @[ MessagePattern(direction: D_r, tokens: @[T_e, T_ee]),
MessagePattern(direction: D_l, tokens: @[T_s, T_es]),
MessagePattern(direction: D_r, tokens: @[T_s, T_se, T_ss])]
)
}.toTable()
# Supported Protocol ID for PayloadV2 objects
# Protocol IDs are defined according to https://rfc.vac.dev/spec/35/#specification
PayloadV2ProtocolIDs* = {
"": 0.uint8,
"Noise_K1K1_25519_ChaChaPoly_SHA256": 10.uint8,
"Noise_XK1_25519_ChaChaPoly_SHA256": 11.uint8,
"Noise_XX_25519_ChaChaPoly_SHA256": 12.uint8,
"Noise_XXpsk0_25519_ChaChaPoly_SHA256": 13.uint8,
"Noise_WakuPairing_25519_ChaChaPoly_SHA256": 14.uint8,
"ChaChaPoly": 30.uint8
}.toTable()