Browse Source

Merge branch 'storage-eb' of dan/cwtch into master

full_block
Sarah Jamie Lewis Gogs 1 year ago
parent
commit
0d026cda45
17 changed files with 246 additions and 251 deletions
  1. +17
    -20
      app/app.go
  2. +0
    -9
      app/cli/main.go
  3. +17
    -65
      app/cwtchutil/main.go
  4. +2
    -0
      event/common.go
  5. +13
    -18
      model/profile.go
  6. +2
    -2
      model/profile_test.go
  7. +1
    -1
      server/server.go
  8. +1
    -1
      server/server_instance.go
  9. +1
    -1
      server/server_instance_test.go
  10. +0
    -0
      server/storage/message_store.go
  11. +0
    -0
      server/storage/message_store_test.go
  12. +1
    -0
      storage/engine.go
  13. +23
    -0
      storage/file_enc.go
  14. +0
    -109
      storage/file_profile_store.go
  15. +0
    -22
      storage/file_profile_store_test.go
  16. +90
    -0
      storage/file_store.go
  17. +78
    -3
      storage/profile_store.go

+ 17
- 20
app/app.go View File

@@ -29,7 +29,6 @@ type application struct {

// 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)

@@ -59,21 +58,16 @@ 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.Debugf("CreatePeer(%v)\n", name)

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)
err := fileStore.Save(p)
app.eventBus.Publish(event.NewEvent(event.SetProfileName, map[string]string{"Name": name}))
if err != nil {
return nil, err
}
@@ -85,7 +79,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.storage[p.GetProfile().Onion] = profileStore
app.mutex.Unlock()

return p, nil
@@ -99,27 +93,30 @@ func (app *application) LoadProfiles(password string) error {

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 {
continue
}

_, exists := app.peers[p.GetProfile().Onion]
profile := profileStore.GetProfileCopy()
_, exists := app.peers[profile.Onion]
if exists {
p.Shutdown()
log.Errorf("profile for onion %v already exists", p.GetProfile().Onion)
profileStore.Shutdown()
log.Errorf("profile for onion %v already exists", profile.Onion)
continue
}

p.Init(app.acn, app.eventBus)
peer := peer.FromProfile(profile)
peer.Init(app.acn, app.eventBus)

app.mutex.Lock()
app.peers[p.GetProfile().Onion] = p
app.storage[p.GetProfile().Onion] = fileStore
app.peers[profile.Onion] = peer
app.storage[profile.Onion] = profileStore
if app.primaryonion == "" {
app.primaryonion = p.GetProfile().Onion
app.primaryonion = profile.Onion
}
app.mutex.Unlock()
}


+ 0
- 9
app/cli/main.go View File

@@ -548,8 +548,6 @@ func main() {
fmt.Printf("%v", commands)
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
}
case "/save":
app.SaveProfile(peer)
case "/help":
for _, command := range suggestions {
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()


+ 17
- 65
app/cwtchutil/main.go View File

@@ -1,54 +1,14 @@
package main

import (
"bufio"
"crypto/rand"
libpeer "cwtch.im/cwtch/peer"
storage2 "cwtch.im/cwtch/storage"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"

"errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
//"bufio"
//"cwtch.im/cwtch/storage"
)

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 {
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().Ed25519PublicKey = pk
peer.GetProfile().Onion = string(onion)
fileStore := storage2.CreateFileProfileStore(filename, password)
fileStore := storage2.NewFileStore(filename, password)
err = fileStore.Save(peer)
if err != nil {
return err
@@ -87,6 +47,7 @@ func convertTorFile(filename string, password string) error {
return nil*/
}

/*
func vanity() error {
for {
pk, sk, err := ed25519.GenerateKey(rand.Reader)
@@ -100,16 +61,14 @@ func vanity() error {
peer.GetProfile().Ed25519PrivateKey = sk
peer.GetProfile().Ed25519PublicKey = pk
peer.GetProfile().Onion = onion
fileStore := storage2.CreateFileProfileStore(os.Args[3], onion+".cwtch")
err := fileStore.Save(peer)
if err != nil {
return err
}
profileStore, _ := storage2.NewProfileStore(nil, os.Args[3], onion+".cwtch")
profileStore.Init("")
// need to signal new onion? impossible
log.Infof("found %s.onion\n", onion)
}
}
}
}
}*/

func printHelp() {
log.Infoln("usage: cwtchutil {help, convert-cwtch-file, convert-tor-file, changepw, vanity}")
@@ -127,15 +86,6 @@ func main() {
printHelp()
case "help":
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":
if len(os.Args) != 4 {
fmt.Println("example: cwtchutil convert-tor-file /var/lib/tor/hs1 passw0rd")
@@ -145,7 +95,7 @@ func main() {
if err != nil {
log.Errorln(err)
}
case "vanity":
/*case "vanity":
if len(os.Args) < 5 {
fmt.Println("example: cwtchutil vanity 4 passw0rd erinn openpriv")
os.Exit(1)
@@ -163,8 +113,8 @@ func main() {

for { // run until ctrl+c
time.Sleep(time.Hour * 24)
}
case "changepw":
}*/
/*case "changepw":
if len(os.Args) != 3 {
fmt.Println("example: cwtch changepw ~/.cwtch/profiles/XXX")
os.Exit(1)
@@ -179,9 +129,9 @@ func main() {
}
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 {
log.Errorln(err)
os.Exit(1)
@@ -195,7 +145,8 @@ func main() {
}
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)
if err != nil {
log.Errorln(err)
@@ -203,5 +154,6 @@ func main() {
}

log.Infoln("success!")
*/
}
}

+ 2
- 0
event/common.go View File

@@ -24,6 +24,8 @@ const (

SendMessageToPeer = Type("SendMessageToPeer")
NewMessageFromPeer = Type("NewMessageFromPeer")

SetProfileName = Type("SetProfileName")
)

// Field defines common event attributes


+ 13
- 18
model/profile.go View File

@@ -4,6 +4,7 @@ import (
"crypto/rand"
"cwtch.im/cwtch/protocol"
"encoding/base32"
"encoding/json"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"github.com/golang/protobuf/proto"
@@ -32,8 +33,6 @@ type Profile struct {
Contacts map[string]*PublicProfile
Ed25519PrivateKey ed25519.PrivateKey
Groups map[string]*Group
Custom map[string]string
lock sync.Mutex
}

// 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.
func GenerateNewProfile(name string) *Profile {
p := new(Profile)
p.init()
p.Name = name
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
p.Ed25519PublicKey = pub
@@ -71,7 +71,6 @@ func GenerateNewProfile(name string) *Profile {
p.Contacts = make(map[string]*PublicProfile)
p.Contacts[p.Onion] = &p.PublicProfile
p.Groups = make(map[string]*Group)
p.Custom = make(map[string]string)
return p
}

@@ -149,21 +148,6 @@ func (p *Profile) GetGroups() []string {
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.
func (p *Profile) GetContacts() []string {
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")
}

// 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
}

+ 2
- 2
model/profile_test.go View File

@@ -50,8 +50,8 @@ func TestProfileIdentity(t *testing.T) {
t.Errorf("alice should be only contact: %v", alice.GetContacts())
}

alice.SetCustomAttribute("test", "hello world")
value, _ := alice.GetCustomAttribute("test")
alice.SetAttribute("test", "hello world")
value, _ := alice.GetAttribute("test")
if value != "hello world" {
t.Errorf("value from custom attribute should have been 'hello world', instead was: %v", value)
}


+ 1
- 1
server/server.go View File

@@ -5,7 +5,7 @@ import (
"cwtch.im/cwtch/server/listen"
"cwtch.im/cwtch/server/metrics"
"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/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"


+ 1
- 1
server/server_instance.go View File

@@ -3,7 +3,7 @@ package server
import (
"cwtch.im/cwtch/protocol"
"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/channels"
)


+ 1
- 1
server/server_instance_test.go View File

@@ -3,7 +3,7 @@ package server
import (
"cwtch.im/cwtch/protocol"
"cwtch.im/cwtch/server/metrics"
"cwtch.im/cwtch/storage"
"cwtch.im/cwtch/server/storage"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"os"
"testing"


storage/message_store.go → server/storage/message_store.go View File


storage/message_store_test.go → server/storage/message_store_test.go View File


+ 1
- 0
storage/engine.go View File

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

+ 23
- 0
storage/file_enc.go View File

@@ -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
}

+ 0
- 109
storage/file_profile_store.go View File

@@ -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
}

+ 0
- 22
storage/file_profile_store_test.go View File

@@ -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)
}
}

+ 90
- 0
storage/file_store.go View File

@@ -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
}

+ 78
- 3
storage/profile_store.go View File

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

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
type ProfileStore interface {
Save(cwtchPeer peer.CwtchPeer) error
Load() (peer.CwtchPeer, error)
Save() 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…
Cancel
Save