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

Este commit está contenido en:
Sarah Jamie Lewis 2019-01-21 20:23:31 +00:00 cometido por Gogs
commit 0d026cda45
Se han modificado 17 ficheros con 246 adiciones y 251 borrados

Ver fichero

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

Ver fichero

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

Ver fichero

@ -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!")
*/
}
}

Ver fichero

@ -24,6 +24,8 @@ const (
SendMessageToPeer = Type("SendMessageToPeer")
NewMessageFromPeer = Type("NewMessageFromPeer")
SetProfileName = Type("SetProfileName")
)
// Field defines common event attributes

Ver fichero

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

Ver fichero

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

Ver fichero

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

Ver fichero

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

Ver fichero

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

1
storage/engine.go Archivo normal
Ver fichero

@ -0,0 +1 @@
package storage

23
storage/file_enc.go Archivo normal
Ver fichero

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

Ver fichero

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

Ver fichero

@ -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
storage/file_store.go Archivo normal
Ver fichero

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

Ver fichero

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