2018-03-11 18:49:10 +00:00
package peer
2018-03-09 20:44:13 +00:00
import (
2018-05-30 17:41:02 +00:00
"crypto/rsa"
2018-05-28 18:05:06 +00:00
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/peer/connections"
"cwtch.im/cwtch/peer/peer"
"cwtch.im/cwtch/protocol"
2018-06-09 07:49:18 +00:00
"encoding/base64"
2018-03-30 21:16:51 +00:00
"errors"
2018-06-24 18:37:11 +00:00
"fmt"
2018-06-23 16:15:36 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
2018-10-05 03:18:34 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
2018-06-24 18:37:11 +00:00
"github.com/golang/protobuf/proto"
2018-06-09 07:49:18 +00:00
"golang.org/x/crypto/ed25519"
2018-05-01 20:44:45 +00:00
"log"
2018-06-09 07:49:18 +00:00
"strings"
2018-03-09 20:44:13 +00:00
"sync"
)
2018-10-06 03:50:55 +00:00
// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
2018-06-19 22:28:44 +00:00
type cwtchPeer struct {
2018-03-09 20:44:13 +00:00
connection . AutoConnectionHandler
2018-03-30 21:16:51 +00:00
Profile * model . Profile
2018-06-03 19:36:20 +00:00
app * application . RicochetApplication
2018-03-30 21:16:51 +00:00
mutex sync . Mutex
2018-03-15 20:53:22 +00:00
connectionsManager * connections . Manager
2018-10-04 19:15:03 +00:00
dataHandler func ( string , [ ] byte ) [ ] byte
2018-10-27 08:49:30 +00:00
//handlers map[string]func(*application.ApplicationInstance) func() channels.Handler
2018-10-29 19:19:30 +00:00
aif application . ApplicationInstanceFactory
2018-03-09 20:44:13 +00:00
}
2018-09-21 18:53:10 +00:00
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
2018-06-19 22:28:44 +00:00
// directly implement a cwtchPeer.
2018-09-21 18:53:10 +00:00
type CwtchPeer interface {
2018-10-06 03:50:55 +00:00
Init ( )
2018-10-04 19:15:03 +00:00
PeerWithOnion ( string ) * connections . PeerPeerConnection
2018-06-19 22:28:44 +00:00
InviteOnionToGroup ( string , string ) error
TrustPeer ( string ) error
BlockPeer ( string ) error
AcceptInvite ( string ) error
RejectInvite ( string )
JoinServer ( string )
SendMessageToGroup ( string , string ) error
GetProfile ( ) * model . Profile
GetPeers ( ) map [ string ] connections . ConnectionState
GetServers ( ) map [ string ] connections . ConnectionState
StartGroup ( string ) ( string , [ ] byte , error )
ImportGroup ( string ) ( string , error )
ExportGroup ( string ) ( string , error )
GetGroup ( string ) * model . Group
GetGroups ( ) [ ] string
GetContacts ( ) [ ] string
GetContact ( string ) * model . PublicProfile
2018-10-27 08:49:30 +00:00
SetApplicationInstanceFactory ( factory application . ApplicationInstanceFactory )
2018-10-04 19:15:03 +00:00
SetPeerDataHandler ( func ( string , [ ] byte ) [ ] byte )
2018-06-19 22:28:44 +00:00
Listen ( ) error
Shutdown ( )
}
2018-06-24 18:59:42 +00:00
// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
2018-10-06 03:50:55 +00:00
func NewCwtchPeer ( name string ) CwtchPeer {
2018-06-23 23:56:51 +00:00
cp := new ( cwtchPeer )
2018-05-01 20:44:45 +00:00
cp . Profile = model . GenerateNewProfile ( name )
2018-10-06 03:50:55 +00:00
cp . Init ( )
return cp
2018-10-15 00:59:53 +00:00
}
2018-10-06 03:50:55 +00:00
// FromProfile generates a new peer from a profile.
func FromProfile ( profile * model . Profile ) CwtchPeer {
cp := new ( cwtchPeer )
cp . Profile = profile
cp . Init ( )
return cp
2018-03-09 20:44:13 +00:00
}
2018-10-06 03:50:55 +00:00
// Init instantiates a cwtchPeer
func ( cp * cwtchPeer ) Init ( ) {
cp . connectionsManager = connections . NewConnectionsManager ( )
go cp . connectionsManager . AttemptReconnections ( )
2018-03-09 20:44:13 +00:00
}
2018-06-09 07:49:18 +00:00
// ImportGroup intializes a group from an imported source rather than a peer invite
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ImportGroup ( exportedInvite string ) ( groupID string , err error ) {
2018-10-05 03:18:34 +00:00
if strings . HasPrefix ( exportedInvite , "torv3" ) {
data , err := base64 . StdEncoding . DecodeString ( exportedInvite [ 5 + 44 : ] )
2018-06-09 07:49:18 +00:00
if err == nil {
cpp := & protocol . CwtchPeerPacket { }
err := proto . Unmarshal ( data , cpp )
if err == nil {
2018-10-05 03:18:34 +00:00
pk , err := base64 . StdEncoding . DecodeString ( exportedInvite [ 5 : 5 + 44 ] )
2018-06-09 07:49:18 +00:00
if err == nil {
edpk := ed25519 . PublicKey ( pk )
2018-10-05 03:18:34 +00:00
onion := utils . GetTorV3Hostname ( edpk )
2018-06-19 22:38:22 +00:00
cp . Profile . AddContact ( onion , & model . PublicProfile { Name : "" , Ed25519PublicKey : edpk , Trusted : true , Blocked : false , Onion : onion } )
2018-06-09 07:49:18 +00:00
cp . Profile . ProcessInvite ( cpp . GetGroupChatInvite ( ) , onion )
return cpp . GroupChatInvite . GetGroupName ( ) , nil
}
}
}
} else {
err = errors . New ( "unsupported exported group type" )
}
return
}
2018-10-27 08:49:30 +00:00
// 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
// a handy channel that it's nice to have this convenience shortcut
2018-10-06 03:50:55 +00:00
// SetPeerDataHandler sets the handler for the (optional) data channel for cwtch peers.
2018-10-04 19:15:03 +00:00
func ( cp * cwtchPeer ) SetPeerDataHandler ( dataHandler func ( string , [ ] byte ) [ ] byte ) {
cp . dataHandler = dataHandler
}
2018-10-27 08:49:30 +00:00
// add extra channel handlers (note that the peer will merge these with the ones necessary to make cwtch work, so you
// are not clobbering the underlying functionality)
func ( cp * cwtchPeer ) SetApplicationInstanceFactory ( aif application . ApplicationInstanceFactory ) {
cp . aif = aif
}
2018-06-09 07:49:18 +00:00
// ExportGroup serializes a group invite so it can be given offline
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ExportGroup ( groupID string ) ( string , error ) {
2018-06-09 07:49:18 +00:00
group := cp . Profile . GetGroupByGroupID ( groupID )
if group != nil {
2018-10-03 05:46:00 +00:00
invite , err := group . Invite ( group . GetInitialMessage ( ) )
2018-06-09 07:49:18 +00:00
if err == nil {
2018-10-05 03:18:34 +00:00
exportedInvite := "torv3" + base64 . StdEncoding . EncodeToString ( cp . Profile . Ed25519PublicKey ) + base64 . StdEncoding . EncodeToString ( invite )
2018-06-09 07:49:18 +00:00
return exportedInvite , err
}
}
return "" , errors . New ( "group id could not be found" )
}
2018-06-19 22:28:44 +00:00
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
func ( cp * cwtchPeer ) StartGroup ( server string ) ( string , [ ] byte , error ) {
return cp . Profile . StartGroup ( server )
}
2018-09-21 18:02:46 +00:00
// StartGroupWithMessage create a new group linked to the given server and returns the group ID, an invite or an error.
func ( cp * cwtchPeer ) StartGroupWithMessage ( server string , initialMessage [ ] byte ) ( string , [ ] byte , error ) {
return cp . Profile . StartGroupWithMessage ( server , initialMessage )
}
2018-06-19 22:28:44 +00:00
// GetGroups returns an unordered list of all group IDs.
func ( cp * cwtchPeer ) GetGroups ( ) [ ] string {
return cp . Profile . GetGroups ( )
}
// GetGroup returns a pointer to a specific group, nil if no group exists.
func ( cp * cwtchPeer ) GetGroup ( groupID string ) * model . Group {
return cp . Profile . GetGroupByGroupID ( groupID )
}
// GetContacts returns an unordered list of onions
func ( cp * cwtchPeer ) GetContacts ( ) [ ] string {
return cp . Profile . GetContacts ( )
}
// GetContact returns a given contact, nil is no such contact exists
func ( cp * cwtchPeer ) GetContact ( onion string ) * model . PublicProfile {
contact , _ := cp . Profile . GetContact ( onion )
return contact
}
2018-10-06 03:50:55 +00:00
// GetProfile returns the profile associated with this cwtchPeer.
2018-06-19 22:28:44 +00:00
// 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 {
return cp . Profile
}
// PeerWithOnion is the entry point for cwtchPeer relationships
2018-10-04 19:15:03 +00:00
func ( cp * cwtchPeer ) PeerWithOnion ( onion string ) * connections . PeerPeerConnection {
2018-10-27 08:49:30 +00:00
return cp . connectionsManager . ManagePeerConnection ( onion , cp . Profile , cp . dataHandler , cp . aif )
2018-03-09 20:44:13 +00:00
}
// InviteOnionToGroup kicks off the invite process
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) InviteOnionToGroup ( onion string , groupid string ) error {
2018-05-03 19:23:02 +00:00
2018-05-16 20:53:09 +00:00
group := cp . Profile . GetGroupByGroupID ( groupid )
2018-05-01 21:36:03 +00:00
if group != nil {
2018-06-05 22:06:38 +00:00
log . Printf ( "Constructing invite for group: %v\n" , group )
2018-09-21 18:02:46 +00:00
invite , err := group . Invite ( group . GetInitialMessage ( ) )
2018-05-16 20:18:47 +00:00
if err != nil {
return err
}
2018-03-15 20:53:22 +00:00
ppc := cp . connectionsManager . GetPeerPeerConnectionForOnion ( onion )
2018-05-03 19:23:02 +00:00
if ppc == nil {
return errors . New ( "peer connection not setup for onion. peers must be trusted before sending" )
}
if ppc . GetState ( ) == connections . AUTHENTICATED {
2018-06-05 22:06:38 +00:00
log . Printf ( "Got connection for group: %v - Sending Invite\n" , ppc )
2018-05-03 19:23:02 +00:00
ppc . SendGroupInvite ( invite )
} else {
return errors . New ( "cannot send invite to onion: peer connection is not ready" )
}
2018-05-03 04:12:45 +00:00
return nil
2018-03-15 20:53:22 +00:00
}
return errors . New ( "group id could not be found" )
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// ReceiveGroupMessage is a callback function that processes GroupMessages from a given server
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ReceiveGroupMessage ( server string , gm * protocol . GroupMessage ) {
2018-06-22 18:11:23 +00:00
cp . Profile . AttemptDecryption ( gm . Ciphertext , gm . Signature )
2018-03-30 21:16:51 +00:00
}
2018-05-16 21:31:06 +00:00
// JoinServer manages a new server connection with the given onion address
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) JoinServer ( onion string ) {
2018-03-30 21:16:51 +00:00
cp . connectionsManager . ManageServerConnection ( onion , cp . ReceiveGroupMessage )
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// SendMessageToGroup attemps to sent the given message to the given group id.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) SendMessageToGroup ( groupid string , message string ) error {
2018-05-16 20:53:09 +00:00
group := cp . Profile . GetGroupByGroupID ( groupid )
2018-05-03 04:12:45 +00:00
if group == nil {
2018-06-05 22:06:38 +00:00
return errors . New ( "group does not exist" )
2018-05-03 04:12:45 +00:00
}
2018-03-30 21:16:51 +00:00
psc := cp . connectionsManager . GetPeerServerConnectionForOnion ( group . GroupServer )
2018-05-03 19:23:02 +00:00
if psc == nil {
2018-05-09 19:09:00 +00:00
return errors . New ( "could not find server connection to send message to" )
}
2018-06-22 18:11:23 +00:00
ct , sig , err := cp . Profile . EncryptMessageToGroup ( message , groupid )
2018-05-09 19:09:00 +00:00
if err != nil {
return err
2018-05-03 19:23:02 +00:00
}
2018-03-30 21:16:51 +00:00
gm := & protocol . GroupMessage {
Ciphertext : ct ,
2018-06-22 18:11:23 +00:00
Signature : sig ,
2018-03-30 21:16:51 +00:00
}
2018-05-09 19:09:00 +00:00
err = psc . SendGroupMessage ( gm )
return err
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// GetPeers returns a list of peer connections.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) GetPeers ( ) map [ string ] connections . ConnectionState {
2018-05-01 20:44:45 +00:00
return cp . connectionsManager . GetPeers ( )
}
2018-05-16 21:31:06 +00:00
// GetServers returns a list of server connections
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) GetServers ( ) map [ string ] connections . ConnectionState {
2018-05-03 04:12:45 +00:00
return cp . connectionsManager . GetServers ( )
}
2018-05-16 21:31:06 +00:00
// TrustPeer sets an existing peer relationship to trusted
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) TrustPeer ( peer string ) error {
2018-05-30 17:41:02 +00:00
err := cp . Profile . TrustPeer ( peer )
if err == nil {
cp . PeerWithOnion ( peer )
2018-05-03 19:23:02 +00:00
}
2018-05-30 17:41:02 +00:00
return err
2018-05-03 19:23:02 +00:00
}
2018-05-16 21:31:06 +00:00
// BlockPeer blocks an existing peer relationship.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) BlockPeer ( peer string ) error {
2018-05-30 17:41:02 +00:00
err := cp . Profile . BlockPeer ( peer )
2018-05-30 18:42:17 +00:00
cp . connectionsManager . ClosePeerConnection ( peer )
2018-05-30 17:41:02 +00:00
return err
2018-05-03 19:23:02 +00:00
}
2018-05-16 21:31:06 +00:00
// AcceptInvite accepts a given existing group invite
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) AcceptInvite ( groupID string ) error {
2018-05-30 17:41:02 +00:00
return cp . Profile . AcceptInvite ( groupID )
2018-05-03 04:12:45 +00:00
}
2018-05-16 21:31:06 +00:00
// RejectInvite rejects a given group invite.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) RejectInvite ( groupID string ) {
2018-05-30 17:41:02 +00:00
cp . Profile . RejectInvite ( groupID )
}
// LookupContact returns that a contact is known and allowed to communicate for all cases.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) LookupContact ( hostname string , publicKey rsa . PublicKey ) ( allowed , known bool ) {
2018-05-30 17:41:02 +00:00
blocked := cp . Profile . IsBlocked ( hostname )
return ! blocked , true
}
2018-10-06 03:50:55 +00:00
// LookupContactV3 returns that a contact is known and allowed to communicate for all cases.
2018-10-04 19:15:03 +00:00
func ( cp * cwtchPeer ) LookupContactV3 ( hostname string , publicKey ed25519 . PublicKey ) ( allowed , known bool ) {
blocked := cp . Profile . IsBlocked ( hostname )
return ! blocked , true
}
2018-05-30 17:41:02 +00:00
// ContactRequest needed to implement ContactRequestHandler Interface
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ContactRequest ( name string , message string ) string {
2018-05-30 17:41:02 +00:00
return "Accepted"
2018-05-03 04:12:45 +00:00
}
2018-05-16 21:31:06 +00:00
// Listen sets up an onion listener to process incoming cwtch messages
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) Listen ( ) error {
2018-03-09 20:44:13 +00:00
cwtchpeer := new ( application . RicochetApplication )
2018-10-10 00:12:08 +00:00
l , err := application . SetupOnionV3 ( "127.0.0.1:9051" , "tcp4" , "" , cp . Profile . Ed25519PrivateKey , cp . GetProfile ( ) . Onion , 9878 )
if err != nil && fmt . Sprintf ( "%v" , err ) != "550 Unspecified Tor error: Onion address collision" {
2018-03-09 20:44:13 +00:00
return err
}
af := application . ApplicationInstanceFactory { }
af . Init ( )
af . AddHandler ( "im.cwtch.peer" , func ( rai * application . ApplicationInstance ) func ( ) channels . Handler {
cpi := new ( CwtchPeerInstance )
cpi . Init ( rai , cwtchpeer )
return func ( ) channels . Handler {
2018-03-14 22:03:53 +00:00
cpc := new ( peer . CwtchPeerChannel )
cpc . Handler = & CwtchPeerHandler { Onion : rai . RemoteHostname , Peer : cp }
return cpc
2018-03-09 20:44:13 +00:00
}
} )
2018-10-04 19:15:03 +00:00
if cp . dataHandler != nil {
af . AddHandler ( "im.cwtch.peer.data" , func ( rai * application . ApplicationInstance ) func ( ) channels . Handler {
cpi := new ( CwtchPeerInstance )
cpi . Init ( rai , cwtchpeer )
return func ( ) channels . Handler {
cpc := new ( peer . CwtchPeerDataChannel )
cpc . Handler = & CwtchPeerHandler { Onion : rai . RemoteHostname , Peer : cp , DataHandler : cp . dataHandler }
return cpc
}
} )
}
2018-10-27 08:49:30 +00:00
handlers := cp . aif . GetHandlers ( )
for i := range handlers {
af . AddHandler ( handlers [ i ] , cp . aif . GetHandler ( handlers [ i ] ) )
}
2018-10-05 03:18:34 +00:00
cwtchpeer . InitV3 ( cp . Profile . Name , identity . InitializeV3 ( cp . Profile . Name , & cp . Profile . Ed25519PrivateKey , & cp . Profile . Ed25519PublicKey ) , af , cp )
2018-05-01 20:44:45 +00:00
log . Printf ( "Running cwtch peer on %v" , l . Addr ( ) . String ( ) )
2018-05-30 18:42:17 +00:00
cp . app = cwtchpeer
2018-03-09 20:44:13 +00:00
cwtchpeer . Run ( l )
return nil
}
2018-06-15 16:21:07 +00:00
// Shutdown kills all connections and cleans up all goroutines for the peer
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) Shutdown ( ) {
2018-05-30 18:42:17 +00:00
cp . connectionsManager . Shutdown ( )
2018-10-24 03:49:00 +00:00
if cp . app != nil {
cp . app . Shutdown ( )
}
2018-05-30 18:42:17 +00:00
}
2018-05-16 21:31:06 +00:00
// CwtchPeerInstance encapsulates incoming peer connections
2018-03-15 20:53:22 +00:00
type CwtchPeerInstance struct {
rai * application . ApplicationInstance
ra * application . RicochetApplication
}
2018-05-16 21:31:06 +00:00
// Init sets up a CwtchPeerInstance
2018-03-15 20:53:22 +00:00
func ( cpi * CwtchPeerInstance ) Init ( rai * application . ApplicationInstance , ra * application . RicochetApplication ) {
cpi . rai = rai
cpi . ra = ra
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// CwtchPeerHandler encapsulates handling of incoming CwtchPackets
2018-03-09 20:44:13 +00:00
type CwtchPeerHandler struct {
2018-10-04 19:15:03 +00:00
Onion string
Peer * cwtchPeer
DataHandler func ( string , [ ] byte ) [ ] byte
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// HandleGroupInvite handles incoming GroupInvites
2018-03-14 22:03:53 +00:00
func ( cph * CwtchPeerHandler ) HandleGroupInvite ( gci * protocol . GroupChatInvite ) {
2018-05-01 21:36:03 +00:00
log . Printf ( "Received GroupID from %v %v\n" , cph . Onion , gci . String ( ) )
2018-03-15 20:53:22 +00:00
cph . Peer . Profile . ProcessInvite ( gci , cph . Onion )
2018-03-09 20:44:13 +00:00
}
2018-03-10 21:26:19 +00:00
2018-10-06 03:50:55 +00:00
// HandlePacket handles the Cwtch cwtchPeer Data Channel
2018-10-04 19:15:03 +00:00
func ( cph * CwtchPeerHandler ) HandlePacket ( data [ ] byte ) [ ] byte {
return cph . DataHandler ( cph . Onion , data )
}