diff --git a/api/client.go b/api/client.go index 106a10355007a0ce6e4609df4475cc2a74434534..faa5d57afe5089aab6e9075cead6128e21e70717 100644 --- a/api/client.go +++ b/api/client.go @@ -9,7 +9,6 @@ package api import ( "bufio" "crypto" - "crypto/rand" gorsa "crypto/rsa" "crypto/sha256" "encoding/base64" @@ -25,11 +24,8 @@ import ( "gitlab.com/elixxir/client/rekey" "gitlab.com/elixxir/client/user" "gitlab.com/elixxir/comms/mixmessages" - "gitlab.com/elixxir/crypto/csprng" "gitlab.com/elixxir/crypto/cyclic" - "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/crypto/large" - "gitlab.com/elixxir/crypto/registration" "gitlab.com/elixxir/crypto/signature/rsa" "gitlab.com/elixxir/crypto/tls" "gitlab.com/elixxir/primitives/circuit" @@ -38,7 +34,6 @@ import ( "gitlab.com/elixxir/primitives/switchboard" goio "io" "strings" - "sync" "time" ) @@ -58,145 +53,6 @@ var noNDFErr = errors.New("Failed to get ndf from permissioning: rpc error: code //used to report the state of registration type OperationProgressCallback func(int) -// Populates a text message and returns its wire representation -// TODO support multi-type messages or telling if a message is too long? -func FormatTextMessage(message string) []byte { - textMessage := cmixproto.TextMessage{ - Color: -1, - Message: message, - Time: time.Now().Unix(), - } - - wireRepresentation, _ := proto.Marshal(&textMessage) - return wireRepresentation -} - -//GetUpdatedNDF: Connects to the permissioning server to get the updated NDF from it -func (cl *Client) getUpdatedNDF() (*ndf.NetworkDefinition, error) { // again, uses internal ndf. stay here, return results instead - - //Hash the client's ndf for comparison with registration's ndf - hash := sha256.New() - ndfBytes := cl.ndf.Serialize() - hash.Write(ndfBytes) - ndfHash := hash.Sum(nil) - - //Put the hash in a message - msg := &mixmessages.NDFHash{Hash: ndfHash} - - host, ok := cl.commManager.Comms.GetHost(PermissioningAddrID) - if !ok { - return nil, errors.New("Failed to find permissioning host") - } - - //Send the hash to registration - response, err := cl.commManager.Comms.SendGetUpdatedNDF(host, msg) - if err != nil { - errMsg := fmt.Sprintf("Failed to get ndf from permissioning: %v", err) - return nil, errors.New(errMsg) - } - - //If there was no error and the response is nil, client's ndf is up-to-date - if response == nil { - globals.Log.DEBUG.Printf("Client NDF up-to-date") - return nil, nil - } - - //FixMe: use verify instead? Probs need to add a signature to ndf, like in registration's getupdate? - - globals.Log.INFO.Printf("Remote NDF: %s", string(response.Ndf)) - - //Otherwise pull the ndf out of the response - updatedNdf, _, err := ndf.DecodeNDF(string(response.Ndf)) - if err != nil { - //If there was an error decoding ndf - errMsg := fmt.Sprintf("Failed to decode response to ndf: %v", err) - return nil, errors.New(errMsg) - } - return updatedNdf, nil -} - -// VerifyNDF verifies the signature of the network definition file (NDF) and -// returns the structure. Panics when the NDF string cannot be decoded and when -// the signature cannot be verified. If the NDF public key is empty, then the -// signature verification is skipped and warning is printed. -func VerifyNDF(ndfString, ndfPub string) *ndf.NetworkDefinition { - // If there is no public key, then skip verification and print warning - if ndfPub == "" { - globals.Log.WARN.Printf("Running without signed network " + - "definition file") - } else { - ndfReader := bufio.NewReader(strings.NewReader(ndfString)) - ndfData, err := ndfReader.ReadBytes('\n') - ndfData = ndfData[:len(ndfData)-1] - if err != nil { - globals.Log.FATAL.Panicf("Could not read NDF: %v", err) - } - ndfSignature, err := ndfReader.ReadBytes('\n') - if err != nil { - globals.Log.FATAL.Panicf("Could not read NDF Sig: %v", - err) - } - ndfSignature, err = base64.StdEncoding.DecodeString( - string(ndfSignature[:len(ndfSignature)-1])) - if err != nil { - globals.Log.FATAL.Panicf("Could not read NDF Sig: %v", - err) - } - // Load the TLS cert given to us, and from that get the RSA public key - cert, err := tls.LoadCertificate(ndfPub) - if err != nil { - globals.Log.FATAL.Panicf("Could not load public key: %v", err) - } - pubKey := &rsa.PublicKey{PublicKey: *cert.PublicKey.(*gorsa.PublicKey)} - - // Hash NDF JSON - rsaHash := sha256.New() - rsaHash.Write(ndfData) - - globals.Log.INFO.Printf("%s \n::\n %s", - ndfSignature, ndfData) - - // Verify signature - err = rsa.Verify( - pubKey, crypto.SHA256, rsaHash.Sum(nil), ndfSignature, nil) - - if err != nil { - globals.Log.FATAL.Panicf("Could not verify NDF: %v", err) - } - } - - ndfJSON, _, err := ndf.DecodeNDF(ndfString) - if err != nil { - globals.Log.FATAL.Panicf("Could not decode NDF: %v", err) - } - return ndfJSON -} - -//request calls getUpdatedNDF for a new NDF repeatedly until it gets an NDF -func requestNdf(cl *Client) error { - // Continuously polls for a new ndf after sleeping until response if gotten - globals.Log.INFO.Printf("Polling for a new NDF") - newNDf, err := cl.getUpdatedNDF() - - if err != nil { - //lets the client continue when permissioning does not provide NDFs - if err.Error() == noNDFErr.Error() { - globals.Log.WARN.Println("Continuing without an updated NDF") - return nil - } - - errMsg := fmt.Sprintf("Failed to get updated ndf: %v", err) - globals.Log.ERROR.Printf(errMsg) - return errors.New(errMsg) - } - - if newNDf != nil { - cl.ndf = newNDf - } - - return nil -} - // Creates a new Client using the storage mechanism provided. // If none is provided, a default storage using OS file access // is created @@ -244,383 +100,171 @@ func NewClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinit return cl, nil } -func (cl *Client) GetRegistrationVersion() string { // on client - return cl.registrationVersion -} - -//GetNDF returns the clients ndf -func (cl *Client) GetNDF() *ndf.NetworkDefinition { - return cl.ndf -} - -func (cl *Client) SetOperationProgressCallback(rpc OperationProgressCallback) { - cl.opStatus = func(i int) { go rpc(i) } -} - -const SaltSize = 256 - -// RegisterWithPermissioning registers user with permissioning and returns the -// User ID. Returns an error if registration fails. -func (cl *Client) RegisterWithPermissioning(preCan bool, registrationCode, nick, email, - password string, privateKeyRSA *rsa.PrivateKey) (*id.User, error) { +// LoadSession loads the session object for the UID +func (cl *Client) Login(password string) (string, error) { + var session user.Session var err error - var u *user.User - var UID *id.User - - cl.opStatus(globals.REG_KEYGEN) - - largeIntBits := 16 - - cmixGrp := cyclic.NewGroup( - large.NewIntFromString(cl.ndf.CMIX.Prime, largeIntBits), - large.NewIntFromString(cl.ndf.CMIX.Generator, largeIntBits)) - - e2eGrp := cyclic.NewGroup( - large.NewIntFromString(cl.ndf.E2E.Prime, largeIntBits), - large.NewIntFromString(cl.ndf.E2E.Generator, largeIntBits)) - - // Make CMIX keys array - nk := make(map[id.Node]user.NodeKeys) - - // GENERATE CLIENT RSA KEYS - if privateKeyRSA == nil { - privateKeyRSA, err = rsa.GenerateKey(rand.Reader, rsa.DefaultRSABitLen) - if err != nil { - return nil, err - } - } - - publicKeyRSA := privateKeyRSA.GetPublic() - - cmixPrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG()) + done := make(chan struct{}) - if err != nil { - return nil, errors.New(fmt.Sprintf("Could not generate cmix DH private key: %s", err.Error())) - } + // run session loading in a separate goroutine so if it panics it can + // be caught and an error can be returned + go func() { + defer func() { + if r := recover(); r != nil { + globals.Log.ERROR.Println("Session file loading crashed") + err = sessionFileError + done <- struct{}{} + } + }() - cmixPrivateKeyDH := cmixGrp.NewIntFromBytes(cmixPrivKeyDHByte) - cmixPublicKeyDH := cmixGrp.ExpG(cmixPrivateKeyDH, cmixGrp.NewMaxInt()) + session, err = user.LoadSession(cl.storage, password) + done <- struct{}{} + }() - e2ePrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG()) + //wait for session file loading to complete + <-done if err != nil { - return nil, errors.New(fmt.Sprintf("Could not generate e2e DH private key: %s", err.Error())) + return "", errors.Wrap(err, "Login: Could not login") } - e2ePrivateKeyDH := e2eGrp.NewIntFromBytes(e2ePrivKeyDHByte) - e2ePublicKeyDH := e2eGrp.ExpG(e2ePrivateKeyDH, e2eGrp.NewMaxInt()) - - // Initialized response from Registration Server - regValidationSignature := make([]byte, 0) - - var salt []byte - - // Handle precanned registration - if preCan { - cl.opStatus(globals.REG_PRECAN) - globals.Log.INFO.Printf("Registering precanned user...") - u, UID, nk, err = cl.precannedRegister(registrationCode, nick, nk) - if err != nil { - globals.Log.ERROR.Printf("Unable to complete precanned registration: %+v", err) - return id.ZeroID, err - } - } else { - cl.opStatus(globals.REG_UID_GEN) - globals.Log.INFO.Printf("Registering dynamic user...") - - // Generate salt for UserID - salt = make([]byte, SaltSize) - _, err = csprng.NewSystemRNG().Read(salt) - if err != nil { - globals.Log.ERROR.Printf("Register: Unable to generate salt! %s", err) - return id.ZeroID, err - } - - // Generate UserID by hashing salt and public key - UID = registration.GenUserID(publicKeyRSA, salt) - - // If Registration Server is specified, contact it - // Only if registrationCode is set - globals.Log.INFO.Println("Register: Contacting registration server") - if cl.ndf.Registration.Address != "" && registrationCode != "" { - cl.opStatus(globals.REG_PERM) - regValidationSignature, err = cl.sendRegistrationMessage(registrationCode, publicKeyRSA) - if err != nil { - globals.Log.ERROR.Printf("Register: Unable to send registration message: %+v", err) - return id.ZeroID, err - } - } - globals.Log.INFO.Println("Register: successfully passed Registration message") - - var actualNick string - if nick != "" { - actualNick = nick - } else { - actualNick = base64.StdEncoding.EncodeToString(UID[:]) - } - u = user.Users.NewUser(UID, actualNick) - user.Users.UpsertUser(u) + if session == nil { + return "", errors.New("Unable to load session, no error reported") } - - cl.opStatus(globals.REG_SECURE_STORE) - - u.Email = email - - // Create the user session - newSession := user.NewSession(cl.storage, u, nk, publicKeyRSA, - privateKeyRSA, cmixPublicKeyDH, cmixPrivateKeyDH, e2ePublicKeyDH, - e2ePrivateKeyDH, salt, cmixGrp, e2eGrp, password, regValidationSignature) - cl.opStatus(globals.REG_SAVE) - - //set the registration state - err = newSession.SetRegState(user.PermissioningComplete) - if err != nil { - return id.ZeroID, errors.Wrap(err, "Permissioning Registration "+ - "Failed") + if session.GetRegState() < user.PermissioningComplete { + return "", errors.New("Cannot log a user in which has not " + + "completed registration ") } - // Store the user session - errStore := newSession.StoreSession() - - if errStore != nil { - err = errors.New(fmt.Sprintf( - "Permissioning Register: could not register due to failed session save"+ - ": %s", errStore.Error())) - return id.ZeroID, err - } - cl.session = newSession - return UID, nil + cl.session = session + return cl.session.GetCurrentUser().Nick, nil } -// RegisterWithUDB uses the account's email to register with the UDB for -// User discovery. Must be called after Register and InitNetwork. -// It will fail if the user has already registered with UDB -func (cl *Client) RegisterWithUDB(timeout time.Duration) error { - - regState := cl.GetSession().GetRegState() - - if regState != user.PermissioningComplete { - return errors.New("Cannot register with UDB when registration " + - "state is not PermissioningComplete") +// Logout closes the connection to the server at this time and does +// nothing with the user id. In the future this will release resources +// and safely release any sensitive memory. +// fixme: blocks forever is message reciever +func (cl *Client) Logout() error { + if cl.session == nil { + err := errors.New("Logout: Cannot Logout when you are not logged in") + globals.Log.ERROR.Printf(err.Error()) + return err } - email := cl.session.GetCurrentUser().Email - - var err error - - if email != "" { - globals.Log.INFO.Printf("Registering user as %s with UDB", email) - - valueType := "EMAIL" - - publicKeyBytes := cl.session.GetE2EDHPublicKey().Bytes() - err = bots.Register(valueType, email, publicKeyBytes, cl.opStatus, timeout) - if err == nil { - globals.Log.INFO.Printf("Registered with UDB!") - } else { - globals.Log.WARN.Printf("Could not register with UDB: %s", err) - } - - } else { - globals.Log.INFO.Printf("Not registering with UDB because no " + - "email found") - } + // Stop reception runner goroutine + close(cl.session.GetQuitChan()) - if err != nil { - return errors.Wrap(err, "Could not register with UDB") - } + cl.commManager.Comms.DisconnectAll() - //set the registration state - err = cl.session.SetRegState(user.UDBComplete) + errStore := cl.session.StoreSession() - if err != nil { - return errors.Wrap(err, "UDB Registration Failed") + if errStore != nil { + err := errors.New(fmt.Sprintf("Logout: Store Failed: %s" + + errStore.Error())) + globals.Log.ERROR.Printf(err.Error()) + return err } - cl.opStatus(globals.REG_SECURE_STORE) - - errStore := cl.session.StoreSession() + errImmolate := cl.session.Immolate() + cl.session = nil - // FIXME If we have an error here, the session that gets created - // doesn't get immolated. Immolation should happen in a deferred - // call instead. - if errStore != nil { - err = errors.New(fmt.Sprintf( - "UDB Register: could not register due to failed session save"+ - ": %s", errStore.Error())) + if errImmolate != nil { + err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" + + errImmolate.Error())) + globals.Log.ERROR.Printf(err.Error()) return err } return nil } -func (cl *Client) RegisterWithNodes() error { - cl.opStatus(globals.REG_NODE) - session := cl.GetSession() - //Load Cmix keys & group - cmixDHPrivKey := session.GetCMIXDHPrivateKey() - cmixDHPubKey := session.GetCMIXDHPublicKey() - cmixGrp := session.GetCmixGroup() - - //Load the rsa keys - rsaPubKey := session.GetRSAPublicKey() - rsaPrivKey := session.GetRSAPrivateKey() - - //Load the user ID - UID := session.GetCurrentUser().User - - //Load the registration signature - regSignature := session.GetRegistrationValidationSignature() - - var wg sync.WaitGroup - errChan := make(chan error, len(cl.ndf.Gateways)) - - //Get the registered node keys - registeredNodes := session.GetNodes() - - salt := session.GetSalt() - - // This variable keeps track of whether there were new registrations - // required, thus requiring the state file to be saved again - newRegistrations := false - - for i := range cl.ndf.Gateways { - localI := i - nodeID := *id.NewNodeFromBytes(cl.ndf.Nodes[i].ID) - //Register with node if the node has not been registered with already - if _, ok := registeredNodes[nodeID]; !ok { - wg.Add(1) - newRegistrations = true - go func() { - cl.registerWithNode(localI, salt, regSignature, UID, rsaPubKey, rsaPrivKey, - cmixDHPubKey, cmixDHPrivKey, cmixGrp, errChan) - wg.Done() - }() +// VerifyNDF verifies the signature of the network definition file (NDF) and +// returns the structure. Panics when the NDF string cannot be decoded and when +// the signature cannot be verified. If the NDF public key is empty, then the +// signature verification is skipped and warning is printed. +func VerifyNDF(ndfString, ndfPub string) *ndf.NetworkDefinition { + // If there is no public key, then skip verification and print warning + if ndfPub == "" { + globals.Log.WARN.Printf("Running without signed network " + + "definition file") + } else { + ndfReader := bufio.NewReader(strings.NewReader(ndfString)) + ndfData, err := ndfReader.ReadBytes('\n') + ndfData = ndfData[:len(ndfData)-1] + if err != nil { + globals.Log.FATAL.Panicf("Could not read NDF: %v", err) } - } - - wg.Wait() - //See if the registration returned errors at all - var errs error - for len(errChan) > 0 { - err := <-errChan - if errs != nil { - errs = errors.Wrap(errs, err.Error()) - } else { - errs = err + ndfSignature, err := ndfReader.ReadBytes('\n') + if err != nil { + globals.Log.FATAL.Panicf("Could not read NDF Sig: %v", + err) } - - } - //If an error every occurred, return with error - if errs != nil { - cl.opStatus(globals.REG_FAIL) - return errs - } - - // Store the user session if there were changes during node registration - if newRegistrations { - cl.opStatus(globals.REG_SECURE_STORE) - errStore := session.StoreSession() - if errStore != nil { - err := errors.New(fmt.Sprintf( - "Register: could not register due to failed session save"+ - ": %s", errStore.Error())) - return err + ndfSignature, err = base64.StdEncoding.DecodeString( + string(ndfSignature[:len(ndfSignature)-1])) + if err != nil { + globals.Log.FATAL.Panicf("Could not read NDF Sig: %v", + err) } - } - - return nil -} - -//registerWithNode registers a user. It serves as a helper for Register -func (cl *Client) registerWithNode(index int, salt, registrationValidationSignature []byte, UID *id.User, - publicKeyRSA *rsa.PublicKey, privateKeyRSA *rsa.PrivateKey, - cmixPublicKeyDH, cmixPrivateKeyDH *cyclic.Int, - cmixGrp *cyclic.Group, errorChan chan error) { + // Load the TLS cert given to us, and from that get the RSA public key + cert, err := tls.LoadCertificate(ndfPub) + if err != nil { + globals.Log.FATAL.Panicf("Could not load public key: %v", err) + } + pubKey := &rsa.PublicKey{PublicKey: *cert.PublicKey.(*gorsa.PublicKey)} - gatewayID := id.NewNodeFromBytes(cl.ndf.Nodes[index].ID).NewGateway() + // Hash NDF JSON + rsaHash := sha256.New() + rsaHash.Write(ndfData) - // Initialise blake2b hash for transmission keys and sha256 for reception - // keys - transmissionHash, _ := hash.NewCMixHash() - receptionHash := sha256.New() + globals.Log.INFO.Printf("%s \n::\n %s", + ndfSignature, ndfData) - // Request nonce message from gateway - globals.Log.INFO.Printf("Register: Requesting nonce from gateway %v/%v", - index+1, len(cl.ndf.Gateways)) - nonce, dhPub, err := cl.requestNonce(salt, registrationValidationSignature, cmixPublicKeyDH, - publicKeyRSA, privateKeyRSA, gatewayID) + // Verify signature + err = rsa.Verify( + pubKey, crypto.SHA256, rsaHash.Sum(nil), ndfSignature, nil) - if err != nil { - errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err) - errorChan <- errors.New(errMsg) + if err != nil { + globals.Log.FATAL.Panicf("Could not verify NDF: %v", err) + } } - // Load server DH pubkey - serverPubDH := cmixGrp.NewIntFromBytes(dhPub) - - // Confirm received nonce - globals.Log.INFO.Println("Register: Confirming received nonce") - err = cl.confirmNonce(UID.Bytes(), nonce, privateKeyRSA, gatewayID) + ndfJSON, _, err := ndf.DecodeNDF(ndfString) if err != nil { - errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err) - errorChan <- errors.New(errMsg) - } - nodeID := cl.topology.GetNodeAtIndex(index) - key := user.NodeKeys{ - TransmissionKey: registration.GenerateBaseKey(cmixGrp, - serverPubDH, cmixPrivateKeyDH, transmissionHash), - ReceptionKey: registration.GenerateBaseKey(cmixGrp, serverPubDH, - cmixPrivateKeyDH, receptionHash), + globals.Log.FATAL.Panicf("Could not decode NDF: %v", err) } - cl.session.PushNodeKey(nodeID, key) + return ndfJSON } -var sessionFileError = errors.New("Session file cannot be loaded and " + - "is possibly corrupt. Please contact support@xxmessenger.io") - -// LoadSession loads the session object for the UID -func (cl *Client) Login(password string) (string, error) { - - var session user.Session - var err error - done := make(chan struct{}) - - // run session loading in a separate goroutine so if it panics it can - // be caught and an error can be returned - go func() { - defer func() { - if r := recover(); r != nil { - globals.Log.ERROR.Println("Session file loading crashed") - err = sessionFileError - done <- struct{}{} - } - }() - - session, err = user.LoadSession(cl.storage, password) - done <- struct{}{} - }() +func (cl *Client) GetRegistrationVersion() string { // on client + return cl.registrationVersion +} - //wait for session file loading to complete - <-done +//GetNDF returns the clients ndf +func (cl *Client) GetNDF() *ndf.NetworkDefinition { + return cl.ndf +} - if err != nil { - return "", errors.Wrap(err, "Login: Could not login") - } +func (cl *Client) SetOperationProgressCallback(rpc OperationProgressCallback) { + cl.opStatus = func(i int) { go rpc(i) } +} - if session == nil { - return "", errors.New("Unable to load session, no error reported") - } - if session.GetRegState() < user.PermissioningComplete { - return "", errors.New("Cannot log a user in which has not " + - "completed registration ") +// Populates a text message and returns its wire representation +// TODO support multi-type messages or telling if a message is too long? +func FormatTextMessage(message string) []byte { + textMessage := cmixproto.TextMessage{ + Color: -1, + Message: message, + Time: time.Now().Unix(), } - cl.session = session - return cl.session.GetCurrentUser().Nick, nil + wireRepresentation, _ := proto.Marshal(&textMessage) + return wireRepresentation } +var sessionFileError = errors.New("Session file cannot be loaded and " + + "is possibly corrupt. Please contact support@xxmessenger.io") + // Logs in user and sets session on client object // returns the nickname or error if login fails func (cl *Client) StartMessageReceiver(callback func(error)) error { @@ -706,44 +350,6 @@ func (cl *Client) GetKeyParams() *keyStore.KeyParams { return cl.session.GetKeyStore().GetKeyParams() } -// Logout closes the connection to the server at this time and does -// nothing with the user id. In the future this will release resources -// and safely release any sensitive memory. -// fixme: blocks forever is message reciever -func (cl *Client) Logout() error { - if cl.session == nil { - err := errors.New("Logout: Cannot Logout when you are not logged in") - globals.Log.ERROR.Printf(err.Error()) - return err - } - - // Stop reception runner goroutine - close(cl.session.GetQuitChan()) - - cl.commManager.Comms.DisconnectAll() - - errStore := cl.session.StoreSession() - - if errStore != nil { - err := errors.New(fmt.Sprintf("Logout: Store Failed: %s" + - errStore.Error())) - globals.Log.ERROR.Printf(err.Error()) - return err - } - - errImmolate := cl.session.Immolate() - cl.session = nil - - if errImmolate != nil { - err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" + - errImmolate.Error())) - globals.Log.ERROR.Printf(err.Error()) - return err - } - - return nil -} - // Returns the local version of the client repo func GetLocalVersion() string { return globals.SEMVER @@ -920,3 +526,72 @@ func (cl *Client) GetSession() user.Session { func (cl *Client) GetCommManager() *io.ReceptionManager { return cl.commManager } + +//GetUpdatedNDF: Connects to the permissioning server to get the updated NDF from it +func (cl *Client) getUpdatedNDF() (*ndf.NetworkDefinition, error) { // again, uses internal ndf. stay here, return results instead + + //Hash the client's ndf for comparison with registration's ndf + hash := sha256.New() + ndfBytes := cl.ndf.Serialize() + hash.Write(ndfBytes) + ndfHash := hash.Sum(nil) + + //Put the hash in a message + msg := &mixmessages.NDFHash{Hash: ndfHash} + + host, ok := cl.commManager.Comms.GetHost(PermissioningAddrID) + if !ok { + return nil, errors.New("Failed to find permissioning host") + } + + //Send the hash to registration + response, err := cl.commManager.Comms.SendGetUpdatedNDF(host, msg) + if err != nil { + errMsg := fmt.Sprintf("Failed to get ndf from permissioning: %v", err) + return nil, errors.New(errMsg) + } + + //If there was no error and the response is nil, client's ndf is up-to-date + if response == nil { + globals.Log.DEBUG.Printf("Client NDF up-to-date") + return nil, nil + } + + //FixMe: use verify instead? Probs need to add a signature to ndf, like in registration's getupdate? + + globals.Log.INFO.Printf("Remote NDF: %s", string(response.Ndf)) + + //Otherwise pull the ndf out of the response + updatedNdf, _, err := ndf.DecodeNDF(string(response.Ndf)) + if err != nil { + //If there was an error decoding ndf + errMsg := fmt.Sprintf("Failed to decode response to ndf: %v", err) + return nil, errors.New(errMsg) + } + return updatedNdf, nil +} + +//request calls getUpdatedNDF for a new NDF repeatedly until it gets an NDF +func requestNdf(cl *Client) error { + // Continuously polls for a new ndf after sleeping until response if gotten + globals.Log.INFO.Printf("Polling for a new NDF") + newNDf, err := cl.getUpdatedNDF() + + if err != nil { + //lets the client continue when permissioning does not provide NDFs + if err.Error() == noNDFErr.Error() { + globals.Log.WARN.Println("Continuing without an updated NDF") + return nil + } + + errMsg := fmt.Sprintf("Failed to get updated ndf: %v", err) + globals.Log.ERROR.Printf(errMsg) + return errors.New(errMsg) + } + + if newNDf != nil { + cl.ndf = newNDf + } + + return nil +} diff --git a/api/register.go b/api/register.go new file mode 100644 index 0000000000000000000000000000000000000000..6620e9b37cbf5e2bc33cebc35e68c8db550b063e --- /dev/null +++ b/api/register.go @@ -0,0 +1,341 @@ +package api + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "fmt" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/bots" + "gitlab.com/elixxir/client/globals" + "gitlab.com/elixxir/client/user" + "gitlab.com/elixxir/crypto/csprng" + "gitlab.com/elixxir/crypto/cyclic" + "gitlab.com/elixxir/crypto/hash" + "gitlab.com/elixxir/crypto/large" + "gitlab.com/elixxir/crypto/registration" + "gitlab.com/elixxir/crypto/signature/rsa" + "gitlab.com/elixxir/primitives/id" + "sync" + "time" +) + +const SaltSize = 256 + +// RegisterWithPermissioning registers user with permissioning and returns the +// User ID. Returns an error if registration fails. +func (cl *Client) RegisterWithPermissioning(preCan bool, registrationCode, nick, email, + password string, privateKeyRSA *rsa.PrivateKey) (*id.User, error) { + + var err error + var u *user.User + var UID *id.User + + cl.opStatus(globals.REG_KEYGEN) + + largeIntBits := 16 + + cmixGrp := cyclic.NewGroup( + large.NewIntFromString(cl.ndf.CMIX.Prime, largeIntBits), + large.NewIntFromString(cl.ndf.CMIX.Generator, largeIntBits)) + + e2eGrp := cyclic.NewGroup( + large.NewIntFromString(cl.ndf.E2E.Prime, largeIntBits), + large.NewIntFromString(cl.ndf.E2E.Generator, largeIntBits)) + + // Make CMIX keys array + nk := make(map[id.Node]user.NodeKeys) + + // GENERATE CLIENT RSA KEYS + if privateKeyRSA == nil { + privateKeyRSA, err = rsa.GenerateKey(rand.Reader, rsa.DefaultRSABitLen) + if err != nil { + return nil, err + } + } + + publicKeyRSA := privateKeyRSA.GetPublic() + + cmixPrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG()) + + if err != nil { + return nil, errors.New(fmt.Sprintf("Could not generate cmix DH private key: %s", err.Error())) + } + + cmixPrivateKeyDH := cmixGrp.NewIntFromBytes(cmixPrivKeyDHByte) + cmixPublicKeyDH := cmixGrp.ExpG(cmixPrivateKeyDH, cmixGrp.NewMaxInt()) + + e2ePrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG()) + + if err != nil { + return nil, errors.New(fmt.Sprintf("Could not generate e2e DH private key: %s", err.Error())) + } + + e2ePrivateKeyDH := e2eGrp.NewIntFromBytes(e2ePrivKeyDHByte) + e2ePublicKeyDH := e2eGrp.ExpG(e2ePrivateKeyDH, e2eGrp.NewMaxInt()) + + // Initialized response from Registration Server + regValidationSignature := make([]byte, 0) + + var salt []byte + + // Handle precanned registration + if preCan { + cl.opStatus(globals.REG_PRECAN) + globals.Log.INFO.Printf("Registering precanned user...") + u, UID, nk, err = cl.precannedRegister(registrationCode, nick, nk) + if err != nil { + globals.Log.ERROR.Printf("Unable to complete precanned registration: %+v", err) + return id.ZeroID, err + } + } else { + cl.opStatus(globals.REG_UID_GEN) + globals.Log.INFO.Printf("Registering dynamic user...") + + // Generate salt for UserID + salt = make([]byte, SaltSize) + _, err = csprng.NewSystemRNG().Read(salt) + if err != nil { + globals.Log.ERROR.Printf("Register: Unable to generate salt! %s", err) + return id.ZeroID, err + } + + // Generate UserID by hashing salt and public key + UID = registration.GenUserID(publicKeyRSA, salt) + + // If Registration Server is specified, contact it + // Only if registrationCode is set + globals.Log.INFO.Println("Register: Contacting registration server") + if cl.ndf.Registration.Address != "" && registrationCode != "" { + cl.opStatus(globals.REG_PERM) + regValidationSignature, err = cl.sendRegistrationMessage(registrationCode, publicKeyRSA) + if err != nil { + globals.Log.ERROR.Printf("Register: Unable to send registration message: %+v", err) + return id.ZeroID, err + } + } + globals.Log.INFO.Println("Register: successfully passed Registration message") + + var actualNick string + if nick != "" { + actualNick = nick + } else { + actualNick = base64.StdEncoding.EncodeToString(UID[:]) + } + u = user.Users.NewUser(UID, actualNick) + user.Users.UpsertUser(u) + } + + cl.opStatus(globals.REG_SECURE_STORE) + + u.Email = email + + // Create the user session + newSession := user.NewSession(cl.storage, u, nk, publicKeyRSA, + privateKeyRSA, cmixPublicKeyDH, cmixPrivateKeyDH, e2ePublicKeyDH, + e2ePrivateKeyDH, salt, cmixGrp, e2eGrp, password, regValidationSignature) + cl.opStatus(globals.REG_SAVE) + + //set the registration state + err = newSession.SetRegState(user.PermissioningComplete) + if err != nil { + return id.ZeroID, errors.Wrap(err, "Permissioning Registration "+ + "Failed") + } + + // Store the user session + errStore := newSession.StoreSession() + + if errStore != nil { + err = errors.New(fmt.Sprintf( + "Permissioning Register: could not register due to failed session save"+ + ": %s", errStore.Error())) + return id.ZeroID, err + } + cl.session = newSession + return UID, nil +} + +// RegisterWithUDB uses the account's email to register with the UDB for +// User discovery. Must be called after Register and InitNetwork. +// It will fail if the user has already registered with UDB +func (cl *Client) RegisterWithUDB(timeout time.Duration) error { + + regState := cl.GetSession().GetRegState() + + if regState != user.PermissioningComplete { + return errors.New("Cannot register with UDB when registration " + + "state is not PermissioningComplete") + } + + email := cl.session.GetCurrentUser().Email + + var err error + + if email != "" { + globals.Log.INFO.Printf("Registering user as %s with UDB", email) + + valueType := "EMAIL" + + publicKeyBytes := cl.session.GetE2EDHPublicKey().Bytes() + err = bots.Register(valueType, email, publicKeyBytes, cl.opStatus, timeout) + if err == nil { + globals.Log.INFO.Printf("Registered with UDB!") + } else { + globals.Log.WARN.Printf("Could not register with UDB: %s", err) + } + + } else { + globals.Log.INFO.Printf("Not registering with UDB because no " + + "email found") + } + + if err != nil { + return errors.Wrap(err, "Could not register with UDB") + } + + //set the registration state + err = cl.session.SetRegState(user.UDBComplete) + + if err != nil { + return errors.Wrap(err, "UDB Registration Failed") + } + + cl.opStatus(globals.REG_SECURE_STORE) + + errStore := cl.session.StoreSession() + + // FIXME If we have an error here, the session that gets created + // doesn't get immolated. Immolation should happen in a deferred + // call instead. + if errStore != nil { + err = errors.New(fmt.Sprintf( + "UDB Register: could not register due to failed session save"+ + ": %s", errStore.Error())) + return err + } + + return nil +} + +func (cl *Client) RegisterWithNodes() error { + cl.opStatus(globals.REG_NODE) + session := cl.GetSession() + //Load Cmix keys & group + cmixDHPrivKey := session.GetCMIXDHPrivateKey() + cmixDHPubKey := session.GetCMIXDHPublicKey() + cmixGrp := session.GetCmixGroup() + + //Load the rsa keys + rsaPubKey := session.GetRSAPublicKey() + rsaPrivKey := session.GetRSAPrivateKey() + + //Load the user ID + UID := session.GetCurrentUser().User + + //Load the registration signature + regSignature := session.GetRegistrationValidationSignature() + + var wg sync.WaitGroup + errChan := make(chan error, len(cl.ndf.Gateways)) + + //Get the registered node keys + registeredNodes := session.GetNodes() + + salt := session.GetSalt() + + // This variable keeps track of whether there were new registrations + // required, thus requiring the state file to be saved again + newRegistrations := false + + for i := range cl.ndf.Gateways { + localI := i + nodeID := *id.NewNodeFromBytes(cl.ndf.Nodes[i].ID) + //Register with node if the node has not been registered with already + if _, ok := registeredNodes[nodeID]; !ok { + wg.Add(1) + newRegistrations = true + go func() { + cl.registerWithNode(localI, salt, regSignature, UID, rsaPubKey, rsaPrivKey, + cmixDHPubKey, cmixDHPrivKey, cmixGrp, errChan) + wg.Done() + }() + } + } + + wg.Wait() + //See if the registration returned errors at all + var errs error + for len(errChan) > 0 { + err := <-errChan + if errs != nil { + errs = errors.Wrap(errs, err.Error()) + } else { + errs = err + } + + } + //If an error every occurred, return with error + if errs != nil { + cl.opStatus(globals.REG_FAIL) + return errs + } + + // Store the user session if there were changes during node registration + if newRegistrations { + cl.opStatus(globals.REG_SECURE_STORE) + errStore := session.StoreSession() + if errStore != nil { + err := errors.New(fmt.Sprintf( + "Register: could not register due to failed session save"+ + ": %s", errStore.Error())) + return err + } + } + + return nil +} + +//registerWithNode registers a user. It serves as a helper for Register +func (cl *Client) registerWithNode(index int, salt, registrationValidationSignature []byte, UID *id.User, + publicKeyRSA *rsa.PublicKey, privateKeyRSA *rsa.PrivateKey, + cmixPublicKeyDH, cmixPrivateKeyDH *cyclic.Int, + cmixGrp *cyclic.Group, errorChan chan error) { + + gatewayID := id.NewNodeFromBytes(cl.ndf.Nodes[index].ID).NewGateway() + + // Initialise blake2b hash for transmission keys and sha256 for reception + // keys + transmissionHash, _ := hash.NewCMixHash() + receptionHash := sha256.New() + + // Request nonce message from gateway + globals.Log.INFO.Printf("Register: Requesting nonce from gateway %v/%v", + index+1, len(cl.ndf.Gateways)) + nonce, dhPub, err := cl.requestNonce(salt, registrationValidationSignature, cmixPublicKeyDH, + publicKeyRSA, privateKeyRSA, gatewayID) + + if err != nil { + errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err) + errorChan <- errors.New(errMsg) + } + + // Load server DH pubkey + serverPubDH := cmixGrp.NewIntFromBytes(dhPub) + + // Confirm received nonce + globals.Log.INFO.Println("Register: Confirming received nonce") + err = cl.confirmNonce(UID.Bytes(), nonce, privateKeyRSA, gatewayID) + if err != nil { + errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err) + errorChan <- errors.New(errMsg) + } + nodeID := cl.topology.GetNodeAtIndex(index) + key := user.NodeKeys{ + TransmissionKey: registration.GenerateBaseKey(cmixGrp, + serverPubDH, cmixPrivateKeyDH, transmissionHash), + ReceptionKey: registration.GenerateBaseKey(cmixGrp, serverPubDH, + cmixPrivateKeyDH, receptionHash), + } + cl.session.PushNodeKey(nodeID, key) +}