mirror of https://github.com/status-im/go-waku.git
feat: noise (#258)
This commit is contained in:
parent
2b4a2d72d3
commit
dec00e69ad
9
go.mod
9
go.mod
|
@ -6,6 +6,8 @@ replace github.com/raulk/go-watchdog v1.2.0 => github.com/status-im/go-watchdog
|
||||||
|
|
||||||
replace github.com/ethereum/go-ethereum v1.10.18 => github.com/status-im/go-ethereum v1.10.4-status.2
|
replace github.com/ethereum/go-ethereum v1.10.18 => github.com/status-im/go-ethereum v1.10.4-status.2
|
||||||
|
|
||||||
|
replace github.com/flynn/noise v1.0.0 => github.com/status-im/noise v1.0.1-handshakeMessages
|
||||||
|
|
||||||
require (
|
require (
|
||||||
contrib.go.opencensus.io/exporter/prometheus v0.4.1
|
contrib.go.opencensus.io/exporter/prometheus v0.4.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3
|
github.com/btcsuite/btcd/btcec/v2 v2.1.3
|
||||||
|
@ -36,7 +38,10 @@ require (
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.3.7
|
require (
|
||||||
|
github.com/flynn/noise v1.0.0
|
||||||
|
golang.org/x/text v0.3.7
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||||
|
@ -55,7 +60,6 @@ require (
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/elastic/gosigar v0.14.1 // indirect
|
github.com/elastic/gosigar v0.14.1 // indirect
|
||||||
github.com/flynn/noise v1.0.0 // indirect
|
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/go-kit/log v0.1.0 // indirect
|
github.com/go-kit/log v0.1.0 // indirect
|
||||||
|
@ -90,7 +94,6 @@ require (
|
||||||
github.com/libp2p/go-libp2p-blankhost v0.3.0 // indirect
|
github.com/libp2p/go-libp2p-blankhost v0.3.0 // indirect
|
||||||
github.com/libp2p/go-libp2p-connmgr v0.3.1 // indirect
|
github.com/libp2p/go-libp2p-connmgr v0.3.1 // indirect
|
||||||
github.com/libp2p/go-libp2p-discovery v0.6.0 // indirect
|
github.com/libp2p/go-libp2p-discovery v0.6.0 // indirect
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.17.0 // indirect
|
|
||||||
github.com/libp2p/go-libp2p-resource-manager v0.3.0 // indirect
|
github.com/libp2p/go-libp2p-resource-manager v0.3.0 // indirect
|
||||||
github.com/libp2p/go-libp2p-swarm v0.10.2 // indirect
|
github.com/libp2p/go-libp2p-swarm v0.10.2 // indirect
|
||||||
github.com/libp2p/go-libp2p-tls v0.3.1 // indirect
|
github.com/libp2p/go-libp2p-tls v0.3.1 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -541,8 +541,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
|
|
||||||
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
|
||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
|
@ -1115,18 +1113,12 @@ github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5B
|
||||||
github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0=
|
github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0=
|
||||||
github.com/libp2p/go-libp2p-peerstore v0.6.0 h1:HJminhQSGISBIRb93N6WK3t6Fa8OOTnHd/VBjL4mY5A=
|
github.com/libp2p/go-libp2p-peerstore v0.6.0 h1:HJminhQSGISBIRb93N6WK3t6Fa8OOTnHd/VBjL4mY5A=
|
||||||
github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc=
|
github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc=
|
||||||
github.com/libp2p/go-libp2p-peerstore v0.7.0 h1:2iIUwok3vtmnWJTZeTeLgnBO6GbkXcwSRwgZHEKrQZs=
|
|
||||||
github.com/libp2p/go-libp2p-peerstore v0.7.0/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0=
|
|
||||||
github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k=
|
github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k=
|
||||||
github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA=
|
github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA=
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.6.1 h1:wycbV+f4rreCoVY61Do6g/BUk0RIrbNRcYVbn+QkjGk=
|
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.6.1/go.mod h1:nJv87QM2cU0w45KPR1rZicq+FmFIOD16zmT+ep1nOmg=
|
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.7.1 h1:e2CPBP5uxvDkE0FiS0obZGZPzt+xuBOc6PpG+50pIAo=
|
github.com/libp2p/go-libp2p-pubsub v0.7.1 h1:e2CPBP5uxvDkE0FiS0obZGZPzt+xuBOc6PpG+50pIAo=
|
||||||
github.com/libp2p/go-libp2p-pubsub v0.7.1/go.mod h1:EuyBJFtF8qF67IEA98biwK8Xnw5MNJpJ/Z+8iWCMFwc=
|
github.com/libp2p/go-libp2p-pubsub v0.7.1/go.mod h1:EuyBJFtF8qF67IEA98biwK8Xnw5MNJpJ/Z+8iWCMFwc=
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc=
|
github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc=
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ=
|
github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ=
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.16.1 h1:N/XqYXHurphPLDfXYhll8NyqzdZYQqAF4GIr7+SmLV8=
|
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.16.1/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ=
|
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.17.0 h1:yFh4Gf5MlToAYLuw/dRvuzYd1EnE2pX3Lq1N6KDiWRQ=
|
github.com/libp2p/go-libp2p-quic-transport v0.17.0 h1:yFh4Gf5MlToAYLuw/dRvuzYd1EnE2pX3Lq1N6KDiWRQ=
|
||||||
github.com/libp2p/go-libp2p-quic-transport v0.17.0/go.mod h1:x4pw61P3/GRCcSLypcQJE/Q2+E9f4X+5aRcZLXf20LM=
|
github.com/libp2p/go-libp2p-quic-transport v0.17.0/go.mod h1:x4pw61P3/GRCcSLypcQJE/Q2+E9f4X+5aRcZLXf20LM=
|
||||||
github.com/libp2p/go-libp2p-resource-manager v0.3.0 h1:2+cYxUNi33tcydsVLt6K5Fv2E3OTiVeafltecAj15E0=
|
github.com/libp2p/go-libp2p-resource-manager v0.3.0 h1:2+cYxUNi33tcydsVLt6K5Fv2E3OTiVeafltecAj15E0=
|
||||||
|
@ -1689,6 +1681,8 @@ github.com/status-im/go-waku-rendezvous v0.0.0-20211018070416-a93f3b70c432/go.mo
|
||||||
github.com/status-im/go-watchdog v1.2.0-ios-nolibproc h1:BJwZEF7OVKaXc2zErBUAolFSGzwrTBbWnN8e/6MER5E=
|
github.com/status-im/go-watchdog v1.2.0-ios-nolibproc h1:BJwZEF7OVKaXc2zErBUAolFSGzwrTBbWnN8e/6MER5E=
|
||||||
github.com/status-im/go-watchdog v1.2.0-ios-nolibproc/go.mod h1:lzSbAl5sh4rtI8tYHU01BWIDzgzqaQLj6RcA1i4mlqI=
|
github.com/status-im/go-watchdog v1.2.0-ios-nolibproc/go.mod h1:lzSbAl5sh4rtI8tYHU01BWIDzgzqaQLj6RcA1i4mlqI=
|
||||||
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
|
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
|
||||||
|
github.com/status-im/noise v1.0.1-handshakeMessages h1:mj1btE58Qk2pS0qz+BHE22HYIOhZoEFNTnRpQeMfHYk=
|
||||||
|
github.com/status-im/noise v1.0.1-handshakeMessages/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||||
github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU=
|
github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU=
|
||||||
github.com/status-im/status-go/extkeys v1.1.2/go.mod h1:hCmFzb2jiiVF2voZKYbzuhOQiHHCmyLJsZJXrFFg7BY=
|
github.com/status-im/status-go/extkeys v1.1.2/go.mod h1:hCmFzb2jiiVF2voZKYbzuhOQiHHCmyLJsZJXrFFg7BY=
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||||
|
"github.com/status-im/go-waku/waku/v2/noise"
|
||||||
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -449,3 +450,26 @@ func bytesToUintLittleEndian(b []byte) (res uint64) {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decodes a WakuMessage to a PayloadV2
|
||||||
|
// Currently, this is just a wrapper over deserializePayloadV2 and encryption/decryption is done on top (no KeyInfo)
|
||||||
|
func DecodePayloadV2(message *pb.WakuMessage) (*noise.PayloadV2, error) {
|
||||||
|
if message.Version != 2 {
|
||||||
|
return nil, errors.New("wrong message version while decoding payload")
|
||||||
|
}
|
||||||
|
return noise.DeserializePayloadV2(message.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodes a PayloadV2 to a WakuMessage
|
||||||
|
// Currently, this is just a wrapper over serializePayloadV2 and encryption/decryption is done on top (no KeyInfo)
|
||||||
|
func EncodePayloadV2(payload2 *noise.PayloadV2) (*pb.WakuMessage, error) {
|
||||||
|
serializedPayload2, err := payload2.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.WakuMessage{
|
||||||
|
Payload: serializedPayload2,
|
||||||
|
Version: 2,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
package noise
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
n "github.com/flynn/noise"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WakuNoiseProtocolID indicates the protocol ID defined according to https://rfc.vac.dev/spec/35/#specification
|
||||||
|
type WakuNoiseProtocolID = byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
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)
|
||||||
|
ChaChaPoly = WakuNoiseProtocolID(30)
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrorHandshakeComplete = errors.New("handshake complete")
|
||||||
|
|
||||||
|
// All protocols share same cipher suite
|
||||||
|
var cipherSuite = n.NewCipherSuite(n.DH25519, n.CipherChaChaPoly, n.HashSHA256)
|
||||||
|
|
||||||
|
func newHandshakeState(pattern n.HandshakePattern, initiator bool, staticKeypair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (hs *n.HandshakeState, err error) {
|
||||||
|
defer func() {
|
||||||
|
if rerr := recover(); rerr != nil {
|
||||||
|
err = fmt.Errorf("panic in Noise handshake: %s", rerr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cfg := n.Config{
|
||||||
|
CipherSuite: cipherSuite,
|
||||||
|
Pattern: pattern,
|
||||||
|
Initiator: initiator,
|
||||||
|
StaticKeypair: staticKeypair,
|
||||||
|
Prologue: prologue,
|
||||||
|
PresharedKey: presharedKey,
|
||||||
|
PeerStatic: peerStatic,
|
||||||
|
PeerEphemeral: peerEphemeral,
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.NewHandshakeState(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handshake struct {
|
||||||
|
protocolID WakuNoiseProtocolID
|
||||||
|
pattern n.HandshakePattern
|
||||||
|
state *n.HandshakeState
|
||||||
|
|
||||||
|
hsBuff []byte
|
||||||
|
|
||||||
|
enc *n.CipherState
|
||||||
|
dec *n.CipherState
|
||||||
|
|
||||||
|
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
|
||||||
|
default:
|
||||||
|
return n.HandshakePattern{}, errors.New("unsupported handshake pattern")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandshake creates a new handshake using aa WakuNoiseProtocolID that is maped to a handshake pattern.
|
||||||
|
func NewHandshake(protocolID WakuNoiseProtocolID, initiator bool, staticKeypair n.DHKey, prologue []byte, presharedKey []byte, peerStatic []byte, peerEphemeral []byte) (*Handshake, error) {
|
||||||
|
hsPattern, err := getHandshakePattern(protocolID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hsState, err := newHandshakeState(hsPattern, initiator, staticKeypair, prologue, presharedKey, peerStatic, peerEphemeral)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handshake{
|
||||||
|
protocolID: protocolID,
|
||||||
|
pattern: hsPattern,
|
||||||
|
initiator: initiator,
|
||||||
|
shouldWrite: initiator,
|
||||||
|
state: hsState,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// TODO: this might be refactored into a separate `sendHandshakeMessage` and `receiveHandshakeMessage`
|
||||||
|
func (hs *Handshake) Step(readPayloadV2 *PayloadV2, transportMessage []byte) (*HandshakeStepResult, error) {
|
||||||
|
if hs.enc != nil || hs.dec != nil {
|
||||||
|
return nil, ErrorHandshakeComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs1 *n.CipherState
|
||||||
|
var cs2 *n.CipherState
|
||||||
|
var err error
|
||||||
|
var msg []byte
|
||||||
|
|
||||||
|
result := HandshakeStepResult{}
|
||||||
|
|
||||||
|
if hs.shouldWrite {
|
||||||
|
// We initialize a payload v2 and we set proper protocol ID (if supported)
|
||||||
|
result.Payload2.ProtocolId = hs.protocolID
|
||||||
|
|
||||||
|
var noisePubKeys [][]byte
|
||||||
|
msg, cs1, cs2, err = hs.state.WriteMessageAndGetPK(hs.hsBuff, &noisePubKeys, transportMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.shouldWrite = false
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
readTMessage := readPayloadV2.TransportMessage
|
||||||
|
|
||||||
|
// Since we only read, nothing meanigful (i.e. public keys) is returned. (hsBuffer is not affected)
|
||||||
|
msg, cs1, cs2, err = hs.state.ReadMessage(nil, readTMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.shouldWrite = true
|
||||||
|
|
||||||
|
// We retrieve and store the (decrypted) received transport message
|
||||||
|
result.TransportMessage = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs1 != nil && cs2 != nil {
|
||||||
|
hs.setCipherStates(cs1, cs2)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
func (hs *Handshake) setCipherStates(cs1, cs2 *n.CipherState) {
|
||||||
|
if hs.initiator {
|
||||||
|
hs.enc = cs1
|
||||||
|
hs.dec = cs2
|
||||||
|
} else {
|
||||||
|
hs.enc = cs2
|
||||||
|
hs.dec = cs1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt calls the cipher's encryption. It encrypts the provided plaintext and returns a PayloadV2
|
||||||
|
func (hs *Handshake) Encrypt(plaintext []byte) (*PayloadV2, error) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add padding (?)
|
||||||
|
|
||||||
|
cyphertext, err := hs.enc.Encrypt(nil, nil, plaintext)
|
||||||
|
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,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt calls the cipher's decryption. It decrypts the provided payload and returns the message in plaintext
|
||||||
|
func (hs *Handshake) Decrypt(payload *PayloadV2) ([]byte, error) {
|
||||||
|
if hs.dec == nil {
|
||||||
|
return nil, errors.New("cannot decrypt, handshake incomplete")
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload == nil {
|
||||||
|
return nil, errors.New("no payload to decrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(payload.TransportMessage) == 0 {
|
||||||
|
return nil, errors.New("tried to decrypt empty ciphertext")
|
||||||
|
}
|
||||||
|
|
||||||
|
return hs.dec.Decrypt(nil, nil, payload.TransportMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return NewHandshake(Noise_XX_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return NewHandshake(Noise_XXpsk0_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, presharedKey, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return NewHandshake(Noise_K1K1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, peerStaticKey, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
return NewHandshake(Noise_XK1_25519_ChaChaPoly_SHA256, initiator, staticKeypair, prologue, nil, peerStaticKey, nil)
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
package noise
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/flynn/noise"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateRandomBytes(t *testing.T, n int) []byte {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerialization(t *testing.T) {
|
||||||
|
handshakeMessages := make([]*NoisePublicKey, 2)
|
||||||
|
|
||||||
|
pk1, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
pk2, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
handshakeMessages[0] = Ed25519PubKeyToNoisePublicKey(pk1)
|
||||||
|
handshakeMessages[1] = Ed25519PubKeyToNoisePublicKey(pk2)
|
||||||
|
|
||||||
|
p1 := &PayloadV2{
|
||||||
|
ProtocolId: Noise_K1K1_25519_ChaChaPoly_SHA256,
|
||||||
|
HandshakeMessage: handshakeMessages,
|
||||||
|
TransportMessage: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedPayload, err := p1.Serialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
deserializedPayload, err := DeserializePayloadV2(serializedPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, p1, deserializedPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handshakeTest(t *testing.T, hsAlice *Handshake, hsBob *Handshake) {
|
||||||
|
// ###############
|
||||||
|
// # 1st step
|
||||||
|
// ###############
|
||||||
|
|
||||||
|
// By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message
|
||||||
|
// and the (encrypted) transport message
|
||||||
|
sentTransportMessage := generateRandomBytes(t, 32)
|
||||||
|
aliceStep, err := hsAlice.Step(nil, sentTransportMessage)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||||
|
bobStep, err := hsBob.Step(&aliceStep.Payload2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check:
|
||||||
|
require.Equal(t, sentTransportMessage, bobStep.TransportMessage)
|
||||||
|
|
||||||
|
// ###############
|
||||||
|
// # 2nd step
|
||||||
|
// ###############
|
||||||
|
|
||||||
|
// At this step, Bob writes and returns a payload
|
||||||
|
sentTransportMessage = generateRandomBytes(t, 32)
|
||||||
|
bobStep, err = hsBob.Step(nil, sentTransportMessage)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// While Alice reads and returns the (decrypted) transport message
|
||||||
|
aliceStep, err = hsAlice.Step(&bobStep.Payload2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check:
|
||||||
|
require.Equal(t, sentTransportMessage, aliceStep.TransportMessage)
|
||||||
|
|
||||||
|
// ###############
|
||||||
|
// # 3rd step
|
||||||
|
// ###############
|
||||||
|
|
||||||
|
// Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message
|
||||||
|
sentTransportMessage = generateRandomBytes(t, 32)
|
||||||
|
aliceStep, err = hsAlice.Step(nil, sentTransportMessage)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
|
||||||
|
bobStep, err = hsBob.Step(&aliceStep.Payload2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check:
|
||||||
|
require.Equal(t, sentTransportMessage, bobStep.TransportMessage)
|
||||||
|
|
||||||
|
// Note that for this handshake pattern, no more message patterns are left for processing
|
||||||
|
// We test that extra calls to stepHandshake do not affect parties' handshake states
|
||||||
|
require.True(t, hsAlice.HandshakeComplete())
|
||||||
|
require.True(t, hsBob.HandshakeComplete())
|
||||||
|
|
||||||
|
_, err = hsAlice.Step(nil, generateRandomBytes(t, 32))
|
||||||
|
require.ErrorIs(t, err, ErrorHandshakeComplete)
|
||||||
|
|
||||||
|
_, err = hsBob.Step(nil, generateRandomBytes(t, 32))
|
||||||
|
require.ErrorIs(t, err, ErrorHandshakeComplete)
|
||||||
|
|
||||||
|
// #########################
|
||||||
|
// After Handshake
|
||||||
|
// #########################
|
||||||
|
|
||||||
|
// We test read/write of random messages exchanged between Alice and Bob
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
// Alice writes to Bob
|
||||||
|
message := generateRandomBytes(t, 32)
|
||||||
|
|
||||||
|
encryptedPayload, err := hsAlice.Encrypt(message)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plaintext, err := hsBob.Decrypt(encryptedPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, message, plaintext)
|
||||||
|
|
||||||
|
// Bob writes to Alice
|
||||||
|
message = generateRandomBytes(t, 32)
|
||||||
|
|
||||||
|
encryptedPayload, err = hsBob.Encrypt(message)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
plaintext, err = hsAlice.Decrypt(encryptedPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, message, plaintext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoiseXXHandshakeRoundtrip(t *testing.T) {
|
||||||
|
aliceKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
bobKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
|
||||||
|
hsAlice, err := NewHandshake_XX_25519_ChaChaPoly_SHA256(aliceKP, true, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hsBob, err := NewHandshake_XX_25519_ChaChaPoly_SHA256(bobKP, false, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
handshakeTest(t, hsAlice, hsBob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoiseXXpsk0HandshakeRoundtrip(t *testing.T) {
|
||||||
|
aliceKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
bobKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
|
||||||
|
// We generate a random psk
|
||||||
|
psk := generateRandomBytes(t, 32)
|
||||||
|
|
||||||
|
hsAlice, err := NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256(aliceKP, true, psk, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hsBob, err := NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256(bobKP, false, psk, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
handshakeTest(t, hsAlice, hsBob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoiseK1K1HandshakeRoundtrip(t *testing.T) {
|
||||||
|
aliceKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
bobKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
|
||||||
|
hsAlice, err := NewHandshake_K1K1_25519_ChaChaPoly_SHA256(aliceKP, true, bobKP.Public, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hsBob, err := NewHandshake_K1K1_25519_ChaChaPoly_SHA256(bobKP, false, aliceKP.Public, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
handshakeTest(t, hsAlice, hsBob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoiseXK1HandshakeRoundtrip(t *testing.T) {
|
||||||
|
aliceKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
bobKP, _ := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||||
|
|
||||||
|
hsAlice, err := NewHandshake_XK1_25519_ChaChaPoly_SHA256(aliceKP, true, bobKP.Public, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hsBob, err := NewHandshake_XK1_25519_ChaChaPoly_SHA256(bobKP, false, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
handshakeTest(t, hsAlice, hsBob)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package noise
|
||||||
|
|
||||||
|
import (
|
||||||
|
n "github.com/flynn/noise"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
K1K1:
|
||||||
|
-> s
|
||||||
|
<- s
|
||||||
|
...
|
||||||
|
-> e
|
||||||
|
<- e, ee, es
|
||||||
|
-> se
|
||||||
|
*/
|
||||||
|
var HandshakeK1K1 = n.HandshakePattern{
|
||||||
|
Name: "K1K1",
|
||||||
|
InitiatorPreMessages: []n.MessagePattern{n.MessagePatternS},
|
||||||
|
ResponderPreMessages: []n.MessagePattern{n.MessagePatternS},
|
||||||
|
Messages: [][]n.MessagePattern{
|
||||||
|
{n.MessagePatternE},
|
||||||
|
{n.MessagePatternE, n.MessagePatternDHEE, n.MessagePatternDHES},
|
||||||
|
{n.MessagePatternDHSE},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
XK1:
|
||||||
|
<- s
|
||||||
|
...
|
||||||
|
-> e
|
||||||
|
<- e, ee, es
|
||||||
|
-> s, se
|
||||||
|
*/
|
||||||
|
var HandshakeXK1 = n.HandshakePattern{
|
||||||
|
Name: "XK1",
|
||||||
|
ResponderPreMessages: []n.MessagePattern{n.MessagePatternS},
|
||||||
|
Messages: [][]n.MessagePattern{
|
||||||
|
{n.MessagePatternE},
|
||||||
|
{n.MessagePatternE, n.MessagePatternDHEE, n.MessagePatternDHES},
|
||||||
|
{n.MessagePatternS, n.MessagePatternDHSE},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
XX:
|
||||||
|
-> e
|
||||||
|
<- e, ee, s, es
|
||||||
|
-> s, se
|
||||||
|
*/
|
||||||
|
var HandshakeXX = n.HandshakePattern{
|
||||||
|
Name: "XX",
|
||||||
|
Messages: [][]n.MessagePattern{
|
||||||
|
{n.MessagePatternE},
|
||||||
|
{n.MessagePatternE, n.MessagePatternDHEE, n.MessagePatternS, n.MessagePatternDHES},
|
||||||
|
{n.MessagePatternS, n.MessagePatternDHSE},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
XXpsk0:
|
||||||
|
-> psk, e
|
||||||
|
<- e, ee, s, es
|
||||||
|
-> s, se
|
||||||
|
*/
|
||||||
|
var HandshakeXXpsk0 = n.HandshakePattern{
|
||||||
|
Name: "XXpsk0",
|
||||||
|
Messages: [][]n.MessagePattern{
|
||||||
|
{n.MessagePatternPSK, n.MessagePatternE},
|
||||||
|
{n.MessagePatternE, n.MessagePatternDHEE, n.MessagePatternS, n.MessagePatternDHES},
|
||||||
|
{n.MessagePatternS, n.MessagePatternDHSE},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
package noise
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
n "github.com/flynn/noise"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MaxUint8 = 1<<8 - 1
|
||||||
|
|
||||||
|
// 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
|
||||||
|
type NoisePublicKey struct {
|
||||||
|
Flag byte
|
||||||
|
PubKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteToNoisePublicKey(input []byte) *NoisePublicKey {
|
||||||
|
flag := byte(0)
|
||||||
|
if len(input) > n.DH25519.DHLen() {
|
||||||
|
flag = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NoisePublicKey{
|
||||||
|
Flag: flag,
|
||||||
|
PubKey: input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EcdsaPubKeyToNoisePublicKey converts a Elliptic Curve public key
|
||||||
|
// to an unencrypted Noise public key
|
||||||
|
func Ed25519PubKeyToNoisePublicKey(pk ed25519.PublicKey) *NoisePublicKey {
|
||||||
|
return &NoisePublicKey{
|
||||||
|
Flag: 0,
|
||||||
|
PubKey: pk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals checks equality between two Noise public keys
|
||||||
|
func (pk *NoisePublicKey) Equals(pk2 *NoisePublicKey) bool {
|
||||||
|
return pk.Flag == pk2.Flag && bytes.Equal(pk.PubKey, pk2.PubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SerializedNoisePublicKey []byte
|
||||||
|
|
||||||
|
// Serialize converts a Noise public key to a stream of bytes as in
|
||||||
|
// https://rfc.vac.dev/spec/35/#public-keys-serialization
|
||||||
|
func (pk *NoisePublicKey) Serialize() SerializedNoisePublicKey {
|
||||||
|
// Public key is serialized as (flag || pk)
|
||||||
|
// Note that pk contains the X coordinate of the public key if unencrypted
|
||||||
|
// or the encryption concatenated with the authorization tag if encrypted
|
||||||
|
serializedPK := make([]byte, len(pk.PubKey)+1)
|
||||||
|
serializedPK[0] = pk.Flag
|
||||||
|
copy(serializedPK[1:], pk.PubKey)
|
||||||
|
|
||||||
|
return serializedPK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unserialize converts a serialized Noise public key to a NoisePublicKey object as in
|
||||||
|
// https://rfc.vac.dev/spec/35/#public-keys-serialization
|
||||||
|
func (s SerializedNoisePublicKey) Unserialize() (*NoisePublicKey, error) {
|
||||||
|
if len(s) <= 1 {
|
||||||
|
return nil, errors.New("invalid serialized public key length")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubk := &NoisePublicKey{}
|
||||||
|
pubk.Flag = s[0]
|
||||||
|
if !(pubk.Flag == 0 || pubk.Flag == 1) {
|
||||||
|
return nil, errors.New("invalid flag in serialized public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubk.PubKey = s[1:]
|
||||||
|
|
||||||
|
return pubk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts a Noise public key using a Cipher State
|
||||||
|
func (pk *NoisePublicKey) Encrypt(state *n.CipherState) error {
|
||||||
|
if pk.Flag == 0 {
|
||||||
|
// Authorization tag is appended to output
|
||||||
|
encPk, err := state.Encrypt(nil, nil, pk.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pk.Flag = 1
|
||||||
|
pk.PubKey = encPk
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypts decrypts a Noise public key using a Cipher State
|
||||||
|
func (pk *NoisePublicKey) Decrypt(state *n.CipherState) error {
|
||||||
|
if pk.Flag == 1 {
|
||||||
|
decPk, err := state.Decrypt(nil, nil, pk.PubKey) // encrypted pk should contain the auth tag
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pk.Flag = 0
|
||||||
|
pk.PubKey = decPk
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
type PayloadV2 struct {
|
||||||
|
ProtocolId byte
|
||||||
|
HandshakeMessage []*NoisePublicKey
|
||||||
|
TransportMessage []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks equality between two PayloadsV2 objects
|
||||||
|
func (p *PayloadV2) Equals(p2 *PayloadV2) bool {
|
||||||
|
if p.ProtocolId != p2.ProtocolId || !bytes.Equal(p.TransportMessage, p2.TransportMessage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p1 := range p.HandshakeMessage {
|
||||||
|
for _, p2 := range p2.HandshakeMessage {
|
||||||
|
if !p1.Equals(p2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serializes a PayloadV2 object to a byte sequences according to https://rfc.vac.dev/spec/35/
|
||||||
|
// The output serialized payload concatenates the input PayloadV2 object fields as
|
||||||
|
// payload = ( protocolId || serializedHandshakeMessageLen || serializedHandshakeMessage || transportMessageLen || transportMessage)
|
||||||
|
// The output can be then passed to the payload field of a WakuMessage https://rfc.vac.dev/spec/14/
|
||||||
|
func (p *PayloadV2) Serialize() ([]byte, error) {
|
||||||
|
// We collect public keys contained in the handshake message
|
||||||
|
|
||||||
|
// 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 := 0
|
||||||
|
// This variables will store the concatenation of the serializations of all public keys in the handshake message
|
||||||
|
serializedHandshakeMessage := make([]byte, 0, 256)
|
||||||
|
serializedHandshakeMessageBuffer := bytes.NewBuffer(serializedHandshakeMessage)
|
||||||
|
|
||||||
|
for _, pk := range p.HandshakeMessage {
|
||||||
|
serializedPK := pk.Serialize()
|
||||||
|
serializedHandshakeMessageLen += len(serializedPK)
|
||||||
|
if _, err := serializedHandshakeMessageBuffer.Write(serializedPK); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if serializedHandshakeMessageLen > MaxUint8 {
|
||||||
|
return nil, errors.New("too many public keys in handshake message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The output payload as in https://rfc.vac.dev/spec/35/. We concatenate all the PayloadV2 fields as
|
||||||
|
// payload = ( protocolId || serializedHandshakeMessageLen || serializedHandshakeMessage || transportMessageLen || transportMessage)
|
||||||
|
|
||||||
|
// We declare it as a byte sequence of length accordingly to the PayloadV2 information read
|
||||||
|
payload := make([]byte, 0, 1+ // 1 byte for protocol ID
|
||||||
|
1+ // 1 byte for length of serializedHandshakeMessage field
|
||||||
|
serializedHandshakeMessageLen+ // serializedHandshakeMessageLen bytes for serializedHandshakeMessage
|
||||||
|
8+ // 8 bytes for transportMessageLen
|
||||||
|
len(p.TransportMessage), // transportMessageLen bytes for transportMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
payloadBuf := bytes.NewBuffer(payload)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if err := payloadBuf.WriteByte(p.ProtocolId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := payloadBuf.WriteByte(byte(serializedHandshakeMessageLen)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := payloadBuf.Write(serializedHandshakeMessageBuffer.Bytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
TransportMessageLen := uint64(len(p.TransportMessage))
|
||||||
|
if err := binary.Write(payloadBuf, binary.LittleEndian, TransportMessageLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := payloadBuf.Write(p.TransportMessage); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadBuf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProtocolIDSupported(protocolID WakuNoiseProtocolID) bool {
|
||||||
|
return protocolID == Noise_K1K1_25519_ChaChaPoly_SHA256 || protocolID == Noise_XK1_25519_ChaChaPoly_SHA256 ||
|
||||||
|
protocolID == Noise_XX_25519_ChaChaPoly_SHA256 || protocolID == Noise_XXpsk0_25519_ChaChaPoly_SHA256 ||
|
||||||
|
protocolID == ChaChaPoly || protocolID == None
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChaChaPolyTagSize = byte(16)
|
||||||
|
|
||||||
|
// Deserializes a byte sequence to a PayloadV2 object according to https://rfc.vac.dev/spec/35/.
|
||||||
|
// The input serialized payload concatenates the output PayloadV2 object fields as
|
||||||
|
// payload = ( protocolId || serializedHandshakeMessageLen || serializedHandshakeMessage || transportMessageLen || transportMessage)
|
||||||
|
func DeserializePayloadV2(payload []byte) (*PayloadV2, error) {
|
||||||
|
payloadBuf := bytes.NewBuffer(payload)
|
||||||
|
|
||||||
|
result := &PayloadV2{}
|
||||||
|
|
||||||
|
// We start reading the Protocol ID
|
||||||
|
// TODO: when the list of supported protocol ID is defined, check if read protocol ID is supported
|
||||||
|
if err := binary.Read(payloadBuf, binary.BigEndian, &result.ProtocolId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isProtocolIDSupported(result.ProtocolId) {
|
||||||
|
return nil, errors.New("unsupported protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We read the Handshake Message length (1 byte)
|
||||||
|
var handshakeMessageLen byte
|
||||||
|
if err := binary.Read(payloadBuf, binary.BigEndian, &handshakeMessageLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if handshakeMessageLen > MaxUint8 {
|
||||||
|
return nil, errors.New("too many public keys in handshake message")
|
||||||
|
}
|
||||||
|
|
||||||
|
written := byte(0)
|
||||||
|
var handshakeMessages []*NoisePublicKey
|
||||||
|
for written < handshakeMessageLen {
|
||||||
|
// We obtain the current Noise Public key encryption flag
|
||||||
|
flag, err := payloadBuf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag == 0 {
|
||||||
|
// If the key is unencrypted, we only read the X coordinate of the EC public key and we deserialize into a Noise Public Key
|
||||||
|
pkLen := ed25519.PublicKeySize
|
||||||
|
var pkBytes SerializedNoisePublicKey = make([]byte, pkLen)
|
||||||
|
if err := binary.Read(payloadBuf, binary.BigEndian, &pkBytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedPK := SerializedNoisePublicKey(make([]byte, ed25519.PublicKeySize+1))
|
||||||
|
serializedPK[0] = flag
|
||||||
|
copy(serializedPK[1:], pkBytes)
|
||||||
|
|
||||||
|
pk, err := serializedPK.Unserialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handshakeMessages = append(handshakeMessages, pk)
|
||||||
|
written += uint8(len(serializedPK))
|
||||||
|
|
||||||
|
} else if flag == 1 {
|
||||||
|
// If the key is encrypted, we only read the encrypted X coordinate and the authorization tag, and we deserialize into a Noise Public Key
|
||||||
|
pkLen := ed25519.PublicKeySize + ChaChaPolyTagSize
|
||||||
|
// TODO: duplicated code: ==============
|
||||||
|
|
||||||
|
var pkBytes SerializedNoisePublicKey = make([]byte, pkLen)
|
||||||
|
if err := binary.Read(payloadBuf, binary.BigEndian, &pkBytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedPK := SerializedNoisePublicKey(make([]byte, ed25519.PublicKeySize+1))
|
||||||
|
serializedPK[0] = flag
|
||||||
|
copy(serializedPK[1:], pkBytes)
|
||||||
|
|
||||||
|
pk, err := serializedPK.Unserialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handshakeMessages = append(handshakeMessages, pk)
|
||||||
|
written += uint8(len(serializedPK))
|
||||||
|
// TODO: duplicated
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("invalid flag for Noise public key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.HandshakeMessage = handshakeMessages
|
||||||
|
|
||||||
|
var TransportMessageLen uint64
|
||||||
|
if err := binary.Read(payloadBuf, binary.LittleEndian, &TransportMessageLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TransportMessage = make([]byte, TransportMessageLen)
|
||||||
|
if err := binary.Read(payloadBuf, binary.BigEndian, &result.TransportMessage); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
Loading…
Reference in New Issue