diff --git a/api/client.go b/api/client.go index 4244899234c5a1ab4157ae7f4741382086f99365..9d4cb7550435ed9e669055c59d1d86861f581c0c 100644 --- a/api/client.go +++ b/api/client.go @@ -11,6 +11,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/user" "gitlab.com/elixxir/client/keyExchange" "gitlab.com/elixxir/client/network" "gitlab.com/elixxir/client/permissioning" @@ -70,9 +71,8 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str // Create Storage passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, - protoUser.UID, protoUser.Salt, protoUser.RSAKey, protoUser.IsPrecanned, - protoUser.CMixKey, protoUser.E2EKey, cmixGrp, e2eGrp, rngStreamGen) + storageSess, err := storage.New(storageDir, passwordStr, protoUser, + cmixGrp, e2eGrp, rngStreamGen) if err != nil { return nil, err } @@ -108,9 +108,8 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [ // Create Storage passwordStr := string(password) - storageSess, err := storage.New(storageDir, passwordStr, - protoUser.UID, protoUser.Salt, protoUser.RSAKey, protoUser.IsPrecanned, - protoUser.CMixKey, protoUser.E2EKey, cmixGrp, e2eGrp, rngStreamGen) + storageSess, err := storage.New(storageDir, passwordStr, protoUser, + cmixGrp, e2eGrp, rngStreamGen) if err != nil { return nil, err } @@ -202,6 +201,8 @@ func loadClient(session *storage.Session, rngStreamGen *fastRNG.StreamGenerator) // state and stopping those threads. // Call this when returning from sleep and close when going back to // sleep. +// These threads may become a significant drain on battery when offline, ensure +// they are stopped if there is no internet access // Threads Started: // - Network Follower (/network/follow.go) // tracks the network events and hands them off to workers for handling @@ -270,27 +271,43 @@ func (c *Client) StopNetworkFollower(timeout time.Duration) error { return nil } -//gets the state of the network follower +// Gets the state of the network follower. Returns: +// Stopped - 0 +// Starting - 1000 +// Running - 2000 +// Stopping - 3000 func (c *Client) NetworkFollowerStatus() Status { + jww.INFO.Printf("NetworkFollowerStatus()") return c.status.get() } -// Returns the switchboard for Registration -func (c *Client) GetSwitchboard() interfaces.Switchboard { - return c.switchboard -} - // Returns the health tracker for registration and polling func (c *Client) GetHealth() interfaces.HealthTracker { + jww.INFO.Printf("GetHealth()") return c.network.GetHealthTracker() } // RegisterRoundEventsCb registers a callback for round // events. func (c *Client) GetRoundEvents() interfaces.RoundEvents { + jww.INFO.Printf("GetRoundEvents()") return c.network.GetInstance().GetRoundEvents() } + +// Returns the switchboard for Registration +func (c *Client) GetSwitchboard() interfaces.Switchboard { + jww.INFO.Printf("GetSwitchboard()") + return c.switchboard +} + +// GetUser returns the current user Identity for this client. This +// can be serialized into a byte stream for out-of-band sharing. +func (c *Client) GetUser() user.User { + jww.INFO.Printf("GetUser()") + return c.storage.GetUser() +} + // ----- Utility Functions ----- // parseNDF parses the initial ndf string for the client. do not check the // signature, it is deprecated. diff --git a/api/contact.go b/api/contact.go deleted file mode 100644 index a105270c0e81f0edbce2c603c95138cd428b984e..0000000000000000000000000000000000000000 --- a/api/contact.go +++ /dev/null @@ -1,35 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2019 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - -package api - -import jww "github.com/spf13/jwalterweatherman" - -import ( - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/id" -) - -// GetUser returns the current user Identity for this client. This -// can be serialized into a byte stream for out-of-band sharing. -func (c *Client) GetUser() (Contact, error) { - jww.INFO.Printf("GetUser()") - return Contact{}, nil -} - -// MakeContact creates a contact from a byte stream (i.e., unmarshal's a -// Contact object), allowing out-of-band import of identities. -func (c *Client) MakeContact(contactBytes []byte) (Contact, error) { - jww.INFO.Printf("MakeContact(%s)", contactBytes) - return Contact{}, nil -} - -// GetContact returns a Contact object for the given user id, or -// an error -func (c *Client) GetContact(uid []byte) (Contact, error) { - jww.INFO.Printf("GetContact(%s)", uid) - return Contact{}, nil -} diff --git a/api/user.go b/api/user.go index 7151a88437b2ad1006459733bb79a65c31bfca81..da54da2aaafefa528ae01cdb0ae80579897a906d 100644 --- a/api/user.go +++ b/api/user.go @@ -1,37 +1,23 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2019 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// - package api import ( "encoding/binary" - jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/interfaces/user" "gitlab.com/elixxir/crypto/csprng" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/xx" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" + jww "github.com/spf13/jwalterweatherman" ) -type user struct { - UID *id.ID - Salt []byte - RSAKey *rsa.PrivateKey - CMixKey *cyclic.Int - E2EKey *cyclic.Int - IsPrecanned bool -} - const ( // SaltSize size of user salts SaltSize = 32 ) // createNewUser generates an identity for cMix -func createNewUser(rng csprng.Source, cmix, e2e *cyclic.Group) user { +func createNewUser(rng csprng.Source, cmix, e2e *cyclic.Group) user.User { // RSA Keygen (4096 bit defaults) rsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen) if err != nil { @@ -70,19 +56,19 @@ func createNewUser(rng csprng.Source, cmix, e2e *cyclic.Group) user { jww.FATAL.Panicf(err.Error()) } - return user{ - UID: userID, - Salt: salt, - RSAKey: rsaKey, - CMixKey: cmix.NewIntFromBytes(cMixKeyBytes), - E2EKey: e2e.NewIntFromBytes(e2eKeyBytes), - IsPrecanned: false, + return user.User{ + ID: userID.DeepCopy(), + Salt: salt, + RSA: rsaKey, + Precanned: false, + CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes), + E2eDhPrivateKey: cmix.NewIntFromBytes(e2eKeyBytes), } } // TODO: Add precanned user code structures here. // creates a precanned user -func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.Group) user { +func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.Group) user.User { // DH Keygen // FIXME: Why 256 bits? -- this is spec but not explained, it has // to do with optimizing operations on one side and still preserves @@ -99,12 +85,10 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic. binary.BigEndian.PutUint64(userID[:], uint64(precannedID)) userID.SetType(id.User) - return user{ - UID: &userID, - Salt: salt, - RSAKey: &rsa.PrivateKey{}, - CMixKey: cmix.NewInt(1), - E2EKey: e2e.NewIntFromBytes(e2eKeyBytes), - IsPrecanned: true, + return user.User{ + ID: userID.DeepCopy(), + Salt: salt, + Precanned: false, + E2eDhPrivateKey: cmix.NewIntFromBytes(e2eKeyBytes), } -} \ No newline at end of file +} diff --git a/bindings/api.go b/bindings/client.go similarity index 87% rename from bindings/api.go rename to bindings/client.go index 3195423f109913a399747e46fc2ba985dc4fc461..679c4acd58f7cdb117ba120a6fd3510ee13693d7 100644 --- a/bindings/api.go +++ b/bindings/client.go @@ -8,15 +8,16 @@ package bindings import ( "gitlab.com/elixxir/client/api" + "time" ) // BindingsClient wraps the api.Client, implementing additional functions // to support the gomobile Client interface type BindingsClient struct { - api.Client + api api.Client } -// NewClient connects and registers to the network using a json encoded +/*// NewClient connects and registers to the network using a json encoded // network information string and then creates a new client at the specified // storageDir using the specified password. This function will fail // when: @@ -58,7 +59,48 @@ func LoadClient(storageDir string, password []byte) (Client, error) { } bindingsClient := &BindingsClient{*client} return bindingsClient, nil +}*/ + +func (b *BindingsClient) StartNetworkFollower() error { + return b.api.StartNetworkFollower() +} + +func (b *BindingsClient) StopNetworkFollower(timeoutMS int) error { + timeout := time.Duration(timeoutMS) * time.Millisecond + return b.api.StopNetworkFollower(timeout) +} + +func (b *BindingsClient) NetworkFollowerStatus() int { + return int(b.api.NetworkFollowerStatus()) +} + +func (b *BindingsClient) IsNetworkHealthy() bool { + return b.api.GetHealth().IsHealthy() } + +func (b *BindingsClient) RegisterNetworkHealthCB(cb func(bool)) { + b.api.GetHealth().AddFunc(cb) +} + + + + + + + + + + + + + + + + + + + + // RegisterListener records and installs a listener for messages // matching specific uid, msgType, and/or username diff --git a/bindings/interfaces.go b/bindings/interfaces.go index a4fd23c45cff5af5f4d3597afd8f233828e2faeb..a9d19c181381e767652bb20605152b96d934cdd7 100644 --- a/bindings/interfaces.go +++ b/bindings/interfaces.go @@ -7,7 +7,7 @@ package bindings import ( - "gitlab.com/elixxir/client/api" + "gitlab.com/elixxir/client/interfaces" "gitlab.com/xx_network/primitives/id" ) @@ -15,7 +15,41 @@ import ( // functionality defined here. A Client handles all network connectivity, key // generation, and storage for a given cryptographic identity on the cmix // network. +// These threads may become a significant drain on battery when offline, ensure +// they are stopped if there is no internet access type Client interface { + // ----- Network ----- + // StartNetworkFollower kicks off the tracking of the network. It starts + // long running network client threads and returns an object for checking + // state and stopping those threads. + // Call this when returning from sleep and close when going back to + // sleep. + StartNetworkFollower() error + + // StopNetworkFollower stops the network follower if it is running. + // It returns errors if the Follower is in the wrong status to stop or if it + // fails to stop it. + // if the network follower is running and this fails, the client object will + // most likely be in an unrecoverable state and need to be trashed. + StopNetworkFollower(timeoutMS int) error + + // NetworkFollowerStatus gets the state of the network follower. + // Returns: + // Stopped - 0 + // Starting - 1000 + // Running - 2000 + // Stopping - 3000 + NetworkFollowerStatus() int + + // Returns true if the following of the network is in a state where messages + // can be sent, false otherwise + IsNetworkHealthy() bool + + // Registers a callback which gets triggered every time network health + // changes + RegisterNetworkHealthCB(func(bool)) + + RegisterRoundEventCallback(rid int, hdlr RoundEventHandler, ) // ----- Reception ----- @@ -85,13 +119,7 @@ type Client interface { // GetUser returns the current user Identity for this client. This // can be serialized into a byte stream for out-of-band sharing. - GetUser() (api.Contact, error) - // MakeContact creates a contact from a byte stream (i.e., unmarshal's a - // Contact object), allowing out-of-band import of identities. - MakeContact(contactBytes []byte) (api.Contact, error) - // GetContact returns a Contact object for the given user id, or - // an error - GetContact(uid []byte) (api.Contact, error) + GetUser() (interfaces.Contact, error) // ----- User Discovery ----- @@ -110,22 +138,16 @@ type Client interface { // so this user can send messages to the desired recipient Contact. // To receive confirmation from the remote user, clients must // register a listener to do that. - CreateAuthenticatedChannel(recipient api.Contact, payload []byte) error + CreateAuthenticatedChannel(recipient interfaces.Contact, payload []byte) error // RegierAuthEventsHandler registers a callback interface for channel // authentication events. RegisterAuthEventsHandler(hdlr AuthEventHandler) // ----- Network ----- - // StartNetworkRunner kicks off the longrunning network client threads - // and returns an object for checking state and stopping those threads. - // Call this when returning from sleep and close when going back to - // sleep. - StartNetworkFollower() error - // RegisterRoundEventsHandler registers a callback interface for round // events. - RegisterRoundEventsHandler(hdlr RoundEventHandler) + RegisterRoundEventsHandler() } // ContactList contains a list of contacts @@ -133,7 +155,7 @@ type ContactList interface { // GetLen returns the number of contacts in the list GetLen() int // GetContact returns the contact at index i - GetContact(i int) api.Contact + GetContact(i int) interfaces.Contact } // ----- Callback interfaces ----- @@ -154,13 +176,13 @@ type AuthEventHandler interface { // the client has called CreateAuthenticatedChannel for // the provided contact. Payload is typically empty but // may include a small introductory message. - HandleConfirmation(contact api.Contact, payload []byte) + HandleConfirmation(contact interfaces.Contact, payload []byte) // HandleRequest handles AuthEvents received before // the client has called CreateAuthenticatedChannel for // the provided contact. It should prompt the user to accept // the channel creation "request" and, if approved, // call CreateAuthenticatedChannel for this Contact. - HandleRequest(contact api.Contact, payload []byte) + HandleRequest(contact interfaces.Contact, payload []byte) } // RoundList contains a list of contacts @@ -171,18 +193,10 @@ type RoundList interface { GetRoundID(i int) int } -// RoundEvent contains event information for a given round. -// TODO: This is a half-baked interface and will be filled out later. -type RoundEvent interface { - // GetID returns the round ID for this round. - GetID() int - // GetStatus returns the status of this round. - GetStatus() int -} // RoundEventHandler handles round events happening on the cMix network. type RoundEventHandler interface { - HandleEvent(re RoundEvent) + HandleEvent(id int, state byte) } // UserDiscoveryHandler handles search results against the user discovery agent. @@ -213,4 +227,4 @@ type Message interface { GetTimestamp() int64 // Returns the message's timestamp in ns since unix epoc GetTimestampNano() int64 -} +} \ No newline at end of file diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go index 38f5d04f3ca8453436a6375da2cabcfd55bd8ca3..2655b49c74b91e4a7db4ca8aab9b7089185e061d 100644 --- a/interfaces/contact/contact.go +++ b/interfaces/contact/contact.go @@ -13,7 +13,7 @@ import ( // in go, the structure is meant to be edited directly, the functions are for // bindings compatibility type Contact struct { - ID id.ID + ID *id.ID DhPubKey *cyclic.Int Facts []fact.Fact } diff --git a/interfaces/user.go b/interfaces/user.go new file mode 100644 index 0000000000000000000000000000000000000000..4819783a50f87b5374d1da89af8e980870249672 --- /dev/null +++ b/interfaces/user.go @@ -0,0 +1,14 @@ +package interfaces + +type User interface { + GetID() []byte + GetSalt() []byte + GetRSAPrivateKeyPem() []byte + GetRSAPublicKeyPem() []byte + IsPrecanned() bool + GetCmixDhPrivateKey() []byte + GetCmixDhPublicKey() []byte + GetE2EDhPrivateKey() []byte + GetE2EDhPublicKey() []byte + GetContact() Contact +} diff --git a/interfaces/user/user.go b/interfaces/user/user.go new file mode 100644 index 0000000000000000000000000000000000000000..d84f406b5ca3d7a70fd1b2ac0873728958cc5fef --- /dev/null +++ b/interfaces/user/user.go @@ -0,0 +1,69 @@ +package user + +import ( + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" +) + +type User struct { + //General Identity + ID *id.ID + Salt []byte + RSA *rsa.PrivateKey + Precanned bool + + //cmix Identity + CmixDhPrivateKey *cyclic.Int + CmixDhPublicKey *cyclic.Int + + //e2e Identity + E2eDhPrivateKey *cyclic.Int + E2eDhPublicKey *cyclic.Int +} + +func (u User) GetID() []byte { + return u.ID.Marshal() +} + +func (u User) GetSalt() []byte { + return u.Salt +} + +func (u User) GetRSAPrivateKeyPem() []byte { + return rsa.CreatePrivateKeyPem(u.RSA) +} + +func (u User) GetRSAPublicKeyPem() []byte { + return rsa.CreatePublicKeyPem(u.RSA.GetPublic()) +} + +func (u User) IsPrecanned() bool { + return u.Precanned +} + +func (u User) GetCmixDhPrivateKey() []byte { + return u.CmixDhPrivateKey.Bytes() +} + +func (u User) GetCmixDhPublicKey() []byte { + return u.CmixDhPublicKey.Bytes() +} + +func (u User) GetE2EDhPrivateKey() []byte { + return u.E2eDhPrivateKey.Bytes() +} + +func (u User) GetE2EDhPublicKey() []byte { + return u.E2eDhPublicKey.Bytes() +} + +func (u User) GetContact() interfaces.Contact { + return contact.Contact{ + ID: u.ID.DeepCopy(), + DhPubKey: u.E2eDhPublicKey, + Facts: nil, + } +} diff --git a/stoppable/bindings.go b/stoppable/bindings.go new file mode 100644 index 0000000000000000000000000000000000000000..73e879f557a5b91a3cbc96d88c7c28f0269d6131 --- /dev/null +++ b/stoppable/bindings.go @@ -0,0 +1,30 @@ +package stoppable + +import "time" + +type Bindings interface { + Close(timeoutMS int) error + IsRunning() bool + Name() string +} + +func WrapForBindings(s Stoppable) Bindings { + return &bindingsStoppable{s: s} +} + +type bindingsStoppable struct { + s Stoppable +} + +func (bs *bindingsStoppable) Close(timeoutMS int) error { + timeout := time.Duration(timeoutMS) * time.Millisecond + return bs.s.Close(timeout) +} + +func (bs *bindingsStoppable) IsRunning() bool { + return bs.s.IsRunning() +} + +func (bs *bindingsStoppable) Name() string { + return bs.s.Name() +} diff --git a/storage/session.go b/storage/session.go index 7367c1c00a0ef619cc9f11d010694f25d1c6a2df..246740e83e717c9bfa165e27da894d0ee47c6919 100644 --- a/storage/session.go +++ b/storage/session.go @@ -27,6 +27,7 @@ import ( "gitlab.com/xx_network/primitives/ndf" "sync" "testing" + userInterface "gitlab.com/elixxir/client/interfaces/user" ) // Number of rounds to store in the CheckedRound buffer @@ -69,8 +70,8 @@ func initStore(baseDir, password string) (*Session, error) { } // Creates new UserData in the session -func New(baseDir, password string, uid *id.ID, salt []byte, rsaKey *rsa.PrivateKey, - isPrecanned bool, cmixDHPrivKey, e2eDHPrivKey *cyclic.Int, cmixGrp, + +func New(baseDir, password string, u userInterface.User, cmixGrp, e2eGrp *cyclic.Group, rng *fastRNG.StreamGenerator) (*Session, error) { s, err := initStore(baseDir, password) @@ -84,17 +85,17 @@ func New(baseDir, password string, uid *id.ID, salt []byte, rsaKey *rsa.PrivateK "Create new session") } - s.user, err = user.NewUser(s.kv, uid, salt, rsaKey, isPrecanned) + s.user, err = user.NewUser(s.kv, u.ID, u.Salt, u.RSA, u.Precanned) if err != nil { return nil, errors.WithMessage(err, "Failed to create session") } - s.cmix, err = cmix.NewStore(cmixGrp, s.kv, cmixDHPrivKey) + s.cmix, err = cmix.NewStore(cmixGrp, s.kv, u.CmixDhPrivateKey) if err != nil { return nil, errors.WithMessage(err, "Failed to create session") } - s.e2e, err = e2e.NewStore(e2eGrp, s.kv, e2eDHPrivKey, rng) + s.e2e, err = e2e.NewStore(e2eGrp, s.kv, u.E2eDhPrivateKey, rng) if err != nil { return nil, errors.WithMessage(err, "Failed to create session") } diff --git a/storage/user.go b/storage/user.go new file mode 100644 index 0000000000000000000000000000000000000000..a4fb8742b9bacc16a254168ce643bff5473010d9 --- /dev/null +++ b/storage/user.go @@ -0,0 +1,26 @@ +package storage + +import "gitlab.com/elixxir/client/interfaces/user" + +func (s *Session) GetUser() user.User { + s.mux.RLock() + defer s.mux.RUnlock() + ci := s.user.GetCryptographicIdentity() + return user.User{ + ID: ci.GetUserID().DeepCopy(), + Salt: copySlice(ci.GetSalt()), + RSA: ci.GetRSA(), + Precanned: ci.IsPrecanned(), + CmixDhPrivateKey: s.cmix.GetDHPrivateKey().DeepCopy(), + CmixDhPublicKey: s.cmix.GetDHPublicKey().DeepCopy(), + E2eDhPrivateKey: s.e2e.GetDHPrivateKey().DeepCopy(), + E2eDhPublicKey: s.e2e.GetDHPublicKey().DeepCopy(), + } + +} + +func copySlice(s []byte) []byte { + n := make([]byte, len(s)) + copy(n, s) + return n +}