diff --git a/api/client.go b/api/client.go index bedb3a165f787c8fd48b2758970df5d170ab2118..8e09b41f71a745ec4ffd7b69ffe82bcd21714a98 100644 --- a/api/client.go +++ b/api/client.go @@ -9,6 +9,7 @@ package api import ( "bufio" "crypto" + "crypto/rand" gorsa "crypto/rsa" "crypto/sha256" "encoding/base64" @@ -23,8 +24,11 @@ import ( "gitlab.com/elixxir/client/parse" "gitlab.com/elixxir/client/rekey" "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/crypto/tls" "gitlab.com/elixxir/primitives/circuit" @@ -33,6 +37,7 @@ import ( "gitlab.com/elixxir/primitives/switchboard" goio "io" "strings" + "sync" "time" ) @@ -447,6 +452,331 @@ type SearchCallback interface { Callback(userID, pubKey []byte, err error) } +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) { + + if !preCan && cl.commManager.GetConnectionStatus() != io.Online { + return nil, errors.New("Cannot register when disconnected from the network") + } + + 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.Errorf("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.Errorf("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 { + errMsg := errors.Errorf("Unable to complete precanned registration: %+v", err) + return id.ZeroID, errMsg + } + } 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 { + errMsg := errors.Errorf("Register: Unable to generate salt! %s", err) + return id.ZeroID, errMsg + } + + // 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 { + err = errors.Errorf("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 { + errMsg := errors.Errorf( + "Permissioning Register: could not register due to failed session save"+ + ": %s", errStore.Error()) + return id.ZeroID, errMsg + } + 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 Connect. +// 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") + } + + status := cl.commManager.GetConnectionStatus() + if status == io.Connecting || status == io.Offline { + return errors.New("ERROR: could not RegisterWithUDB - connection is either offline or connecting") + } + + 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") + } + + 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 { + errMsg := errors.Errorf( + "UDB Register: could not register due to failed session save"+ + ": %s", errStore.Error()) + return errMsg + } + + return nil +} + +func (cl *Client) RegisterWithNodes() error { + 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 { + errStore := session.StoreSession() + if errStore != nil { + err := errors.Errorf( + "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 := errors.Errorf("Register: Failed requesting nonce from gateway: %+v", err) + errorChan <- 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 := errors.Errorf("Register: Unable to confirm nonce: %v", err) + errorChan <- 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) +} + // UDB Search API // Pass a callback function to extract results func (cl *Client) SearchForUser(emailAddress string, diff --git a/api/register.go b/api/register.go deleted file mode 100644 index 122b62d5729796a7814d6c24e4b89a4f215fc049..0000000000000000000000000000000000000000 --- a/api/register.go +++ /dev/null @@ -1,351 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2019 Privategrity Corporation / -// / -// All rights reserved. / -//////////////////////////////////////////////////////////////////////////////// -package api - -import ( - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "github.com/pkg/errors" - "gitlab.com/elixxir/client/bots" - "gitlab.com/elixxir/client/globals" - "gitlab.com/elixxir/client/io" - "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) { - - if !preCan && cl.commManager.GetConnectionStatus() != io.Online { - return nil, errors.New("Cannot register when disconnected from the network") - } - - 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.Errorf("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.Errorf("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 { - errMsg := errors.Errorf("Unable to complete precanned registration: %+v", err) - return id.ZeroID, errMsg - } - } 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 { - errMsg := errors.Errorf("Register: Unable to generate salt! %s", err) - return id.ZeroID, errMsg - } - - // 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 { - err = errors.Errorf("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 { - errMsg := errors.Errorf( - "Permissioning Register: could not register due to failed session save"+ - ": %s", errStore.Error()) - return id.ZeroID, errMsg - } - 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 Connect. -// 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") - } - - status := cl.commManager.GetConnectionStatus() - if status == io.Connecting || status == io.Offline { - return errors.New("ERROR: could not RegisterWithUDB - connection is either offline or connecting") - } - - 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") - } - - 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 { - errMsg := errors.Errorf( - "UDB Register: could not register due to failed session save"+ - ": %s", errStore.Error()) - return errMsg - } - - return nil -} - -func (cl *Client) RegisterWithNodes() error { - 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 { - errStore := session.StoreSession() - if errStore != nil { - err := errors.Errorf( - "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 := errors.Errorf("Register: Failed requesting nonce from gateway: %+v", err) - errorChan <- 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 := errors.Errorf("Register: Unable to confirm nonce: %v", err) - errorChan <- 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) -}