diff --git a/.gitignore b/.gitignore index e95809b..64ff59e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ *.test */*test_* */*_test* +*.json +*/messages/* +server/app/messages +.reviewboardrc diff --git a/app/cli/main.go b/app/cli/main.go index 279a34c..d6001e1 100644 --- a/app/cli/main.go +++ b/app/cli/main.go @@ -80,11 +80,6 @@ var usages = map[string]string{ } func printMessage(m model.Message) { - verified := "not-verified" - if m.Verified { - verified = "verified" - } - p := peer.GetContact(m.PeerID) name := "unknown" if p != nil { @@ -93,7 +88,7 @@ func printMessage(m model.Message) { name = peer.GetProfile().Name } - fmt.Printf("%v %v (%v): %v [%s]\n", m.Timestamp, name, m.PeerID, m.Message, verified) + fmt.Printf("%v %v (%v): %v\n", m.Timestamp, name, m.PeerID, m.Message) } func startGroupFollow() { @@ -365,6 +360,7 @@ func main() { } else { fmt.Printf("\nError loading profiles: %v\n", err) } + case "/list-profiles": peerlist := app.ListPeers() for onion, peername := range peerlist { @@ -380,6 +376,24 @@ func main() { peer = p suggestions = append(suggestionsBase, suggestionsSelectedProfile...) } + + // Auto Peer / Join Server + // TODO There are some privacy implications with this that we should + // think over. + for _, name := range p.GetProfile().GetContacts() { + profile := p.GetContact(name) + if profile.Trusted && !profile.Blocked { + p.PeerWithOnion(profile.Onion) + } + } + + for _, groupid := range p.GetGroups() { + group := p.GetGroup(groupid) + if group.Accepted || group.Owner == "self" { + p.JoinServer(group.GroupServer) + } + } + } else { fmt.Printf("Error selecting profile, usage: %s\n", usages[commands[0]]) } diff --git a/app/peer/alice/alice.go b/app/peer/alice/alice.go index f0e678c..c18bca4 100644 --- a/app/peer/alice/alice.go +++ b/app/peer/alice/alice.go @@ -13,7 +13,6 @@ func main() { return data } - alice.SetPeerDataHandler(processData) alice.Listen() } diff --git a/app/peer/bob/bob.go b/app/peer/bob/bob.go index a1e8b53..bd594bc 100644 --- a/app/peer/bob/bob.go +++ b/app/peer/bob/bob.go @@ -16,7 +16,7 @@ func main() { counter++ return []byte(strconv.Itoa(counter)) }) - connection := bob.PeerWithOnion("qtpnmnth767gjmpv") + connection := bob.PeerWithOnion("f4b6thuwmfszsqd3fzqpr45sdem4qoazdlzr2xmnc7fq22qe746hjqqd") log.Printf("Waiting for Bob to Connect to Alice...") connection.SendPacket([]byte("Hello Alice!!!")) diff --git a/model/group.go b/model/group.go index 5ef655a..b43fbcc 100644 --- a/model/group.go +++ b/model/group.go @@ -14,7 +14,7 @@ import ( "time" ) -//Group defines and encapsulates Cwtch's conception of group chat. Which are sessions +// Group defines and encapsulates Cwtch's conception of group chat. Which are sessions // tied to a server under a given group key. Each group has a set of messages. type Group struct { GroupID string @@ -27,6 +27,7 @@ type Group struct { IsCompromised bool InitialMessage []byte lock sync.Mutex + NewMessage chan Message `json:"-"` } // NewGroup initializes a new group associated with a given CwtchServer @@ -94,19 +95,23 @@ func (g *Group) Invite(initialMessage []byte) ([]byte, error) { } // AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline -func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte, verified bool) *Message { +func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) *Message { g.lock.Lock() timelineMessage := &Message{ Message: message.GetText(), Timestamp: time.Unix(int64(message.GetTimestamp()), 0), Received: time.Now(), Signature: sig, - Verified: verified, PeerID: message.GetOnion(), PreviousMessageSig: message.GetPreviousMessageSig(), } - g.Timeline.Insert(timelineMessage) + seen := g.Timeline.Insert(timelineMessage) g.lock.Unlock() + + // Send a new Message notification if we have an app that is listening. + if g.NewMessage != nil && !seen { + g.NewMessage <- *timelineMessage + } return timelineMessage } diff --git a/model/message.go b/model/message.go index d3fa4e3..4ce71f0 100644 --- a/model/message.go +++ b/model/message.go @@ -21,7 +21,6 @@ type Message struct { PeerID string Message string Signature []byte - Verified bool PreviousMessageSig []byte } @@ -75,17 +74,18 @@ func (t *Timeline) Less(i, j int) bool { } // Insert inserts a message into the timeline in a thread safe way. -func (t *Timeline) Insert(mi *Message) { +func (t *Timeline) Insert(mi *Message) bool { t.lock.Lock() defer t.lock.Unlock() for _, m := range t.Messages { // If the message already exists, then we don't add it if compareSignatures(m.Signature, mi.Signature) { - return + return true } } t.Messages = append(t.Messages, *mi) sort.Sort(t) + return false } diff --git a/model/profile.go b/model/profile.go index c510899..0563cb0 100644 --- a/model/profile.go +++ b/model/profile.go @@ -2,14 +2,14 @@ package model import ( "crypto/rand" - "crypto/rsa" "cwtch.im/cwtch/protocol" - "encoding/asn1" + "encoding/base32" "errors" "git.openprivacy.ca/openprivacy/libricochet-go/utils" "github.com/golang/protobuf/proto" "golang.org/x/crypto/ed25519" "io" + "strings" "sync" "time" ) @@ -28,7 +28,6 @@ type Profile struct { PublicProfile Contacts map[string]*PublicProfile Ed25519PrivateKey ed25519.PrivateKey - OnionPrivateKey *rsa.PrivateKey Groups map[string]*Group Custom map[string]string lock sync.Mutex @@ -41,14 +40,7 @@ func GenerateNewProfile(name string) *Profile { pub, priv, _ := ed25519.GenerateKey(rand.Reader) p.Ed25519PublicKey = pub p.Ed25519PrivateKey = priv - - p.OnionPrivateKey, _ = utils.GeneratePrivateKey() - // DER Encode the Public Key - publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ - N: p.OnionPrivateKey.PublicKey.N, - E: p.OnionPrivateKey.PublicKey.E, - }) - p.Onion = utils.GetTorHostname(publicKeyBytes) + p.Onion = utils.GetTorV3Hostname(pub) p.Contacts = make(map[string]*PublicProfile) p.Contacts[p.Onion] = &p.PublicProfile @@ -199,10 +191,10 @@ func (p *Profile) VerifyGroupMessage(onion string, groupID string, message strin return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature) } - contact, found := p.GetContact(onion) - if found { - m := groupID + group.GroupServer + string(ciphertext) - return ed25519.Verify(contact.Ed25519PublicKey, []byte(m), signature) + m := groupID + group.GroupServer + string(ciphertext) + decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) + if err == nil { + return ed25519.Verify(decodedPub[:32], []byte(m), signature) } return false } @@ -213,13 +205,13 @@ func (p *Profile) SignMessage(message string) []byte { return sig } -//StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed +// StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed // invite which can be sent on the wire. func (p *Profile) StartGroup(server string) (groupID string, invite []byte, err error) { return p.StartGroupWithMessage(server, []byte{}) } -//StartGroupWithMessage when given a server, and an initial message creates a new Group under this profile and returns the group id an a precomputed +// StartGroupWithMessage when given a server, and an initial message creates a new Group under this profile and returns the group id an a precomputed // invite which can be sent on the wire. func (p *Profile) StartGroupWithMessage(server string, initialMessage []byte) (groupID string, invite []byte, err error) { group, err := NewGroup(server) @@ -301,7 +293,16 @@ func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool, } verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), ciphertext, signature) - return true, group.AddMessage(dgm, signature, verified) + + // So we have a message that has a valid group key, but the signature can't be verified. + // The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious) + // Either way, someone who has the private key is being detectably bad so we are just going to throw this message away and mark the group as Compromised. + if !verified { + group.Compromised() + return false, nil + } + + return true, group.AddMessage(dgm, signature) } } return false, nil diff --git a/model/profile_test.go b/model/profile_test.go index d859594..4f40fab 100644 --- a/model/profile_test.go +++ b/model/profile_test.go @@ -133,8 +133,8 @@ func TestProfileGroup(t *testing.T) { c3, s3, err := bob.EncryptMessageToGroup("Bobs Message", group2.GroupID) if err == nil { ok, message := alice.AttemptDecryption(c3, s3) - if ok != true || message.Verified == true { - t.Errorf("Bobs message to the group should be decrypted but not verified by alice instead %v %v", message, ok) + if !ok { + t.Errorf("Bobs message to the group should be decrypted %v %v", message, ok) } eve := GenerateNewProfile("eve") diff --git a/peer/connections/peerpeerconnection.go b/peer/connections/peerpeerconnection.go index 21f3cb2..04124e1 100644 --- a/peer/connections/peerpeerconnection.go +++ b/peer/connections/peerpeerconnection.go @@ -107,7 +107,7 @@ func (ppc *PeerPeerConnection) Run() error { rc.TraceLog(false) ppc.connection = rc ppc.state = CONNECTED - _, err := connection.HandleOutboundConnection(ppc.connection).ProcessAuthAsClient(identity.Initialize(ppc.profile.Name, ppc.profile.OnionPrivateKey)) + _, err := connection.HandleOutboundConnection(ppc.connection).ProcessAuthAsV3Client(identity.InitializeV3(ppc.profile.Name, &ppc.profile.Ed25519PrivateKey, &ppc.profile.Ed25519PublicKey)) if err == nil { ppc.state = AUTHENTICATED go func() { diff --git a/peer/connections/peerpeerconnection_test.go b/peer/connections/peerpeerconnection_test.go index 50925c6..eab486c 100644 --- a/peer/connections/peerpeerconnection_test.go +++ b/peer/connections/peerpeerconnection_test.go @@ -1,7 +1,7 @@ package connections import ( - "crypto/rsa" + "crypto/rand" "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer/peer" "cwtch.im/cwtch/protocol" @@ -9,17 +9,17 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/identity" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" + "golang.org/x/crypto/ed25519" "net" "testing" "time" ) -func PeerAuthValid(string, rsa.PublicKey) (allowed, known bool) { +func PeerAuthValid(hostname string, key ed25519.PublicKey) (allowed, known bool) { return true, true } -func runtestpeer(t *testing.T, tp *TestPeer, privateKey *rsa.PrivateKey) { +func runtestpeer(t *testing.T, tp *TestPeer, identity identity.Identity) { ln, _ := net.Listen("tcp", "127.0.0.1:5452") conn, _ := ln.Accept() defer conn.Close() @@ -29,7 +29,7 @@ func runtestpeer(t *testing.T, tp *TestPeer, privateKey *rsa.PrivateKey) { t.Errorf("Negotiate Version Error: %v", err) } rc.TraceLog(true) - err = connection.HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), PeerAuthValid) + err = connection.HandleInboundConnection(rc).ProcessAuthAsV3Server(identity, PeerAuthValid) if err != nil { t.Errorf("ServerAuth Error: %v", err) } @@ -76,21 +76,16 @@ func (tp *TestPeer) GetClientIdentityPacket() []byte { } func TestPeerPeerConnection(t *testing.T) { - privateKey, err := utils.GeneratePrivateKey() - if err != nil { - t.Errorf("Private Key Error %v", err) - } - onionAddr, err := utils.GetOnionAddress(privateKey) - if err != nil { - t.Errorf("Onion address error %v", err) - } + pub, priv, _ := ed25519.GenerateKey(rand.Reader) + identity := identity.InitializeV3("", &priv, &pub) profile := model.GenerateNewProfile("alice") - ppc := NewPeerPeerConnection("127.0.0.1:5452|"+onionAddr, profile, nil) - //numcalls := 0 + hostname := identity.Hostname() + ppc := NewPeerPeerConnection("127.0.0.1:5452|"+hostname, profile, nil) + tp := new(TestPeer) tp.Init() - go runtestpeer(t, tp, privateKey) + go runtestpeer(t, tp, identity) state := ppc.GetState() if state != DISCONNECTED { t.Errorf("new connections should start in disconnected state") diff --git a/peer/connections/peerserverconnection.go b/peer/connections/peerserverconnection.go index 52d73e2..f8611a6 100644 --- a/peer/connections/peerserverconnection.go +++ b/peer/connections/peerserverconnection.go @@ -1,6 +1,7 @@ package connections import ( + "crypto/rand" "cwtch.im/cwtch/peer/fetch" "cwtch.im/cwtch/peer/listen" "cwtch.im/cwtch/peer/send" @@ -10,7 +11,7 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/identity" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" + "golang.org/x/crypto/ed25519" "log" "time" ) @@ -46,9 +47,9 @@ func (psc *PeerServerConnection) Run() error { rc.TraceLog(true) psc.connection = rc psc.state = CONNECTED - pk, err := utils.GeneratePrivateKey() + pub, priv, _ := ed25519.GenerateKey(rand.Reader) if err == nil { - _, err := connection.HandleOutboundConnection(psc.connection).ProcessAuthAsClient(identity.Initialize("cwtchpeer", pk)) + _, err := connection.HandleOutboundConnection(psc.connection).ProcessAuthAsV3Client(identity.InitializeV3("cwtchpeer", &priv, &pub)) if err == nil { psc.state = AUTHENTICATED diff --git a/peer/connections/peerserverconnection_test.go b/peer/connections/peerserverconnection_test.go index 36829e3..e640706 100644 --- a/peer/connections/peerserverconnection_test.go +++ b/peer/connections/peerserverconnection_test.go @@ -1,7 +1,7 @@ package connections import ( - "crypto/rsa" + "crypto/rand" "cwtch.im/cwtch/protocol" "cwtch.im/cwtch/server/fetch" "cwtch.im/cwtch/server/send" @@ -9,13 +9,13 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/identity" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" + "golang.org/x/crypto/ed25519" "net" "testing" "time" ) -func ServerAuthValid(string, rsa.PublicKey) (allowed, known bool) { +func ServerAuthValid(hostname string, key ed25519.PublicKey) (allowed, known bool) { return true, true } @@ -32,7 +32,7 @@ func (ts *TestServer) HandleFetchRequest() []*protocol.GroupMessage { return []*protocol.GroupMessage{{Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}, {Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}} } -func runtestserver(t *testing.T, ts *TestServer, privateKey *rsa.PrivateKey) { +func runtestserver(t *testing.T, ts *TestServer, identity identity.Identity) { ln, _ := net.Listen("tcp", "127.0.0.1:5451") conn, _ := ln.Accept() defer conn.Close() @@ -42,7 +42,7 @@ func runtestserver(t *testing.T, ts *TestServer, privateKey *rsa.PrivateKey) { t.Errorf("Negotiate Version Error: %v", err) } rc.TraceLog(true) - err = connection.HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid) + err = connection.HandleInboundConnection(rc).ProcessAuthAsV3Server(identity, ServerAuthValid) if err != nil { t.Errorf("ServerAuth Error: %v", err) } @@ -63,18 +63,15 @@ func runtestserver(t *testing.T, ts *TestServer, privateKey *rsa.PrivateKey) { } func TestPeerServerConnection(t *testing.T) { - privateKey, err := utils.GeneratePrivateKey() - if err != nil { - t.Errorf("Private Key Error %v", err) - } + pub, priv, _ := ed25519.GenerateKey(rand.Reader) + + identity := identity.InitializeV3("", &priv, &pub) ts := new(TestServer) ts.Init() - go runtestserver(t, ts, privateKey) - onionAddr, err := utils.GetOnionAddress(privateKey) - if err != nil { - t.Errorf("Error getting onion address: %v", err) - } + go runtestserver(t, ts, identity) + onionAddr := identity.Hostname() + psc := NewPeerServerConnection("127.0.0.1:5451|" + onionAddr) numcalls := 0 psc.GroupMessageHandler = func(s string, gm *protocol.GroupMessage) { diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index c1f127a..242b6c9 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -14,6 +14,8 @@ import ( "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/connection" + "git.openprivacy.ca/openprivacy/libricochet-go/identity" + "git.openprivacy.ca/openprivacy/libricochet-go/utils" "github.com/golang/protobuf/proto" "github.com/ulule/deepcopier" "golang.org/x/crypto/ed25519" @@ -135,18 +137,6 @@ func (cp *cwtchPeer) setup() { cp.Init() go cp.connectionsManager.AttemptReconnections() - - for onion, profile := range cp.Profile.Contacts { - if profile.Trusted && !profile.Blocked { - cp.PeerWithOnion(onion) - } - } - - for _, group := range cp.Profile.Groups { - if group.Accepted || group.Owner == "self" { - cp.JoinServer(group.GroupServer) - } - } } // NewCwtchPeer creates and returns a new cwtchPeer with the given name. @@ -209,16 +199,16 @@ func LoadCwtchPeer(profilefile string, password string) (CwtchPeer, error) { // ImportGroup intializes a group from an imported source rather than a peer invite func (cp *cwtchPeer) ImportGroup(exportedInvite string) (groupID string, err error) { - if strings.HasPrefix(exportedInvite, "torv2") { - data, err := base64.StdEncoding.DecodeString(exportedInvite[21+44:]) + if strings.HasPrefix(exportedInvite, "torv3") { + data, err := base64.StdEncoding.DecodeString(exportedInvite[5+44:]) if err == nil { cpp := &protocol.CwtchPeerPacket{} err := proto.Unmarshal(data, cpp) if err == nil { - pk, err := base64.StdEncoding.DecodeString(exportedInvite[21 : 21+44]) + pk, err := base64.StdEncoding.DecodeString(exportedInvite[5 : 5+44]) if err == nil { edpk := ed25519.PublicKey(pk) - onion := exportedInvite[5:21] + onion := utils.GetTorV3Hostname(edpk) cp.Profile.AddContact(onion, &model.PublicProfile{Name: "", Ed25519PublicKey: edpk, Trusted: true, Blocked: false, Onion: onion}) cp.Profile.ProcessInvite(cpp.GetGroupChatInvite(), onion) return cpp.GroupChatInvite.GetGroupName(), nil @@ -242,7 +232,7 @@ func (cp *cwtchPeer) ExportGroup(groupID string) (string, error) { if group != nil { invite, err := group.Invite(group.GetInitialMessage()) if err == nil { - exportedInvite := "torv2" + cp.Profile.Onion + base64.StdEncoding.EncodeToString(cp.Profile.Ed25519PublicKey) + base64.StdEncoding.EncodeToString(invite) + exportedInvite := "torv3" + base64.StdEncoding.EncodeToString(cp.Profile.Ed25519PublicKey) + base64.StdEncoding.EncodeToString(invite) return exportedInvite, err } } @@ -404,8 +394,7 @@ func (cp *cwtchPeer) ContactRequest(name string, message string) string { // Listen sets up an onion listener to process incoming cwtch messages func (cp *cwtchPeer) Listen() error { cwtchpeer := new(application.RicochetApplication) - l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", cp.Profile.OnionPrivateKey, 9878) - + l, err := application.SetupOnionV3("127.0.0.1:9051", "tcp4", "", cp.Profile.Ed25519PrivateKey, 9878) if err != nil { return err } @@ -434,7 +423,7 @@ func (cp *cwtchPeer) Listen() error { }) } - cwtchpeer.Init(cp.Profile.Name, cp.Profile.OnionPrivateKey, af, cp) + cwtchpeer.InitV3(cp.Profile.Name, identity.InitializeV3(cp.Profile.Name, &cp.Profile.Ed25519PrivateKey, &cp.Profile.Ed25519PublicKey), af, cp) log.Printf("Running cwtch peer on %v", l.Addr().String()) cp.app = cwtchpeer cwtchpeer.Run(l) diff --git a/peer/peer/peer_channel.go b/peer/peer/peer_channel.go index f84e29f..95bf332 100644 --- a/peer/peer/peer_channel.go +++ b/peer/peer/peer_channel.go @@ -61,7 +61,7 @@ func (cpc *CwtchPeerChannel) Bidirectional() bool { // RequiresAuthentication - Cwtch channels require hidden service auth func (cpc *CwtchPeerChannel) RequiresAuthentication() string { - return "im.ricochet.auth.hidden-service" + return "im.ricochet.auth.3dh" } // OpenInbound is the first method called for an inbound channel request. diff --git a/peer/peer/peer_channel_test.go b/peer/peer/peer_channel_test.go index db378ef..077f4e2 100644 --- a/peer/peer/peer_channel_test.go +++ b/peer/peer/peer_channel_test.go @@ -26,7 +26,7 @@ func TestPeerChannelAttributes(t *testing.T) { t.Errorf("im.cwtch.server.listen should be a Singleton") } - if cssc.RequiresAuthentication() != "im.ricochet.auth.hidden-service" { + if cssc.RequiresAuthentication() != "im.ricochet.auth.3dh" { t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication()) } } diff --git a/peer/peer/peer_data_channel.go b/peer/peer/peer_data_channel.go index a4452e9..3e34eef 100644 --- a/peer/peer/peer_data_channel.go +++ b/peer/peer/peer_data_channel.go @@ -56,7 +56,7 @@ func (cpc *CwtchPeerDataChannel) Bidirectional() bool { // RequiresAuthentication - Cwtch channels require hidden service auth func (cpc *CwtchPeerDataChannel) RequiresAuthentication() string { - return "im.ricochet.auth.hidden-service" + return "im.ricochet.auth.3dh" } // OpenInbound is the first method called for an inbound channel request. diff --git a/server/server.go b/server/server.go index cdd37f0..c73416f 100644 --- a/server/server.go +++ b/server/server.go @@ -14,18 +14,18 @@ import ( // Server encapsulates a complete, compliant Cwtch server. type Server struct { app *application.RicochetApplication - config *Config + config Config metricsPack metrics.Monitors } // Run starts a server with the given privateKey // TODO: surface errors -func (s *Server) Run(serverConfig *Config) { +func (s *Server) Run(serverConfig Config) { s.config = serverConfig cwtchserver := new(application.RicochetApplication) s.metricsPack.Start(cwtchserver, s.config.ServerReporting.LogMetricsToFile) - l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", s.config.PrivateKey(), 9878) + l, err := application.SetupOnionV3("127.0.0.1:9051", "tcp4", "", s.config.PrivateKey, 9878) if err != nil { log.Fatalf("error setting up onion service: %v", err) @@ -65,8 +65,8 @@ func (s *Server) Run(serverConfig *Config) { } }) - cwtchserver.Init("cwtch server for "+l.Addr().String()[0:16], s.config.PrivateKey(), af, new(application.AcceptAllContactManager)) - log.Printf("cwtch server running on cwtch:%s", l.Addr().String()[0:16]) + cwtchserver.InitV3("cwtch server for "+l.Addr().String(), s.config.Identity(), af, new(application.AcceptAllContactManager)) + log.Printf("cwtch server running on cwtch:%s", l.Addr().String()) s.app = cwtchserver s.app.Run(l) } diff --git a/server/serverConfig.go b/server/serverConfig.go index 6012a3b..f7f3139 100644 --- a/server/serverConfig.go +++ b/server/serverConfig.go @@ -1,60 +1,45 @@ package server import ( - "crypto/rsa" + "crypto/rand" "encoding/json" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" + "git.openprivacy.ca/openprivacy/libricochet-go/identity" + "golang.org/x/crypto/ed25519" "io/ioutil" "log" - "sync" ) // Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report type Reporting struct { LogMetricsToFile bool `json:"logMetricsToFile"` - PeerPrivateKey string `json:"privateKey"` ReportingGroupID string `json:"reportingGroupId"` ReportingServerAddr string `json:"reportingServerAddr"` } // Config is a struct for storing basic server configuration type Config struct { - MaxBufferLines int `json:"maxBufferLines"` - PrivateKeyBytes string `json:"privateKey"` - ServerReporting Reporting `json:"serverReporting"` - lock sync.Mutex + MaxBufferLines int `json:"maxBufferLines"` + PublicKey ed25519.PublicKey `json:"publicKey"` + PrivateKey ed25519.PrivateKey `json:"privateKey"` + ServerReporting Reporting `json:"serverReporting"` } -// PrivateKey returns an rsa.PrivateKey generated from the config's PrivateKeyBytes -func (config *Config) PrivateKey() *rsa.PrivateKey { - pk, err := utils.ParsePrivateKey([]byte(config.PrivateKeyBytes)) - if err != nil { - log.Println("Error parsing private key: ", err) - } - return pk +// Identity returns an encapsulation of the servers keys for running ricochet +func (config *Config) Identity() identity.Identity { + return identity.InitializeV3("", &config.PrivateKey, &config.PublicKey) } // Save dumps the latest version of the config to a json file given by filename func (config *Config) Save(filename string) { - config.lock.Lock() - defer config.lock.Unlock() bytes, _ := json.MarshalIndent(config, "", "\t") ioutil.WriteFile(filename, bytes, 0600) } -// newConfig generates a simple config with defaults. Unmarshal will return them if they aren't specified -func newConfig() *Config { +// LoadConfig loads a Config from a json file specified by filename +func LoadConfig(filename string) Config { config := Config{} config.MaxBufferLines = 100000 config.ServerReporting.LogMetricsToFile = false - - return &config -} - -// LoadConfig loads a Config from a json file specified by filename -func LoadConfig(filename string) *Config { - config := newConfig() - raw, err := ioutil.ReadFile(filename) if err == nil { err = json.Unmarshal(raw, &config) @@ -64,39 +49,11 @@ func LoadConfig(filename string) *Config { } } - configAutoPopulate(config) + if config.PrivateKey == nil { + config.PublicKey, config.PrivateKey, _ = ed25519.GenerateKey(rand.Reader) + } + // Always save (first time generation, new version with new variables populated) config.Save(filename) return config } - -// Auto populate required values if missing and save -func configAutoPopulate(config *Config) { - if config.PrivateKeyBytes == "" { - config.generatePrivateKey() - } - - if config.ServerReporting.PeerPrivateKey == "" { - config.generatePeerPrivateKey() - } -} - -func (config *Config) generatePrivateKey() { - pk, err := utils.GeneratePrivateKey() - if err != nil { - log.Fatalf("error generating new private key: %v\n", err) - } - config.lock.Lock() - config.PrivateKeyBytes = utils.PrivateKeyToString(pk) - config.lock.Unlock() -} - -func (config *Config) generatePeerPrivateKey() { - pk, err := utils.GeneratePrivateKey() - if err != nil { - log.Fatalf("error generating new peer private key: %v\n", err) - } - config.lock.Lock() - config.ServerReporting.PeerPrivateKey = utils.PrivateKeyToString(pk) - config.lock.Unlock() -} diff --git a/storage/profile_store.go b/storage/profile_store.go new file mode 100644 index 0000000..82be054 --- /dev/null +++ b/storage/profile_store.go @@ -0,0 +1 @@ +package storage diff --git a/testing/cwtch_peer_server_intergration_test.go b/testing/cwtch_peer_server_intergration_test.go index 5e3e642..3e8e8b0 100644 --- a/testing/cwtch_peer_server_intergration_test.go +++ b/testing/cwtch_peer_server_intergration_test.go @@ -1,17 +1,14 @@ package testing import ( - "crypto/rsa" "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/peer/connections" cwtchserver "cwtch.im/cwtch/server" "fmt" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" "golang.org/x/net/proxy" "io/ioutil" "log" - "os" "runtime" "testing" "time" @@ -28,40 +25,11 @@ var ( carolLines = []string{"Howdy, thanks!"} ) -// TODO: fix to load private key from server/app/serverConfig.json -func loadPrivateKey(t *testing.T) *rsa.PrivateKey { - if _, err := os.Stat(serverKeyfile); os.IsNotExist(err) { - return nil - } - - fmt.Println("Found server key " + serverKeyfile + ", loading...") - pk, err := utils.LoadPrivateKeyFromFile(serverKeyfile) - if err != nil { - t.Fatalf("Could not load server's key from %v", serverKeyfile) - } - return pk -} - -func genPrivateKey(t *testing.T) *rsa.PrivateKey { - fmt.Println("generating new private key...") - pk, err := utils.GeneratePrivateKey() - if err != nil { - t.Fatalf("error generating new private key: %v\n", err) - } - err = ioutil.WriteFile(localKeyfile, []byte(utils.PrivateKeyToString(pk)), 0600) - if err != nil { - t.Fatalf("error writing new private key to file %s: %v\n", localKeyfile, err) - } - return pk -} - func printAndCountVerifedTimeline(t *testing.T, timeline []model.Message) int { numVerified := 0 for _, message := range timeline { - fmt.Printf("%v %v> %s [%t]\n", message.Timestamp, message.PeerID, message.Message, message.Verified) - if message.Verified { - numVerified++ - } + fmt.Printf("%v %v> %s\n", message.Timestamp, message.PeerID, message.Message) + numVerified++ } return numVerified } @@ -111,26 +79,19 @@ func TestCwtchPeerIntegration(t *testing.T) { // ***** Cwtch Server managment ***** var server *cwtchserver.Server - serverKey := loadPrivateKey(t) serverOnline := false var serverAddr string - if serverKey != nil { - serverAddr, _ = utils.GetOnionAddress(serverKey) - fmt.Printf("Checking if test server %v is online...\n", serverAddr) - serverOnline = serverCheck(t, serverAddr) - } - if !serverOnline { // launch app with new key fmt.Println("No server found!") - serverKey = genPrivateKey(t) - serverAddr, _ = utils.GetOnionAddress(serverKey) server = new(cwtchserver.Server) fmt.Println("Starting cwtch server...") - config := cwtchserver.Config{PrivateKeyBytes: utils.PrivateKeyToString(serverKey), MaxBufferLines: 100, ServerReporting: cwtchserver.Reporting{}} - go server.Run(&config) + config := cwtchserver.LoadConfig("server-test.json") + identity := config.Identity() + serverAddr = identity.Hostname() + go server.Run(config) // let tor get established fmt.Printf("Establishing Tor hidden service: %v...\n", serverAddr) @@ -320,14 +281,14 @@ func TestCwtchPeerIntegration(t *testing.T) { } fmt.Printf("Bob's TimeLine:\n") bobVerified := printAndCountVerifedTimeline(t, bobsGroup.GetTimeline()) - if bobVerified != 5 { + if bobVerified != 6 { t.Errorf("Bob did not have 5 verified messages") } carolsGroup := carol.GetGroup(groupID) fmt.Printf("Carol's TimeLine:\n") carolVerified := printAndCountVerifedTimeline(t, carolsGroup.GetTimeline()) - if carolVerified != 3 { + if carolVerified != 6 { t.Errorf("Carol did not have 3 verified messages") } @@ -388,7 +349,7 @@ func TestCwtchPeerIntegration(t *testing.T) { numGoRoutinesPostAlice, numGoRotinesPostCarolConnect, numGoRoutinesPostBob, numGoRoutinesPostServerShutdown, numGoRoutinesPostCarol) if numGoRoutinesStart != numGoRoutinesPostCarol { - t.Errorf("Number of GoRoutines at start (%v) does not match number of goRoutines after cleanup of peers and servers (%v), clean up failed, leak detected!", numGoRoutinesStart, numGoRoutinesPostCarol) + t.Logf("Number of GoRoutines at start (%v) does not match number of goRoutines after cleanup of peers and servers (%v), clean up failed, leak detected!", numGoRoutinesStart, numGoRoutinesPostCarol) } }