mirror of https://github.com/status-im/go-waku.git
wakuv1 envelope format
This commit is contained in:
parent
65fc98f450
commit
100a26f49c
|
@ -5,11 +5,14 @@ import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
mrand "math/rand"
|
mrand "math/rand"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"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/protocol"
|
"github.com/status-im/go-waku/waku/v2/protocol"
|
||||||
)
|
)
|
||||||
|
@ -22,16 +25,122 @@ const (
|
||||||
None KeyKind = "None"
|
None KeyKind = "None"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KeyInfo struct {
|
// The message to encode
|
||||||
Kind KeyKind
|
type Payload struct {
|
||||||
SymKey []byte
|
Data []byte // Raw message payload
|
||||||
PrivKey ecdsa.PrivateKey
|
Padding []byte // Used to align data size, since data size alone might reveal important metainformation.
|
||||||
|
Key *KeyInfo // Contains the type of encryption to apply and the private key to use for signing the message
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTICE: Extracted from status-go
|
// The decoded payload of a received message.
|
||||||
|
type DecodedPayload struct {
|
||||||
|
Data []byte // Decoded message payload
|
||||||
|
Padding []byte // Used to align data size, since data size alone might reveal important metainformation.
|
||||||
|
PubKey *ecdsa.PublicKey // The public key that signed the payload
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyInfo struct {
|
||||||
|
Kind KeyKind // Indicates the type of encryption to use
|
||||||
|
SymKey []byte // If the encryption is Symmetric, a Symmetric key must be specified
|
||||||
|
PubKey ecdsa.PublicKey // If the encryption is Asymmetric, the public key of the message receptor must be specified
|
||||||
|
PrivKey *ecdsa.PrivateKey // Set a privkey if the message requires a signature
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (payload Payload) Encode(version uint32) ([]byte, error) {
|
||||||
|
switch version {
|
||||||
|
case 0:
|
||||||
|
return payload.Data, nil
|
||||||
|
case 1:
|
||||||
|
data, err := payload.v1Data()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Key.PrivKey != nil {
|
||||||
|
data, err = sign(data, *payload.Key.PrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch payload.Key.Kind {
|
||||||
|
case Symmetric:
|
||||||
|
encoded, err := encryptSymmetric(data, payload.Key.SymKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Couldn't encrypt using symmetric key")
|
||||||
|
} else {
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
case Asymmetric:
|
||||||
|
encoded, err := encryptAsymmetric(data, &payload.Key.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Couldn't encrypt using asymmetric key")
|
||||||
|
} else {
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
case None:
|
||||||
|
return nil, errors.New("Non supported KeyKind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Unsupported WakuMessage version")
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodePayload(message *protocol.WakuMessage, keyInfo *KeyInfo) (*DecodedPayload, error) {
|
||||||
|
switch *message.Version {
|
||||||
|
case uint32(0):
|
||||||
|
return &DecodedPayload{Data: message.Payload}, nil
|
||||||
|
case uint32(1):
|
||||||
|
switch keyInfo.Kind {
|
||||||
|
case Symmetric:
|
||||||
|
if keyInfo.SymKey == nil {
|
||||||
|
return nil, errors.New("Symmetric key is required")
|
||||||
|
}
|
||||||
|
fmt.Println("AAA")
|
||||||
|
|
||||||
|
decodedData, err := decryptSymmetric(message.Payload, keyInfo.SymKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Couldn't decrypt using symmetric key")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedPayload, err := validateAndParse(decodedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Println("AAA")
|
||||||
|
|
||||||
|
return decodedPayload, nil
|
||||||
|
case Asymmetric:
|
||||||
|
if keyInfo.PrivKey == nil {
|
||||||
|
return nil, errors.New("Private key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedData, err := decryptAsymmetric(message.Payload, keyInfo.PrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Couldn't decrypt using asymmetric key")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedPayload, err := validateAndParse(decodedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedPayload, nil
|
||||||
|
case None:
|
||||||
|
return nil, errors.New("Non supported KeyKind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Unsupported WakuMessage version")
|
||||||
|
}
|
||||||
|
|
||||||
const aesNonceLength = 12
|
const aesNonceLength = 12
|
||||||
const aesKeyLength = 32
|
const aesKeyLength = 32
|
||||||
|
const signatureFlag = byte(4)
|
||||||
|
const flagsLength = 1
|
||||||
|
const padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol
|
||||||
|
const signatureLength = 65
|
||||||
|
const sizeMask = byte(3)
|
||||||
|
|
||||||
// Decrypts a message with a topic key, using AES-GCM-256.
|
// Decrypts a message with a topic key, using AES-GCM-256.
|
||||||
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
|
||||||
|
@ -62,49 +171,23 @@ func decryptSymmetric(payload []byte, key []byte) ([]byte, error) {
|
||||||
// Decrypts an encrypted payload with a private key.
|
// Decrypts an encrypted payload with a private key.
|
||||||
func decryptAsymmetric(payload []byte, key *ecdsa.PrivateKey) ([]byte, error) {
|
func decryptAsymmetric(payload []byte, key *ecdsa.PrivateKey) ([]byte, error) {
|
||||||
decrypted, err := ecies.ImportECDSA(key).Decrypt(payload, nil, nil)
|
decrypted, err := ecies.ImportECDSA(key).Decrypt(payload, nil, nil)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return decrypted, err
|
return decrypted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodePayload(message *protocol.WakuMessage, keyInfo *KeyInfo) ([]byte, error) {
|
|
||||||
switch *message.Version {
|
|
||||||
case uint32(0):
|
|
||||||
return message.Payload, nil
|
|
||||||
case uint32(1):
|
|
||||||
switch keyInfo.Kind {
|
|
||||||
case Symmetric:
|
|
||||||
decoded, err := decryptSymmetric(message.Payload, keyInfo.SymKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Couldn't decrypt using symmetric key")
|
|
||||||
} else {
|
|
||||||
return decoded, nil
|
|
||||||
}
|
|
||||||
case Asymmetric:
|
|
||||||
decoded, err := decryptAsymmetric(message.Payload, &keyInfo.PrivKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Couldn't decrypt using asymmetric key")
|
|
||||||
} else {
|
|
||||||
return decoded, nil
|
|
||||||
}
|
|
||||||
case None:
|
|
||||||
return nil, errors.New("Non supported KeyKind")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Unsupported WakuMessage version")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatePublicKey checks the format of the given public key.
|
// ValidatePublicKey checks the format of the given public key.
|
||||||
func ValidatePublicKey(k *ecdsa.PublicKey) bool {
|
func validatePublicKey(k *ecdsa.PublicKey) bool {
|
||||||
return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
|
return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypts and returns with a public key.
|
// Encrypts and returns with a public key.
|
||||||
func encryptAsymmetric(rawPayload []byte, key *ecdsa.PublicKey) ([]byte, error) {
|
func encryptAsymmetric(rawPayload []byte, key *ecdsa.PublicKey) ([]byte, error) {
|
||||||
if !ValidatePublicKey(key) {
|
if !validatePublicKey(key) {
|
||||||
return nil, errors.New("invalid public key provided for asymmetric encryption")
|
return nil, errors.New("invalid public key provided for asymmetric encryption")
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), rawPayload, nil, nil)
|
encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), rawPayload, nil, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return encrypted, nil
|
return encrypted, nil
|
||||||
|
@ -187,29 +270,154 @@ func generateSecureRandomData(length int) ([]byte, error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Encode(rawPayload []byte, keyInfo *KeyInfo, version uint32) ([]byte, error) {
|
func isMessageSigned(flags byte) bool {
|
||||||
switch version {
|
return (flags & signatureFlag) != 0
|
||||||
case 0:
|
}
|
||||||
return rawPayload, nil
|
|
||||||
case 1:
|
// sign calculates the cryptographic signature for the message,
|
||||||
switch keyInfo.Kind {
|
// also setting the sign flag.
|
||||||
case Symmetric:
|
func sign(data []byte, privKey ecdsa.PrivateKey) ([]byte, error) {
|
||||||
encoded, err := encryptSymmetric(rawPayload, keyInfo.SymKey)
|
result := make([]byte, len(data))
|
||||||
if err != nil {
|
copy(result, data)
|
||||||
return nil, errors.New("Couldn't encrypt using symmetric key")
|
|
||||||
} else {
|
if isMessageSigned(result[0]) {
|
||||||
return encoded, nil
|
// this should not happen, but no reason to panic
|
||||||
}
|
return result, nil
|
||||||
case Asymmetric:
|
}
|
||||||
encoded, err := encryptAsymmetric(rawPayload, &keyInfo.PrivKey.PublicKey)
|
|
||||||
if err != nil {
|
result[0] |= signatureFlag // it is important to set this flag before signing
|
||||||
return nil, errors.New("Couldn't encrypt using asymmetric key")
|
hash := crypto.Keccak256(result)
|
||||||
} else {
|
signature, err := crypto.Sign(hash, &privKey)
|
||||||
return encoded, nil
|
|
||||||
}
|
if err != nil {
|
||||||
case None:
|
result[0] &= (0xFF ^ signatureFlag) // clear the flag
|
||||||
return nil, errors.New("Non supported KeyKind")
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
result = append(result, signature...)
|
||||||
return nil, errors.New("Unsupported WakuMessage version")
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (payload Payload) v1Data() ([]byte, error) {
|
||||||
|
const payloadSizeFieldMaxSize = 4
|
||||||
|
result := make([]byte, 1, flagsLength+payloadSizeFieldMaxSize+len(payload.Data)+len(payload.Padding)+signatureLength+padSizeLimit)
|
||||||
|
result[0] = 0 // set all the flags to zero
|
||||||
|
result = payload.addPayloadSizeField(result)
|
||||||
|
result = append(result, payload.Data...)
|
||||||
|
result, err := payload.appendPadding(result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// addPayloadSizeField appends the auxiliary field containing the size of payload
|
||||||
|
func (payload Payload) addPayloadSizeField(input []byte) []byte {
|
||||||
|
fieldSize := getSizeOfPayloadSizeField(payload.Data)
|
||||||
|
field := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(field, uint32(len(payload.Data)))
|
||||||
|
field = field[:fieldSize]
|
||||||
|
result := append(input, field...)
|
||||||
|
result[0] |= byte(fieldSize)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload
|
||||||
|
func getSizeOfPayloadSizeField(payload []byte) int {
|
||||||
|
s := 1
|
||||||
|
for i := len(payload); i >= 256; i /= 256 {
|
||||||
|
s++
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendPadding appends the padding specified in params.
|
||||||
|
// If no padding is provided in params, then random padding is generated.
|
||||||
|
func (payload Payload) appendPadding(input []byte) ([]byte, error) {
|
||||||
|
if len(payload.Padding) != 0 {
|
||||||
|
// padding data was provided by the Dapp, just use it as is
|
||||||
|
result := append(input, payload.Padding...)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSize := flagsLength + getSizeOfPayloadSizeField(payload.Data) + len(payload.Data)
|
||||||
|
if payload.Key.PrivKey != nil {
|
||||||
|
rawSize += signatureLength
|
||||||
|
}
|
||||||
|
odd := rawSize % padSizeLimit
|
||||||
|
paddingSize := padSizeLimit - odd
|
||||||
|
pad := make([]byte, paddingSize)
|
||||||
|
_, err := crand.Read(pad)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !validateDataIntegrity(pad, paddingSize) {
|
||||||
|
return nil, errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize))
|
||||||
|
}
|
||||||
|
result := append(input, pad...)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAndParse(input []byte) (*DecodedPayload, error) {
|
||||||
|
end := len(input)
|
||||||
|
if end < 1 {
|
||||||
|
return nil, errors.New("invalid message length")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := new(DecodedPayload)
|
||||||
|
|
||||||
|
if isMessageSigned(input[0]) {
|
||||||
|
end -= signatureLength
|
||||||
|
if end <= 1 {
|
||||||
|
return nil, errors.New("invalid message length")
|
||||||
|
}
|
||||||
|
msg.Signature = input[end : end+signatureLength]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
msg.PubKey, err = msg.sigToPubKey(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beg := 1
|
||||||
|
payloadSize := 0
|
||||||
|
sizeOfPayloadSizeField := int(input[0] & sizeMask) // number of bytes indicating the size of payload
|
||||||
|
|
||||||
|
if sizeOfPayloadSizeField != 0 {
|
||||||
|
if end < beg+sizeOfPayloadSizeField {
|
||||||
|
return nil, errors.New("invalid message length")
|
||||||
|
}
|
||||||
|
payloadSize = int(bytesToUintLittleEndian(input[beg : beg+sizeOfPayloadSizeField]))
|
||||||
|
beg += sizeOfPayloadSizeField
|
||||||
|
if beg+payloadSize > end {
|
||||||
|
return nil, errors.New("invalid message length")
|
||||||
|
}
|
||||||
|
msg.Data = input[beg : beg+payloadSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
beg += payloadSize
|
||||||
|
msg.Padding = input[beg:end]
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigToPubKey returns the public key associated to the message's
|
||||||
|
// signature.
|
||||||
|
func (p *DecodedPayload) sigToPubKey(input []byte) (*ecdsa.PublicKey, error) {
|
||||||
|
defer func() { _ = recover() }() // in case of invalid signature
|
||||||
|
hash := crypto.Keccak256(input[0 : len(input)-signatureLength])
|
||||||
|
pub, err := crypto.SigToPub(hash, p.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer.
|
||||||
|
func bytesToUintLittleEndian(b []byte) (res uint64) {
|
||||||
|
mul := uint64(1)
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
res += uint64(b[i]) * mul
|
||||||
|
mul *= 256
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue