Merge branch 'storage-eb' of dan/cwtch into master
This commit is contained in:
commit
0d026cda45
37
app/app.go
37
app/app.go
|
@ -29,7 +29,6 @@ type application struct {
|
||||||
|
|
||||||
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
||||||
type Application interface {
|
type Application interface {
|
||||||
SaveProfile(cwtchPeer peer.CwtchPeer)
|
|
||||||
LoadProfiles(password string) error
|
LoadProfiles(password string) error
|
||||||
CreatePeer(name string, password string) (peer.CwtchPeer, error)
|
CreatePeer(name string, password string) (peer.CwtchPeer, error)
|
||||||
|
|
||||||
|
@ -59,21 +58,16 @@ func generateRandomFilename() string {
|
||||||
return filepath.Join(hex.EncodeToString(randBytes))
|
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.
|
// NewProfile creates a new cwtchPeer with a given name.
|
||||||
func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) {
|
func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) {
|
||||||
log.Debugf("CreatePeer(%v)\n", name)
|
log.Debugf("CreatePeer(%v)\n", name)
|
||||||
|
|
||||||
randomFileName := generateRandomFilename()
|
randomFileName := generateRandomFilename()
|
||||||
fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", randomFileName), password)
|
// TODO: eventBus per profile
|
||||||
|
profileStore, err := storage.NewProfileStore(app.eventBus, path.Join(app.directory, "profiles", randomFileName), password)
|
||||||
|
profileStore.Init(name)
|
||||||
p := peer.NewCwtchPeer(name)
|
p := peer.NewCwtchPeer(name)
|
||||||
err := fileStore.Save(p)
|
app.eventBus.Publish(event.NewEvent(event.SetProfileName, map[string]string{"Name": name}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -85,7 +79,7 @@ func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer
|
||||||
}
|
}
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
app.peers[p.GetProfile().Onion] = p
|
app.peers[p.GetProfile().Onion] = p
|
||||||
app.storage[p.GetProfile().Onion] = fileStore
|
app.storage[p.GetProfile().Onion] = profileStore
|
||||||
app.mutex.Unlock()
|
app.mutex.Unlock()
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
|
@ -99,27 +93,30 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
|
||||||
fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", file.Name()), password)
|
// TODO: Per profile eventBus
|
||||||
|
profileStore, err := storage.NewProfileStore(app.eventBus, path.Join(app.directory, "profiles", file.Name()), password)
|
||||||
|
|
||||||
p, err := fileStore.Load()
|
err = profileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := app.peers[p.GetProfile().Onion]
|
profile := profileStore.GetProfileCopy()
|
||||||
|
_, exists := app.peers[profile.Onion]
|
||||||
if exists {
|
if exists {
|
||||||
p.Shutdown()
|
profileStore.Shutdown()
|
||||||
log.Errorf("profile for onion %v already exists", p.GetProfile().Onion)
|
log.Errorf("profile for onion %v already exists", profile.Onion)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Init(app.acn, app.eventBus)
|
peer := peer.FromProfile(profile)
|
||||||
|
peer.Init(app.acn, app.eventBus)
|
||||||
|
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
app.peers[p.GetProfile().Onion] = p
|
app.peers[profile.Onion] = peer
|
||||||
app.storage[p.GetProfile().Onion] = fileStore
|
app.storage[profile.Onion] = profileStore
|
||||||
if app.primaryonion == "" {
|
if app.primaryonion == "" {
|
||||||
app.primaryonion = p.GetProfile().Onion
|
app.primaryonion = profile.Onion
|
||||||
}
|
}
|
||||||
app.mutex.Unlock()
|
app.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
|
@ -548,8 +548,6 @@ func main() {
|
||||||
fmt.Printf("%v", commands)
|
fmt.Printf("%v", commands)
|
||||||
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
|
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "/save":
|
|
||||||
app.SaveProfile(peer)
|
|
||||||
case "/help":
|
case "/help":
|
||||||
for _, command := range suggestions {
|
for _, command := range suggestions {
|
||||||
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
||||||
|
@ -599,13 +597,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if peer != nil {
|
|
||||||
app.SaveProfile(peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer != nil {
|
|
||||||
app.SaveProfile(peer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Shutdown()
|
app.Shutdown()
|
||||||
|
|
|
@ -1,54 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"crypto/rand"
|
|
||||||
libpeer "cwtch.im/cwtch/peer"
|
|
||||||
storage2 "cwtch.im/cwtch/storage"
|
|
||||||
"fmt"
|
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
//"bufio"
|
||||||
"strings"
|
//"cwtch.im/cwtch/storage"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertCwtchFile(filename string, password string) error {
|
|
||||||
fileStore := storage2.CreateFileProfileStore(filename, password)
|
|
||||||
peer, err := fileStore.Load()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b := []byte("== ed25519v1-secret: type0 ==")
|
|
||||||
b = append(b, peer.GetProfile().Ed25519PrivateKey...)
|
|
||||||
err = ioutil.WriteFile("hs_ed25519_secret_key", b, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = []byte("== ed25519v1-public: type0 ==")
|
|
||||||
b = append(b, peer.GetProfile().Ed25519PublicKey...)
|
|
||||||
err = ioutil.WriteFile("hs_ed25519_public_key", b, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b = []byte(peer.GetProfile().Onion + ".onion\n")
|
|
||||||
err = ioutil.WriteFile("hostname", b, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infoln("success!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertTorFile(filename string, password string) error {
|
func convertTorFile(filename string, password string) error {
|
||||||
return errors.New("this code doesn't work and can never work :( it's a math thing")
|
return errors.New("this code doesn't work and can never work :( it's a math thing")
|
||||||
|
|
||||||
|
@ -77,7 +37,7 @@ func convertTorFile(filename string, password string) error {
|
||||||
peer.GetProfile().Ed25519PrivateKey = sk
|
peer.GetProfile().Ed25519PrivateKey = sk
|
||||||
peer.GetProfile().Ed25519PublicKey = pk
|
peer.GetProfile().Ed25519PublicKey = pk
|
||||||
peer.GetProfile().Onion = string(onion)
|
peer.GetProfile().Onion = string(onion)
|
||||||
fileStore := storage2.CreateFileProfileStore(filename, password)
|
fileStore := storage2.NewFileStore(filename, password)
|
||||||
err = fileStore.Save(peer)
|
err = fileStore.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -87,6 +47,7 @@ func convertTorFile(filename string, password string) error {
|
||||||
return nil*/
|
return nil*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func vanity() error {
|
func vanity() error {
|
||||||
for {
|
for {
|
||||||
pk, sk, err := ed25519.GenerateKey(rand.Reader)
|
pk, sk, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
@ -100,16 +61,14 @@ func vanity() error {
|
||||||
peer.GetProfile().Ed25519PrivateKey = sk
|
peer.GetProfile().Ed25519PrivateKey = sk
|
||||||
peer.GetProfile().Ed25519PublicKey = pk
|
peer.GetProfile().Ed25519PublicKey = pk
|
||||||
peer.GetProfile().Onion = onion
|
peer.GetProfile().Onion = onion
|
||||||
fileStore := storage2.CreateFileProfileStore(os.Args[3], onion+".cwtch")
|
profileStore, _ := storage2.NewProfileStore(nil, os.Args[3], onion+".cwtch")
|
||||||
err := fileStore.Save(peer)
|
profileStore.Init("")
|
||||||
if err != nil {
|
// need to signal new onion? impossible
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("found %s.onion\n", onion)
|
log.Infof("found %s.onion\n", onion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
func printHelp() {
|
func printHelp() {
|
||||||
log.Infoln("usage: cwtchutil {help, convert-cwtch-file, convert-tor-file, changepw, vanity}")
|
log.Infoln("usage: cwtchutil {help, convert-cwtch-file, convert-tor-file, changepw, vanity}")
|
||||||
|
@ -127,15 +86,6 @@ func main() {
|
||||||
printHelp()
|
printHelp()
|
||||||
case "help":
|
case "help":
|
||||||
printHelp()
|
printHelp()
|
||||||
case "convert-cwtch-file":
|
|
||||||
if len(os.Args) != 4 {
|
|
||||||
fmt.Println("example: cwtchutil convert-cwtch-file ~/.cwtch/profiles/11ddd78a9918c064e742d5e36a8b8fd4 passw0rd")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err := convertCwtchFile(os.Args[2], os.Args[3])
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln(err)
|
|
||||||
}
|
|
||||||
case "convert-tor-file":
|
case "convert-tor-file":
|
||||||
if len(os.Args) != 4 {
|
if len(os.Args) != 4 {
|
||||||
fmt.Println("example: cwtchutil convert-tor-file /var/lib/tor/hs1 passw0rd")
|
fmt.Println("example: cwtchutil convert-tor-file /var/lib/tor/hs1 passw0rd")
|
||||||
|
@ -145,7 +95,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
case "vanity":
|
/*case "vanity":
|
||||||
if len(os.Args) < 5 {
|
if len(os.Args) < 5 {
|
||||||
fmt.Println("example: cwtchutil vanity 4 passw0rd erinn openpriv")
|
fmt.Println("example: cwtchutil vanity 4 passw0rd erinn openpriv")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -163,8 +113,8 @@ func main() {
|
||||||
|
|
||||||
for { // run until ctrl+c
|
for { // run until ctrl+c
|
||||||
time.Sleep(time.Hour * 24)
|
time.Sleep(time.Hour * 24)
|
||||||
}
|
}*/
|
||||||
case "changepw":
|
/*case "changepw":
|
||||||
if len(os.Args) != 3 {
|
if len(os.Args) != 3 {
|
||||||
fmt.Println("example: cwtch changepw ~/.cwtch/profiles/XXX")
|
fmt.Println("example: cwtch changepw ~/.cwtch/profiles/XXX")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -179,9 +129,9 @@ func main() {
|
||||||
}
|
}
|
||||||
pw = pw[:len(pw)-1]
|
pw = pw[:len(pw)-1]
|
||||||
|
|
||||||
fileStore := storage2.CreateFileProfileStore(os.Args[2], pw)
|
profileStore, _ := storage.NewProfileStore(nil, os.Args[2], pw)
|
||||||
|
|
||||||
peer, err := fileStore.Load()
|
err = profileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -195,7 +145,8 @@ func main() {
|
||||||
}
|
}
|
||||||
newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea
|
newpw1 = newpw1[:len(newpw1)-1] // fuck go with this linebreak shit ^ea
|
||||||
|
|
||||||
fileStore2 := storage2.CreateFileProfileStore(os.Args[2], newpw1)
|
fileStore2, _ := storage.NewProfileStore(nil, os.Args[2], newpw1)
|
||||||
|
// No way to copy, populate this method
|
||||||
err = fileStore2.Save(peer)
|
err = fileStore2.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
|
@ -203,5 +154,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("success!")
|
log.Infoln("success!")
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ const (
|
||||||
|
|
||||||
SendMessageToPeer = Type("SendMessageToPeer")
|
SendMessageToPeer = Type("SendMessageToPeer")
|
||||||
NewMessageFromPeer = Type("NewMessageFromPeer")
|
NewMessageFromPeer = Type("NewMessageFromPeer")
|
||||||
|
|
||||||
|
SetProfileName = Type("SetProfileName")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Field defines common event attributes
|
// Field defines common event attributes
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
@ -32,8 +33,6 @@ type Profile struct {
|
||||||
Contacts map[string]*PublicProfile
|
Contacts map[string]*PublicProfile
|
||||||
Ed25519PrivateKey ed25519.PrivateKey
|
Ed25519PrivateKey ed25519.PrivateKey
|
||||||
Groups map[string]*Group
|
Groups map[string]*Group
|
||||||
Custom map[string]string
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxGroupMessageLength is the maximum length of a message posted to a server group.
|
// MaxGroupMessageLength is the maximum length of a message posted to a server group.
|
||||||
|
@ -62,6 +61,7 @@ func (p *PublicProfile) GetAttribute(name string) (value string, exists bool) {
|
||||||
// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
|
// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
|
||||||
func GenerateNewProfile(name string) *Profile {
|
func GenerateNewProfile(name string) *Profile {
|
||||||
p := new(Profile)
|
p := new(Profile)
|
||||||
|
p.init()
|
||||||
p.Name = name
|
p.Name = name
|
||||||
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
p.Ed25519PublicKey = pub
|
p.Ed25519PublicKey = pub
|
||||||
|
@ -71,7 +71,6 @@ func GenerateNewProfile(name string) *Profile {
|
||||||
p.Contacts = make(map[string]*PublicProfile)
|
p.Contacts = make(map[string]*PublicProfile)
|
||||||
p.Contacts[p.Onion] = &p.PublicProfile
|
p.Contacts[p.Onion] = &p.PublicProfile
|
||||||
p.Groups = make(map[string]*Group)
|
p.Groups = make(map[string]*Group)
|
||||||
p.Custom = make(map[string]string)
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,21 +148,6 @@ func (p *Profile) GetGroups() []string {
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCustomAttribute allows applications to store arbitrary configuration info at the profile level.
|
|
||||||
func (p *Profile) SetCustomAttribute(name string, value string) {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
p.Custom[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCustomAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false.
|
|
||||||
func (p *Profile) GetCustomAttribute(name string) (value string, exists bool) {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
value, exists = p.Custom[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContacts returns an unordered list of contact onions associated with this profile.
|
// GetContacts returns an unordered list of contact onions associated with this profile.
|
||||||
func (p *Profile) GetContacts() []string {
|
func (p *Profile) GetContacts() []string {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
@ -370,3 +354,14 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte,
|
||||||
}
|
}
|
||||||
return nil, nil, errors.New("group does not exist")
|
return nil, nil, errors.New("group does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCopy returns a full deep copy of the Profile struct and its members
|
||||||
|
func (p *Profile) GetCopy() *Profile {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
newp := new(Profile)
|
||||||
|
bytes, _ := json.Marshal(p)
|
||||||
|
json.Unmarshal(bytes, &newp)
|
||||||
|
return newp
|
||||||
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ func TestProfileIdentity(t *testing.T) {
|
||||||
t.Errorf("alice should be only contact: %v", alice.GetContacts())
|
t.Errorf("alice should be only contact: %v", alice.GetContacts())
|
||||||
}
|
}
|
||||||
|
|
||||||
alice.SetCustomAttribute("test", "hello world")
|
alice.SetAttribute("test", "hello world")
|
||||||
value, _ := alice.GetCustomAttribute("test")
|
value, _ := alice.GetAttribute("test")
|
||||||
if value != "hello world" {
|
if value != "hello world" {
|
||||||
t.Errorf("value from custom attribute should have been 'hello world', instead was: %v", value)
|
t.Errorf("value from custom attribute should have been 'hello world', instead was: %v", value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"cwtch.im/cwtch/server/listen"
|
"cwtch.im/cwtch/server/listen"
|
||||||
"cwtch.im/cwtch/server/metrics"
|
"cwtch.im/cwtch/server/metrics"
|
||||||
"cwtch.im/cwtch/server/send"
|
"cwtch.im/cwtch/server/send"
|
||||||
"cwtch.im/cwtch/storage"
|
"cwtch.im/cwtch/server/storage"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||||
|
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol"
|
||||||
"cwtch.im/cwtch/server/listen"
|
"cwtch.im/cwtch/server/listen"
|
||||||
"cwtch.im/cwtch/storage"
|
"cwtch.im/cwtch/server/storage"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol"
|
||||||
"cwtch.im/cwtch/server/metrics"
|
"cwtch.im/cwtch/server/metrics"
|
||||||
"cwtch.im/cwtch/storage"
|
"cwtch.im/cwtch/server/storage"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package storage
|
|
@ -0,0 +1,23 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileStore stores a cwtchPeer in an encrypted file
|
||||||
|
type fileStore struct {
|
||||||
|
filename string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileStore is a primitive around storing encrypted files
|
||||||
|
type FileStore interface {
|
||||||
|
Save([]byte) error
|
||||||
|
Load() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStore instantiates a fileStore given a filename and a password
|
||||||
|
func NewFileStore(filename string, password string) FileStore {
|
||||||
|
filestore := new(fileStore)
|
||||||
|
filestore.password = password
|
||||||
|
filestore.filename = filename
|
||||||
|
return filestore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save serializes a cwtchPeer to a file
|
||||||
|
func (fps *fileStore) Save(data []byte) error {
|
||||||
|
key, salt, _ := createKey(fps.password)
|
||||||
|
encryptedbytes, err := encryptFileData(data, 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.filename, encryptedbytes, 0600)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//encryptFileData encrypts the cwtchPeer via the specified 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 into a cwtchPeer via the specified 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, fmt.Errorf("Failed to decrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load instantiates a cwtchPeer from the file store
|
||||||
|
func (fps *fileStore) Load() ([]byte, error) {
|
||||||
|
encryptedbytes, err := ioutil.ReadFile(fps.filename)
|
||||||
|
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)
|
||||||
|
|
||||||
|
data, err := decryptFile(encryptedbytes, dkr)
|
||||||
|
if err == nil {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
|
@ -1,11 +1,86 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type profileStore struct {
|
||||||
|
fs FileStore
|
||||||
|
profile *model.Profile
|
||||||
|
eventManager *event.Manager
|
||||||
|
queue *event.Queue
|
||||||
|
}
|
||||||
|
|
||||||
// ProfileStore is an interface to managing the storage of Cwtch Profiles
|
// ProfileStore is an interface to managing the storage of Cwtch Profiles
|
||||||
type ProfileStore interface {
|
type ProfileStore interface {
|
||||||
Save(cwtchPeer peer.CwtchPeer) error
|
Save() error
|
||||||
Load() (peer.CwtchPeer, error)
|
Init(name string)
|
||||||
|
Load() error
|
||||||
|
Shutdown()
|
||||||
|
GetProfileCopy() *model.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProfileStore returns a profile store backed by a filestore listening for events and saving them
|
||||||
|
func NewProfileStore(eventManager *event.Manager, filename string, password string) (ProfileStore, error) {
|
||||||
|
ps := &profileStore{fs: NewFileStore(filename, password), profile: nil, eventManager: eventManager}
|
||||||
|
err := ps.Load()
|
||||||
|
if err == nil {
|
||||||
|
ps.queue = event.NewEventQueue(100)
|
||||||
|
go ps.eventHandler()
|
||||||
|
|
||||||
|
ps.eventManager.Subscribe(event.BlockPeer, ps.queue.EventChannel)
|
||||||
|
}
|
||||||
|
return ps, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *profileStore) Init(name string) {
|
||||||
|
ps.profile = model.GenerateNewProfile(name)
|
||||||
|
ps.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *profileStore) Save() error {
|
||||||
|
bytes, _ := json.Marshal(ps.profile)
|
||||||
|
return ps.fs.Save(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load instantiates a cwtchPeer from the file store
|
||||||
|
func (ps *profileStore) Load() error {
|
||||||
|
decrypted, err := ps.fs.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cp := new(model.Profile)
|
||||||
|
err = json.Unmarshal(decrypted, &cp)
|
||||||
|
if err == nil {
|
||||||
|
ps.profile = cp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *profileStore) GetProfileCopy() *model.Profile {
|
||||||
|
return ps.profile.GetCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *profileStore) eventHandler() {
|
||||||
|
for {
|
||||||
|
ev := ps.queue.Next()
|
||||||
|
switch ev.EventType {
|
||||||
|
case event.BlockPeer:
|
||||||
|
contact, exists := ps.profile.GetContact(ev.Data["Onion"])
|
||||||
|
if exists {
|
||||||
|
contact.Blocked = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.Save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *profileStore) Shutdown() {
|
||||||
|
ps.queue.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue