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

move register functions to their own file

parent 64ac846b
No related branches found
No related tags found
No related merge requests found
......@@ -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
if session == nil {
return "", errors.New("Unable to load session, no error reported")
}
} 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
if session.GetRegState() < user.PermissioningComplete {
return "", errors.New("Cannot log a user in which has not " +
"completed registration ")
}
// 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
}
cl.session = session
return cl.session.GetCurrentUser().Nick, nil
}
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)
// 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
}
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)
// Stop reception runner goroutine
close(cl.session.GetQuitChan())
//set the registration state
err = newSession.SetRegState(user.PermissioningComplete)
if err != nil {
return id.ZeroID, errors.Wrap(err, "Permissioning Registration "+
"Failed")
}
cl.commManager.Comms.DisconnectAll()
// Store the user session
errStore := newSession.StoreSession()
errStore := cl.session.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
err := errors.New(fmt.Sprintf("Logout: Store Failed: %s" +
errStore.Error()))
globals.Log.ERROR.Printf(err.Error())
return err
}
// 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()
errImmolate := cl.session.Immolate()
cl.session = nil
if regState != user.PermissioningComplete {
return errors.New("Cannot register with UDB when registration " +
"state is not PermissioningComplete")
if errImmolate != nil {
err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" +
errImmolate.Error()))
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)
return 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 {
globals.Log.INFO.Printf("Not registering with UDB because no " +
"email found")
}
ndfReader := bufio.NewReader(strings.NewReader(ndfString))
ndfData, err := ndfReader.ReadBytes('\n')
ndfData = ndfData[:len(ndfData)-1]
if err != nil {
return errors.Wrap(err, "Could not register with UDB")
globals.Log.FATAL.Panicf("Could not read NDF: %v", err)
}
//set the registration state
err = cl.session.SetRegState(user.UDBComplete)
ndfSignature, err := ndfReader.ReadBytes('\n')
if err != nil {
return errors.Wrap(err, "UDB Registration Failed")
globals.Log.FATAL.Panicf("Could not read NDF Sig: %v",
err)
}
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
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
// 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)}
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()
// Hash NDF JSON
rsaHash := sha256.New()
rsaHash.Write(ndfData)
salt := session.GetSalt()
globals.Log.INFO.Printf("%s \n::\n %s",
ndfSignature, ndfData)
// This variable keeps track of whether there were new registrations
// required, thus requiring the state file to be saved again
newRegistrations := false
// Verify signature
err = rsa.Verify(
pubKey, crypto.SHA256, rsaHash.Sum(nil), ndfSignature, nil)
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()
}()
if err != nil {
globals.Log.FATAL.Panicf("Could not verify 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
}
ndfJSON, _, err := ndf.DecodeNDF(ndfString)
if err != nil {
globals.Log.FATAL.Panicf("Could not decode NDF: %v", err)
}
//If an error every occurred, return with error
if errs != nil {
cl.opStatus(globals.REG_FAIL)
return errs
return ndfJSON
}
// 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
}
func (cl *Client) GetRegistrationVersion() string { // on client
return cl.registrationVersion
}
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) 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)
func (cl *Client) SetOperationProgressCallback(rpc OperationProgressCallback) {
cl.opStatus = func(i int) { go rpc(i) }
}
// 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),
// 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.PushNodeKey(nodeID, key)
wireRepresentation, _ := proto.Marshal(&textMessage)
return wireRepresentation
}
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{}{}
}()
//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
// 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
}
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