mirror of https://github.com/waku-org/go-noise.git
313 lines
10 KiB
Go
313 lines
10 KiB
Go
package noise
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"hash"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
)
|
|
|
|
// The Noise tokens appearing in Noise (pre)message patterns
|
|
// as in http://www.noiseprotocol.org/noise.html#handshake-pattern-basics
|
|
type NoiseTokens string
|
|
|
|
const (
|
|
E NoiseTokens = "e"
|
|
S NoiseTokens = "s"
|
|
ES NoiseTokens = "es"
|
|
EE NoiseTokens = "ee"
|
|
SE NoiseTokens = "se"
|
|
SS NoiseTokens = "ss"
|
|
PSK NoiseTokens = "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
|
|
type MessageDirection string
|
|
|
|
const (
|
|
Right MessageDirection = "->"
|
|
Left MessageDirection = "<-"
|
|
)
|
|
|
|
// 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)
|
|
type PreMessagePattern struct {
|
|
direction MessageDirection
|
|
tokens []NoiseTokens
|
|
}
|
|
|
|
func NewPreMessagePattern(direction MessageDirection, tokens []NoiseTokens) PreMessagePattern {
|
|
return PreMessagePattern{
|
|
direction: direction,
|
|
tokens: tokens,
|
|
}
|
|
}
|
|
|
|
func (p PreMessagePattern) Equals(b PreMessagePattern) bool {
|
|
if p.direction != b.direction {
|
|
return false
|
|
}
|
|
|
|
if len(p.tokens) != len(b.tokens) {
|
|
return false
|
|
}
|
|
|
|
for i := range p.tokens {
|
|
if p.tokens[i] != b.tokens[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// The message pattern consisting of a message direction and some Noise tokens
|
|
// All Noise tokens are allowed
|
|
type MessagePattern struct {
|
|
direction MessageDirection
|
|
tokens []NoiseTokens
|
|
}
|
|
|
|
func NewMessagePattern(direction MessageDirection, tokens []NoiseTokens) MessagePattern {
|
|
return MessagePattern{
|
|
direction: direction,
|
|
tokens: tokens,
|
|
}
|
|
}
|
|
|
|
func (p MessagePattern) Equals(b MessagePattern) bool {
|
|
if p.direction != b.direction {
|
|
return false
|
|
}
|
|
|
|
if len(p.tokens) != len(b.tokens) {
|
|
return false
|
|
}
|
|
|
|
for i := range p.tokens {
|
|
if p.tokens[i] != b.tokens[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
|
|
type HandshakePattern struct {
|
|
protocolID byte
|
|
name string
|
|
premessagePatterns []PreMessagePattern
|
|
messagePatterns []MessagePattern
|
|
hashFn func() hash.Hash
|
|
cipherFn func([]byte) (cipher.AEAD, error)
|
|
dhKey DHKey
|
|
}
|
|
|
|
func NewHandshakePattern(protocolID byte, name string, hashFn func() hash.Hash, cipherFn func([]byte) (cipher.AEAD, error), dhKey DHKey, preMessagePatterns []PreMessagePattern, messagePatterns []MessagePattern) HandshakePattern {
|
|
return HandshakePattern{
|
|
protocolID: protocolID,
|
|
name: name,
|
|
hashFn: hashFn,
|
|
cipherFn: cipherFn,
|
|
dhKey: dhKey,
|
|
premessagePatterns: preMessagePatterns,
|
|
messagePatterns: messagePatterns,
|
|
}
|
|
}
|
|
|
|
func (p HandshakePattern) Equals(b HandshakePattern) bool {
|
|
|
|
if len(p.premessagePatterns) != len(b.premessagePatterns) {
|
|
return false
|
|
}
|
|
|
|
for i := range p.premessagePatterns {
|
|
if !p.premessagePatterns[i].Equals(b.premessagePatterns[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if len(p.messagePatterns) != len(b.messagePatterns) {
|
|
return false
|
|
}
|
|
|
|
for i := range p.messagePatterns {
|
|
if !p.messagePatterns[i].Equals(b.messagePatterns[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return p.name == b.name
|
|
}
|
|
|
|
var EmptyPreMessage = []PreMessagePattern{}
|
|
|
|
// Supported Noise handshake patterns as defined in https://rfc.vac.dev/spec/35/#specification
|
|
|
|
var K1K1 = NewHandshakePattern(
|
|
Noise_K1K1_25519_ChaChaPoly_SHA256,
|
|
"Noise_K1K1_25519_ChaChaPoly_SHA256",
|
|
sha256.New,
|
|
chacha20poly1305.New,
|
|
DH25519,
|
|
[]PreMessagePattern{
|
|
NewPreMessagePattern(Right, []NoiseTokens{S}),
|
|
NewPreMessagePattern(Left, []NoiseTokens{S}),
|
|
},
|
|
[]MessagePattern{
|
|
NewMessagePattern(Right, []NoiseTokens{E}),
|
|
NewMessagePattern(Left, []NoiseTokens{E, EE, ES}),
|
|
NewMessagePattern(Right, []NoiseTokens{SE}),
|
|
},
|
|
)
|
|
|
|
var XK1 = NewHandshakePattern(
|
|
Noise_XK1_25519_ChaChaPoly_SHA256,
|
|
"Noise_XK1_25519_ChaChaPoly_SHA256",
|
|
sha256.New,
|
|
chacha20poly1305.New,
|
|
DH25519,
|
|
[]PreMessagePattern{
|
|
NewPreMessagePattern(Left, []NoiseTokens{S}),
|
|
},
|
|
[]MessagePattern{
|
|
NewMessagePattern(Right, []NoiseTokens{E}),
|
|
NewMessagePattern(Left, []NoiseTokens{E, EE, ES}),
|
|
NewMessagePattern(Right, []NoiseTokens{S, SE}),
|
|
},
|
|
)
|
|
|
|
var XX = NewHandshakePattern(
|
|
Noise_XX_25519_ChaChaPoly_SHA256,
|
|
"Noise_XX_25519_ChaChaPoly_SHA256",
|
|
sha256.New,
|
|
chacha20poly1305.New,
|
|
DH25519,
|
|
EmptyPreMessage,
|
|
[]MessagePattern{
|
|
NewMessagePattern(Right, []NoiseTokens{E}),
|
|
NewMessagePattern(Left, []NoiseTokens{E, EE, S, ES}),
|
|
NewMessagePattern(Right, []NoiseTokens{S, SE}),
|
|
},
|
|
)
|
|
|
|
var XXpsk0 = NewHandshakePattern(
|
|
Noise_XXpsk0_25519_ChaChaPoly_SHA256,
|
|
"Noise_XXpsk0_25519_ChaChaPoly_SHA256",
|
|
sha256.New,
|
|
chacha20poly1305.New,
|
|
DH25519,
|
|
EmptyPreMessage,
|
|
[]MessagePattern{
|
|
NewMessagePattern(Right, []NoiseTokens{PSK, E}),
|
|
NewMessagePattern(Left, []NoiseTokens{E, EE, S, ES}),
|
|
NewMessagePattern(Right, []NoiseTokens{S, SE}),
|
|
},
|
|
)
|
|
|
|
var WakuPairing = NewHandshakePattern(
|
|
Noise_WakuPairing_25519_ChaChaPoly_SHA256,
|
|
"Noise_WakuPairing_25519_ChaChaPoly_SHA256",
|
|
sha256.New,
|
|
chacha20poly1305.New,
|
|
DH25519,
|
|
[]PreMessagePattern{
|
|
NewPreMessagePattern(Left, []NoiseTokens{E}),
|
|
},
|
|
[]MessagePattern{
|
|
NewMessagePattern(Right, []NoiseTokens{E, EE}),
|
|
NewMessagePattern(Left, []NoiseTokens{S, ES}),
|
|
NewMessagePattern(Right, []NoiseTokens{S, SE, SS}),
|
|
},
|
|
)
|
|
|
|
// Supported Protocol ID for PayloadV2 objects
|
|
// Protocol IDs are defined according to https://rfc.vac.dev/spec/35/#specification
|
|
const Noise_K1K1_25519_ChaChaPoly_SHA256 = 10
|
|
const Noise_XK1_25519_ChaChaPoly_SHA256 = 11
|
|
const Noise_XX_25519_ChaChaPoly_SHA256 = 12
|
|
const Noise_XXpsk0_25519_ChaChaPoly_SHA256 = 13
|
|
const Noise_WakuPairing_25519_ChaChaPoly_SHA256 = 14
|
|
const ChaChaPoly = 30
|
|
const None = 0
|
|
|
|
func IsProtocolIDSupported(protocolID byte) 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 == Noise_WakuPairing_25519_ChaChaPoly_SHA256 ||
|
|
protocolID == None
|
|
}
|
|
|
|
func GetHandshakePattern(protocol byte) (HandshakePattern, error) {
|
|
switch protocol {
|
|
case Noise_K1K1_25519_ChaChaPoly_SHA256:
|
|
return K1K1, nil
|
|
case Noise_XK1_25519_ChaChaPoly_SHA256:
|
|
return XK1, nil
|
|
case Noise_XX_25519_ChaChaPoly_SHA256:
|
|
return XX, nil
|
|
case Noise_XXpsk0_25519_ChaChaPoly_SHA256:
|
|
return XXpsk0, nil
|
|
case Noise_WakuPairing_25519_ChaChaPoly_SHA256:
|
|
return WakuPairing, nil
|
|
default:
|
|
return HandshakePattern{}, errors.New("unsupported handshake pattern")
|
|
}
|
|
}
|
|
|
|
// NewHandshake_XX_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and responder are not aware of each other static keys
|
|
func NewHandshake_XX_25519_ChaChaPoly_SHA256(staticKeypair Keypair, initiator bool, prologue []byte) (*Handshake, error) {
|
|
return NewHandshake(XX, staticKeypair, Keypair{}, prologue, nil, nil, initiator)
|
|
}
|
|
|
|
// NewHandshake_XXpsk0_25519_ChaChaPoly_SHA256 creates a handshake where the initiator and responder 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 Keypair, initiator bool, presharedKey []byte, prologue []byte) (*Handshake, error) {
|
|
return NewHandshake(XXpsk0, staticKeypair, Keypair{}, prologue, presharedKey, nil, initiator)
|
|
}
|
|
|
|
// 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 responder
|
|
func NewHandshake_K1K1_25519_ChaChaPoly_SHA256(myStaticKeypair Keypair, initiator bool, peerStaticKey []byte, prologue []byte) (*Handshake, error) {
|
|
var presharedKeys []*NoisePublicKey
|
|
if initiator {
|
|
presharedKeys = append(presharedKeys, byteToNoisePublicKey(K1K1.dhKey, myStaticKeypair.Public))
|
|
presharedKeys = append(presharedKeys, byteToNoisePublicKey(K1K1.dhKey, peerStaticKey))
|
|
} else {
|
|
presharedKeys = append(presharedKeys, byteToNoisePublicKey(K1K1.dhKey, peerStaticKey))
|
|
presharedKeys = append(presharedKeys, byteToNoisePublicKey(K1K1.dhKey, myStaticKeypair.Public))
|
|
}
|
|
return NewHandshake(K1K1, myStaticKeypair, Keypair{}, prologue, nil, presharedKeys, initiator)
|
|
}
|
|
|
|
// NewHandshake_XK1_25519_ChaChaPoly_SHA256 creates a handshake where the initiator knows the responder public static key. Within this handshake,
|
|
// the initiator and responder reciprocally authenticate their static keys using ephemeral keys. We note that while the responder's
|
|
// static key is assumed to be known to Alice (and hence is not transmitted), The initiator static key is sent to the
|
|
// responder encrypted with a key derived from both parties ephemeral keys and the responder's static key.
|
|
func NewHandshake_XK1_25519_ChaChaPoly_SHA256(myStaticKeypair Keypair, initiator bool, responderStaticKey []byte, prologue []byte) (*Handshake, error) {
|
|
if !initiator {
|
|
// Overwrite responderStaticKey with responder's static key in case they're different
|
|
responderStaticKey = myStaticKeypair.Public
|
|
}
|
|
pubK := byteToNoisePublicKey(XK1.dhKey, responderStaticKey)
|
|
return NewHandshake(XK1, myStaticKeypair, Keypair{}, prologue, nil, []*NoisePublicKey{pubK}, initiator)
|
|
}
|
|
|
|
// NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256
|
|
func NewHandshake_WakuPairing_25519_ChaChaPoly_SHA256(myStaticKeypair Keypair, myEphemeralKeypair Keypair, initiator bool, prologue []byte, receiverEphemeralKey []byte) (*Handshake, error) {
|
|
if !initiator {
|
|
// Overwrite responderStaticKey with responder's static key in case they're different
|
|
receiverEphemeralKey = myEphemeralKeypair.Public
|
|
}
|
|
pubK := byteToNoisePublicKey(WakuPairing.dhKey, receiverEphemeralKey)
|
|
return NewHandshake(WakuPairing, myStaticKeypair, myEphemeralKeypair, prologue, nil, []*NoisePublicKey{pubK}, initiator)
|
|
}
|