From ffc4254f1862e64e79c51862d07525402fc39910 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Mon, 14 Jan 2019 12:09:25 -0800 Subject: [PATCH] file storage refactor to make file and profile stores --- app/app.go | 4 +- app/cwtchutil/main.go | 10 +- {storage => server/storage}/message_store.go | 0 .../storage}/message_store_test.go | 0 storage/engine.go | 1 + storage/file_enc.go | 23 ++++ storage/file_profile_store.go | 109 ------------------ storage/file_store.go | 89 ++++++++++++++ ...ofile_store_test.go => file_store_test.go} | 2 +- storage/profile_store.go | 34 +++++- 10 files changed, 152 insertions(+), 120 deletions(-) rename {storage => server/storage}/message_store.go (100%) rename {storage => server/storage}/message_store_test.go (100%) create mode 100644 storage/engine.go create mode 100644 storage/file_enc.go delete mode 100644 storage/file_profile_store.go create mode 100644 storage/file_store.go rename storage/{file_profile_store_test.go => file_store_test.go} (87%) diff --git a/app/app.go b/app/app.go index 8d916a3..7167051 100644 --- a/app/app.go +++ b/app/app.go @@ -71,7 +71,7 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer log.Debugf("CreatePeer(%v)\n", name) randomFileName := generateRandomFilename() - fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", randomFileName), password) + fileStore := storage.NewFileStore(path.Join(app.directory, "profiles", randomFileName), password) p := peer.NewCwtchPeer(name) err := fileStore.Save(p) if err != nil { @@ -99,7 +99,7 @@ func (app *application) LoadProfiles(password string) error { for _, file := range files { - fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", file.Name()), password) + fileStore := storage.NewFileStore(path.Join(app.directory, "profiles", file.Name()), password) p, err := fileStore.Load() if err != nil { diff --git a/app/cwtchutil/main.go b/app/cwtchutil/main.go index ca59f9e..3c895c2 100644 --- a/app/cwtchutil/main.go +++ b/app/cwtchutil/main.go @@ -19,7 +19,7 @@ import ( ) func convertCwtchFile(filename string, password string) error { - fileStore := storage2.CreateFileProfileStore(filename, password) + fileStore := storage2.NewFileStore(filename, password) peer, err := fileStore.Load() if err != nil { return err @@ -77,7 +77,7 @@ func convertTorFile(filename string, password string) error { peer.GetProfile().Ed25519PrivateKey = sk peer.GetProfile().Ed25519PublicKey = pk peer.GetProfile().Onion = string(onion) - fileStore := storage2.CreateFileProfileStore(filename, password) + fileStore := storage2.NewFileStore(filename, password) err = fileStore.Save(peer) if err != nil { return err @@ -100,7 +100,7 @@ func vanity() error { peer.GetProfile().Ed25519PrivateKey = sk peer.GetProfile().Ed25519PublicKey = pk peer.GetProfile().Onion = onion - fileStore := storage2.CreateFileProfileStore(os.Args[3], onion+".cwtch") + fileStore := storage2.NewFileStore(os.Args[3], onion+".cwtch") err := fileStore.Save(peer) if err != nil { return err @@ -179,7 +179,7 @@ func main() { } pw = pw[:len(pw)-1] - fileStore := storage2.CreateFileProfileStore(os.Args[2], pw) + fileStore := storage2.NewFileStore(os.Args[2], pw) peer, err := fileStore.Load() if err != nil { @@ -195,7 +195,7 @@ func main() { } newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea - fileStore2 := storage2.CreateFileProfileStore(os.Args[2], newpw1) + fileStore2 := storage2.NewFileStore(os.Args[2], newpw1) err = fileStore2.Save(peer) if err != nil { log.Errorln(err) diff --git a/storage/message_store.go b/server/storage/message_store.go similarity index 100% rename from storage/message_store.go rename to server/storage/message_store.go diff --git a/storage/message_store_test.go b/server/storage/message_store_test.go similarity index 100% rename from storage/message_store_test.go rename to server/storage/message_store_test.go diff --git a/storage/engine.go b/storage/engine.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/storage/engine.go @@ -0,0 +1 @@ +package storage diff --git a/storage/file_enc.go b/storage/file_enc.go new file mode 100644 index 0000000..e6d377b --- /dev/null +++ b/storage/file_enc.go @@ -0,0 +1,23 @@ +package storage + +import ( + "crypto/rand" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/sha3" + "io" +) + +// createKey derives a key from a password +func createKey(password string) ([32]byte, [128]byte, error) { + var salt [128]byte + if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { + log.Errorf("Cannot read from random: %v\n", err) + return [32]byte{}, salt, err + } + dk := pbkdf2.Key([]byte(password), salt[:], 4096, 32, sha3.New512) + + var dkr [32]byte + copy(dkr[:], dk) + return dkr, salt, nil +} diff --git a/storage/file_profile_store.go b/storage/file_profile_store.go deleted file mode 100644 index 455706b..0000000 --- a/storage/file_profile_store.go +++ /dev/null @@ -1,109 +0,0 @@ -package storage - -import ( - "crypto/rand" - "cwtch.im/cwtch/model" - "cwtch.im/cwtch/peer" - "encoding/json" - "fmt" - "git.openprivacy.ca/openprivacy/libricochet-go/log" - "golang.org/x/crypto/nacl/secretbox" - "golang.org/x/crypto/pbkdf2" - "golang.org/x/crypto/sha3" - "io" - "io/ioutil" -) - -// fileProfileStore stores a cwtchPeer in an encrypted file -type fileProfileStore struct { - profilefile string - password string -} - -// CreateFileProfileStore instantiates a fileProfileStore given a filename and a password -func CreateFileProfileStore(profilefile string, password string) ProfileStore { - filestore := new(fileProfileStore) - filestore.password = password - filestore.profilefile = profilefile - return filestore -} - -// Save serializes a cwtchPeer to a file -func (fps *fileProfileStore) Save(cwtchPeer peer.CwtchPeer) error { - - key, salt, _ := createKey(fps.password) - encryptedbytes, err := encryptProfile(cwtchPeer, key) - if err != nil { - return err - } - - // the salt for the derived key is appended to the front of the file - encryptedbytes = append(salt[:], encryptedbytes...) - err = ioutil.WriteFile(fps.profilefile, encryptedbytes, 0600) - return err - -} - -// createKey derives a key from a password -func createKey(password string) ([32]byte, [128]byte, error) { - var salt [128]byte - if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { - log.Errorf("Cannot read from random: %v\n", err) - return [32]byte{}, salt, err - } - dk := pbkdf2.Key([]byte(password), salt[:], 4096, 32, sha3.New512) - - var dkr [32]byte - copy(dkr[:], dk) - return dkr, salt, nil -} - -//encryptProfile encrypts the cwtchPeer via the specified key. -func encryptProfile(p peer.CwtchPeer, key [32]byte) ([]byte, error) { - var nonce [24]byte - - if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { - log.Errorf("Cannot read from random: %v\n", err) - return nil, err - } - - bytes, _ := json.Marshal(p.GetProfile()) - encrypted := secretbox.Seal(nonce[:], []byte(bytes), &nonce, &key) - return encrypted, nil -} - -//decryptProfile decrypts the passed ciphertext into a cwtchPeer via the specified key. -func decryptProfile(ciphertext []byte, key [32]byte) (*model.Profile, error) { - - var decryptNonce [24]byte - copy(decryptNonce[:], ciphertext[:24]) - decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key) - if ok { - cp := new(model.Profile) - err := json.Unmarshal(decrypted, &cp) - if err == nil { - return cp, nil - } - return nil, err - } - return nil, fmt.Errorf("Failed to decrypt") -} - -// Load instantiates a cwtchPeer from the file store -func (fps *fileProfileStore) Load() (peer.CwtchPeer, error) { - encryptedbytes, err := ioutil.ReadFile(fps.profilefile) - if err == nil { - var dkr [32]byte - //Separate the salt from the encrypted bytes, then generate the derived key - salt, encryptedbytes := encryptedbytes[0:128], encryptedbytes[128:] - dk := pbkdf2.Key([]byte(fps.password), salt, 4096, 32, sha3.New512) - copy(dkr[:], dk) - - profile, err := decryptProfile(encryptedbytes, dkr) - if err == nil { - return peer.FromProfile(profile), nil - } - return nil, err - } - return nil, err -} diff --git a/storage/file_store.go b/storage/file_store.go new file mode 100644 index 0000000..22fac72 --- /dev/null +++ b/storage/file_store.go @@ -0,0 +1,89 @@ +package storage + +import ( + "crypto/rand" + "fmt" + "git.openprivacy.ca/openprivacy/libricochet-go/log" + "golang.org/x/crypto/nacl/secretbox" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/sha3" + "io" + "io/ioutil" +) + +// fileStore stores a cwtchPeer in an encrypted file +type fileStore struct { + filename string + password string +} + +type FileStore interface { + Save([]byte) error + Load() ([]byte, error) +} + +// NewFileStore instantiates a fileStore given a filename and a password +func NewFileStore(filename string, password string) FileStore { + filestore := new(fileStore) + filestore.password = password + filestore.filename = filename + return filestore +} + +// Save serializes a cwtchPeer to a file +func (fps *fileStore) Save(data []byte) error { + key, salt, _ := createKey(fps.password) + encryptedbytes, err := encryptFileData(data, key) + if err != nil { + return err + } + + // the salt for the derived key is appended to the front of the file + encryptedbytes = append(salt[:], encryptedbytes...) + err = ioutil.WriteFile(fps.filename, encryptedbytes, 0600) + return err + +} + +//encryptFileData encrypts the cwtchPeer via the specified key. +func encryptFileData(data []byte, key [32]byte) ([]byte, error) { + var nonce [24]byte + + if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { + log.Errorf("Cannot read from random: %v\n", err) + return nil, err + } + + encrypted := secretbox.Seal(nonce[:], data, &nonce, &key) + return encrypted, nil +} + +//decryptFile decrypts the passed ciphertext into a cwtchPeer via the specified key. +func decryptFile(ciphertext []byte, key [32]byte) ([]byte, error) { + var decryptNonce [24]byte + copy(decryptNonce[:], ciphertext[:24]) + decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key) + if ok { + return decrypted, nil + } + return nil, fmt.Errorf("Failed to decrypt") +} + +// Load instantiates a cwtchPeer from the file store +func (fps *fileStore) Load() ([]byte, error) { + encryptedbytes, err := ioutil.ReadFile(fps.filename) + if err == nil { + var dkr [32]byte + //Separate the salt from the encrypted bytes, then generate the derived key + salt, encryptedbytes := encryptedbytes[0:128], encryptedbytes[128:] + dk := pbkdf2.Key([]byte(fps.password), salt, 4096, 32, sha3.New512) + copy(dkr[:], dk) + + data, err := decryptFile(encryptedbytes, dkr) + if err == nil { + return data, nil + } + return nil, err + } + return nil, err +} diff --git a/storage/file_profile_store_test.go b/storage/file_store_test.go similarity index 87% rename from storage/file_profile_store_test.go rename to storage/file_store_test.go index df298d7..da2a955 100644 --- a/storage/file_profile_store_test.go +++ b/storage/file_store_test.go @@ -6,7 +6,7 @@ import ( ) func TestFileProfileStore(t *testing.T) { - fileStore := CreateFileProfileStore(".test.json", "password") + fileStore := NewFileStore(".test.json", "password") alice := peer.NewCwtchPeer("alice") fileStore.Save(alice) diff --git a/storage/profile_store.go b/storage/profile_store.go index e0e0b4e..18c8958 100644 --- a/storage/profile_store.go +++ b/storage/profile_store.go @@ -1,11 +1,39 @@ package storage import ( - "cwtch.im/cwtch/peer" + "cwtch.im/cwtch/model" + "encoding/json" ) +type profileStore struct { + fs FileStore +} + // ProfileStore is an interface to managing the storage of Cwtch Profiles type ProfileStore interface { - Save(cwtchPeer peer.CwtchPeer) error - Load() (peer.CwtchPeer, error) + Save(profile *model.Profile) error + Load() (*model.Profile, error) +} + +func NewProfileStore(filename string, password string) ProfileStore { + return &profileStore{NewFileStore(filename, password)} +} + +func (ps *profileStore) Save(profile *model.Profile) error { + bytes, _ := json.Marshal(profile) + return ps.fs.Save(bytes) +} + +// Load instantiates a cwtchPeer from the file store +func (ps *profileStore) Load() (*model.Profile, error) { + decrypted, err := ps.fs.Load() + if err != nil { + return nil, err + } + cp := new(model.Profile) + err = json.Unmarshal(decrypted, &cp) + if err == nil { + return cp, nil + } + return nil, err }