forked from cwtch.im/cwtch
109 lines
3.0 KiB
Go
109 lines
3.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"cwtch.im/cwtch/model"
|
|
"cwtch.im/cwtch/peer"
|
|
"encoding/json"
|
|
"fmt"
|
|
"golang.org/x/crypto/nacl/secretbox"
|
|
"golang.org/x/crypto/pbkdf2"
|
|
"golang.org/x/crypto/sha3"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
)
|
|
|
|
// 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.Printf("Error: 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.Printf("Error: 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
|
|
}
|