Browse Source

Storage Refactor

pull/135/head
Sarah Jamie Lewis 1 year ago
parent
commit
f4c45e863e
14 changed files with 229 additions and 194 deletions
  1. +0
    -2
      .gitignore
  2. +25
    -11
      app/app.go
  3. +7
    -8
      app/cli/main.go
  4. +22
    -14
      app/cwtchutil/main.go
  5. +1
    -1
      app/peer/alice/alice.go
  6. +1
    -1
      app/peer/bob/bob.go
  7. +1
    -1
      peer/connections/peerpeerconnection.go
  8. +3
    -1
      peer/connections/peerserverconnection.go
  9. +19
    -139
      peer/cwtch_peer.go
  10. +6
    -12
      peer/cwtch_peer_test.go
  11. +108
    -0
      storage/file_profile_store.go
  12. +22
    -0
      storage/file_profile_store_test.go
  13. +10
    -0
      storage/profile_store.go
  14. +4
    -4
      testing/cwtch_peer_server_intergration_test.go

+ 0
- 2
.gitignore View File

@@ -3,8 +3,6 @@
*private_key*
*.messages
*.test
*/*test_*
*/*_test*
*.json
*/messages/*
server/app/messages


+ 25
- 11
app/app.go View File

@@ -4,8 +4,10 @@ import (
"crypto/rand"
"cwtch.im/cwtch/connectivity/tor"
"cwtch.im/cwtch/peer"
"cwtch.im/cwtch/storage"
"encoding/hex"
"fmt"

"io/ioutil"
"log"
"os"
@@ -20,10 +22,12 @@ type application struct {
directory string
mutex sync.Mutex
primaryonion string
storage map[string]storage.ProfileStore
}

// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
type Application interface {
SaveProfile(cwtchPeer peer.CwtchPeer)
LoadProfiles(password string) error
CreatePeer(name string, password string) (peer.CwtchPeer, error)

@@ -39,7 +43,7 @@ type Application interface {
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
func NewApp(appDirectory string, torPath string) (Application, error) {
log.Printf("NewApp(%v, %v)\n", appDirectory, torPath)
app := &application{peers: make(map[string]peer.CwtchPeer), directory: appDirectory}
app := &application{peers: make(map[string]peer.CwtchPeer), storage: make(map[string]storage.ProfileStore), directory: appDirectory}
os.MkdirAll(path.Join(appDirectory, "tor"), 0700)
os.Mkdir(path.Join(app.directory, "profiles"), 0700)

@@ -56,22 +60,25 @@ func generateRandomFilename() string {
return filepath.Join(hex.EncodeToString(randBytes))
}

func (app *application) SaveProfile(p peer.CwtchPeer) {
app.mutex.Lock()
defer app.mutex.Unlock()
app.peers[p.GetProfile().Onion] = p
app.storage[p.GetProfile().Onion].Save(p)
}

// NewProfile creates a new cwtchPeer with a given name.
func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) {
log.Printf("CreatePeer(%v)\n", name)

randomFileName := generateRandomFilename()
p, err := peer.NewCwtchPeer(name, password, path.Join(app.directory, "profiles", randomFileName))
fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", randomFileName), password)
p := peer.NewCwtchPeer(name)
err := fileStore.Save(p)
if err != nil {
return nil, err
}
err = p.Save()
if err != nil {
p.Shutdown() //attempt
return nil, fmt.Errorf("Error attempting to save new profile: %v", err)
}
app.startPeer(p)

_, exists := app.peers[p.GetProfile().Onion]
if exists {
p.Shutdown()
@@ -79,6 +86,7 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer
}
app.mutex.Lock()
app.peers[p.GetProfile().Onion] = p
app.storage[p.GetProfile().Onion] = fileStore
app.mutex.Unlock()

return p, nil
@@ -91,10 +99,15 @@ func (app *application) LoadProfiles(password string) error {
}

for _, file := range files {
p, err := peer.LoadCwtchPeer(path.Join(app.directory, "profiles", file.Name()), password)

fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", file.Name()), password)

p, err := fileStore.Load()
if err != nil {

continue
}

_, exists := app.peers[p.GetProfile().Onion]
if exists {
p.Shutdown()
@@ -104,6 +117,7 @@ func (app *application) LoadProfiles(password string) error {
app.startPeer(p)
app.mutex.Lock()
app.peers[p.GetProfile().Onion] = p
app.storage[p.GetProfile().Onion] = fileStore
if app.primaryonion == "" {
app.primaryonion = p.GetProfile().Onion
}
@@ -155,12 +169,12 @@ func (app *application) ListPeers() map[string]string {
return keys
}

// PrimaryIdentity returns a Peer for a given onion address
// PrimaryIdentity returns a cwtchPeer for a given onion address
func (app *application) PrimaryIdentity() peer.CwtchPeer {
return app.peers[app.primaryonion]
}

// GetPeer returns a Peer for a given onion address
// GetPeer returns a cwtchPeer for a given onion address
func (app *application) GetPeer(onion string) peer.CwtchPeer {
if peer, ok := app.peers[onion]; ok {
return peer


+ 7
- 8
app/cli/main.go View File

@@ -304,9 +304,6 @@ func main() {

switch commands[0] {
case "/quit":
if peer != nil {
peer.Save()
}
quit = true
case "/new-profile":
if len(commands) == 2 {
@@ -377,7 +374,7 @@ func main() {
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
}

// Auto Peer / Join Server
// Auto cwtchPeer / Join Server
// TODO There are some privacy implications with this that we should
// think over.
for _, name := range p.GetProfile().GetContacts() {
@@ -450,7 +447,6 @@ func main() {
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
peer.Save()
group := peer.GetGroup(groupID)
if group == nil {
fmt.Printf("Error: group does not exist\n")
@@ -477,7 +473,6 @@ func main() {
id, _, err := peer.StartGroup(commands[1])
if err == nil {
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
peer.Save()
group := peer.GetGroup(id)
if group == nil {
fmt.Printf("Error: group does not exist\n")
@@ -542,7 +537,7 @@ func main() {
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
}
case "/save":
peer.Save()
app.SaveProfile(peer)
case "/help":
for _, command := range suggestions {
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
@@ -593,10 +588,14 @@ func main() {
}
}
if peer != nil {
peer.Save()
app.SaveProfile(peer)
}
}

if peer != nil {
app.SaveProfile(peer)
}

app.Shutdown()
os.Exit(0)



+ 22
- 14
app/cwtchutil/main.go View File

@@ -4,8 +4,10 @@ import (
"bufio"
"crypto/rand"
libpeer "cwtch.im/cwtch/peer"
storage2 "cwtch.im/cwtch/storage"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"

"github.com/sethvargo/go-diceware/diceware"
"golang.org/x/crypto/ed25519"
"io/ioutil"
@@ -17,7 +19,8 @@ import (
)

func convertCwtchFile(filename string, password string) {
peer, err := libpeer.LoadCwtchPeer(filename, password)
fileStore := storage2.CreateFileProfileStore(filename, password)
peer, err := fileStore.Load()
if err != nil {
log.Fatalf("%v", err)
}
@@ -67,16 +70,17 @@ func convertTorFile(filename string, password string) {
}
onion = onion[:56]

peer, err := libpeer.NewCwtchPeer(strings.Join(name, "-"), password, filename)
if err != nil {
log.Fatalf("%v", err)
}
peer := libpeer.NewCwtchPeer(strings.Join(name, "-"))

fmt.Printf("%d %d %s\n", len(peer.GetProfile().Ed25519PublicKey), len(peer.GetProfile().Ed25519PrivateKey), peer.GetProfile().Onion)
peer.GetProfile().Ed25519PrivateKey = sk
peer.GetProfile().Ed25519PublicKey = pk
peer.GetProfile().Onion = string(onion)
peer.Save()
fileStore := storage2.CreateFileProfileStore(filename, password)
err = fileStore.Save(peer)
if err != nil {
log.Fatalf("%v", err)
}

log.Printf("success! loaded %d byte pk and %d byte sk for %s.onion\n", len(pk), len(sk), onion)
}
@@ -90,14 +94,15 @@ func vanity() {
onion := utils.GetTorV3Hostname(pk)
for i := 4; i < len(os.Args); i++ {
if strings.HasPrefix(onion, os.Args[i]) {
peer, err := libpeer.NewCwtchPeer(os.Args[i], os.Args[3], onion+".cwtch")
if err != nil {
log.Fatalf("%v", err)
}
peer := libpeer.NewCwtchPeer(os.Args[i])
peer.GetProfile().Ed25519PrivateKey = sk
peer.GetProfile().Ed25519PublicKey = pk
peer.GetProfile().Onion = onion
peer.Save()
fileStore := storage2.CreateFileProfileStore(os.Args[3], onion+".cwtch")
err := fileStore.Save(peer)
if err != nil {
log.Fatalf("%v", err)
}
log.Printf("found %s.onion\n", onion)
}
}
@@ -163,7 +168,9 @@ func main() {
}
pw = pw[:len(pw)-1]

peer, err := libpeer.LoadCwtchPeer(os.Args[2], pw)
fileStore := storage2.CreateFileProfileStore(os.Args[2], pw)

peer, err := fileStore.Load()
if err != nil {
log.Fatalf("%v", err)
}
@@ -175,12 +182,13 @@ func main() {
}
newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea

err = peer.ChangePassword(newpw1)
fileStore2 := storage2.CreateFileProfileStore(os.Args[2], newpw1)
err = fileStore2.Save(peer)
if err != nil {
log.Fatalf("%v", err)
}

log.Println("success!")
peer.Save()
}
}

+ 1
- 1
app/peer/alice/alice.go View File

@@ -6,7 +6,7 @@ import (
)

func main() {
alice, _ := peer.NewCwtchPeer("alice", "password", ".")
alice := peer.NewCwtchPeer("alice")

processData := func(onion string, data []byte) []byte {
log.Printf("Recieved %s from %v", data, onion)


+ 1
- 1
app/peer/bob/bob.go View File

@@ -8,7 +8,7 @@ import (
)

func main() {
bob, _ := peer.NewCwtchPeer("bob", "password", ".")
bob := peer.NewCwtchPeer("bob")
counter := 1

bob.SetPeerDataHandler(func(onion string, data []byte) []byte {


+ 1
- 1
peer/connections/peerpeerconnection.go View File

@@ -13,7 +13,7 @@ import (
"time"
)

// PeerPeerConnection encapsulates a single outgoing Peer->Peer connection
// PeerPeerConnection encapsulates a single outgoing cwtchPeer->cwtchPeer connection
type PeerPeerConnection struct {
connection.AutoConnectionHandler
PeerHostname string


+ 3
- 1
peer/connections/peerserverconnection.go View File

@@ -126,7 +126,9 @@ func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) err
// Close shuts down the connection (freeing the handler goroutines)
func (psc *PeerServerConnection) Close() {
psc.state = KILLED
psc.connection.Conn.Close()
if psc.connection != nil {
psc.connection.Conn.Close()
}
}

// HandleGroupMessage passes the given group message back to the profile.


+ 19
- 139
peer/cwtch_peer.go View File

@@ -1,14 +1,12 @@
package peer

import (
"crypto/rand"
"crypto/rsa"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/peer/connections"
"cwtch.im/cwtch/peer/peer"
"cwtch.im/cwtch/protocol"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
@@ -17,29 +15,19 @@ import (
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"github.com/golang/protobuf/proto"
"github.com/ulule/deepcopier"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/sha3"
"io"
"io/ioutil"
"log"
"strings"
"sync"
)

// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch Peer
// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
type cwtchPeer struct {
connection.AutoConnectionHandler
Profile *model.Profile
app *application.RicochetApplication
mutex sync.Mutex
Log chan string `json:"-"`
connectionsManager *connections.Manager
profilefile string
key [32]byte
salt [128]byte
dataHandler func(string, []byte) []byte
//handlers map[string]func(*application.ApplicationInstance) func() channels.Handler
aif application.ApplicationInstanceFactory
@@ -48,8 +36,7 @@ type cwtchPeer struct {
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
// directly implement a cwtchPeer.
type CwtchPeer interface {
Save() error
ChangePassword(string) error
Init()
PeerWithOnion(string) *connections.PeerPeerConnection
InviteOnionToGroup(string, string) error

@@ -83,132 +70,26 @@ type CwtchPeer interface {
Shutdown()
}

// createKey derives a key and salt for use in encrypting cwtchPeers
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 *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
}

//copy the struct, then remove the key and salt before saving the copy
cpc := &cwtchPeer{}
deepcopier.Copy(p).To(cpc)
var blankkey [32]byte
var blanksalt [128]byte
cpc.key = blankkey
cpc.salt = blanksalt
bytes, _ := json.Marshal(cpc)
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) (*cwtchPeer, error) {

var decryptNonce [24]byte
copy(decryptNonce[:], ciphertext[:24])
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key)
if ok {
cp := &cwtchPeer{}
err := json.Unmarshal(decrypted, &cp)
if err == nil {
return cp, nil
}
return nil, err
}
return nil, fmt.Errorf("Failed to decrypt")
}

func (cp *cwtchPeer) setup() {
cp.Log = make(chan string)
cp.connectionsManager = connections.NewConnectionsManager()
cp.Init()

go cp.connectionsManager.AttemptReconnections()
}

// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
func NewCwtchPeer(name string, password string, profilefile string) (CwtchPeer, error) {
func NewCwtchPeer(name string) CwtchPeer {
cp := new(cwtchPeer)
cp.profilefile = profilefile
cp.Profile = model.GenerateNewProfile(name)
cp.setup()
key, salt, err := createKey(password)
if err != nil {
return nil, err
}
cp.key = key
cp.salt = salt
return cp, nil
}

func (cp *cwtchPeer) ChangePassword(password string) error {
key, salt, err := createKey(password)
if err != nil {
return err
}
cp.key = key
cp.salt = salt
return nil
cp.Init()
return cp
}

// Save saves the cwtchPeer profile state to a file.
func (cp *cwtchPeer) Save() error {
cp.mutex.Lock()
defer cp.mutex.Unlock()
encryptedbytes, err := encryptProfile(cp, cp.key)
if err != nil {
return err
}

// the salt for the derived key is appended to the front of the file
encryptedbytes = append(cp.salt[:], encryptedbytes...)
err = ioutil.WriteFile(cp.profilefile, encryptedbytes, 0600)
return err
// FromProfile generates a new peer from a profile.
func FromProfile(profile *model.Profile) CwtchPeer {
cp := new(cwtchPeer)
cp.Profile = profile
cp.Init()
return cp
}

// LoadCwtchPeer loads an existing cwtchPeer from a file.
func LoadCwtchPeer(profilefile string, password string) (CwtchPeer, error) {
encryptedbytes, err := ioutil.ReadFile(profilefile)
if err == nil {
var dkr [32]byte
var salty [128]byte

//Separate the salt from the encrypted bytes, then generate the derived key
salt, encryptedbytes := encryptedbytes[0:128], encryptedbytes[128:]
dk := pbkdf2.Key([]byte(password), salt, 4096, 32, sha3.New512)

//cast to arrays
copy(dkr[:], dk)
copy(salty[:], salt)

var cp *cwtchPeer
cp, err = decryptProfile(encryptedbytes, dkr)
if err == nil {
cp.setup()
cp.profilefile = profilefile
cp.key = dkr
cp.salt = salty
return cp, nil
}
}
return nil, err
// Init instantiates a cwtchPeer
func (cp *cwtchPeer) Init() {
cp.connectionsManager = connections.NewConnectionsManager()
go cp.connectionsManager.AttemptReconnections()
}

// ImportGroup intializes a group from an imported source rather than a peer invite
@@ -238,6 +119,7 @@ func (cp *cwtchPeer) ImportGroup(exportedInvite string) (groupID string, err err
// a handler for the optional data handler
// note that the "correct" way to do this would be to AddChannelHandler("im.cwtch.peerdata", ...") but peerdata is such
// a handy channel that it's nice to have this convenience shortcut
// SetPeerDataHandler sets the handler for the (optional) data channel for cwtch peers.
func (cp *cwtchPeer) SetPeerDataHandler(dataHandler func(string, []byte) []byte) {
cp.dataHandler = dataHandler
}
@@ -292,7 +174,7 @@ func (cp *cwtchPeer) GetContact(onion string) *model.PublicProfile {
return contact
}

// GetProfile returns the profile associated with this Peer.
// GetProfile returns the profile associated with this cwtchPeer.
// TODO While it is probably "safe", it is not really "safe", to call functions on this profile. This only exists to return things like Name and Onion,we should gate these.
func (cp *cwtchPeer) GetProfile() *model.Profile {
return cp.Profile
@@ -402,7 +284,7 @@ func (cp *cwtchPeer) LookupContact(hostname string, publicKey rsa.PublicKey) (al
return !blocked, true
}

// LookupContact returns that a contact is known and allowed to communicate for all cases.
// LookupContactV3 returns that a contact is known and allowed to communicate for all cases.
func (cp *cwtchPeer) LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
blocked := cp.Profile.IsBlocked(hostname)
return !blocked, true
@@ -463,7 +345,6 @@ func (cp *cwtchPeer) Shutdown() {
if cp.app != nil {
cp.app.Shutdown()
}
cp.Save()
}

// CwtchPeerInstance encapsulates incoming peer connections
@@ -489,7 +370,6 @@ type CwtchPeerHandler struct {
func (cph *CwtchPeerHandler) ClientIdentity(ci *protocol.CwtchIdentity) {
log.Printf("Received Client Identity from %v %v\n", cph.Onion, ci.String())
cph.Peer.Profile.AddCwtchIdentity(cph.Onion, ci)
cph.Peer.Save()
}

// HandleGroupInvite handles incoming GroupInvites
@@ -503,7 +383,7 @@ func (cph *CwtchPeerHandler) GetClientIdentityPacket() []byte {
return cph.Peer.Profile.GetCwtchIdentityPacket()
}

// HandlePacket handles the Cwtch Peer Data Channel
// HandlePacket handles the Cwtch cwtchPeer Data Channel
func (cph *CwtchPeerHandler) HandlePacket(data []byte) []byte {
return cph.DataHandler(cph.Onion, data)
}

+ 6
- 12
peer/cwtch_peer_test.go View File

@@ -6,17 +6,11 @@ import (

func TestCwtchPeerGenerate(t *testing.T) {

alice, _ := NewCwtchPeer("alice", "testpass", "./alice.json")
alice.Save()
alice := NewCwtchPeer("alice")

aliceLoaded, err := LoadCwtchPeer("./alice.json", "testpass")
if err != nil || aliceLoaded.GetProfile().Name != "alice" {
t.Errorf("something went wrong saving and loading profiles %v %v", err, aliceLoaded)
}

groupID, _, _ := aliceLoaded.StartGroup("test.server")
exportedGroup, _ := aliceLoaded.ExportGroup(groupID)
t.Logf("Exported Group: %v from %v", exportedGroup, aliceLoaded.GetProfile().Onion)
groupID, _, _ := alice.StartGroup("test.server")
exportedGroup, _ := alice.ExportGroup(groupID)
t.Logf("Exported Group: %v from %v", exportedGroup, alice.GetProfile().Onion)

importedGroupID, err := alice.ImportGroup(exportedGroup)
group := alice.GetGroup(importedGroupID)
@@ -26,8 +20,8 @@ func TestCwtchPeerGenerate(t *testing.T) {

func TestTrustPeer(t *testing.T) {
groupName := "test.server"
alice, _ := NewCwtchPeer("alice", "alicepass", "")
bob, _ := NewCwtchPeer("bob", "bobpass", "")
alice := NewCwtchPeer("alice")
bob := NewCwtchPeer("bob")

bobOnion := bob.GetProfile().Onion
aliceOnion := alice.GetProfile().Onion


+ 108
- 0
storage/file_profile_store.go View File

@@ -0,0 +1,108 @@
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
}

+ 22
- 0
storage/file_profile_store_test.go View File

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

import (
"cwtch.im/cwtch/peer"
"testing"
)

func TestFileProfileStore(t *testing.T) {
fileStore := CreateFileProfileStore(".test.json", "password")
alice := peer.NewCwtchPeer("alice")
fileStore.Save(alice)

aliceLoaded, err := fileStore.Load()

if err != nil {
t.Errorf("alice profile should have been loaded from store instead %v", err)
}

if aliceLoaded.GetProfile().Name != "alice" {
t.Errorf("alice profile should have been loaded from store instead %v", aliceLoaded)
}
}

+ 10
- 0
storage/profile_store.go View File

@@ -1 +1,11 @@
package storage

import (
"cwtch.im/cwtch/peer"
)

// ProfileStore is an interface to managing the storage of Cwtch Profiles
type ProfileStore interface {
Save(cwtchPeer peer.CwtchPeer) error
Load() (peer.CwtchPeer, error)
}

+ 4
- 4
testing/cwtch_peer_server_intergration_test.go View File

@@ -101,20 +101,20 @@ func TestCwtchPeerIntegration(t *testing.T) {

numGoRoutinesPostServer := runtime.NumGoroutine()

// ***** Peer setup *****
// ***** cwtchPeer setup *****

fmt.Println("Creating Alice...")
alice, _ := peer.NewCwtchPeer("Alice", "alicepass", "")
alice := peer.NewCwtchPeer("Alice")
go alice.Listen()
fmt.Println("Alice created:", alice.GetProfile().Onion)

fmt.Println("Creating Bob...")
bob, _ := peer.NewCwtchPeer("Bob", "bobpass", "")
bob := peer.NewCwtchPeer("Bob")
go bob.Listen()
fmt.Println("Bob created:", bob.GetProfile().Onion)

fmt.Println("Creating Carol...")
carol, _ := peer.NewCwtchPeer("Carol", "carolpass", "")
carol := peer.NewCwtchPeer("Carol")
go carol.Listen()
fmt.Println("Carol created:", carol.GetProfile().Onion)



Loading…
Cancel
Save