whisper5: minor fixes
This commit is contained in:
parent
01d3aa9fe1
commit
82fe888853
|
@ -108,6 +108,8 @@ var (
|
|||
WhisperTTLFlag,
|
||||
WhisperInjectTestAccounts,
|
||||
FirebaseAuthorizationKey,
|
||||
HTTPEnabledFlag,
|
||||
HTTPPortFlag,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -170,6 +172,8 @@ func makeWhisperNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
nodeConfig.LightEthConfig.Enabled = false
|
||||
|
||||
whisperConfig := nodeConfig.WhisperConfig
|
||||
|
||||
whisperConfig.Enabled = true
|
||||
|
@ -221,6 +225,12 @@ func makeWhisperNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// RPC configuration
|
||||
if !ctx.Bool(HTTPEnabledFlag.Name) {
|
||||
nodeConfig.HTTPHost = "" // HTTP RPC is disabled
|
||||
}
|
||||
nodeConfig.HTTPPort = ctx.Int(HTTPPortFlag.Name)
|
||||
|
||||
return nodeConfig, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1110,7 +1110,7 @@ func TestJailWhisper(t *testing.T) {
|
|||
for testKey, filter := range installedFilters {
|
||||
if filter != "" {
|
||||
t.Logf("filter found: %v", filter)
|
||||
for _, message := range whisperAPI.GetSubscriptionMessages(filter) {
|
||||
for _, message := range whisperAPI.GetNewSubscriptionMessages(filter) {
|
||||
t.Logf("message found: %s", common.FromHex(message.Payload))
|
||||
passedTests[testKey] = true
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,8 +10,8 @@ describe('Whisper Tests', function () {
|
|||
node1.setProvider(new web3.providers.HttpProvider('http://localhost:8645'));
|
||||
node2.setProvider(new web3.providers.HttpProvider('http://localhost:8745'));
|
||||
|
||||
console.log('Node is expected: statusd --datadir app1 --http --httpport 8645 wnode');
|
||||
console.log('Node is expected: statusd --datadir app2 --http --httpport 8745 wnode');
|
||||
console.log('Node is expected: statusd --datadir app1 wnode --http --httpport 8645');
|
||||
console.log('Node is expected: statusd --datadir app2 wnode --http --httpport 8745');
|
||||
console.log('Node is expected: statusd --datadir wnode1 wnode --notify --injectaccounts=false --identity ./static/keys/wnodekey --firebaseauth ./static/keys/firebaseauthkey');
|
||||
|
||||
// some common vars
|
||||
|
@ -216,9 +216,9 @@ describe('Whisper Tests', function () {
|
|||
assert.lengthOf(filterid1, 64);
|
||||
});
|
||||
|
||||
it('shh.getMessages(filterID) - symmetric filter', function () {
|
||||
it('shh.getFloatingMessages(filterID) - symmetric filter', function () {
|
||||
// let's try to capture message that was there *before* filter is created
|
||||
var messages = node1.shh.getMessages(filterid1);
|
||||
var messages = node1.shh.getFloatingMessages(filterid1);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.equal(web3.toAscii(messages[0].payload), payloadBeforeSymFilter);
|
||||
|
@ -233,9 +233,9 @@ describe('Whisper Tests', function () {
|
|||
expect(node1.shh.post(message)).to.equal(null);
|
||||
});
|
||||
|
||||
it('shh.getMessages(filterID) - asymmetric filter', function () {
|
||||
it('shh.getFloatingMessages(filterID) - asymmetric filter', function () {
|
||||
// let's try to capture message that was there *before* filter is created
|
||||
var messages = node1.shh.getMessages(filterid2);
|
||||
var messages = node1.shh.getFloatingMessages(filterid2);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.equal(web3.toAscii(messages[0].payload), payloadBeforeAsymFilter);
|
||||
|
@ -250,17 +250,17 @@ describe('Whisper Tests', function () {
|
|||
expect(node1.shh.post(message)).to.equal(null);
|
||||
});
|
||||
|
||||
it('shh.getSubscriptionMessages(filterID) - symmetric filter', function (done) {
|
||||
it('shh.getNewSubscriptionMessages(filterID) - symmetric filter', function (done) {
|
||||
// allow some time for message to propagate
|
||||
setTimeout(function () {
|
||||
// now let's try to capture new messages from our last capture
|
||||
var messages = node1.shh.getSubscriptionMessages(filterid1);
|
||||
var messages = node1.shh.getNewSubscriptionMessages(filterid1);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.equal(web3.toAscii(messages[0].payload), payloadAfterSymFilter);
|
||||
|
||||
// no more messages should be returned
|
||||
messages = node1.shh.getSubscriptionMessages(filterid1);
|
||||
messages = node1.shh.getNewSubscriptionMessages(filterid1);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 0);
|
||||
|
||||
|
@ -268,17 +268,17 @@ describe('Whisper Tests', function () {
|
|||
}, 200);
|
||||
});
|
||||
|
||||
it('shh.getSubscriptionMessages(filterID) - asymmetric filter', function () {
|
||||
it('shh.getNewSubscriptionMessages(filterID) - asymmetric filter', function () {
|
||||
// allow some time for message to propagate
|
||||
setTimeout(function () {
|
||||
// now let's try to capture new messages from our last capture
|
||||
var messages = node1.shh.getSubscriptionMessages(filterid2);
|
||||
var messages = node1.shh.getNewSubscriptionMessages(filterid2);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.equal(web3.toAscii(messages[0].payload), payloadAfterAsymFilter);
|
||||
|
||||
// no more messages should be returned
|
||||
messages = node1.shh.getSubscriptionMessages(filterid2);
|
||||
messages = node1.shh.getNewSubscriptionMessages(filterid2);
|
||||
assert.typeOf(messages, 'array');
|
||||
assert.lengthOf(messages, 0);
|
||||
|
||||
|
|
|
@ -81,7 +81,10 @@ func (s *discoveryService) processDiscoveryRequest(msg *whisper.ReceivedMessage)
|
|||
PoW: s.server.config.MinimumPoW,
|
||||
WorkTime: 5,
|
||||
}
|
||||
response := whisper.NewSentMessage(&msgParams)
|
||||
response, err := whisper.NewSentMessage(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create proposal message: %v", err)
|
||||
}
|
||||
env, err := response.Wrap(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wrap server proposal message: %v", err)
|
||||
|
@ -133,7 +136,10 @@ func (s *discoveryService) processServerAcceptedRequest(msg *whisper.ReceivedMes
|
|||
PoW: s.server.config.MinimumPoW,
|
||||
WorkTime: 5,
|
||||
}
|
||||
response := whisper.NewSentMessage(&msgParams)
|
||||
response, err := whisper.NewSentMessage(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server proposal message: %v", err)
|
||||
}
|
||||
env, err := response.Wrap(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wrap server proposal message: %v", err)
|
||||
|
|
|
@ -324,7 +324,10 @@ func (s *NotificationServer) processNewChatSessionRequest(msg *whisper.ReceivedM
|
|||
PoW: s.config.MinimumPoW,
|
||||
WorkTime: 5,
|
||||
}
|
||||
response := whisper.NewSentMessage(&msgParams)
|
||||
response, err := whisper.NewSentMessage(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server response message: %v", err)
|
||||
}
|
||||
env, err := response.Wrap(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wrap server response message: %v", err)
|
||||
|
@ -386,7 +389,10 @@ func (s *NotificationServer) processNewDeviceRegistrationRequest(msg *whisper.Re
|
|||
PoW: s.config.MinimumPoW,
|
||||
WorkTime: 5,
|
||||
}
|
||||
response := whisper.NewSentMessage(&msgParams)
|
||||
response, err := whisper.NewSentMessage(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server response message: %v", err)
|
||||
}
|
||||
env, err := response.Wrap(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wrap server response message: %v", err)
|
||||
|
@ -459,7 +465,10 @@ func (s *NotificationServer) processClientSessionStatusRequest(msg *whisper.Rece
|
|||
PoW: s.config.MinimumPoW,
|
||||
WorkTime: 5,
|
||||
}
|
||||
response := whisper.NewSentMessage(&msgParams)
|
||||
response, err := whisper.NewSentMessage(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server response message: %v", err)
|
||||
}
|
||||
env, err := response.Wrap(&msgParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to wrap server response message: %v", err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package notifications
|
|||
import (
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"crypto/sha256"
|
||||
|
||||
crand "crypto/rand"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
|
@ -14,8 +15,7 @@ import (
|
|||
func makeSessionKey() ([]byte, error) {
|
||||
// generate random key
|
||||
const keyLen = 32
|
||||
const size = keyLen * 2
|
||||
buf := make([]byte, size)
|
||||
buf := make([]byte, keyLen)
|
||||
_, err := crand.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -24,8 +24,7 @@ func makeSessionKey() ([]byte, error) {
|
|||
}
|
||||
|
||||
key := buf[:keyLen]
|
||||
salt := buf[keyLen:]
|
||||
derived, err := whisper.DeriveOneTimeKey(key, salt, whisper.EnvelopeVersion)
|
||||
derived, err := deriveKeyMaterial(key, whisper.EnvelopeVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !validateSymmetricKey(derived) {
|
||||
|
@ -50,6 +49,19 @@ func containsOnlyZeros(data []byte) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// deriveKeyMaterial derives symmetric key material from the key or password./~~~
|
||||
// pbkdf2 is used for security, in case people use password instead of randomly generated keys.
|
||||
func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error) {
|
||||
if version == 0 {
|
||||
// kdf should run no less than 0.1 seconds on average compute,
|
||||
// because it's a once in a session experience
|
||||
derivedKey := pbkdf2.Key(key, nil, 65356, 32, sha256.New)
|
||||
return derivedKey, nil
|
||||
} else {
|
||||
return nil, errors.New("unknown version")
|
||||
}
|
||||
}
|
||||
|
||||
// MakeTopic returns Whisper topic *as bytes array* by generating cryptographic key from the provided password
|
||||
func MakeTopicAsBytes(password []byte) ([]byte) {
|
||||
topic := make([]byte, int(whisper.TopicLength))
|
||||
|
|
|
@ -219,7 +219,6 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) {
|
|||
}
|
||||
|
||||
filter := Filter{
|
||||
Src: crypto.ToECDSAPub(common.FromHex(args.Sig)),
|
||||
PoW: args.MinPoW,
|
||||
Messages: make(map[common.Hash]*ReceivedMessage),
|
||||
AllowP2P: args.AllowP2P,
|
||||
|
@ -243,8 +242,13 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) {
|
|||
}
|
||||
|
||||
if len(args.Sig) > 0 {
|
||||
sb := common.FromHex(args.Sig)
|
||||
if sb == nil {
|
||||
return "", errors.New("subscribe: sig parameter is invalid")
|
||||
}
|
||||
filter.Src = crypto.ToECDSAPub(sb)
|
||||
if !ValidatePublicKey(filter.Src) {
|
||||
return "", errors.New("subscribe: sig invalid is invalid")
|
||||
return "", errors.New("subscribe: invalid 'sig' field")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,14 +288,15 @@ func (api *PublicWhisperAPI) Unsubscribe(id string) {
|
|||
api.whisper.Unsubscribe(id)
|
||||
}
|
||||
|
||||
// GetFilterChanges is alias for GetSubscriptionMessages
|
||||
// GetFilterChanges is alias for GetNewSubscriptionMessages
|
||||
func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage {
|
||||
return api.GetSubscriptionMessages(filterId)
|
||||
return api.GetNewSubscriptionMessages(filterId)
|
||||
}
|
||||
|
||||
// GetSubscriptionMessages retrieves all the new messages matched by a filter since the last retrieval.
|
||||
func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage {
|
||||
f := api.whisper.GetFilter(filterId)
|
||||
// GetNewSubscriptionMessages retrieves all the new messages matched by the corresponding
|
||||
// subscription filter since the last retrieval.
|
||||
func (api *PublicWhisperAPI) GetNewSubscriptionMessages(id string) []*WhisperMessage {
|
||||
f := api.whisper.GetFilter(id)
|
||||
if f != nil {
|
||||
newMail := f.Retrieve()
|
||||
return toWhisperMessages(newMail)
|
||||
|
@ -299,10 +304,10 @@ func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*Whisper
|
|||
return toWhisperMessages(nil)
|
||||
}
|
||||
|
||||
// GetMessages retrieves all the floating messages that match a specific filter.
|
||||
// GetMessages retrieves all the floating messages that match a specific subscription filter.
|
||||
// It is likely to be called once per session, right after Subscribe call.
|
||||
func (api *PublicWhisperAPI) GetMessages(filterId string) []*WhisperMessage {
|
||||
all := api.whisper.Messages(filterId)
|
||||
func (api *PublicWhisperAPI) GetFloatingMessages(id string) []*WhisperMessage {
|
||||
all := api.whisper.Messages(id)
|
||||
return toWhisperMessages(all)
|
||||
}
|
||||
|
||||
|
@ -365,7 +370,11 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error {
|
|||
return errors.New("post: topic is missing for symmetric encryption")
|
||||
}
|
||||
} else if args.Type == "asym" {
|
||||
params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key))
|
||||
kb := common.FromHex(args.Key)
|
||||
if kb == nil {
|
||||
return errors.New("post: public key for asymmetric encryption is invalid")
|
||||
}
|
||||
params.Dst = crypto.ToECDSAPub(kb)
|
||||
if !ValidatePublicKey(params.Dst) {
|
||||
return errors.New("post: public key for asymmetric encryption is invalid")
|
||||
}
|
||||
|
@ -374,9 +383,9 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error {
|
|||
}
|
||||
|
||||
// encrypt and send
|
||||
message := NewSentMessage(¶ms)
|
||||
if message == nil {
|
||||
return errors.New("post: failed create new message, probably due to failed rand function (OS level)")
|
||||
message, err := NewSentMessage(¶ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envelope, err := message.Wrap(¶ms)
|
||||
if err != nil {
|
||||
|
@ -403,7 +412,7 @@ type PostArgs struct {
|
|||
Type string `json:"type"` // "sym"/"asym" (symmetric or asymmetric)
|
||||
TTL uint32 `json:"ttl"` // time-to-live in seconds
|
||||
Sig string `json:"sig"` // id of the signing key
|
||||
Key string `json:"key"` // id of encryption key
|
||||
Key string `json:"key"` // key id (in case of sym) or public key (in case of asym)
|
||||
Topic hexutil.Bytes `json:"topic"` // topic (4 bytes)
|
||||
Padding hexutil.Bytes `json:"padding"` // optional padding bytes
|
||||
Payload hexutil.Bytes `json:"payload"` // payload to be encrypted
|
||||
|
@ -544,7 +553,6 @@ type WhisperMessage struct {
|
|||
// NewWhisperMessage converts an internal message into an API version.
|
||||
func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage {
|
||||
msg := WhisperMessage{
|
||||
Topic: common.ToHex(message.Topic[:]),
|
||||
Payload: common.ToHex(message.Payload),
|
||||
Padding: common.ToHex(message.Padding),
|
||||
Timestamp: message.Sent,
|
||||
|
@ -553,11 +561,20 @@ func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage {
|
|||
Hash: common.ToHex(message.EnvelopeHash.Bytes()),
|
||||
}
|
||||
|
||||
if len(message.Topic) == TopicLength {
|
||||
msg.Topic = common.ToHex(message.Topic[:])
|
||||
}
|
||||
if message.Dst != nil {
|
||||
msg.Dst = common.ToHex(crypto.FromECDSAPub(message.Dst))
|
||||
b := crypto.FromECDSAPub(message.Dst)
|
||||
if b != nil {
|
||||
msg.Dst = common.ToHex(b)
|
||||
}
|
||||
}
|
||||
if isMessageSigned(message.Raw[0]) {
|
||||
msg.Src = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey()))
|
||||
b := crypto.FromECDSAPub(message.SigToPubKey())
|
||||
if b != nil {
|
||||
msg.Src = common.ToHex(b)
|
||||
}
|
||||
}
|
||||
return &msg
|
||||
}
|
||||
|
|
|
@ -51,19 +51,17 @@ const (
|
|||
paddingMask = byte(3)
|
||||
signatureFlag = byte(4)
|
||||
|
||||
TopicLength = 4
|
||||
signatureLength = 65
|
||||
aesKeyLength = 32
|
||||
saltLength = 12
|
||||
AESNonceMaxLength = 12
|
||||
keyIdSize = 32
|
||||
TopicLength = 4
|
||||
signatureLength = 65
|
||||
aesKeyLength = 32
|
||||
AESNonceLength = 12
|
||||
keyIdSize = 32
|
||||
|
||||
DefaultMaxMessageLength = 1024 * 1024
|
||||
DefaultMinimumPoWTime = 2 // todo: review after testing.
|
||||
DefaultMinimumPoW = 0.001 // todo: review after testing.
|
||||
|
||||
padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature
|
||||
padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility
|
||||
padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol (must not exceed 2^24)
|
||||
messageQueueLimit = 1024
|
||||
|
||||
expirationCycle = time.Second
|
||||
|
|
|
@ -40,7 +40,6 @@ type Envelope struct {
|
|||
Expiry uint32
|
||||
TTL uint32
|
||||
Topic TopicType
|
||||
Salt []byte
|
||||
AESNonce []byte
|
||||
Data []byte
|
||||
EnvNonce uint64
|
||||
|
@ -50,15 +49,25 @@ type Envelope struct {
|
|||
// Don't access hash directly, use Hash() function instead.
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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})
|
||||
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, salt []byte, aesNonce []byte, msg *SentMessage) *Envelope {
|
||||
func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *SentMessage) *Envelope {
|
||||
env := Envelope{
|
||||
Version: make([]byte, 1),
|
||||
Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
|
||||
TTL: ttl,
|
||||
Topic: topic,
|
||||
Salt: salt,
|
||||
AESNonce: aesNonce,
|
||||
Data: msg.Raw,
|
||||
EnvNonce: 0,
|
||||
|
@ -126,10 +135,6 @@ func (e *Envelope) Seal(options *MessageParams) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *Envelope) size() int {
|
||||
return len(e.Data) + len(e.Version) + len(e.AESNonce) + len(e.Salt) + 20
|
||||
}
|
||||
|
||||
func (e *Envelope) PoW() float64 {
|
||||
if e.pow == 0 {
|
||||
e.calculatePoW(0)
|
||||
|
@ -159,12 +164,6 @@ func (e *Envelope) powToFirstBit(pow float64) int {
|
|||
return int(bits)
|
||||
}
|
||||
|
||||
// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
|
||||
func (e *Envelope) rlpWithoutNonce() []byte {
|
||||
res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Salt, e.AESNonce, e.Data})
|
||||
return res
|
||||
}
|
||||
|
||||
// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
|
||||
func (e *Envelope) Hash() common.Hash {
|
||||
if (e.hash == common.Hash{}) {
|
||||
|
@ -210,7 +209,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.Salt, e.AESNonce)
|
||||
err = msg.decryptSymmetric(key, e.AESNonce)
|
||||
if err != nil {
|
||||
msg = nil
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Options specifies the exact way a message should be wrapped into an Envelope.
|
||||
|
@ -86,58 +86,76 @@ 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 {
|
||||
func NewSentMessage(params *MessageParams) (*SentMessage, error) {
|
||||
msg := SentMessage{}
|
||||
msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper)
|
||||
msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
|
||||
msg.Raw[0] = 0 // set all the flags to zero
|
||||
err := msg.appendPadding(params)
|
||||
if err != nil {
|
||||
log.Error("failed to create NewSentMessage", "err", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
msg.Raw = append(msg.Raw, params.Payload...)
|
||||
return &msg
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
// getSizeOfLength returns the number of bytes necessary to encode the entire size padding (including these bytes)
|
||||
func getSizeOfLength(b []byte) (sz int, err error) {
|
||||
sz = intSize(len(b)) // first iteration
|
||||
sz = intSize(len(b) + sz) // second iteration
|
||||
if sz > 3 {
|
||||
err = errors.New("oversized padding parameter")
|
||||
}
|
||||
return sz, err
|
||||
}
|
||||
|
||||
// sizeOfIntSize returns minimal number of bytes necessary to encode an integer value
|
||||
func intSize(i int) (s int) {
|
||||
for s = 1; i >= 256; s++ {
|
||||
i /= 256
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
total := len(params.Payload) + 1
|
||||
rawSize := len(params.Payload) + 1
|
||||
if params.Src != nil {
|
||||
total += signatureLength
|
||||
rawSize += 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).
|
||||
odd := rawSize % padSizeLimit
|
||||
|
||||
if len(params.Padding) != 0 {
|
||||
padSize := len(params.Padding)
|
||||
padLengthSize, err := getSizeOfLength(params.Padding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalPadSize := padSize + padLengthSize
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(totalPadSize))
|
||||
buf = buf[:padLengthSize]
|
||||
msg.Raw = append(msg.Raw, buf...)
|
||||
msg.Raw = append(msg.Raw, params.Padding...)
|
||||
msg.Raw[0] |= byte(padLengthSize) // number of bytes indicating the padding size
|
||||
} else if odd != 0 {
|
||||
totalPadSize := padSizeLimit - odd
|
||||
if totalPadSize > 255 {
|
||||
// this algorithm is only valid if padSizeLimit < 256.
|
||||
// if padSizeLimit will ever change, please fix the algorithm
|
||||
// (please see also ReceivedMessage.extractPadding() function).
|
||||
panic("please fix the padding algorithm before releasing new version")
|
||||
}
|
||||
buf := make([]byte, padSize)
|
||||
buf := make([]byte, totalPadSize)
|
||||
_, err := crand.Read(buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf[0] = byte(padSize)
|
||||
if params.Padding != nil {
|
||||
copy(buf[1:], params.Padding)
|
||||
if totalPadSize > 6 && !validateSymmetricKey(buf) {
|
||||
return errors.New("failed to generate random padding of size " + strconv.Itoa(totalPadSize))
|
||||
}
|
||||
buf[0] = byte(totalPadSize)
|
||||
msg.Raw = append(msg.Raw, buf...)
|
||||
msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
|
||||
}
|
||||
|
@ -178,46 +196,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) (salt []byte, nonce []byte, err error) {
|
||||
func (msg *SentMessage) encryptSymmetric(key []byte) (nonce []byte, err error) {
|
||||
if !validateSymmetricKey(key) {
|
||||
return nil, nil, errors.New("invalid key provided for symmetric encryption")
|
||||
return nil, errors.New("invalid key provided for symmetric encryption")
|
||||
}
|
||||
|
||||
salt = make([]byte, saltLength)
|
||||
_, err = crand.Read(salt)
|
||||
block, err := aes.NewCipher(key)
|
||||
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
|
||||
return nil, err
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return 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
|
||||
return nil, err
|
||||
} else if !validateSymmetricKey(nonce) {
|
||||
return nil, nil, errors.New("crypto/rand failed to generate nonce")
|
||||
return nil, errors.New("crypto/rand failed to generate nonce")
|
||||
}
|
||||
|
||||
msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil)
|
||||
return salt, nonce, nil
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// Wrap bundles the message into an Envelope to transmit over the network.
|
||||
|
@ -231,11 +234,11 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
var salt, nonce []byte
|
||||
var nonce []byte
|
||||
if options.Dst != nil {
|
||||
err = msg.encryptAsymmetric(options.Dst)
|
||||
} else if options.KeySym != nil {
|
||||
salt, nonce, err = msg.encryptSymmetric(options.KeySym)
|
||||
nonce, err = msg.encryptSymmetric(options.KeySym)
|
||||
} else {
|
||||
err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
|
||||
}
|
||||
|
@ -244,7 +247,7 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg)
|
||||
envelope = NewEnvelope(options.TTL, options.Topic, nonce, msg)
|
||||
err = envelope.Seal(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -254,13 +257,8 @@ 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, salt []byte, nonce []byte) error {
|
||||
derivedKey, err := DeriveOneTimeKey(key, salt, msg.EnvelopeVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(derivedKey)
|
||||
func (msg *ReceivedMessage) decryptSymmetric(key []byte, nonce []byte) error {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -323,7 +321,8 @@ func (msg *ReceivedMessage) Validate() bool {
|
|||
// 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
|
||||
sz := int(msg.Raw[0] & paddingMask) // number of bytes indicating the entire size of padding (including these bytes)
|
||||
// could be zero -- it means no padding
|
||||
if sz != 0 {
|
||||
paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz]))
|
||||
if paddingSize < sz || paddingSize+1 > end {
|
||||
|
|
|
@ -149,23 +149,22 @@ func (peer *Peer) expire() {
|
|||
// broadcast iterates over the collection of envelopes and transmits yet unknown
|
||||
// ones over the network.
|
||||
func (p *Peer) broadcast() error {
|
||||
// Fetch the envelopes and collect the unknown ones
|
||||
var cnt int
|
||||
envelopes := p.host.Envelopes()
|
||||
transmit := make([]*Envelope, 0, len(envelopes))
|
||||
for _, envelope := range envelopes {
|
||||
if !p.marked(envelope) {
|
||||
transmit = append(transmit, envelope)
|
||||
p.mark(envelope)
|
||||
err := p2p.Send(p.ws, messagesCode, envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.mark(envelope)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(transmit) == 0 {
|
||||
return nil
|
||||
if cnt > 0 {
|
||||
log.Trace("broadcast", "num. messages", cnt)
|
||||
}
|
||||
// Transmit the unknown batch (potentially empty)
|
||||
if err := p2p.Send(p.ws, messagesCode, transmit); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace("broadcast", "num. messages", len(transmit))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -331,24 +331,14 @@ func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
|
|||
// GenerateSymKey generates a random symmetric key and stores it under id,
|
||||
// which is then returned. Will be used in the future for session key exchange.
|
||||
func (w *Whisper) GenerateSymKey() (string, error) {
|
||||
const size = aesKeyLength * 2
|
||||
buf := make([]byte, size)
|
||||
_, err := crand.Read(buf)
|
||||
key := make([]byte, aesKeyLength)
|
||||
_, err := crand.Read(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !validateSymmetricKey(buf) {
|
||||
} else if !validateSymmetricKey(key) {
|
||||
return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data")
|
||||
}
|
||||
|
||||
key := buf[:aesKeyLength]
|
||||
salt := buf[aesKeyLength:]
|
||||
derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !validateSymmetricKey(derived) {
|
||||
return "", fmt.Errorf("failed to derive valid key")
|
||||
}
|
||||
|
||||
id, err := GenerateRandomID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate ID: %s", err)
|
||||
|
@ -360,7 +350,7 @@ func (w *Whisper) GenerateSymKey() (string, error) {
|
|||
if w.symKeys[id] != nil {
|
||||
return "", fmt.Errorf("failed to generate unique ID")
|
||||
}
|
||||
w.symKeys[id] = derived
|
||||
w.symKeys[id] = key
|
||||
return id, nil
|
||||
}
|
||||
|
||||
|
@ -481,6 +471,9 @@ func (w *Whisper) Unsubscribe(id string) error {
|
|||
// network in the coming cycles.
|
||||
func (w *Whisper) Send(envelope *Envelope) error {
|
||||
ok, err := w.add(envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to add envelope")
|
||||
}
|
||||
|
@ -646,14 +639,11 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
|
|||
return false, fmt.Errorf("oversized version [%x]", envelope.Hash())
|
||||
}
|
||||
|
||||
if len(envelope.AESNonce) > AESNonceMaxLength {
|
||||
// the standard AES GSM nonce size is 12,
|
||||
// but const gcmStandardNonceSize cannot be accessed directly
|
||||
return false, fmt.Errorf("oversized AESNonce [%x]", envelope.Hash())
|
||||
}
|
||||
|
||||
if len(envelope.Salt) > saltLength {
|
||||
return false, fmt.Errorf("oversized salt [%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 {
|
||||
|
|
Loading…
Reference in New Issue