aboutsummaryrefslogtreecommitdiffstats
path: root/whisper/whisperv5/message.go
diff options
context:
space:
mode:
Diffstat (limited to 'whisper/whisperv5/message.go')
-rw-r--r--whisper/whisperv5/message.go378
1 files changed, 378 insertions, 0 deletions
diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go
new file mode 100644
index 000000000..680d1f8a2
--- /dev/null
+++ b/whisper/whisperv5/message.go
@@ -0,0 +1,378 @@
+// Copyright 2016 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 Whisper protocol Message element. For formal details please see
+// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
+// todo: fix the spec link, and move it to doc.go
+
+package whisperv5
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/ecdsa"
+ crand "crypto/rand"
+ "crypto/sha256"
+ "errors"
+ "fmt"
+ mrand "math/rand"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ "golang.org/x/crypto/pbkdf2"
+)
+
+// Options specifies the exact way a message should be wrapped into an Envelope.
+type MessageParams struct {
+ TTL uint32
+ Src *ecdsa.PrivateKey
+ Dst *ecdsa.PublicKey
+ KeySym []byte
+ Topic TopicType
+ WorkTime uint32
+ PoW float64
+ Payload []byte
+ Padding []byte
+}
+
+// SentMessage represents an end-user data packet to transmit through the
+// Whisper protocol. These are wrapped into Envelopes that need not be
+// understood by intermediate nodes, just forwarded.
+type SentMessage struct {
+ Raw []byte
+}
+
+// ReceivedMessage represents a data packet to be received through the
+// Whisper protocol.
+type ReceivedMessage struct {
+ Raw []byte
+
+ Payload []byte
+ Padding []byte
+ Signature []byte
+
+ PoW float64 // Proof of work as described in the Whisper spec
+ Sent uint32 // Time when the message was posted into the network
+ TTL uint32 // Maximum time to live allowed for the message
+ Src *ecdsa.PublicKey // Message recipient (identity used to decode the message)
+ Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message)
+ Topic TopicType
+
+ SymKeyHash common.Hash // The Keccak256Hash of the key, associated with the Topic
+ EnvelopeHash common.Hash // Message envelope hash to act as a unique id
+ EnvelopeVersion uint64
+}
+
+func isMessageSigned(flags byte) bool {
+ return (flags & signatureFlag) != 0
+}
+
+func (msg *ReceivedMessage) isSymmetricEncryption() bool {
+ return msg.SymKeyHash != common.Hash{}
+}
+
+func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
+ return msg.Dst != nil
+}
+
+func DeriveOneTimeKey(key []byte, salt []byte, version uint64) ([]byte, error) {
+ if version == 0 {
+ derivedKey := pbkdf2.Key(key, salt, 8, aesKeyLength, sha256.New)
+ return derivedKey, nil
+ } else {
+ return nil, unknownVersionError(version)
+ }
+}
+
+// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
+func NewSentMessage(params *MessageParams) *SentMessage {
+ msg := SentMessage{}
+ msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper)
+ msg.Raw[0] = 0 // set all the flags to zero
+ msg.appendPadding(params)
+ msg.Raw = append(msg.Raw, params.Payload...)
+ return &msg
+}
+
+// appendPadding appends the pseudorandom padding bytes and sets the padding flag.
+// The last byte contains the size of padding (thus, its size must not exceed 256).
+func (msg *SentMessage) appendPadding(params *MessageParams) {
+ total := len(params.Payload) + 1
+ if params.Src != nil {
+ total += signatureLength
+ }
+ padChunk := padSizeLimitUpper
+ if total <= padSizeLimitLower {
+ padChunk = padSizeLimitLower
+ }
+ odd := total % padChunk
+ if odd > 0 {
+ padSize := padChunk - odd
+ if padSize > 255 {
+ // this algorithm is only valid if padSizeLimitUpper <= 256.
+ // if padSizeLimitUpper will ever change, please fix the algorithm
+ // (for more information see ReceivedMessage.extractPadding() function).
+ panic("please fix the padding algorithm before releasing new version")
+ }
+ buf := make([]byte, padSize)
+ randomize(buf[1:]) // change to: err = mrand.Read(buf[1:])
+ buf[0] = byte(padSize)
+ if params.Padding != nil {
+ copy(buf[1:], params.Padding)
+ }
+ msg.Raw = append(msg.Raw, buf...)
+ msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
+ }
+}
+
+// sign calculates and sets the cryptographic signature for the message,
+// also setting the sign flag.
+func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
+ if isMessageSigned(msg.Raw[0]) {
+ // this should not happen, but no reason to panic
+ glog.V(logger.Error).Infof("Trying to sign a message which was already signed")
+ return nil
+ }
+
+ msg.Raw[0] |= signatureFlag
+ hash := crypto.Keccak256(msg.Raw)
+ signature, err := crypto.Sign(hash, key)
+ if err != nil {
+ msg.Raw[0] &= ^signatureFlag // clear the flag
+ return err
+ }
+ msg.Raw = append(msg.Raw, signature...)
+ return nil
+}
+
+// encryptAsymmetric encrypts a message with a public key.
+func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
+ if !ValidatePublicKey(key) {
+ return fmt.Errorf("Invalid public key provided for asymmetric encryption")
+ }
+ encrypted, err := crypto.Encrypt(key, msg.Raw)
+ if err == nil {
+ msg.Raw = encrypted
+ }
+ return err
+}
+
+// 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) (salt []byte, nonce []byte, err error) {
+ if !validateSymmetricKey(key) {
+ return nil, nil, errors.New("invalid key provided for symmetric encryption")
+ }
+
+ salt = make([]byte, saltLength)
+ _, err = crand.Read(salt)
+ if err != nil {
+ return nil, nil, err
+ } else if !validateSymmetricKey(salt) {
+ return nil, nil, errors.New("crypto/rand failed to generate salt")
+ }
+
+ derivedKey, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
+ if err != nil {
+ return nil, nil, err
+ }
+ if !validateSymmetricKey(derivedKey) {
+ return nil, nil, errors.New("failed to derive one-time key")
+ }
+ block, err := aes.NewCipher(derivedKey)
+ if err != nil {
+ return nil, nil, err
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // never use more than 2^32 random nonces with a given key
+ nonce = make([]byte, aesgcm.NonceSize())
+ _, err = crand.Read(nonce)
+ if err != nil {
+ return nil, nil, err
+ }
+ msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil)
+ return salt, nonce, nil
+}
+
+// Wrap bundles the message into an Envelope to transmit over the network.
+//
+// pow (Proof Of Work) controls how much time to spend on hashing the message,
+// inherently controlling its priority through the network (smaller hash, bigger
+// priority).
+//
+// The user can control the amount of identity, privacy and encryption through
+// the options parameter as follows:
+// - options.From == nil && options.To == nil: anonymous broadcast
+// - options.From != nil && options.To == nil: signed broadcast (known sender)
+// - options.From == nil && options.To != nil: encrypted anonymous message
+// - options.From != nil && options.To != nil: encrypted signed message
+func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
+ if options.TTL == 0 {
+ options.TTL = DefaultTTL
+ }
+ if options.Src != nil {
+ err = msg.sign(options.Src)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if len(msg.Raw) > MaxMessageLength {
+ glog.V(logger.Error).Infof("Message size must not exceed %d bytes", MaxMessageLength)
+ return nil, errors.New("Oversized message")
+ }
+ var salt, nonce []byte
+ if options.Dst != nil {
+ err = msg.encryptAsymmetric(options.Dst)
+ } else if options.KeySym != nil {
+ salt, nonce, err = msg.encryptSymmetric(options.KeySym)
+ } else {
+ err = errors.New("Unable to encrypt the message: neither Dst nor Key")
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg)
+ envelope.Seal(options)
+ return envelope, nil
+}
+
+// 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, salt []byte, nonce []byte) error {
+ derivedKey, err := DeriveOneTimeKey(key, salt, msg.EnvelopeVersion)
+ if err != nil {
+ return err
+ }
+
+ block, err := aes.NewCipher(derivedKey)
+ if err != nil {
+ return err
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return err
+ }
+ if len(nonce) != aesgcm.NonceSize() {
+ info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize())
+ glog.V(logger.Error).Infof(info)
+ return errors.New(info)
+ }
+ decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
+ if err != nil {
+ return err
+ }
+ msg.Raw = decrypted
+ return nil
+}
+
+// decryptAsymmetric decrypts an encrypted payload with a private key.
+func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
+ decrypted, err := crypto.Decrypt(key, msg.Raw)
+ if err == nil {
+ msg.Raw = decrypted
+ }
+ return err
+}
+
+// Validate checks the validity and extracts the fields in case of success
+func (msg *ReceivedMessage) Validate() bool {
+ end := len(msg.Raw)
+ if end < 1 {
+ return false
+ }
+
+ if isMessageSigned(msg.Raw[0]) {
+ end -= signatureLength
+ if end <= 1 {
+ return false
+ }
+ msg.Signature = msg.Raw[end:]
+ msg.Src = msg.SigToPubKey()
+ if msg.Src == nil {
+ return false
+ }
+ }
+
+ padSize, ok := msg.extractPadding(end)
+ if !ok {
+ return false
+ }
+
+ msg.Payload = msg.Raw[1+padSize : end]
+ return true
+}
+
+// extractPadding extracts the padding from raw message.
+// although we don't support sending messages with padding size
+// exceeding 255 bytes, such messages are perfectly valid, and
+// can be successfully decrypted.
+func (msg *ReceivedMessage) extractPadding(end int) (int, bool) {
+ paddingSize := 0
+ sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero
+ if sz != 0 {
+ paddingSize = int(bytesToIntLittleEndian(msg.Raw[1 : 1+sz]))
+ if paddingSize < sz || paddingSize+1 > end {
+ return 0, false
+ }
+ msg.Padding = msg.Raw[1+sz : 1+paddingSize]
+ }
+ return paddingSize, true
+}
+
+// Recover retrieves the public key of the message signer.
+func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
+ defer func() { recover() }() // in case of invalid signature
+
+ pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
+ if err != nil {
+ glog.V(logger.Error).Infof("Could not get public key from signature: %v", err)
+ return nil
+ }
+ return pub
+}
+
+// hash calculates the SHA3 checksum of the message flags, payload and padding.
+func (msg *ReceivedMessage) hash() []byte {
+ if isMessageSigned(msg.Raw[0]) {
+ sz := len(msg.Raw) - signatureLength
+ return crypto.Keccak256(msg.Raw[:sz])
+ }
+ return crypto.Keccak256(msg.Raw)
+}
+
+// rand.Rand provides a Read method in Go 1.7 and later,
+// but we can't use it yet.
+func randomize(b []byte) {
+ cnt := 0
+ val := mrand.Int63()
+ for n := 0; n < len(b); n++ {
+ b[n] = byte(val)
+ val >>= 8
+ cnt++
+ if cnt >= 7 {
+ cnt = 0
+ val = mrand.Int63()
+ }
+ }
+}