diff --git a/application/acceptallcontactmanager.go b/application/acceptallcontactmanager.go new file mode 100644 index 0000000..3aebec4 --- /dev/null +++ b/application/acceptallcontactmanager.go @@ -0,0 +1,23 @@ +package application + +import ( + "crypto/rsa" +) + +// AcceptAllContactManager implements the contact manager interface an presumes +// all connections are allowed. +type AcceptAllContactManager struct { +} + +// LookupContact returns that a contact is known and allowed to communicate for all cases. +func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { + return true, true +} + +func (aacm *AcceptAllContactManager) GetContactDetails() (string, string) { + return "", "" +} + +func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string { + return "Accepted" +} diff --git a/application/application.go b/application/application.go index 9639378..ac73cfb 100644 --- a/application/application.go +++ b/application/application.go @@ -1,36 +1,134 @@ package application import ( - "errors" + "crypto/rsa" + "github.com/s-rah/go-ricochet" "github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/connection" + "log" + "net" + "time" ) // RicochetApplication bundles many useful constructs that are // likely standard in a ricochet application type RicochetApplication struct { - connection *connection.Connection + contactManager ContactManagerInterface + privateKey *rsa.PrivateKey + chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) + chatMessageAckHandler func(*RicochetApplicationInstance, uint32) } -// NewRicochetApplication ... -func NewRicochetApplication(connection *connection.Connection) *RicochetApplication { - ra := new(RicochetApplication) - ra.connection = connection - return ra +type RicochetApplicationInstance struct { + connection.AutoConnectionHandler + connection *connection.Connection + RemoteHostname string + ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) + ChatMessageAckHandler func(*RicochetApplicationInstance, uint32) } -// SendMessage ... -func (ra *RicochetApplication) SendChatMessage(message string) error { - return ra.connection.Do(func() error { - channel := ra.connection.Channel("im.ricochet.chat", channels.Outbound) +func (rai *RicochetApplicationInstance) GetContactDetails() (string, string) { + return "EchoBot", "I LIVE 😈😈!!!!" +} + +func (rai *RicochetApplicationInstance) ContactRequest(name string, message string) string { + return "Accepted" +} + +func (rai *RicochetApplicationInstance) ContactRequestRejected() { +} +func (rai *RicochetApplicationInstance) ContactRequestAccepted() { +} +func (rai *RicochetApplicationInstance) ContactRequestError() { +} + +func (rai *RicochetApplicationInstance) SendChatMessage(message string) { + + // Technically this errors afte the second time but we can ignore it. + rai.connection.RequestOpenChannel("im.ricochet.chat", rai) + + rai.connection.Do(func() error { + channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound) if channel != nil { chatchannel, ok := (*channel.Handler).(*channels.ChatChannel) if ok { chatchannel.SendMessage(message) } - } else { - return errors.New("") } return nil }) } + +func (rai *RicochetApplicationInstance) ChatMessage(messageID uint32, when time.Time, message string) bool { + go rai.ChatMessageHandler(rai, messageID, when, message) + return true +} + +func (rai *RicochetApplicationInstance) ChatMessageAck(messageID uint32) { + rai.ChatMessageAckHandler(rai, messageID) +} + +func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, cm ContactManagerInterface) { + ra.privateKey = pk + ra.contactManager = cm + ra.chatMessageHandler = func(*RicochetApplicationInstance, uint32, time.Time, string) {} + ra.chatMessageAckHandler = func(*RicochetApplicationInstance, uint32) {} +} + +func (ra *RicochetApplication) OnChatMessage(call func(*RicochetApplicationInstance, uint32, time.Time, string)) { + ra.chatMessageHandler = call +} + +func (ra *RicochetApplication) OnChatMessageAck(call func(*RicochetApplicationInstance, uint32)) { + ra.chatMessageAckHandler = call +} + +func (ra *RicochetApplication) handleConnection(conn net.Conn) { + rc, err := goricochet.NegotiateVersionInbound(conn) + if err != nil { + log.Printf("There was an error") + conn.Close() + return + } + + ich := connection.HandleInboundConnection(rc) + + err = ich.ProcessAuthAsServer(ra.privateKey, ra.contactManager.LookupContact) + if err != nil { + log.Printf("There was an error") + conn.Close() + return + } + + rai := new(RicochetApplicationInstance) + rai.Init(ra.privateKey, "") + rai.RemoteHostname = rc.RemoteHostname + rai.connection = rc + rai.ChatMessageHandler = ra.chatMessageHandler + rai.ChatMessageAckHandler = ra.chatMessageAckHandler + + rai.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler { + contact := new(channels.ContactRequestChannel) + contact.Handler = rai + return contact + }) + rai.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler { + chat := new(channels.ChatChannel) + chat.Handler = rai + return chat + }) + rc.Process(rai) +} + +func (ra *RicochetApplication) Run(l net.Listener) { + if ra.privateKey == nil || ra.contactManager == nil { + return + } + + for { + conn, err := l.Accept() + if err == nil { + go ra.handleConnection(conn) + } + } +} diff --git a/application/contactmanagerinterface.go b/application/contactmanagerinterface.go new file mode 100644 index 0000000..4573178 --- /dev/null +++ b/application/contactmanagerinterface.go @@ -0,0 +1,11 @@ +package application + +import ( + "crypto/rsa" +) + +// ContactManagerInterface provides a mechanism for autonous applications +// to make decisions on what connections to accept or reject. +type ContactManagerInterface interface { + LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) +} diff --git a/application/examples/echobot/main.go b/application/examples/echobot/main.go new file mode 100644 index 0000000..dc11951 --- /dev/null +++ b/application/examples/echobot/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/s-rah/go-ricochet/application" + "github.com/s-rah/go-ricochet/utils" + "log" + "time" +) + +func main() { + echobot := new(application.RicochetApplication) + pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key") + + if err != nil { + log.Fatalf("error reading private key file: %v", err) + } + + l, err := application.SetupOnion("127.0.0.1:9051", "", pk, 9878) + + if err != nil { + log.Fatalf("error setting up onion service: %v", err) + } + + echobot.Init(pk, new(application.AcceptAllContactManager)) + echobot.OnChatMessage(func(rai *application.RicochetApplicationInstance, id uint32, timestamp time.Time, message string) { + log.Printf("message from %v - %v", rai.RemoteHostname, message) + rai.SendChatMessage(message) + }) + log.Printf("echobot listening on %s", l.Addr().String()) + echobot.Run(l) +} diff --git a/application/ricochetonion.go b/application/ricochetonion.go new file mode 100644 index 0000000..b3dc6ec --- /dev/null +++ b/application/ricochetonion.go @@ -0,0 +1,25 @@ +package application + +import ( + "crypto/rsa" + "github.com/yawning/bulb" + "net" +) + +func SetupOnion(proxyServer string, authentication string, pk *rsa.PrivateKey, onionport uint16) (net.Listener, error) { + c, err := bulb.Dial("tcp4", proxyServer) + if err != nil { + return nil, err + } + + if err := c.Authenticate(authentication); err != nil { + return nil, err + } + + cfg := &bulb.NewOnionConfig{ + DiscardPK: true, + PrivateKey: pk, + } + + return c.NewListener(cfg, onionport) +} diff --git a/channels/channel.go b/channels/channel.go index cc263c4..5823360 100644 --- a/channels/channel.go +++ b/channels/channel.go @@ -12,6 +12,7 @@ const ( // AuthChannelResult captures the result of an authentication flow type AuthChannelResult struct { + Hostname string Accepted bool IsKnownContact bool } diff --git a/channels/contactrequestchannel.go b/channels/contactrequestchannel.go index f501c06..af33f1d 100644 --- a/channels/contactrequestchannel.go +++ b/channels/contactrequestchannel.go @@ -1,13 +1,19 @@ package channels import ( - "errors" "github.com/golang/protobuf/proto" "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/wire/contact" "github.com/s-rah/go-ricochet/wire/control" ) +// Defining Versions +const ( + InvalidContactNameError = utils.Error("InvalidContactNameError") + InvalidContactMessageError = utils.Error("InvalidContactMessageError") + InvalidContactRequestError = utils.Error("InvalidContactRequestError") +) + // ContactRequestChannel implements the ChannelHandler interface for a channel of // type "im.ricochet.contact.request". The channel may be inbound or outbound. // a ContactRequestChannelHandler implementation to handle chat events. @@ -73,12 +79,12 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) { // Violation of the Protocol - return nil, errors.New("invalid nickname") + return nil, InvalidContactNameError } if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) { // Violation of the Protocol - return nil, errors.New("invalid message") + return nil, InvalidContactMessageError } result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText()) @@ -86,7 +92,7 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil } } - return nil, errors.New("could not parse contact request extension") + return nil, InvalidContactRequestError } // OpenOutbound is the first method called for an outbound channel request. diff --git a/channels/hiddenserviceauthchannel.go b/channels/hiddenserviceauthchannel.go index a614d63..54866db 100644 --- a/channels/hiddenserviceauthchannel.go +++ b/channels/hiddenserviceauthchannel.go @@ -7,7 +7,6 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/asn1" - "errors" "github.com/golang/protobuf/proto" "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/wire/auth" @@ -15,6 +14,10 @@ import ( "io" ) +const ( + InvalidClientCookieError = utils.Error("InvalidClientCookieError") +) + // HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service" type HiddenServiceAuthChannel struct { // Methods of Handler are called for events on this channel @@ -75,15 +78,15 @@ func (ah *HiddenServiceAuthChannel) Closed(err error) { // Remote -> [Open Authentication Channel] -> Local func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { - if ah.PrivateKey == nil { - return nil, utils.PrivateKeyNotSetError - } + if ah.PrivateKey == nil { + return nil, utils.PrivateKeyNotSetError + } ah.channel = channel clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie) if len(clientCookie.([]byte)[:]) != 16 { // reutrn without opening channel. - return nil, errors.New("invalid client cookie") + return nil, InvalidClientCookieError } ah.AddClientCookie(clientCookie.([]byte)[:]) messageBuilder := new(utils.MessageBuilder) @@ -97,10 +100,9 @@ func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_D // Local -> [Open Authentication Channel] -> Remote func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) { - if ah.PrivateKey == nil { - return nil, utils.PrivateKeyNotSetError - } - + if ah.PrivateKey == nil { + return nil, utils.PrivateKeyNotSetError + } ah.channel = channel messageBuilder := new(utils.MessageBuilder) diff --git a/connection/autoconnectionhandler.go b/connection/autoconnectionhandler.go index 42a8a9e..29aad54 100644 --- a/connection/autoconnectionhandler.go +++ b/connection/autoconnectionhandler.go @@ -65,7 +65,7 @@ func (ach *AutoConnectionHandler) ClientAuthResult(accepted bool, isKnownContact func (ach *AutoConnectionHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { // Do something accepted, isKnownContact := ach.sach(hostname, publicKey) - ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact} + ach.authResultChannel <- channels.AuthChannelResult{Hostname: hostname, Accepted: accepted, IsKnownContact: isKnownContact} return accepted, isKnownContact } diff --git a/connection/channelmanager.go b/connection/channelmanager.go index 8dfe5a4..59cc0a4 100644 --- a/connection/channelmanager.go +++ b/connection/channelmanager.go @@ -1,8 +1,8 @@ package connection import ( - "errors" "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/utils" ) // ChannelManager encapsulates the logic for server and client side assignment @@ -38,7 +38,7 @@ func NewServerChannelManager() *ChannelManager { func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channels.Channel, error) { // Some channels only allow us to open one of them per connection if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Outbound) != nil { - return nil, errors.New("Connection already has channel of type " + chandler.Type()) + return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError } channel := new(channels.Channel) @@ -57,20 +57,20 @@ func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channe func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler channels.Handler) (*channels.Channel, error) { if cm.isClient && (channelID%2) != 0 { // Server is trying to open odd numbered channels - return nil, errors.New("server may only open even numbered channels") + return nil, utils.ServerAttemptedToOpenEvenNumberedChannelError } else if !cm.isClient && (channelID%2) == 0 { // Server is trying to open odd numbered channels - return nil, errors.New("client may only open odd numbered channels") + return nil, utils.ClientAttemptedToOpenOddNumberedChannelError } _, exists := cm.channels[channelID] if exists { - return nil, errors.New("channel id is already in use") + return nil, utils.ChannelIDIsAlreadyInUseError } // Some channels only allow us to open one of them per connection if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil { - return nil, errors.New("Connection already has channel of type " + chandler.Type()) + return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError } channel := new(channels.Channel) diff --git a/connection/connection.go b/connection/connection.go index 6d05373..d221413 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -2,14 +2,13 @@ package connection import ( "errors" + "fmt" "github.com/golang/protobuf/proto" "github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/wire/control" "io" "log" - "time" - "fmt" ) // Connection encapsulates the state required to maintain a connection to @@ -30,7 +29,7 @@ type Connection struct { unlockResponseChannel chan bool messageBuilder utils.MessageBuilder - trace bool + trace bool Conn io.ReadWriteCloser IsInbound bool @@ -77,7 +76,7 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn } func (rc *Connection) TraceLog(enabled bool) { - rc.trace = enabled + rc.trace = enabled } // start @@ -111,12 +110,12 @@ func (rc *Connection) Do(do func() error) error { // are not met on the local side (a nill error return does not mean the // channel was opened successfully) func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error { - rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype)) + rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype)) return rc.Do(func() error { chandler, err := handler.OnOpenChannelRequest(ctype) if err != nil { - rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) + rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err)) return err } @@ -125,14 +124,14 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error { // Enforce Authentication Check. _, authed := rc.Authentication[chandler.RequiresAuthentication()] if !authed { - return errors.New("connection is not auth'd") + return utils.UnauthorizedActionError } } channel, err := rc.channelManager.OpenChannelRequest(chandler) if err != nil { - rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) + rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) return err } @@ -148,10 +147,10 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error { } response, err := chandler.OpenOutbound(channel) if err == nil { - rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype)) + rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype)) rc.SendRicochetPacket(rc.Conn, 0, response) } else { - rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) + rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) rc.channelManager.RemoveChannel(channel.ID) } return nil @@ -174,7 +173,6 @@ func (rc *Connection) Process(handler Handler) error { for !breaked { var packet utils.RicochetData - tick := time.Tick(30 * time.Second) select { case <-rc.unlockChannel: <-rc.unlockResponseChannel @@ -189,14 +187,10 @@ func (rc *Connection) Process(handler Handler) error { rc.Conn.Close() handler.OnClosed(err) return err - case <-tick: - rc.traceLog("peer timed out") - return errors.New("peer timed out") } - if packet.Channel == 0 { - rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel)) + rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel)) res := new(Protocol_Data_Control.Packet) err := proto.Unmarshal(packet.Data[:], res) if err == nil { @@ -209,9 +203,9 @@ func (rc *Connection) Process(handler Handler) error { if len(packet.Data) == 0 { rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel)) rc.channelManager.RemoveChannel(packet.Channel) - (*channel.Handler).Closed(errors.New("channel closed by peer")) + (*channel.Handler).Closed(utils.ChannelClosedByPeerError) } else { - rc.traceLog(fmt.Sprintf("received packet on %v channel %d", (*channel.Handler).Type(), packet.Channel)) + rc.traceLog(fmt.Sprintf("received packet on %v channel %d", (*channel.Handler).Type(), packet.Channel)) // Send The Ricochet Packet to the Handler (*channel.Handler).Packet(packet.Data[:]) } @@ -219,7 +213,7 @@ func (rc *Connection) Process(handler Handler) error { // When a non-zero packet is received for an unknown // channel, the recipient responds by closing // that channel. - rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel)) + rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel)) if len(packet.Data) != 0 { rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{}) } @@ -248,7 +242,7 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control. // Check that we have the authentication already if chandler.RequiresAuthentication() != "none" { - rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication())) + rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication())) // Enforce Authentication Check. _, authed := rc.Authentication[chandler.RequiresAuthentication()] if !authed { @@ -276,10 +270,10 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control. response, err := chandler.OpenInbound(channel, opm) if err == nil && channel.Pending == false { - rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID)) + rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID)) rc.SendRicochetPacket(rc.Conn, 0, response) } else { - rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID)) + rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID)) rc.channelManager.RemoveChannel(channel.ID) rc.SendRicochetPacket(rc.Conn, 0, []byte{}) } @@ -297,15 +291,15 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control. channel, found := rc.channelManager.GetChannel(id) if !found { - rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id)) + rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id)) return } if cr.GetOpened() { - rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id)) + rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id)) (*channel.Handler).OpenOutboundResult(nil, cr) } else { - rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id)) + rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id)) (*channel.Handler).OpenOutboundResult(errors.New(""), cr) } @@ -321,27 +315,27 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control. rc.SendRicochetPacket(rc.Conn, 0, raw) } } else if res.GetEnableFeatures() != nil { - rc.traceLog("received features enabled packet") + rc.traceLog("received features enabled packet") messageBuilder := new(utils.MessageBuilder) raw := messageBuilder.FeaturesEnabled([]string{}) - rc.traceLog("sending featured enabled empty response") + rc.traceLog("sending featured enabled empty response") rc.SendRicochetPacket(rc.Conn, 0, raw) } else if res.GetFeaturesEnabled() != nil { // TODO We should never send out an enabled features // request. - rc.traceLog("sending unsolicited features enabled response") + rc.traceLog("sending unsolicited features enabled response") } } func (rc *Connection) traceLog(message string) { - if rc.trace { - log.Printf(message) - } + if rc.trace { + log.Printf(message) + } } // Break causes Process() to return, but does not close the underlying connection func (rc *Connection) Break() { - rc.traceLog("breaking out of process loop") + rc.traceLog("breaking out of process loop") rc.breakChannel <- true <-rc.breakResultChannel // Wait for Process to End } diff --git a/connection/inboundconnectionhandler.go b/connection/inboundconnectionhandler.go index 89a005c..5b6111d 100644 --- a/connection/inboundconnectionhandler.go +++ b/connection/inboundconnectionhandler.go @@ -35,9 +35,9 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler { // assume they are required to send a contact request before any other activity. func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.PrivateKey, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error { - if privateKey == nil { - return utils.PrivateKeyNotSetError - } + if privateKey == nil { + return utils.PrivateKeyNotSetError + } ach := new(AutoConnectionHandler) ach.Init(privateKey, ich.connection.RemoteHostname) @@ -56,6 +56,7 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.Private if err == nil { if authResult.Accepted == true { + ich.connection.RemoteHostname = authResult.Hostname return nil } return utils.ClientFailedToAuthenticateError diff --git a/connection/outboundconnectionhandler.go b/connection/outboundconnectionhandler.go index e6998c2..999a0d8 100644 --- a/connection/outboundconnectionhandler.go +++ b/connection/outboundconnectionhandler.go @@ -2,10 +2,9 @@ package connection import ( "crypto/rsa" - "errors" "github.com/s-rah/go-ricochet/channels" - "github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/policies" + "github.com/s-rah/go-ricochet/utils" ) // OutboundConnectionHandler is a convieniance wrapper for handling outbound @@ -34,9 +33,9 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler { // request before any other activity. func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) { - if privateKey == nil { - return false, utils.PrivateKeyNotSetError - } + if privateKey == nil { + return false, utils.PrivateKeyNotSetError + } ach := new(AutoConnectionHandler) ach.Init(privateKey, och.connection.RemoteHostname) @@ -61,5 +60,5 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat return result.IsKnownContact, nil } } - return false, errors.New("authentication was not accepted by the server") + return false, utils.ServerRejectedClientConnectionError } diff --git a/examples/echobot/main.go b/examples/echobot/main.go index d3be6c4..2281a7c 100644 --- a/examples/echobot/main.go +++ b/examples/echobot/main.go @@ -58,7 +58,7 @@ func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string) }) rc, _ := goricochet.Open(hostname) - known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey) + known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey) if err == nil { go rc.Process(echobot) diff --git a/ricochet.go b/ricochet.go index 36c80bd..270c5d4 100644 --- a/ricochet.go +++ b/ricochet.go @@ -1,60 +1,59 @@ package goricochet import ( - "github.com/s-rah/go-ricochet/utils" - "github.com/s-rah/go-ricochet/connection" - "io" - "net" + "github.com/s-rah/go-ricochet/connection" + "github.com/s-rah/go-ricochet/utils" + "io" + "net" ) + // Open establishes a protocol session on an established net.Conn, and returns a new // OpenConnection instance representing this connection. On error, the connection // will be closed. This function blocks until version negotiation has completed. // The application should call Process() on the returned OpenConnection to continue // handling protocol messages. func Open(remoteHostname string) (*connection.Connection, error) { - networkResolver := utils.NetworkResolver{} - conn, remoteHostname, err := networkResolver.Resolve(remoteHostname) + networkResolver := utils.NetworkResolver{} + conn, remoteHostname, err := networkResolver.Resolve(remoteHostname) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - rc, err := negotiateVersion(conn, remoteHostname) - if err != nil { - conn.Close() - return nil, err - } - return rc, nil + rc, err := negotiateVersion(conn, remoteHostname) + if err != nil { + conn.Close() + return nil, err + } + return rc, nil } - // negotiate version takes an open network connection and executes // the ricochet version negotiation procedure. func negotiateVersion(conn net.Conn, remoteHostname string) (*connection.Connection, error) { - versions := []byte{0x49, 0x4D, 0x01, 0x01} - if n, err := conn.Write(versions); err != nil || n < len(versions) { - return nil, utils.VersionNegotiationError - } + versions := []byte{0x49, 0x4D, 0x01, 0x01} + if n, err := conn.Write(versions); err != nil || n < len(versions) { + return nil, utils.VersionNegotiationError + } - res := make([]byte, 1) - if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil { - return nil, utils.VersionNegotiationError - } + res := make([]byte, 1) + if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil { + return nil, utils.VersionNegotiationError + } - if res[0] != 0x01 { - return nil, utils.VersionNegotiationFailed - } - rc := connection.NewOutboundConnection(conn,remoteHostname) - return rc, nil + if res[0] != 0x01 { + return nil, utils.VersionNegotiationFailed + } + rc := connection.NewOutboundConnection(conn, remoteHostname) + return rc, nil } - // NegotiateVersionInbound takes in a connection and performs version negotiation // as if that connection was a client. Returns a ricochet connection if successful // error otherwise. func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) { - versions := []byte{0x49, 0x4D, 0x01, 0x01} - // Read version response header + versions := []byte{0x49, 0x4D, 0x01, 0x01} + // Read version response header header := make([]byte, 3) if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { return nil, err @@ -85,10 +84,7 @@ func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) { if selectedVersion == 0xff { return nil, utils.VersionNegotiationFailed } - + rc := connection.NewInboundConnection(conn) return rc, nil } - - - diff --git a/ricochet_test.go b/ricochet_test.go index 1c55f78..d8aea05 100644 --- a/ricochet_test.go +++ b/ricochet_test.go @@ -1,70 +1,68 @@ package goricochet import ( - "testing" - "github.com/s-rah/go-ricochet/utils" - "net" - "time" + "github.com/s-rah/go-ricochet/utils" + "net" + "testing" + "time" ) - func SimpleServer() { - ln,_ := net.Listen("tcp", "127.0.0.1:11000") - conn,_ := ln.Accept() - b := make([]byte, 4) - n,err := conn.Read(b) - if n == 4 && err == nil { - conn.Write([]byte{0x01}) - } - conn.Close() + ln, _ := net.Listen("tcp", "127.0.0.1:11000") + conn, _ := ln.Accept() + b := make([]byte, 4) + n, err := conn.Read(b) + if n == 4 && err == nil { + conn.Write([]byte{0x01}) + } + conn.Close() } func BadVersionNegotiation() { - ln,_ := net.Listen("tcp", "127.0.0.1:11001") - conn,_ := ln.Accept() - // We are already testing negotiation bytes, we don't care, just send a termination. - conn.Write([]byte{0x00}) - conn.Close() + ln, _ := net.Listen("tcp", "127.0.0.1:11001") + conn, _ := ln.Accept() + // We are already testing negotiation bytes, we don't care, just send a termination. + conn.Write([]byte{0x00}) + conn.Close() } func NotRicochetServer() { - ln,_ := net.Listen("tcp", "127.0.0.1:11002") - conn,_ := ln.Accept() - conn.Close() + ln, _ := net.Listen("tcp", "127.0.0.1:11002") + conn, _ := ln.Accept() + conn.Close() } func TestRicochet(t *testing.T) { - go SimpleServer() - // Wait for Server to Initialize - time.Sleep(time.Second) + go SimpleServer() + // Wait for Server to Initialize + time.Sleep(time.Second) - rc,err := Open("127.0.0.1:11000|abcdefghijklmno.onion") - if err == nil { - if rc.IsInbound { - t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen") - } - return - } - t.Errorf("RicochetProtocol: Open Failed: %v", err) + rc, err := Open("127.0.0.1:11000|abcdefghijklmno.onion") + if err == nil { + if rc.IsInbound { + t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen") + } + return + } + t.Errorf("RicochetProtocol: Open Failed: %v", err) } -func TestBadVersionNegotiation(t*testing.T) { - go BadVersionNegotiation() - time.Sleep(time.Second) +func TestBadVersionNegotiation(t *testing.T) { + go BadVersionNegotiation() + time.Sleep(time.Second) - _,err := Open("127.0.0.1:11001|abcdefghijklmno.onion") - if err != utils.VersionNegotiationFailed { - t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err) - } + _, err := Open("127.0.0.1:11001|abcdefghijklmno.onion") + if err != utils.VersionNegotiationFailed { + t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err) + } } +func TestNotARicochetServer(t *testing.T) { + go NotRicochetServer() + time.Sleep(time.Second) -func TestNotARicochetServer(t*testing.T) { - go NotRicochetServer() - time.Sleep(time.Second) - - _,err := Open("127.0.0.1:11002|abcdefghijklmno.onion") - if err != utils.VersionNegotiationError { - t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err) - } + _, err := Open("127.0.0.1:11002|abcdefghijklmno.onion") + if err != utils.VersionNegotiationError { + t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err) + } } diff --git a/utils/crypto.go b/utils/crypto.go index 2f10c9c..4852fb0 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -4,10 +4,13 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" - "errors" "io/ioutil" ) +const ( + InvalidPrivateKeyFileError = Error("InvalidPrivateKeyFileError") +) + // LoadPrivateKeyFromFile loads a private key from a file... func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) { pemData, err := ioutil.ReadFile(filename) @@ -18,7 +21,7 @@ func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) { block, _ := pem.Decode(pemData) if block == nil || block.Type != "RSA PRIVATE KEY" { - return nil, errors.New("not a private key") + return nil, InvalidPrivateKeyFileError } return x509.ParsePKCS1PrivateKey(block.Bytes) diff --git a/utils/error.go b/utils/error.go index bd2f869..7cf28cf 100644 --- a/utils/error.go +++ b/utils/error.go @@ -20,12 +20,25 @@ const ( UnknownChannelTypeError = Error("UnknownChannelTypeError") UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError") + // Timeout Errors ActionTimedOutError = Error("ActionTimedOutError") + PeerTimedOutError = Error("PeerTimedOutError") - ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError") + // Authentication Errors + ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError") + ServerRejectedClientConnectionError = Error("ServerRejectedClientConnectionError") + UnauthorizedActionError = Error("UnauthorizedActionError") + ChannelClosedByPeerError = Error("ChannelClosedByPeerError") - PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError") + // Channel Management Errors + ServerAttemptedToOpenEvenNumberedChannelError = Error("ServerAttemptedToOpenEvenNumberedChannelError") + ClientAttemptedToOpenOddNumberedChannelError = Error("ClientAttemptedToOpenOddNumberedChannelError") + ChannelIDIsAlreadyInUseError = Error("ChannelIDIsAlreadyInUseError") + AttemptToOpenMoreThanOneSingletonChannelError = Error("AttemptToOpenMoreThanOneSingletonChannelError") + + // Library Use Errors + PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError") ) // CheckError is a helper function for panicing on errors which we need to handle diff --git a/utils/networking.go b/utils/networking.go index 20343c9..b41018d 100644 --- a/utils/networking.go +++ b/utils/networking.go @@ -3,10 +3,14 @@ package utils import ( "bytes" "encoding/binary" - "errors" "io" ) +const ( + InvalidPacketLengthError = Error("InvalidPacketLengthError") + InvalidChannelIDError = Error("InvalidChannelIDError") +) + // RicochetData is a structure containing the raw data and the channel it the // message originated on. type RicochetData struct { @@ -36,11 +40,11 @@ type RicochetNetwork struct { func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data []byte) error { packet := make([]byte, 4+len(data)) if len(packet) > 65535 { - return errors.New("packet too large") + return InvalidPacketLengthError } binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet))) if channel < 0 || channel > 65535 { - return errors.New("invalid channel ID") + return InvalidChannelIDError } binary.BigEndian.PutUint16(packet[2:4], uint16(channel)) copy(packet[4:], data[:]) @@ -68,7 +72,7 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e size := int(binary.BigEndian.Uint16(header[0:2])) if size < 4 { - return packet, errors.New("invalid packet length") + return packet, InvalidPacketLengthError } packet.Channel = int32(binary.BigEndian.Uint16(header[2:4])) diff --git a/utils/networkresolver.go b/utils/networkresolver.go index faf2602..a571efc 100644 --- a/utils/networkresolver.go +++ b/utils/networkresolver.go @@ -1,12 +1,17 @@ package utils import ( - "errors" "golang.org/x/net/proxy" "net" "strings" ) +const ( + CannotResolveLocalTCPAddressError = Error("CannotResolveLocalTCPAddressError") + CannotDialLocalTCPAddressError = Error("CannotDialLocalTCPAddressError") + CannotDialRicochetAddressError = Error("CannotDialRicochetAddressError") +) + // NetworkResolver allows a client to resolve various hostnames to connections // The supported types are onions address are: // * ricochet:jlq67qzo6s4yp3sp @@ -21,11 +26,11 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) { addrParts := strings.Split(hostname, "|") tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0]) if err != nil { - return nil, "", errors.New("Cannot Resolve Local TCP Address") + return nil, "", CannotResolveLocalTCPAddressError } conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { - return nil, "", errors.New("Cannot Dial Local TCP Address") + return nil, "", CannotDialLocalTCPAddressError } // return just the onion address, not the local override for the hostname @@ -45,7 +50,7 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) { conn, err := torDialer.Dial("tcp", resolvedHostname+".onion:9878") if err != nil { - return nil, "", errors.New("Cannot Dial Remote Ricochet Address") + return nil, "", CannotDialRicochetAddressError } return conn, resolvedHostname, nil