forked from cwtch.im/cwtch
Merge branch 'storagerefactor' of cwtch.im/cwtch into master
This commit is contained in:
commit
0baaba348b
|
@ -3,8 +3,6 @@
|
||||||
*private_key*
|
*private_key*
|
||||||
*.messages
|
*.messages
|
||||||
*.test
|
*.test
|
||||||
*/*test_*
|
|
||||||
*/*_test*
|
|
||||||
*.json
|
*.json
|
||||||
*/messages/*
|
*/messages/*
|
||||||
server/app/messages
|
server/app/messages
|
||||||
|
|
36
app/app.go
36
app/app.go
|
@ -4,8 +4,10 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/connectivity/tor"
|
"cwtch.im/cwtch/connectivity/tor"
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
|
"cwtch.im/cwtch/storage"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -20,10 +22,12 @@ type application struct {
|
||||||
directory string
|
directory string
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
primaryonion string
|
primaryonion string
|
||||||
|
storage map[string]storage.ProfileStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
|
@ -39,7 +43,7 @@ type Application interface {
|
||||||
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
||||||
func NewApp(appDirectory string, torPath string) (Application, error) {
|
func NewApp(appDirectory string, torPath string) (Application, error) {
|
||||||
log.Printf("NewApp(%v, %v)\n", appDirectory, torPath)
|
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.MkdirAll(path.Join(appDirectory, "tor"), 0700)
|
||||||
os.Mkdir(path.Join(app.directory, "profiles"), 0700)
|
os.Mkdir(path.Join(app.directory, "profiles"), 0700)
|
||||||
|
|
||||||
|
@ -56,22 +60,25 @@ 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.Printf("CreatePeer(%v)\n", name)
|
log.Printf("CreatePeer(%v)\n", name)
|
||||||
|
|
||||||
randomFileName := generateRandomFilename()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
app.startPeer(p)
|
||||||
|
|
||||||
_, exists := app.peers[p.GetProfile().Onion]
|
_, exists := app.peers[p.GetProfile().Onion]
|
||||||
if exists {
|
if exists {
|
||||||
p.Shutdown()
|
p.Shutdown()
|
||||||
|
@ -79,6 +86,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.mutex.Unlock()
|
app.mutex.Unlock()
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
|
@ -91,10 +99,15 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
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 {
|
if err != nil {
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := app.peers[p.GetProfile().Onion]
|
_, exists := app.peers[p.GetProfile().Onion]
|
||||||
if exists {
|
if exists {
|
||||||
p.Shutdown()
|
p.Shutdown()
|
||||||
|
@ -104,6 +117,7 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
app.startPeer(p)
|
app.startPeer(p)
|
||||||
app.mutex.Lock()
|
app.mutex.Lock()
|
||||||
app.peers[p.GetProfile().Onion] = p
|
app.peers[p.GetProfile().Onion] = p
|
||||||
|
app.storage[p.GetProfile().Onion] = fileStore
|
||||||
if app.primaryonion == "" {
|
if app.primaryonion == "" {
|
||||||
app.primaryonion = p.GetProfile().Onion
|
app.primaryonion = p.GetProfile().Onion
|
||||||
}
|
}
|
||||||
|
@ -155,12 +169,12 @@ func (app *application) ListPeers() map[string]string {
|
||||||
return keys
|
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 {
|
func (app *application) PrimaryIdentity() peer.CwtchPeer {
|
||||||
return app.peers[app.primaryonion]
|
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 {
|
func (app *application) GetPeer(onion string) peer.CwtchPeer {
|
||||||
if peer, ok := app.peers[onion]; ok {
|
if peer, ok := app.peers[onion]; ok {
|
||||||
return peer
|
return peer
|
||||||
|
|
|
@ -304,9 +304,6 @@ func main() {
|
||||||
|
|
||||||
switch commands[0] {
|
switch commands[0] {
|
||||||
case "/quit":
|
case "/quit":
|
||||||
if peer != nil {
|
|
||||||
peer.Save()
|
|
||||||
}
|
|
||||||
quit = true
|
quit = true
|
||||||
case "/new-profile":
|
case "/new-profile":
|
||||||
if len(commands) == 2 {
|
if len(commands) == 2 {
|
||||||
|
@ -377,7 +374,7 @@ func main() {
|
||||||
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto Peer / Join Server
|
// Auto cwtchPeer / Join Server
|
||||||
// TODO There are some privacy implications with this that we should
|
// TODO There are some privacy implications with this that we should
|
||||||
// think over.
|
// think over.
|
||||||
for _, name := range p.GetProfile().GetContacts() {
|
for _, name := range p.GetProfile().GetContacts() {
|
||||||
|
@ -450,7 +447,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
peer.Save()
|
|
||||||
group := peer.GetGroup(groupID)
|
group := peer.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
fmt.Printf("Error: group does not exist\n")
|
||||||
|
@ -477,7 +473,6 @@ func main() {
|
||||||
id, _, err := peer.StartGroup(commands[1])
|
id, _, err := peer.StartGroup(commands[1])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
fmt.Printf("New Group [%v] created for server %v\n", id, commands[1])
|
||||||
peer.Save()
|
|
||||||
group := peer.GetGroup(id)
|
group := peer.GetGroup(id)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
fmt.Printf("Error: group does not exist\n")
|
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]])
|
fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]])
|
||||||
}
|
}
|
||||||
case "/save":
|
case "/save":
|
||||||
peer.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])
|
||||||
|
@ -593,10 +588,14 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if peer != nil {
|
if peer != nil {
|
||||||
peer.Save()
|
app.SaveProfile(peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if peer != nil {
|
||||||
|
app.SaveProfile(peer)
|
||||||
|
}
|
||||||
|
|
||||||
app.Shutdown()
|
app.Shutdown()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
libpeer "cwtch.im/cwtch/peer"
|
libpeer "cwtch.im/cwtch/peer"
|
||||||
|
storage2 "cwtch.im/cwtch/storage"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
|
|
||||||
"github.com/sethvargo/go-diceware/diceware"
|
"github.com/sethvargo/go-diceware/diceware"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -17,7 +19,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertCwtchFile(filename string, password string) {
|
func convertCwtchFile(filename string, password string) {
|
||||||
peer, err := libpeer.LoadCwtchPeer(filename, password)
|
fileStore := storage2.CreateFileProfileStore(filename, password)
|
||||||
|
peer, err := fileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
@ -67,16 +70,17 @@ func convertTorFile(filename string, password string) {
|
||||||
}
|
}
|
||||||
onion = onion[:56]
|
onion = onion[:56]
|
||||||
|
|
||||||
peer, err := libpeer.NewCwtchPeer(strings.Join(name, "-"), password, filename)
|
peer := libpeer.NewCwtchPeer(strings.Join(name, "-"))
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%d %d %s\n", len(peer.GetProfile().Ed25519PublicKey), len(peer.GetProfile().Ed25519PrivateKey), peer.GetProfile().Onion)
|
fmt.Printf("%d %d %s\n", len(peer.GetProfile().Ed25519PublicKey), len(peer.GetProfile().Ed25519PrivateKey), peer.GetProfile().Onion)
|
||||||
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)
|
||||||
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)
|
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)
|
onion := utils.GetTorV3Hostname(pk)
|
||||||
for i := 4; i < len(os.Args); i++ {
|
for i := 4; i < len(os.Args); i++ {
|
||||||
if strings.HasPrefix(onion, os.Args[i]) {
|
if strings.HasPrefix(onion, os.Args[i]) {
|
||||||
peer, err := libpeer.NewCwtchPeer(os.Args[i], os.Args[3], onion+".cwtch")
|
peer := libpeer.NewCwtchPeer(os.Args[i])
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
peer.GetProfile().Ed25519PrivateKey = sk
|
peer.GetProfile().Ed25519PrivateKey = sk
|
||||||
peer.GetProfile().Ed25519PublicKey = pk
|
peer.GetProfile().Ed25519PublicKey = pk
|
||||||
peer.GetProfile().Onion = onion
|
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)
|
log.Printf("found %s.onion\n", onion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +168,9 @@ func main() {
|
||||||
}
|
}
|
||||||
pw = pw[:len(pw)-1]
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
@ -175,12 +182,13 @@ 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
|
||||||
|
|
||||||
err = peer.ChangePassword(newpw1)
|
fileStore2 := storage2.CreateFileProfileStore(os.Args[2], newpw1)
|
||||||
|
err = fileStore2.Save(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("success!")
|
log.Println("success!")
|
||||||
peer.Save()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
alice, _ := peer.NewCwtchPeer("alice", "password", ".")
|
alice := peer.NewCwtchPeer("alice")
|
||||||
|
|
||||||
processData := func(onion string, data []byte) []byte {
|
processData := func(onion string, data []byte) []byte {
|
||||||
log.Printf("Recieved %s from %v", data, onion)
|
log.Printf("Recieved %s from %v", data, onion)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bob, _ := peer.NewCwtchPeer("bob", "password", ".")
|
bob := peer.NewCwtchPeer("bob")
|
||||||
counter := 1
|
counter := 1
|
||||||
|
|
||||||
bob.SetPeerDataHandler(func(onion string, data []byte) []byte {
|
bob.SetPeerDataHandler(func(onion string, data []byte) []byte {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerPeerConnection encapsulates a single outgoing Peer->Peer connection
|
// PeerPeerConnection encapsulates a single outgoing cwtchPeer->cwtchPeer connection
|
||||||
type PeerPeerConnection struct {
|
type PeerPeerConnection struct {
|
||||||
connection.AutoConnectionHandler
|
connection.AutoConnectionHandler
|
||||||
PeerHostname string
|
PeerHostname string
|
||||||
|
|
|
@ -126,7 +126,9 @@ func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) err
|
||||||
// Close shuts down the connection (freeing the handler goroutines)
|
// Close shuts down the connection (freeing the handler goroutines)
|
||||||
func (psc *PeerServerConnection) Close() {
|
func (psc *PeerServerConnection) Close() {
|
||||||
psc.state = KILLED
|
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.
|
// HandleGroupMessage passes the given group message back to the profile.
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package peer
|
package peer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"cwtch.im/cwtch/model"
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/peer/connections"
|
"cwtch.im/cwtch/peer/connections"
|
||||||
"cwtch.im/cwtch/peer/peer"
|
"cwtch.im/cwtch/peer/peer"
|
||||||
"cwtch.im/cwtch/protocol"
|
"cwtch.im/cwtch/protocol"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/application"
|
"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/identity"
|
||||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/ulule/deepcopier"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
"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"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"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 {
|
type cwtchPeer struct {
|
||||||
connection.AutoConnectionHandler
|
connection.AutoConnectionHandler
|
||||||
Profile *model.Profile
|
Profile *model.Profile
|
||||||
app *application.RicochetApplication
|
app *application.RicochetApplication
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
Log chan string `json:"-"`
|
|
||||||
connectionsManager *connections.Manager
|
connectionsManager *connections.Manager
|
||||||
profilefile string
|
|
||||||
key [32]byte
|
|
||||||
salt [128]byte
|
|
||||||
dataHandler func(string, []byte) []byte
|
dataHandler func(string, []byte) []byte
|
||||||
//handlers map[string]func(*application.ApplicationInstance) func() channels.Handler
|
//handlers map[string]func(*application.ApplicationInstance) func() channels.Handler
|
||||||
aif application.ApplicationInstanceFactory
|
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
|
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
||||||
// directly implement a cwtchPeer.
|
// directly implement a cwtchPeer.
|
||||||
type CwtchPeer interface {
|
type CwtchPeer interface {
|
||||||
Save() error
|
Init()
|
||||||
ChangePassword(string) error
|
|
||||||
PeerWithOnion(string) *connections.PeerPeerConnection
|
PeerWithOnion(string) *connections.PeerPeerConnection
|
||||||
InviteOnionToGroup(string, string) error
|
InviteOnionToGroup(string, string) error
|
||||||
|
|
||||||
|
@ -83,132 +70,26 @@ type CwtchPeer interface {
|
||||||
Shutdown()
|
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.
|
// 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 := new(cwtchPeer)
|
||||||
cp.profilefile = profilefile
|
|
||||||
cp.Profile = model.GenerateNewProfile(name)
|
cp.Profile = model.GenerateNewProfile(name)
|
||||||
cp.setup()
|
cp.Init()
|
||||||
key, salt, err := createKey(password)
|
return cp
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cp.key = key
|
|
||||||
cp.salt = salt
|
|
||||||
return cp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *cwtchPeer) ChangePassword(password string) error {
|
// FromProfile generates a new peer from a profile.
|
||||||
key, salt, err := createKey(password)
|
func FromProfile(profile *model.Profile) CwtchPeer {
|
||||||
if err != nil {
|
cp := new(cwtchPeer)
|
||||||
return err
|
cp.Profile = profile
|
||||||
}
|
cp.Init()
|
||||||
cp.key = key
|
return cp
|
||||||
cp.salt = salt
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves the cwtchPeer profile state to a file.
|
// Init instantiates a cwtchPeer
|
||||||
func (cp *cwtchPeer) Save() error {
|
func (cp *cwtchPeer) Init() {
|
||||||
cp.mutex.Lock()
|
cp.connectionsManager = connections.NewConnectionsManager()
|
||||||
defer cp.mutex.Unlock()
|
go cp.connectionsManager.AttemptReconnections()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportGroup intializes a group from an imported source rather than a peer invite
|
// 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
|
// 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
|
// 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
|
// 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) {
|
func (cp *cwtchPeer) SetPeerDataHandler(dataHandler func(string, []byte) []byte) {
|
||||||
cp.dataHandler = dataHandler
|
cp.dataHandler = dataHandler
|
||||||
}
|
}
|
||||||
|
@ -292,7 +174,7 @@ func (cp *cwtchPeer) GetContact(onion string) *model.PublicProfile {
|
||||||
return contact
|
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.
|
// 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 {
|
func (cp *cwtchPeer) GetProfile() *model.Profile {
|
||||||
return cp.Profile
|
return cp.Profile
|
||||||
|
@ -402,7 +284,7 @@ func (cp *cwtchPeer) LookupContact(hostname string, publicKey rsa.PublicKey) (al
|
||||||
return !blocked, true
|
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) {
|
func (cp *cwtchPeer) LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||||
blocked := cp.Profile.IsBlocked(hostname)
|
blocked := cp.Profile.IsBlocked(hostname)
|
||||||
return !blocked, true
|
return !blocked, true
|
||||||
|
@ -463,7 +345,6 @@ func (cp *cwtchPeer) Shutdown() {
|
||||||
if cp.app != nil {
|
if cp.app != nil {
|
||||||
cp.app.Shutdown()
|
cp.app.Shutdown()
|
||||||
}
|
}
|
||||||
cp.Save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CwtchPeerInstance encapsulates incoming peer connections
|
// CwtchPeerInstance encapsulates incoming peer connections
|
||||||
|
@ -489,7 +370,6 @@ type CwtchPeerHandler struct {
|
||||||
func (cph *CwtchPeerHandler) ClientIdentity(ci *protocol.CwtchIdentity) {
|
func (cph *CwtchPeerHandler) ClientIdentity(ci *protocol.CwtchIdentity) {
|
||||||
log.Printf("Received Client Identity from %v %v\n", cph.Onion, ci.String())
|
log.Printf("Received Client Identity from %v %v\n", cph.Onion, ci.String())
|
||||||
cph.Peer.Profile.AddCwtchIdentity(cph.Onion, ci)
|
cph.Peer.Profile.AddCwtchIdentity(cph.Onion, ci)
|
||||||
cph.Peer.Save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleGroupInvite handles incoming GroupInvites
|
// HandleGroupInvite handles incoming GroupInvites
|
||||||
|
@ -503,7 +383,7 @@ func (cph *CwtchPeerHandler) GetClientIdentityPacket() []byte {
|
||||||
return cph.Peer.Profile.GetCwtchIdentityPacket()
|
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 {
|
func (cph *CwtchPeerHandler) HandlePacket(data []byte) []byte {
|
||||||
return cph.DataHandler(cph.Onion, data)
|
return cph.DataHandler(cph.Onion, data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,11 @@ import (
|
||||||
|
|
||||||
func TestCwtchPeerGenerate(t *testing.T) {
|
func TestCwtchPeerGenerate(t *testing.T) {
|
||||||
|
|
||||||
alice, _ := NewCwtchPeer("alice", "testpass", "./alice.json")
|
alice := NewCwtchPeer("alice")
|
||||||
alice.Save()
|
|
||||||
|
|
||||||
aliceLoaded, err := LoadCwtchPeer("./alice.json", "testpass")
|
groupID, _, _ := alice.StartGroup("test.server")
|
||||||
if err != nil || aliceLoaded.GetProfile().Name != "alice" {
|
exportedGroup, _ := alice.ExportGroup(groupID)
|
||||||
t.Errorf("something went wrong saving and loading profiles %v %v", err, aliceLoaded)
|
t.Logf("Exported Group: %v from %v", exportedGroup, alice.GetProfile().Onion)
|
||||||
}
|
|
||||||
|
|
||||||
groupID, _, _ := aliceLoaded.StartGroup("test.server")
|
|
||||||
exportedGroup, _ := aliceLoaded.ExportGroup(groupID)
|
|
||||||
t.Logf("Exported Group: %v from %v", exportedGroup, aliceLoaded.GetProfile().Onion)
|
|
||||||
|
|
||||||
importedGroupID, err := alice.ImportGroup(exportedGroup)
|
importedGroupID, err := alice.ImportGroup(exportedGroup)
|
||||||
group := alice.GetGroup(importedGroupID)
|
group := alice.GetGroup(importedGroupID)
|
||||||
|
@ -26,8 +20,8 @@ func TestCwtchPeerGenerate(t *testing.T) {
|
||||||
|
|
||||||
func TestTrustPeer(t *testing.T) {
|
func TestTrustPeer(t *testing.T) {
|
||||||
groupName := "test.server"
|
groupName := "test.server"
|
||||||
alice, _ := NewCwtchPeer("alice", "alicepass", "")
|
alice := NewCwtchPeer("alice")
|
||||||
bob, _ := NewCwtchPeer("bob", "bobpass", "")
|
bob := NewCwtchPeer("bob")
|
||||||
|
|
||||||
bobOnion := bob.GetProfile().Onion
|
bobOnion := bob.GetProfile().Onion
|
||||||
aliceOnion := alice.GetProfile().Onion
|
aliceOnion := alice.GetProfile().Onion
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,11 @@
|
||||||
package storage
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -101,20 +101,20 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
|
|
||||||
numGoRoutinesPostServer := runtime.NumGoroutine()
|
numGoRoutinesPostServer := runtime.NumGoroutine()
|
||||||
|
|
||||||
// ***** Peer setup *****
|
// ***** cwtchPeer setup *****
|
||||||
|
|
||||||
fmt.Println("Creating Alice...")
|
fmt.Println("Creating Alice...")
|
||||||
alice, _ := peer.NewCwtchPeer("Alice", "alicepass", "")
|
alice := peer.NewCwtchPeer("Alice")
|
||||||
go alice.Listen()
|
go alice.Listen()
|
||||||
fmt.Println("Alice created:", alice.GetProfile().Onion)
|
fmt.Println("Alice created:", alice.GetProfile().Onion)
|
||||||
|
|
||||||
fmt.Println("Creating Bob...")
|
fmt.Println("Creating Bob...")
|
||||||
bob, _ := peer.NewCwtchPeer("Bob", "bobpass", "")
|
bob := peer.NewCwtchPeer("Bob")
|
||||||
go bob.Listen()
|
go bob.Listen()
|
||||||
fmt.Println("Bob created:", bob.GetProfile().Onion)
|
fmt.Println("Bob created:", bob.GetProfile().Onion)
|
||||||
|
|
||||||
fmt.Println("Creating Carol...")
|
fmt.Println("Creating Carol...")
|
||||||
carol, _ := peer.NewCwtchPeer("Carol", "carolpass", "")
|
carol := peer.NewCwtchPeer("Carol")
|
||||||
go carol.Listen()
|
go carol.Listen()
|
||||||
fmt.Println("Carol created:", carol.GetProfile().Onion)
|
fmt.Println("Carol created:", carol.GetProfile().Onion)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue