diff --git a/.gitignore b/.gitignore index 64ff59e..84fea8b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,6 @@ *private_key* *.messages *.test -*/*test_* -*/*_test* *.json */messages/* server/app/messages diff --git a/app/app.go b/app/app.go index fa74d51..6a33042 100644 --- a/app/app.go +++ b/app/app.go @@ -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 diff --git a/app/cli/main.go b/app/cli/main.go index ec12f1e..f8ffd07 100644 --- a/app/cli/main.go +++ b/app/cli/main.go @@ -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) diff --git a/app/cwtchutil/main.go b/app/cwtchutil/main.go index 6062552..edd436b 100644 --- a/app/cwtchutil/main.go +++ b/app/cwtchutil/main.go @@ -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() + } } diff --git a/app/peer/alice/alice.go b/app/peer/alice/alice.go index c18bca4..17c7cc8 100644 --- a/app/peer/alice/alice.go +++ b/app/peer/alice/alice.go @@ -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) diff --git a/app/peer/bob/bob.go b/app/peer/bob/bob.go index bd594bc..171a0da 100644 --- a/app/peer/bob/bob.go +++ b/app/peer/bob/bob.go @@ -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 { diff --git a/peer/connections/peerpeerconnection.go b/peer/connections/peerpeerconnection.go index aac76d1..7f38160 100644 --- a/peer/connections/peerpeerconnection.go +++ b/peer/connections/peerpeerconnection.go @@ -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 diff --git a/peer/connections/peerserverconnection.go b/peer/connections/peerserverconnection.go index 0b5bcce..ba75e7d 100644 --- a/peer/connections/peerserverconnection.go +++ b/peer/connections/peerserverconnection.go @@ -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. diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index bfd15fd..96272d0 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -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 + cp.Init() + return cp } -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 +// FromProfile generates a new peer from a profile. +func FromProfile(profile *model.Profile) CwtchPeer { + cp := new(cwtchPeer) + cp.Profile = profile + 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 -} - -// 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) } diff --git a/peer/cwtch_peer_test.go b/peer/cwtch_peer_test.go index 6a6d075..780309b 100644 --- a/peer/cwtch_peer_test.go +++ b/peer/cwtch_peer_test.go @@ -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 diff --git a/storage/file_profile_store.go b/storage/file_profile_store.go new file mode 100644 index 0000000..10e1433 --- /dev/null +++ b/storage/file_profile_store.go @@ -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 +} diff --git a/storage/file_profile_store_test.go b/storage/file_profile_store_test.go new file mode 100644 index 0000000..df298d7 --- /dev/null +++ b/storage/file_profile_store_test.go @@ -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) + } +} diff --git a/storage/profile_store.go b/storage/profile_store.go index 82be054..e0e0b4e 100644 --- a/storage/profile_store.go +++ b/storage/profile_store.go @@ -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) +} diff --git a/testing/cwtch_peer_server_intergration_test.go b/testing/cwtch_peer_server_intergration_test.go index 3e8e8b0..3e4a84f 100644 --- a/testing/cwtch_peer_server_intergration_test.go +++ b/testing/cwtch_peer_server_intergration_test.go @@ -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)