aboutsummaryrefslogtreecommitdiffstats
path: root/whisper/whisperv6/message.go
diff options
context:
space:
mode:
Diffstat (limited to 'whisper/whisperv6/message.go')
-rw-r--r--whisper/whisperv6/message.go206
1 files changed, 98 insertions, 108 deletions
diff --git a/whisper/whisperv6/message.go b/whisper/whisperv6/message.go
index b5c8279b1..7def35f14 100644
--- a/whisper/whisperv6/message.go
+++ b/whisper/whisperv6/message.go
@@ -25,6 +25,7 @@ import (
crand "crypto/rand"
"encoding/binary"
"errors"
+ mrand "math/rand"
"strconv"
"github.com/ethereum/go-ethereum/common"
@@ -55,7 +56,7 @@ type sentMessage struct {
}
// ReceivedMessage represents a data packet to be received through the
-// Whisper protocol.
+// Whisper protocol and successfully decrypted.
type ReceivedMessage struct {
Raw []byte
@@ -71,7 +72,7 @@ type ReceivedMessage struct {
Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message)
Topic TopicType
- SymKeyHash common.Hash // The Keccak256Hash of the key, associated with the Topic
+ SymKeyHash common.Hash // The Keccak256Hash of the key
EnvelopeHash common.Hash // Message envelope hash to act as a unique id
}
@@ -89,81 +90,60 @@ func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message.
func newSentMessage(params *MessageParams) (*sentMessage, error) {
+ const payloadSizeFieldMaxSize = 4
msg := sentMessage{}
- msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
+ 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
- err := msg.appendPadding(params)
- if err != nil {
- return nil, err
- }
+ msg.addPayloadSizeField(params.Payload)
msg.Raw = append(msg.Raw, params.Payload...)
- return &msg, nil
+ err := msg.appendPadding(params)
+ return &msg, err
}
-// getSizeOfLength returns the number of bytes necessary to encode the entire size padding (including these bytes)
-func getSizeOfLength(b []byte) (sz int, err error) {
- sz = intSize(len(b)) // first iteration
- sz = intSize(len(b) + sz) // second iteration
- if sz > 3 {
- err = errors.New("oversized padding parameter")
- }
- return sz, 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)
}
-// sizeOfIntSize returns minimal number of bytes necessary to encode an integer value
-func intSize(i int) (s int) {
- for s = 1; i >= 256; s++ {
- i /= 256
+// 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 pseudorandom padding bytes and sets the padding flag.
-// The last byte contains the size of padding (thus, its size must not exceed 256).
+// 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 {
- rawSize := len(params.Payload) + 1
- if params.Src != nil {
- rawSize += signatureLength
+ 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
}
- if params.KeySym != nil {
- rawSize += AESNonceLength
+ rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload)
+ if params.Src != nil {
+ rawSize += signatureLength
}
odd := rawSize % padSizeLimit
-
- if len(params.Padding) != 0 {
- padSize := len(params.Padding)
- padLengthSize, err := getSizeOfLength(params.Padding)
- if err != nil {
- return err
- }
- totalPadSize := padSize + padLengthSize
- buf := make([]byte, 8)
- binary.LittleEndian.PutUint32(buf, uint32(totalPadSize))
- buf = buf[:padLengthSize]
- msg.Raw = append(msg.Raw, buf...)
- msg.Raw = append(msg.Raw, params.Padding...)
- msg.Raw[0] |= byte(padLengthSize) // number of bytes indicating the padding size
- } else if odd != 0 {
- totalPadSize := padSizeLimit - odd
- if totalPadSize > 255 {
- // this algorithm is only valid if padSizeLimit < 256.
- // if padSizeLimit will ever change, please fix the algorithm
- // (please see also ReceivedMessage.extractPadding() function).
- panic("please fix the padding algorithm before releasing new version")
- }
- buf := make([]byte, totalPadSize)
- _, err := crand.Read(buf[1:])
- if err != nil {
- return err
- }
- if totalPadSize > 6 && !validateSymmetricKey(buf) {
- return errors.New("failed to generate random padding of size " + strconv.Itoa(totalPadSize))
- }
- buf[0] = byte(totalPadSize)
- msg.Raw = append(msg.Raw, buf...)
- msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
+ 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
}
@@ -176,11 +156,11 @@ func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error {
return nil
}
- msg.Raw[0] |= signatureFlag
+ 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] &= ^signatureFlag // clear the flag
+ msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag
return err
}
msg.Raw = append(msg.Raw, signature...)
@@ -202,10 +182,9 @@ func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
// 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 !validateSymmetricKey(key) {
- return errors.New("invalid key provided for symmetric encryption")
+ 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
@@ -214,20 +193,46 @@ func (msg *sentMessage) encryptSymmetric(key []byte) (err error) {
if err != nil {
return err
}
-
- // never use more than 2^32 random nonces with a given key
- salt := make([]byte, aesgcm.NonceSize())
- _, err = crand.Read(salt)
+ salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key
if err != nil {
return err
- } else if !validateSymmetricKey(salt) {
- return errors.New("crypto/rand failed to generate salt")
}
-
- msg.Raw = append(aesgcm.Seal(nil, salt, msg.Raw, nil), salt...)
+ 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) (envelope *Envelope, err error) {
if options.TTL == 0 {
@@ -259,12 +264,11 @@ func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
// 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 {
- // In v6, symmetric messages are expected to contain the 12-byte
- // "salt" at the end of the payload.
- if len(msg.Raw) < AESNonceLength {
+ // 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:]
+ salt := msg.Raw[len(msg.Raw)-aesNonceLength:]
block, err := aes.NewCipher(key)
if err != nil {
@@ -274,11 +278,7 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
if err != nil {
return err
}
- if len(salt) != aesgcm.NonceSize() {
- log.Error("decrypting the message", "AES salt size", len(salt))
- return errors.New("wrong AES salt size")
- }
- decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-AESNonceLength], nil)
+ decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil)
if err != nil {
return err
}
@@ -296,8 +296,8 @@ func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
return err
}
-// Validate checks the validity and extracts the fields in case of success
-func (msg *ReceivedMessage) Validate() bool {
+// 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
@@ -308,40 +308,30 @@ func (msg *ReceivedMessage) Validate() bool {
if end <= 1 {
return false
}
- msg.Signature = msg.Raw[end:]
+ msg.Signature = msg.Raw[end : end+signatureLength]
msg.Src = msg.SigToPubKey()
if msg.Src == nil {
return false
}
}
- padSize, ok := msg.extractPadding(end)
- if !ok {
- return false
+ beg := 1
+ payloadSize := 0
+ sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload
+ if sizeOfPayloadSizeField != 0 {
+ payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField]))
+ if payloadSize+1 > end {
+ return false
+ }
+ beg += sizeOfPayloadSizeField
+ msg.Payload = msg.Raw[beg : beg+payloadSize]
}
- msg.Payload = msg.Raw[1+padSize : end]
+ beg += payloadSize
+ msg.Padding = msg.Raw[beg:end]
return true
}
-// extractPadding extracts the padding from raw message.
-// although we don't support sending messages with padding size
-// exceeding 255 bytes, such messages are perfectly valid, and
-// can be successfully decrypted.
-func (msg *ReceivedMessage) extractPadding(end int) (int, bool) {
- paddingSize := 0
- sz := int(msg.Raw[0] & paddingMask) // number of bytes indicating the entire size of padding (including these bytes)
- // could be zero -- it means no padding
- if sz != 0 {
- paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz]))
- if paddingSize < sz || paddingSize+1 > end {
- return 0, false
- }
- msg.Padding = msg.Raw[1+sz : 1+paddingSize]
- }
- return paddingSize, true
-}
-
// SigToPubKey returns the public key associated to the message's
// signature.
func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
@@ -355,7 +345,7 @@ func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
return pub
}
-// hash calculates the SHA3 checksum of the message flags, payload and padding.
+// 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