From 631da856a57501e1049a1de4d5c148deffdbfcae Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 13 Apr 2017 17:38:09 +0200 Subject: [PATCH] whisper: padding format updated --- whisper/whisperv5/doc.go | 13 +++-- whisper/whisperv5/message.go | 82 ++++++++++++++++++++++++------- whisper/whisperv5/message_test.go | 14 ++++-- whisper/whisperv5/whisper.go | 12 +++-- 4 files changed, 88 insertions(+), 33 deletions(-) diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index ed1d4e4b47..ad3e3f009a 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -49,17 +49,16 @@ const ( paddingMask = byte(3) signatureFlag = byte(4) - TopicLength = 4 - signatureLength = 65 - aesKeyLength = 32 - AESNonceMaxLength = 12 - keyIdSize = 32 + TopicLength = 4 + signatureLength = 65 + aesKeyLength = 32 + AESNonceLength = 12 + keyIdSize = 32 DefaultMaxMessageLength = 1024 * 1024 DefaultMinimumPoW = 1.0 // todo: review after testing. - padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature - padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility + padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol (must not exceed 2^24) messageQueueLimit = 1024 expirationCycle = time.Second diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index a34ea028e3..6051592824 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -23,6 +23,7 @@ import ( "crypto/cipher" "crypto/ecdsa" crand "crypto/rand" + "encoding/binary" "errors" "github.com/ethereum/go-ethereum/common" @@ -87,7 +88,7 @@ func (msg *ReceivedMessage) isAsymmetricEncryption() bool { // NewMessage creates and initializes a non-signed, non-encrypted Whisper message. func NewSentMessage(params *MessageParams) *SentMessage { msg := SentMessage{} - msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper) + msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimit) msg.Raw[0] = 0 // set all the flags to zero err := msg.appendPadding(params) if err != nil { @@ -98,35 +99,77 @@ func NewSentMessage(params *MessageParams) *SentMessage { return &msg } +// getSizeOfLength returns the number of bytes necessary to encode the entire size padding (including these bytes) +func getSizeOfLength(b []byte) (int, error) { + // prefer clarity over efficiency + var sizeOfLength int + len := len(b) + + // first iteration + if len < 256 { + sizeOfLength = 1 + } else if len < 256*256 { + sizeOfLength = 2 + } else if len < 256*256*256 { + sizeOfLength = 3 + } else { + return 0, errors.New("oversized padding parameter") + } + + // second iteration + total := len + sizeOfLength + if total < 256 { + sizeOfLength = 1 + } else if total < 256*256 { + sizeOfLength = 2 + } else if total < 256*256*256 { + sizeOfLength = 3 + } else { + return 0, errors.New("oversized padding parameter") + } + + return sizeOfLength, nil +} + // 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). func (msg *SentMessage) appendPadding(params *MessageParams) error { - total := len(params.Payload) + 1 + rawSize := len(params.Payload) + 1 if params.Src != nil { - total += signatureLength + rawSize += signatureLength } - padChunk := padSizeLimitUpper - if total <= padSizeLimitLower { - padChunk = padSizeLimitLower - } - odd := total % padChunk - if odd > 0 { - padSize := padChunk - odd - if padSize > 255 { - // this algorithm is only valid if padSizeLimitUpper <= 256. - // if padSizeLimitUpper will ever change, please fix the algorithm - // (for more information see ReceivedMessage.extractPadding() function). + 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, padSize) + buf := make([]byte, totalPadSize) _, err := crand.Read(buf[1:]) if err != nil { return err } - buf[0] = byte(padSize) - if params.Padding != nil { - copy(buf[1:], params.Padding) + if !validateSymmetricKey(buf) { + return errors.New("failed to generate random padding") } + buf[0] = byte(totalPadSize) msg.Raw = append(msg.Raw, buf...) msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size } @@ -292,7 +335,8 @@ func (msg *ReceivedMessage) Validate() bool { // can be successfully decrypted. func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { paddingSize := 0 - sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero + 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 { diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go index 0f805ee5b9..44a0d6c1cb 100644 --- a/whisper/whisperv5/message_test.go +++ b/whisper/whisperv5/message_test.go @@ -42,7 +42,7 @@ func generateMessageParams() (*MessageParams, error) { p.WorkTime = 1 p.TTL = uint32(mrand.Intn(1024)) p.Payload = make([]byte, sz) - p.Padding = make([]byte, padSizeLimitUpper) + p.Padding = make([]byte, padSizeLimit) p.KeySym = make([]byte, aesKeyLength) var b int @@ -289,21 +289,29 @@ func TestEncryptWithZeroKey(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) - params.KeySym = make([]byte, aesKeyLength) _, err = msg.Wrap(params) if err == nil { t.Fatalf("wrapped with zero key, seed: %d.", seed) } + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg = NewSentMessage(params) params.KeySym = make([]byte, 0) _, err = msg.Wrap(params) if err == nil { t.Fatalf("wrapped with empty key, seed: %d.", seed) } + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg = NewSentMessage(params) params.KeySym = nil _, err = msg.Wrap(params) if err == nil { diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index af24e6c31c..4be9803312 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -385,6 +385,9 @@ func (w *Whisper) Unsubscribe(id string) error { // network in the coming cycles. func (w *Whisper) Send(envelope *Envelope) error { ok, err := w.add(envelope) + if err != nil { + return err + } if !ok { return fmt.Errorf("failed to add envelope") } @@ -540,10 +543,11 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("oversized version [%x]", envelope.Hash()) } - if len(envelope.AESNonce) > AESNonceMaxLength { - // the standard AES GSM nonce size is 12, - // but const gcmStandardNonceSize cannot be accessed directly - return false, fmt.Errorf("oversized AESNonce [%x]", envelope.Hash()) + aesNonceSize := len(envelope.AESNonce) + if aesNonceSize != 0 && aesNonceSize != AESNonceLength { + // the standard AES GCM nonce size is 12 bytes, + // but constant gcmStandardNonceSize cannot be accessed (not exported) + return false, fmt.Errorf("wrong size of AESNonce: %d bytes [env: %x]", aesNonceSize, envelope.Hash()) } if envelope.PoW() < wh.minPoW {