From 0d31e257e094fcee69e64c189a5a347fcd37fb09 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 28 Nov 2017 17:38:06 +0100 Subject: [PATCH] whisper: remove AESNonce from envelope in v6 The aes nonce is now part of the payload of a symmetric message. Since there is no futher available indication whether a message is symmetric or not, functions isSymmetric and isAsymmetric have been removed and a symmetric topic match will be attempted if the filter is symmetric. Accordingly, other checks have been removed at the top level, because they can no longer be performed until it is known for a fact that the message uses symmetric encryption. --- whisper/whisperv6/envelope.go | 24 +++++++--------- whisper/whisperv6/filter.go | 13 +++++---- whisper/whisperv6/filter_test.go | 6 ---- whisper/whisperv6/message.go | 46 ++++++++++++++++++------------- whisper/whisperv6/message_test.go | 4 +-- whisper/whisperv6/whisper.go | 7 ----- 6 files changed, 45 insertions(+), 55 deletions(-) diff --git a/whisper/whisperv6/envelope.go b/whisper/whisperv6/envelope.go index c93ab84a28..58c56bceea 100644 --- a/whisper/whisperv6/envelope.go +++ b/whisper/whisperv6/envelope.go @@ -40,7 +40,6 @@ type Envelope struct { Expiry uint32 TTL uint32 Topic TopicType - AESNonce []byte Data []byte Nonce uint64 @@ -51,12 +50,12 @@ type Envelope struct { // size returns the size of envelope as it is sent (i.e. public fields only) func (e *Envelope) size() int { - return 20 + len(e.Version) + len(e.AESNonce) + len(e.Data) + return 20 + len(e.Version) + len(e.Data) } // rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce. func (e *Envelope) rlpWithoutNonce() []byte { - res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.AESNonce, e.Data}) + res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.Data}) return res } @@ -82,14 +81,6 @@ func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage) return &env } -func (e *Envelope) IsSymmetric() bool { - return len(e.AESNonce) > 0 -} - -func (e *Envelope) isAsymmetric() bool { - return !e.IsSymmetric() -} - func (e *Envelope) Ver() uint64 { return bytesToUintLittleEndian(e.Version) } @@ -209,7 +200,7 @@ func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, erro // OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key. func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { msg = &ReceivedMessage{Raw: e.Data} - err = msg.decryptSymmetric(key, e.AESNonce) + err = msg.decryptSymmetric(key) if err != nil { msg = nil } @@ -218,12 +209,17 @@ func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { // Open tries to decrypt an envelope, and populates the message fields in case of success. func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) { - if e.isAsymmetric() { + // if the filter has some asymmetric key provided, it will + // attempt to open it asymmetrically. If this fails, then + // attempt to open it symmetrically. + if watcher.KeyAsym != nil { msg, _ = e.OpenAsymmetric(watcher.KeyAsym) if msg != nil { msg.Dst = &watcher.KeyAsym.PublicKey } - } else if e.IsSymmetric() { + } + + if msg == nil { msg, _ = e.OpenSymmetric(watcher.KeySym) if msg != nil { msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) diff --git a/whisper/whisperv6/filter.go b/whisper/whisperv6/filter.go index 5cb371b7d6..2786f97a09 100644 --- a/whisper/whisperv6/filter.go +++ b/whisper/whisperv6/filter.go @@ -175,6 +175,9 @@ func (f *Filter) Retrieve() (all []*ReceivedMessage) { return all } +// MatchMessage checks if the filter matches an already decrypted +// message (i.e. a Message that has already been handled by +// MatchEnvelope when checked by a previous filter) func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { if f.PoW > 0 && msg.PoW < f.PoW { return false @@ -188,17 +191,15 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { return false } +// MatchEvelope checks if it's worth decrypting the message. If +// it returns `true`, client code is expected to attempt decrypting +// the message and subsequently call MatchMessage. func (f *Filter) MatchEnvelope(envelope *Envelope) bool { if f.PoW > 0 && envelope.pow < f.PoW { return false } - if f.expectsAsymmetricEncryption() && envelope.isAsymmetric() { - return f.MatchTopic(envelope.Topic) - } else if f.expectsSymmetricEncryption() && envelope.IsSymmetric() { - return f.MatchTopic(envelope.Topic) - } - return false + return f.MatchTopic(envelope.Topic) } func (f *Filter) MatchTopic(topic TopicType) bool { diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go index 58d90d60c2..652efc12e8 100644 --- a/whisper/whisperv6/filter_test.go +++ b/whisper/whisperv6/filter_test.go @@ -312,12 +312,6 @@ func TestMatchEnvelope(t *testing.T) { t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed) } - // asymmetric + matching topic: mismatch - match = fasym.MatchEnvelope(env) - if match { - t.Fatalf("failed MatchEnvelope() asymmetric with seed %d.", seed) - } - // symmetric + matching topic + insufficient PoW: mismatch fsym.PoW = env.PoW() + 1.0 match = fsym.MatchEnvelope(env) diff --git a/whisper/whisperv6/message.go b/whisper/whisperv6/message.go index 0815f07a2d..2f39afda64 100644 --- a/whisper/whisperv6/message.go +++ b/whisper/whisperv6/message.go @@ -61,6 +61,7 @@ type ReceivedMessage struct { Payload []byte Padding []byte Signature []byte + Salt []byte PoW float64 // Proof of work as described in the Whisper spec Sent uint32 // Time when the message was posted into the network @@ -196,31 +197,31 @@ 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) (nonce []byte, err error) { +func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { if !validateSymmetricKey(key) { - return nil, errors.New("invalid key provided for symmetric encryption") + return errors.New("invalid key provided for symmetric encryption") } block, err := aes.NewCipher(key) if err != nil { - return nil, err + return err } aesgcm, err := cipher.NewGCM(block) if err != nil { - return nil, err + return err } // never use more than 2^32 random nonces with a given key - nonce = make([]byte, aesgcm.NonceSize()) - _, err = crand.Read(nonce) + salt := make([]byte, aesgcm.NonceSize()) + _, err = crand.Read(salt) if err != nil { - return nil, err - } else if !validateSymmetricKey(nonce) { - return nil, errors.New("crypto/rand failed to generate nonce") + return err + } else if !validateSymmetricKey(salt) { + return errors.New("crypto/rand failed to generate salt") } - msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil) - return nonce, nil + msg.Raw = append(aesgcm.Seal(nil, salt, msg.Raw, nil), salt...) + return nil } // Wrap bundles the message into an Envelope to transmit over the network. @@ -233,11 +234,10 @@ func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er return nil, err } } - var nonce []byte if options.Dst != nil { err = msg.encryptAsymmetric(options.Dst) } else if options.KeySym != nil { - nonce, err = msg.encryptSymmetric(options.KeySym) + err = msg.encryptSymmetric(options.KeySym) } else { err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") } @@ -245,7 +245,7 @@ func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er return nil, err } - envelope = NewEnvelope(options.TTL, options.Topic, nonce, msg) + envelope = NewEnvelope(options.TTL, options.Topic, msg) if err = envelope.Seal(options); err != nil { return nil, err } @@ -254,7 +254,14 @@ 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, nonce []byte) error { +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 { + 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 @@ -263,15 +270,16 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, nonce []byte) error { if err != nil { return err } - if len(nonce) != aesgcm.NonceSize() { - log.Error("decrypting the message", "AES nonce size", len(nonce)) - return errors.New("wrong AES nonce size") + 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, nonce, msg.Raw, nil) + 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 } diff --git a/whisper/whisperv6/message_test.go b/whisper/whisperv6/message_test.go index 912b90f145..f4c16b256f 100644 --- a/whisper/whisperv6/message_test.go +++ b/whisper/whisperv6/message_test.go @@ -174,10 +174,8 @@ func TestMessageSeal(t *testing.T) { t.Fatalf("failed to create new message with seed %d: %s.", seed, err) } params.TTL = 1 - aesnonce := make([]byte, 12) - mrand.Read(aesnonce) - env := NewEnvelope(params.TTL, params.Topic, aesnonce, msg) + env := NewEnvelope(params.TTL, params.Topic, msg) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index e2b884f3d4..0ed82a0ff9 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -591,13 +591,6 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("oversized version [%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() { log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex()) return false, nil // drop envelope without error