// Copyright 2019 The Waku Library Authors. // // The Waku library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The Waku library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty off // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the Waku library. If not, see . // // This software uses the go-ethereum library, which is licensed // under the GNU Lesser General Public Library, version 3 or any later. package waku import ( "crypto/aes" "crypto/cipher" "crypto/ecdsa" crand "crypto/rand" "encoding/binary" "errors" mrand "math/rand" "strconv" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/prometheus/common/log" ) // MessageParams specifies the exact way a message should be wrapped // into an Envelope. type MessageParams struct { TTL uint32 Src *ecdsa.PrivateKey Dst *ecdsa.PublicKey KeySym []byte Topic TopicType WorkTime uint32 PoW float64 Payload []byte Padding []byte } // SentMessage represents an end-user data packet to transmit through the // Waku protocol. These are wrapped into Envelopes that need not be // understood by intermediate nodes, just forwarded. type sentMessage struct { Raw []byte } // ReceivedMessage represents a data packet to be received through the // Waku protocol and successfully decrypted. type ReceivedMessage struct { Raw []byte Payload []byte Padding []byte Signature []byte Salt []byte PoW float64 // Proof of work as described in the Waku spec Sent uint32 // Time when the message was posted into the network TTL uint32 // Maximum time to live allowed for the message Src *ecdsa.PublicKey // Message recipient (identity used to decode the message) Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message) Topic TopicType SymKeyHash common.Hash // The Keccak256Hash of the key EnvelopeHash common.Hash // Message envelope hash to act as a unique id P2P bool // is set to true if this message was received from mail server. } func isMessageSigned(flags byte) bool { return (flags & signatureFlag) != 0 } func (msg *ReceivedMessage) isSymmetricEncryption() bool { return msg.SymKeyHash != common.Hash{} } func (msg *ReceivedMessage) isAsymmetricEncryption() bool { return msg.Dst != nil } // NewSentMessage creates and initializes a non-signed, non-encrypted Waku message. func NewSentMessage(params *MessageParams) (*sentMessage, error) { const payloadSizeFieldMaxSize = 4 msg := sentMessage{} msg.Raw = make([]byte, 1, flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) msg.Raw[0] = 0 // set all the flags to zero msg.addPayloadSizeField(params.Payload) msg.Raw = append(msg.Raw, params.Payload...) err := msg.appendPadding(params) return &msg, err } // addPayloadSizeField appends the auxiliary field containing the size of payload func (msg *sentMessage) addPayloadSizeField(payload []byte) { fieldSize := getSizeOfPayloadSizeField(payload) field := make([]byte, 4) binary.LittleEndian.PutUint32(field, uint32(len(payload))) field = field[:fieldSize] msg.Raw = append(msg.Raw, field...) msg.Raw[0] |= byte(fieldSize) } // 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 (msg *sentMessage) appendPadding(params *MessageParams) error { if len(params.Padding) != 0 { // padding data was provided by the Dapp, just use it as is msg.Raw = append(msg.Raw, params.Padding...) return nil } rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload) if params.Src != nil { rawSize += signatureLength } odd := rawSize % padSizeLimit paddingSize := padSizeLimit - odd pad := make([]byte, paddingSize) _, err := crand.Read(pad) if err != nil { return err } if !validateDataIntegrity(pad, paddingSize) { return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize)) } msg.Raw = append(msg.Raw, pad...) return nil } // sign calculates and sets the cryptographic signature for the message, // also setting the sign flag. func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error { if isMessageSigned(msg.Raw[0]) { // this should not happen, but no reason to panic log.Error("failed to sign the message: already signed") return nil } msg.Raw[0] |= signatureFlag // it is important to set this flag before signing hash := crypto.Keccak256(msg.Raw) signature, err := crypto.Sign(hash, key) if err != nil { msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag return err } msg.Raw = append(msg.Raw, signature...) return nil } // encryptAsymmetric encrypts a message with a public key. func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { if !ValidatePublicKey(key) { return errors.New("invalid public key provided for asymmetric encryption") } encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil) if err == nil { msg.Raw = encrypted } return err } // encryptSymmetric encrypts a message with a topic key, using AES-GCM-256. // nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { if !validateDataIntegrity(key, aesKeyLength) { return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key))) } block, err := aes.NewCipher(key) if err != nil { return err } aesgcm, err := cipher.NewGCM(block) if err != nil { return err } salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key if err != nil { return err } encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil) msg.Raw = append(encrypted, salt...) return nil } // generateSecureRandomData generates random data where extra security is required. // The purpose of this function is to prevent some bugs in software or in hardware // from delivering not-very-random data. This is especially useful for AES nonce, // where true randomness does not really matter, but it is very important to have // a unique nonce for every message. func generateSecureRandomData(length int) ([]byte, error) { x := make([]byte, length) y := make([]byte, length) res := make([]byte, length) _, err := crand.Read(x) if err != nil { return nil, err } else if !validateDataIntegrity(x, length) { return nil, errors.New("crypto/rand failed to generate secure random data") } _, err = mrand.Read(y) if err != nil { return nil, err } else if !validateDataIntegrity(y, length) { return nil, errors.New("math/rand failed to generate secure random data") } for i := 0; i < length; i++ { res[i] = x[i] ^ y[i] } if !validateDataIntegrity(res, length) { return nil, errors.New("failed to generate secure random data") } return res, nil } // Wrap bundles the message into an Envelope to transmit over the network. func (msg *sentMessage) Wrap(options *MessageParams, now time.Time) (envelope *Envelope, err error) { if options.TTL == 0 { options.TTL = DefaultTTL } if options.Src != nil { if err = msg.sign(options.Src); err != nil { return nil, err } } if options.Dst != nil { err = msg.encryptAsymmetric(options.Dst) } else if options.KeySym != nil { err = msg.encryptSymmetric(options.KeySym) } else { err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") } if err != nil { return nil, err } envelope = NewEnvelope(options.TTL, options.Topic, msg, now) if err = envelope.Seal(options); err != nil { return nil, err } return envelope, nil } // decryptSymmetric decrypts a message with a topic key, using AES-GCM-256. // nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). func (msg *ReceivedMessage) decryptSymmetric(key []byte) error { // symmetric messages are expected to contain the 12-byte nonce at the end of the payload if len(msg.Raw) < aesNonceLength { return errors.New("missing salt or invalid payload in symmetric message") } salt := msg.Raw[len(msg.Raw)-aesNonceLength:] block, err := aes.NewCipher(key) if err != nil { return err } aesgcm, err := cipher.NewGCM(block) if err != nil { return err } decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil) if err != nil { return err } msg.Raw = decrypted msg.Salt = salt return nil } // decryptAsymmetric decrypts an encrypted payload with a private key. func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error { decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil) if err == nil { msg.Raw = decrypted } return err } // ValidateAndParse checks the message validity and extracts the fields in case of success. func (msg *ReceivedMessage) ValidateAndParse() bool { end := len(msg.Raw) if end < 1 { return false } if isMessageSigned(msg.Raw[0]) { end -= signatureLength if end <= 1 { return false } msg.Signature = msg.Raw[end : end+signatureLength] msg.Src = msg.SigToPubKey() if msg.Src == nil { return false } } beg := 1 payloadSize := 0 sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload if sizeOfPayloadSizeField != 0 { if end < beg+sizeOfPayloadSizeField { return false } payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField])) beg += sizeOfPayloadSizeField if beg+payloadSize > end { return false } msg.Payload = msg.Raw[beg : beg+payloadSize] } beg += payloadSize msg.Padding = msg.Raw[beg:end] return true } // SigToPubKey returns the public key associated to the message's // signature. func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { defer func() { recover() }() // in case of invalid signature pub, err := crypto.SigToPub(msg.hash(), msg.Signature) if err != nil { log.Error("failed to recover public key from signature", "err", err) return nil } return pub } // hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding. func (msg *ReceivedMessage) hash() []byte { if isMessageSigned(msg.Raw[0]) { sz := len(msg.Raw) - signatureLength return crypto.Keccak256(msg.Raw[:sz]) } return crypto.Keccak256(msg.Raw) }