go-noise/patterns.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)
}