158 lines
4.2 KiB
Go
158 lines
4.2 KiB
Go
package storage
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
"golang.org/x/crypto/nacl/secretbox"
|
|
"golang.org/x/crypto/pbkdf2"
|
|
"golang.org/x/crypto/sha3"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
)
|
|
|
|
// SaltFile is the standard filename to store an encrypted config's SALT under beside it
|
|
const SaltFile = "SALT"
|
|
|
|
const version = "1"
|
|
const versionFile = "VERSION"
|
|
|
|
// GenerateRandomID generates a random 16 byte hex id code
|
|
func GenerateRandomID() string {
|
|
randBytes := make([]byte, 16)
|
|
rand.Read(randBytes)
|
|
return hex.EncodeToString(randBytes)
|
|
}
|
|
|
|
// InitV1Directory generates a key and salt from a password, writes a SALT and VERSION file and returns the key and salt
|
|
func InitV1Directory(directory, password string) ([32]byte, [128]byte, error) {
|
|
os.Mkdir(directory, 0700)
|
|
|
|
key, salt, err := CreateKeySalt(password)
|
|
if err != nil {
|
|
log.Errorf("Could not create key for profile store from password: %v\n", err)
|
|
return [32]byte{}, [128]byte{}, err
|
|
}
|
|
|
|
if err = ioutil.WriteFile(path.Join(directory, versionFile), []byte(version), 0600); err != nil {
|
|
log.Errorf("Could not write version file: %v", err)
|
|
return [32]byte{}, [128]byte{}, err
|
|
}
|
|
|
|
if err = ioutil.WriteFile(path.Join(directory, SaltFile), salt[:], 0600); err != nil {
|
|
log.Errorf("Could not write salt file: %v", err)
|
|
return [32]byte{}, [128]byte{}, err
|
|
}
|
|
|
|
return key, salt, nil
|
|
}
|
|
|
|
// fileStore stores a cwtchPeer in an encrypted file
|
|
type fileStore struct {
|
|
directory string
|
|
filename string
|
|
key [32]byte
|
|
}
|
|
|
|
// FileStore is a primitive around storing encrypted files
|
|
type FileStore interface {
|
|
Write([]byte) error
|
|
Read() ([]byte, error)
|
|
Delete()
|
|
ChangeKey(newkey [32]byte)
|
|
}
|
|
|
|
// NewFileStore instantiates a fileStore given a filename and a password
|
|
func NewFileStore(directory string, filename string, key [32]byte) FileStore {
|
|
filestore := new(fileStore)
|
|
filestore.key = key
|
|
filestore.filename = filename
|
|
filestore.directory = directory
|
|
return filestore
|
|
}
|
|
|
|
// CreateKeySalt derives a key and salt from a password: returns key, salt, err
|
|
func CreateKeySalt(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
|
|
}
|
|
|
|
// CreateKey derives a key from a password and salt
|
|
func CreateKey(password string, salt []byte) [32]byte {
|
|
dk := pbkdf2.Key([]byte(password), salt, 4096, 32, sha3.New512)
|
|
|
|
var dkr [32]byte
|
|
copy(dkr[:], dk)
|
|
return dkr
|
|
}
|
|
|
|
//EncryptFileData encrypts the data with the supplied 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 with the supplied 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, errors.New("failed to decrypt")
|
|
}
|
|
|
|
// ReadEncryptedFile reads data from an encrypted file in directory with key
|
|
func ReadEncryptedFile(directory, filename string, key [32]byte) ([]byte, error) {
|
|
encryptedbytes, err := ioutil.ReadFile(path.Join(directory, filename))
|
|
if err == nil {
|
|
return DecryptFile(encryptedbytes, key)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// write serializes a cwtchPeer to a file
|
|
func (fps *fileStore) Write(data []byte) error {
|
|
encryptedbytes, err := EncryptFileData(data, fps.key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ioutil.WriteFile(path.Join(fps.directory, fps.filename), encryptedbytes, 0600)
|
|
return err
|
|
}
|
|
|
|
func (fps *fileStore) Read() ([]byte, error) {
|
|
return ReadEncryptedFile(fps.directory, fps.filename, fps.key)
|
|
}
|
|
|
|
func (fps *fileStore) Delete() {
|
|
err := os.Remove(path.Join(fps.directory, fps.filename))
|
|
if err != nil {
|
|
log.Errorf("Deleting file %v\n", err)
|
|
}
|
|
}
|
|
|
|
func (fps *fileStore) ChangeKey(newkey [32]byte) {
|
|
fps.key = newkey
|
|
}
|