diff --git a/api/client_test.go b/api/client_test.go index 77045f9df4690366e527e9c84f31577ea66a1578..01dff2c7cc9aec25427124273f00a2f7f7706e4a 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -14,6 +14,7 @@ import ( "gitlab.com/elixxir/client/keyStore" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/crypto/csprng" "gitlab.com/elixxir/crypto/cyclic" @@ -170,10 +171,10 @@ func TestRegisterUserE2E(t *testing.T) { privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize) publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey} - myUser := &storage.User{User: userID, Username: "test"} + myUser := &user2.User{User: userID, Username: "test"} session := user.NewSession(testClient.storage, "password") - userData := &storage.UserData{ + userData := &user2.UserData{ ThisUser: myUser, RSAPrivateKey: privateKeyRSA, RSAPublicKey: &publicKeyRSA, @@ -280,10 +281,10 @@ func TestRegisterUserE2E_CheckAllKeys(t *testing.T) { privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize) publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey} - myUser := &storage.User{User: userID, Username: "test"} + myUser := &user2.User{User: userID, Username: "test"} session := user.NewSession(testClient.storage, "password") - userData := &storage.UserData{ + userData := &user2.UserData{ ThisUser: myUser, RSAPrivateKey: privateKeyRSA, RSAPublicKey: &publicKeyRSA, diff --git a/api/mockserver_test.go b/api/mockserver_test.go index 870c01c7c4d55a5a2dbac571b5b00a842df5f854..89cad54aee31423c4715015000a53cb0e91bafb5 100644 --- a/api/mockserver_test.go +++ b/api/mockserver_test.go @@ -12,6 +12,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/io" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/client/userRegistry" "gitlab.com/elixxir/comms/gateway" @@ -237,7 +238,7 @@ func TestRegister_InvalidRegState(t *testing.T) { client.session = user.NewSession(nil, "password") client.sessionV2, _ = storage.Init(".ekv-invalidregstate", "password") - userData := &storage.UserData{ + userData := &user2.UserData{ ThisUser: usr, RSAPrivateKey: privKey, RSAPublicKey: pubKey, diff --git a/api/private.go b/api/private.go index 6ce7a05264e324d1522ebf9083ea6778983957d9..78903ceff8f3e16bd4c9489a939e6160d9bbfb47 100644 --- a/api/private.go +++ b/api/private.go @@ -14,6 +14,7 @@ import ( "gitlab.com/elixxir/client/io" "gitlab.com/elixxir/client/keyStore" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/client/userRegistry" pb "gitlab.com/elixxir/comms/mixmessages" @@ -33,10 +34,10 @@ const PermissioningAddrID = "Permissioning" // precannedRegister is a helper function for Register // It handles the precanned registration case -func (cl *Client) precannedRegister(registrationCode string) (*storage.User, *id.ID, map[id.ID]user.NodeKeys, error) { +func (cl *Client) precannedRegister(registrationCode string) (*user2.User, *id.ID, map[id.ID]user.NodeKeys, error) { var successLook bool var UID *id.ID - var u *storage.User + var u *user2.User var err error nk := make(map[id.ID]user.NodeKeys) @@ -314,8 +315,8 @@ func (cl *Client) GenerateKeys(rsaPrivKey *rsa.PrivateKey, return err } - userData := &storage.UserData{ - ThisUser: &storage.User{ + userData := &user2.UserData{ + ThisUser: &user2.User{ User: usr.User, Username: usr.Username, Precan: usr.Precan, @@ -436,7 +437,7 @@ func generateE2eKeys(cmixGrp, e2eGrp *cyclic.Group) (e2ePrivateKey, e2ePublicKey //generateUserInformation serves as a helper function for RegisterUser. // It generates a salt s.t. it can create a user and their ID func generateUserInformation(publicKeyRSA *rsa.PublicKey) ([]byte, *id.ID, - *storage.User, error) { + *user2.User, error) { //Generate salt for UserID salt := make([]byte, SaltSize) _, err := csprng.NewSystemRNG().Read(salt) diff --git a/api/register_test.go b/api/register_test.go index 34cabf70b5554532b12ca961561246775e1ad6eb..6fa3bddf001fdb8a520b208b28f5674587a158e7 100644 --- a/api/register_test.go +++ b/api/register_test.go @@ -7,7 +7,7 @@ package api import ( "gitlab.com/elixxir/client/io" - "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/xx_network/primitives/id" "testing" @@ -87,7 +87,7 @@ func TestClient_Register(t *testing.T) { } //Verify the user from the session make in the registration above matches expected user -func VerifyRegisterGobUser(curUser *storage.User, t *testing.T) { +func VerifyRegisterGobUser(curUser *user2.User, t *testing.T) { expectedUser := id.NewIdFromUInt(5, id.User, t) diff --git a/bots/bots_test.go b/bots/bots_test.go index 7318d309c6f8c555594cf0ecb4bbe296521a9373..6dd35b682e9450248d9466a5f47b87e890daaa75 100644 --- a/bots/bots_test.go +++ b/bots/bots_test.go @@ -17,6 +17,7 @@ import ( "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/large" @@ -72,7 +73,7 @@ var keyFingerprint string var pubKey []byte func TestMain(m *testing.M) { - u := &storage.User{ + u := &user2.User{ User: new(id.ID), Username: "Bernie", } @@ -83,8 +84,8 @@ func TestMain(m *testing.M) { fakeSession := user.NewSession(&globals.RamStorage{}, "password") fakeSession2 := storage.InitTestingSession(m) - fakeSession2.CommitUserData(&storage.UserData{ - ThisUser: &storage.User{ + fakeSession2.CommitUserData(&user2.UserData{ + ThisUser: &user2.User{ User: u.User, Username: u.Username, }, diff --git a/crypto/encryptdecrypt_test.go b/crypto/encryptdecrypt_test.go index 247748d72a6167feb8fbaa08ce94ecd283bb577b..0ed8c376f6be5e2b9d8171e72fa4661c551ab380 100644 --- a/crypto/encryptdecrypt_test.go +++ b/crypto/encryptdecrypt_test.go @@ -11,6 +11,7 @@ import ( "encoding/binary" "fmt" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/client/userRegistry" pb "gitlab.com/elixxir/comms/mixmessages" @@ -74,7 +75,7 @@ func setup() { SessionV2, _ = storage.Init(".ekvcryptotest", "password") - userData := &storage.UserData{ + userData := &user2.UserData{ ThisUser: u, CmixGrp: cmixGrp, E2EGrp: e2eGrp, diff --git a/storage/processingrounds.go b/io/processingrounds.go similarity index 97% rename from storage/processingrounds.go rename to io/processingrounds.go index 59a2f598d1d36319d758d4346ba614b2992c3ef8..3c7d09201651b98c2af9e8a27f6c2fc38b218fb7 100644 --- a/storage/processingrounds.go +++ b/io/processingrounds.go @@ -1,4 +1,4 @@ -package storage +package io // File for storing info about which rounds are processing diff --git a/storage/processingrounds_test.go b/io/processingrounds_test.go similarity index 98% rename from storage/processingrounds_test.go rename to io/processingrounds_test.go index 7cd6f56788774842ae521e8a48bec7de0c2a4b15..2b86976aff6a0ac12a8adb1e5ca34cc7aab9876e 100644 --- a/storage/processingrounds_test.go +++ b/io/processingrounds_test.go @@ -1,4 +1,4 @@ -package storage +package io // Testing functions for Processing Round structure diff --git a/rekey/rekey_test.go b/rekey/rekey_test.go index b45c75338114847d2ff0a29553edfd0783266d3c..6ed7ffc7515941af7817041bb613e95b9781f221 100644 --- a/rekey/rekey_test.go +++ b/rekey/rekey_test.go @@ -9,6 +9,7 @@ import ( "gitlab.com/elixxir/client/keyStore" "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/client/userRegistry" "gitlab.com/elixxir/crypto/csprng" @@ -68,7 +69,7 @@ func TestMain(m *testing.M) { grp, e2eGrp := getGroups() userRegistry.InitUserRegistry(grp) rng := csprng.NewSystemRNG() - u := &storage.User{ + u := &user2.User{ User: new(id.ID), Username: "Bernie", } @@ -96,7 +97,7 @@ func TestMain(m *testing.M) { sessionV2 := storage.InitTestingSession(m) - userData := &storage.UserData{ + userData := &user2.UserData{ ThisUser: u, RSAPrivateKey: privateKeyRSA, RSAPublicKey: &publicKeyRSA, diff --git a/storage/cmix/key.go b/storage/cmix/key.go index b0f9bcf2d0f6e1e9bd9f0302e53c2128e6693045..f2078b678e1d78902337663ce4cede06b5fddeff 100644 --- a/storage/cmix/key.go +++ b/storage/cmix/key.go @@ -26,6 +26,11 @@ func NewKey(kv *versioned.KV, k *cyclic.Int, id *id.ID) (*key, error) { return newKey, newKey.save() } +// returns the cyclic key +func (k *key) Get() *cyclic.Int { + return k.k +} + // loads the key for the given node id from the versioned keystore func loadKey(kv *versioned.KV, id *id.ID) (*key, error) { k := &key{} diff --git a/storage/cmix/roundKeys.go b/storage/cmix/roundKeys.go index 96bfeccd1c8f375ca8743e59f8fd26b49fb058fc..6e04e5d5c502341f8b9093639da45ab4fd7d6131 100644 --- a/storage/cmix/roundKeys.go +++ b/storage/cmix/roundKeys.go @@ -2,33 +2,41 @@ package cmix import ( jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/crypto/cmix" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/primitives/format" ) -type RoundKeys []*cyclic.Int +type RoundKeys struct { + keys []*key + g *cyclic.Group +} // Encrypts the given message for CMIX // Panics if the passed message is not sized correctly for the group -func (rk RoundKeys) Encrypt(grp *cyclic.Group, msg format.Message, +func (rk *RoundKeys) Encrypt(msg format.Message, salt []byte) (format.Message, [][]byte) { - if msg.GetPrimeByteLen() != grp.GetP().ByteLen() { + if msg.GetPrimeByteLen() != rk.g.GetP().ByteLen() { jww.FATAL.Panicf("Cannot encrypt message whose size does not " + "align with the size of the prime") } - ecrMsg := cmix.ClientEncrypt(grp, msg, salt, rk) + keys := make([]*cyclic.Int, len(rk.keys)) + + for i, k := range rk.keys { + keys[i] = k.Get() + } + + ecrMsg := cmix.ClientEncrypt(rk.g, msg, salt, keys) h, err := hash.NewCMixHash() if err != nil { - globals.Log.ERROR.Printf("Cound not get hash for KMAC generation: %+v", h) + jww.FATAL.Panicf("Cound not get hash for KMAC generation: %+v", h) } - KMAC := cmix.GenerateKMACs(salt, rk, h) + KMAC := cmix.GenerateKMACs(salt, keys, h) return ecrMsg, KMAC } diff --git a/storage/cmix/roundKeys_test.go b/storage/cmix/roundKeys_test.go index d64044e97168aabf8140a566b6512a4f46324ff2..be9dcaef05d4c2d39557855b9ccd7d0a5e7cdf16 100644 --- a/storage/cmix/roundKeys_test.go +++ b/storage/cmix/roundKeys_test.go @@ -83,11 +83,13 @@ func TestRoundKeys_Encrypt_Consistency(t *testing.T) { prng := rand.New(rand.NewSource(42)) - rk := RoundKeys(make([]*cyclic.Int, numKeys)) + keys := make([]*key, numKeys) for i := 0; i < numKeys; i++ { keyBytes, _ := csprng.GenerateInGroup(cmixGrp.GetPBytes(), cmixGrp.GetP().ByteLen(), prng) - rk[i] = cmixGrp.NewIntFromBytes(keyBytes) + keys[i] = &key{ + k: cmixGrp.NewIntFromBytes(keyBytes), + } } salt := make([]byte, 32) @@ -98,7 +100,12 @@ func TestRoundKeys_Encrypt_Consistency(t *testing.T) { prng.Read(contents) msg.SetContents(contents) - encMsg, kmacs := rk.Encrypt(cmixGrp, msg, salt) + rk := RoundKeys{ + keys: keys, + g: cmixGrp, + } + + encMsg, kmacs := rk.Encrypt(msg, salt) if !bytes.Equal(encMsg.GetData(), expectedPayload) { t.Errorf("Encrypted messages do not match") diff --git a/storage/cmix/store.go b/storage/cmix/store.go index 043f0dbdd9682d1c5902ebba9d9de795f9c4fa4d..3ff308f25033883455870a5e40e4cc8bdea3c41e 100644 --- a/storage/cmix/store.go +++ b/storage/cmix/store.go @@ -3,8 +3,10 @@ package cmix import ( "encoding/json" "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/utility" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/xx_network/comms/connect" "gitlab.com/xx_network/primitives/id" "sync" @@ -12,39 +14,53 @@ import ( ) const currentStoreVersion = 0 -const currentKeyStoreVersion = 0 const storeKey = "cmixKeyStore" const pubKeyKey = "cmixDhPubKey" const privKeyKey = "cmixDhPrivKey" +const grpKey = "cmixGroupKey" type Store struct { nodes map[id.ID]*key dhPrivateKey *cyclic.Int dhPublicKey *cyclic.Int + grp *cyclic.Group + kv *versioned.KV mux sync.RWMutex } // returns a new cmix storage object -func NewStore(kv *versioned.KV, pub, priv *cyclic.Int) (*Store, error) { +func NewStore(grp *cyclic.Group, kv *versioned.KV, priv *cyclic.Int) (*Store, error) { + //generate public key + pub := diffieHellman.GeneratePublicKey(priv, grp) + s := &Store{ nodes: make(map[id.ID]*key), dhPrivateKey: priv, dhPublicKey: priv, + grp: grp, kv: kv, } - err := storeDhKey(kv, pub, pubKeyKey) + err := utility.StoreCyclicKey(kv, pub, pubKeyKey) if err != nil { return nil, - errors.WithMessage(err, "Failed to store cmix DH public key") + errors.WithMessage(err, + "Failed to store cmix DH public key") + } + + err = utility.StoreCyclicKey(kv, priv, privKeyKey) + if err != nil { + return nil, errors.WithMessage(err, + "Failed to store cmix DH private key") } - err = storeDhKey(kv, priv, privKeyKey) + err = utility.StoreGroup(kv, grp, grpKey) if err != nil { - return nil, errors.WithMessage(err, "Failed to store cmix DH private key") + return nil, errors.WithMessage(err, + "Failed to store cmix group") } return s, s.save() @@ -108,13 +124,13 @@ func (s *Store) Remove(nid *id.ID, k *cyclic.Int) error { } //Returns a RoundKeys for the topology and a list of nodes it did not have a key for -func (s *Store) GetRoundKeys(topology *connect.Circuit) (RoundKeys, []*id.ID) { +func (s *Store) GetRoundKeys(topology *connect.Circuit) (*RoundKeys, []*id.ID) { s.mux.RLock() defer s.mux.RUnlock() var missingNodes []*id.ID - rk := RoundKeys(make([]*cyclic.Int, topology.Len())) + keys := make([]*key, topology.Len()) for i := 0; i < topology.Len(); i++ { nid := topology.GetNodeAtIndex(i) @@ -122,13 +138,33 @@ func (s *Store) GetRoundKeys(topology *connect.Circuit) (RoundKeys, []*id.ID) { if !ok { missingNodes = append(missingNodes, nid) } else { - rk[i] = k.k + keys[i] = k } } + rk := &RoundKeys{ + keys: keys, + g: s.grp, + } + return rk, missingNodes } +//Returns the diffie hellman private key +func (s *Store) GetDHPrivateKey() *cyclic.Int { + return s.dhPrivateKey +} + +//Returns the diffie hellman public key +func (s *Store) GetDHPublicKey() *cyclic.Int { + return s.dhPublicKey +} + +//Returns the cyclic group used for cmix +func (s *Store) GetGroup() *cyclic.Group { + return s.grp +} + // stores the cmix store func (s *Store) save() error { now := time.Now() @@ -176,45 +212,23 @@ func (s *Store) unmarshal(b []byte) error { s.nodes[nid] = k } - s.dhPrivateKey, err = loadDhKey(s.kv, privKeyKey) + s.dhPrivateKey, err = utility.LoadCyclicKey(s.kv, privKeyKey) if err != nil { return errors.WithMessage(err, "Failed to load cmix DH private key") } - s.dhPublicKey, err = loadDhKey(s.kv, pubKeyKey) + s.dhPublicKey, err = utility.LoadCyclicKey(s.kv, pubKeyKey) if err != nil { return errors.WithMessage(err, "Failed to load cmix DH public key") } - return nil -} - -func storeDhKey(kv *versioned.KV, dh *cyclic.Int, key string) error { - now := time.Now() - - data, err := dh.GobEncode() - if err != nil { - return err - } - - obj := versioned.Object{ - Version: currentKeyVersion, - Timestamp: now, - Data: data, - } - - return kv.Set(key, &obj) -} - -func loadDhKey(kv *versioned.KV, key string) (*cyclic.Int, error) { - vo, err := kv.Get(key) + s.grp, err = utility.LoadGroup(s.kv, grpKey) if err != nil { - return nil, err + return errors.WithMessage(err, + "Failed to load cmix group") } - dhKey := &cyclic.Int{} - - return dhKey, dhKey.GobDecode(vo.Data) + return nil } \ No newline at end of file diff --git a/storage/contact.go b/storage/contact.go deleted file mode 100644 index 4eb415dd264f2e4eae89c8825642ccdbbd6c837d..0000000000000000000000000000000000000000 --- a/storage/contact.go +++ /dev/null @@ -1,128 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - -package storage - -import ( - "encoding/json" - "gitlab.com/elixxir/client/globals" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/xx_network/primitives/id" - "time" -) - -const currentContactVersion = 0 - -// Contact holds the public key and ID of a given contact. -type Contact struct { - Id *id.ID - PublicKey []byte - Email string -} - -// loadAllContacts populates the "contacts" variable for the session -func (s *Session) loadAllContacts() { - s.contactsLck.Lock() - defer s.contactsLck.Unlock() - obj, err := s.Get("AllContacts") - if err != nil { - s.contacts = make(map[string]*Contact) - return - } - err = json.Unmarshal(obj.Data, s.contacts) - if err != nil { - s.contacts = make(map[string]*Contact) - } -} - -func (s *Session) saveContacts() error { - data, err := json.Marshal(s.contacts) - if err != nil { - return err - } - obj := versioned.Object{ - Version: currentContactVersion, - Timestamp: time.Now(), - Data: data, - } - return s.Set("AllContacts", &obj) -} - -func (s *Session) updateContact(record *Contact) error { - s.contactsLck.Lock() - defer s.contactsLck.Unlock() - s.contacts[record.Id.String()] = record - return s.saveContacts() -} - -// GetContactByEmail reads contact information from disk -func (s *Session) GetContactByEmail(email string) (*Contact, error) { - key := versioned.MakeKeyWithPrefix("Contact", email) - - obj, err := s.Get(key) - if err != nil { - return nil, err - } - // Correctly implemented upgrade should always change the version number to what's current - if obj.Version != currentContactVersion { - globals.Log.WARN.Printf("Session.GetContact: got unexpected "+ - "version %v, expected version %v", obj.Version, - currentContactVersion) - } - - // deserialize - var contact Contact - err = json.Unmarshal(obj.Data, &contact) - return &contact, err -} - -// SetContactByEmail saves contact information to disk. -func (s *Session) SetContactByEmail(email string, record *Contact) error { - err := s.updateContact(record) - if err != nil { - return err - } - - key := versioned.MakeKeyWithPrefix("Contact", email) - data, err := json.Marshal(record) - if err != nil { - return err - } - obj := versioned.Object{ - Version: currentContactVersion, - Timestamp: time.Now(), - Data: data, - } - return s.Set(key, &obj) -} - -func (s *Session) GetContactByID(ID *id.ID) *Contact { - s.contactsLck.Lock() - defer s.contactsLck.Unlock() - c, ok := s.contacts[ID.String()] - if !ok { - return nil - } - return c -} - -// DeleteContactByID removes the contact from disk -func (s *Session) DeleteContactByID(ID *id.ID) error { - s.contactsLck.Lock() - defer s.contactsLck.Unlock() - record, ok := s.contacts[ID.String()] - if !ok { - return nil - } - delete(s.contacts, record.Id.String()) - err := s.saveContacts() - if err != nil { - return err - } - - key := versioned.MakeKeyWithPrefix("Contact", record.Email) - return s.Delete(key) -} diff --git a/storage/contact_test.go b/storage/contact_test.go deleted file mode 100644 index 1787252e0022149595dba2181ef86b75695c0dca..0000000000000000000000000000000000000000 --- a/storage/contact_test.go +++ /dev/null @@ -1,41 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - -package storage - -import ( - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/ekv" - "gitlab.com/xx_network/primitives/id" - "reflect" - "testing" -) - -// Show that all fields of a searched user record get stored -func TestSession_Contact(t *testing.T) { - store := make(ekv.Memstore) - session := &Session{kv: versioned.NewKV(store)} - session.loadAllContacts() - - expectedRecord := &Contact{ - Id: id.NewIdFromUInt(24601, id.User, t), - PublicKey: []byte("not a real public key"), - } - - name := "niamh@elixxir.io" - err := session.SetContactByEmail(name, expectedRecord) - if err != nil { - t.Fatal(err) - } - retrievedRecord, err := session.GetContactByEmail(name) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(expectedRecord, retrievedRecord) { - t.Error("Expected and retrieved records were different") - } -} diff --git a/storage/e2e/session.go b/storage/e2e/session.go index ce9fa1bb49018fd6de540623b2fe20247ec0abd9..a9943ddb78016cdfdf6c7e6555300f73e45a692c 100644 --- a/storage/e2e/session.go +++ b/storage/e2e/session.go @@ -2,7 +2,7 @@ package e2e import ( "encoding/json" - "errors" + "github.com/pkg/errors" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/csprng" "gitlab.com/elixxir/crypto/cyclic" @@ -76,9 +76,12 @@ func newSession(manager *Manager, myPrivKey *cyclic.Int, partnerPubKey *cyclic.I confirmed: t == Receive, } - session.generate() + err := session.generate() + if err != nil { + return nil, err + } - err := session.save() + err = session.save() if err != nil { return nil, err } @@ -280,7 +283,7 @@ func (s *Session) useKey(keynum uint32) error { // generates keys from the base data stored in the session object. // myPrivKey will be generated if not present -func (s *Session) generate() { +func (s *Session) generate() error { grp := s.manager.ctx.grp //generate public key if it is not present @@ -304,13 +307,19 @@ func (s *Session) generate() { s.ttl = uint32(keysTTL) //create the new state vectors. This will cause disk operations storing them - s.keyState = newStateVector(s.manager.ctx, keyEKVPrefix, numKeys) + var err error + s.keyState, err = newStateVector(s.manager.ctx, keyEKVPrefix, numKeys) + if err != nil { + return errors.WithMessage(err, "Failed key generation") + } //register keys for reception if this is a reception session if s.t == Receive { //register keys s.manager.ctx.fa.add(s.getUnusedKeys()) } + + return nil } //returns key objects for all unused keys diff --git a/storage/e2e/store.go b/storage/e2e/store.go index b2cef1c8b8d33abc1fda97fe37ecb4a47420cef0..964f88e251c72d89b4f3ecfb08788bf0a74680b0 100644 --- a/storage/e2e/store.go +++ b/storage/e2e/store.go @@ -4,31 +4,49 @@ import ( "encoding/json" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/storage/utility" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" "sync" "time" ) -const storeKey = "e2eKeyStore" + const currentStoreVersion = 0 +const storeKey = "e2eKeyStore" +const pubKeyKey = "e2eDhPubKey" +const privKeyKey = "e2eDhPrivKey" +const grpKey = "e2eGroupKey" type Store struct { managers map[id.ID]*Manager mux sync.RWMutex + dhPrivateKey *cyclic.Int + dhPublicKey *cyclic.Int + grp *cyclic.Group + fingerprints context } -func NewStore(grp *cyclic.Group, kv *versioned.KV) *Store { +func NewStore(grp *cyclic.Group, kv *versioned.KV, priv *cyclic.Int) (*Store, error) { + //generate public key + pub := diffieHellman.GeneratePublicKey(priv, grp) + fingerprints := newFingerprints() - return &Store{ + s := &Store{ managers: make(map[id.ID]*Manager), fingerprints: fingerprints, + + dhPrivateKey: priv, + dhPublicKey: pub, + grp: grp, + context: context{ fa: &fingerprints, grp: grp, @@ -36,10 +54,39 @@ func NewStore(grp *cyclic.Group, kv *versioned.KV) *Store { }, } + err := utility.StoreCyclicKey(kv, pub, pubKeyKey) + if err != nil { + return nil, + errors.WithMessage(err, + "Failed to store e2e DH public key") + } + + err = utility.StoreCyclicKey(kv, priv, privKeyKey) + if err != nil { + return nil, errors.WithMessage(err, + "Failed to store e2e DH private key") + } + + err = utility.StoreGroup(kv, grp, grpKey) + if err != nil { + return nil, errors.WithMessage(err, + "Failed to store e2e group") + } + + return s, s.save() } -func LoadStore(grp *cyclic.Group, kv *versioned.KV) (*Store, error) { - s := NewStore(grp, kv) +func LoadStore(kv *versioned.KV) (*Store, error) { + fingerprints := newFingerprints() + s := &Store{ + managers: make(map[id.ID]*Manager), + fingerprints: fingerprints, + + context: context{ + fa: &fingerprints, + kv: kv, + }, + } obj, err := kv.Get(storeKey) if err != nil { @@ -52,6 +99,8 @@ func LoadStore(grp *cyclic.Group, kv *versioned.KV) (*Store, error) { return nil, err } + s.context.grp = s.grp + return s, nil } @@ -101,6 +150,26 @@ func (s *Store) GetPartner(partnerID *id.ID) (*Manager, error) { return m, nil } +//Pops a key for use based upon its fingerprint +func (s *Store) PopKey(f format.Fingerprint) (*Key, error) { + return s.fingerprints.Pop(f) +} + +//Returns the diffie hellman private key +func (s *Store) GetDHPrivateKey() *cyclic.Int { + return s.dhPrivateKey +} + +//Returns the diffie hellman public key +func (s *Store) GetDHPublicKey() *cyclic.Int { + return s.dhPublicKey +} + +//Returns the cyclic group used for cmix +func (s *Store) GetGroup() *cyclic.Group { + return s.grp +} + //ekv functions func (s *Store) marshal() ([]byte, error) { @@ -134,6 +203,25 @@ func (s *Store) unmarshal(b []byte) error { s.managers[partnerID] = manager } + + s.dhPrivateKey, err = utility.LoadCyclicKey(s.kv, privKeyKey) + if err != nil { + return errors.WithMessage(err, + "Failed to load e2e DH private key") + } + + s.dhPublicKey, err = utility.LoadCyclicKey(s.kv, pubKeyKey) + if err != nil { + return errors.WithMessage(err, + "Failed to load e2e DH public key") + } + + s.grp, err = utility.LoadGroup(s.kv, grpKey) + if err != nil { + return errors.WithMessage(err, + "Failed to load e2e group") + } + return nil } diff --git a/storage/regStatus.go b/storage/regStatus.go index 00b89539bfc56a3acdbe5ff1c547a6bbe81796f1..28f55a99e15919ea755bf1319204aa402b3e651b 100644 --- a/storage/regStatus.go +++ b/storage/regStatus.go @@ -1,5 +1,17 @@ package storage +import ( + "encoding/binary" + "fmt" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "os" + "time" +) + +const currentRegistrationStatusVersion = 0 +const registrationStatusKey = "regStatusKey" + type RegistrationStatus uint32 const ( @@ -8,3 +20,85 @@ const ( PermissioningComplete RegistrationStatus = 20000 // Set upon completion of RegisterWithPermissioning UDBComplete RegistrationStatus = 30000 // Set upon completion of RegisterWithUdb ) + +// stringer for Registration Status +func (rs RegistrationStatus) String() string { + switch rs { + case NotStarted: + return "Not Started" + case KeyGenComplete: + return "Key Generation Complete" + case PermissioningComplete: + return "Permissioning Registration Complete" + case UDBComplete: + return "User Discovery Registration Complete" + default: + return fmt.Sprintf("Unknown registration state %v", uint32(rs)) + } +} + +// creates a registration status from binary data +func regStatusUnmarshalBinary(b []byte) RegistrationStatus { + return RegistrationStatus(binary.BigEndian.Uint32(b)) +} + +// returns the binary representation of the registration status +func (rs RegistrationStatus) marshalBinary() []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint32(b, uint32(rs)) + return b +} + +// loads the registration status from disk. If the status cannot be found, it +// defaults to Not Started +func (s *Session) loadOrCreateRegStatus() error { + obj, err := s.Get(registrationStatusKey) + if err != nil { + if os.IsNotExist(err) { + // set at not started but do not save until it is updated + s.regStatus = NotStarted + return nil + } else { + return errors.WithMessagef(err, "Failed to load registration status") + } + } + s.regStatus = regStatusUnmarshalBinary(obj.Data) + return nil +} + +// sets the registration status to the passed status if it is greater than the +// current stats, otherwise returns an error +func (s *Session) ForwardRegistrationStatus(regStatus RegistrationStatus) error { + s.mux.Lock() + defer s.mux.Unlock() + + if regStatus <= s.regStatus { + return errors.Errorf("Cannot set registration status to a "+ + "status before the current stats: Current: %s, New: %s", + s.regStatus, regStatus) + } + + now := time.Now() + + obj := versioned.Object{ + Version: currentRegistrationStatusVersion, + Timestamp: now, + Data: regStatus.marshalBinary(), + } + + err := s.Set(registrationStatusKey, &obj) + if err != nil { + return errors.WithMessagef(err, "Failed to store registration status") + } + + s.regStatus = regStatus + return nil +} + +// sets the registration status to the passed status if it is greater than the +// current stats, otherwise returns an error +func (s *Session) GetRegistrationStatus() RegistrationStatus { + s.mux.RLock() + defer s.mux.RUnlock() + return s.regStatus +} \ No newline at end of file diff --git a/storage/registration.go b/storage/registration.go deleted file mode 100644 index 5ffd0b8fcb9503c37e14223f96be1819b4baa155..0000000000000000000000000000000000000000 --- a/storage/registration.go +++ /dev/null @@ -1,98 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// -package storage - -import ( - "encoding/json" - "gitlab.com/elixxir/client/globals" - "gitlab.com/elixxir/client/storage/versioned" - "time" -) - -var currentRegistrationVersion = uint64(0) - -// SetRegValidationSig builds the versioned object and sets it in the -// key-value store -func (s *Session) SetRegValidationSig(newVal []byte) error { - // Construct the versioned object - vo := &versioned.Object{ - Version: currentRegistrationVersion, - Timestamp: time.Now(), - Data: newVal, - } - - // Construct the key and place in the key-value store - key := "RegValidationSig" - - return s.kv.Set(key, vo) -} - -// GetRegValidationSig pulls the versioned object by the key and parses -// it into the requested registration signature -func (s *Session) GetRegValidationSig() ([]byte, error) { - key := "RegValidationSig" - - // Pull the object from the key-value store - voData, err := s.kv.Get(key) - if err != nil { - return nil, err - } - - if voData.Version != currentRegistrationVersion { - globals.Log.WARN.Printf("Session.GetRegValidationSig: got "+ - "unexpected version %v, expected version %v", - voData.Version, currentRegistrationVersion) - } - - return voData.Data, nil -} - -// SetRegState uses the SetInterface method to place the regstate into -// the key-value store -func (s *Session) SetRegState(newVal int64) error { - key := "RegState" - - data, err := json.Marshal(newVal) - if err != nil { - return err - } - - obj := versioned.Object{ - Version: currentRegistrationVersion, - Timestamp: time.Now(), - Data: data, - } - - return s.kv.Set(key, &obj) -} - -// GetRegValidationSig pulls the versioned object by the key and parses -// it into the requested registration signature -func (s *Session) GetRegState() (int64, error) { - // Construct the key from the - key := "RegState" - - // Pull the object from the key-value store - voData, err := s.kv.Get(key) - if err != nil { - return 0, err - } - - if voData.Version != currentRegistrationVersion { - globals.Log.WARN.Printf("Session.GetRegState: got unexpected "+ - "version %v, expected version %v", - voData.Version, currentRegistrationVersion) - } - - var data int64 - err = json.Unmarshal(voData.Data, &data) - if err != nil { - return 0, err - } - - return data, nil - -} diff --git a/storage/registration_test.go b/storage/registration_test.go deleted file mode 100644 index db6fdc447b645bf77092ae80aaa58364fb999d9e..0000000000000000000000000000000000000000 --- a/storage/registration_test.go +++ /dev/null @@ -1,55 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// -package storage - -import ( - "bytes" - "testing" -) - -func TestSession_RegState(t *testing.T) { - testSession := InitTestingSession(t) - - expectedVal := int64(42) - err := testSession.SetRegState(expectedVal) - if err != nil { - t.Errorf("Failed to place value in session: %v", err) - } - - retrievedVal, err := testSession.GetRegState() - if err != nil { - t.Errorf("Faield to get value from session: %v", err) - } - - if retrievedVal != expectedVal { - t.Errorf("Expected value not retrieved from file store!"+ - "\n\tExpected: %v"+ - "\n\tRecieved: %v", expectedVal, retrievedVal) - } - -} - -func TestSession_RegValidation(t *testing.T) { - testSession := InitTestingSession(t) - - expectedVal := []byte("testData") - - err := testSession.SetRegValidationSig(expectedVal) - if err != nil { - t.Errorf("Failed to place value in session: %v", err) - } - - retrievedVal, err := testSession.GetRegValidationSig() - if err != nil { - t.Errorf("Faield to get value from session: %v", err) - } - - if !bytes.Equal(retrievedVal, expectedVal) { - t.Errorf("Expected value not retrieved from file store!"+ - "\n\tExpected: %v"+ - "\n\tRecieved: %v", expectedVal, retrievedVal) - } -} diff --git a/storage/session.go b/storage/session.go index a541b5a8c8e47e5eeb92031d33b011c8902b36ed..ad4569f35975e7d45af6484db6b59c822f9bd778 100644 --- a/storage/session.go +++ b/storage/session.go @@ -9,180 +9,156 @@ package storage import ( - "bytes" - "encoding/gob" + "github.com/pkg/errors" "gitlab.com/elixxir/client/globals" "gitlab.com/elixxir/client/storage/cmix" + "gitlab.com/elixxir/client/storage/e2e" + "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/client/user" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/ekv" - "gitlab.com/xx_network/comms/connect" + "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" "sync" "testing" - "time" ) // Session object, backed by encrypted filestore type Session struct { - kv *versioned.KV - userData *UserData - mux sync.Mutex + kv *versioned.KV + mux sync.RWMutex - // Contacts controls - contacts map[string]*Contact - contactsLck sync.Mutex + regStatus RegistrationStatus + + //sub-stores + e2e *e2e.Store + cmix *cmix.Store + user *user.User + + loaded bool - //keystores - cmixKeys cmix.Store } // Initialize a new Session object func Init(baseDir, password string) (*Session, error) { fs, err := ekv.NewFilestore(baseDir, password) var s *Session - if err == nil { - s = &Session{ - kv: versioned.NewKV(fs), - } + if err != nil { + return nil, errors.WithMessage(err, + "Failed to create storage session") } - s.loadAllContacts() - - return s, err -} + s = &Session{ + kv: versioned.NewKV(fs), + loaded: false, + } -// a storage session with a memory backed for testing -func InitMem(t *testing.T) *Session { - if t == nil { - panic("cannot use a memstore not for testing") + err = s.loadOrCreateRegStatus() + if err != nil { + return nil, errors.WithMessage(err, + "Failed to load or create registration status") } - store := make(ekv.Memstore) - return &Session{kv: versioned.NewKV(store)} -} -// Get an object from the session -func (s *Session) Get(key string) (*versioned.Object, error) { - return s.kv.Get(key) + return s, nil } -// Set a value in the session -func (s *Session) Set(key string, object *versioned.Object) error { - return s.kv.Set(key, object) -} +// Creates new UserData in the session +func (s *Session) Create(uid *id.ID, salt []byte, rsaKey *rsa.PrivateKey, + isPrecanned bool, cmixDHPrivKey, e2eDHPrivKey *cyclic.Int, cmixGrp, + e2eGrp *cyclic.Group) error { + s.mux.Lock() + defer s.mux.Unlock() + if s.loaded { + return errors.New("Cannot create a session which already has one loaded") + } -// Delete a value in the session -func (s *Session) Delete(key string) error { - return s.kv.Delete(key) -} + var err error -// Obtain the LastMessageID from the Session -func (s *Session) GetLastMessageId() (string, error) { - v, err := s.Get("LastMessageID") - if v == nil || err != nil { - return "", nil + s.user, err = user.NewUser(s.kv, uid, salt, rsaKey, isPrecanned) + if err != nil { + return errors.WithMessage(err, "Failed to create Session due "+ + "to failed user creation") } - return string(v.Data), nil -} -// Set the LastMessageID in the Session -func (s *Session) SetLastMessageId(id string) error { - vo := &versioned.Object{ - Timestamp: time.Now(), - Data: []byte(id), + s.cmix, err = cmix.NewStore(cmixGrp, s.kv, cmixDHPrivKey) + if err != nil { + return errors.WithMessage(err, "Failed to create Session due "+ + "to failed cmix keystore creation") } - return s.Set("LastMessageID", vo) -} -// GetNodeKeys returns all keys -func (s *Session) GetNodeKeys() (map[string]user.NodeKeys, error) { - key := "NodeKeys" - var nodeKeys map[string]user.NodeKeys + s.e2e, err = e2e.NewStore(e2eGrp, s.kv, e2eDHPrivKey) + if err != nil { + return errors.WithMessage(err, "Failed to create Session due "+ + "to failed e2e keystore creation") + } - // Attempt to locate the keys map - v, err := s.Get(key) + s.loaded = true + return nil +} - // If the map doesn't exist, initialize it - if err != nil { - // Encode the new map - nodeKeys = make(map[string]user.NodeKeys) - var nodeKeysBuffer bytes.Buffer - enc := gob.NewEncoder(&nodeKeysBuffer) - err = enc.Encode(nodeKeys) - if err != nil { - return nil, err - } - - // Store the new map - vo := &versioned.Object{ - Timestamp: time.Now(), - Data: nodeKeysBuffer.Bytes(), - } - err = s.Set(key, vo) - if err != nil { - return nil, err - } - - // Return newly-initialized map - return nodeKeys, nil +// Loads existing user data into the session +func (s *Session) Load() error { + s.mux.Lock() + defer s.mux.Unlock() + if s.loaded { + return errors.New("Cannot load a session which already has one loaded") } - // If the map exists, decode and return it - var nodeKeyBuffer bytes.Buffer - nodeKeyBuffer.Write(v.Data) - dec := gob.NewDecoder(&nodeKeyBuffer) - err = dec.Decode(&nodeKeys) + var err error - return nodeKeys, err -} + s.user, err = user.LoadUser(s.kv) + if err != nil { + return errors.WithMessage(err, "Failed to load Session due "+ + "to failure to load user") + } -// GetNodeKeysFromCircuit obtains NodeKeys for a given circuit -func (s *Session) GetNodeKeysFromCircuit(topology *connect.Circuit) ( - []user.NodeKeys, error) { - nodeKeys, err := s.GetNodeKeys() + s.cmix, err = cmix.LoadStore(s.kv) if err != nil { - return nil, err + return errors.WithMessage(err, "Failed to load Session due "+ + "to failure to load cmix keystore") } - // Build a list of NodeKeys from the map - keys := make([]user.NodeKeys, topology.Len()) - for i := 0; i < topology.Len(); i++ { - nid := topology.GetNodeAtIndex(i) - keys[i] = nodeKeys[nid.String()] - globals.Log.INFO.Printf("Read NodeKey: %s: %v", nid, keys[i]) + s.e2e, err = e2e.LoadStore(s.kv) + if err != nil { + return errors.WithMessage(err, "Failed to load Session due "+ + "to failure to load e2e keystore") } - return keys, nil + s.loaded = true + return nil } -// Set NodeKeys in the Session -func (s *Session) PushNodeKey(id *id.ID, key user.NodeKeys) error { - // Thread-safety - s.mux.Lock() - defer s.mux.Unlock() +func (s *Session) User() *user.User { + s.mux.RLock() + defer s.mux.RUnlock() + return s.user +} - // Obtain NodeKeys map - nodeKeys, err := s.GetNodeKeys() - if err != nil { - return err - } +func (s *Session) Cmix() *cmix.Store { + s.mux.RLock() + defer s.mux.RUnlock() + return s.cmix +} - // Set new value inside of map - nodeKeys[id.String()] = key +func (s *Session) E2e() *e2e.Store { + s.mux.RLock() + defer s.mux.RUnlock() + return s.e2e +} - globals.Log.INFO.Printf("Adding NodeKey: %s: %v", id.String(), key) +// Get an object from the session +func (s *Session) Get(key string) (*versioned.Object, error) { + return s.kv.Get(key) +} - // Encode the map - var nodeKeysBuffer bytes.Buffer - enc := gob.NewEncoder(&nodeKeysBuffer) - err = enc.Encode(nodeKeys) +// Set a value in the session +func (s *Session) Set(key string, object *versioned.Object) error { + return s.kv.Set(key, object) +} - // Insert the map back into the Session - vo := &versioned.Object{ - Timestamp: time.Now(), - Data: nodeKeysBuffer.Bytes(), - } - return s.Set("NodeKeys", vo) +// Delete a value in the session +func (s *Session) Delete(key string) error { + return s.kv.Delete(key) } // Initializes a Session object wrapped around a MemStore object. diff --git a/storage/session_test.go b/storage/session_test.go index 048fa870c126ac51bc44c27494f2054c8397c0a1..5101075bffc1bb05100c8683b0f9493ee597a916 100644 --- a/storage/session_test.go +++ b/storage/session_test.go @@ -9,11 +9,6 @@ package storage import ( "bytes" "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/client/user" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/large" - "gitlab.com/xx_network/comms/connect" - "gitlab.com/xx_network/primitives/id" "os" "testing" "time" @@ -55,62 +50,4 @@ func TestSession_Smoke(t *testing.T) { if bytes.Compare(o.Data, []byte("test")) != 0 { t.Errorf("Failed to get data") } -} - -// Happy path for getting/setting LastMessageID -func TestSession_GetSetLastMessageId(t *testing.T) { - testId := "testLastMessageId" - - s := initTest(t) - - err := s.SetLastMessageId(testId) - if err != nil { - t.Errorf("Failed to set LastMessageId: %+v", err) - } - o, err := s.GetLastMessageId() - if err != nil { - t.Errorf("Failed to get LastMessageId") - } - - if testId != o { - t.Errorf("Failed to get LastMessageID, Got %s Expected %s", o, testId) - } -} - -// Happy path for getting/setting node keys -func TestSession_GetPushNodeKeys(t *testing.T) { - s := initTest(t) - - testId := id.NewIdFromString("test", id.Node, t) - testId2 := id.NewIdFromString("test2", id.Node, t) - testInt := cyclic.NewGroup(large.NewIntFromUInt(6), large.NewIntFromUInt(6)).NewInt(1) - testNodeKey := user.NodeKeys{ - TransmissionKey: testInt, - ReceptionKey: testInt, - } - - err := s.PushNodeKey(testId, testNodeKey) - if err != nil { - t.Errorf("Unable to push node key: %+v", err) - } - err = s.PushNodeKey(testId2, testNodeKey) - if err != nil { - t.Errorf("Unable to push node key: %+v", err) - } - - circ := connect.NewCircuit([]*id.ID{testId, testId2}) - results, err := s.GetNodeKeysFromCircuit(circ) - - if len(results) != 2 { - t.Errorf("Returned unexpected number of node keys: %d", len(results)) - return - } - if results[0].TransmissionKey.Cmp(testInt) != 0 { - t.Errorf("Returned invalid transmission key: %s, Expected: %s", results[0].TransmissionKey.Text(10), - testInt.Text(10)) - } - if results[0].ReceptionKey.Cmp(testInt) != 0 { - t.Errorf("Returned invalid reception key: %s, Expected: %s", results[0].TransmissionKey.Text(10), - testInt.Text(10)) - } -} +} \ No newline at end of file diff --git a/storage/user/cryptographic.go b/storage/user/cryptographic.go new file mode 100644 index 0000000000000000000000000000000000000000..bc7eb730a3789295bdf661e1e44564e41fbb6f68 --- /dev/null +++ b/storage/user/cryptographic.go @@ -0,0 +1,89 @@ +package user + +import ( + "bytes" + "encoding/gob" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "time" +) + +const currentCryptographicIdentityVersion = 0 +const cryptographicIdentityKey = "cryptographicIdentity" + +type CryptographicIdentity struct { + userID *id.ID + salt []byte + rsaKey *rsa.PrivateKey + isPrecanned bool +} + +func newCryptographicIdentity(uid *id.ID, salt []byte, rsaKey *rsa.PrivateKey, + isPrecanned bool, kv *versioned.KV) (*CryptographicIdentity, error) { + + _, err := kv.Get(cryptographicIdentityKey) + if err == nil { + return nil, errors.New("cannot create cryptographic identity " + + "when one already exists") + } + + ci := &CryptographicIdentity{ + userID: uid, + salt: salt, + rsaKey: rsaKey, + isPrecanned: isPrecanned, + } + + return ci, ci.save(kv) +} + +func loadCryptographicIdentity(kv *versioned.KV) (*CryptographicIdentity, error) { + obj, err := kv.Get(cryptographicIdentityKey) + if err != nil { + return nil, errors.WithMessage(err, "Failed to get user "+ + "cryptographic identity from EKV") + } + + var resultBuffer bytes.Buffer + var result *CryptographicIdentity + resultBuffer.Write(obj.Data) + dec := gob.NewDecoder(&resultBuffer) + err = dec.Decode(result) + + return result, err +} + +func (ci *CryptographicIdentity) save(kv *versioned.KV) error { + var userDataBuffer bytes.Buffer + enc := gob.NewEncoder(&userDataBuffer) + err := enc.Encode(ci) + if err != nil { + return err + } + + obj := &versioned.Object{ + Version: currentCryptographicIdentityVersion, + Timestamp: time.Now(), + Data: userDataBuffer.Bytes(), + } + + return kv.Set(cryptographicIdentityKey, obj) +} + +func (ci *CryptographicIdentity) GetUserID() *id.ID { + return ci.userID.DeepCopy() +} + +func (ci *CryptographicIdentity) GetSalt() []byte { + return ci.salt +} + +func (ci *CryptographicIdentity) GetRSA() *rsa.PrivateKey { + return ci.rsaKey +} + +func (ci *CryptographicIdentity) IsPrecanned() bool { + return ci.isPrecanned +} diff --git a/storage/user/regValidationSig.go b/storage/user/regValidationSig.go new file mode 100644 index 0000000000000000000000000000000000000000..89593f40391c2a2d00f002c2b06071f2d7c9b5e4 --- /dev/null +++ b/storage/user/regValidationSig.go @@ -0,0 +1,56 @@ +package user + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "time" +) + +const currentRegValidationSigVersion = 0 +const regValidationSigKey = "registrationValidationSignature" + +// Returns the Registration Validation Signature stored in RAM. May return +// nil of no signature is stored +func (u *User) GetRegistrationValidationSignature() []byte { + u.rvsMux.RLock() + defer u.rvsMux.RUnlock() + return u.regValidationSig +} + +// Loads the Registration Validation Signature if it exists in the ekv +func (u *User) loadRegistrationValidationSignature() { + u.rvsMux.Lock() + obj, err := u.kv.Get(regValidationSigKey) + if err == nil { + u.regValidationSig = obj.Data + } + u.rvsMux.Unlock() +} + +// Sets the Registration Validation Signature if it is not set and stores it in +// the ekv +func (u *User) SetRegistrationValidationSignature(b []byte) error { + u.rvsMux.Lock() + defer u.rvsMux.Unlock() + + //check if the signature already exists + if u.regValidationSig != nil { + return errors.New("cannot overwrite existing Registration Validation Signature") + } + + obj := &versioned.Object{ + Version: currentRegValidationSigVersion, + Timestamp: time.Now(), + Data: b, + } + + err := u.kv.Set(regValidationSigKey, obj) + if err != nil { + return errors.WithMessage(err, "Failed to store the "+ + "Registration Validation Signature") + } + + u.regValidationSig = b + + return nil +} diff --git a/storage/user/user.go b/storage/user/user.go new file mode 100644 index 0000000000000000000000000000000000000000..34e6f2e96b20a363b94db77700fb0dd26db1da67 --- /dev/null +++ b/storage/user/user.go @@ -0,0 +1,52 @@ +package user + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "sync" +) + +type User struct { + ci *CryptographicIdentity + + regValidationSig []byte + rvsMux sync.RWMutex + + username string + usernameMux sync.RWMutex + + kv *versioned.KV +} + +// builds a new user. +func NewUser(kv *versioned.KV, uid *id.ID, salt []byte, rsaKey *rsa.PrivateKey, + isPrecanned bool) (*User, error) { + + ci, err := newCryptographicIdentity(uid, salt, rsaKey, isPrecanned, kv) + if err != nil { + return nil, errors.WithMessage(err, "Failed to create user "+ + "due to failure to create cryptographic identity") + } + + return &User{ci: ci, kv: kv}, nil +} + +func LoadUser(kv *versioned.KV) (*User, error) { + ci, err := loadCryptographicIdentity(kv) + if err != nil { + return nil, errors.WithMessage(err, "Failed to load user "+ + "due to failure to load cryptographic identity") + } + + u := &User{ci: ci, kv: kv} + u.loadRegistrationValidationSignature() + u.loadUsername() + + return u, nil +} + +func (u *User) GetCryptographicIdentity() *CryptographicIdentity { + return u.ci +} diff --git a/storage/user/username.go b/storage/user/username.go new file mode 100644 index 0000000000000000000000000000000000000000..242f794f64b0674d46da91c3cf63427bdfe9761a --- /dev/null +++ b/storage/user/username.go @@ -0,0 +1,49 @@ +package user + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "time" +) + +const currentUsernameVersion = 0 +const usernameKey = "username" + +func (u *User) loadUsername() { + u.usernameMux.Lock() + obj, err := u.kv.Get(usernameKey) + if err == nil { + u.username = string(obj.Data) + } + u.usernameMux.Unlock() +} + +func (u *User) SetUsername(username string) error { + u.usernameMux.Lock() + defer u.usernameMux.Unlock() + if u.username != "" { + return errors.New("Cannot set username when already set") + } + + obj := &versioned.Object{ + Version: currentUsernameVersion, + Timestamp: time.Now(), + Data: []byte(username), + } + + err := u.kv.Set(usernameKey, obj) + if err != nil { + return errors.WithMessage(err, "Failed to store the username") + } + + return nil +} + +func (u *User) GetUsername() (string, error) { + u.usernameMux.RLock() + defer u.usernameMux.RUnlock() + if u.username == "" { + return "", errors.New("no username set") + } + return u.username, nil +} diff --git a/storage/userdata.go b/storage/userdata.go deleted file mode 100644 index f942a7a60aa1247292dc194533c18f4e095b057c..0000000000000000000000000000000000000000 --- a/storage/userdata.go +++ /dev/null @@ -1,101 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2020 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - -package storage - -import ( - "bytes" - "encoding/gob" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/id" - "time" -) - -// Struct representing a User in the system -type User struct { - User *id.ID - Username string - Precan bool -} - -// DeepCopy performs a deep copy of a user and returns a pointer to the new copy -func (u *User) DeepCopy() *User { - if u == nil { - return nil - } - nu := new(User) - nu.User = u.User - nu.Username = u.Username - nu.Precan = u.Precan - return nu -} - -// This whole struct is serialized/deserialized all together -type UserData struct { - // Fields - ThisUser *User - RSAPrivateKey *rsa.PrivateKey - RSAPublicKey *rsa.PublicKey - CMIXDHPrivateKey *cyclic.Int - CMIXDHPublicKey *cyclic.Int - E2EDHPrivateKey *cyclic.Int - E2EDHPublicKey *cyclic.Int - CmixGrp *cyclic.Group - E2EGrp *cyclic.Group - Salt []byte -} - -const currentUserDataVersion = 0 - -func makeUserDataKey() string { - return "UserData" -} - -func (s *Session) GetUserData() (*UserData, error) { - if s.userData != nil { - // We already got this and don't need to get it again - return s.userData, nil - } - obj, err := s.Get(makeUserDataKey()) - if err != nil { - return nil, err - } - - var resultBuffer bytes.Buffer - var result UserData - resultBuffer.Write(obj.Data) - dec := gob.NewDecoder(&resultBuffer) - err = dec.Decode(&result) - if err != nil { - return nil, err - } - s.userData = &result - return &result, nil -} - -// Make changes to the user data after getting it, then -// commit those changes to the ekv store using this -// I haven't added a mutex to the user data because it's only -// created once. If you add modification to the user data structure, -// please -func (s *Session) CommitUserData(data *UserData) error { - // Serialize the data - var userDataBuffer bytes.Buffer - enc := gob.NewEncoder(&userDataBuffer) - err := enc.Encode(data) - if err != nil { - return err - } - - obj := &versioned.Object{ - Version: currentUserDataVersion, - Timestamp: time.Now(), - Data: userDataBuffer.Bytes(), - } - return s.Set(makeUserDataKey(), obj) -} diff --git a/storage/userdata_test.go b/storage/userdata_test.go deleted file mode 100644 index 34741c6ab087eb89ac901ab3e46a74d549034a40..0000000000000000000000000000000000000000 --- a/storage/userdata_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package storage - -import ( - "bytes" - "gitlab.com/elixxir/client/storage/versioned" - "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/large" - "gitlab.com/elixxir/ekv" - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/id" - "math/rand" - "reflect" - "testing" -) - -// Test committing/retrieving userdata struct -func TestSession_CommitUserData(t *testing.T) { - rsaPrivateKey, err := rsa.GenerateKey(rand.New(rand.NewSource(0)), 64) - if err != nil { - t.Fatal(err) - } - // These don't have to represent actual data because they're just stored and retrieved - cmixGrp := cyclic.NewGroup(large.NewInt(53), large.NewInt(2)) - e2eGrp := cyclic.NewGroup(large.NewInt(53), large.NewInt(2)) - expectedData := &UserData{ - ThisUser: &User{ - User: id.NewIdFromUInt(5, id.User, t), - Username: "ted", - Precan: true, - }, - RSAPrivateKey: rsaPrivateKey, - RSAPublicKey: rsaPrivateKey.GetPublic(), - CMIXDHPrivateKey: cmixGrp.NewInt(3), - CMIXDHPublicKey: cmixGrp.NewInt(4), - E2EDHPrivateKey: e2eGrp.NewInt(5), - E2EDHPublicKey: e2eGrp.NewInt(6), - CmixGrp: cmixGrp, - E2EGrp: e2eGrp, - Salt: []byte("potassium permanganate"), - } - - // Create a session backed by memory - store := make(ekv.Memstore) - vkv := versioned.NewKV(store) - session := Session{kv: vkv} - err = session.CommitUserData(expectedData) - if err != nil { - t.Fatal(err) - } - retrievedData, err := session.GetUserData() - if err != nil { - t.Fatal(err) - } - - // Field by field comparison - if !retrievedData.ThisUser.User.Cmp(expectedData.ThisUser.User) { - t.Error("User IDs didn't match") - } - if retrievedData.ThisUser.Precan != expectedData.ThisUser.Precan { - t.Error("User precan didn't match") - } - if retrievedData.ThisUser.Username != expectedData.ThisUser.Username { - t.Error("User names didn't match") - } - if retrievedData.CMIXDHPublicKey.Cmp(expectedData.CMIXDHPublicKey) != 0 { - t.Error("cmix DH public key didn't match") - } - if retrievedData.CMIXDHPrivateKey.Cmp(expectedData.CMIXDHPrivateKey) != 0 { - t.Error("cmix DH private key didn't match") - } - if retrievedData.E2EDHPrivateKey.Cmp(expectedData.E2EDHPrivateKey) != 0 { - t.Error("e2e DH private key didn't match") - } - if retrievedData.E2EDHPublicKey.Cmp(expectedData.E2EDHPublicKey) != 0 { - t.Error("e2e DH public key didn't match") - } - if !reflect.DeepEqual(retrievedData.CmixGrp, expectedData.CmixGrp) { - t.Error("cmix groups didn't match") - } - if !reflect.DeepEqual(retrievedData.E2EGrp, expectedData.E2EGrp) { - t.Error("e2e groups didn't match") - } - if retrievedData.RSAPrivateKey.D.Cmp(expectedData.RSAPrivateKey.D) != 0 { - t.Error("rsa D doesn't match") - } - if !bytes.Equal(retrievedData.Salt, expectedData.Salt) { - t.Error("salts don't match") - } -} diff --git a/storage/utility/dh.go b/storage/utility/dh.go new file mode 100644 index 0000000000000000000000000000000000000000..0a892020c29bc991e72081cbf2ea9d68a06a8d28 --- /dev/null +++ b/storage/utility/dh.go @@ -0,0 +1,37 @@ +package utility + +import ( + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/crypto/cyclic" + "time" +) + +const currentCyclicVersion = 0 + +func StoreCyclicKey(kv *versioned.KV, cy *cyclic.Int, key string) error { + now := time.Now() + + data, err := cy.GobEncode() + if err != nil { + return err + } + + obj := versioned.Object{ + Version: currentCyclicVersion, + Timestamp: now, + Data: data, + } + + return kv.Set(key, &obj) +} + +func LoadCyclicKey(kv *versioned.KV, key string) (*cyclic.Int, error) { + vo, err := kv.Get(key) + if err != nil { + return nil, err + } + + cy := &cyclic.Int{} + + return cy, cy.GobDecode(vo.Data) +} diff --git a/storage/utility/group.go b/storage/utility/group.go new file mode 100644 index 0000000000000000000000000000000000000000..60cce18318f8114d42393c9ee4a2e65a8128bf00 --- /dev/null +++ b/storage/utility/group.go @@ -0,0 +1,37 @@ +package utility + +import ( + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/crypto/cyclic" + "time" +) + +const currentGroupVersion = 0 + +func StoreGroup(kv *versioned.KV, grp *cyclic.Group, key string) error { + now := time.Now() + + data, err := grp.GobEncode() + if err != nil { + return err + } + + obj := versioned.Object{ + Version: currentGroupVersion, + Timestamp: now, + Data: data, + } + + return kv.Set(key, &obj) +} + +func LoadGroup(kv *versioned.KV, key string) (*cyclic.Group, error) { + vo, err := kv.Get(key) + if err != nil { + return nil, err + } + + grp := &cyclic.Group{} + + return grp, grp.GobDecode(vo.Data) +} diff --git a/userRegistry/user.go b/userRegistry/user.go index 88185809eae2a8eb455afeb26c85d73636247fbc..25c5330552ca64ad28a0256833541d9755ddc0bd 100644 --- a/userRegistry/user.go +++ b/userRegistry/user.go @@ -10,7 +10,7 @@ import ( "crypto/sha256" "encoding/binary" "gitlab.com/elixxir/client/globals" - "gitlab.com/elixxir/client/storage" + user2 "gitlab.com/elixxir/client/storage/user" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/xx_network/primitives/id" @@ -34,10 +34,10 @@ func InitUserRegistry(grp *cyclic.Group) { // Interface for User Registry operations type Registry interface { - NewUser(id *id.ID, nickname string) *storage.User + NewUser(id *id.ID, nickname string) *user2.User DeleteUser(id *id.ID) - GetUser(id *id.ID) (user *storage.User, ok bool) - UpsertUser(user *storage.User) + GetUser(id *id.ID) (user *user2.User, ok bool) + UpsertUser(user *user2.User) CountUsers() int LookupUser(hid string) (uid *id.ID, ok bool) LookupKeys(uid *id.ID) (*user.NodeKeys, bool) @@ -45,7 +45,7 @@ type Registry interface { type UserMap struct { // Map acting as the User Registry containing User -> ID mapping - userCollection map[id.ID]*storage.User + userCollection map[id.ID]*user2.User // Increments sequentially for User.ID values idCounter uint64 // Temporary map acting as a lookup table for demo user registration codes @@ -60,7 +60,7 @@ func newRegistry(grp *cyclic.Group) Registry { if len(DemoChannelNames) > 10 || len(DemoUserNicks) > 30 { globals.Log.ERROR.Print("Not enough demo users have been hardcoded.") } - userUserIdMap := make(map[id.ID]*storage.User) + userUserIdMap := make(map[id.ID]*user2.User) userRegCodeMap := make(map[string]*id.ID) nk := make(map[id.ID]*user.NodeKeys) @@ -70,7 +70,7 @@ func newRegistry(grp *cyclic.Group) Registry { currentID := new(id.ID) binary.BigEndian.PutUint64(currentID[:], i) currentID.SetType(id.User) - newUsr := new(storage.User) + newUsr := new(user2.User) nodeKey := new(user.NodeKeys) // Generate user parameters @@ -119,13 +119,13 @@ func newRegistry(grp *cyclic.Group) Registry { } // NewUser creates a new User object with default fields and given address. -func (m *UserMap) NewUser(id *id.ID, username string) *storage.User { - return &storage.User{User: id, Username: username} +func (m *UserMap) NewUser(id *id.ID, username string) *user2.User { + return &user2.User{User: id, Username: username} } // GetUser returns a user with the given ID from userCollection // and a boolean for whether the user exists -func (m *UserMap) GetUser(id *id.ID) (user *storage.User, ok bool) { +func (m *UserMap) GetUser(id *id.ID) (user *user2.User, ok bool) { user, ok = m.userCollection[*id] user = user.DeepCopy() return @@ -139,7 +139,7 @@ func (m *UserMap) DeleteUser(id *id.ID) { // UpsertUser inserts given user into userCollection or update the user if it // already exists (Upsert operation). -func (m *UserMap) UpsertUser(user *storage.User) { +func (m *UserMap) UpsertUser(user *user2.User) { m.userCollection[*user.User] = user }