Skip to content
Snippets Groups Projects
Select Git revision
  • 5140db51eeff6dfefc3b14bee2a3446de460ed0c
  • release default protected
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • hotfix/TestHostPool_UpdateNdf_AddFilter
  • XX-4719/announcementChannels
  • xx-4717/logLevel
  • jonah/noob-channel
  • master protected
  • XX-4707/tagDiskJson
  • xx-4698/notification-retry
  • hotfix/notifylockup
  • syncNodes
  • hotfix/localCB
  • XX-4677/NewChanManagerMobile
  • XX-4689/DmSync
  • duplicatePrefix
  • XX-4601/HavenInvites
  • finalizedUICallbacks
  • XX-4673/AdminKeySync
  • debugNotifID
  • anne/test
  • v4.7.5
  • v4.7.4
  • v4.7.3
  • v4.7.2
  • v4.7.1
  • v4.6.3
  • v4.6.1
  • v4.5.0
  • v4.4.4
  • v4.3.11
  • v4.3.8
  • v4.3.7
  • v4.3.6
  • v4.3.5
  • v4.2.0
  • v4.3.0
  • v4.3.4
  • v4.3.3
  • v4.3.2
  • v4.3.1
41 results

backup.go

Blame
  • user avatar
    Jake Taylor authored
    5140db51
    History
    backup.go 9.34 KiB
    ////////////////////////////////////////////////////////////////////////////////
    // Copyright © 2020 xx network SEZC                                           //
    //                                                                            //
    // Use of this source code is governed by a license that can be found in the  //
    // LICENSE file                                                               //
    ////////////////////////////////////////////////////////////////////////////////
    
    package backup
    
    import (
    	"sync"
    	"time"
    
    	"gitlab.com/elixxir/client/storage/versioned"
    	"gitlab.com/elixxir/client/xxdk/e2eApi"
    	"gitlab.com/elixxir/crypto/cyclic"
    	"gitlab.com/elixxir/primitives/fact"
    	"gitlab.com/xx_network/primitives/id"
    
    	"github.com/pkg/errors"
    	jww "github.com/spf13/jwalterweatherman"
    	"gitlab.com/elixxir/crypto/backup"
    	"gitlab.com/elixxir/crypto/fastRNG"
    	"gitlab.com/xx_network/crypto/signature/rsa"
    )
    
    // Error messages.
    const (
    	// InitializeBackup
    	errSavePassword      = "failed to save password: %+v"
    	errSaveKeySaltParams = "failed to save key, salt, and params: %+v"
    
    	// Backup.StopBackup
    	errDeletePassword = "failed to delete password: %+v"
    	errDeleteCrypto   = "failed to delete key, salt, and parameters: %+v"
    )
    
    // Backup stores the user's key and backup callback used to encrypt and transmit
    // the backup data.
    type Backup struct {
    	// Callback that is called with the encrypted backup when triggered
    	updateBackupCb UpdateBackupFn
    
    	container *e2eApi.Container
    
    	jsonParams string
    
    	// E2e structures
    	e2e     E2e
    	session Session
    	ud      UserDiscovery
    	kv      *versioned.KV
    	rng     *fastRNG.StreamGenerator
    
    	mux sync.RWMutex
    }
    
    // E2e is a subset of functions from the interface e2e.Handler.
    type E2e interface {
    	GetAllPartnerIDs() []*id.ID
    	GetHistoricalDHPubkey() *cyclic.Int
    	GetHistoricalDHPrivkey() *cyclic.Int
    }
    
    // Session is a subset of functions from the interface storage.Session.
    type Session interface {
    	GetRegCode() (string, error)
    	GetTransmissionID() *id.ID
    	GetTransmissionSalt() []byte
    	GetReceptionID() *id.ID
    	GetReceptionSalt() []byte
    	GetReceptionRSA() *rsa.PrivateKey
    	GetTransmissionRSA() *rsa.PrivateKey
    	GetTransmissionRegistrationValidationSignature() []byte
    	GetReceptionRegistrationValidationSignature() []byte
    	GetRegistrationTimestamp() time.Time
    }
    
    type UserDiscovery interface {
    	GetFacts() fact.FactList
    }
    
    // UpdateBackupFn is the callback that encrypted backup data is returned on
    type UpdateBackupFn func(encryptedBackup []byte)
    
    // InitializeBackup creates a new Backup object with the callback to return
    // backups when triggered. On initialization, 32-bit key is derived from the
    // user's password via Argon2 and a 16-bit salt is generated. Both are saved to
    // storage along with the parameters used in Argon2 to be used when encrypting
    // new backups.
    // Call this to turn on backups for the first time or to replace the user's
    // password.
    func InitializeBackup(password string, updateBackupCb UpdateBackupFn,
    	container *e2eApi.Container, e2e E2e, session Session, ud UserDiscovery,
    	kv *versioned.KV, rng *fastRNG.StreamGenerator) (*Backup, error) {
    	b := &Backup{
    		updateBackupCb: updateBackupCb,
    		container:      container,
    		e2e:            e2e,
    		session:        session,
    		ud:             ud,
    		kv:             kv,
    		rng:            rng,
    	}
    
    	// Derive key and get generated salt and parameters
    	rand := b.rng.GetStream()
    	salt, err := backup.MakeSalt(rand)
    	if err != nil {
    		return nil, err
    	}
    	rand.Close()
    
    	params := backup.DefaultParams()
    	params.Memory = 64 * 1024 // 64 MiB
    	params.Threads = 1
    	params.Time = 5
    	key := backup.DeriveKey(password, salt, params)
    
    	// Save key, salt, and parameters to storage
    	err = saveBackup(key, salt, params, b.kv)
    	if err != nil {
    		return nil, errors.Errorf(errSaveKeySaltParams, err)
    	}
    
    	// Setting backup trigger in client
    	b.container.SetBackup(b.TriggerBackup)
    
    	b.TriggerBackup("InitializeBackup")
    	jww.INFO.Print("Initialized backup with new user key.")
    
    	return b, nil
    }
    
    // ResumeBackup resumes a backup by restoring the Backup object and registering
    // a new callback. Call this to resume backups that have already been
    // initialized. Returns an error if backups have not already been initialized.
    func ResumeBackup(updateBackupCb UpdateBackupFn, container *e2eApi.Container,
    	e2e E2e, session Session, ud UserDiscovery, kv *versioned.KV,
    	rng *fastRNG.StreamGenerator) (*Backup, error) {
    	_, _, _, err := loadBackup(kv)
    	if err != nil {
    		return nil, err
    	}
    
    	b := &Backup{
    		updateBackupCb: updateBackupCb,
    		container:      container,
    		jsonParams:     loadJson(kv),
    		e2e:            e2e,
    		session:        session,
    		ud:             ud,
    		kv:             kv,
    		rng:            rng,
    	}
    
    	// Setting backup trigger in client
    	b.container.SetBackup(b.TriggerBackup)
    
    	jww.INFO.Print("Resumed backup with password loaded from storage.")
    
    	return b, nil
    }
    
    // getKeySaltParams derives a key from the user's password, a generated salt,
    // and the default parameters and return all three.
    func (b *Backup) getKeySaltParams(password string) (
    	key, salt []byte, params backup.Params, err error) {
    	rand := b.rng.GetStream()
    	salt, err = backup.MakeSalt(rand)
    	if err != nil {
    		return
    	}
    	rand.Close()
    
    	params = backup.DefaultParams()
    	key = backup.DeriveKey(password, salt, params)
    
    	return
    }
    
    // TriggerBackup assembles the backup and calls it on the registered backup
    // callback. Does nothing if no encryption key or backup callback is registered.
    // The passed in reason will be printed to the log when the backup is sent. It
    // should be in the past tense. For example, if a contact is deleted, the
    // reason can be "contact deleted" and the log will show:
    //	Triggering backup: contact deleted
    func (b *Backup) TriggerBackup(reason string) {
    	b.mux.RLock()
    	defer b.mux.RUnlock()
    
    	if b == nil || b.kv == nil {
    		jww.ERROR.Printf("TriggerBackup called on unitialized object")
    		return
    	}
    
    	key, salt, params, err := loadBackup(b.kv)
    	if err != nil {
    		jww.ERROR.Printf("Backup Failed: could not load key, salt, and "+
    			"parameters for encrypting backup from storage: %+v", err)
    		return
    	}
    
    	// Grab backup data
    	collatedBackup := b.assembleBackup()
    
    	// Encrypt backup data with user key
    	rand := b.rng.GetStream()
    	encryptedBackup, err := collatedBackup.Encrypt(rand, key, salt, params)
    	if err != nil {
    		jww.FATAL.Panicf("Failed to encrypt backup: %+v", err)
    	}
    	rand.Close()
    
    	jww.INFO.Printf("Backup triggered: %s", reason)
    
    	// Send backup on callback
    	b.mux.RLock()
    	defer b.mux.RUnlock()
    	if b.updateBackupCb != nil {
    		go b.updateBackupCb(encryptedBackup)
    	} else {
    		jww.WARN.Printf("could not call backup callback, stopped...")
    	}
    }
    
    func (b *Backup) AddJson(newJson string) {
    	b.mux.Lock()
    	defer b.mux.Unlock()
    
    	if newJson != b.jsonParams {
    		b.jsonParams = newJson
    		if err := storeJson(newJson, b.kv); err != nil {
    			jww.FATAL.Panicf("Failed to store json: %+v", err)
    		}
    		go b.TriggerBackup("New Json")
    	}
    }
    
    // StopBackup stops the backup processes and deletes the user's password, key,
    // salt, and parameters from storage.
    func (b *Backup) StopBackup() error {
    	b.mux.Lock()
    	defer b.mux.Unlock()
    	b.updateBackupCb = nil
    
    	err := deleteBackup(b.kv)
    	if err != nil {
    		return errors.Errorf(errDeleteCrypto, err)
    	}
    
    	jww.INFO.Print("Stopped backups.")
    
    	return nil
    }
    
    // IsBackupRunning returns true if the backup has been initialized and is
    // running. Returns false if it has been stopped.
    func (b *Backup) IsBackupRunning() bool {
    	b.mux.RLock()
    	defer b.mux.RUnlock()
    	return b.updateBackupCb != nil
    }
    
    // assembleBackup gathers all the contents of the backup and stores them in a
    // backup.Backup. This backup contains:
    //  1. Cryptographic information for the transmission identity
    //  2. Cryptographic information for the reception identity
    //  3. User's UD facts (username, email, phone number)
    //  4. Contact list
    func (b *Backup) assembleBackup() backup.Backup {
    	bu := backup.Backup{
    		TransmissionIdentity:      backup.TransmissionIdentity{},
    		ReceptionIdentity:         backup.ReceptionIdentity{},
    		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{},
    		Contacts:                  backup.Contacts{},
    	}
    
    	// Get registration timestamp
    	bu.RegistrationTimestamp = b.session.GetRegistrationTimestamp().UnixNano()
    
    	// Get registration code; ignore the error because if there is no
    	// registration, then an empty string is returned
    	bu.RegistrationCode, _ = b.session.GetRegCode()
    
    	// Get transmission identity
    	bu.TransmissionIdentity = backup.TransmissionIdentity{
    		RSASigningPrivateKey: b.session.GetTransmissionRSA(),
    		RegistrarSignature:   b.session.GetTransmissionRegistrationValidationSignature(),
    		Salt:                 b.session.GetTransmissionSalt(),
    		ComputedID:           b.session.GetTransmissionID(),
    	}
    
    	// Get reception identity
    	bu.ReceptionIdentity = backup.ReceptionIdentity{
    		RSASigningPrivateKey: b.session.GetReceptionRSA(),
    		RegistrarSignature:   b.session.GetReceptionRegistrationValidationSignature(),
    		Salt:                 b.session.GetReceptionSalt(),
    		ComputedID:           b.session.GetReceptionID(),
    		DHPrivateKey:         b.e2e.GetHistoricalDHPrivkey(),
    		DHPublicKey:          b.e2e.GetHistoricalDHPubkey(),
    	}
    
    	// Get facts
    	if b.ud != nil {
    		bu.UserDiscoveryRegistration.FactList = b.ud.GetFacts()
    	} else {
    		bu.UserDiscoveryRegistration.FactList = fact.FactList{}
    	}
    
    	// Get contacts
    	bu.Contacts.Identities = b.e2e.GetAllPartnerIDs()
    
    	// Add the memoized json params
    	bu.JSONParams = b.jsonParams
    
    	return bu
    }