Browse Source

file storage refactor to make file and profile stores

pull/193/head
Dan Ballard 11 months ago
parent
commit
ffc4254f18

+ 2
- 2
app/app.go View File

@@ -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 {

+ 5
- 5
app/cwtchutil/main.go View File

@@ -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)

storage/message_store.go → server/storage/message_store.go View File


storage/message_store_test.go → server/storage/message_store_test.go View File


+ 1
- 0
storage/engine.go View File

@@ -0,0 +1 @@
package storage

+ 23
- 0
storage/file_enc.go View File

@@ -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
}

+ 0
- 109
storage/file_profile_store.go View File

@@ -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
}

+ 89
- 0
storage/file_store.go View File

@@ -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
}

storage/file_profile_store_test.go → storage/file_store_test.go View File

@@ -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)


+ 31
- 3
storage/profile_store.go View File

@@ -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
}

Loading…
Cancel
Save