diff --git a/api/mnemonic.go b/api/mnemonic.go new file mode 100644 index 0000000000000000000000000000000000000000..ac4d7199a9d8eefeca533cb239d156b4307968d4 --- /dev/null +++ b/api/mnemonic.go @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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" + "golang.org/x/crypto/salsa20" +) + +const ( + nonceSize = 8 +) + +// StoreSecretWithMnemonic creates a mnemonic and uses it to encrypt the secret. +// This encrypted data saved in storage. +func (c *Client) StoreSecretWithMnemonic(secret []byte) (string, error) { + rng := c.rng.GetStream() + + // Create a mnemonic + mnemonic, err := xxMnemonic.GenerateMnemonic(rng, 32) + if err != nil { + return "", errors.Errorf("Failed to generate mnemonic: %v", err) + } + + // Encrypt secret with mnemonic as key + ciphertext, nonce, err := encryptWithMnemonic(mnemonic, secret, rng) + if err != nil { + return "", errors.Errorf("Failed to encrypt secret with mnemonic: %v", err) + } + + // Concatenate ciphertext with nonce for storage + data := marshalMnemonicInformation(nonce, ciphertext) + + // Save data to storage + err = c.storage.SaveMnemonicInformation(data) + if err != nil { + return "", errors.Errorf("Failed to store mnemonic information: %v", err) + } + + return mnemonic, nil +} + +// LoadSecretWithMnemonic loads the encrypted secret from storage and decrypts +// the secret using the given mnemonic. +func (c *Client) LoadSecretWithMnemonic(mnemonic string) (secret []byte, err error) { + data, err := c.storage.LoadMnemonicInformation() + if err != nil { + return nil, errors.Errorf("Failed to load mnemonic information: %v", err) + } + + nonce, ciphertext := unmarshalMnemonicInformation(data) + + secret = decryptWithMnemonic(nonce, ciphertext, mnemonic) + + return secret, nil +} + +// encryptWithMnemonic is a helper function which encrypts the given secret +// using the mnemonic as the key. +func encryptWithMnemonic(mnemonic string, secret []byte, + rng *fastRNG.Stream) (ciphertext, nonce []byte, err error) { + + // Place the key into a 32 byte array for salsa 20 + var keyArray [32]byte + copy(keyArray[:], mnemonic) + + // Generate the nonce + nonce, err = csprng.Generate(nonceSize, rng) + if err != nil { + return nil, nil, errors.Errorf("Failed to generate nonce for encryption: %v", err) + } + + // Encrypt the secret + ciphertext = make([]byte, len(secret)) + salsa20.XORKeyStream(ciphertext, secret, nonce, &keyArray) + + return ciphertext, nonce, nil +} + +// decryptWithMnemonic is a helper function which decrypts the secret +// from storage, using the mnemonic as the key. +func decryptWithMnemonic(nonce, ciphertext []byte, mnemonic string) (secret []byte) { + // Place the key into a 32 byte array for salsa 20 + var keyArray [32]byte + copy(keyArray[:], mnemonic) + + // Decrypt the secret + secret = make([]byte, len(ciphertext)) + salsa20.XORKeyStream(secret, ciphertext, nonce, &keyArray) + + return secret +} + +// marshalMnemonicInformation is a helper function which concatenates the nonce +// and ciphertext. +func marshalMnemonicInformation(nonce, ciphertext []byte) []byte { + return append(nonce, ciphertext...) +} + +// unmarshalMnemonicInformation is a helper function which separates the +// concatenated data containing the nonce and ciphertext of the mnemonic +// handling. This is the inverse of marshalMnemonicInformation. +func unmarshalMnemonicInformation(data []byte) (nonce, ciphertext []byte) { + return data[:nonceSize], data[nonceSize:] +} diff --git a/go.mod b/go.mod index 4374a2dd5a339fa386637cf5b8a10d5fcc367968..6d04f1cbadb4e8ba8832578bddfcce1a7c2a0075 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( gitlab.com/elixxir/ekv v0.1.5 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/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 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 diff --git a/go.sum b/go.sum index c8eb24d11ebf4db213445d279faf780c546faa42..b1bcece1eb14a05bbd817eae2da9f70e9e363271 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/storage/mnemonic.go b/storage/mnemonic.go new file mode 100644 index 0000000000000000000000000000000000000000..ee9d955caaf883eade5adc6cc086c9b10517d498 --- /dev/null +++ b/storage/mnemonic.go @@ -0,0 +1,37 @@ +package storage + +import ( + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/netTime" +) + +const ( + mnemonicKvKey = "mnemonic" + mnemonicKvVersion = 0 + mnemonicPath = "/.recovery" +) + +func (s *Session) SaveMnemonicInformation(data []byte) error { + s.mnemonicMux.Lock() + defer s.mnemonicMux.Unlock() + + vo := &versioned.Object{ + Version: mnemonicKvVersion, + Timestamp: netTime.Now(), + Data: data, + } + + return s.mnemonicKV.Set(mnemonicKvKey, mnemonicKvVersion, vo) +} + +func (s *Session) LoadMnemonicInformation() ([]byte, error) { + s.mux.RLock() + defer s.mux.RUnlock() + + vo, err := s.mnemonicKv.Get(mnemonicKvKey, mnemonicKvVersion) + if err != nil { + return nil, err + } + + return vo.Data, err +} diff --git a/storage/session.go b/storage/session.go index e42cad445594e30d7b85eafa688d90b80d4d62ed..aadf9ffb849726ff1d775a37c101d52a3143b274 100644 --- a/storage/session.go +++ b/storage/session.go @@ -46,8 +46,12 @@ const currentSessionVersion = 0 // Session object, backed by encrypted filestore type Session struct { - kv *versioned.KV - mux sync.RWMutex + kv *versioned.KV + mnemonicKV *versioned.KV + mnemonicKv *versioned.KV + + mux sync.RWMutex + mnemonicMux sync.RWMutex //memoized data regStatus RegistrationStatus @@ -78,8 +82,16 @@ func initStore(baseDir, password string) (*Session, error) { "Failed to create storage session") } + // Create a separate file store system for account recovery + mnemonicFS, err := ekv.NewFilestore(baseDir+mnemonicPath, password) + if err != nil { + return nil, errors.WithMessage(err, + "Failed to create mnemonic store") + } + s = &Session{ - kv: versioned.NewKV(fs), + kv: versioned.NewKV(fs), + mnemonicKV: versioned.NewKV(mnemonicFS), } return s, nil