diff options
author | gluk256 <gluk256@gmail.com> | 2019-02-17 13:29:41 +0800 |
---|---|---|
committer | Rafael Matias <rafael@skyle.net> | 2019-02-19 20:11:52 +0800 |
commit | d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72 (patch) | |
tree | d244f30c302b3185e966d0355af4602da5d9e851 /swarm/pss/keystore.go | |
parent | 79cac793c013832457a89911cc477f345e46ced9 (diff) | |
download | dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar.gz dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar.bz2 dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar.lz dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar.xz dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.tar.zst dexon-d6c1fcbe045c94cf629a9df956bd7aef7dcf8d72.zip |
swarm/pss: refactoring (#19110)
* swarm/pss: split pss and keystore
* swarm/pss: moved whisper to keystore
* swarm/pss: goimports fixed
(cherry picked from commit 12ca3b172a7e1b2b63ef2369e8dc37c75144c81f)
Diffstat (limited to 'swarm/pss/keystore.go')
-rw-r--r-- | swarm/pss/keystore.go | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/swarm/pss/keystore.go b/swarm/pss/keystore.go new file mode 100644 index 000000000..510d21bcf --- /dev/null +++ b/swarm/pss/keystore.go @@ -0,0 +1,281 @@ +// Copyright 2019 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/>. + +package pss + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/swarm/log" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" +) + +type KeyStore struct { + w *whisper.Whisper // key and encryption backend + + mx sync.RWMutex + pubKeyPool map[string]map[Topic]*pssPeer // mapping of hex public keys to peer address by topic. + symKeyPool map[string]map[Topic]*pssPeer // mapping of symkeyids to peer address by topic. + symKeyDecryptCache []*string // fast lookup of symkeys recently used for decryption; last used is on top of stack + symKeyDecryptCacheCursor int // modular cursor pointing to last used, wraps on symKeyDecryptCache array +} + +func loadKeyStore() *KeyStore { + return &KeyStore{ + w: whisper.New(&whisper.DefaultConfig), + + pubKeyPool: make(map[string]map[Topic]*pssPeer), + symKeyPool: make(map[string]map[Topic]*pssPeer), + symKeyDecryptCache: make([]*string, defaultSymKeyCacheCapacity), + } +} + +func (ks *KeyStore) isSymKeyStored(key string) bool { + ks.mx.RLock() + defer ks.mx.RUnlock() + var ok bool + _, ok = ks.symKeyPool[key] + return ok +} + +func (ks *KeyStore) isPubKeyStored(key string) bool { + ks.mx.RLock() + defer ks.mx.RUnlock() + var ok bool + _, ok = ks.pubKeyPool[key] + return ok +} + +func (ks *KeyStore) getPeerSym(symkeyid string, topic Topic) (*pssPeer, bool) { + ks.mx.RLock() + defer ks.mx.RUnlock() + psp, ok := ks.symKeyPool[symkeyid][topic] + return psp, ok +} + +func (ks *KeyStore) getPeerPub(pubkeyid string, topic Topic) (*pssPeer, bool) { + ks.mx.RLock() + defer ks.mx.RUnlock() + psp, ok := ks.pubKeyPool[pubkeyid][topic] + return psp, ok +} + +// Links a peer ECDSA public key to a topic. +// This is required for asymmetric message exchange on the given topic. +// The value in `address` will be used as a routing hint for the public key / topic association. +func (ks *KeyStore) SetPeerPublicKey(pubkey *ecdsa.PublicKey, topic Topic, address PssAddress) error { + if err := validateAddress(address); err != nil { + return err + } + pubkeybytes := crypto.FromECDSAPub(pubkey) + if len(pubkeybytes) == 0 { + return fmt.Errorf("invalid public key: %v", pubkey) + } + pubkeyid := common.ToHex(pubkeybytes) + psp := &pssPeer{ + address: address, + } + ks.mx.Lock() + if _, ok := ks.pubKeyPool[pubkeyid]; !ok { + ks.pubKeyPool[pubkeyid] = make(map[Topic]*pssPeer) + } + ks.pubKeyPool[pubkeyid][topic] = psp + ks.mx.Unlock() + log.Trace("added pubkey", "pubkeyid", pubkeyid, "topic", topic, "address", address) + return nil +} + +// adds a symmetric key to the pss key pool, and optionally adds the key to the +// collection of keys used to attempt symmetric decryption of incoming messages +func (ks *KeyStore) addSymmetricKeyToPool(keyid string, topic Topic, address PssAddress, addtocache bool, protected bool) { + psp := &pssPeer{ + address: address, + protected: protected, + } + ks.mx.Lock() + if _, ok := ks.symKeyPool[keyid]; !ok { + ks.symKeyPool[keyid] = make(map[Topic]*pssPeer) + } + ks.symKeyPool[keyid][topic] = psp + ks.mx.Unlock() + if addtocache { + ks.symKeyDecryptCacheCursor++ + ks.symKeyDecryptCache[ks.symKeyDecryptCacheCursor%cap(ks.symKeyDecryptCache)] = &keyid + } +} + +// Returns all recorded topic and address combination for a specific public key +func (ks *KeyStore) GetPublickeyPeers(keyid string) (topic []Topic, address []PssAddress, err error) { + ks.mx.RLock() + defer ks.mx.RUnlock() + for t, peer := range ks.pubKeyPool[keyid] { + topic = append(topic, t) + address = append(address, peer.address) + } + return topic, address, nil +} + +func (ks *KeyStore) getPeerAddress(keyid string, topic Topic) (PssAddress, error) { + ks.mx.RLock() + defer ks.mx.RUnlock() + if peers, ok := ks.pubKeyPool[keyid]; ok { + if t, ok := peers[topic]; ok { + return t.address, nil + } + } + return nil, fmt.Errorf("peer with pubkey %s, topic %x not found", keyid, topic) +} + +// Attempt to decrypt, validate and unpack a symmetrically encrypted message. +// If successful, returns the unpacked whisper ReceivedMessage struct +// encapsulating the decrypted message, and the whisper backend id +// of the symmetric key used to decrypt the message. +// It fails if decryption of the message fails or if the message is corrupted. +func (ks *KeyStore) processSym(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error) { + metrics.GetOrRegisterCounter("pss.process.sym", nil).Inc(1) + + for i := ks.symKeyDecryptCacheCursor; i > ks.symKeyDecryptCacheCursor-cap(ks.symKeyDecryptCache) && i > 0; i-- { + symkeyid := ks.symKeyDecryptCache[i%cap(ks.symKeyDecryptCache)] + symkey, err := ks.w.GetSymKey(*symkeyid) + if err != nil { + continue + } + recvmsg, err := envelope.OpenSymmetric(symkey) + if err != nil { + continue + } + if !recvmsg.ValidateAndParse() { + return nil, "", nil, errors.New("symmetrically encrypted message has invalid signature or is corrupt") + } + var from PssAddress + ks.mx.RLock() + if ks.symKeyPool[*symkeyid][Topic(envelope.Topic)] != nil { + from = ks.symKeyPool[*symkeyid][Topic(envelope.Topic)].address + } + ks.mx.RUnlock() + ks.symKeyDecryptCacheCursor++ + ks.symKeyDecryptCache[ks.symKeyDecryptCacheCursor%cap(ks.symKeyDecryptCache)] = symkeyid + return recvmsg, *symkeyid, from, nil + } + return nil, "", nil, errors.New("could not decrypt message") +} + +// Attempt to decrypt, validate and unpack an asymmetrically encrypted message. +// If successful, returns the unpacked whisper ReceivedMessage struct +// encapsulating the decrypted message, and the byte representation of +// the public key used to decrypt the message. +// It fails if decryption of message fails, or if the message is corrupted. +func (ks *Pss) processAsym(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error) { + metrics.GetOrRegisterCounter("pss.process.asym", nil).Inc(1) + + recvmsg, err := envelope.OpenAsymmetric(ks.privateKey) + if err != nil { + return nil, "", nil, fmt.Errorf("could not decrypt message: %s", err) + } + // check signature (if signed), strip padding + if !recvmsg.ValidateAndParse() { + return nil, "", nil, errors.New("invalid message") + } + pubkeyid := common.ToHex(crypto.FromECDSAPub(recvmsg.Src)) + var from PssAddress + ks.mx.RLock() + if ks.pubKeyPool[pubkeyid][Topic(envelope.Topic)] != nil { + from = ks.pubKeyPool[pubkeyid][Topic(envelope.Topic)].address + } + ks.mx.RUnlock() + return recvmsg, pubkeyid, from, nil +} + +// Symkey garbage collection +// a key is removed if: +// - it is not marked as protected +// - it is not in the incoming decryption cache +func (ks *Pss) cleanKeys() (count int) { + for keyid, peertopics := range ks.symKeyPool { + var expiredtopics []Topic + for topic, psp := range peertopics { + if psp.protected { + continue + } + + var match bool + for i := ks.symKeyDecryptCacheCursor; i > ks.symKeyDecryptCacheCursor-cap(ks.symKeyDecryptCache) && i > 0; i-- { + cacheid := ks.symKeyDecryptCache[i%cap(ks.symKeyDecryptCache)] + if *cacheid == keyid { + match = true + } + } + if !match { + expiredtopics = append(expiredtopics, topic) + } + } + for _, topic := range expiredtopics { + ks.mx.Lock() + delete(ks.symKeyPool[keyid], topic) + log.Trace("symkey cleanup deletion", "symkeyid", keyid, "topic", topic, "val", ks.symKeyPool[keyid]) + ks.mx.Unlock() + count++ + } + } + return count +} + +// Automatically generate a new symkey for a topic and address hint +func (ks *KeyStore) GenerateSymmetricKey(topic Topic, address PssAddress, addToCache bool) (string, error) { + keyid, err := ks.w.GenerateSymKey() + if err == nil { + ks.addSymmetricKeyToPool(keyid, topic, address, addToCache, false) + } + return keyid, err +} + +// Returns a symmetric key byte sequence stored in the whisper backend by its unique id. +// Passes on the error value from the whisper backend. +func (ks *KeyStore) GetSymmetricKey(symkeyid string) ([]byte, error) { + return ks.w.GetSymKey(symkeyid) +} + +// Links a peer symmetric key (arbitrary byte sequence) to a topic. +// +// This is required for symmetrically encrypted message exchange on the given topic. +// +// The key is stored in the whisper backend. +// +// If addtocache is set to true, the key will be added to the cache of keys +// used to attempt symmetric decryption of incoming messages. +// +// Returns a string id that can be used to retrieve the key bytes +// from the whisper backend (see pss.GetSymmetricKey()) +func (ks *KeyStore) SetSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool) (string, error) { + if err := validateAddress(address); err != nil { + return "", err + } + return ks.setSymmetricKey(key, topic, address, addtocache, true) +} + +func (ks *KeyStore) setSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool, protected bool) (string, error) { + keyid, err := ks.w.AddSymKeyDirect(key) + if err == nil { + ks.addSymmetricKeyToPool(keyid, topic, address, addtocache, protected) + } + return keyid, err +} |