Prechádzať zdrojové kódy

whisper/whisperv6: remove aesnonce (#15578)

As per EIP-627, the salt for symmetric encryption is now
part of the payload. This commit does that.
Guillaume Ballet 8 rokov pred
rodič
commit
d95962cd5d

+ 5 - 3
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

+ 24 - 28
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)

+ 64 - 0
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)
+	}
+}

+ 11 - 6
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 {

+ 30 - 6
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)

+ 27 - 19
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
 }
 

+ 7 - 4
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)

+ 0 - 7
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