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.
This commit is contained in:
Guillaume Ballet 2017-11-28 17:38:06 +01:00 committed by Felix Lange
parent b5874273ce
commit 0d31e257e0
6 changed files with 45 additions and 55 deletions

View file

@ -40,7 +40,6 @@ type Envelope struct {
Expiry uint32 Expiry uint32
TTL uint32 TTL uint32
Topic TopicType Topic TopicType
AESNonce []byte
Data []byte Data []byte
Nonce uint64 Nonce uint64
@ -51,12 +50,12 @@ type Envelope struct {
// size returns the size of envelope as it is sent (i.e. public fields only) // size returns the size of envelope as it is sent (i.e. public fields only)
func (e *Envelope) size() int { 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. // rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
func (e *Envelope) rlpWithoutNonce() []byte { 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 return res
} }
@ -82,14 +81,6 @@ func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *sentMessage)
return &env 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 { func (e *Envelope) Ver() uint64 {
return bytesToUintLittleEndian(e.Version) 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. // OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
msg = &ReceivedMessage{Raw: e.Data} msg = &ReceivedMessage{Raw: e.Data}
err = msg.decryptSymmetric(key, e.AESNonce) err = msg.decryptSymmetric(key)
if err != nil { if err != nil {
msg = 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. // Open tries to decrypt an envelope, and populates the message fields in case of success.
func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) { 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) msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
if msg != nil { if msg != nil {
msg.Dst = &watcher.KeyAsym.PublicKey msg.Dst = &watcher.KeyAsym.PublicKey
} }
} else if e.IsSymmetric() { }
if msg == nil {
msg, _ = e.OpenSymmetric(watcher.KeySym) msg, _ = e.OpenSymmetric(watcher.KeySym)
if msg != nil { if msg != nil {
msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)

View file

@ -175,6 +175,9 @@ func (f *Filter) Retrieve() (all []*ReceivedMessage) {
return all 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 { func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
if f.PoW > 0 && msg.PoW < f.PoW { if f.PoW > 0 && msg.PoW < f.PoW {
return false return false
@ -188,17 +191,15 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
return false 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 { func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
if f.PoW > 0 && envelope.pow < f.PoW { if f.PoW > 0 && envelope.pow < f.PoW {
return false return false
} }
if f.expectsAsymmetricEncryption() && envelope.isAsymmetric() { return f.MatchTopic(envelope.Topic)
return f.MatchTopic(envelope.Topic)
} else if f.expectsSymmetricEncryption() && envelope.IsSymmetric() {
return f.MatchTopic(envelope.Topic)
}
return false
} }
func (f *Filter) MatchTopic(topic TopicType) bool { func (f *Filter) MatchTopic(topic TopicType) bool {

View file

@ -312,12 +312,6 @@ func TestMatchEnvelope(t *testing.T) {
t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed) 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 // symmetric + matching topic + insufficient PoW: mismatch
fsym.PoW = env.PoW() + 1.0 fsym.PoW = env.PoW() + 1.0
match = fsym.MatchEnvelope(env) match = fsym.MatchEnvelope(env)

View file

@ -61,6 +61,7 @@ type ReceivedMessage struct {
Payload []byte Payload []byte
Padding []byte Padding []byte
Signature []byte Signature []byte
Salt []byte
PoW float64 // Proof of work as described in the Whisper spec PoW float64 // Proof of work as described in the Whisper spec
Sent uint32 // Time when the message was posted into the network 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. // encryptSymmetric encrypts a message with a topic key, using AES-GCM-256.
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). // 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) { 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) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
return nil, err return err
} }
aesgcm, err := cipher.NewGCM(block) aesgcm, err := cipher.NewGCM(block)
if err != nil { if err != nil {
return nil, err return err
} }
// never use more than 2^32 random nonces with a given key // never use more than 2^32 random nonces with a given key
nonce = make([]byte, aesgcm.NonceSize()) salt := make([]byte, aesgcm.NonceSize())
_, err = crand.Read(nonce) _, err = crand.Read(salt)
if err != nil { if err != nil {
return nil, err return err
} else if !validateSymmetricKey(nonce) { } else if !validateSymmetricKey(salt) {
return nil, errors.New("crypto/rand failed to generate nonce") return errors.New("crypto/rand failed to generate salt")
} }
msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil) msg.Raw = append(aesgcm.Seal(nil, salt, msg.Raw, nil), salt...)
return nonce, nil return nil
} }
// Wrap bundles the message into an Envelope to transmit over the network. // 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 return nil, err
} }
} }
var nonce []byte
if options.Dst != nil { if options.Dst != nil {
err = msg.encryptAsymmetric(options.Dst) err = msg.encryptAsymmetric(options.Dst)
} else if options.KeySym != nil { } else if options.KeySym != nil {
nonce, err = msg.encryptSymmetric(options.KeySym) err = msg.encryptSymmetric(options.KeySym)
} else { } else {
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") 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 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 { if err = envelope.Seal(options); err != nil {
return nil, err 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. // decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). // 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) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
return err return err
@ -263,15 +270,16 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, nonce []byte) error {
if err != nil { if err != nil {
return err return err
} }
if len(nonce) != aesgcm.NonceSize() { if len(salt) != aesgcm.NonceSize() {
log.Error("decrypting the message", "AES nonce size", len(nonce)) log.Error("decrypting the message", "AES salt size", len(salt))
return errors.New("wrong AES nonce size") 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 { if err != nil {
return err return err
} }
msg.Raw = decrypted msg.Raw = decrypted
msg.Salt = salt
return nil return nil
} }

View file

@ -174,10 +174,8 @@ func TestMessageSeal(t *testing.T) {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err) t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
} }
params.TTL = 1 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 { if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err) t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
} }

View file

@ -591,13 +591,6 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
return false, fmt.Errorf("oversized version [%x]", envelope.Hash()) 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() { if envelope.PoW() < wh.MinPow() {
log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex()) log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex())
return false, nil // drop envelope without error return false, nil // drop envelope without error