file storage refactor to make file and profile stores
This commit is contained in:
parent
d95b8aa9ed
commit
ffc4254f18
|
@ -71,7 +71,7 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer
|
||||||
log.Debugf("CreatePeer(%v)\n", name)
|
log.Debugf("CreatePeer(%v)\n", name)
|
||||||
|
|
||||||
randomFileName := generateRandomFilename()
|
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)
|
p := peer.NewCwtchPeer(name)
|
||||||
err := fileStore.Save(p)
|
err := fileStore.Save(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,7 +99,7 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
|
|
||||||
for _, file := range files {
|
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()
|
p, err := fileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertCwtchFile(filename string, password string) error {
|
func convertCwtchFile(filename string, password string) error {
|
||||||
fileStore := storage2.CreateFileProfileStore(filename, password)
|
fileStore := storage2.NewFileStore(filename, password)
|
||||||
peer, err := fileStore.Load()
|
peer, err := fileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -77,7 +77,7 @@ func convertTorFile(filename string, password string) error {
|
||||||
peer.GetProfile().Ed25519PrivateKey = sk
|
peer.GetProfile().Ed25519PrivateKey = sk
|
||||||
peer.GetProfile().Ed25519PublicKey = pk
|
peer.GetProfile().Ed25519PublicKey = pk
|
||||||
peer.GetProfile().Onion = string(onion)
|
peer.GetProfile().Onion = string(onion)
|
||||||
fileStore := storage2.CreateFileProfileStore(filename, password)
|
fileStore := storage2.NewFileStore(filename, password)
|
||||||
err = fileStore.Save(peer)
|
err = fileStore.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -100,7 +100,7 @@ func vanity() error {
|
||||||
peer.GetProfile().Ed25519PrivateKey = sk
|
peer.GetProfile().Ed25519PrivateKey = sk
|
||||||
peer.GetProfile().Ed25519PublicKey = pk
|
peer.GetProfile().Ed25519PublicKey = pk
|
||||||
peer.GetProfile().Onion = onion
|
peer.GetProfile().Onion = onion
|
||||||
fileStore := storage2.CreateFileProfileStore(os.Args[3], onion+".cwtch")
|
fileStore := storage2.NewFileStore(os.Args[3], onion+".cwtch")
|
||||||
err := fileStore.Save(peer)
|
err := fileStore.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -179,7 +179,7 @@ func main() {
|
||||||
}
|
}
|
||||||
pw = pw[:len(pw)-1]
|
pw = pw[:len(pw)-1]
|
||||||
|
|
||||||
fileStore := storage2.CreateFileProfileStore(os.Args[2], pw)
|
fileStore := storage2.NewFileStore(os.Args[2], pw)
|
||||||
|
|
||||||
peer, err := fileStore.Load()
|
peer, err := fileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -195,7 +195,7 @@ func main() {
|
||||||
}
|
}
|
||||||
newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea
|
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)
|
err = fileStore2.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package storage
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFileProfileStore(t *testing.T) {
|
func TestFileProfileStore(t *testing.T) {
|
||||||
fileStore := CreateFileProfileStore(".test.json", "password")
|
fileStore := NewFileStore(".test.json", "password")
|
||||||
alice := peer.NewCwtchPeer("alice")
|
alice := peer.NewCwtchPeer("alice")
|
||||||
fileStore.Save(alice)
|
fileStore.Save(alice)
|
||||||
|
|
|
@ -1,11 +1,39 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
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
|
// ProfileStore is an interface to managing the storage of Cwtch Profiles
|
||||||
type ProfileStore interface {
|
type ProfileStore interface {
|
||||||
Save(cwtchPeer peer.CwtchPeer) error
|
Save(profile *model.Profile) error
|
||||||
Load() (peer.CwtchPeer, 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…
Reference in New Issue