From d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 8 Dec 2017 11:40:59 +0100 Subject: whisper/whisperv6: remove aesnonce (#15578) As per EIP-627, the salt for symmetric encryption is now part of the payload. This commit does that. --- whisper/whisperv6/doc.go | 8 +++-- whisper/whisperv6/envelope.go | 52 ++++++++++++++----------------- whisper/whisperv6/envelope_test.go | 64 ++++++++++++++++++++++++++++++++++++++ whisper/whisperv6/filter.go | 17 ++++++---- whisper/whisperv6/filter_test.go | 36 +++++++++++++++++---- whisper/whisperv6/message.go | 46 ++++++++++++++++----------- whisper/whisperv6/message_test.go | 11 ++++--- whisper/whisperv6/whisper.go | 7 ----- 8 files changed, 168 insertions(+), 73 deletions(-) create mode 100644 whisper/whisperv6/envelope_test.go (limited to 'whisper') diff --git a/whisper/whisperv6/doc.go b/whisper/whisperv6/doc.go index e64dd2f42..64925ba48 100644 --- a/whisper/whisperv6/doc.go +++ b/whisper/whisperv6/doc.go @@ -36,15 +36,15 @@ import ( const ( EnvelopeVersion = uint64(0) - ProtocolVersion = uint64(5) - ProtocolVersionStr = "5.0" + ProtocolVersion = uint64(6) + ProtocolVersionStr = "6.0" ProtocolName = "shh" statusCode = 0 // used by whisper protocol messagesCode = 1 // normal whisper message p2pCode = 2 // peer-to-peer message (to be consumed by the peer, but not forwarded any further) p2pRequestCode = 3 // peer-to-peer message, used by Dapp protocol - NumberOfMessageCodes = 64 + NumberOfMessageCodes = 128 paddingMask = byte(3) signatureFlag = byte(4) @@ -67,6 +67,8 @@ const ( DefaultTTL = 50 // seconds SynchAllowance = 10 // seconds + + EnvelopeHeaderLength = 20 ) type unknownVersionError uint64 diff --git a/whisper/whisperv6/envelope.go b/whisper/whisperv6/envelope.go index c93ab84a2..c8b56ff36 100644 --- a/whisper/whisperv6/envelope.go +++ b/whisper/whisperv6/envelope.go @@ -36,13 +36,12 @@ import ( // Envelope represents a clear-text data packet to transmit through the Whisper // network. Its contents may or may not be encrypted and signed. type Envelope struct { - Version []byte - Expiry uint32 - TTL uint32 - Topic TopicType - AESNonce []byte - Data []byte - Nonce uint64 + Version []byte + Expiry uint32 + TTL uint32 + Topic TopicType + Data []byte + Nonce uint64 pow float64 // Message-specific PoW as described in the Whisper specification. hash common.Hash // Cached hash of the envelope to avoid rehashing every time. @@ -51,26 +50,25 @@ 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 EnvelopeHeaderLength + 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 } // NewEnvelope wraps a Whisper message with expiration and destination data // included into an envelope for network forwarding. -func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage) *Envelope { +func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage) *Envelope { env := Envelope{ - Version: make([]byte, 1), - Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()), - TTL: ttl, - Topic: topic, - AESNonce: aesNonce, - Data: msg.Raw, - Nonce: 0, + Version: make([]byte, 1), + Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()), + TTL: ttl, + Topic: topic, + Data: msg.Raw, + Nonce: 0, } if EnvelopeVersion < 256 { @@ -82,14 +80,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 +199,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 +208,18 @@ 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() { + // The API interface forbids filters doing both symmetric and + // asymmetric encryption. + if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() { + return nil + } + + if watcher.expectsAsymmetricEncryption() { msg, _ = e.OpenAsymmetric(watcher.KeyAsym) if msg != nil { msg.Dst = &watcher.KeyAsym.PublicKey } - } else if e.IsSymmetric() { + } else if watcher.expectsSymmetricEncryption() { msg, _ = e.OpenSymmetric(watcher.KeySym) if msg != nil { msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) diff --git a/whisper/whisperv6/envelope_test.go b/whisper/whisperv6/envelope_test.go new file mode 100644 index 000000000..410b250a3 --- /dev/null +++ b/whisper/whisperv6/envelope_test.go @@ -0,0 +1,64 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// 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 go-ethereum library. If not, see . + +// Contains the tests associated with the Whisper protocol Envelope object. + +package whisperv6 + +import ( + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) { + symKey := make([]byte, aesKeyLength) + mrand.Read(symKey) + + asymKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + + params := MessageParams{ + PoW: 0.01, + WorkTime: 1, + TTL: uint32(mrand.Intn(1024)), + Payload: make([]byte, 50), + KeySym: symKey, + Dst: nil, + } + + mrand.Read(params.Payload) + + msg, err := NewSentMessage(¶ms) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + + e, err := msg.Wrap(¶ms) + if err != nil { + t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err) + } + + f := Filter{KeySym: symKey, KeyAsym: asymKey} + + decrypted := e.Open(&f) + if decrypted != nil { + t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed) + } +} diff --git a/whisper/whisperv6/filter.go b/whisper/whisperv6/filter.go index 5cb371b7d..2f52dd6b9 100644 --- a/whisper/whisperv6/filter.go +++ b/whisper/whisperv6/filter.go @@ -53,6 +53,10 @@ func NewFilters(w *Whisper) *Filters { } func (fs *Filters) Install(watcher *Filter) (string, error) { + if watcher.KeySym != nil && watcher.KeyAsym != nil { + return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys") + } + if watcher.Messages == nil { watcher.Messages = make(map[common.Hash]*ReceivedMessage) } @@ -175,6 +179,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 +195,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 58d90d60c..dd0de0f6e 100644 --- a/whisper/whisperv6/filter_test.go +++ b/whisper/whisperv6/filter_test.go @@ -229,6 +229,36 @@ func TestInstallIdenticalFilters(t *testing.T) { } } +func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { + InitSingleTest() + + w := New(&Config{}) + filters := NewFilters(w) + filter1, _ := generateFilter(t, true) + + asymKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Unable to create asymetric keys: %v", err) + } + + // Copy the first filter since some of its fields + // are randomly gnerated. + filter := &Filter{ + KeySym: filter1.KeySym, + KeyAsym: asymKey, + Topics: filter1.Topics, + PoW: filter1.PoW, + AllowP2P: filter1.AllowP2P, + Messages: make(map[common.Hash]*ReceivedMessage), + } + + _, err = filters.Install(filter) + + if err == nil { + t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed) + } +} + func TestComparePubKey(t *testing.T) { InitSingleTest() @@ -312,12 +342,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 0815f07a2..2f39afda6 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 912b90f14..281a852d6 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) } @@ -242,7 +240,12 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } - f := Filter{KeyAsym: key, KeySym: params.KeySym} + var f Filter + if symmetric { + f = Filter{KeySym: params.KeySym} + } else { + f = Filter{KeyAsym: key} + } decrypted := env.Open(&f) if decrypted == nil { t.Fatalf("failed to open with seed %d.", seed) diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index e2b884f3d..0ed82a0ff 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 -- cgit v1.2.3