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 }