diff --git i/whisper/whisperv6/api.go w/whisper/whisperv6/api.go index c60bc46a1..2de99f293 100644 --- i/whisper/whisperv6/api.go +++ w/whisper/whisperv6/api.go @@ -317,6 +317,16 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil. return result, err } +// UninstallFilter is alias for Unsubscribe +func (api *PublicWhisperAPI) UninstallFilter(id string) { + api.w.Unsubscribe(id) +} + +// Unsubscribe disables and removes an existing filter. +func (api *PublicWhisperAPI) Unsubscribe(id string) { + api.w.Unsubscribe(id) +} + //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go // Criteria holds various filter options for inbound messages. diff --git i/whisper/whisperv6/whisper.go w/whisper/whisperv6/whisper.go index 880cced09..702556079 100644 --- i/whisper/whisperv6/whisper.go +++ w/whisper/whisperv6/whisper.go @@ -382,9 +382,9 @@ func (whisper *Whisper) NewKeyPair() (string, error) { return "", fmt.Errorf("failed to generate valid key") } - id, err := GenerateRandomID() + id, err := toDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) + return "", err } whisper.keyMu.Lock() @@ -399,11 +399,16 @@ func (whisper *Whisper) NewKeyPair() (string, error) { // DeleteKeyPair deletes the specified key if it exists. func (whisper *Whisper) DeleteKeyPair(key string) bool { + deterministicID, err := toDeterministicID(key, keyIDSize) + if err != nil { + return false + } + whisper.keyMu.Lock() defer whisper.keyMu.Unlock() - if whisper.privateKeys[key] != nil { - delete(whisper.privateKeys, key) + if whisper.privateKeys[deterministicID] != nil { + delete(whisper.privateKeys, deterministicID) return true } return false @@ -411,31 +416,73 @@ func (whisper *Whisper) DeleteKeyPair(key string) bool { // AddKeyPair imports a asymmetric private key and returns it identifier. func (whisper *Whisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { - id, err := GenerateRandomID() + id, err := makeDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) + return "", err + } + if whisper.HasKeyPair(id) { + return id, nil // no need to re-inject } whisper.keyMu.Lock() whisper.privateKeys[id] = key whisper.keyMu.Unlock() + log.Info("Whisper identity added", "id", id, "pubkey", common.ToHex(crypto.FromECDSAPub(&key.PublicKey))) return id, nil } +// SelectKeyPair adds cryptographic identity, and makes sure +// that it is the only private key known to the node. +func (whisper *Whisper) SelectKeyPair(key *ecdsa.PrivateKey) error { + id, err := makeDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + if err != nil { + return err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + whisper.privateKeys = make(map[string]*ecdsa.PrivateKey) // reset key store + whisper.privateKeys[id] = key + + log.Info("Whisper identity selected", "id", id, "key", common.ToHex(crypto.FromECDSAPub(&key.PublicKey))) + return nil +} + +// DeleteKeyPairs removes all cryptographic identities known to the node +func (whisper *Whisper) DeleteKeyPairs() error { + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + whisper.privateKeys = make(map[string]*ecdsa.PrivateKey) + + return nil +} + // HasKeyPair checks if the whisper node is configured with the private key // of the specified public pair. func (whisper *Whisper) HasKeyPair(id string) bool { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return false + } + whisper.keyMu.RLock() defer whisper.keyMu.RUnlock() - return whisper.privateKeys[id] != nil + return whisper.privateKeys[deterministicID] != nil } // GetPrivateKey retrieves the private key of the specified identity. func (whisper *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return nil, err + } + whisper.keyMu.RLock() defer whisper.keyMu.RUnlock() - key := whisper.privateKeys[id] + key := whisper.privateKeys[deterministicID] if key == nil { return nil, fmt.Errorf("invalid id") } @@ -467,6 +514,23 @@ func (whisper *Whisper) GenerateSymKey() (string, error) { return id, nil } +// AddSymKey stores the key with a given id. +func (whisper *Whisper) AddSymKey(id string, key []byte) (string, error) { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return "", err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.symKeys[deterministicID] != nil { + return "", fmt.Errorf("key already exists: %v", id) + } + whisper.symKeys[deterministicID] = key + return deterministicID, nil +} + // AddSymKeyDirect stores the key, and returns its id. func (whisper *Whisper) AddSymKeyDirect(key []byte) (string, error) { if len(key) != aesKeyLength { @@ -1013,6 +1077,33 @@ func GenerateRandomID() (id string, err error) { return id, err } +// makeDeterministicID generates a deterministic ID, based on a given input +func makeDeterministicID(input string, keyLen int) (id string, err error) { + buf := pbkdf2.Key([]byte(input), nil, 4096, keyLen, sha256.New) + if !validateDataIntegrity(buf, keyIDSize) { + return "", fmt.Errorf("error in GenerateDeterministicID: failed to generate key") + } + id = common.Bytes2Hex(buf) + return id, err +} + +// toDeterministicID reviews incoming id, and transforms it to format +// expected internally be private key store. Originally, public keys +// were used as keys, now random keys are being used. And in order to +// make it easier to consume, we now allow both random IDs and public +// keys to be passed. +func toDeterministicID(id string, expectedLen int) (string, error) { + if len(id) != (expectedLen * 2) { // we received hex key, so number of chars in id is doubled + var err error + id, err = makeDeterministicID(id, expectedLen) + if err != nil { + return "", err + } + } + + return id, nil +} + func isFullNode(bloom []byte) bool { if bloom == nil { return true @@ -1048,3 +1139,15 @@ func addBloom(a, b []byte) []byte { } return c } + +// SelectedKeyPairID returns the id of currently selected key pair. +// It helps distinguish between different users w/o exposing the user identity itself. +func (whisper *Whisper) SelectedKeyPairID() string { + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + + for id := range whisper.privateKeys { + return id + } + return "" +}