diff --git a/api/mnemonic.go b/api/mnemonic.go new file mode 100644 index 0000000000000000000000000000000000000000..fd3ce7259be248692591a48bc86f745fd0089e7c --- /dev/null +++ b/api/mnemonic.go @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package api + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/crypto/csprng" + xxMnemonic "gitlab.com/xx_network/crypto/mnemonic" + "gitlab.com/xx_network/primitives/utils" + "golang.org/x/crypto/chacha20poly1305" + "path/filepath" + "strings" +) + +const mnemonicFile = ".recovery" + +// StoreSecretWithMnemonic creates a mnemonic and uses it to encrypt the secret. +// This encrypted data saved in storage. +func StoreSecretWithMnemonic(secret []byte, path string) (string, error) { + // Use fastRNG for RNG ops (AES fortuna based RNG using system RNG) + rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG).GetStream() + + // Ensure path is appended by filepath separator "/" + if !strings.HasSuffix(path, string(filepath.Separator)) { + path = path + string(filepath.Separator) + } + + // Create a mnemonic + mnemonic, err := xxMnemonic.GenerateMnemonic(rng, 32) + if err != nil { + return "", errors.Errorf("Failed to generate mnemonic: %v", err) + } + + // Decode mnemonic + decodedMnemonic, err := xxMnemonic.DecodeMnemonic(mnemonic) + if err != nil { + return "", errors.Errorf("Failed to decode mnemonic: %v", err) + } + + // Encrypt secret with mnemonic as key + ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, rng) + if err != nil { + return "", errors.Errorf("Failed to encrypt secret with mnemonic: %v", err) + } + + // Save encrypted secret to file + recoveryFile := path + mnemonicFile + err = utils.WriteFileDef(recoveryFile, ciphertext) + if err != nil { + return "", errors.Errorf("Failed to save mnemonic information to file") + } + + return mnemonic, nil +} + +// LoadSecretWithMnemonic loads the encrypted secret from storage and decrypts +// the secret using the given mnemonic. +func LoadSecretWithMnemonic(mnemonic, path string) (secret []byte, err error) { + // Ensure path is appended by filepath separator "/" + if !strings.HasSuffix(path, string(filepath.Separator)) { + path = path + string(filepath.Separator) + } + + // Ensure that the recovery file exists + recoveryFile := path + mnemonicFile + if !utils.Exists(recoveryFile) { + return nil, errors.Errorf("Recovery file does not exist. " + + "Did you properly set up recovery or provide an incorrect filepath?") + } + + // Read file from storage + data, err := utils.ReadFile(recoveryFile) + if err != nil { + return nil, errors.Errorf("Failed to load mnemonic information: %v", err) + } + + // Decode mnemonic + decodedMnemonic, err := xxMnemonic.DecodeMnemonic(mnemonic) + if err != nil { + return nil, errors.Errorf("Failed to decode mnemonic: %v", err) + } + + // Decrypt the stored secret + secret, err = decryptWithMnemonic(data, decodedMnemonic) + if err != nil { + return nil, errors.Errorf("Failed to decrypt secret: %v", err) + } + + return secret, nil +} + +// encryptWithMnemonic is a helper function which encrypts the given secret +// using the mnemonic as the key. +func encryptWithMnemonic(data, decodedMnemonic []byte, + rng csprng.Source) (ciphertext []byte, error error) { + chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:]) + if err != nil { + return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err) + } + + // Generate the nonce + nonce := make([]byte, chaCipher.NonceSize()) + nonce, err = csprng.Generate(chaCipher.NonceSize(), rng) + if err != nil { + return nil, errors.Errorf("Failed to generate nonce: %v", err) + } + + ciphertext = chaCipher.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +// decryptWithMnemonic is a helper function which decrypts the secret +// from storage, using the mnemonic as the key. +func decryptWithMnemonic(data, decodedMnemonic []byte) ([]byte, error) { + chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:]) + if err != nil { + return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err) + } + + nonceLen := chaCipher.NonceSize() + nonce, ciphertext := data[:nonceLen], data[nonceLen:] + plaintext, err := chaCipher.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, errors.Wrap(err, "Cannot decrypt with password!") + } + return plaintext, nil +} diff --git a/api/mnemonic_test.go b/api/mnemonic_test.go new file mode 100644 index 0000000000000000000000000000000000000000..66b2f13dfc8e3f297be051f1f58206c94d22c399 --- /dev/null +++ b/api/mnemonic_test.go @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package api + +import ( + "bytes" + "gitlab.com/xx_network/crypto/csprng" + xxMnemonic "gitlab.com/xx_network/crypto/mnemonic" + "gitlab.com/xx_network/primitives/utils" + "io" + "math/rand" + "testing" +) + +func TestStoreSecretWithMnemonic(t *testing.T) { + secret := []byte("test123") + storageDir := "ignore.1/" + mnemonic, err := StoreSecretWithMnemonic(secret, storageDir) + if err != nil { + t.Errorf("StoreSecretWithMnemonic error; %v", err) + } + + // Tests the mnemonic returned is valid + _, err = xxMnemonic.DecodeMnemonic(mnemonic) + if err != nil { + t.Errorf("StoreSecretWithMnemonic did not return a decodable mnemonic: %v", err) + } + + // Test that the file was written to + if !utils.Exists(storageDir + mnemonicFile) { + t.Errorf("Mnemonic file does not exist in storage: %v", err) + } + +} + +func TestEncryptDecryptMnemonic(t *testing.T) { + prng := NewPrng(32) + + // Generate a test mnemonic + testMnemonic, err := xxMnemonic.GenerateMnemonic(prng, 32) + if err != nil { + t.Fatalf("GenerateMnemonic error: %v", err) + } + + decodedMnemonic, err := xxMnemonic.DecodeMnemonic(testMnemonic) + if err != nil { + t.Fatalf("DecodeMnemonic error: %v", err) + } + + secret := []byte("test123") + + // Encrypt the secret + ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, prng) + if err != nil { + t.Fatalf("encryptWithMnemonic error: %v", err) + } + + // Decrypt the secret + received, err := decryptWithMnemonic(ciphertext, decodedMnemonic) + if err != nil { + t.Fatalf("decryptWithMnemonic error: %v", err) + } + + // Test if secret matches decrypted data + if !bytes.Equal(received, secret) { + t.Fatalf("Decrypted data does not match original plaintext."+ + "\n\tExpected: %v\n\tReceived: %v", secret, received) + } +} + +func TestLoadSecretWithMnemonic(t *testing.T) { + secret := []byte("test123") + storageDir := "ignore.1" + mnemonic, err := StoreSecretWithMnemonic(secret, storageDir) + if err != nil { + t.Errorf("StoreSecretWithMnemonic error; %v", err) + } + + received, err := LoadSecretWithMnemonic(mnemonic, storageDir) + if err != nil { + t.Errorf("LoadSecretWithMnemonic error: %v", err) + } + + if !bytes.Equal(received, secret) { + t.Fatalf("Loaded secret does not match original data."+ + "\n\tExpected: %v\n\tReceived: %v", secret, received) + } + + _, err = LoadSecretWithMnemonic(mnemonic, "badDirectory") + if err == nil { + t.Fatalf("LoadSecretWithMnemonic should error when provided a path " + + "where a recovery file does not exist.") + } +} + +// Prng is a PRNG that satisfies the csprng.Source interface. +type Prng struct{ prng io.Reader } + +func NewPrng(seed int64) csprng.Source { return &Prng{rand.New(rand.NewSource(seed))} } +func (s *Prng) Read(b []byte) (int, error) { return s.prng.Read(b) } +func (s *Prng) SetSeed([]byte) error { return nil } diff --git a/bindings/mnemonic.go b/bindings/mnemonic.go new file mode 100644 index 0000000000000000000000000000000000000000..7062341a1958bb3cd2c0e0e9a960b454f1a143cf --- /dev/null +++ b/bindings/mnemonic.go @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package bindings + +import "gitlab.com/elixxir/client/api" + +// StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage. +// Unlike other storage operations, this does not use EKV, as that is +// intrinsically tied to client operations, which the user will not have while +// trying to recover their account. As such, we store the encrypted data +// directly, with a specified path. Path will be a valid filepath in which the +// recover file will be stored as ".recovery". +// +// As an example, given "home/user/xxmessenger/storagePath", +// the recovery file will be stored at +// "home/user/xxmessenger/storagePath/.recovery" +func StoreSecretWithMnemonic(secret []byte, path string) (string, error) { + return api.StoreSecretWithMnemonic(secret, path) +} + +// LoadSecretWithMnemonic loads the secret stored from the call to +// StoreSecretWithMnemonic. The path given should be the same filepath +// as the path given in StoreSecretWithMnemonic. There should be a file +// in this path called ".recovery". This operation is not tied +// to client operations, as the user will not have a client when trying to +// recover their account. +func LoadSecretWithMnemonic(mnemonic, path string) (secret []byte, err error) { + return api.LoadSecretWithMnemonic(mnemonic, path) +} diff --git a/go.sum b/go.sum index 91ba50390e2c10e39f32b1e9c8e2a2166c79793a..13e906a9f6aae43758b96700122e89d52a2cdb02 100644 --- a/go.sum +++ b/go.sum @@ -239,6 +239,8 @@ github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0 github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w= github.com/ttacon/libphonenumber v1.2.1 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4= github.com/ttacon/libphonenumber v1.2.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -272,6 +274,8 @@ gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= gitlab.com/xx_network/crypto v0.0.5-0.20210803231814-b18476a2257c h1:pwP50UFdh68KPcP7VopsH34/d6rIuVDwXymgFXnCOjA= gitlab.com/xx_network/crypto v0.0.5-0.20210803231814-b18476a2257c/go.mod h1:e/5MGrKDQbCNJnskBeDscrolr2EK5iIatEgTQnEWmOg= +gitlab.com/xx_network/crypto v0.0.5-0.20210909170042-07755821e8c5 h1:1K+FuxnlOTHI5H2OmoBokh/K9Pbmel8VBaCdP5RW9FU= +gitlab.com/xx_network/crypto v0.0.5-0.20210909170042-07755821e8c5/go.mod h1:hLNL8YCSiEMof0wgZHuRmN5C98TYZiLaz/rLqUYTdII= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= diff --git a/network/ephemeral/tracker.go b/network/ephemeral/tracker.go index 82b25ec0ad6fd15b5d406773933a1cd4c3e20e0e..b3265afe1a14738d9abd1247dd7932024a0debc3 100644 --- a/network/ephemeral/tracker.go +++ b/network/ephemeral/tracker.go @@ -141,15 +141,13 @@ func generateIdentities(protoIds []ephemeral.ProtoIdentity, ourId *id.ID, // Add identities for every ephemeral ID for i, eid := range protoIds { // Expand the grace period for both start and end - eid.End.Add(validityGracePeriod) - eid.Start.Add(-validityGracePeriod) identities[i] = reception.Identity{ EphId: eid.Id, Source: ourId, AddressSize: addressSize, End: eid.End, - StartValid: eid.Start, - EndValid: eid.End, + StartValid: eid.Start.Add(-validityGracePeriod), + EndValid: eid.End.Add(validityGracePeriod), Ephemeral: false, } @@ -211,6 +209,6 @@ func calculateTickerTime(baseIDs []ephemeral.ProtoIdentity, now time.Time) time. // Factor out the grace period previously expanded upon // Calculate and return that duration - gracePeriod := lastIdentity.End.Add(-2 * validityGracePeriod) + gracePeriod := lastIdentity.End.Add(-1 * validityGracePeriod) return gracePeriod.Sub(now) } diff --git a/network/ephemeral/tracker_test.go b/network/ephemeral/tracker_test.go index 3307446bc17f25cbf8d8bc119e01a2889b1c2ae2..47b8f034766c45c003f9f4997a599cf1ad22cfbd 100644 --- a/network/ephemeral/tracker_test.go +++ b/network/ephemeral/tracker_test.go @@ -17,6 +17,7 @@ import ( "gitlab.com/xx_network/comms/signature" "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" "gitlab.com/xx_network/primitives/netTime" "gitlab.com/xx_network/primitives/utils" "testing" @@ -122,3 +123,18 @@ func setupInstance(instance interfaces.NetworkManager) error { return nil } + +func TestGenerateIdentities(t *testing.T) { + eid, s, e, err := ephemeral.GetId(id.NewIdFromString("zezima", id.Node, t), 16, time.Now().UnixNano()) + if err != nil { + t.Errorf("Failed to get eid: %+v", err) + } + protoIds := []ephemeral.ProtoIdentity{{eid, s, e}} + generated := generateIdentities(protoIds, id.NewIdFromString("escaline", id.Node, t), 16) + if generated[0].EndValid != protoIds[0].End.Add(5*time.Minute) { + t.Errorf("End was not modified. Orig %+v, Generated %+v", protoIds[0].End, generated[0].End) + } + if generated[0].StartValid != protoIds[0].Start.Add(-5*time.Minute) { + t.Errorf("End was not modified. Orig %+v, Generated %+v", protoIds[0].End, generated[0].End) + } +} diff --git a/network/gateway/sender.go b/network/gateway/sender.go index d689a91dac3794f8c49e4e7039a0533d2b1b2ed7..ddd41478cc7845347bd5adde640794766cf9cc8f 100644 --- a/network/gateway/sender.go +++ b/network/gateway/sender.go @@ -102,8 +102,8 @@ func (s *Sender) SendToPreferred(targets []*id.ID, for targetIdx := range proxies { target := targets[targetIdx] targetProxies := proxies[targetIdx] - if !(int(proxyIdx)<len(targetProxies)){ - jww.WARN.Printf("Failed to send to proxy %d on target %d (%s) " + + if !(int(proxyIdx) < len(targetProxies)) { + jww.WARN.Printf("Failed to send to proxy %d on target %d (%s) "+ "due to not enough proxies (only %d), skipping attempt", proxyIdx, targetIdx, target, len(targetProxies)) continue diff --git a/network/manager.go b/network/manager.go index 3ebc1d1d9cd84007e01bce843cb9afd11d09e24b..f15199a419335f5ec6a02e5b2ca9299bfd3f83ec 100644 --- a/network/manager.go +++ b/network/manager.go @@ -86,6 +86,7 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard, param: params, tracker: &tracker, addrSpace: ephemeral.NewAddressSpace(), + events: events, } m.Internal = internal.Internal{ diff --git a/storage/session.go b/storage/session.go index e42cad445594e30d7b85eafa688d90b80d4d62ed..fd98a85c22e1d1c3aea073ad3d1d9d6a709c6e1c 100644 --- a/storage/session.go +++ b/storage/session.go @@ -46,7 +46,8 @@ const currentSessionVersion = 0 // Session object, backed by encrypted filestore type Session struct { - kv *versioned.KV + kv *versioned.KV + mux sync.RWMutex //memoized data