2022-08-09 13:55:08 +00:00
package noise
import (
2022-12-14 16:22:48 +00:00
"bytes"
2022-08-09 13:55:08 +00:00
"errors"
"fmt"
2022-12-14 16:22:48 +00:00
"hash"
"io"
"math/big"
2022-08-09 13:55:08 +00:00
2022-10-27 15:23:20 +00:00
n "github.com/waku-org/noise"
2022-12-14 16:22:48 +00:00
"golang.org/x/crypto/hkdf"
2022-08-09 13:55:08 +00:00
)
// WakuNoiseProtocolID indicates the protocol ID defined according to https://rfc.vac.dev/spec/35/#specification
type WakuNoiseProtocolID = byte
var (
2022-12-14 16:22:48 +00:00
None = WakuNoiseProtocolID ( 0 )
Noise_K1K1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID ( 10 )
Noise_XK1_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID ( 11 )
Noise_XX_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID ( 12 )
Noise_XXpsk0_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID ( 13 )
Noise_WakuPairing_25519_ChaChaPoly_SHA256 = WakuNoiseProtocolID ( 14 )
ChaChaPoly = WakuNoiseProtocolID ( 30 )
2022-08-09 13:55:08 +00:00
)
2022-09-07 19:24:35 +00:00
const NoisePaddingBlockSize = 248
2022-08-09 13:55:08 +00:00
var ErrorHandshakeComplete = errors . New ( "handshake complete" )
// All protocols share same cipher suite
var cipherSuite = n . NewCipherSuite ( n . DH25519 , n . CipherChaChaPoly , n . HashSHA256 )
2022-12-14 16:22:48 +00:00
func newHandshakeState ( pattern n . HandshakePattern , initiator bool , staticKeypair n . DHKey , ephemeralKeyPair n . DHKey , prologue [ ] byte , presharedKey [ ] byte , peerStatic [ ] byte , peerEphemeral [ ] byte ) ( hs * n . HandshakeState , err error ) {
2022-08-09 13:55:08 +00:00
defer func ( ) {
if rerr := recover ( ) ; rerr != nil {
err = fmt . Errorf ( "panic in Noise handshake: %s" , rerr )
}
} ( )
cfg := n . Config {
2022-12-14 16:22:48 +00:00
CipherSuite : cipherSuite ,
Pattern : pattern ,
Initiator : initiator ,
StaticKeypair : staticKeypair ,
EphemeralKeypair : ephemeralKeyPair ,
Prologue : prologue ,
PresharedKey : presharedKey ,
PeerStatic : peerStatic ,
PeerEphemeral : peerEphemeral ,
2022-08-09 13:55:08 +00:00
}
return n . NewHandshakeState ( cfg )
}
type Handshake struct {
protocolID WakuNoiseProtocolID
pattern n . HandshakePattern
state * n . HandshakeState
hsBuff [ ] byte
2022-12-14 16:22:48 +00:00
enc * n . CipherState
dec * n . CipherState
nametagsInbound * MessageNametagBuffer
nametagsOutbound * MessageNametagBuffer
2022-08-09 13:55:08 +00:00
initiator bool
shouldWrite bool
}
// HandshakeStepResult stores the intermediate result of processing messages patterns
type HandshakeStepResult struct {
Payload2 PayloadV2
TransportMessage [ ] byte
}
func getHandshakePattern ( protocol WakuNoiseProtocolID ) ( n . HandshakePattern , error ) {
switch protocol {
case Noise_K1K1_25519_ChaChaPoly_SHA256 :
return HandshakeK1K1 , nil
case Noise_XK1_25519_ChaChaPoly_SHA256 :
return HandshakeXK1 , nil
case Noise_XX_25519_ChaChaPoly_SHA256 :
return HandshakeXX , nil
case Noise_XXpsk0_25519_ChaChaPoly_SHA256 :
return HandshakeXXpsk0 , nil
2022-12-14 16:22:48 +00:00
case Noise_WakuPairing_25519_ChaChaPoly_SHA256 :
return HandshakeWakuPairing , nil
2022-08-09 13:55:08 +00:00
default :
return n . HandshakePattern { } , errors . New ( "unsupported handshake pattern" )
}
}
// NewHandshake creates a new handshake using aa WakuNoiseProtocolID that is maped to a handshake pattern.
2022-12-14 16:22:48 +00:00
func NewHandshake ( protocolID WakuNoiseProtocolID , initiator bool , staticKeypair n . DHKey , ephemeralKeyPair n . DHKey , prologue [ ] byte , presharedKey [ ] byte , peerStatic [ ] byte , peerEphemeral [ ] byte ) ( * Handshake , error ) {
2022-08-09 13:55:08 +00:00
hsPattern , err := getHandshakePattern ( protocolID )
if err != nil {
return nil , err
}
2022-12-14 16:22:48 +00:00
hsState , err := newHandshakeState ( hsPattern , initiator , staticKeypair , ephemeralKeyPair , prologue , presharedKey , peerStatic , peerEphemeral )
2022-08-09 13:55:08 +00:00
if err != nil {
return nil , err
}
return & Handshake {
protocolID : protocolID ,
pattern : hsPattern ,
initiator : initiator ,
shouldWrite : initiator ,
state : hsState ,
} , nil
}
2022-12-14 16:22:48 +00:00
func ( hs * Handshake ) Hash ( ) hash . Hash {
return hs . state . Hash ( )
}
func ( hs * Handshake ) H ( ) [ ] byte {
return hs . state . H ( )
}
func ( hs * Handshake ) RS ( ) [ ] byte {
return hs . state . RS ( )
}
2022-08-09 13:55:08 +00:00
// Step advances a step in the 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.
2022-12-14 16:22:48 +00:00
// TODO: this might be refactored into a separate `sendHandshakeMessage` and `receiveHandshakeMessage`.
// The messageNameTag is an optional value and can be used to identify missing messages
func ( hs * Handshake ) Step ( readPayloadV2 * PayloadV2 , transportMessage [ ] byte , messageNametag * MessageNametag ) ( * HandshakeStepResult , error ) {
2022-08-09 13:55:08 +00:00
if hs . enc != nil || hs . dec != nil {
return nil , ErrorHandshakeComplete
}
var cs1 * n . CipherState
var cs2 * n . CipherState
var err error
var msg [ ] byte
2022-10-23 13:09:49 +00:00
var noisePubKeys [ ] [ ] byte
2022-08-09 13:55:08 +00:00
result := HandshakeStepResult { }
if hs . shouldWrite {
// We initialize a payload v2 and we set proper protocol ID (if supported)
result . Payload2 . ProtocolId = hs . protocolID
2022-09-07 19:24:35 +00:00
payload , err := PKCS7_Pad ( transportMessage , NoisePaddingBlockSize )
if err != nil {
return nil , err
}
2022-12-14 16:22:48 +00:00
var mtag MessageNametag
if messageNametag != nil {
mtag = * messageNametag
}
msg , noisePubKeys , cs1 , cs2 , err = hs . state . WriteMessageAndGetPK ( hs . hsBuff , [ ] [ ] byte { } , payload , mtag [ : ] )
2022-08-09 13:55:08 +00:00
if err != nil {
return nil , err
}
hs . shouldWrite = false
2022-12-14 16:22:48 +00:00
if messageNametag != nil {
result . Payload2 . MessageNametag = * messageNametag
}
2022-08-09 13:55:08 +00:00
result . Payload2 . TransportMessage = msg
for _ , npk := range noisePubKeys {
result . Payload2 . HandshakeMessage = append ( result . Payload2 . HandshakeMessage , byteToNoisePublicKey ( npk ) )
}
} else {
if readPayloadV2 == nil {
return nil , errors . New ( "readPayloadV2 is required" )
}
2022-12-14 16:22:48 +00:00
var mtag MessageNametag
if messageNametag != nil {
mtag = * messageNametag
if ! bytes . Equal ( readPayloadV2 . MessageNametag [ : ] , mtag [ : ] ) {
return nil , ErrNametagNotExpected
}
}
2022-08-09 13:55:08 +00:00
readTMessage := readPayloadV2 . TransportMessage
2022-12-14 16:22:48 +00:00
// We retrieve and store the (decrypted) received transport message by passing the messageNametag as extra additional data
2022-08-09 13:55:08 +00:00
// Since we only read, nothing meanigful (i.e. public keys) is returned. (hsBuffer is not affected)
2022-12-14 16:22:48 +00:00
msg , cs1 , cs2 , err = hs . state . ReadMessage ( nil , readTMessage , mtag [ : ] ... )
2022-08-09 13:55:08 +00:00
if err != nil {
return nil , err
}
hs . shouldWrite = true
2022-09-07 19:24:35 +00:00
// We retrieve, and store the (unpadded decrypted) received transport message
payload , err := PKCS7_Unpad ( msg , NoisePaddingBlockSize )
if err != nil {
return nil , err
}
result . TransportMessage = payload
2022-08-09 13:55:08 +00:00
}
if cs1 != nil && cs2 != nil {
2022-12-14 16:22:48 +00:00
err = hs . setCipherStates ( cs1 , cs2 )
if err != nil {
return nil , err
}
2022-08-09 13:55:08 +00:00
}
return & result , nil
}
// HandshakeComplete indicates whether the handshake process is complete or not
func ( hs * Handshake ) HandshakeComplete ( ) bool {
return hs . enc != nil && hs . dec != nil
}
// This is called when the final handshake message is processed
2022-12-14 16:22:48 +00:00
func ( hs * Handshake ) setCipherStates ( cs1 , cs2 * n . CipherState ) error {
// Optional: We derive a secret for the nametag derivation
nms1 , nms2 , err := hs . messageNametagSecrets ( )
if err != nil {
return err
}
2022-08-09 13:55:08 +00:00
if hs . initiator {
hs . enc = cs1
hs . dec = cs2
2022-12-14 16:22:48 +00:00
// and nametags secrets
hs . nametagsInbound = NewMessageNametagBuffer ( nms1 )
hs . nametagsOutbound = NewMessageNametagBuffer ( nms2 )
2022-08-09 13:55:08 +00:00
} else {
hs . enc = cs2
hs . dec = cs1
2022-12-14 16:22:48 +00:00
// and nametags secrets
hs . nametagsInbound = NewMessageNametagBuffer ( nms2 )
hs . nametagsOutbound = NewMessageNametagBuffer ( nms1 )
2022-08-09 13:55:08 +00:00
}
2022-12-14 16:22:48 +00:00
// We initialize the message nametags inbound/outbound buffers
hs . nametagsInbound . Init ( )
hs . nametagsOutbound . Init ( )
return nil
2022-08-09 13:55:08 +00:00
}
// Encrypt calls the cipher's encryption. It encrypts the provided plaintext and returns a PayloadV2
2022-12-14 16:22:48 +00:00
func ( hs * Handshake ) Encrypt ( plaintext [ ] byte , outboundMessageNametagBuffer ... * MessageNametagBuffer ) ( * PayloadV2 , error ) {
2022-08-09 13:55:08 +00:00
if hs . enc == nil {
return nil , errors . New ( "cannot encrypt, handshake incomplete" )
}
if len ( plaintext ) == 0 {
return nil , errors . New ( "tried to encrypt empty plaintext" )
}
2022-09-07 19:24:35 +00:00
paddedTransportMessage , err := PKCS7_Pad ( plaintext , NoisePaddingBlockSize )
if err != nil {
return nil , err
}
2022-08-09 13:55:08 +00:00
2022-12-14 16:22:48 +00:00
// We set the message nametag using the input buffer
var messageNametag MessageNametag
if len ( outboundMessageNametagBuffer ) != 0 {
messageNametag = outboundMessageNametagBuffer [ 0 ] . Pop ( )
} else {
messageNametag = hs . nametagsOutbound . Pop ( )
}
cyphertext , err := hs . enc . Encrypt ( nil , messageNametag [ : ] , paddedTransportMessage )
2022-08-09 13:55:08 +00:00
if err != nil {
return nil , err
}
// According to 35/WAKU2-NOISE RFC, no Handshake protocol information is sent when exchanging messages
// This correspond to setting protocol-id to 0 (None)
return & PayloadV2 {
ProtocolId : None ,
TransportMessage : cyphertext ,
2022-12-14 16:22:48 +00:00
MessageNametag : messageNametag ,
2022-08-09 13:55:08 +00:00
} , nil
}
// Decrypt calls the cipher's decryption. It decrypts the provided payload and returns the message in plaintext
2022-12-14 16:22:48 +00:00
func ( hs * Handshake ) Decrypt ( payload * PayloadV2 , inboundMessageNametagBuffer ... * MessageNametagBuffer ) ( [ ] byte , error ) {
2022-08-09 13:55:08 +00:00
if hs . dec == nil {
return nil , errors . New ( "cannot decrypt, handshake incomplete" )
}
if payload == nil {
return nil , errors . New ( "no payload to decrypt" )
}
2022-12-14 16:22:48 +00:00
// If the message nametag does not correspond to the nametag expected in the inbound message nametag buffer
// an error is raised (to be handled externally, i.e. re-request lost messages, discard, etc.)
if len ( inboundMessageNametagBuffer ) != 0 {
err := inboundMessageNametagBuffer [ 0 ] . CheckNametag ( payload . MessageNametag )
if err != nil {
return nil , err
}
} else {
err := hs . nametagsInbound . CheckNametag ( payload . MessageNametag )
if err != nil {
return nil , err
}
}
2022-08-09 13:55:08 +00:00
if len ( payload . TransportMessage ) == 0 {
return nil , errors . New ( "tried to decrypt empty ciphertext" )
}
2022-12-14 16:22:48 +00:00
// Decryption is done with messageNametag as associated data
paddedMessage , err := hs . dec . Decrypt ( nil , payload . MessageNametag [ : ] , payload . TransportMessage )
2022-09-07 19:24:35 +00:00
if err != nil {
return nil , err
}
2022-12-14 16:22:48 +00:00
// The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer
hs . nametagsInbound . Delete ( 1 )
2022-09-07 19:24:35 +00:00
return PKCS7_Unpad ( paddedMessage , NoisePaddingBlockSize )
2022-08-09 13:55:08 +00:00
}
2022-12-14 16:22:48 +00:00
func getHKDF ( h func ( ) hash . Hash , ck [ ] byte , ikm [ ] byte , numBytes int ) ( [ ] byte , error ) {
hkdf := hkdf . New ( h , ikm , ck , nil )
result := make ( [ ] byte , numBytes )
if _ , err := io . ReadFull ( hkdf , result ) ; err != nil {
return nil , err
}
return result , nil
}
// Generates an 8 decimal digits authorization code using HKDF and the handshake state
func ( hs * Handshake ) Authcode ( ) ( string , error ) {
output0 , err := getHKDF ( hs . Hash , hs . H ( ) , nil , 8 )
if err != nil {
return "" , err
}
bn := new ( big . Int )
bn . SetBytes ( output0 )
code := new ( big . Int )
code . Mod ( bn , big . NewInt ( 100_000_000 ) )
2022-12-16 00:47:54 +00:00
return fmt . Sprintf ( "'%08s'" , code . String ( ) ) , nil
2022-12-14 16:22:48 +00:00
}
func ( hs * Handshake ) messageNametagSecrets ( ) ( nms1 [ ] byte , nms2 [ ] byte , err error ) {
output , err := getHKDF ( hs . Hash , hs . H ( ) , nil , 64 )
if err != nil {
return nil , nil , err
}
nms1 = output [ 0 : 32 ]
nms2 = output [ 32 : ]
return
}
// Uses the cryptographic information stored in the input handshake state to generate a random message nametag
// In current implementation the messageNametag = HKDF(handshake hash value), but other derivation mechanisms can be implemented
func ( hs * Handshake ) ToMessageNametag ( ) ( MessageNametag , error ) {
output , err := getHKDF ( hs . Hash , hs . H ( ) , nil , 32 )
if err != nil {
return [ 16 ] byte { } , err
}
return BytesToMessageNametag ( output ) , nil
}
2022-08-09 13:55:08 +00:00
// NewHandshake_XX_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and receiver are not aware of each other static keys
func NewHandshake_XX_25519_ChaChaPoly_SHA256 ( staticKeypair n . DHKey , initiator bool , prologue [ ] byte ) ( * Handshake , error ) {
2022-12-14 16:22:48 +00:00
return NewHandshake ( Noise_XX_25519_ChaChaPoly_SHA256 , initiator , staticKeypair , n . DHKey { } , prologue , nil , nil , nil )
2022-08-09 13:55:08 +00:00
}
// NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and receiver are not aware of each other static keys
// and use a preshared secret to strengthen their mutual authentication
func NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256 ( staticKeypair n . DHKey , initiator bool , presharedKey [ ] byte , prologue [ ] byte ) ( * Handshake , error ) {
2022-12-14 16:22:48 +00:00
return NewHandshake ( Noise_XXpsk0_25519_ChaChaPoly_SHA256 , initiator , staticKeypair , n . DHKey { } , prologue , presharedKey , nil , nil )
2022-08-09 13:55:08 +00:00
}
// NewHandshake_K1K1_25519_ChaChaPoly_SHA256 creates a handshake where both initiator and recever know each other handshake. Only ephemeral keys
// are exchanged. This handshake is useful in case the initiator needs to instantiate a new separate encrypted communication
// channel with the receiver
func NewHandshake_K1K1_25519_ChaChaPoly_SHA256 ( staticKeypair n . DHKey , initiator bool , peerStaticKey [ ] byte , prologue [ ] byte ) ( * Handshake , error ) {
2022-12-14 16:22:48 +00:00
return NewHandshake ( Noise_K1K1_25519_ChaChaPoly_SHA256 , initiator , staticKeypair , n . DHKey { } , prologue , nil , peerStaticKey , nil )
2022-08-09 13:55:08 +00:00
}
// NewHandshake_XK1_25519_ChaChaPoly_SHA256 creates a handshake where the initiator knows the receiver public static key. Within this handshake,
// the initiator and receiver reciprocally authenticate their static keys using ephemeral keys. We note that while the receiver's
// static key is assumed to be known to Alice (and hence is not transmitted), The initiator static key is sent to the
// receiver encrypted with a key derived from both parties ephemeral keys and the receiver's static key.
func NewHandshake_XK1_25519_ChaChaPoly_SHA256 ( staticKeypair n . DHKey , initiator bool , peerStaticKey [ ] byte , prologue [ ] byte ) ( * Handshake , error ) {
if ! initiator && len ( peerStaticKey ) != 0 {
return nil , errors . New ( "recipient shouldnt know initiator key" )
}
2022-12-14 16:22:48 +00:00
return NewHandshake ( Noise_XK1_25519_ChaChaPoly_SHA256 , initiator , staticKeypair , n . DHKey { } , prologue , nil , peerStaticKey , nil )
}
// NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256
func NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256 ( staticKeypair n . DHKey , ephemeralKeyPair n . DHKey , initiator bool , prologue [ ] byte , presharedKey [ ] byte ) ( * Handshake , error ) {
peerEphemeral := presharedKey [ 0 : 32 ]
return NewHandshake ( Noise_WakuPairing_25519_ChaChaPoly_SHA256 , initiator , staticKeypair , ephemeralKeyPair , prologue , presharedKey , nil , peerEphemeral )
2022-08-09 13:55:08 +00:00
}