Skip to content
Snippets Groups Projects
Commit 3c0d3a82 authored by Jonah Husson's avatar Jonah Husson
Browse files

move register functions to their own file

parent d5b05d59
No related branches found
No related tags found
No related merge requests found
...@@ -9,7 +9,6 @@ package api ...@@ -9,7 +9,6 @@ package api
import ( import (
"bufio" "bufio"
"crypto" "crypto"
"crypto/rand"
gorsa "crypto/rsa" gorsa "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
...@@ -25,11 +24,8 @@ import ( ...@@ -25,11 +24,8 @@ import (
"gitlab.com/elixxir/client/rekey" "gitlab.com/elixxir/client/rekey"
"gitlab.com/elixxir/client/user" "gitlab.com/elixxir/client/user"
"gitlab.com/elixxir/comms/mixmessages" "gitlab.com/elixxir/comms/mixmessages"
"gitlab.com/elixxir/crypto/csprng"
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/hash"
"gitlab.com/elixxir/crypto/large" "gitlab.com/elixxir/crypto/large"
"gitlab.com/elixxir/crypto/registration"
"gitlab.com/elixxir/crypto/signature/rsa" "gitlab.com/elixxir/crypto/signature/rsa"
"gitlab.com/elixxir/crypto/tls" "gitlab.com/elixxir/crypto/tls"
"gitlab.com/elixxir/primitives/circuit" "gitlab.com/elixxir/primitives/circuit"
...@@ -38,7 +34,6 @@ import ( ...@@ -38,7 +34,6 @@ import (
"gitlab.com/elixxir/primitives/switchboard" "gitlab.com/elixxir/primitives/switchboard"
goio "io" goio "io"
"strings" "strings"
"sync"
"time" "time"
) )
...@@ -58,144 +53,6 @@ var noNDFErr = errors.New("Failed to get ndf from permissioning: rpc error: code ...@@ -58,144 +53,6 @@ var noNDFErr = errors.New("Failed to get ndf from permissioning: rpc error: code
//used to report the state of registration //used to report the state of registration
type OperationProgressCallback func(int) 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 := errors.Errorf("Failed to get updated ndf: %v", err)
return errMsg
}
if newNDf != nil {
cl.ndf = newNDf
}
return nil
}
// Creates a new Client using the storage mechanism provided. // Creates a new Client using the storage mechanism provided.
// If none is provided, a default storage using OS file access // If none is provided, a default storage using OS file access
// is created // is created
...@@ -243,379 +100,171 @@ func NewClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinit ...@@ -243,379 +100,171 @@ func NewClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinit
return cl, nil return cl, nil
} }
func (cl *Client) GetRegistrationVersion() string { // on client // LoadSession loads the session object for the UID
return cl.registrationVersion func (cl *Client) Login(password string) (string, error) {
}
//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) {
var session user.Session
var err error var err error
var u *user.User done := make(chan struct{})
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 { // run session loading in a separate goroutine so if it panics it can
return nil, errors.Errorf("Could not generate cmix DH private key: %s", err.Error()) // 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) session, err = user.LoadSession(cl.storage, password)
cmixPublicKeyDH := cmixGrp.ExpG(cmixPrivateKeyDH, cmixGrp.NewMaxInt()) done <- struct{}{}
}()
e2ePrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG()) //wait for session file loading to complete
<-done
if err != nil { if err != nil {
return nil, errors.Errorf("Could not generate e2e DH private key: %s", err.Error()) return "", errors.Wrap(err, "Login: Could not login")
} }
e2ePrivateKeyDH := e2eGrp.NewIntFromBytes(e2ePrivKeyDHByte) if session == nil {
e2ePublicKeyDH := e2eGrp.ExpG(e2ePrivateKeyDH, e2eGrp.NewMaxInt()) return "", errors.New("Unable to load session, no error reported")
// 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 { if session.GetRegState() < user.PermissioningComplete {
cl.opStatus(globals.REG_UID_GEN) return "", errors.New("Cannot log a user in which has not " +
globals.Log.INFO.Printf("Registering dynamic user...") "completed registration ")
// 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 cl.session = session
UID = registration.GenUserID(publicKeyRSA, salt) return cl.session.GetCurrentUser().Nick, nil
// 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 // Logout closes the connection to the server at this time and does
if nick != "" { // nothing with the user id. In the future this will release resources
actualNick = nick // and safely release any sensitive memory.
} else { // fixme: blocks forever is message reciever
actualNick = base64.StdEncoding.EncodeToString(UID[:]) func (cl *Client) Logout() error {
} if cl.session == nil {
u = user.Users.NewUser(UID, actualNick) err := errors.New("Logout: Cannot Logout when you are not logged in")
user.Users.UpsertUser(u) globals.Log.ERROR.Printf(err.Error())
return err
} }
cl.opStatus(globals.REG_SECURE_STORE) // Stop reception runner goroutine
close(cl.session.GetQuitChan())
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 cl.commManager.Comms.DisconnectAll()
err = newSession.SetRegState(user.PermissioningComplete)
if err != nil {
return id.ZeroID, errors.Wrap(err, "Permissioning Registration "+
"Failed")
}
// Store the user session errStore := cl.session.StoreSession()
errStore := newSession.StoreSession()
if errStore != nil { if errStore != nil {
errMsg := errors.Errorf( err := errors.New(fmt.Sprintf("Logout: Store Failed: %s" +
"Permissioning Register: could not register due to failed session save"+ errStore.Error()))
": %s", errStore.Error()) globals.Log.ERROR.Printf(err.Error())
return id.ZeroID, errMsg return err
}
cl.session = newSession
return UID, nil
} }
// RegisterWithUDB uses the account's email to register with the UDB for errImmolate := cl.session.Immolate()
// User discovery. Must be called after Register and InitNetwork. cl.session = nil
// 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 { if errImmolate != nil {
return errors.New("Cannot register with UDB when registration " + err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" +
"state is not PermissioningComplete") errImmolate.Error()))
globals.Log.ERROR.Printf(err.Error())
return err
} }
email := cl.session.GetCurrentUser().Email return nil
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)
} }
// 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 { } else {
globals.Log.INFO.Printf("Not registering with UDB because no " + ndfReader := bufio.NewReader(strings.NewReader(ndfString))
"email found") ndfData, err := ndfReader.ReadBytes('\n')
} ndfData = ndfData[:len(ndfData)-1]
if err != nil { if err != nil {
return errors.Wrap(err, "Could not register with UDB") globals.Log.FATAL.Panicf("Could not read NDF: %v", err)
} }
ndfSignature, err := ndfReader.ReadBytes('\n')
//set the registration state
err = cl.session.SetRegState(user.UDBComplete)
if err != nil { if err != nil {
return errors.Wrap(err, "UDB Registration Failed") globals.Log.FATAL.Panicf("Could not read NDF Sig: %v",
err)
} }
ndfSignature, err = base64.StdEncoding.DecodeString(
errStore := cl.session.StoreSession() string(ndfSignature[:len(ndfSignature)-1]))
if err != nil {
// FIXME If we have an error here, the session that gets created globals.Log.FATAL.Panicf("Could not read NDF Sig: %v",
// doesn't get immolated. Immolation should happen in a deferred err)
// call instead.
if errStore != nil {
errMsg := errors.Errorf(
"UDB Register: could not register due to failed session save"+
": %s", errStore.Error())
return errMsg
} }
// Load the TLS cert given to us, and from that get the RSA public key
return nil 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)}
func (cl *Client) RegisterWithNodes() error { // Hash NDF JSON
session := cl.GetSession() rsaHash := sha256.New()
//Load Cmix keys & group rsaHash.Write(ndfData)
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() globals.Log.INFO.Printf("%s \n::\n %s",
ndfSignature, ndfData)
// This variable keeps track of whether there were new registrations // Verify signature
// required, thus requiring the state file to be saved again err = rsa.Verify(
newRegistrations := false pubKey, crypto.SHA256, rsaHash.Sum(nil), ndfSignature, nil)
for i := range cl.ndf.Gateways { if err != nil {
localI := i globals.Log.FATAL.Panicf("Could not verify NDF: %v", err)
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() ndfJSON, _, err := ndf.DecodeNDF(ndfString)
//See if the registration returned errors at all if err != nil {
var errs error globals.Log.FATAL.Panicf("Could not decode NDF: %v", err)
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 return ndfJSON
if errs != nil {
cl.opStatus(globals.REG_FAIL)
return errs
} }
// Store the user session if there were changes during node registration func (cl *Client) GetRegistrationVersion() string { // on client
if newRegistrations { return cl.registrationVersion
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 //GetNDF returns the clients ndf
func (cl *Client) GetNDF() *ndf.NetworkDefinition {
return cl.ndf
} }
//registerWithNode registers a user. It serves as a helper for Register func (cl *Client) SetOperationProgressCallback(rpc OperationProgressCallback) {
func (cl *Client) registerWithNode(index int, salt, registrationValidationSignature []byte, UID *id.User, cl.opStatus = func(i int) { go rpc(i) }
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 // Populates a text message and returns its wire representation
serverPubDH := cmixGrp.NewIntFromBytes(dhPub) // TODO support multi-type messages or telling if a message is too long?
func FormatTextMessage(message string) []byte {
// Confirm received nonce textMessage := cmixproto.TextMessage{
globals.Log.INFO.Println("Register: Confirming received nonce") Color: -1,
err = cl.confirmNonce(UID.Bytes(), nonce, privateKeyRSA, gatewayID) Message: message,
if err != nil { Time: time.Now().Unix(),
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)
wireRepresentation, _ := proto.Marshal(&textMessage)
return wireRepresentation
} }
var sessionFileError = errors.New("Session file cannot be loaded and " + var sessionFileError = errors.New("Session file cannot be loaded and " +
"is possibly corrupt. Please contact support@xxmessenger.io") "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{}{}
}()
//wait for session file loading to complete
<-done
if err != nil {
return "", errors.Wrap(err, "Login: Could not login")
}
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 ")
}
cl.session = session
return cl.session.GetCurrentUser().Nick, nil
}
// Logs in user and sets session on client object // Logs in user and sets session on client object
// returns the nickname or error if login fails // returns the nickname or error if login fails
func (cl *Client) StartMessageReceiver(callback func(error)) error { func (cl *Client) StartMessageReceiver(callback func(error)) error {
...@@ -704,44 +353,6 @@ func (cl *Client) GetKeyParams() *keyStore.KeyParams { ...@@ -704,44 +353,6 @@ func (cl *Client) GetKeyParams() *keyStore.KeyParams {
return cl.session.GetKeyStore().GetKeyParams() 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 // Returns the local version of the client repo
func GetLocalVersion() string { func GetLocalVersion() string {
return globals.SEMVER return globals.SEMVER
...@@ -918,3 +529,72 @@ func (cl *Client) GetSession() user.Session { ...@@ -918,3 +529,72 @@ func (cl *Client) GetSession() user.Session {
func (cl *Client) GetCommManager() *io.ReceptionManager { func (cl *Client) GetCommManager() *io.ReceptionManager {
return cl.commManager 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
}
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)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment