go-waku/waku/v2/noise/payload.go

307 lines
10 KiB
Go

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
}