Skip to content
Snippets Groups Projects
Commit d435922e authored by Jake Taylor's avatar Jake Taylor :lips:
Browse files

Merge branch 'release' into 'master'

Revert "Update store to print changes to the partners list"

See merge request !231
parents 5ff33567 0ad0b7a5
No related branches found
No related tags found
1 merge request!231Revert "Update store to print changes to the partners list"
...@@ -52,6 +52,7 @@ build: ...@@ -52,6 +52,7 @@ build:
except: except:
- tags - tags
script: script:
- go mod vendor -v
- make version - make version
- mkdir -p release - mkdir -p release
# - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./... # - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./...
...@@ -83,8 +84,11 @@ bindings-ios: ...@@ -83,8 +84,11 @@ bindings-ios:
- ios - ios
script: script:
- export PATH=$PATH:$HOME/go/bin - export PATH=$PATH:$HOME/go/bin
- go mod tidy
- rm -rf vendor/
- go build ./...
- go get golang.org/x/mobile/bind
- go install golang.org/x/mobile/cmd/gomobile@latest - go install golang.org/x/mobile/cmd/gomobile@latest
- go get golang.org/x/mobile/cmd/gobind
- gomobile init - gomobile init
- gomobile bind -target ios gitlab.com/elixxir/client/bindings - gomobile bind -target ios gitlab.com/elixxir/client/bindings
- ls - ls
...@@ -103,10 +107,12 @@ bindings-android: ...@@ -103,10 +107,12 @@ bindings-android:
script: script:
- export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin:/android-sdk/platform-tools/ - export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin:/android-sdk/platform-tools/
- export ANDROID_HOME=/android-sdk - export ANDROID_HOME=/android-sdk
# Build the bindings # Build the bindings
- go mod tidy
- rm -rf vendor/
- go build ./...
- go get golang.org/x/mobile/bind
- go install golang.org/x/mobile/cmd/gomobile@latest - go install golang.org/x/mobile/cmd/gomobile@latest
- go get golang.org/x/mobile/cmd/gobind
- gomobile init - gomobile init
- gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings - gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings
artifacts: artifacts:
......
This diff is collapsed.
...@@ -9,6 +9,8 @@ package api ...@@ -9,6 +9,8 @@ package api
import ( import (
"encoding/binary" "encoding/binary"
"math/rand"
"github.com/cloudflare/circl/dh/sidh" "github.com/cloudflare/circl/dh/sidh"
"github.com/pkg/errors" "github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
...@@ -20,7 +22,6 @@ import ( ...@@ -20,7 +22,6 @@ import (
"gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/primitives/fact" "gitlab.com/elixxir/primitives/fact"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
"math/rand"
) )
// RequestAuthenticatedChannel sends a request to another party to establish an // RequestAuthenticatedChannel sends a request to another party to establish an
...@@ -44,6 +45,20 @@ func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact, ...@@ -44,6 +45,20 @@ func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact,
c.storage, c.network) c.storage, c.network)
} }
// ResetSession resets an authenticate channel that already exists
func (c *Client) ResetSession(recipient, me contact.Contact,
message string) (id.Round, error) {
jww.INFO.Printf("ResetSession(%s)", recipient.ID)
if !c.network.GetHealthTracker().IsHealthy() {
return 0, errors.New("Cannot request authenticated channel " +
"creation when the network is not healthy")
}
return auth.ResetSession(recipient, me, c.rng.GetStream(),
c.storage, c.network)
}
// GetAuthRegistrar gets the object which allows the registration of auth // GetAuthRegistrar gets the object which allows the registration of auth
// callbacks // callbacks
func (c *Client) GetAuthRegistrar() interfaces.Auth { func (c *Client) GetAuthRegistrar() interfaces.Auth {
...@@ -76,8 +91,7 @@ func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) (id.Roun ...@@ -76,8 +91,7 @@ func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) (id.Roun
"creation when the network is not healthy") "creation when the network is not healthy")
} }
return auth.ConfirmRequestAuth(recipient, c.rng.GetStream(), return c.auth.ConfirmRequestAuth(recipient)
c.storage, c.network)
} }
// VerifyOwnership checks if the ownership proof on a passed contact matches the // VerifyOwnership checks if the ownership proof on a passed contact matches the
...@@ -165,7 +179,6 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co ...@@ -165,7 +179,6 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co
Source: precan.ID[:], Source: precan.ID[:],
}, me) }, me)
return precan, err return precan, err
} }
......
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/edge"
"gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/client/switchboard"
"gitlab.com/elixxir/comms/client" "gitlab.com/elixxir/comms/client"
"gitlab.com/elixxir/crypto/backup"
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/primitives/version" "gitlab.com/elixxir/primitives/version"
...@@ -69,6 +70,9 @@ type Client struct { ...@@ -69,6 +70,9 @@ type Client struct {
// Event reporting in event.go // Event reporting in event.go
events *eventManager events *eventManager
// Handles the triggering and delivery of backups
backup *interfaces.BackupContainer
} }
// NewClient creates client storage, generates keys, connects, and registers // NewClient creates client storage, generates keys, connects, and registers
...@@ -92,8 +96,7 @@ func NewClient(ndfJSON, storageDir string, password []byte, ...@@ -92,8 +96,7 @@ func NewClient(ndfJSON, storageDir string, password []byte,
protoUser := createNewUser(rngStreamGen, cmixGrp, e2eGrp) protoUser := createNewUser(rngStreamGen, cmixGrp, e2eGrp)
jww.DEBUG.Printf("User generation took: %s", time.Now().Sub(start)) jww.DEBUG.Printf("User generation took: %s", time.Now().Sub(start))
_, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
if err != nil { if err != nil {
return err return err
} }
...@@ -123,8 +126,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, ...@@ -123,8 +126,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string,
protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp) protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp)
_, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, true, "")
cmixGrp, e2eGrp, rngStreamGen, true, "")
if err != nil { if err != nil {
return err return err
} }
...@@ -153,8 +155,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, ...@@ -153,8 +155,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte,
protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix) protoUser := createNewVanityUser(rngStream, cmixGrp, e2eGrp, userIdPrefix)
_, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, _, err = checkVersionAndSetupStorage(def, storageDir, password, protoUser, cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
cmixGrp, e2eGrp, rngStreamGen, false, registrationCode)
if err != nil { if err != nil {
return err return err
} }
...@@ -163,6 +164,55 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, ...@@ -163,6 +164,55 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte,
return nil return nil
} }
// NewClientFromBackup constructs a new Client from an encrypted backup.
// The backup is decrypted using the backupPassphrase. On success a
// successful client creation, the function will return a JSON encoded
// list of the E2E partners contained in the backup and a json-encoded
//string containing parameters stored in the backup.
func NewClientFromBackup(ndfJSON, storageDir string, sessionPassword,
backupPassphrase []byte, backupFileContents []byte) ([]*id.ID, string, error) {
backUp := &backup.Backup{}
err := backUp.Decrypt(string(backupPassphrase), backupFileContents)
if err != nil {
return nil, "", errors.WithMessage(err, "Failed to "+
"unmarshal decrypted client contents.")
}
usr := user.NewUserFromBackup(backUp)
// Parse the NDF
def, err := parseNDF(ndfJSON)
if err != nil {
return nil, "", err
}
cmixGrp, e2eGrp := decodeGroups(def)
// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
// Create storage object.
// Note we do not need registration
storageSess, err := checkVersionAndSetupStorage(def, storageDir, []byte(sessionPassword), usr, cmixGrp, e2eGrp, rngStreamGen, false, backUp.RegistrationCode)
// Set registration values in storage
storageSess.User().SetReceptionRegistrationValidationSignature(backUp.
ReceptionIdentity.RegistrarSignature)
storageSess.User().SetTransmissionRegistrationValidationSignature(backUp.
TransmissionIdentity.RegistrarSignature)
storageSess.User().SetRegistrationTimestamp(backUp.RegistrationTimestamp)
//move the registration state to indicate registered with registration
//on proto client
err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete)
if err != nil {
return nil, "", err
}
return backUp.Contacts.Identities, backUp.JSONParams, nil
}
// OpenClient session, but don't connect to the network or log in // OpenClient session, but don't connect to the network or log in
func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) { func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) {
jww.INFO.Printf("OpenClient()") jww.INFO.Printf("OpenClient()")
...@@ -194,6 +244,7 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) ( ...@@ -194,6 +244,7 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) (
parameters: parameters, parameters: parameters,
clientErrorChannel: make(chan interfaces.ClientError, 1000), clientErrorChannel: make(chan interfaces.ClientError, 1000),
events: newEventManager(), events: newEventManager(),
backup: &interfaces.BackupContainer{},
} }
return c, nil return c, nil
...@@ -228,8 +279,7 @@ func NewProtoClient_Unsafe(ndfJSON, storageDir string, password, ...@@ -228,8 +279,7 @@ func NewProtoClient_Unsafe(ndfJSON, storageDir string, password,
usr := user.NewUserFromProto(protoUser) usr := user.NewUserFromProto(protoUser)
// Set up storage // Set up storage
storageSess, err := checkVersionAndSetupStorage(def, storageDir, password, usr, storageSess, err := checkVersionAndSetupStorage(def, storageDir, password, usr, cmixGrp, e2eGrp, rngStreamGen, false, protoUser.RegCode)
cmixGrp, e2eGrp, rngStreamGen, false, protoUser.RegCode)
if err != nil { if err != nil {
return err return err
} }
...@@ -304,7 +354,8 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie ...@@ -304,7 +354,8 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie
} }
// initialize the auth tracker // initialize the auth tracker
c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests) c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
c.backup.TriggerBackup, parameters.ReplayRequests)
// Add all processes to the followerServices // Add all processes to the followerServices
err = c.registerFollower() err = c.registerFollower()
...@@ -363,7 +414,8 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte, ...@@ -363,7 +414,8 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte,
} }
// initialize the auth tracker // initialize the auth tracker
c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests) c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
c.backup.TriggerBackup, parameters.ReplayRequests)
err = c.registerFollower() err = c.registerFollower()
if err != nil { if err != nil {
...@@ -420,7 +472,8 @@ func LoginWithProtoClient(storageDir string, password []byte, protoClientJSON [] ...@@ -420,7 +472,8 @@ func LoginWithProtoClient(storageDir string, password []byte, protoClientJSON []
} }
// initialize the auth tracker // initialize the auth tracker
c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests) c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
c.backup.TriggerBackup, parameters.ReplayRequests)
err = c.registerFollower() err = c.registerFollower()
if err != nil { if err != nil {
...@@ -641,6 +694,12 @@ func (c *Client) GetNetworkInterface() interfaces.NetworkManager { ...@@ -641,6 +694,12 @@ func (c *Client) GetNetworkInterface() interfaces.NetworkManager {
return c.network return c.network
} }
// GetBackup returns a pointer to the backup container so that the backup can be
// set and triggered.
func (c *Client) GetBackup() *interfaces.BackupContainer {
return c.backup
}
// GetRateLimitParams retrieves the rate limiting parameters. // GetRateLimitParams retrieves the rate limiting parameters.
func (c *Client) GetRateLimitParams() (uint32, uint32, int64) { func (c *Client) GetRateLimitParams() (uint32, uint32, int64) {
rateLimitParams := c.storage.GetBucketParams().Get() rateLimitParams := c.storage.GetBucketParams().Get()
...@@ -684,6 +743,14 @@ func (c *Client) GetNodeRegistrationStatus() (int, int, error) { ...@@ -684,6 +743,14 @@ func (c *Client) GetNodeRegistrationStatus() (int, int, error) {
return numRegistered, len(nodes) - numStale, nil return numRegistered, len(nodes) - numStale, nil
} }
// DeleteRequest will delete a request, agnostic of request type
// for the given partner ID. If no request exists for this
// partner ID an error will be returned.
func (c *Client) DeleteRequest(partnerId *id.ID) error {
jww.DEBUG.Printf("Deleting request for partner ID: %s", partnerId)
return c.GetStorage().Auth().DeleteRequest(partnerId)
}
// DeleteAllRequests clears all requests from client's auth storage. // DeleteAllRequests clears all requests from client's auth storage.
func (c *Client) DeleteAllRequests() error { func (c *Client) DeleteAllRequests() error {
jww.DEBUG.Printf("Deleting all requests") jww.DEBUG.Printf("Deleting all requests")
...@@ -705,7 +772,7 @@ func (c *Client) DeleteReceiveRequests() error { ...@@ -705,7 +772,7 @@ func (c *Client) DeleteReceiveRequests() error {
// DeleteContact is a function which removes a partner from Client's storage // DeleteContact is a function which removes a partner from Client's storage
func (c *Client) DeleteContact(partnerId *id.ID) error { func (c *Client) DeleteContact(partnerId *id.ID) error {
jww.DEBUG.Printf("Deleting contact with ID %s", partnerId) jww.DEBUG.Printf("Deleting contact with ID %s", partnerId)
//get the partner so they can be removed from preiamge store // get the partner so that they can be removed from preimage store
partner, err := c.storage.E2e().GetPartner(partnerId) partner, err := c.storage.E2e().GetPartner(partnerId)
if err != nil { if err != nil {
return errors.WithMessagef(err, "Could not delete %s because "+ return errors.WithMessagef(err, "Could not delete %s because "+
...@@ -720,6 +787,10 @@ func (c *Client) DeleteContact(partnerId *id.ID) error { ...@@ -720,6 +787,10 @@ func (c *Client) DeleteContact(partnerId *id.ID) error {
if err = c.storage.E2e().DeletePartner(partnerId); err != nil { if err = c.storage.E2e().DeletePartner(partnerId); err != nil {
return err return err
} }
// Trigger backup
c.backup.TriggerBackup("contact deleted")
//delete the preimages //delete the preimages
if err = c.storage.GetEdge().Remove(edge.Preimage{ if err = c.storage.GetEdge().Remove(edge.Preimage{
Data: e2ePreimage, Data: e2ePreimage,
...@@ -868,8 +939,7 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) { ...@@ -868,8 +939,7 @@ func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) {
// checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient // checkVersionAndSetupStorage is common code shared by NewClient, NewPrecannedClient and NewVanityClient
// it checks client version and creates a new storage for user data // it checks client version and creates a new storage for user data
func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, func checkVersionAndSetupStorage(def *ndf.NetworkDefinition,
storageDir string, password []byte, storageDir string, password []byte, protoUser user.User,
protoUser user.User,
cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator, cmixGrp, e2eGrp *cyclic.Group, rngStreamGen *fastRNG.StreamGenerator,
isPrecanned bool, registrationCode string) (*storage.Session, error) { isPrecanned bool, registrationCode string) (*storage.Session, error) {
// Get current client version // Get current client version
...@@ -906,6 +976,12 @@ func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, ...@@ -906,6 +976,12 @@ func checkVersionAndSetupStorage(def *ndf.NetworkDefinition,
Source: protoUser.ReceptionID[:], Source: protoUser.ReceptionID[:],
}, protoUser.ReceptionID) }, protoUser.ReceptionID)
storageSess.GetEdge().Add(edge.Preimage{
Data: preimage.GenerateReset(protoUser.ReceptionID),
Type: preimage.Reset,
Source: protoUser.ReceptionID[:],
}, protoUser.ReceptionID)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "Failed to denote state "+ return nil, errors.WithMessage(err, "Failed to denote state "+
"change in session") "change in session")
......
...@@ -73,8 +73,6 @@ func (c *Client) ConstructProtoUerFile() ([]byte, error) { ...@@ -73,8 +73,6 @@ func (c *Client) ConstructProtoUerFile() ([]byte, error) {
RegCode: regCode, RegCode: regCode,
TransmissionRegValidationSig: c.storage.User().GetTransmissionRegistrationValidationSignature(), TransmissionRegValidationSig: c.storage.User().GetTransmissionRegistrationValidationSignature(),
ReceptionRegValidationSig: c.storage.User().GetReceptionRegistrationValidationSignature(), ReceptionRegValidationSig: c.storage.User().GetReceptionRegistrationValidationSignature(),
CmixDhPrivateKey: c.GetStorage().Cmix().GetDHPrivateKey(),
CmixDhPublicKey: c.GetStorage().Cmix().GetDHPublicKey(),
E2eDhPrivateKey: c.GetStorage().E2e().GetDHPrivateKey(), E2eDhPrivateKey: c.GetStorage().E2e().GetDHPrivateKey(),
E2eDhPublicKey: c.GetStorage().E2e().GetDHPublicKey(), E2eDhPublicKey: c.GetStorage().E2e().GetDHPublicKey(),
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
// Use of this source code is governed by a license that can be found in the // // Use of this source code is governed by a license that can be found in the //
// LICENSE file // // LICENSE file //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
package api package api
import ( import (
...@@ -18,7 +19,7 @@ import ( ...@@ -18,7 +19,7 @@ import (
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
) )
// Enum of possible round results to pass back // RoundResult is the enum of possible round results to pass back
type RoundResult uint type RoundResult uint
const ( const (
...@@ -40,7 +41,7 @@ func (rr RoundResult) String() string { ...@@ -40,7 +41,7 @@ func (rr RoundResult) String() string {
} }
} }
// Callback interface which reports the requested rounds. // RoundEventCallback interface which reports the requested rounds.
// Designed such that the caller may decide how much detail they need. // Designed such that the caller may decide how much detail they need.
// allRoundsSucceeded: // allRoundsSucceeded:
// Returns false if any rounds in the round map were unsuccessful. // Returns false if any rounds in the round map were unsuccessful.
...@@ -60,7 +61,7 @@ type historicalRoundsComm interface { ...@@ -60,7 +61,7 @@ type historicalRoundsComm interface {
GetHost(hostId *id.ID) (*connect.Host, bool) GetHost(hostId *id.ID) (*connect.Host, bool)
} }
// Adjudicates on the rounds requested. Checks if they are // GetRoundResults adjudicates on the rounds requested. Checks if they are
// older rounds or in progress rounds. // older rounds or in progress rounds.
func (c *Client) GetRoundResults(roundList []id.Round, timeout time.Duration, func (c *Client) GetRoundResults(roundList []id.Round, timeout time.Duration,
roundCallback RoundEventCallback) error { roundCallback RoundEventCallback) error {
...@@ -168,7 +169,7 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration, ...@@ -168,7 +169,7 @@ func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration,
roundsResults[roundId] = Failed roundsResults[roundId] = Failed
allRoundsSucceeded = false allRoundsSucceeded = false
} }
return continue
} }
allRoundsSucceeded = false allRoundsSucceeded = false
anyRoundTimedOut = true anyRoundTimedOut = true
......
...@@ -34,10 +34,10 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U ...@@ -34,10 +34,10 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U
// CMIX Keygen // CMIX Keygen
var transmissionRsaKey, receptionRsaKey *rsa.PrivateKey var transmissionRsaKey, receptionRsaKey *rsa.PrivateKey
var cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt []byte var e2eKeyBytes, transmissionSalt, receptionSalt []byte
cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt, e2eKeyBytes, transmissionSalt, receptionSalt,
transmissionRsaKey, receptionRsaKey = createDhKeys(rng, cmix, e2e) transmissionRsaKey, receptionRsaKey = createDhKeys(rng, e2e)
// Salt, UID, etc gen // Salt, UID, etc gen
stream := rng.GetStream() stream := rng.GetStream()
...@@ -83,32 +83,17 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U ...@@ -83,32 +83,17 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U
ReceptionSalt: receptionSalt, ReceptionSalt: receptionSalt,
ReceptionRSA: receptionRsaKey, ReceptionRSA: receptionRsaKey,
Precanned: false, Precanned: false,
CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes),
} }
} }
func createDhKeys(rng *fastRNG.StreamGenerator, func createDhKeys(rng *fastRNG.StreamGenerator,
cmix, e2e *cyclic.Group) (cMixKeyBytes, e2eKeyBytes, e2e *cyclic.Group) (e2eKeyBytes,
transmissionSalt, receptionSalt []byte, transmissionSalt, receptionSalt []byte,
transmissionRsaKey, receptionRsaKey *rsa.PrivateKey) { transmissionRsaKey, receptionRsaKey *rsa.PrivateKey) {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(4) wg.Add(3)
go func() {
defer wg.Done()
var err error
// FIXME: Why 256 bits? -- this is spec but not explained, it has
// to do with optimizing operations on one side and still preserves
// decent security -- cite this.
stream := rng.GetStream()
cMixKeyBytes, err = csprng.GenerateInGroup(cmix.GetPBytes(), 256, stream)
stream.Close()
if err != nil {
jww.FATAL.Panicf(err.Error())
}
}()
go func() { go func() {
defer wg.Done() defer wg.Done()
...@@ -186,8 +171,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic. ...@@ -186,8 +171,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.
ReceptionSalt: salt, ReceptionSalt: salt,
Precanned: true, Precanned: true,
E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes),
// NOTE: These are dummy/not used
CmixDhPrivateKey: cmix.NewInt(1),
TransmissionRSA: rsaKey, TransmissionRSA: rsaKey,
ReceptionRSA: rsaKey, ReceptionRSA: rsaKey,
} }
...@@ -196,15 +179,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic. ...@@ -196,15 +179,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.
// createNewVanityUser generates an identity for cMix // createNewVanityUser generates an identity for cMix
// The identity's ReceptionID is not random but starts with the supplied prefix // The identity's ReceptionID is not random but starts with the supplied prefix
func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User { func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User {
// CMIX Keygen
// FIXME: Why 256 bits? -- this is spec but not explained, it has
// to do with optimizing operations on one side and still preserves
// decent security -- cite this.
cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng)
if err != nil {
jww.FATAL.Panicf(err.Error())
}
// DH Keygen // DH Keygen
// FIXME: Why 256 bits? -- this is spec but not explained, it has // FIXME: Why 256 bits? -- this is spec but not explained, it has
// to do with optimizing operations on one side and still preserves // to do with optimizing operations on one side and still preserves
...@@ -311,7 +285,6 @@ func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix stri ...@@ -311,7 +285,6 @@ func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix stri
ReceptionSalt: receptionSalt, ReceptionSalt: receptionSalt,
ReceptionRSA: receptionRsaKey, ReceptionRSA: receptionRsaKey,
Precanned: false, Precanned: false,
CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes), E2eDhPrivateKey: e2e.NewIntFromBytes(e2eKeyBytes),
} }
} }
...@@ -76,7 +76,6 @@ func CompressJpeg(imgBytes []byte) ([]byte, error) { ...@@ -76,7 +76,6 @@ func CompressJpeg(imgBytes []byte) ([]byte, error) {
return newImgBuf.Bytes(), nil return newImgBuf.Bytes(), nil
} }
// CompressJpeg takes a JPEG image in byte format // CompressJpeg takes a JPEG image in byte format
// and compresses it based on desired output size // and compresses it based on desired output size
func CompressJpegForPreview(imgBytes []byte) ([]byte, error) { func CompressJpegForPreview(imgBytes []byte) ([]byte, error) {
......
// Code generated by go generate; DO NOT EDIT. // Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at // This file was generated by robots at
// 2022-02-15 12:19:19.667352 -0600 CST m=+0.033427370 // 2022-06-06 11:33:31.114383 -0500 CDT m=+0.028284450
package api package api
const GITVERSION = `d8832766 made splitSends default to false` const GITVERSION = `6850ced3 log cleanup`
const SEMVER = "4.0.0" const SEMVER = "4.1.0"
const DEPENDENCIES = `module gitlab.com/elixxir/client const DEPENDENCIES = `module gitlab.com/elixxir/client
go 1.17 go 1.17
require ( require (
github.com/cloudflare/circl v1.0.1-0.20211008185751-59b49bc148ce github.com/cloudflare/circl v1.1.0
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
...@@ -19,13 +19,13 @@ require ( ...@@ -19,13 +19,13 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/viper v1.7.1 github.com/spf13/viper v1.7.1
gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228
gitlab.com/elixxir/comms v0.0.4-0.20220214214811-4a1bd320aa45 gitlab.com/elixxir/comms v0.0.4-0.20220603231314-e47e4af13326
gitlab.com/elixxir/crypto v0.0.7-0.20220211185439-4a6d9f41f8ab gitlab.com/elixxir/crypto v0.0.7-0.20220414225314-6f3eb9c073a5
gitlab.com/elixxir/ekv v0.1.6 gitlab.com/elixxir/ekv v0.1.6
gitlab.com/elixxir/primitives v0.0.3-0.20220211185255-f9bc3df21e1d gitlab.com/elixxir/primitives v0.0.3-0.20220323183834-b98f255361b8
gitlab.com/xx_network/comms v0.0.4-0.20220211184526-00dc9cfe8e2e gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac
gitlab.com/xx_network/crypto v0.0.5-0.20220211184244-5803ecaafd59 gitlab.com/xx_network/crypto v0.0.5-0.20220317171841-084640957d71
gitlab.com/xx_network/primitives v0.0.4-0.20220211183913-d6f5fd114a2a gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
google.golang.org/grpc v1.42.0 google.golang.org/grpc v1.42.0
...@@ -53,8 +53,8 @@ require ( ...@@ -53,8 +53,8 @@ require (
github.com/ttacon/libphonenumber v1.2.1 // indirect github.com/ttacon/libphonenumber v1.2.1 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/zeebo/blake3 v0.1.1 // indirect github.com/zeebo/blake3 v0.1.1 // indirect
gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 // indirect gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 // indirect
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
......
...@@ -24,10 +24,9 @@ import ( ...@@ -24,10 +24,9 @@ import (
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/elixxir/crypto/diffieHellman"
cAuth "gitlab.com/elixxir/crypto/e2e/auth" cAuth "gitlab.com/elixxir/crypto/e2e/auth"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/elixxir/primitives/fact" "gitlab.com/elixxir/primitives/fact"
"gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/format"
"gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/id"
) )
func (m *Manager) StartProcesses() (stoppable.Stoppable, error) { func (m *Manager) StartProcesses() (stoppable.Stoppable, error) {
...@@ -108,9 +107,18 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -108,9 +107,18 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
jww.TRACE.Printf("handleRequest ECRPAYLOAD: %v", baseFmt.GetEcrPayload()) jww.TRACE.Printf("handleRequest ECRPAYLOAD: %v", baseFmt.GetEcrPayload())
jww.TRACE.Printf("handleRequest MAC: %v", cmixMsg.GetMac()) jww.TRACE.Printf("handleRequest MAC: %v", cmixMsg.GetMac())
ecrPayload := baseFmt.GetEcrPayload()
success, payload := cAuth.Decrypt(myHistoricalPrivKey, success, payload := cAuth.Decrypt(myHistoricalPrivKey,
partnerPubKey, baseFmt.GetEcrPayload(), partnerPubKey, ecrPayload,
cmixMsg.GetMac(), grp)
if !success {
jww.WARN.Printf("Attempting to decrypt old request packet...")
ecrPayload = append(ecrPayload, baseFmt.GetVersion())
success, payload = cAuth.Decrypt(myHistoricalPrivKey,
partnerPubKey, ecrPayload,
cmixMsg.GetMac(), grp) cmixMsg.GetMac(), grp)
}
if !success { if !success {
jww.WARN.Printf("Received auth request failed " + jww.WARN.Printf("Received auth request failed " +
...@@ -153,6 +161,68 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -153,6 +161,68 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
events.Report(1, "Auth", "RequestReceived", em) events.Report(1, "Auth", "RequestReceived", em)
/*do state edge checks*/ /*do state edge checks*/
// Check if this is a reset, which are valid as of version 1
// Resets happen when our fingerprint is new AND we are
// the latest fingerprint to be added to the list and we already have
// a negotiation or authenticated channel in progress
fp := cAuth.CreateNegotiationFingerprint(partnerPubKey,
partnerSIDHPubKey)
newFP, latest := m.storage.Auth().AddIfNew(partnerID, fp)
resetSession := false
autoConfirm := false
if baseFmt.GetVersion() >= 1 && newFP && latest {
// If we had an existing session and it's new, then yes, we
// want to reset
if _, err := m.storage.E2e().GetPartner(partnerID); err == nil {
jww.INFO.Printf("Resetting session for %s", partnerID)
resetSession = true
// Most likely, we got 2 reset sessions at once, so this
// is a non-fatal error but we will record a warning
// just in case.
err = m.storage.E2e().DeletePartner(partnerID)
if err != nil {
jww.WARN.Printf("Unable to delete channel: %+v",
err)
}
// Also delete any existing request, sent or received
m.storage.Auth().Delete(partnerID)
}
// If we had an existing negotiation open, then it depends
// If we've only received, then user has not confirmed, treat as
// a non-duplicate request, so delete the old one (to cause new
// callback to be called)
rType, _, _, err := m.storage.Auth().GetRequest(partnerID)
if err != nil && rType == auth.Receive {
m.storage.Auth().Delete(partnerID)
}
// If we've already Sent and are now receiving,
// then we attempt auto-confirm as below
// This poses a potential problem if it is truly a session
// reset by the other user, because we may not actually
// autoconfirm based on our public key compared to theirs.
// This could result in a permanently broken association, as
// the other side has attempted to reset it's session and
// can no longer detect a sent request collision, so this side
// cannot ever successfully resend.
// We prevent this by stopping session resets if they
// are called when the other side is in the "Sent" state.
// If the other side is in the "received" state we also block,
// but we could autoconfirm.
// Note that you can still get into this state by one side
// deleting requests. In that case, both sides need to clear
// out all requests and retry negotiation from scratch.
// NOTE: This protocol part could use an overhaul/second look,
// there's got to be a way to do this with far less state
// but this is the spec so we're sticking with it for now.
// If not an existing request, we do nothing.
} else {
jww.WARN.Printf("Version: %d, newFP: %v, latest: %v", baseFmt.GetVersion(),
newFP, latest)
}
// check if a relationship already exists. // check if a relationship already exists.
// if it does and the keys used are the same as we have, send a // if it does and the keys used are the same as we have, send a
// confirmation in case there are state issues. // confirmation in case there are state issues.
...@@ -232,57 +302,13 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -232,57 +302,13 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
// If I do, delete my request on disk // If I do, delete my request on disk
m.storage.Auth().Delete(partnerID) m.storage.Auth().Delete(partnerID)
//process the inner payload // Do the normal, fall out of this if block and
facts, _, err := fact.UnstringifyFactList(
string(requestFmt.msgPayload))
if err != nil {
em := fmt.Sprintf("failed to parse facts and message "+
"from Auth Request: %s", err)
jww.WARN.Print(em)
events.Report(10, "Auth", "RequestError", em)
return
}
// create the contact, note that we use the data // create the contact, note that we use the data
// sent in the request and not any data we had // sent in the request and not any data we had
// already // already
partnerContact := contact.Contact{
ID: partnerID,
DhPubKey: partnerPubKey,
OwnershipProof: copySlice(ownership),
Facts: facts,
}
// add a confirmation to disk
if err = m.storage.Auth().AddReceived(partnerContact,
partnerSIDHPubKey); err != nil {
em := fmt.Sprintf("failed to store contact Auth "+
"Request: %s", err)
jww.WARN.Print(em)
events.Report(10, "Auth", "RequestError", em)
}
// Call ConfirmRequestAuth to send confirmation autoConfirm = true
rngGen := fastRNG.NewStreamGenerator(1, 1,
csprng.NewSystemRNG)
rng := rngGen.GetStream()
rndNum, err := ConfirmRequestAuth(partnerContact,
rng, m.storage, m.net)
if err != nil {
jww.ERROR.Printf("Could not ConfirmRequestAuth: %+v",
err)
return
}
jww.INFO.Printf("ConfirmRequestAuth to %s on round %d",
partnerID, rndNum)
c := partnerContact
cbList := m.confirmCallbacks.Get(c.ID)
for _, cb := range cbList {
ccb := cb.(interfaces.ConfirmCallback)
go ccb(c)
}
return
} }
} }
} }
...@@ -300,8 +326,8 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -300,8 +326,8 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
//create the contact, note that no facts are sent in the payload //create the contact, note that no facts are sent in the payload
c := contact.Contact{ c := contact.Contact{
ID: partnerID, ID: partnerID.DeepCopy(),
DhPubKey: partnerPubKey, DhPubKey: partnerPubKey.DeepCopy(),
OwnershipProof: copySlice(ecrFmt.ownership), OwnershipProof: copySlice(ecrFmt.ownership),
Facts: facts, Facts: facts,
} }
...@@ -317,6 +343,37 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -317,6 +343,37 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
return return
} }
// We autoconfirm anytime we had already sent a request OR we are
// resetting an existing session
var rndNum id.Round
if autoConfirm || resetSession {
// Call ConfirmRequestAuth to send confirmation
rndNum, err = m.confirmRequestAuth(c, true)
if err != nil {
jww.ERROR.Printf("Could not ConfirmRequestAuth: %+v",
err)
return
}
if autoConfirm {
jww.INFO.Printf("ConfirmRequestAuth to %s on round %d",
partnerID, rndNum)
cbList := m.confirmCallbacks.Get(c.ID)
for _, cb := range cbList {
ccb := cb.(interfaces.ConfirmCallback)
go ccb(c)
}
}
if resetSession {
jww.INFO.Printf("Reset Auth %s on round %d",
partnerID, rndNum)
cbList := m.resetCallbacks.Get(c.ID)
for _, cb := range cbList {
ccb := cb.(interfaces.ResetNotificationCallback)
go ccb(c)
}
}
} else {
// fixme: if a crash occurs before or during the calls, the notification // fixme: if a crash occurs before or during the calls, the notification
// will never be sent. // will never be sent.
cbList := m.requestCallbacks.Get(c.ID) cbList := m.requestCallbacks.Get(c.ID)
...@@ -324,6 +381,7 @@ func (m *Manager) handleRequest(cmixMsg format.Message, ...@@ -324,6 +381,7 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
rcb := cb.(interfaces.RequestCallback) rcb := cb.(interfaces.RequestCallback)
go rcb(c) go rcb(c)
} }
}
return return
} }
...@@ -427,6 +485,8 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group, ...@@ -427,6 +485,8 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group,
sr.GetPartner(), err) sr.GetPartner(), err)
} }
m.backupTrigger("received confirmation from request")
//remove the confirm fingerprint //remove the confirm fingerprint
fp := sr.GetFingerprint() fp := sr.GetFingerprint()
if err := m.storage.GetEdge().Remove(edge.Preimage{ if err := m.storage.GetEdge().Remove(edge.Preimage{
...@@ -438,40 +498,7 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group, ...@@ -438,40 +498,7 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group,
sr.GetPartner(), err) sr.GetPartner(), err)
} }
//add the e2e and rekey firngeprints addPreimages(sr.GetPartner(), m.storage)
//e2e
sessionPartner, err := m.storage.E2e().GetPartner(sr.GetPartner())
if err != nil {
jww.FATAL.Panicf("Cannot find %s right after creating: %+v", sr.GetPartner(), err)
}
me := m.storage.GetUser().ReceptionID
m.storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetE2EPreimage(),
Type: preimage.E2e,
Source: sr.GetPartner()[:],
}, me)
//silent (rekey)
m.storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetSilentPreimage(),
Type: preimage.Silent,
Source: sr.GetPartner()[:],
}, me)
// File transfer end
m.storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetFileTransferPreimage(),
Type: preimage.EndFT,
Source: sr.GetPartner()[:],
}, me)
//group Request
m.storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetGroupRequestPreimage(),
Type: preimage.GroupRq,
Source: sr.GetPartner()[:],
}, me)
// delete the in progress negotiation // delete the in progress negotiation
// this undoes the request lock // this undoes the request lock
...@@ -513,7 +540,7 @@ func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat, ...@@ -513,7 +540,7 @@ func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat,
baseFmt, err := unmarshalBaseFormat(cmixMsg.GetContents(), baseFmt, err := unmarshalBaseFormat(cmixMsg.GetContents(),
grp.GetP().ByteLen()) grp.GetP().ByteLen())
if err != nil { if err != nil && baseFmt == nil {
return baseFormat{}, nil, errors.WithMessage(err, "Failed to"+ return baseFormat{}, nil, errors.WithMessage(err, "Failed to"+
" unmarshal auth") " unmarshal auth")
} }
...@@ -524,5 +551,5 @@ func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat, ...@@ -524,5 +551,5 @@ func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat,
} }
partnerPubKey := grp.NewIntFromBytes(baseFmt.pubkey) partnerPubKey := grp.NewIntFromBytes(baseFmt.pubkey)
return baseFmt, partnerPubKey, nil return *baseFmt, partnerPubKey, nil
} }
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
// cmix.go cMix functions for the auth module
package auth
import (
"fmt"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/client/interfaces/params"
"gitlab.com/elixxir/client/interfaces/preimage"
"gitlab.com/elixxir/primitives/format"
"gitlab.com/xx_network/primitives/id"
)
// getMixPayloadSize calculates the payload size of a cMix Message based on the
// total message size.
// TODO: Maybe move this to primitives and export it?
// FIXME: This can only vary per cMix network target, and it could be scoped
// to a Client instance.
func getMixPayloadSize(primeSize int) int {
return 2*primeSize - format.AssociatedDataSize - 1
}
// sendAuthRequest is a helper to send the cMix Message after the request
// is created.
func sendAuthRequest(recipient *id.ID, contents, mac []byte, primeSize int,
fingerprint format.Fingerprint, net interfaces.NetworkManager,
cMixParams params.CMIX, reset bool) (id.Round, error) {
cmixMsg := format.NewMessage(primeSize)
cmixMsg.SetKeyFP(fingerprint)
cmixMsg.SetMac(mac)
cmixMsg.SetContents(contents)
jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
recipient, cmixMsg.Digest())
if reset {
cMixParams.IdentityPreimage = preimage.GenerateReset(recipient)
} else {
cMixParams.IdentityPreimage = preimage.GenerateRequest(recipient)
}
cMixParams.DebugTag = "auth.Request"
round, _, err := net.SendCMIX(cmixMsg, recipient, cMixParams)
if err != nil {
// if the send fails just set it to failed, it will
// but automatically retried
return 0, errors.WithMessagef(err, "Auth Request with %s "+
"(msgDigest: %s) failed to transmit: %+v", recipient,
cmixMsg.Digest(), err)
}
em := fmt.Sprintf("Auth Request with %s (msgDigest: %s) sent"+
" on round %d", recipient, cmixMsg.Digest(), round)
jww.INFO.Print(em)
net.GetEventManager().Report(1, "Auth", "RequestSent", em)
return round, nil
}
...@@ -9,84 +9,96 @@ package auth ...@@ -9,84 +9,96 @@ package auth
import ( import (
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/interfaces/params"
"gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/interfaces/preimage"
"gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage"
"gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/edge"
util "gitlab.com/elixxir/client/storage/utility" util "gitlab.com/elixxir/client/storage/utility"
"gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/diffieHellman"
cAuth "gitlab.com/elixxir/crypto/e2e/auth" cAuth "gitlab.com/elixxir/crypto/e2e/auth"
"gitlab.com/elixxir/primitives/format" "gitlab.com/elixxir/primitives/format"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
"io"
) )
func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, func (m *Manager) ConfirmRequestAuth(partner contact.Contact) (id.Round, error) {
storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
/*edge checking*/ /*edge checking*/
// check that messages can be sent over the network // check that messages can be sent over the network
if !net.GetHealthTracker().IsHealthy() { if !m.net.GetHealthTracker().IsHealthy() {
return 0, errors.New("Cannot confirm authenticated message " + return 0, errors.New("Cannot confirm authenticated message " +
"when the network is not healthy") "when the network is not healthy")
} }
return m.confirmRequestAuth(partner, false)
}
func (m *Manager) confirmRequestAuth(partner contact.Contact, critical bool) (id.Round,
error) {
// Cannot confirm already established channels
if _, err := m.storage.E2e().GetPartner(partner.ID); err == nil {
em := fmt.Sprintf("Cannot ConfirmRequestAuth for %s, "+
"channel already exists. Ignoring", partner.ID)
jww.WARN.Print(em)
m.net.GetEventManager().Report(5, "Auth",
"ConfirmRequestAuthIgnored", em)
//exit
return 0, errors.New(em)
}
// check if the partner has an auth in progress // check if the partner has an auth in progress
// this takes the lock, from this point forward any errors need to // this takes the lock, from this point forward any errors need to
// release the lock // release the lock
storedContact, theirSidhKey, err := storage.Auth().GetReceivedRequest( storedContact, theirSidhKey, err := m.storage.Auth().GetReceivedRequest(
partner.ID) partner.ID)
if err != nil { if err != nil {
return 0, errors.Errorf( return 0, errors.Errorf(
"failed to find a pending Auth Request: %s", "failed to find a pending Auth Request: %s",
err) err)
} }
defer storage.Auth().Done(partner.ID) defer m.storage.Auth().Done(partner.ID)
// verify the passed contact matches what is stored // verify the passed contact matches what is stored
if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 { if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 {
storage.Auth().Done(partner.ID)
return 0, errors.WithMessage(err, return 0, errors.WithMessage(err,
"Pending Auth Request has different pubkey than stored") "Pending Auth Request has different pubkey than stored")
} }
grp := storage.E2e().GetGroup() grp := m.storage.E2e().GetGroup()
/*cryptographic generation*/ /*cryptographic generation*/
// generate ownership proof // generate ownership proof
ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(), ownership := cAuth.MakeOwnershipProof(m.storage.E2e().GetDHPrivateKey(),
partner.DhPubKey, storage.E2e().GetGroup()) partner.DhPubKey, m.storage.E2e().GetGroup())
//generate new keypair rng := m.rng.GetStream()
newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng)
newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp)
// generate new keypair
dhGrp := grp
dhPriv, dhPub := genDHKeys(dhGrp, rng)
sidhVariant := util.GetCompatibleSIDHVariant(theirSidhKey.Variant()) sidhVariant := util.GetCompatibleSIDHVariant(theirSidhKey.Variant())
newSIDHPrivKey := util.NewSIDHPrivateKey(sidhVariant) sidhPriv, sidhPub := util.GenerateSIDHKeyPair(sidhVariant, rng)
newSIDHPubKey := util.NewSIDHPublicKey(sidhVariant)
newSIDHPrivKey.Generate(rng) rng.Close()
newSIDHPrivKey.GeneratePublicKey(newSIDHPubKey)
/*construct message*/ /*construct message*/
// we build the payload before we save because it is technically fallible // we build the payload before we save because it is technically fallible
// which can get into a bricked state if it fails // which can get into a bricked state if it fails
cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen()) cmixMsg := format.NewMessage(m.storage.Cmix().GetGroup().GetP().ByteLen())
baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen()) baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen()) ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
// setup the encrypted payload // setup the encrypted payload
ecrFmt.SetOwnership(ownership) ecrFmt.SetOwnership(ownership)
ecrFmt.SetSidHPubKey(newSIDHPubKey) ecrFmt.SetSidHPubKey(sidhPub)
// confirmation has no custom payload // confirmation has no custom payload
// encrypt the payload // encrypt the payload
ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey, ecrPayload, mac := cAuth.Encrypt(dhPriv, partner.DhPubKey,
ecrFmt.data, grp) ecrFmt.data, grp)
// get the fingerprint from the old ownership proof // get the fingerprint from the old ownership proof
...@@ -95,22 +107,32 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, ...@@ -95,22 +107,32 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
// final construction // final construction
baseFmt.SetEcrPayload(ecrPayload) baseFmt.SetEcrPayload(ecrPayload)
baseFmt.SetPubKey(newPubKey) baseFmt.SetPubKey(dhPub)
cmixMsg.SetKeyFP(fp) cmixMsg.SetKeyFP(fp)
cmixMsg.SetMac(mac) cmixMsg.SetMac(mac)
cmixMsg.SetContents(baseFmt.Marshal()) cmixMsg.SetContents(baseFmt.Marshal())
jww.TRACE.Printf("SendConfirm cMixMsg contents: %v",
cmixMsg.GetContents())
jww.TRACE.Printf("SendConfirm PARTNERPUBKEY: %v",
partner.DhPubKey.Bytes())
jww.TRACE.Printf("SendConfirm MYPUBKEY: %v", dhPub.Bytes())
jww.TRACE.Printf("SendConfirm ECRPAYLOAD: %v", baseFmt.GetEcrPayload())
jww.TRACE.Printf("SendConfirm MAC: %v", mac)
// fixme: channel can get into a bricked state if the first save occurs and // fixme: channel can get into a bricked state if the first save occurs and
// the second does not or the two occur and the storage into critical // the second does not or the two occur and the storage into critical
// messages does not occur // messages does not occur
events := net.GetEventManager() events := m.net.GetEventManager()
// create local relationship // create local relationship
p := storage.E2e().GetE2ESessionParams() p := m.storage.E2e().GetE2ESessionParams()
if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey, if err := m.storage.E2e().AddPartner(partner.ID, partner.DhPubKey,
newPrivKey, theirSidhKey, newSIDHPrivKey, dhPriv, theirSidhKey, sidhPriv,
p, p); err != nil { p, p); err != nil {
em := fmt.Sprintf("Failed to create channel with partner (%s) "+ em := fmt.Sprintf("Failed to create channel with partner (%s) "+
"on confirmation, this is likley a replay: %s", "on confirmation, this is likley a replay: %s",
...@@ -119,40 +141,9 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, ...@@ -119,40 +141,9 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
events.Report(10, "Auth", "SendConfirmError", em) events.Report(10, "Auth", "SendConfirmError", em)
} }
//add the preimages m.backupTrigger("confirmed authenticated channel")
sessionPartner, err := storage.E2e().GetPartner(partner.ID)
if err != nil {
jww.FATAL.Panicf("Cannot find %s right after creating: %+v", partner.ID, err)
}
me := storage.GetUser().ReceptionID
//e2e
storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetE2EPreimage(),
Type: preimage.E2e,
Source: partner.ID[:],
}, me)
//slient (rekey)
storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetSilentPreimage(),
Type: preimage.Silent,
Source: partner.ID[:],
}, me)
// File transfer end
storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetFileTransferPreimage(),
Type: preimage.EndFT,
Source: partner.ID[:],
}, me)
//group Request addPreimages(partner.ID, m.storage)
storage.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetGroupRequestPreimage(),
Type: preimage.GroupRq,
Source: partner.ID[:],
}, me)
// delete the in progress negotiation // delete the in progress negotiation
// this unlocks the request lock // this unlocks the request lock
...@@ -170,15 +161,28 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, ...@@ -170,15 +161,28 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
param.IdentityPreimage = preimg param.IdentityPreimage = preimg
param.DebugTag = "auth.Confirm" param.DebugTag = "auth.Confirm"
/*send message*/ /*send message*/
round, _, err := net.SendCMIX(cmixMsg, partner.ID, param) if critical {
m.storage.GetCriticalRawMessages().AddProcessing(cmixMsg,
partner.ID)
}
round, _, err := m.net.SendCMIX(cmixMsg, partner.ID, param)
if err != nil { if err != nil {
// if the send fails just set it to failed, it will but automatically // if the send fails just set it to failed, it will but automatically
// retried // retried
jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+ jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+
"to transmit: %+v", partner.ID, cmixMsg.Digest(), err) "to transmit: %+v", partner.ID, cmixMsg.Digest(), err)
if critical {
m.storage.GetCriticalRawMessages().Failed(cmixMsg,
partner.ID)
}
return 0, errors.WithMessage(err, "Auth Confirm Failed to transmit") return 0, errors.WithMessage(err, "Auth Confirm Failed to transmit")
} }
if critical {
m.storage.GetCriticalRawMessages().Succeeded(cmixMsg,
partner.ID)
}
em := fmt.Sprintf("Confirm Request with %s (msgDigest: %s) sent on round %d", em := fmt.Sprintf("Confirm Request with %s (msgDigest: %s) sent on round %d",
partner.ID, cmixMsg.Digest(), round) partner.ID, cmixMsg.Digest(), round)
jww.INFO.Print(em) jww.INFO.Print(em)
...@@ -186,3 +190,65 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, ...@@ -186,3 +190,65 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
return round, nil return round, nil
} }
func addPreimages(partner *id.ID, store *storage.Session) {
// add the preimages
sessionPartner, err := store.E2e().GetPartner(partner)
if err != nil {
jww.FATAL.Panicf("Cannot find %s right after creating: %+v",
partner, err)
}
// Delete any known pre-existing edges for this partner
existingEdges, _ := store.GetEdge().Get(partner)
for i := range existingEdges {
delete := true
switch existingEdges[i].Type {
case preimage.E2e:
case preimage.Silent:
case preimage.EndFT:
case preimage.GroupRq:
default:
delete = false
}
if delete {
err = store.GetEdge().Remove(existingEdges[i], partner)
if err != nil {
jww.ERROR.Printf(
"Unable to delete %s edge for %s: %v",
existingEdges[i].Type, partner, err)
}
}
}
me := store.GetUser().ReceptionID
// e2e
store.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetE2EPreimage(),
Type: preimage.E2e,
Source: partner[:],
}, me)
// silent (rekey)
store.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetSilentPreimage(),
Type: preimage.Silent,
Source: partner[:],
}, me)
// File transfer end
store.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetFileTransferPreimage(),
Type: preimage.EndFT,
Source: partner[:],
}, me)
// group Request
store.GetEdge().Add(edge.Preimage{
Data: sessionPartner.GetGroupRequestPreimage(),
Type: preimage.GroupRq,
Source: partner[:],
}, me)
}
...@@ -17,26 +17,33 @@ import ( ...@@ -17,26 +17,33 @@ import (
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
) )
const requestFmtVersion = 1
//Basic Format////////////////////////////////////////////////////////////////// //Basic Format//////////////////////////////////////////////////////////////////
type baseFormat struct { type baseFormat struct {
data []byte data []byte
pubkey []byte pubkey []byte
ecrPayload []byte ecrPayload []byte
version []byte
} }
func newBaseFormat(payloadSize, pubkeySize int) baseFormat { func newBaseFormat(payloadSize, pubkeySize int) baseFormat {
total := pubkeySize + sidhinterface.PubKeyByteSize + 1 total := pubkeySize
// Size of sidh pubkey
total += sidhinterface.PubKeyByteSize + 1
// Size of version
total += 1
if payloadSize < total { if payloadSize < total {
jww.FATAL.Panicf("Size of baseFormat is too small (%d), must be big "+ jww.FATAL.Panicf("Size of baseFormat is too small (%d), must be big "+
"enough to contain public key (%d) and sidh key (%d)"+ "enough to contain public key (%d) and sidh key (%d)"+
"which totals to %d", payloadSize, pubkeySize, "and version which totals to %d", payloadSize,
sidhinterface.PubKeyByteSize+1, total) pubkeySize, sidhinterface.PubKeyByteSize+1, total)
} }
jww.INFO.Printf("Empty Space RequestAuth: %d", payloadSize-total) jww.INFO.Printf("Empty Space RequestAuth: %d", payloadSize-total)
f := buildBaseFormat(make([]byte, payloadSize), pubkeySize) f := buildBaseFormat(make([]byte, payloadSize), pubkeySize)
f.version[0] = requestFmtVersion
return f return f
} }
...@@ -47,25 +54,39 @@ func buildBaseFormat(data []byte, pubkeySize int) baseFormat { ...@@ -47,25 +54,39 @@ func buildBaseFormat(data []byte, pubkeySize int) baseFormat {
start := 0 start := 0
end := pubkeySize end := pubkeySize
f.pubkey = f.data[:end] f.pubkey = f.data[start:end]
start = end start = end
f.ecrPayload = f.data[start:] end = len(f.data) - 1
f.ecrPayload = f.data[start:end]
f.version = f.data[end:]
return f return f
} }
func unmarshalBaseFormat(b []byte, pubkeySize int) (baseFormat, error) { func unmarshalBaseFormat(b []byte, pubkeySize int) (*baseFormat, error) {
if len(b) < pubkeySize { if len(b) < pubkeySize {
return baseFormat{}, errors.New("Received baseFormat too small") return nil, errors.New("Received baseFormat too small")
}
bfmt := buildBaseFormat(b, pubkeySize)
version := bfmt.GetVersion()
if version != requestFmtVersion {
return &bfmt, errors.Errorf(
"Unknown baseFormat version: %d", version)
} }
return buildBaseFormat(b, pubkeySize), nil return &bfmt, nil
} }
func (f baseFormat) Marshal() []byte { func (f baseFormat) Marshal() []byte {
return f.data return f.data
} }
func (f baseFormat) GetVersion() byte {
return f.version[0]
}
func (f baseFormat) GetPubKey(grp *cyclic.Group) *cyclic.Int { func (f baseFormat) GetPubKey(grp *cyclic.Group) *cyclic.Int {
return grp.NewIntFromBytes(f.pubkey) return grp.NewIntFromBytes(f.pubkey)
} }
......
...@@ -9,20 +9,26 @@ package auth ...@@ -9,20 +9,26 @@ package auth
import ( import (
"bytes" "bytes"
sidhinterface "gitlab.com/elixxir/client/interfaces/sidh"
"gitlab.com/xx_network/primitives/id"
"math/rand" "math/rand"
"reflect" "reflect"
"testing" "testing"
sidhinterface "gitlab.com/elixxir/client/interfaces/sidh"
"gitlab.com/xx_network/primitives/id"
) )
// Tests newBaseFormat // Tests newBaseFormat
func TestNewBaseFormat(t *testing.T) { func TestNewBaseFormat(t *testing.T) {
// Construct message // Construct message
pubKeySize := 256 pubKeySize := 256
payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 1 payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 2
baseMsg := newBaseFormat(payloadSize, pubKeySize) baseMsg := newBaseFormat(payloadSize, pubKeySize)
if baseMsg.GetVersion() != requestFmtVersion {
t.Errorf("Incorrect version: %d, expect %d",
baseMsg.GetVersion(), requestFmtVersion)
}
// Check that the base format was constructed properly // Check that the base format was constructed properly
if !bytes.Equal(baseMsg.pubkey, make([]byte, pubKeySize)) { if !bytes.Equal(baseMsg.pubkey, make([]byte, pubKeySize)) {
t.Errorf("NewBaseFormat error: "+ t.Errorf("NewBaseFormat error: "+
...@@ -31,7 +37,7 @@ func TestNewBaseFormat(t *testing.T) { ...@@ -31,7 +37,7 @@ func TestNewBaseFormat(t *testing.T) {
"\n\tReceived: %v", make([]byte, pubKeySize), baseMsg.pubkey) "\n\tReceived: %v", make([]byte, pubKeySize), baseMsg.pubkey)
} }
expectedEcrPayloadSize := payloadSize - (pubKeySize) expectedEcrPayloadSize := payloadSize - (pubKeySize) - 1
if !bytes.Equal(baseMsg.ecrPayload, make([]byte, expectedEcrPayloadSize)) { if !bytes.Equal(baseMsg.ecrPayload, make([]byte, expectedEcrPayloadSize)) {
t.Errorf("NewBaseFormat error: "+ t.Errorf("NewBaseFormat error: "+
"Unexpected payload field in base format."+ "Unexpected payload field in base format."+
...@@ -56,7 +62,7 @@ func TestNewBaseFormat(t *testing.T) { ...@@ -56,7 +62,7 @@ func TestNewBaseFormat(t *testing.T) {
func TestBaseFormat_SetGetPubKey(t *testing.T) { func TestBaseFormat_SetGetPubKey(t *testing.T) {
// Construct message // Construct message
pubKeySize := 256 pubKeySize := 256
payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 1 payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 2
baseMsg := newBaseFormat(payloadSize, pubKeySize) baseMsg := newBaseFormat(payloadSize, pubKeySize)
// Test setter // Test setter
...@@ -88,7 +94,7 @@ func TestBaseFormat_SetGetEcrPayload(t *testing.T) { ...@@ -88,7 +94,7 @@ func TestBaseFormat_SetGetEcrPayload(t *testing.T) {
baseMsg := newBaseFormat(payloadSize, pubKeySize) baseMsg := newBaseFormat(payloadSize, pubKeySize)
// Test setter // Test setter
ecrPayloadSize := payloadSize - (pubKeySize) ecrPayloadSize := payloadSize - (pubKeySize) - 1
ecrPayload := newPayload(ecrPayloadSize, "ecrPayload") ecrPayload := newPayload(ecrPayloadSize, "ecrPayload")
baseMsg.SetEcrPayload(ecrPayload) baseMsg.SetEcrPayload(ecrPayload)
if !bytes.Equal(ecrPayload, baseMsg.ecrPayload) { if !bytes.Equal(ecrPayload, baseMsg.ecrPayload) {
...@@ -123,7 +129,7 @@ func TestBaseFormat_MarshalUnmarshal(t *testing.T) { ...@@ -123,7 +129,7 @@ func TestBaseFormat_MarshalUnmarshal(t *testing.T) {
pubKeySize := 256 pubKeySize := 256
payloadSize := (pubKeySize + sidhinterface.PubKeyByteSize) * 2 payloadSize := (pubKeySize + sidhinterface.PubKeyByteSize) * 2
baseMsg := newBaseFormat(payloadSize, pubKeySize) baseMsg := newBaseFormat(payloadSize, pubKeySize)
ecrPayloadSize := payloadSize - (pubKeySize) ecrPayloadSize := payloadSize - (pubKeySize) - 1
ecrPayload := newPayload(ecrPayloadSize, "ecrPayload") ecrPayload := newPayload(ecrPayloadSize, "ecrPayload")
baseMsg.SetEcrPayload(ecrPayload) baseMsg.SetEcrPayload(ecrPayload)
grp := getGroup() grp := getGroup()
...@@ -145,10 +151,10 @@ func TestBaseFormat_MarshalUnmarshal(t *testing.T) { ...@@ -145,10 +151,10 @@ func TestBaseFormat_MarshalUnmarshal(t *testing.T) {
"Could not unmarshal into baseFormat: %v", err) "Could not unmarshal into baseFormat: %v", err)
} }
if !reflect.DeepEqual(newMsg, baseMsg) { if !reflect.DeepEqual(*newMsg, baseMsg) {
t.Errorf("unmarshalBaseFormat() error: "+ t.Errorf("unmarshalBaseFormat() error: "+
"Unmarshalled message does not match originally marshalled message."+ "Unmarshalled message does not match originally marshalled message."+
"\n\tExpected: %v\n\tRecieved: %v", baseMsg, newMsg) "\n\tExpected: %v\n\tRecieved: %v", baseMsg, *newMsg)
} }
// Unmarshal error test: Invalid size parameter // Unmarshal error test: Invalid size parameter
......
...@@ -12,29 +12,37 @@ import ( ...@@ -12,29 +12,37 @@ import (
"gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/message"
"gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage"
"gitlab.com/elixxir/client/switchboard" "gitlab.com/elixxir/client/switchboard"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
) )
type Manager struct { type Manager struct {
requestCallbacks *callbackMap requestCallbacks *callbackMap
confirmCallbacks *callbackMap confirmCallbacks *callbackMap
resetCallbacks *callbackMap
rawMessages chan message.Receive rawMessages chan message.Receive
storage *storage.Session storage *storage.Session
net interfaces.NetworkManager net interfaces.NetworkManager
rng *fastRNG.StreamGenerator
backupTrigger interfaces.TriggerBackup
replayRequests bool replayRequests bool
} }
func NewManager(sw interfaces.Switchboard, storage *storage.Session, func NewManager(sw interfaces.Switchboard, storage *storage.Session,
net interfaces.NetworkManager, replayRequests bool) *Manager { net interfaces.NetworkManager, rng *fastRNG.StreamGenerator,
backupTrigger interfaces.TriggerBackup, replayRequests bool) *Manager {
m := &Manager{ m := &Manager{
requestCallbacks: newCallbackMap(), requestCallbacks: newCallbackMap(),
confirmCallbacks: newCallbackMap(), confirmCallbacks: newCallbackMap(),
resetCallbacks: newCallbackMap(),
rawMessages: make(chan message.Receive, 1000), rawMessages: make(chan message.Receive, 1000),
storage: storage, storage: storage,
net: net, net: net,
rng: rng,
backupTrigger: backupTrigger,
replayRequests: replayRequests, replayRequests: replayRequests,
} }
...@@ -93,6 +101,11 @@ func (m *Manager) RemoveSpecificConfirmCallback(id *id.ID) { ...@@ -93,6 +101,11 @@ func (m *Manager) RemoveSpecificConfirmCallback(id *id.ID) {
m.confirmCallbacks.RemoveSpecific(id) m.confirmCallbacks.RemoveSpecific(id)
} }
// Adds a general callback to be used on auth session renegotiations.
func (m *Manager) AddResetNotificationCallback(cb interfaces.ResetNotificationCallback) {
m.resetCallbacks.AddOverride(cb)
}
// ReplayRequests will iterate through all pending contact requests and resend them // ReplayRequests will iterate through all pending contact requests and resend them
// to the desired contact. // to the desired contact.
func (m *Manager) ReplayRequests() { func (m *Manager) ReplayRequests() {
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
package auth package auth
import ( import (
"fmt" "io"
"strings"
"github.com/cloudflare/circl/dh/sidh" "github.com/cloudflare/circl/dh/sidh"
"github.com/pkg/errors" "github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
...@@ -24,25 +26,50 @@ import ( ...@@ -24,25 +26,50 @@ import (
"gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/elixxir/crypto/diffieHellman"
cAuth "gitlab.com/elixxir/crypto/e2e/auth" cAuth "gitlab.com/elixxir/crypto/e2e/auth"
"gitlab.com/elixxir/primitives/format"
"gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id"
"io"
"strings"
) )
const terminator = ";" const terminator = ";"
func RequestAuth(partner, me contact.Contact, rng io.Reader, func RequestAuth(partner, me contact.Contact, rng io.Reader,
storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) { storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
/*edge checks generation*/ // check that an authenticated channel does not already exist
// check that an authenticated channel does not already exists
if _, err := storage.E2e().GetPartner(partner.ID); err == nil || if _, err := storage.E2e().GetPartner(partner.ID); err == nil ||
!strings.Contains(err.Error(), e2e.NoPartnerErrorStr) { !strings.Contains(err.Error(), e2e.NoPartnerErrorStr) {
return 0, errors.Errorf("Authenticated channel already " + return 0, errors.Errorf("Authenticated channel already " +
"established with partner") "established with partner")
} }
return requestAuth(partner, me, rng, false, storage, net)
}
func ResetSession(partner, me contact.Contact, rng io.Reader,
storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
// Delete authenticated channel if it exists.
if err := storage.E2e().DeletePartner(partner.ID); err != nil {
jww.WARN.Printf("Unable to delete partner when "+
"resetting session: %+v", err)
} else {
// Delete any stored sent/received requests
storage.Auth().Delete(partner.ID)
}
rqType, _, _, err := storage.Auth().GetRequest(partner.ID)
if err == nil && rqType == auth.Sent {
return 0, errors.New("Cannot reset a session after " +
"sending request, caller must resend request instead")
}
// Try to initiate a clean session request
return requestAuth(partner, me, rng, true, storage, net)
}
// requestAuth internal helper
func requestAuth(partner, me contact.Contact, rng io.Reader, reset bool,
storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
/*edge checks generation*/
// check that the request is being sent from the proper ID // check that the request is being sent from the proper ID
if !me.ID.Cmp(storage.GetUser().ReceptionID) { if !me.ID.Cmp(storage.GetUser().ReceptionID) {
return 0, errors.Errorf("Authenticated channel request " + return 0, errors.Errorf("Authenticated channel request " +
...@@ -54,98 +81,81 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader, ...@@ -54,98 +81,81 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader,
//lookup if an ongoing request is occurring //lookup if an ongoing request is occurring
rqType, sr, _, err := storage.Auth().GetRequest(partner.ID) rqType, sr, _, err := storage.Auth().GetRequest(partner.ID)
if err != nil && !strings.Contains(err.Error(), auth.NoRequest) {
if err == nil {
if rqType == auth.Receive {
return 0, errors.Errorf("Cannot send a request after " +
"receiving a request")
} else if rqType == auth.Sent {
resend = true
} else {
return 0, errors.Errorf("Cannot send a request after "+
" a stored request with unknown rqType: %d", rqType)
}
} else if !strings.Contains(err.Error(), auth.NoRequest) {
return 0, errors.WithMessage(err, return 0, errors.WithMessage(err,
"Cannot send a request after receiving unknown error "+ "Cannot send a request after receiving unknown error "+
"on requesting contact status") "on requesting contact status")
} else if err == nil {
switch rqType {
case auth.Receive:
if reset {
storage.Auth().DeleteRequest(partner.ID)
} else {
return 0, errors.Errorf("Cannot send a " +
"request after receiving a request")
}
case auth.Sent:
resend = true
default:
return 0, errors.Errorf("Cannot send a request after "+
"a stored request with unknown rqType: %d",
rqType)
} }
grp := storage.E2e().GetGroup()
/*generate embedded message structures and check payload*/
cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen())
baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
requestFmt, err := newRequestFormat(ecrFmt)
if err != nil {
return 0, errors.Errorf("failed to make request format: %+v", err)
} }
//check the payload fits
facts := me.Facts.Stringify()
msgPayload := facts + terminator
msgPayloadBytes := []byte(msgPayload)
/*cryptographic generation*/ /*cryptographic generation*/
var newPrivKey, newPubKey *cyclic.Int var dhPriv, dhPub *cyclic.Int
var sidHPrivKeyA *sidh.PrivateKey var sidhPriv *sidh.PrivateKey
var sidHPubKeyA *sidh.PublicKey var sidhPub *sidh.PublicKey
// in this case we have an ongoing request so we can resend the extant // NOTE: E2E group is the group used for DH key exchange, not cMix
// request dhGrp := storage.E2e().GetGroup()
if resend { // origin DH Priv key is the DH Key corresponding to the public key
newPrivKey = sr.GetMyPrivKey() // registered with user discovery
newPubKey = sr.GetMyPubKey() originDHPrivKey := storage.E2e().GetDHPrivateKey()
sidHPrivKeyA = sr.GetMySIDHPrivKey()
sidHPubKeyA = sr.GetMySIDHPubKey()
//in this case it is a new request and we must generate new keys
} else {
//generate new keypair
newPrivKey = diffieHellman.GeneratePrivateKey(256, grp, rng)
newPubKey = diffieHellman.GeneratePublicKey(newPrivKey, grp)
sidHPrivKeyA = util.NewSIDHPrivateKey(sidh.KeyVariantSidhA)
sidHPubKeyA = util.NewSIDHPublicKey(sidh.KeyVariantSidhA)
if err = sidHPrivKeyA.Generate(rng); err != nil { // If we are resending (valid sent request), reuse those keys
return 0, errors.WithMessagef(err, "RequestAuth: "+ if resend {
"could not generate SIDH private key") dhPriv = sr.GetMyPrivKey()
} dhPub = sr.GetMyPubKey()
sidHPrivKeyA.GeneratePublicKey(sidHPubKeyA) sidhPriv = sr.GetMySIDHPrivKey()
sidhPub = sr.GetMySIDHPubKey()
} else {
dhPriv, dhPub = genDHKeys(dhGrp, rng)
sidhPriv, sidhPub = util.GenerateSIDHKeyPair(
sidh.KeyVariantSidhA, rng)
} }
if len(msgPayloadBytes) > requestFmt.MsgPayloadLen() { jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", dhPub.Bytes())
return 0, errors.Errorf("Combined message longer than space "+ jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v",
"available in payload; available: %v, length: %v", partner.DhPubKey.Bytes())
requestFmt.MsgPayloadLen(), len(msgPayloadBytes))
}
//generate ownership proof cMixPrimeSize := storage.Cmix().GetGroup().GetP().ByteLen()
ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(), cMixPayloadSize := getMixPayloadSize(cMixPrimeSize)
partner.DhPubKey, storage.E2e().GetGroup())
jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", newPubKey.Bytes()) sender := storage.GetUser().ReceptionID
jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v", partner.DhPubKey.Bytes())
/*encrypt payload*/ //generate ownership proof
requestFmt.SetID(storage.GetUser().ReceptionID) ownership := cAuth.MakeOwnershipProof(originDHPrivKey, partner.DhPubKey,
requestFmt.SetMsgPayload(msgPayloadBytes) dhGrp)
ecrFmt.SetOwnership(ownership)
ecrFmt.SetSidHPubKey(sidHPubKeyA)
ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey,
ecrFmt.data, grp)
confirmFp := cAuth.MakeOwnershipProofFP(ownership) confirmFp := cAuth.MakeOwnershipProofFP(ownership)
// cMix fingerprint so the recipient can recognize this is a
// request message.
requestfp := cAuth.MakeRequestFingerprint(partner.DhPubKey) requestfp := cAuth.MakeRequestFingerprint(partner.DhPubKey)
/*construct message*/ // My fact data so we can display in the interface.
baseFmt.SetEcrPayload(ecrPayload) msgPayload := []byte(me.Facts.Stringify() + terminator)
baseFmt.SetPubKey(newPubKey)
cmixMsg.SetKeyFP(requestfp) // Create the request packet.
cmixMsg.SetMac(mac) request, mac, err := createRequestAuth(sender, msgPayload, ownership,
cmixMsg.SetContents(baseFmt.Marshal()) dhPriv, dhPub, partner.DhPubKey, sidhPub,
dhGrp, cMixPayloadSize)
if err != nil {
return 0, err
}
contents := request.Marshal()
storage.GetEdge().Add(edge.Preimage{ storage.GetEdge().Add(edge.Preimage{
Data: preimage.Generate(confirmFp[:], preimage.Confirm), Data: preimage.Generate(confirmFp[:], preimage.Confirm),
...@@ -153,40 +163,77 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader, ...@@ -153,40 +163,77 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader,
Source: partner.ID[:], Source: partner.ID[:],
}, me.ID) }, me.ID)
jww.TRACE.Printf("RequestAuth ECRPAYLOAD: %v", baseFmt.GetEcrPayload()) jww.TRACE.Printf("RequestAuth ECRPAYLOAD: %v", request.GetEcrPayload())
jww.TRACE.Printf("RequestAuth MAC: %v", mac) jww.TRACE.Printf("RequestAuth MAC: %v", mac)
/*store state*/ /*store state*/
//fixme: channel is bricked if the first store succedes but the second fails //fixme: channel is bricked if the first store succedes but the second
//store the in progress auth // fails
//store the in progress auth if this is not a resend.
if !resend { if !resend {
err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, err = storage.Auth().AddSent(partner.ID, partner.DhPubKey,
newPubKey, sidHPrivKeyA, sidHPubKeyA, confirmFp) dhPriv, dhPub, sidhPriv, sidhPub, confirmFp)
if err != nil { if err != nil {
return 0, errors.Errorf("Failed to store auth request: %s", err) return 0, errors.Errorf(
"Failed to store auth request: %s", err)
} }
} }
jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s", cMixParams := params.GetDefaultCMIX()
partner.ID, cmixMsg.Digest()) rndID, err := sendAuthRequest(partner.ID, contents, mac, cMixPrimeSize,
requestfp, net, cMixParams, reset)
return rndID, err
}
/*send message*/ // genDHKeys is a short helper to generate a Diffie-Helman Keypair
p := params.GetDefaultCMIX() func genDHKeys(dhGrp *cyclic.Group, csprng io.Reader) (priv, pub *cyclic.Int) {
p.IdentityPreimage = preimage.GenerateRequest(partner.ID) numBytes := len(dhGrp.GetPBytes())
p.DebugTag = "auth.Request" newPrivKey := diffieHellman.GeneratePrivateKey(numBytes, dhGrp, csprng)
round, _, err := net.SendCMIX(cmixMsg, partner.ID, p) newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, dhGrp)
return newPrivKey, newPubKey
}
// createRequestAuth Creates the request packet, including encrypting the
// required parts of it.
func createRequestAuth(sender *id.ID, payload, ownership []byte, myDHPriv,
myDHPub, theirDHPub *cyclic.Int, mySIDHPub *sidh.PublicKey,
dhGrp *cyclic.Group, cMixSize int) (*baseFormat, []byte, error) {
/*generate embedded message structures and check payload*/
dhPrimeSize := dhGrp.GetP().ByteLen()
// FIXME: This base -> ecr -> request structure is a little wonky.
// We should refactor so that is is more direct.
// I recommend we move to a request object that takes:
// sender, dhPub, sidhPub, ownershipProof, payload
// with a Marshal/Unmarshal that takes the Dh/grp needed to gen
// the session key and encrypt or decrypt.
// baseFmt wraps ecrFmt. ecrFmt is encrypted
baseFmt := newBaseFormat(cMixSize, dhPrimeSize)
// ecrFmt wraps requestFmt
ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
requestFmt, err := newRequestFormat(ecrFmt)
if err != nil { if err != nil {
// if the send fails just set it to failed, it will return nil, nil, errors.Errorf("failed to make request format: %+v", err)
// but automatically retried }
return 0, errors.WithMessagef(err, "Auth Request with %s "+
"(msgDigest: %s) failed to transmit: %+v", partner.ID, if len(payload) > requestFmt.MsgPayloadLen() {
cmixMsg.Digest(), err) return nil, nil, errors.Errorf(
"Combined message longer than space "+
"available in payload; available: %v, length: %v",
requestFmt.MsgPayloadLen(), len(payload))
} }
em := fmt.Sprintf("Auth Request with %s (msgDigest: %s) sent"+ /*encrypt payload*/
" on round %d", partner.ID, cmixMsg.Digest(), round) requestFmt.SetID(sender)
jww.INFO.Print(em) requestFmt.SetMsgPayload(payload)
net.GetEventManager().Report(1, "Auth", "RequestSent", em) ecrFmt.SetOwnership(ownership)
ecrFmt.SetSidHPubKey(mySIDHPub)
ecrPayload, mac := cAuth.Encrypt(myDHPriv, theirDHPub, ecrFmt.data,
dhGrp)
/*construct message*/
baseFmt.SetEcrPayload(ecrPayload)
baseFmt.SetPubKey(myDHPub)
return round, nil return &baseFmt, mac, nil
} }
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// 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"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/api"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/client/storage"
"gitlab.com/elixxir/crypto/backup"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/primitives/id"
)
// 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
mux sync.RWMutex
// Client structures
client *api.Client
store *storage.Session
backupContainer *interfaces.BackupContainer
rng *fastRNG.StreamGenerator
jsonParams string
}
// 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,
c *api.Client) (*Backup, error) {
return initializeBackup(
password, updateBackupCb, c, c.GetStorage(), c.GetBackup(), c.GetRng())
}
// initializeBackup is a helper function that takes in all the fields for Backup
// as parameters for easier testing.
func initializeBackup(password string, updateBackupCb UpdateBackupFn,
c *api.Client, store *storage.Session,
backupContainer *interfaces.BackupContainer, rng *fastRNG.StreamGenerator) (
*Backup, error) {
b := &Backup{
updateBackupCb: updateBackupCb,
client: c,
store: store,
backupContainer: backupContainer,
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.store.GetKV())
if err != nil {
return nil, errors.Errorf(errSaveKeySaltParams, err)
}
// Setting backup trigger in client
b.backupContainer.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, c *api.Client) (*Backup, error) {
return resumeBackup(
updateBackupCb, c, c.GetStorage(), c.GetBackup(), c.GetRng())
}
// resumeBackup is a helper function that takes in all the fields for Backup as
// parameters for easier testing.
func resumeBackup(updateBackupCb UpdateBackupFn, c *api.Client,
store *storage.Session, backupContainer *interfaces.BackupContainer,
rng *fastRNG.StreamGenerator) (*Backup, error) {
_, _, _, err := loadBackup(store.GetKV())
if err != nil {
return nil, err
}
b := &Backup{
updateBackupCb: updateBackupCb,
client: c,
store: store,
backupContainer: backupContainer,
rng: rng,
jsonParams: loadJson(store.GetKV()),
}
// Setting backup trigger in client
b.backupContainer.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()
key, salt, params, err := loadBackup(b.store.GetKV())
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.store.GetKV()); 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.store.GetKV())
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 user and storage user
u := b.store.GetUser()
su := b.store.User()
// Get registration timestamp
bu.RegistrationTimestamp = u.RegistrationTimestamp
// Get registration code; ignore the error because if there is no
// registration, then an empty string is returned
bu.RegistrationCode, _ = b.store.GetRegCode()
// Get transmission identity
bu.TransmissionIdentity = backup.TransmissionIdentity{
RSASigningPrivateKey: u.TransmissionRSA,
RegistrarSignature: su.GetTransmissionRegistrationValidationSignature(),
Salt: u.TransmissionSalt,
ComputedID: u.TransmissionID,
}
// Get reception identity
bu.ReceptionIdentity = backup.ReceptionIdentity{
RSASigningPrivateKey: u.ReceptionRSA,
RegistrarSignature: su.GetReceptionRegistrationValidationSignature(),
Salt: u.ReceptionSalt,
ComputedID: u.ReceptionID,
DHPrivateKey: u.E2eDhPrivateKey,
DHPublicKey: u.E2eDhPublicKey,
}
// Get facts
bu.UserDiscoveryRegistration.FactList = b.store.GetUd().GetFacts()
// Get contacts
bu.Contacts.Identities = b.store.E2e().GetPartners()
// Get pending auth requests
// NOTE: Received requests don't matter here, as those are either
// not yet noticed by user OR explicitly rejected.
bu.Contacts.Identities = append(bu.Contacts.Identities,
b.store.Auth().GetAllSentIDs()...)
jww.INFO.Printf("backup saw %d contacts", len(bu.Contacts.Identities))
jww.DEBUG.Printf("contacts in backup list: %+v", bu.Contacts.Identities)
//deduplicate list
bu.Contacts.Identities = deduplicate(bu.Contacts.Identities)
jww.INFO.Printf("backup saved %d contacts after deduplication",
len(bu.Contacts.Identities))
// Add the memoized JSON params
bu.JSONParams = b.jsonParams
return bu
}
func deduplicate(list []*id.ID) []*id.ID {
entryMap := make(map[id.ID]bool)
newList := make([]*id.ID, 0)
for i, _ := range list {
if _, value := entryMap[*list[i]]; !value {
entryMap[*list[i]] = true
newList = append(newList, list[i])
}
}
return newList
}
////////////////////////////////////////////////////////////////////////////////
// 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 (
"bytes"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/cloudflare/circl/dh/sidh"
"gitlab.com/elixxir/client/interfaces/params"
util "gitlab.com/elixxir/client/storage/utility"
"gitlab.com/elixxir/crypto/diffieHellman"
"gitlab.com/xx_network/primitives/id"
"gitlab.com/elixxir/client/interfaces"
"gitlab.com/elixxir/client/storage"
"gitlab.com/elixxir/crypto/backup"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/crypto/csprng"
)
// Tests that Backup.initializeBackup returns a new Backup with a copy of the
// key and the callback.
func Test_initializeBackup(t *testing.T) {
cbChan := make(chan []byte, 2)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
expectedPassword := "MySuperSecurePassword"
b, err := initializeBackup(expectedPassword, cb, nil,
storage.InitTestingSession(t), &interfaces.BackupContainer{},
fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
if err != nil {
t.Errorf("initializeBackup returned an error: %+v", err)
}
select {
case <-cbChan:
case <-time.After(10 * time.Millisecond):
t.Error("Timed out waiting for callback.")
}
// Check that the key, salt, and params were saved to storage
key, salt, _, err := loadBackup(b.store.GetKV())
if err != nil {
t.Errorf("Failed to load key, salt, and params: %+v", err)
}
if len(key) != keyLen || bytes.Equal(key, make([]byte, keyLen)) {
t.Errorf("Invalid key: %v", key)
}
if len(salt) != saltLen || bytes.Equal(salt, make([]byte, saltLen)) {
t.Errorf("Invalid salt: %v", salt)
}
// if !reflect.DeepEqual(p, backup.DefaultParams()) {
// t.Errorf("Invalid params.\nexpected: %+v\nreceived: %+v",
// backup.DefaultParams(), p)
// }
encryptedBackup := []byte("encryptedBackup")
go b.updateBackupCb(encryptedBackup)
select {
case r := <-cbChan:
if !bytes.Equal(encryptedBackup, r) {
t.Errorf("Callback has unexepected data."+
"\nexpected: %q\nreceived: %q", encryptedBackup, r)
}
case <-time.After(10 * time.Millisecond):
t.Error("Timed out waiting for callback.")
}
}
// Initialises a new backup and then tests that Backup.resumeBackup overwrites
// the callback but keeps the password.
func Test_resumeBackup(t *testing.T) {
// Start the first backup
cbChan1 := make(chan []byte)
cb1 := func(encryptedBackup []byte) { cbChan1 <- encryptedBackup }
s := storage.InitTestingSession(t)
expectedPassword := "MySuperSecurePassword"
b, err := initializeBackup(expectedPassword, cb1, nil, s,
&interfaces.BackupContainer{},
fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
if err != nil {
t.Errorf("Failed to initialize new Backup: %+v", err)
}
select {
case <-cbChan1:
case <-time.After(10 * time.Millisecond):
t.Error("Timed out waiting for callback.")
}
// Get key and salt to compare to later
key1, salt1, _, err := loadBackup(b.store.GetKV())
if err != nil {
t.Errorf("Failed to load key, salt, and params from newly "+
"initialized backup: %+v", err)
}
// Resume the backup with a new callback
cbChan2 := make(chan []byte)
cb2 := func(encryptedBackup []byte) { cbChan2 <- encryptedBackup }
b2, err := resumeBackup(cb2, nil, s, &interfaces.BackupContainer{},
fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
if err != nil {
t.Errorf("resumeBackup returned an error: %+v", err)
}
// Get key, salt, and parameters of resumed backup
key2, salt2, _, err := loadBackup(b.store.GetKV())
if err != nil {
t.Errorf("Failed to load key, salt, and params from resumed "+
"backup: %+v", err)
}
// Check that the loaded key and salt are the same
if !bytes.Equal(key1, key2) {
t.Errorf("New key does not match old key.\nold: %v\nnew: %v", key1, key2)
}
if !bytes.Equal(salt1, salt2) {
t.Errorf("New salt does not match old salt.\nold: %v\nnew: %v", salt1, salt2)
}
encryptedBackup := []byte("encryptedBackup")
go b2.updateBackupCb(encryptedBackup)
select {
case r := <-cbChan1:
t.Errorf("Callback of first Backup called: %q", r)
case r := <-cbChan2:
if !bytes.Equal(encryptedBackup, r) {
t.Errorf("Callback has unexepected data."+
"\nexpected: %q\nreceived: %q", encryptedBackup, r)
}
case <-time.After(10 * time.Millisecond):
t.Error("Timed out waiting for callback.")
}
}
// Error path: Tests that Backup.resumeBackup returns an error if no password is
// present in storage.
func Test_resumeBackup_NoKeyError(t *testing.T) {
expectedErr := "object not found"
s := storage.InitTestingSession(t)
_, err := resumeBackup(nil, nil, s, &interfaces.BackupContainer{}, nil)
if err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Errorf("resumeBackup did not return the expected error when no "+
"password is present.\nexpected: %s\nreceived: %+v", expectedErr, err)
}
}
// Tests that Backup.TriggerBackup triggers the callback and that the data
// received can be decrypted.
func TestBackup_TriggerBackup(t *testing.T) {
cbChan := make(chan []byte)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
password := "MySuperSecurePassword"
b := newTestBackup(password, cb, t)
collatedBackup := b.assembleBackup()
b.TriggerBackup("")
select {
case r := <-cbChan:
receivedCollatedBackup := backup.Backup{}
err := receivedCollatedBackup.Decrypt(password, r)
if err != nil {
t.Errorf("Failed to decrypt collated backup: %+v", err)
} else if !reflect.DeepEqual(collatedBackup, receivedCollatedBackup) {
t.Errorf("Unexpected decrypted collated backup."+
"\nexpected: %#v\nreceived: %#v",
collatedBackup, receivedCollatedBackup)
}
case <-time.After(10 * time.Millisecond):
t.Error("Timed out waiting for callback.")
}
}
// Tests that Backup.TriggerBackup does not call the callback if there is no
// key, salt, and params in storage.
func TestBackup_TriggerBackup_NoKey(t *testing.T) {
cbChan := make(chan []byte)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
b := newTestBackup("MySuperSecurePassword", cb, t)
select {
case <-cbChan:
case <-time.After(10 * time.Millisecond):
t.Errorf("backup not called")
}
err := deleteBackup(b.store.GetKV())
if err != nil {
t.Errorf("Failed to delete key, salt, and params: %+v", err)
}
b.TriggerBackup("")
select {
case r := <-cbChan:
t.Errorf("Callback received when it should not have been called: %q", r)
case <-time.After(10 * time.Millisecond):
}
}
// Tests that Backup.StopBackup prevents the callback from triggering and that
// the password, key, salt, and parameters were deleted.
func TestBackup_StopBackup(t *testing.T) {
cbChan := make(chan []byte)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
b := newTestBackup("MySuperSecurePassword", cb, t)
select {
case <-cbChan:
case <-time.After(1000 * time.Millisecond):
t.Errorf("backup not called")
}
err := b.StopBackup()
if err != nil {
t.Errorf("StopBackup returned an error: %+v", err)
}
if b.updateBackupCb != nil {
t.Error("Callback not cleared.")
}
b.TriggerBackup("")
select {
case r := <-cbChan:
t.Errorf("Callback received when it should not have been called: %q", r)
case <-time.After(10 * time.Millisecond):
}
// Make sure key, salt, and params are deleted
key, salt, p, err := loadBackup(b.store.GetKV())
if err == nil || len(key) != 0 || len(salt) != 0 || p != (backup.Params{}) {
t.Errorf("Loaded key, salt, and params that should be deleted.")
}
}
func TestBackup_IsBackupRunning(t *testing.T) {
cbChan := make(chan []byte)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
b := newTestBackup("MySuperSecurePassword", cb, t)
// Check that the backup is running after being initialized
if !b.IsBackupRunning() {
t.Error("Backup is not running after initialization.")
}
// Stop the backup
err := b.StopBackup()
if err != nil {
t.Errorf("Failed to stop backup: %+v", err)
}
// Check that the backup is stopped
if b.IsBackupRunning() {
t.Error("Backup is running after being stopped.")
}
}
func TestBackup_AddJson(t *testing.T) {
b := newTestBackup("MySuperSecurePassword", nil, t)
s := b.store
json := "{'data': {'one': 1}}"
expectedCollatedBackup := backup.Backup{
RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
TransmissionIdentity: backup.TransmissionIdentity{
RSASigningPrivateKey: s.GetUser().TransmissionRSA,
RegistrarSignature: s.User().GetTransmissionRegistrationValidationSignature(),
Salt: s.GetUser().TransmissionSalt,
ComputedID: s.GetUser().TransmissionID,
},
ReceptionIdentity: backup.ReceptionIdentity{
RSASigningPrivateKey: s.GetUser().ReceptionRSA,
RegistrarSignature: s.User().GetReceptionRegistrationValidationSignature(),
Salt: s.GetUser().ReceptionSalt,
ComputedID: s.GetUser().ReceptionID,
DHPrivateKey: s.GetUser().E2eDhPrivateKey,
DHPublicKey: s.GetUser().E2eDhPublicKey,
},
UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
FactList: s.GetUd().GetFacts(),
},
Contacts: backup.Contacts{Identities: s.E2e().GetPartners()},
JSONParams: json,
}
b.AddJson(json)
collatedBackup := b.assembleBackup()
if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
t.Errorf("Collated backup does not match expected."+
"\nexpected: %+v\nreceived: %+v",
expectedCollatedBackup, collatedBackup)
}
}
func TestBackup_AddJson_badJson(t *testing.T) {
b := newTestBackup("MySuperSecurePassword", nil, t)
s := b.store
json := "abc{'i'm a bad json: 'one': 1'''}}"
expectedCollatedBackup := backup.Backup{
RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
TransmissionIdentity: backup.TransmissionIdentity{
RSASigningPrivateKey: s.GetUser().TransmissionRSA,
RegistrarSignature: s.User().GetTransmissionRegistrationValidationSignature(),
Salt: s.GetUser().TransmissionSalt,
ComputedID: s.GetUser().TransmissionID,
},
ReceptionIdentity: backup.ReceptionIdentity{
RSASigningPrivateKey: s.GetUser().ReceptionRSA,
RegistrarSignature: s.User().GetReceptionRegistrationValidationSignature(),
Salt: s.GetUser().ReceptionSalt,
ComputedID: s.GetUser().ReceptionID,
DHPrivateKey: s.GetUser().E2eDhPrivateKey,
DHPublicKey: s.GetUser().E2eDhPublicKey,
},
UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
FactList: s.GetUd().GetFacts(),
},
Contacts: backup.Contacts{Identities: s.E2e().GetPartners()},
JSONParams: json,
}
b.AddJson(json)
collatedBackup := b.assembleBackup()
if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
t.Errorf("Collated backup does not match expected."+
"\nexpected: %+v\nreceived: %+v",
expectedCollatedBackup, collatedBackup)
}
}
// Tests that Backup.assembleBackup returns the backup.Backup with the expected
// results.
func TestBackup_assembleBackup(t *testing.T) {
b := newTestBackup("MySuperSecurePassword", nil, t)
s := b.store
rng := csprng.NewSystemRNG()
for i := 0; i < 10; i++ {
recipient, _ := id.NewRandomID(rng, id.User)
dhKey := s.E2e().GetGroup().NewInt(int64(i + 10))
pubKey := diffieHellman.GeneratePublicKey(dhKey, s.E2e().GetGroup())
_, mySidhPriv := util.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, rng)
theirSidhPub, _ := util.GenerateSIDHKeyPair(sidh.KeyVariantSidhB, rng)
p := params.GetDefaultE2ESessionParams()
err := s.E2e().AddPartner(
recipient, pubKey, dhKey, mySidhPriv, theirSidhPub, p, p)
if err != nil {
t.Errorf("Failed to add partner %s: %+v", recipient, err)
}
}
expectedCollatedBackup := backup.Backup{
RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
TransmissionIdentity: backup.TransmissionIdentity{
RSASigningPrivateKey: s.GetUser().TransmissionRSA,
RegistrarSignature: s.User().GetTransmissionRegistrationValidationSignature(),
Salt: s.GetUser().TransmissionSalt,
ComputedID: s.GetUser().TransmissionID,
},
ReceptionIdentity: backup.ReceptionIdentity{
RSASigningPrivateKey: s.GetUser().ReceptionRSA,
RegistrarSignature: s.User().GetReceptionRegistrationValidationSignature(),
Salt: s.GetUser().ReceptionSalt,
ComputedID: s.GetUser().ReceptionID,
DHPrivateKey: s.GetUser().E2eDhPrivateKey,
DHPublicKey: s.GetUser().E2eDhPublicKey,
},
UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
FactList: s.GetUd().GetFacts(),
},
Contacts: backup.Contacts{Identities: s.E2e().GetPartners()},
}
collatedBackup := b.assembleBackup()
sort.Slice(expectedCollatedBackup.Contacts.Identities, func(i, j int) bool {
return bytes.Compare(expectedCollatedBackup.Contacts.Identities[i].Bytes(),
expectedCollatedBackup.Contacts.Identities[j].Bytes()) == -1
})
sort.Slice(collatedBackup.Contacts.Identities, func(i, j int) bool {
return bytes.Compare(collatedBackup.Contacts.Identities[i].Bytes(),
collatedBackup.Contacts.Identities[j].Bytes()) == -1
})
if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
t.Errorf("Collated backup does not match expected."+
"\nexpected: %+v\nreceived: %+v",
expectedCollatedBackup, collatedBackup)
}
}
// newTestBackup creates a new Backup for testing.
func newTestBackup(password string, cb UpdateBackupFn, t *testing.T) *Backup {
b, err := initializeBackup(
password,
cb,
nil,
storage.InitTestingSession(t),
&interfaces.BackupContainer{},
fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG),
)
if err != nil {
t.Fatalf("Failed to initialize backup: %+v", err)
}
return b
}
// Tests that Backup.InitializeBackup returns a new Backup with a copy of the
// key and the callback.
func Benchmark_InitializeBackup(t *testing.B) {
cbChan := make(chan []byte, 2)
cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
expectedPassword := "MySuperSecurePassword"
for i := 0; i < t.N; i++ {
_, err := initializeBackup(expectedPassword, cb, nil,
storage.InitTestingSession(t), &interfaces.BackupContainer{},
fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
if err != nil {
t.Errorf("InitializeBackup returned an error: %+v", err)
}
}
}
package backup
import (
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/xx_network/primitives/netTime"
)
const (
jsonStorageVersion = 0
jsonStorageKey = "JsonStorage"
)
func storeJson(json string, kv *versioned.KV) error {
obj := &versioned.Object{
Version: jsonStorageVersion,
Timestamp: netTime.Now(),
Data: []byte(json),
}
return kv.Set(jsonStorageKey, jsonStorageVersion, obj)
}
func loadJson(kv *versioned.KV) string {
obj, err := kv.Get(jsonStorageKey, jsonStorageVersion)
if err != nil {
return ""
}
return string(obj.Data)
}
package backup
import (
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/ekv"
"testing"
)
func Test_storeJson_loadJson(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
json := "{'data': {'one': 1}}"
err := storeJson(json, kv)
if err != nil {
t.Errorf("Failed to store JSON: %+v", err)
}
loaded := loadJson(kv)
if loaded != json {
t.Errorf("Did not receive expected data from KV.\n\tExpected: %s, Received: %s\n", json, loaded)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment