aboutsummaryrefslogtreecommitdiffstats
path: root/whisper/whisperv6
diff options
context:
space:
mode:
authorGuillaume Ballet <gballet@gmail.com>2017-12-08 18:40:59 +0800
committerFelix Lange <fjl@users.noreply.github.com>2017-12-08 18:40:59 +0800
commitd95962cd5d3ea163f8e91d7a9ca2f6303799b2e2 (patch)
tree7255f029c04eb4e66e07b757c1ef44ecd1edb986 /whisper/whisperv6
parentb5874273cec729adce84acf5848536d20fb60f7c (diff)
downloaddexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar.gz
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar.bz2
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar.lz
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar.xz
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.tar.zst
dexon-d95962cd5d3ea163f8e91d7a9ca2f6303799b2e2.zip
whisper/whisperv6: remove aesnonce (#15578)
As per EIP-627, the salt for symmetric encryption is now part of the payload. This commit does that.
Diffstat (limited to 'whisper/whisperv6')
-rw-r--r--whisper/whisperv6/doc.go8
-rw-r--r--whisper/whisperv6/envelope.go52
-rw-r--r--whisper/whisperv6/envelope_test.go64
-rw-r--r--whisper/whisperv6/filter.go17
-rw-r--r--whisper/whisperv6/filter_test.go36
-rw-r--r--whisper/whisperv6/message.go46
-rw-r--r--whisper/whisperv6/message_test.go11
-rw-r--r--whisper/whisperv6/whisper.go7
8 files changed, 168 insertions, 73 deletions
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 <http://www.gnu.org/licenses/>.
+
+// 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(&params)
+ if err != nil {
+ t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
+ }
+
+ e, err := msg.Wrap(&params)
+ 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