Skip to content
Snippets Groups Projects
Commit 60cde144 authored by Richard T. Carback III's avatar Richard T. Carback III
Browse files

Merge branch 'XX-3512/mnemonics' into 'release'

Implement mnemonic in client

See merge request !19
parents dc16e1e9 bad230f8
No related branches found
No related tags found
2 merge requests!23Release,!19Implement mnemonic in client
///////////////////////////////////////////////////////////////////////////////
// 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
}
///////////////////////////////////////////////////////////////////////////////
// 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 }
///////////////////////////////////////////////////////////////////////////////
// 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)
}
...@@ -22,7 +22,7 @@ require ( ...@@ -22,7 +22,7 @@ require (
gitlab.com/elixxir/ekv v0.1.5 gitlab.com/elixxir/ekv v0.1.5
gitlab.com/elixxir/primitives v0.0.3-0.20210803231939-7b924f78eaac gitlab.com/elixxir/primitives v0.0.3-0.20210803231939-7b924f78eaac
gitlab.com/xx_network/comms v0.0.4-0.20210813170223-ab758f0bbec5 gitlab.com/xx_network/comms v0.0.4-0.20210813170223-ab758f0bbec5
gitlab.com/xx_network/crypto v0.0.5-0.20210803231814-b18476a2257c gitlab.com/xx_network/crypto v0.0.5-0.20210909170042-07755821e8c5
gitlab.com/xx_network/primitives v0.0.4-0.20210803222745-e898d5e546e9 gitlab.com/xx_network/primitives v0.0.4-0.20210803222745-e898d5e546e9
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 golang.org/x/net v0.0.0-20210525063256-abc453219eb5
......
...@@ -239,6 +239,8 @@ github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0 ...@@ -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/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 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4=
github.com/ttacon/libphonenumber v1.2.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M= 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/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 v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= 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 ...@@ -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.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 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.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-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.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
......
...@@ -47,6 +47,7 @@ const currentSessionVersion = 0 ...@@ -47,6 +47,7 @@ const currentSessionVersion = 0
// Session object, backed by encrypted filestore // Session object, backed by encrypted filestore
type Session struct { type Session struct {
kv *versioned.KV kv *versioned.KV
mux sync.RWMutex mux sync.RWMutex
//memoized data //memoized data
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment