From 9ae0eac4f305e914df410c63324f30b36ad3187c Mon Sep 17 00:00:00 2001 From: John Brooks Date: Fri, 11 Aug 2017 18:10:41 -0600 Subject: [PATCH] Update go-ricochet to new API (plus fixes) go-ricochet upstream is in the midst of a massive API rewrite, moving towards something that is more flexible and robust. It's a work in progress, but already better than what we were on before. This updates the vendored go-ricochet to a fork off of the latest master, including a series of patches that were needed to fix or add behavior needed by ricochet-go. All of these are submitted upstream, and the goal is to point back there as soon as possible. Current upstream is https://github.com/special/go-ricochet-protocol on the api-rework-fixes branch. The go-ricochet/vendor folder was manually deleted during the import, because it vendors only dependencies that are also vendored by this repository. --- vendor/github.com/s-rah/go-ricochet/LICENSE | 10 +- .../application/acceptallcontactmanager.go | 19 + .../go-ricochet/application/application.go | 140 +++++ .../application/contactmanagerinterface.go | 11 + .../application/examples/echobot/main.go | 31 + .../go-ricochet/application/ricochetonion.go | 27 + .../s-rah/go-ricochet/authhandler.go | 57 -- .../s-rah/go-ricochet/channels/channel.go | 28 + .../s-rah/go-ricochet/channels/chatchannel.go | 146 +++++ .../channels/contactrequestchannel.go | 162 ++++++ .../s-rah/go-ricochet/channels/handler.go | 51 ++ .../channels/hiddenserviceauthchannel.go | 260 +++++++++ .../connection/autoconnectionhandler.go | 53 ++ .../go-ricochet/connection/channelmanager.go | 114 ++++ .../go-ricochet/connection/connection.go | 498 ++++++++++++++++ .../s-rah/go-ricochet/connection/handler.go | 28 + .../connection/inboundconnectionhandler.go | 89 +++ .../connection/outboundconnectionhandler.go | 90 +++ .../go-ricochet/examples/echobot/main.go | 105 +++- .../github.com/s-rah/go-ricochet/handlers.go | 51 -- .../s-rah/go-ricochet/openconnection.go | 534 ------------------ .../go-ricochet/policies/timeoutpolicy.go | 32 ++ .../github.com/s-rah/go-ricochet/ricochet.go | 188 ++---- .../go-ricochet/standardricochetservice.go | 210 ------- .../s-rah/go-ricochet/utils/crypto.go | 56 ++ .../s-rah/go-ricochet/utils/error.go | 53 +- .../go-ricochet/{ => utils}/messagebuilder.go | 135 +++-- .../s-rah/go-ricochet/utils/networking.go | 12 +- .../go-ricochet/utils/networkresolver.go | 15 +- .../{ => wire}/auth/auth_message.go | 2 +- .../s-rah/go-ricochet/{ => wire}/chat/chat.go | 0 .../go-ricochet/{ => wire}/contact/request.go | 2 +- .../{ => wire}/control/control_message.go | 0 vendor/manifest | 6 +- 34 files changed, 2141 insertions(+), 1074 deletions(-) create mode 100644 vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go create mode 100644 vendor/github.com/s-rah/go-ricochet/application/application.go create mode 100644 vendor/github.com/s-rah/go-ricochet/application/contactmanagerinterface.go create mode 100644 vendor/github.com/s-rah/go-ricochet/application/examples/echobot/main.go create mode 100644 vendor/github.com/s-rah/go-ricochet/application/ricochetonion.go delete mode 100644 vendor/github.com/s-rah/go-ricochet/authhandler.go create mode 100644 vendor/github.com/s-rah/go-ricochet/channels/channel.go create mode 100644 vendor/github.com/s-rah/go-ricochet/channels/chatchannel.go create mode 100644 vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go create mode 100644 vendor/github.com/s-rah/go-ricochet/channels/handler.go create mode 100644 vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/channelmanager.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/connection.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/handler.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go create mode 100644 vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go delete mode 100644 vendor/github.com/s-rah/go-ricochet/handlers.go delete mode 100644 vendor/github.com/s-rah/go-ricochet/openconnection.go create mode 100644 vendor/github.com/s-rah/go-ricochet/policies/timeoutpolicy.go delete mode 100644 vendor/github.com/s-rah/go-ricochet/standardricochetservice.go create mode 100644 vendor/github.com/s-rah/go-ricochet/utils/crypto.go rename vendor/github.com/s-rah/go-ricochet/{ => utils}/messagebuilder.go (69%) rename vendor/github.com/s-rah/go-ricochet/{ => wire}/auth/auth_message.go (97%) rename vendor/github.com/s-rah/go-ricochet/{ => wire}/chat/chat.go (100%) rename vendor/github.com/s-rah/go-ricochet/{ => wire}/contact/request.go (98%) rename vendor/github.com/s-rah/go-ricochet/{ => wire}/control/control_message.go (100%) diff --git a/vendor/github.com/s-rah/go-ricochet/LICENSE b/vendor/github.com/s-rah/go-ricochet/LICENSE index 51326fa..53fed65 100644 --- a/vendor/github.com/s-rah/go-ricochet/LICENSE +++ b/vendor/github.com/s-rah/go-ricochet/LICENSE @@ -25,8 +25,8 @@ SOFTWARE. -------------------------------------------------------------------------------- -Autogenerated protobuf code was generated using the proto file from Ricochet. -They are covered under the following license. +Autogenerated protobuf code was generated using the proto file from Ricochet. +They are covered under the following license. Ricochet - https://ricochet.im/ Copyright (C) 2014, John Brooks @@ -61,10 +61,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- -The go-ricochet logo is based on an image by Olga Shalakhina - who in turn modified the original gopher images made by -Renee French. The image is licensed under Creative Commons 3.0 Attributions. - --------------------------------------------------------------------------------- - go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project. diff --git a/vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go b/vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go new file mode 100644 index 0000000..8bc34a8 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go @@ -0,0 +1,19 @@ +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) ContactRequest(name string, message string) string { + return "Accepted" +} diff --git a/vendor/github.com/s-rah/go-ricochet/application/application.go b/vendor/github.com/s-rah/go-ricochet/application/application.go new file mode 100644 index 0000000..4338899 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/application/application.go @@ -0,0 +1,140 @@ +package application + +import ( + "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 { + contactManager ContactManagerInterface + privateKey *rsa.PrivateKey + chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) + chatMessageAckHandler func(*RicochetApplicationInstance, uint32) + l net.Listener +} + +type RicochetApplicationInstance struct { + connection.AutoConnectionHandler + connection *connection.Connection + RemoteHostname string + ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) + ChatMessageAckHandler func(*RicochetApplicationInstance, uint32) +} + +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) { + rai.connection.Do(func() error { + // Technically this errors afte the second time but we can ignore it. + rai.connection.RequestOpenChannel("im.ricochet.chat", rai) + + channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound) + if channel != nil { + chatchannel, ok := channel.Handler.(*channels.ChatChannel) + if ok { + chatchannel.SendMessage(message) + } + } + 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) Shutdown () { + log.Printf("Closing") + ra.l.Close() + log.Printf("Closed") +} + +func (ra *RicochetApplication) Run(l net.Listener) { + if ra.privateKey == nil || ra.contactManager == nil { + return + } + ra.l = l + var err error + for err == nil { + conn, err := ra.l.Accept() + if err == nil { + go ra.handleConnection(conn) + } else { + log.Printf("Closing") + return + } + } +} diff --git a/vendor/github.com/s-rah/go-ricochet/application/contactmanagerinterface.go b/vendor/github.com/s-rah/go-ricochet/application/contactmanagerinterface.go new file mode 100644 index 0000000..4573178 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/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/vendor/github.com/s-rah/go-ricochet/application/examples/echobot/main.go b/vendor/github.com/s-rah/go-ricochet/application/examples/echobot/main.go new file mode 100644 index 0000000..dc11951 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/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/vendor/github.com/s-rah/go-ricochet/application/ricochetonion.go b/vendor/github.com/s-rah/go-ricochet/application/ricochetonion.go new file mode 100644 index 0000000..82f965d --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/application/ricochetonion.go @@ -0,0 +1,27 @@ +package application + +import ( + "crypto/rsa" + "github.com/yawning/bulb" + "net" +) + +// "127.0.0.1:9051" "tcp4" +// "/var/run/tor/control" "unix" +func SetupOnion(torControlAddress string, torControlSocketType string, authentication string, pk *rsa.PrivateKey, onionport uint16) (net.Listener, error) { + c, err := bulb.Dial(torControlSocketType, torControlAddress) + 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/vendor/github.com/s-rah/go-ricochet/authhandler.go b/vendor/github.com/s-rah/go-ricochet/authhandler.go deleted file mode 100644 index f2890ef..0000000 --- a/vendor/github.com/s-rah/go-ricochet/authhandler.go +++ /dev/null @@ -1,57 +0,0 @@ -package goricochet - -import ( - "crypto/hmac" - "crypto/rand" - "crypto/sha256" - "io" -) - -// AuthenticationHandler manages the state required for the AuthHiddenService -// authentication scheme for ricochet. -type AuthenticationHandler struct { - clientCookie [16]byte - serverCookie [16]byte -} - -// AddClientCookie adds a client cookie to the state. -func (ah *AuthenticationHandler) AddClientCookie(cookie []byte) { - copy(ah.clientCookie[:], cookie[:16]) -} - -// AddServerCookie adds a server cookie to the state. -func (ah *AuthenticationHandler) AddServerCookie(cookie []byte) { - copy(ah.serverCookie[:], cookie[:16]) -} - -// GenRandom generates a random 16byte cookie string. -func (ah *AuthenticationHandler) GenRandom() [16]byte { - var cookie [16]byte - io.ReadFull(rand.Reader, cookie[:]) - return cookie -} - -// GenClientCookie generates and adds a client cookie to the state. -func (ah *AuthenticationHandler) GenClientCookie() [16]byte { - ah.clientCookie = ah.GenRandom() - return ah.clientCookie -} - -// GenServerCookie generates and adds a server cookie to the state. -func (ah *AuthenticationHandler) GenServerCookie() [16]byte { - ah.serverCookie = ah.GenRandom() - return ah.serverCookie -} - -// GenChallenge constructs the challenge parameter for the AuthHiddenService session. -// The challenge is the a Sha256HMAC(clientHostname+serverHostname, key=clientCookie+serverCookie) -func (ah *AuthenticationHandler) GenChallenge(clientHostname string, serverHostname string) []byte { - key := make([]byte, 32) - copy(key[0:16], ah.clientCookie[:]) - copy(key[16:], ah.serverCookie[:]) - value := []byte(clientHostname + serverHostname) - mac := hmac.New(sha256.New, key) - mac.Write(value) - hmac := mac.Sum(nil) - return hmac -} diff --git a/vendor/github.com/s-rah/go-ricochet/channels/channel.go b/vendor/github.com/s-rah/go-ricochet/channels/channel.go new file mode 100644 index 0000000..53b8632 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/channels/channel.go @@ -0,0 +1,28 @@ +package channels + +// Direction indicated whether we or the remote peer opened the channel +type Direction int + +const ( + // Inbound indcates the channel was opened by the remote peer + Inbound Direction = iota + // Outbound indicated the channel was opened by us + Outbound +) + +// Channel holds the state of a channel on an open connection +type Channel struct { + ID int32 + + Type string + Direction Direction + Handler Handler + Pending bool + ServerHostname string + ClientHostname string + + // Functions for updating the underlying Connection + SendMessage func([]byte) + CloseChannel func() + DelegateAuthorization func() +} diff --git a/vendor/github.com/s-rah/go-ricochet/channels/chatchannel.go b/vendor/github.com/s-rah/go-ricochet/channels/chatchannel.go new file mode 100644 index 0000000..c4e6e19 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/channels/chatchannel.go @@ -0,0 +1,146 @@ +package channels + +import ( + "crypto/rand" + "github.com/golang/protobuf/proto" + "github.com/s-rah/go-ricochet/utils" + "github.com/s-rah/go-ricochet/wire/chat" + "github.com/s-rah/go-ricochet/wire/control" + "math" + "math/big" + "time" +) + +// ChatChannel implements the ChannelHandler interface for a channel of +// type "im.ricochet.chat". The channel may be inbound or outbound. +// +// ChatChannel implements protocol-level sanity and state validation, but +// does not handle or acknowledge chat messages. The application must provide +// a ChatChannelHandler implementation to handle chat events. +type ChatChannel struct { + // Methods of Handler are called for chat events on this channel + Handler ChatChannelHandler + channel *Channel + lastMessageID uint32 +} + +// ChatChannelHandler is implemented by an application type to receive +// events from a ChatChannel. +// +// Note that ChatChannelHandler is composable with other interfaces, including +// ConnectionHandler; there is no need to use a distinct type as a +// ChatChannelHandler. +type ChatChannelHandler interface { + // ChatMessage is called when a chat message is received. Return true to acknowledge + // the message successfully, and false to NACK and refuse the message. + ChatMessage(messageID uint32, when time.Time, message string) bool + // ChatMessageAck is called when an acknowledgement of a sent message is received. + ChatMessageAck(messageID uint32) +} + +// SendMessage sends a given message using this channe +func (cc *ChatChannel) SendMessage(message string) { + messageBuilder := new(utils.MessageBuilder) + //TODO Implement Chat Number + data := messageBuilder.ChatMessage(message, cc.lastMessageID) + cc.lastMessageID++ + cc.channel.SendMessage(data) +} + +// Acknowledge indicates the given messageID was received +func (cc *ChatChannel) Acknowledge(messageID uint32) { + messageBuilder := new(utils.MessageBuilder) + cc.channel.SendMessage(messageBuilder.AckChatMessage(messageID)) +} + +// Type returns the type string for this channel, e.g. "im.ricochet.chat". +func (cc *ChatChannel) Type() string { + return "im.ricochet.chat" +} + +// Closed is called when the channel is closed for any reason. +func (cc *ChatChannel) Closed(err error) { + +} + +// OnlyClientCanOpen - for chat channels any side can open +func (cc *ChatChannel) OnlyClientCanOpen() bool { + return false +} + +// Singleton - for chat channels there can only be one instance per direction +func (cc *ChatChannel) Singleton() bool { + return true +} + +// Bidirectional - for chat channels are not bidrectional +func (cc *ChatChannel) Bidirectional() bool { + return false +} + +// RequiresAuthentication - chat channels require hidden service auth +func (cc *ChatChannel) RequiresAuthentication() string { + return "im.ricochet.auth.hidden-service" +} + +// OpenInbound is the first method called for an inbound channel request. +// If an error is returned, the channel is rejected. If a RawMessage is +// returned, it will be sent as the ChannelResult message. +func (cc *ChatChannel) OpenInbound(channel *Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { + cc.channel = channel + id, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) + if err != nil { + return nil, err + } + cc.lastMessageID = uint32(id.Uint64()) + cc.channel.Pending = false + messageBuilder := new(utils.MessageBuilder) + return messageBuilder.AckOpenChannel(channel.ID), nil +} + +// OpenOutbound is the first method called for an outbound channel request. +// If an error is returned, the channel is not opened. If a RawMessage is +// returned, it will be sent as the OpenChannel message. +func (cc *ChatChannel) OpenOutbound(channel *Channel) ([]byte, error) { + cc.channel = channel + id, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) + if err != nil { + return nil, err + } + cc.lastMessageID = uint32(id.Uint64()) + messageBuilder := new(utils.MessageBuilder) + return messageBuilder.OpenChannel(channel.ID, cc.Type()), nil +} + +// OpenOutboundResult is called when a response is received for an +// outbound OpenChannel request. If `err` is non-nil, the channel was +// rejected and Closed will be called immediately afterwards. `raw` +// contains the raw protocol message including any extension data. +func (cc *ChatChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { + if err == nil { + if crm.GetOpened() { + cc.channel.Pending = false + } + } +} + +// Packet is called for each raw packet received on this channel. +func (cc *ChatChannel) Packet(data []byte) { + if !cc.channel.Pending { + res := new(Protocol_Data_Chat.Packet) + err := proto.Unmarshal(data, res) + if err == nil { + if res.GetChatMessage() != nil { + ack := cc.Handler.ChatMessage(res.GetChatMessage().GetMessageId(), time.Now(), res.GetChatMessage().GetMessageText()) + if ack { + cc.Acknowledge(res.GetChatMessage().GetMessageId()) + } else { + //XXX + } + } else if res.GetChatAcknowledge() != nil { + cc.Handler.ChatMessageAck(res.GetChatMessage().GetMessageId()) + } + // XXX? + } + } +} diff --git a/vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go b/vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go new file mode 100644 index 0000000..4ca1b2d --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go @@ -0,0 +1,162 @@ +package channels + +import ( + "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. +type ContactRequestChannel struct { + // Methods of Handler are called for chat events on this channel + Handler ContactRequestChannelHandler + channel *Channel + + // Properties of the request + Name string + Message string +} + +// ContactRequestChannelHandler is implemented by an application type to receive +// events from a ContactRequestChannel. +// +// Note that ContactRequestChannelHandler is composable with other interfaces, including +// ConnectionHandler; there is no need to use a distinct type as a +// ContactRequestChannelHandler. +type ContactRequestChannelHandler interface { + ContactRequest(name string, message string) string + ContactRequestRejected() + ContactRequestAccepted() + ContactRequestError() +} + +// OnlyClientCanOpen - only clients can open contact requests +func (crc *ContactRequestChannel) OnlyClientCanOpen() bool { + return true +} + +// Singleton - only one contact request can be opened per side +func (crc *ContactRequestChannel) Singleton() bool { + return true +} + +// Bidirectional - only clients can send messages +func (crc *ContactRequestChannel) Bidirectional() bool { + return false +} + +// RequiresAuthentication - contact requests require hidden service auth +func (crc *ContactRequestChannel) RequiresAuthentication() string { + return "im.ricochet.auth.hidden-service" +} + +// Type returns the type string for this channel, e.g. "im.ricochet.chat". +func (crc *ContactRequestChannel) Type() string { + return "im.ricochet.contact.request" +} + +// Closed is called when the channel is closed for any reason. +func (crc *ContactRequestChannel) Closed(err error) { + +} + +// OpenInbound is the first method called for an inbound channel request. +// If an error is returned, the channel is rejected. If a RawMessage is +// returned, it will be sent as the ChannelResult message. +func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { + crc.channel = channel + contactRequestI, err := proto.GetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest) + if err == nil { + contactRequest, check := contactRequestI.(*Protocol_Data_ContactRequest.ContactRequest) + if check { + + if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) { + // Violation of the Protocol + return nil, InvalidContactNameError + } + + if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) { + // Violation of the Protocol + return nil, InvalidContactMessageError + } + + crc.Name = contactRequest.GetNickname() + crc.Message = contactRequest.GetMessageText() + result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText()) + messageBuilder := new(utils.MessageBuilder) + return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil + } + } + return nil, InvalidContactRequestError +} + +// OpenOutbound is the first method called for an outbound channel request. +// If an error is returned, the channel is not opened. If a RawMessage is +// returned, it will be sent as the OpenChannel message. +func (crc *ContactRequestChannel) OpenOutbound(channel *Channel) ([]byte, error) { + crc.channel = channel + messageBuilder := new(utils.MessageBuilder) + return messageBuilder.OpenContactRequestChannel(channel.ID, crc.Name, crc.Message), nil +} + +// OpenOutboundResult is called when a response is received for an +// outbound OpenChannel request. If `err` is non-nil, the channel was +// rejected and Closed will be called immediately afterwards. `raw` +// contains the raw protocol message including any extension data. +func (crc *ContactRequestChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { + if err == nil { + if crm.GetOpened() { + responseI, err := proto.GetExtension(crm, Protocol_Data_ContactRequest.E_Response) + if err == nil { + response, check := responseI.(*Protocol_Data_ContactRequest.Response) + if check { + crc.handleStatus(response.GetStatus().String()) + return + } + } + } + } + crc.channel.SendMessage([]byte{}) +} + +func (crc *ContactRequestChannel) SendResponse(status string) { + messageBuilder := new(utils.MessageBuilder) + crc.channel.SendMessage(messageBuilder.ReplyToContactRequest(crc.channel.ID, status)) +} + +func (crc *ContactRequestChannel) handleStatus(status string) { + switch status { + case "Accepted": + crc.Handler.ContactRequestAccepted() + case "Pending": + break + case "Rejected": + crc.Handler.ContactRequestRejected() + break + case "Error": + crc.Handler.ContactRequestError() + break + } +} + +// Packet is called for each raw packet received on this channel. +func (crc *ContactRequestChannel) Packet(data []byte) { + if !crc.channel.Pending { + response := new(Protocol_Data_ContactRequest.Response) + err := proto.Unmarshal(data, response) + if err == nil { + crc.handleStatus(response.GetStatus().String()) + return + } + } + crc.channel.SendMessage([]byte{}) +} diff --git a/vendor/github.com/s-rah/go-ricochet/channels/handler.go b/vendor/github.com/s-rah/go-ricochet/channels/handler.go new file mode 100644 index 0000000..ac21033 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/channels/handler.go @@ -0,0 +1,51 @@ +package channels + +import ( + "github.com/s-rah/go-ricochet/wire/control" +) + +// Handler reacts to low-level events on a protocol channel. There +// should be a unique instance of a ChannelHandler type per channel. +// +// Applications generally don't need to implement ChannelHandler directly; +// instead, use the built-in implementations for common channel types, and +// their individual callback interfaces. ChannelHandler is useful when +// implementing new channel types, or modifying low level default behavior. +type Handler interface { + // Type returns the type string for this channel, e.g. "im.ricochet.chat". + Type() string + + // Closed is called when the channel is closed for any reason. + Closed(err error) + + // OnlyClientCanOpen indicates if only a client can open a given channel + OnlyClientCanOpen() bool + + // Singleton indicates if a channel can only have one instance per direction + Singleton() bool + + // Bidirectional indicates if messages can be send by either side + Bidirectional() bool + + // RequiresAuthentication describes what authentication is needed for the channel + RequiresAuthentication() string + + // OpenInbound is the first method called for an inbound channel request. + // If an error is returned, the channel is rejected. If a RawMessage is + // returned, it will be sent as the ChannelResult message. + OpenInbound(channel *Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) + + // OpenOutbound is the first method called for an outbound channel request. + // If an error is returned, the channel is not opened. If a RawMessage is + // returned, it will be sent as the OpenChannel message. + OpenOutbound(channel *Channel) ([]byte, error) + + // OpenOutboundResult is called when a response is received for an + // outbound OpenChannel request. If `err` is non-nil, the channel was + // rejected and Closed will be called immediately afterwards. `raw` + // contains the raw protocol message including any extension data. + OpenOutboundResult(err error, raw *Protocol_Data_Control.ChannelResult) + + // Packet is called for each raw packet received on this channel. + Packet(data []byte) +} diff --git a/vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go b/vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go new file mode 100644 index 0000000..cb90f4c --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go @@ -0,0 +1,260 @@ +package channels + +import ( + "crypto" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/asn1" + "github.com/golang/protobuf/proto" + "github.com/s-rah/go-ricochet/utils" + "github.com/s-rah/go-ricochet/wire/auth" + "github.com/s-rah/go-ricochet/wire/control" + "io" +) + +const ( + InvalidClientCookieError = utils.Error("InvalidClientCookieError") +) + +// HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service" +type HiddenServiceAuthChannel struct { + // PrivateKey must be set for client-side authentication channels + PrivateKey *rsa.PrivateKey + // Server Hostname must be set for client-side authentication channels + ServerHostname string + + // Callbacks + ClientAuthResult func(accepted, isKnownContact bool) + ServerAuthValid func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) + ServerAuthInvalid func(err error) + + // Internal state + clientCookie, serverCookie [16]byte + channel *Channel +} + +// Type returns the type string for this channel, e.g. "im.ricochet.chat". +func (ah *HiddenServiceAuthChannel) Type() string { + return "im.ricochet.auth.hidden-service" +} + +// Singleton Returns whether or not the given channel type is a singleton +func (ah *HiddenServiceAuthChannel) Singleton() bool { + return true +} + +// OnlyClientCanOpen ... +func (ah *HiddenServiceAuthChannel) OnlyClientCanOpen() bool { + return true +} + +// Bidirectional Returns whether or not the given channel allows anyone to send messages +func (ah *HiddenServiceAuthChannel) Bidirectional() bool { + return false +} + +// RequiresAuthentication Returns whether or not the given channel type requires authentication +func (ah *HiddenServiceAuthChannel) RequiresAuthentication() string { + return "none" +} + +// Closed is called when the channel is closed for any reason. +func (ah *HiddenServiceAuthChannel) Closed(err error) { + +} + +// OpenInbound is the first method called for an inbound channel request. +// If an error is returned, the channel is rejected. If a RawMessage is +// returned, it will be sent as the ChannelResult message. +// 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 + } + + ah.channel = channel + clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie) + if len(clientCookie.([]byte)[:]) != 16 { + // reutrn without opening channel. + return nil, InvalidClientCookieError + } + ah.AddClientCookie(clientCookie.([]byte)[:]) + messageBuilder := new(utils.MessageBuilder) + channel.Pending = false + return messageBuilder.ConfirmAuthChannel(ah.channel.ID, ah.GenServerCookie()), nil +} + +// OpenOutbound is the first method called for an outbound channel request. +// If an error is returned, the channel is not opened. If a RawMessage is +// returned, it will be sent as the OpenChannel message. +// Local -> [Open Authentication Channel] -> Remote +func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) { + + if ah.PrivateKey == nil { + return nil, utils.PrivateKeyNotSetError + } + + ah.channel = channel + messageBuilder := new(utils.MessageBuilder) + return messageBuilder.OpenAuthenticationChannel(ah.channel.ID, ah.GenClientCookie()), nil +} + +// OpenOutboundResult is called when a response is received for an +// outbound OpenChannel request. If `err` is non-nil, the channel was +// rejected and Closed will be called immediately afterwards. `raw` +// contains the raw protocol message including any extension data. +// Input: Remote -> [ChannelResult] -> {Client} +// Output: {Client} -> [Proof] -> Remote +func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { + + if err == nil { + + if crm.GetOpened() { + serverCookie, _ := proto.GetExtension(crm, Protocol_Data_AuthHiddenService.E_ServerCookie) + + if len(serverCookie.([]byte)[:]) != 16 { + ah.channel.SendMessage([]byte{}) + return + } + + ah.AddServerCookie(serverCookie.([]byte)[:]) + + publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ + N: ah.PrivateKey.PublicKey.N, + E: ah.PrivateKey.PublicKey.E, + }) + + clientHostname := utils.GetTorHostname(publicKeyBytes) + challenge := ah.GenChallenge(clientHostname, ah.ServerHostname) + + signature, err := rsa.SignPKCS1v15(nil, ah.PrivateKey, crypto.SHA256, challenge) + + if err != nil { + ah.channel.SendMessage([]byte{}) + return + } + + messageBuilder := new(utils.MessageBuilder) + proof := messageBuilder.Proof(publicKeyBytes, signature) + ah.channel.SendMessage(proof) + } + } +} + +// Packet is called for each raw packet received on this channel. +// Input: Remote -> [Proof] -> Client +// OR +// Input: Remote -> [Result] -> Client +func (ah *HiddenServiceAuthChannel) Packet(data []byte) { + res := new(Protocol_Data_AuthHiddenService.Packet) + err := proto.Unmarshal(data[:], res) + + if err != nil { + ah.channel.CloseChannel() + return + } + + if res.GetProof() != nil && ah.channel.Direction == Inbound { + provisionalClientHostname := utils.GetTorHostname(res.GetProof().GetPublicKey()) + + publicKeyBytes, err := asn1.Marshal(rsa.PublicKey{ + N: ah.PrivateKey.PublicKey.N, + E: ah.PrivateKey.PublicKey.E, + }) + + if err != nil { + ah.ServerAuthInvalid(err) + ah.channel.SendMessage([]byte{}) + return + } + + serverHostname := utils.GetTorHostname(publicKeyBytes) + + publicKey := rsa.PublicKey{} + _, err = asn1.Unmarshal(res.GetProof().GetPublicKey(), &publicKey) + if err != nil { + ah.ServerAuthInvalid(err) + ah.channel.SendMessage([]byte{}) + return + } + + challenge := ah.GenChallenge(provisionalClientHostname, serverHostname) + + err = rsa.VerifyPKCS1v15(&publicKey, crypto.SHA256, challenge[:], res.GetProof().GetSignature()) + + if err == nil { + // Signature is Good + accepted, isKnownContact := ah.ServerAuthValid(provisionalClientHostname, publicKey) + + // Send Result + messageBuilder := new(utils.MessageBuilder) + result := messageBuilder.AuthResult(accepted, isKnownContact) + ah.channel.DelegateAuthorization() + ah.channel.SendMessage(result) + } else { + // Auth Failed + messageBuilder := new(utils.MessageBuilder) + result := messageBuilder.AuthResult(false, false) + ah.channel.SendMessage(result) + ah.ServerAuthInvalid(err) + } + + } else if res.GetResult() != nil && ah.channel.Direction == Outbound { + if ah.ClientAuthResult != nil { + ah.ClientAuthResult(res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact()) + } + if res.GetResult().GetAccepted() { + ah.channel.DelegateAuthorization() + } + } + + // Any other combination of packets is completely invalid + // Fail the Authorization right here. + ah.channel.CloseChannel() +} + +// AddClientCookie adds a client cookie to the state. +func (ah *HiddenServiceAuthChannel) AddClientCookie(cookie []byte) { + copy(ah.clientCookie[:], cookie[:16]) +} + +// AddServerCookie adds a server cookie to the state. +func (ah *HiddenServiceAuthChannel) AddServerCookie(cookie []byte) { + copy(ah.serverCookie[:], cookie[:16]) +} + +// GenRandom generates a random 16byte cookie string. +func (ah *HiddenServiceAuthChannel) GenRandom() [16]byte { + var cookie [16]byte + io.ReadFull(rand.Reader, cookie[:]) + return cookie +} + +// GenClientCookie generates and adds a client cookie to the state. +func (ah *HiddenServiceAuthChannel) GenClientCookie() [16]byte { + ah.clientCookie = ah.GenRandom() + return ah.clientCookie +} + +// GenServerCookie generates and adds a server cookie to the state. +func (ah *HiddenServiceAuthChannel) GenServerCookie() [16]byte { + ah.serverCookie = ah.GenRandom() + return ah.serverCookie +} + +// GenChallenge constructs the challenge parameter for the AuthHiddenService session. +// The challenge is the a Sha256HMAC(clientHostname+serverHostname, key=clientCookie+serverCookie) +func (ah *HiddenServiceAuthChannel) GenChallenge(clientHostname string, serverHostname string) []byte { + key := make([]byte, 32) + copy(key[0:16], ah.clientCookie[:]) + copy(key[16:], ah.serverCookie[:]) + + value := []byte(clientHostname + serverHostname) + mac := hmac.New(sha256.New, key) + mac.Write(value) + hmac := mac.Sum(nil) + return hmac +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go b/vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go new file mode 100644 index 0000000..cbb96c3 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go @@ -0,0 +1,53 @@ +package connection + +import ( + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/utils" +) + +// AutoConnectionHandler implements the ConnectionHandler interface on behalf of +// the provided application type by automatically providing support for any +// built-in channel type whose high level interface is implemented by the +// application. For example, if the application's type implements the +// ChatChannelHandler interface, `im.ricochet.chat` will be available to the peer. +// +// The application handler can be any other type. To override or augment any of +// AutoConnectionHandler's behavior (such as adding new channel types, or reacting +// to connection close events), this type can be embedded in the type that it serves. +type AutoConnectionHandler struct { + handlerMap map[string]func() channels.Handler + connection *Connection +} + +// Init ... +// TODO: Split this into client and server init +func (ach *AutoConnectionHandler) Init() { + ach.handlerMap = make(map[string]func() channels.Handler) +} + +// OnReady ... +func (ach *AutoConnectionHandler) OnReady(oc *Connection) { + ach.connection = oc +} + +// OnClosed is called when the OpenConnection has closed for any reason. +func (ach *AutoConnectionHandler) OnClosed(err error) { +} + +// RegisterChannelHandler ... +func (ach *AutoConnectionHandler) RegisterChannelHandler(ctype string, handler func() channels.Handler) { + _, exists := ach.handlerMap[ctype] + if !exists { + ach.handlerMap[ctype] = handler + } +} + +// OnOpenChannelRequest ... +func (ach *AutoConnectionHandler) OnOpenChannelRequest(ctype string) (channels.Handler, error) { + handler, ok := ach.handlerMap[ctype] + if ok { + h := handler() + return h, nil + } + return nil, utils.UnknownChannelTypeError +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/channelmanager.go b/vendor/github.com/s-rah/go-ricochet/connection/channelmanager.go new file mode 100644 index 0000000..2f5210d --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/channelmanager.go @@ -0,0 +1,114 @@ +package connection + +import ( + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/utils" +) + +// ChannelManager encapsulates the logic for server and client side assignment +// and removal of channels. +type ChannelManager struct { + channels map[int32]*channels.Channel + nextFreeChannel int32 + isClient bool +} + +// NewClientChannelManager construsts a new channel manager enforcing behaviour +// of a ricochet client +func NewClientChannelManager() *ChannelManager { + channelManager := new(ChannelManager) + channelManager.channels = make(map[int32]*channels.Channel) + channelManager.nextFreeChannel = 1 + channelManager.isClient = true + return channelManager +} + +// NewServerChannelManager construsts a new channel manager enforcing behaviour +// from a ricochet server +func NewServerChannelManager() *ChannelManager { + channelManager := new(ChannelManager) + channelManager.channels = make(map[int32]*channels.Channel) + channelManager.nextFreeChannel = 2 + channelManager.isClient = false + return channelManager +} + +// OpenChannelRequest constructs a channel type ready for processing given a request +// from the client. +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, utils.AttemptToOpenMoreThanOneSingletonChannelError + } + + channel := new(channels.Channel) + channel.ID = cm.nextFreeChannel + cm.nextFreeChannel += 2 + channel.Type = chandler.Type() + channel.Handler = chandler + channel.Pending = true + channel.Direction = channels.Outbound + cm.channels[channel.ID] = channel + return channel, nil +} + +// OpenChannelRequestFromPeer constructs a channel type ready for processing given a request +// from the remote peer. +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, utils.ServerAttemptedToOpenEvenNumberedChannelError + } else if !cm.isClient && (channelID%2) == 0 { + // Server is trying to open odd numbered channels + return nil, utils.ClientAttemptedToOpenOddNumberedChannelError + } + + _, exists := cm.channels[channelID] + if exists { + 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, utils.AttemptToOpenMoreThanOneSingletonChannelError + } + + channel := new(channels.Channel) + channel.ID = channelID + channel.Type = chandler.Type() + channel.Handler = chandler + + channel.Pending = true + channel.Direction = channels.Inbound + cm.channels[channelID] = channel + return channel, nil +} + +// Channel finds an open or pending `type` channel in the direction `way` (Inbound +// or Outbound), and returns the associated state. Returns nil if no matching channel +// exists or if multiple matching channels exist. +func (cm *ChannelManager) Channel(ctype string, way channels.Direction) *channels.Channel { + var foundChannel *channels.Channel + for _, channel := range cm.channels { + if channel.Handler.Type() == ctype && channel.Direction == way { + if foundChannel == nil { + foundChannel = channel + } else { + // we have found multiple channels. + return nil + } + } + } + return foundChannel +} + +// GetChannel finds and returns a given channel if it is found +func (cm *ChannelManager) GetChannel(channelID int32) (*channels.Channel, bool) { + channel, found := cm.channels[channelID] + return channel, found +} + +// RemoveChannel removes a given channel id. +func (cm *ChannelManager) RemoveChannel(channelID int32) { + delete(cm.channels, channelID) +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/connection.go b/vendor/github.com/s-rah/go-ricochet/connection/connection.go new file mode 100644 index 0000000..f2d1460 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/connection.go @@ -0,0 +1,498 @@ +package connection + +import ( + "context" + "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" + "sync" +) + +// Connection encapsulates the state required to maintain a connection to +// a ricochet service. +type Connection struct { + utils.RicochetNetwork + + channelManager *ChannelManager + + // Ricochet Network Loop + packetChannel chan utils.RicochetData + errorChannel chan error + + breakChannel chan bool + breakResultChannel chan error + + unlockChannel chan bool + unlockResponseChannel chan bool + + messageBuilder utils.MessageBuilder + trace bool + + closed bool + closing bool + // This mutex is exclusively for preventing races during blocking + // interactions with Process; specifically Do and Break. Don't use + // it for anything else. See those functions for an explanation. + processBlockMutex sync.Mutex + + Conn io.ReadWriteCloser + IsInbound bool + Authentication map[string]bool + RemoteHostname string +} + +func (rc *Connection) init() { + + rc.packetChannel = make(chan utils.RicochetData) + rc.errorChannel = make(chan error) + + rc.breakChannel = make(chan bool) + rc.breakResultChannel = make(chan error) + + rc.unlockChannel = make(chan bool) + rc.unlockResponseChannel = make(chan bool) + + rc.Authentication = make(map[string]bool) + go rc.start() +} + +// NewInboundConnection creates a new Connection struct +// modelling an Inbound Connection +func NewInboundConnection(conn io.ReadWriteCloser) *Connection { + rc := new(Connection) + rc.Conn = conn + rc.IsInbound = true + rc.init() + rc.channelManager = NewServerChannelManager() + return rc +} + +// NewOutboundConnection creates a new Connection struct +// modelling an Inbound Connection +func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection { + rc := new(Connection) + rc.Conn = conn + rc.IsInbound = false + rc.init() + rc.RemoteHostname = remoteHostname + rc.channelManager = NewClientChannelManager() + return rc +} + +func (rc *Connection) TraceLog(enabled bool) { + rc.trace = enabled +} + +// start +func (rc *Connection) start() { + for { + packet, err := rc.RecvRicochetPacket(rc.Conn) + if err != nil { + rc.errorChannel <- err + return + } + rc.packetChannel <- packet + } +} + +// Do allows any function utilizing Connection to be run safely, if you're +// careful. All operations which require access (directly or indirectly) to +// Connection while Process is running need to use Do. Calls to Do without +// Process running will block unless the connection is closed, which is +// returned as ConnectionClosedError. +// +// Like a mutex, Do cannot be called recursively. This will deadlock. As +// a result, no API in this library that can be reached from the application +// should use Do, with few exceptions. This would make the API impossible +// to use safely in many cases. +// +// Do is safe to call from methods of connection.Handler and channel.Handler +// that are called by Process. +func (rc *Connection) Do(do func() error) error { + // There's a complicated little dance here to prevent a race when the + // Process call is returning for a connection error. The problem is + // that if Do simply checked rc.closed and then tried to send, it's + // possible for Process to change rc.closed and stop reading before the + // send statement is executed, creating a deadlock. + // + // To prevent this, all of the functions that block on Process should + // do so by acquiring processBlockMutex, aborting if rc.closed is true, + // performing their blocking channel operations, and then releasing the + // mutex. + // + // This works because Process will always use a separate goroutine to + // acquire processBlockMutex before changing rc.closed, and the mutex + // guarantees that no blocking channel operation can happen during or + // after the value is changed. Since these operations block the Process + // loop, the behavior of multiple concurrent calls to Do/Break doesn't + // change: they just end up blocking on the mutex before blocking on the + // channel. + rc.processBlockMutex.Lock() + defer rc.processBlockMutex.Unlock() + if rc.closed { + return utils.ConnectionClosedError + } + + // Force process to soft-break so we can lock + rc.traceLog("request unlocking of process loop for do()") + rc.unlockChannel <- true + rc.traceLog("process loop is unlocked for do()") + defer func() { + rc.traceLog("giving up lock process loop after do() ") + rc.unlockResponseChannel <- true + }() + + // Process sets rc.closing when it's trying to acquire the mutex and + // close down the connection. Behave as if the connection was already + // closed. + if rc.closing { + return utils.ConnectionClosedError + } + return do() +} + +// DoContext behaves in the same way as Do, but also respects the provided +// context when blocked, and passes the context to the callback function. +// +// DoContext should be used when any call to Do may need to be cancelled +// or timed out. +func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) error) error { + // .. see above + rc.processBlockMutex.Lock() + defer rc.processBlockMutex.Unlock() + if rc.closed { + return utils.ConnectionClosedError + } + + // Force process to soft-break so we can lock + rc.traceLog("request unlocking of process loop for do()") + select { + case rc.unlockChannel <- true: + break + case <-ctx.Done(): + rc.traceLog("giving up on unlocking process loop for do() because context cancelled") + return ctx.Err() + } + + rc.traceLog("process loop is unlocked for do()") + defer func() { + rc.traceLog("giving up lock process loop after do() ") + rc.unlockResponseChannel <- true + }() + + if rc.closing { + return utils.ConnectionClosedError + } + return do(ctx) +} + +// RequestOpenChannel sends an OpenChannel message to the remote client. +// An error is returned only if the requirements for opening this channel +// are not met on the local side (a nil error return does not mean the +// channel was opened successfully, because channels open asynchronously). +func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler) (*channels.Channel, error) { + rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype)) + + // Check that we have the authentication already + if handler.RequiresAuthentication() != "none" { + // Enforce Authentication Check. + _, authed := rc.Authentication[handler.RequiresAuthentication()] + if !authed { + return nil, utils.UnauthorizedActionError + } + } + + channel, err := rc.channelManager.OpenChannelRequest(handler) + + if err != nil { + rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err)) + return nil, err + } + + channel.SendMessage = func(message []byte) { + rc.SendRicochetPacket(rc.Conn, channel.ID, message) + } + channel.DelegateAuthorization = func() { + rc.Authentication[handler.Type()] = true + } + channel.CloseChannel = func() { + rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{}) + rc.channelManager.RemoveChannel(channel.ID) + } + response, err := handler.OpenOutbound(channel) + if err == nil { + rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype)) + rc.SendRicochetPacket(rc.Conn, 0, response) + } else { + rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err)) + rc.channelManager.RemoveChannel(channel.ID) + } + return channel, nil +} + +// processUserCallback should be used to wrap any calls into handlers or +// application code from the Process goroutine. It handles calls to Do +// from within that code to prevent deadlocks. +func (rc *Connection) processUserCallback(cb func()) { + done := make(chan struct{}) + go func() { + defer close(done) + cb() + }() + for { + select { + case <-done: + return + case <-rc.unlockChannel: + <-rc.unlockResponseChannel + } + } +} + +// Process receives socket and protocol events for the connection. Methods +// of the application-provided `handler` will be called from this goroutine +// for all events. +// +// Process must be running in order to handle any events on the connection, +// including connection close. +// +// Process blocks until the connection is closed or until Break() is called. +// If the connection is closed, a non-nil error is returned. +func (rc *Connection) Process(handler Handler) error { + if rc.closed { + return utils.ConnectionClosedError + } + rc.traceLog("entering process loop") + rc.processUserCallback(func() { handler.OnReady(rc) }) + + // There are exactly two ways out of this loop: a signal on breakChannel + // caused by a call to Break, or a connection-fatal error on errorChannel. + // + // In the Break case, no particular care is necessary; it is the caller's + // responsibility to make sure there aren't e.g. concurrent calls to Do. + // + // Because connection errors can happen spontaneously, they must carefully + // prevent concurrent calls to Break or Do that could deadlock when Process + // returns. + for { + + var packet utils.RicochetData + select { + case <-rc.unlockChannel: + <-rc.unlockResponseChannel + continue + case <-rc.breakChannel: + rc.traceLog("process has ended after break") + rc.breakResultChannel <- nil + return nil + case packet = <-rc.packetChannel: + break + case err := <-rc.errorChannel: + rc.Conn.Close() + rc.closing = true + + // In order to safely close down concurrent calls to Do or Break, + // processBlockMutex must be held before setting rc.closed. That cannot + // happen in this goroutine, because one of those calls may already hold + // the mutex and be blocking on a channel send to this method. So the + // process here is to have a goroutine acquire the lock, set rc.closed, and + // signal back. Meanwhile, this one keeps handling unlockChannel and + // breakChannel. + closedChan := make(chan struct{}) + go func() { + rc.processBlockMutex.Lock() + defer rc.processBlockMutex.Unlock() + rc.closed = true + close(closedChan) + }() + + // Keep accepting calls from Do or Break until closedChan signals that they're + // safely shut down. + clearLoop: + for { + select { + case <-rc.unlockChannel: + <-rc.unlockResponseChannel + case <-rc.breakChannel: + rc.breakResultChannel <- utils.ConnectionClosedError + case <-closedChan: + break clearLoop + } + } + + // This is the one case where processUserCallback isn't necessary, because + // all calls to Do immediately return ConnectionClosedError now. + handler.OnClosed(err) + return err + } + + if packet.Channel == 0 { + 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 { + // Wrap controlPacket in processUserCallback, since it calls out in many + // places, and wrapping the rest is harmless. + rc.processUserCallback(func() { rc.controlPacket(handler, res) }) + } + } else { + // Let's check to see if we have defined this channel. + channel, found := rc.channelManager.GetChannel(packet.Channel) + if found { + if len(packet.Data) == 0 { + rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel)) + rc.channelManager.RemoveChannel(packet.Channel) + rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) }) + } else { + rc.traceLog(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel)) + // Send The Ricochet Packet to the Handler + rc.processUserCallback(func() { channel.Handler.Packet(packet.Data[:]) }) + } + } else { + // 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)) + if len(packet.Data) != 0 { + rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{}) + } + } + } + } +} + +func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.Packet) { + + if res.GetOpenChannel() != nil { + + opm := res.GetOpenChannel() + chandler, err := handler.OnOpenChannelRequest(opm.GetChannelType()) + + if err != nil { + + response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "UnknownTypeError") + rc.SendRicochetPacket(rc.Conn, 0, response) + return + } + + // 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())) + // Enforce Authentication Check. + _, authed := rc.Authentication[chandler.RequiresAuthentication()] + if !authed { + rc.SendRicochetPacket(rc.Conn, 0, []byte{}) + rc.traceLog(fmt.Sprintf("do not have required authorization to open channel type %v", chandler.Type())) + return + } + rc.traceLog("succeeded authorization check") + } + + channel, err := rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler) + + if err == nil { + + channel.SendMessage = func(message []byte) { + rc.SendRicochetPacket(rc.Conn, channel.ID, message) + } + channel.DelegateAuthorization = func() { + rc.Authentication[chandler.Type()] = true + } + channel.CloseChannel = func() { + rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{}) + rc.channelManager.RemoveChannel(channel.ID) + } + + 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.SendRicochetPacket(rc.Conn, 0, response) + } else { + rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID)) + rc.channelManager.RemoveChannel(channel.ID) + rc.SendRicochetPacket(rc.Conn, 0, []byte{}) + } + } else { + // Send Error Packet + response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "GenericError") + rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier())) + rc.SendRicochetPacket(rc.Conn, 0, response) + + } + } else if res.GetChannelResult() != nil { + cr := res.GetChannelResult() + id := cr.GetChannelIdentifier() + + channel, found := rc.channelManager.GetChannel(id) + + if !found { + 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)) + channel.Handler.OpenOutboundResult(nil, cr) + } else { + rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id)) + channel.Handler.OpenOutboundResult(errors.New(""), cr) + } + + } else if res.GetKeepAlive() != nil { + // XXX Though not currently part of the protocol + // We should likely put these calls behind + // authentication. + rc.traceLog("received keep alive packet") + if res.GetKeepAlive().GetResponseRequested() { + messageBuilder := new(utils.MessageBuilder) + raw := messageBuilder.KeepAlive(true) + rc.traceLog("sending keep alive response") + rc.SendRicochetPacket(rc.Conn, 0, raw) + } + } else if res.GetEnableFeatures() != nil { + rc.traceLog("received features enabled packet") + messageBuilder := new(utils.MessageBuilder) + raw := messageBuilder.FeaturesEnabled([]string{}) + 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") + } +} + +func (rc *Connection) traceLog(message string) { + if rc.trace { + log.Printf(message) + } +} + +// Break causes Process() to return, but does not close the underlying connection +// Break returns an error if it would not be valid to call Process() again for +// the connection now. Currently, the only such error is ConnectionClosedError. +func (rc *Connection) Break() error { + // See Do() for an explanation of the concurrency here; it's complicated. + // The summary is that this mutex prevents races on connection close that + // could lead to deadlocks in Block(). + rc.processBlockMutex.Lock() + defer rc.processBlockMutex.Unlock() + if rc.closed { + rc.traceLog("ignoring break because connection is already closed") + return utils.ConnectionClosedError + } + rc.traceLog("breaking out of process loop") + rc.breakChannel <- true + return <-rc.breakResultChannel // Wait for Process to End +} + +// Channel is a convienciance method for returning a given channel to the caller +// of Process() - TODO - this is kind of ugly. +func (rc *Connection) Channel(ctype string, way channels.Direction) *channels.Channel { + return rc.channelManager.Channel(ctype, way) +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/handler.go b/vendor/github.com/s-rah/go-ricochet/connection/handler.go new file mode 100644 index 0000000..b2fd78b --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/handler.go @@ -0,0 +1,28 @@ +package connection + +import ( + "github.com/s-rah/go-ricochet/channels" +) + +// Handler reacts to low-level events on a protocol connection. +// There should be a unique instance of a ConnectionHandler type per +// OpenConnection. +type Handler interface { + // OnReady is called when the connection begins using this handler. + OnReady(oc *Connection) + + // OnClosed is called when the OpenConnection has closed for any reason. + OnClosed(err error) + + // OpenChannelRequest is called when the peer asks to open a channel of + // `type`. `raw` contains the protocol OpenChannel message including any + // extension data. If this channel type is recognized and allowed by this + // connection in this state, return a type implementing ChannelHandler for + // events related to this channel. Returning an error or nil rejects the + // channel. + // + // Channel type handlers may implement additional state and sanity checks. + // A non-nil return from this function does not guarantee that the channel + // will be opened. + OnOpenChannelRequest(ctype string) (channels.Handler, error) +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go b/vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go new file mode 100644 index 0000000..e74bd47 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go @@ -0,0 +1,89 @@ +package connection + +import ( + "crypto/rsa" + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/policies" + "github.com/s-rah/go-ricochet/utils" + "sync" +) + +// InboundConnectionHandler is a convieniance wrapper for handling inbound +// connections +type InboundConnectionHandler struct { + connection *Connection +} + +// HandleInboundConnection returns an InboundConnectionHandler given a connection +func HandleInboundConnection(c *Connection) *InboundConnectionHandler { + ich := new(InboundConnectionHandler) + ich.connection = c + return ich +} + +// ProcessAuthAsServer blocks until authentication has succeeded, failed, or the +// connection is closed. A non-nil error is returned in all cases other than successful +// and accepted authentication. +// +// ProcessAuthAsServer cannot be called at the same time as any other call to a Process +// function. Another Process function must be called after this function successfully +// returns to continue handling connection events. +// +// The acceptCallback function is called after receiving a valid authentication proof +// with the client's authenticated hostname and public key. acceptCallback must return +// true to accept authentication and allow the connection to continue, and also returns a +// boolean indicating whether the contact is known and recognized. Unknown contacts will +// 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 + } + + var breakOnce sync.Once + + var authAllowed, authKnown bool + var authHostname string + + onAuthValid := func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { + authAllowed, authKnown = sach(hostname, publicKey) + if authAllowed { + authHostname = hostname + } + breakOnce.Do(func() { go ich.connection.Break() }) + return authAllowed, authKnown + } + onAuthInvalid := func(err error) { + // err is ignored at the moment + breakOnce.Do(func() { go ich.connection.Break() }) + } + + ach := new(AutoConnectionHandler) + ach.Init() + ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", + func() channels.Handler { + return &channels.HiddenServiceAuthChannel{ + PrivateKey: privateKey, + ServerAuthValid: onAuthValid, + ServerAuthInvalid: onAuthInvalid, + } + }) + + // Ensure that the call to Process() cannot outlive this function, + // particularly for the case where the policy timeout expires + defer breakOnce.Do(func() { ich.connection.Break() }) + policy := policies.UnknownPurposeTimeout + err := policy.ExecuteAction(func() error { + return ich.connection.Process(ach) + }) + + if err == nil { + if authAllowed == true { + ich.connection.RemoteHostname = authHostname + return nil + } + return utils.ClientFailedToAuthenticateError + } + + return err +} diff --git a/vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go b/vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go new file mode 100644 index 0000000..be13328 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go @@ -0,0 +1,90 @@ +package connection + +import ( + "crypto/rsa" + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/policies" + "github.com/s-rah/go-ricochet/utils" + "sync" +) + +// OutboundConnectionHandler is a convieniance wrapper for handling outbound +// connections +type OutboundConnectionHandler struct { + connection *Connection +} + +// HandleOutboundConnection returns an OutboundConnectionHandler given a connection +func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler { + och := new(OutboundConnectionHandler) + och.connection = c + return och +} + +// ProcessAuthAsClient blocks until authentication has succeeded or failed with the +// provided privateKey, or the connection is closed. A non-nil error is returned in all +// cases other than successful authentication. +// +// ProcessAuthAsClient cannot be called at the same time as any other call to a Porcess +// function. Another Process function must be called after this function successfully +// returns to continue handling connection events. +// +// For successful authentication, the `known` return value indicates whether the peer +// accepts us as a known contact. Unknown contacts will generally need to send a contact +// request before any other activity. +func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) { + + if privateKey == nil { + return false, utils.PrivateKeyNotSetError + } + + ach := new(AutoConnectionHandler) + ach.Init() + + // Make sure that calls to Break in this function cannot race + var breakOnce sync.Once + + var accepted, isKnownContact bool + authCallback := func(accept, known bool) { + accepted = accept + isKnownContact = known + // Cause the Process() call below to return. + // If Break() is called from here, it _must_ use go, because this will + // execute in the Process goroutine, and Break() will deadlock. + breakOnce.Do(func() { go och.connection.Break() }) + } + + processResult := make(chan error, 1) + go func() { + // Break Process() if timed out; no-op if Process returned a conn error + defer func() { breakOnce.Do(func() { och.connection.Break() }) }() + policy := policies.UnknownPurposeTimeout + err := policy.ExecuteAction(func() error { + return och.connection.Process(ach) + }) + processResult <- err + }() + + err := och.connection.Do(func() error { + _, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service", + &channels.HiddenServiceAuthChannel{ + PrivateKey: privateKey, + ServerHostname: och.connection.RemoteHostname, + ClientAuthResult: authCallback, + }) + return err + }) + if err != nil { + breakOnce.Do(func() { och.connection.Break() }) + return false, err + } + + if err = <-processResult; err != nil { + return false, err + } + + if accepted == true { + return isKnownContact, nil + } + return false, utils.ServerRejectedClientConnectionError +} diff --git a/vendor/github.com/s-rah/go-ricochet/examples/echobot/main.go b/vendor/github.com/s-rah/go-ricochet/examples/echobot/main.go index e1f1bf9..d3a341f 100644 --- a/vendor/github.com/s-rah/go-ricochet/examples/echobot/main.go +++ b/vendor/github.com/s-rah/go-ricochet/examples/echobot/main.go @@ -2,49 +2,104 @@ package main import ( "github.com/s-rah/go-ricochet" + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/connection" + "github.com/s-rah/go-ricochet/utils" "log" + "time" ) // EchoBotService is an example service which simply echoes back what a client // sends it. -type EchoBotService struct { - goricochet.StandardRicochetService +type RicochetEchoBot struct { + connection.AutoConnectionHandler + messages chan string } -func (ebs *EchoBotService) OnNewConnection(oc *goricochet.OpenConnection) { - ebs.StandardRicochetService.OnNewConnection(oc) - go oc.Process(&EchoBotConnection{}) +func (echobot *RicochetEchoBot) ContactRequest(name string, message string) string { + return "Pending" } -type EchoBotConnection struct { - goricochet.StandardRicochetConnection +func (echobot *RicochetEchoBot) ContactRequestRejected() { +} +func (echobot *RicochetEchoBot) ContactRequestAccepted() { +} +func (echobot *RicochetEchoBot) ContactRequestError() { } -// IsKnownContact is configured to always accept Contact Requests -func (ebc *EchoBotConnection) IsKnownContact(hostname string) bool { +func (echobot *RicochetEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool { + echobot.messages <- message return true } -// OnContactRequest - we always accept new contact request. -func (ebc *EchoBotConnection) OnContactRequest(channelID int32, nick string, message string) { - ebc.StandardRicochetConnection.OnContactRequest(channelID, nick, message) - ebc.Conn.AckContactRequestOnResponse(channelID, "Accepted") - ebc.Conn.CloseChannel(channelID) +func (echobot *RicochetEchoBot) ChatMessageAck(messageID uint32) { + } -// OnChatMessage we acknowledge the message, grab the message content and send it back - opening -// a new channel if necessary. -func (ebc *EchoBotConnection) OnChatMessage(channelID int32, messageID int32, message string) { - log.Printf("Received Message from %s: %s", ebc.Conn.OtherHostname, message) - ebc.Conn.AckChatMessage(channelID, messageID) - if ebc.Conn.GetChannelType(6) == "none" { - ebc.Conn.OpenChatChannel(6) +func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string) { + + privateKey, _ := utils.LoadPrivateKeyFromFile(privateKeyFile) + echobot.messages = make(chan string) + + echobot.Init() + echobot.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler { + contact := new(channels.ContactRequestChannel) + contact.Handler = echobot + return contact + }) + echobot.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler { + chat := new(channels.ChatChannel) + chat.Handler = echobot + return chat + }) + + rc, _ := goricochet.Open(hostname) + known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey) + if err == nil { + + go rc.Process(echobot) + + if !known { + err := rc.Do(func() error { + _, err := rc.RequestOpenChannel("im.ricochet.contact.request", + &channels.ContactRequestChannel{ + Handler: echobot, + Name: "EchoBot", + Message: "I LIVE 😈😈!!!!", + }) + return err + }) + if err != nil { + log.Printf("could not contact %s", err) + } + } + + rc.Do(func() error { + _, err := rc.RequestOpenChannel("im.ricochet.chat", &channels.ChatChannel{Handler: echobot}) + return err + }) + for { + message := <-echobot.messages + log.Printf("Received Message: %s", message) + rc.Do(func() error { + log.Printf("Finding Chat Channel") + channel := rc.Channel("im.ricochet.chat", channels.Outbound) + if channel != nil { + log.Printf("Found Chat Channel") + chatchannel, ok := channel.Handler.(*channels.ChatChannel) + if ok { + chatchannel.SendMessage(message) + } + } else { + log.Printf("Could not find chat channel") + } + return nil + }) + } } - ebc.Conn.SendMessage(6, message) } func main() { - ricochetService := new(EchoBotService) - ricochetService.Init("./private_key") - ricochetService.Listen(ricochetService, 12345) + echoBot := new(RicochetEchoBot) + echoBot.Connect("private_key", "oqf7z4ot6kuejgam") } diff --git a/vendor/github.com/s-rah/go-ricochet/handlers.go b/vendor/github.com/s-rah/go-ricochet/handlers.go deleted file mode 100644 index 083c45f..0000000 --- a/vendor/github.com/s-rah/go-ricochet/handlers.go +++ /dev/null @@ -1,51 +0,0 @@ -package goricochet - -// ServiceHandler is the interface to handle events for an inbound connection listener -type ServiceHandler interface { - // OnNewConnection is called for inbound connections to the service after protocol - // version negotiation has completed successfully. - OnNewConnection(oc *OpenConnection) - // OnFailedConnection is called for inbound connections to the service which fail - // to successfully complete version negotiation for any reason. - OnFailedConnection(err error) -} - -// ConnectionHandler is the interface to handle events for an open protocol connection, -// whether inbound or outbound. Each OpenConnection will need its own instance of an -// application type implementing ConnectionHandler, which could also be used to store -// application state related to the connection. -type ConnectionHandler interface { - // OnReady is called before OpenConnection.Process() begins from the connection - OnReady(oc *OpenConnection) - // OnDisconnect is called when the connection is closed, just before - // OpenConnection.Process() returns - OnDisconnect() - - // Authentication Management - OnAuthenticationRequest(channelID int32, clientCookie [16]byte) - OnAuthenticationChallenge(channelID int32, serverCookie [16]byte) - OnAuthenticationProof(channelID int32, publicKey []byte, signature []byte) - OnAuthenticationResult(channelID int32, result bool, isKnownContact bool) - - // Contact Management - IsKnownContact(hostname string) bool - OnContactRequest(channelID int32, nick string, message string) - OnContactRequestAck(channelID int32, status string) - - // Managing Channels - OnOpenChannelRequest(channelID int32, channelType string) - OnOpenChannelRequestSuccess(channelID int32) - OnChannelClosed(channelID int32) - - // Chat Messages - OnChatMessage(channelID int32, messageID int32, message string) - OnChatMessageAck(channelID int32, messageID int32) - - // Handle Errors - OnFailedChannelOpen(channelID int32, errorType string) - OnGenericError(channelID int32) - OnUnknownTypeError(channelID int32) - OnUnauthorizedError(channelID int32) - OnBadUsageError(channelID int32) - OnFailedError(channelID int32) -} diff --git a/vendor/github.com/s-rah/go-ricochet/openconnection.go b/vendor/github.com/s-rah/go-ricochet/openconnection.go deleted file mode 100644 index bf6465f..0000000 --- a/vendor/github.com/s-rah/go-ricochet/openconnection.go +++ /dev/null @@ -1,534 +0,0 @@ -package goricochet - -import ( - "crypto" - "crypto/rsa" - "encoding/asn1" - "github.com/golang/protobuf/proto" - "github.com/s-rah/go-ricochet/auth" - "github.com/s-rah/go-ricochet/chat" - "github.com/s-rah/go-ricochet/contact" - "github.com/s-rah/go-ricochet/control" - "github.com/s-rah/go-ricochet/utils" - "log" - "net" -) - -// OpenConnection encapsulates the state required to maintain a connection to -// a ricochet service. -// Notably OpenConnection does not enforce limits on the channelIDs, channel Assignments -// or the direction of messages. These are considered to be service enforced rules. -// (and services are considered to be the best to define them). -type OpenConnection struct { - conn net.Conn - authHandler map[int32]*AuthenticationHandler - channels map[int32]string - rni utils.RicochetNetworkInterface - - Client bool - IsAuthed bool - MyHostname string - OtherHostname string - Closed bool -} - -// Init initializes a OpenConnection object to a default state. -func (oc *OpenConnection) Init(outbound bool, conn net.Conn) { - oc.conn = conn - oc.authHandler = make(map[int32]*AuthenticationHandler) - oc.channels = make(map[int32]string) - oc.rni = new(utils.RicochetNetwork) - - oc.Client = outbound - oc.IsAuthed = false - oc.MyHostname = "" - oc.OtherHostname = "" -} - -// UnsetChannel removes a type association from the channel. -func (oc *OpenConnection) UnsetChannel(channel int32) { - oc.channels[channel] = "none" -} - -// GetChannelType returns the type of the channel on this connection -func (oc *OpenConnection) GetChannelType(channel int32) string { - if val, ok := oc.channels[channel]; ok { - return val - } - return "none" -} - -func (oc *OpenConnection) setChannel(channel int32, channelType string) { - oc.channels[channel] = channelType -} - -// HasChannel returns true if the connection has a channel of an associated type, false otherwise -func (oc *OpenConnection) HasChannel(channelType string) bool { - for _, val := range oc.channels { - if val == channelType { - return true - } - } - return false -} - -// CloseChannel closes a given channel -// Prerequisites: -// * Must have previously connected to a service -func (oc *OpenConnection) CloseChannel(channel int32) { - oc.UnsetChannel(channel) - oc.rni.SendRicochetPacket(oc.conn, channel, []byte{}) -} - -// Close closes the entire connection -func (oc *OpenConnection) Close() { - oc.conn.Close() - oc.Closed = true -} - -// Authenticate opens an Authentication Channel and send a client cookie -// Prerequisites: -// * Must have previously connected to a service -func (oc *OpenConnection) Authenticate(channel int32) { - defer utils.RecoverFromError() - - oc.authHandler[channel] = new(AuthenticationHandler) - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.OpenAuthenticationChannel(channel, oc.authHandler[channel].GenClientCookie()) - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.auth.hidden-service") - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// ConfirmAuthChannel responds to a new authentication request. -// Prerequisites: -// * Must have previously connected to a service -func (oc *OpenConnection) ConfirmAuthChannel(channel int32, clientCookie [16]byte) { - defer utils.RecoverFromError() - - oc.authHandler[channel] = new(AuthenticationHandler) - oc.authHandler[channel].AddClientCookie(clientCookie[:]) - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.ConfirmAuthChannel(channel, oc.authHandler[channel].GenServerCookie()) - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.auth.hidden-service") - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// SendProof sends an authentication proof in response to a challenge. -// Prerequisites: -// * Must have previously connected to a service -// * channel must be of type auth -func (oc *OpenConnection) SendProof(channel int32, serverCookie [16]byte, publicKeyBytes []byte, privateKey *rsa.PrivateKey) { - - if oc.authHandler[channel] == nil { - return // NoOp - } - - oc.authHandler[channel].AddServerCookie(serverCookie[:]) - - challenge := oc.authHandler[channel].GenChallenge(oc.MyHostname, oc.OtherHostname) - signature, _ := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, challenge) - - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.Proof(publicKeyBytes, signature) - utils.CheckError(err) - - oc.rni.SendRicochetPacket(oc.conn, channel, data) -} - -// ValidateProof determines if the given public key and signature align with the -// already established challenge vector for this communication -// Prerequisites: -// * Must have previously connected to a service -// * Client and Server must have already sent their respective cookies (Authenticate and ConfirmAuthChannel) -func (oc *OpenConnection) ValidateProof(channel int32, publicKeyBytes []byte, signature []byte) bool { - - if oc.authHandler[channel] == nil { - return false - } - - provisionalHostname := utils.GetTorHostname(publicKeyBytes) - publicKey := new(rsa.PublicKey) - _, err := asn1.Unmarshal(publicKeyBytes, publicKey) - if err != nil { - return false - } - challenge := oc.authHandler[channel].GenChallenge(provisionalHostname, oc.MyHostname) - err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, challenge[:], signature) - if err == nil { - oc.OtherHostname = provisionalHostname - return true - } - return false - -} - -// SendAuthenticationResult responds to an existed authentication Proof -// Prerequisites: -// * Must have previously connected to a service -// * channel must be of type auth -func (oc *OpenConnection) SendAuthenticationResult(channel int32, accepted bool, isKnownContact bool) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.AuthResult(accepted, isKnownContact) - utils.CheckError(err) - oc.rni.SendRicochetPacket(oc.conn, channel, data) -} - -// OpenChatChannel opens a new chat channel with the given id -// Prerequisites: -// * Must have previously connected to a service -// * If acting as the client, id must be odd, else even -func (oc *OpenConnection) OpenChatChannel(channel int32) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.OpenChannel(channel, "im.ricochet.chat") - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.chat") - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// OpenChannel opens a new chat channel with the given id -// Prerequisites: -// * Must have previously connected to a service -// * If acting as the client, id must be odd, else even -func (oc *OpenConnection) OpenChannel(channel int32, channelType string) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.OpenChannel(channel, channelType) - utils.CheckError(err) - - oc.setChannel(channel, channelType) - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// AckOpenChannel acknowledges a previously received open channel message -// Prerequisites: -// * Must have previously connected and authenticated to a service -func (oc *OpenConnection) AckOpenChannel(channel int32, channeltype string) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - - data, err := messageBuilder.AckOpenChannel(channel) - utils.CheckError(err) - - oc.setChannel(channel, channeltype) - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// RejectOpenChannel acknowledges a rejects a previously received open channel message -// Prerequisites: -// * Must have previously connected -func (oc *OpenConnection) RejectOpenChannel(channel int32, errortype string) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.RejectOpenChannel(channel, errortype) - utils.CheckError(err) - - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// SendContactRequest initiates a contact request to the server. -// Prerequisites: -// * Must have previously connected and authenticated to a service -func (oc *OpenConnection) SendContactRequest(channel int32, nick string, message string) { - defer utils.RecoverFromError() - - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message) - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.contact.request") - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// AckContactRequestOnResponse responds a contact request from a client -// Prerequisites: -// * Must have previously connected and authenticated to a service -// * Must have previously received a Contact Request -func (oc *OpenConnection) AckContactRequestOnResponse(channel int32, status string) { - defer utils.RecoverFromError() - - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.ReplyToContactRequestOnResponse(channel, status) - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.contact.request") - oc.rni.SendRicochetPacket(oc.conn, 0, data) -} - -// AckContactRequest responds to contact request from a client -// Prerequisites: -// * Must have previously connected and authenticated to a service -// * Must have previously received a Contact Request -func (oc *OpenConnection) AckContactRequest(channel int32, status string) { - defer utils.RecoverFromError() - - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.ReplyToContactRequest(channel, status) - utils.CheckError(err) - - oc.setChannel(channel, "im.ricochet.contact.request") - oc.rni.SendRicochetPacket(oc.conn, channel, data) -} - -// AckChatMessage acknowledges a previously received chat message. -// Prerequisites: -// * Must have previously connected and authenticated to a service -// * Must have established a known contact status with the other service -// * Must have received a Chat message on an open im.ricochet.chat channel with the messageID -func (oc *OpenConnection) AckChatMessage(channel int32, messageID int32) { - defer utils.RecoverFromError() - - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.AckChatMessage(messageID) - utils.CheckError(err) - - oc.rni.SendRicochetPacket(oc.conn, channel, data) -} - -// SendMessage sends a Chat Message (message) to a give Channel (channel). -// Prerequisites: -// * Must have previously connected and authenticated to a service -// * Must have established a known contact status with the other service -// * Must have previously opened channel with OpenChanel of type im.ricochet.chat -func (oc *OpenConnection) SendMessage(channel int32, message string) { - defer utils.RecoverFromError() - messageBuilder := new(MessageBuilder) - data, err := messageBuilder.ChatMessage(message, 0) - utils.CheckError(err) - oc.rni.SendRicochetPacket(oc.conn, channel, data) -} - -// Process waits for new messages to arrive from the connection and uses the given -// ConnectionHandler to process them. -func (oc *OpenConnection) Process(handler ConnectionHandler) { - handler.OnReady(oc) - defer oc.Close() - defer handler.OnDisconnect() - - for { - if oc.Closed { - return - } - - packet, err := oc.rni.RecvRicochetPacket(oc.conn) - if err != nil { - oc.Close() - return - } - - if len(packet.Data) == 0 { - handler.OnChannelClosed(packet.Channel) - continue - } - - if packet.Channel == 0 { - - res := new(Protocol_Data_Control.Packet) - err := proto.Unmarshal(packet.Data[:], res) - - if err != nil { - handler.OnGenericError(packet.Channel) - continue - } - - if res.GetOpenChannel() != nil { - opm := res.GetOpenChannel() - - if oc.GetChannelType(opm.GetChannelIdentifier()) != "none" { - // Channel is already in use. - handler.OnBadUsageError(opm.GetChannelIdentifier()) - continue - } - - // If I am a Client, the server can only open even numbered channels - if oc.Client && opm.GetChannelIdentifier()%2 != 0 { - handler.OnBadUsageError(opm.GetChannelIdentifier()) - continue - } - - // If I am a Server, the client can only open odd numbered channels - if !oc.Client && opm.GetChannelIdentifier()%2 != 1 { - handler.OnBadUsageError(opm.GetChannelIdentifier()) - continue - } - - switch opm.GetChannelType() { - case "im.ricochet.auth.hidden-service": - if oc.Client { - // Servers are authed by default and can't auth with hidden-service - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } else if oc.IsAuthed { - // Can't auth if already authed - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } else if oc.HasChannel("im.ricochet.auth.hidden-service") { - // Can't open more than 1 auth channel - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } else { - clientCookie, err := proto.GetExtension(opm, Protocol_Data_AuthHiddenService.E_ClientCookie) - if err == nil { - clientCookieB := [16]byte{} - copy(clientCookieB[:], clientCookie.([]byte)[:]) - handler.OnAuthenticationRequest(opm.GetChannelIdentifier(), clientCookieB) - } else { - // Must include Client Cookie - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } - } - case "im.ricochet.chat": - if !oc.IsAuthed { - // Can't open chat channel if not authorized - handler.OnUnauthorizedError(opm.GetChannelIdentifier()) - } else if !handler.IsKnownContact(oc.OtherHostname) { - // Can't open chat channel if not a known contact - handler.OnUnauthorizedError(opm.GetChannelIdentifier()) - } else { - handler.OnOpenChannelRequest(opm.GetChannelIdentifier(), "im.ricochet.chat") - } - case "im.ricochet.contact.request": - if oc.Client { - // Servers are not allowed to send contact requests - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } else if !oc.IsAuthed { - // Can't open a contact channel if not authed - handler.OnUnauthorizedError(opm.GetChannelIdentifier()) - } else if oc.HasChannel("im.ricochet.contact.request") { - // Only 1 contact channel is allowed to be open at a time - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } else { - contactRequestI, err := proto.GetExtension(opm, Protocol_Data_ContactRequest.E_ContactRequest) - if err == nil { - contactRequest, check := contactRequestI.(*Protocol_Data_ContactRequest.ContactRequest) - if check { - handler.OnContactRequest(opm.GetChannelIdentifier(), contactRequest.GetNickname(), contactRequest.GetMessageText()) - break - } - } - handler.OnBadUsageError(opm.GetChannelIdentifier()) - } - default: - handler.OnUnknownTypeError(opm.GetChannelIdentifier()) - } - } else if res.GetChannelResult() != nil { - crm := res.GetChannelResult() - if crm.GetOpened() { - switch oc.GetChannelType(crm.GetChannelIdentifier()) { - case "im.ricochet.auth.hidden-service": - serverCookie, err := proto.GetExtension(crm, Protocol_Data_AuthHiddenService.E_ServerCookie) - if err == nil { - serverCookieB := [16]byte{} - copy(serverCookieB[:], serverCookie.([]byte)[:]) - handler.OnAuthenticationChallenge(crm.GetChannelIdentifier(), serverCookieB) - } else { - handler.OnBadUsageError(crm.GetChannelIdentifier()) - } - case "im.ricochet.chat": - handler.OnOpenChannelRequestSuccess(crm.GetChannelIdentifier()) - case "im.ricochet.contact.request": - responseI, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_ContactRequest.E_Response) - if err == nil { - response, check := responseI.(*Protocol_Data_ContactRequest.Response) - if check { - handler.OnContactRequestAck(crm.GetChannelIdentifier(), response.GetStatus().String()) - break - } - } - handler.OnBadUsageError(crm.GetChannelIdentifier()) - default: - handler.OnBadUsageError(crm.GetChannelIdentifier()) - } - } else { - if oc.GetChannelType(crm.GetChannelIdentifier()) != "none" { - handler.OnFailedChannelOpen(crm.GetChannelIdentifier(), crm.GetCommonError().String()) - } else { - oc.CloseChannel(crm.GetChannelIdentifier()) - } - } - } else { - // Unknown Message - oc.CloseChannel(packet.Channel) - } - } else if oc.GetChannelType(packet.Channel) == "im.ricochet.auth.hidden-service" { - res := new(Protocol_Data_AuthHiddenService.Packet) - err := proto.Unmarshal(packet.Data[:], res) - - if err != nil { - oc.CloseChannel(packet.Channel) - continue - } - - if res.GetProof() != nil && !oc.Client { // Only Clients Send Proofs - handler.OnAuthenticationProof(packet.Channel, res.GetProof().GetPublicKey(), res.GetProof().GetSignature()) - } else if res.GetResult() != nil && oc.Client { // Only Servers Send Results - handler.OnAuthenticationResult(packet.Channel, res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact()) - } else { - // If neither of the above are satisfied we just close the connection - oc.Close() - } - - } else if oc.GetChannelType(packet.Channel) == "im.ricochet.chat" { - - // NOTE: These auth checks should be redundant, however they - // are included here for defense-in-depth if for some reason - // a previously authed connection becomes untrusted / not known and - // the state is not cleaned up. - if !oc.IsAuthed { - // Can't send chat messages if not authorized - handler.OnUnauthorizedError(packet.Channel) - } else if !handler.IsKnownContact(oc.OtherHostname) { - // Can't send chat message if not a known contact - handler.OnUnauthorizedError(packet.Channel) - } else { - res := new(Protocol_Data_Chat.Packet) - err := proto.Unmarshal(packet.Data[:], res) - - if err != nil { - oc.CloseChannel(packet.Channel) - continue - } - - if res.GetChatMessage() != nil { - handler.OnChatMessage(packet.Channel, int32(res.GetChatMessage().GetMessageId()), res.GetChatMessage().GetMessageText()) - } else if res.GetChatAcknowledge() != nil { - handler.OnChatMessageAck(packet.Channel, int32(res.GetChatMessage().GetMessageId())) - } else { - // If neither of the above are satisfied we just close the connection - oc.Close() - } - } - } else if oc.GetChannelType(packet.Channel) == "im.ricochet.contact.request" { - - // NOTE: These auth checks should be redundant, however they - // are included here for defense-in-depth if for some reason - // a previously authed connection becomes untrusted / not known and - // the state is not cleaned up. - if !oc.Client { - // Clients are not allowed to send contact request responses - handler.OnBadUsageError(packet.Channel) - } else if !oc.IsAuthed { - // Can't send a contact request if not authed - handler.OnBadUsageError(packet.Channel) - } else { - res := new(Protocol_Data_ContactRequest.Response) - err := proto.Unmarshal(packet.Data[:], res) - log.Printf("%v", res) - if err != nil { - oc.CloseChannel(packet.Channel) - continue - } - handler.OnContactRequestAck(packet.Channel, res.GetStatus().String()) - } - } else if oc.GetChannelType(packet.Channel) == "none" { - // Invalid Channel Assignment - oc.CloseChannel(packet.Channel) - } else { - oc.Close() - } - } -} diff --git a/vendor/github.com/s-rah/go-ricochet/policies/timeoutpolicy.go b/vendor/github.com/s-rah/go-ricochet/policies/timeoutpolicy.go new file mode 100644 index 0000000..f342575 --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/policies/timeoutpolicy.go @@ -0,0 +1,32 @@ +package policies + +import ( + "github.com/s-rah/go-ricochet/utils" + "time" +) + +// TimeoutPolicy is a convieance interface for enforcing common timeout patterns +type TimeoutPolicy time.Duration + +// Selection of common timeout policies +const ( + UnknownPurposeTimeout TimeoutPolicy = TimeoutPolicy(15 * time.Second) +) + +// ExecuteAction runs a function and returns an error if it hasn't returned +// by the time specified by TimeoutPolicy +func (tp *TimeoutPolicy) ExecuteAction(action func() error) error { + + c := make(chan error) + go func() { + c <- action() + }() + + tick := time.Tick(time.Duration(*tp)) + select { + case <-tick: + return utils.ActionTimedOutError + case err := <-c: + return err + } +} diff --git a/vendor/github.com/s-rah/go-ricochet/ricochet.go b/vendor/github.com/s-rah/go-ricochet/ricochet.go index ad5260f..d92ae20 100644 --- a/vendor/github.com/s-rah/go-ricochet/ricochet.go +++ b/vendor/github.com/s-rah/go-ricochet/ricochet.go @@ -1,158 +1,90 @@ package goricochet import ( - "errors" + "github.com/s-rah/go-ricochet/connection" "github.com/s-rah/go-ricochet/utils" "io" "net" - "sync" ) -// Connect sets up a client ricochet connection to host e.g. qn6uo4cmsrfv4kzq.onion. If this -// function finished successfully then the connection can be assumed to -// be open and authenticated. -// To specify a local port using the format "127.0.0.1:[port]|ricochet-id". -func Connect(host string) (*OpenConnection, error) { - networkResolver := utils.NetworkResolver{} - conn, host, err := networkResolver.Resolve(host) - - if err != nil { - return nil, err - } - - return Open(conn, host) -} - // 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(conn net.Conn, remoteHostname string) (*OpenConnection, error) { - oc, err := negotiateVersion(conn, true) +func Open(remoteHostname string) (*connection.Connection, error) { + networkResolver := utils.NetworkResolver{} + conn, remoteHostname, err := networkResolver.Resolve(remoteHostname) + + if err != nil { + return nil, err + } + + rc, err := NegotiateVersionOutbound(conn, remoteHostname) if err != nil { conn.Close() return nil, err } - oc.OtherHostname = remoteHostname - return oc, nil + return rc, nil } -// Serve accepts incoming connections on a net.Listener, negotiates protocol, -// and calls methods of the ServiceHandler to handle inbound connections. All -// calls to ServiceHandler happen on the caller's goroutine. The listener can -// be closed at any time to close the service. -func Serve(ln net.Listener, handler ServiceHandler) error { - defer ln.Close() - - connChannel := make(chan interface{}) - listenErrorChannel := make(chan error) - - go func() { - var pending sync.WaitGroup - for { - conn, err := ln.Accept() - if err != nil { - // Wait for pending connections before returning an error; this - // prevents abandoned goroutines when the outer loop stops reading - // from connChannel. - pending.Wait() - listenErrorChannel <- err - close(connChannel) - return - } - - pending.Add(1) - go func() { - defer pending.Done() - oc, err := negotiateVersion(conn, false) - if err != nil { - conn.Close() - connChannel <- err - } else { - connChannel <- oc - } - }() - } - }() - - var listenErr error - for { - select { - case err := <-listenErrorChannel: - // Remember error, wait for connChannel to close - listenErr = err - - case result, ok := <-connChannel: - if !ok { - return listenErr - } - - switch v := result.(type) { - case *OpenConnection: - handler.OnNewConnection(v) - case error: - handler.OnFailedConnection(v) - } - } - } - - return nil -} - -// Perform version negotiation on the connection, and create an OpenConnection if successful -func negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) { +// negotiate version takes an open network connection and executes +// the ricochet version negotiation procedure. +func NegotiateVersionOutbound(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 + } - // Outbound side of the connection sends a list of supported versions - if outbound { - if n, err := conn.Write(versions); err != nil || n < len(versions) { - return nil, err - } + 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, err - } + if res[0] != 0x01 { + return nil, utils.VersionNegotiationFailed + } + rc := connection.NewOutboundConnection(conn, remoteHostname) + return rc, nil +} - if res[0] != 0x01 { - return nil, errors.New("unsupported protocol version") - } - } else { - // Read version response header - header := make([]byte, 3) - if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { - return nil, err - } +// 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 + header := make([]byte, 3) + if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { + return nil, err + } - if header[0] != versions[0] || header[1] != versions[1] || header[2] < 1 { - return nil, errors.New("invalid protocol response") - } + if header[0] != versions[0] || header[1] != versions[1] || header[2] < 1 { + return nil, utils.VersionNegotiationError + } - // Read list of supported versions (which is header[2] bytes long) - versionList := make([]byte, header[2]) - if _, err := io.ReadAtLeast(conn, versionList, len(versionList)); err != nil { - return nil, err - } + // Read list of supported versions (which is header[2] bytes long) + versionList := make([]byte, header[2]) + if _, err := io.ReadAtLeast(conn, versionList, len(versionList)); err != nil { + return nil, utils.VersionNegotiationError + } - selectedVersion := byte(0xff) - for _, v := range versionList { - if v == 0x01 { - selectedVersion = v - break - } - } - - if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 { - return nil, err - } - - if selectedVersion == 0xff { - return nil, errors.New("no supported protocol version") + selectedVersion := byte(0xff) + for _, v := range versionList { + if v == 0x01 { + selectedVersion = v + break } } - oc := new(OpenConnection) - oc.Init(outbound, conn) - return oc, nil + if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 { + return nil, utils.VersionNegotiationFailed + } + + if selectedVersion == 0xff { + return nil, utils.VersionNegotiationFailed + } + + rc := connection.NewInboundConnection(conn) + return rc, nil } diff --git a/vendor/github.com/s-rah/go-ricochet/standardricochetservice.go b/vendor/github.com/s-rah/go-ricochet/standardricochetservice.go deleted file mode 100644 index 670f63f..0000000 --- a/vendor/github.com/s-rah/go-ricochet/standardricochetservice.go +++ /dev/null @@ -1,210 +0,0 @@ -package goricochet - -import ( - "crypto/rsa" - "crypto/x509" - "encoding/asn1" - "encoding/pem" - "errors" - "github.com/s-rah/go-ricochet/utils" - "io/ioutil" - "log" - "net" - "strconv" -) - -// StandardRicochetService implements all the necessary flows to implement a -// minimal, protocol compliant Ricochet Service. It can be built on by other -// applications to produce automated riochet applications, and is a useful -// example for other implementations. -type StandardRicochetService struct { - PrivateKey *rsa.PrivateKey - serverHostname string -} - -// StandardRicochetConnection implements the ConnectionHandler interface -// to handle events on connections. An instance of StandardRicochetConnection -// is created for each OpenConnection by the HandleConnection method. -type StandardRicochetConnection struct { - Conn *OpenConnection - PrivateKey *rsa.PrivateKey -} - -// Init initializes a StandardRicochetService with the cryptographic key given -// by filename. -func (srs *StandardRicochetService) Init(filename string) error { - pemData, err := ioutil.ReadFile(filename) - - if err != nil { - return errors.New("Could not setup ricochet service: could not read private key") - } - - block, _ := pem.Decode(pemData) - if block == nil || block.Type != "RSA PRIVATE KEY" { - return errors.New("Could not setup ricochet service: no valid PEM data found") - } - - srs.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return errors.New("Could not setup ricochet service: could not parse private key") - } - - publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ - N: srs.PrivateKey.PublicKey.N, - E: srs.PrivateKey.PublicKey.E, - }) - - srs.serverHostname = utils.GetTorHostname(publicKeyBytes) - log.Printf("Initialised ricochet service for %s", srs.serverHostname) - - return nil -} - -// Listen starts listening for service connections on localhost `port`. -func (srs *StandardRicochetService) Listen(handler ServiceHandler, port int) { - ln, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port)) - if err != nil { - log.Printf("Cannot Listen on Port %v", port) - return - } - - Serve(ln, handler) -} - -// Connect initiates a new client connection to `hostname`, which must be in one -// of the forms accepted by the goricochet.Connect() method. -func (srs *StandardRicochetService) Connect(hostname string) (*OpenConnection, error) { - log.Printf("Connecting to...%s", hostname) - oc, err := Connect(hostname) - if err != nil { - return nil, errors.New("Could not connect to: " + hostname + " " + err.Error()) - } - oc.MyHostname = srs.serverHostname - return oc, nil -} - -// OnNewConnection is called for new inbound connections to our service. This -// method implements the ServiceHandler interface. -func (srs *StandardRicochetService) OnNewConnection(oc *OpenConnection) { - oc.MyHostname = srs.serverHostname -} - -// OnFailedConnection is called for inbound connections that fail to successfully -// complete version negotiation for any reason. This method implements the -// ServiceHandler interface. -func (srs *StandardRicochetService) OnFailedConnection(err error) { - log.Printf("Inbound connection failed: %s", err) -} - -// ------ - -// OnReady is called when a client or server sucessfully passes Version Negotiation. -func (src *StandardRicochetConnection) OnReady(oc *OpenConnection) { - src.Conn = oc - if oc.Client { - log.Printf("Successfully connected to %s", oc.OtherHostname) - oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default - oc.Authenticate(1) - } else { - log.Printf("Inbound connection received") - } -} - -// OnDisconnect is called when a connection is closed -func (src *StandardRicochetConnection) OnDisconnect() { - log.Printf("Disconnected from %s", src.Conn.OtherHostname) -} - -// OnAuthenticationRequest is called when a client requests Authentication -func (src *StandardRicochetConnection) OnAuthenticationRequest(channelID int32, clientCookie [16]byte) { - src.Conn.ConfirmAuthChannel(channelID, clientCookie) -} - -// OnAuthenticationChallenge constructs a valid authentication challenge to the serverCookie -func (src *StandardRicochetConnection) OnAuthenticationChallenge(channelID int32, serverCookie [16]byte) { - // DER Encode the Public Key - publickeyBytes, _ := asn1.Marshal(rsa.PublicKey{ - N: src.PrivateKey.PublicKey.N, - E: src.PrivateKey.PublicKey.E, - }) - src.Conn.SendProof(1, serverCookie, publickeyBytes, src.PrivateKey) -} - -// OnAuthenticationProof is called when a client sends Proof for an existing authentication challenge -func (src *StandardRicochetConnection) OnAuthenticationProof(channelID int32, publicKey []byte, signature []byte) { - result := src.Conn.ValidateProof(channelID, publicKey, signature) - // This implementation always sends 'true', indicating that the contact is known - src.Conn.SendAuthenticationResult(channelID, result, true) - src.Conn.IsAuthed = result - src.Conn.CloseChannel(channelID) -} - -// OnAuthenticationResult is called once a server has returned the result of the Proof Verification -func (src *StandardRicochetConnection) OnAuthenticationResult(channelID int32, result bool, isKnownContact bool) { - src.Conn.IsAuthed = result -} - -// IsKnownContact allows a caller to determine if a hostname an authorized contact. -func (src *StandardRicochetConnection) IsKnownContact(hostname string) bool { - return false -} - -// OnContactRequest is called when a client sends a new contact request -func (src *StandardRicochetConnection) OnContactRequest(channelID int32, nick string, message string) { -} - -// OnContactRequestAck is called when a server sends a reply to an existing contact request -func (src *StandardRicochetConnection) OnContactRequestAck(channelID int32, status string) { -} - -// OnOpenChannelRequest is called when a client or server requests to open a new channel -func (src *StandardRicochetConnection) OnOpenChannelRequest(channelID int32, channelType string) { - src.Conn.AckOpenChannel(channelID, channelType) -} - -// OnOpenChannelRequestSuccess is called when a client or server responds to an open channel request -func (src *StandardRicochetConnection) OnOpenChannelRequestSuccess(channelID int32) { -} - -// OnChannelClosed is called when a client or server closes an existing channel -func (src *StandardRicochetConnection) OnChannelClosed(channelID int32) { -} - -// OnChatMessage is called when a new chat message is received. -func (src *StandardRicochetConnection) OnChatMessage(channelID int32, messageID int32, message string) { - src.Conn.AckChatMessage(channelID, messageID) -} - -// OnChatMessageAck is called when a new chat message is ascknowledged. -func (src *StandardRicochetConnection) OnChatMessageAck(channelID int32, messageID int32) { -} - -// OnFailedChannelOpen is called when a server fails to open a channel -func (src *StandardRicochetConnection) OnFailedChannelOpen(channelID int32, errorType string) { - src.Conn.UnsetChannel(channelID) -} - -// OnGenericError is called when a generalized error is returned from the peer -func (src *StandardRicochetConnection) OnGenericError(channelID int32) { - src.Conn.RejectOpenChannel(channelID, "GenericError") -} - -//OnUnknownTypeError is called when an unknown type error is returned from the peer -func (src *StandardRicochetConnection) OnUnknownTypeError(channelID int32) { - src.Conn.RejectOpenChannel(channelID, "UnknownTypeError") -} - -// OnUnauthorizedError is called when an unathorized error is returned from the peer -func (src *StandardRicochetConnection) OnUnauthorizedError(channelID int32) { - src.Conn.RejectOpenChannel(channelID, "UnauthorizedError") -} - -// OnBadUsageError is called when a bad usage error is returned from the peer -func (src *StandardRicochetConnection) OnBadUsageError(channelID int32) { - src.Conn.RejectOpenChannel(channelID, "BadUsageError") -} - -// OnFailedError is called when a failed error is returned from the peer -func (src *StandardRicochetConnection) OnFailedError(channelID int32) { - src.Conn.RejectOpenChannel(channelID, "FailedError") -} diff --git a/vendor/github.com/s-rah/go-ricochet/utils/crypto.go b/vendor/github.com/s-rah/go-ricochet/utils/crypto.go new file mode 100644 index 0000000..7964d6a --- /dev/null +++ b/vendor/github.com/s-rah/go-ricochet/utils/crypto.go @@ -0,0 +1,56 @@ +package utils + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "io/ioutil" + "errors" + "crypto/rand" +) + +const ( + InvalidPrivateKeyFileError = Error("InvalidPrivateKeyFileError") + RICOCHET_KEY_SIZE = 1024 +) + +// Generate a private key for use +func GeneratePrivateKey() (*rsa.PrivateKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, RICOCHET_KEY_SIZE) + if err != nil { + return nil, errors.New("Could not generate key: " + err.Error()) + } + privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey) + return x509.ParsePKCS1PrivateKey(privateKeyDer) +} + +// LoadPrivateKeyFromFile loads a private key from a file... +func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) { + pemData, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + return ParsePrivateKey(pemData) +} + +// Convert a private key string to a usable private key +func ParsePrivateKey(pemData []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(pemData) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return nil, InvalidPrivateKeyFileError + } + + return x509.ParsePKCS1PrivateKey(block.Bytes) +} + +// turn a private key into storable string +func PrivateKeyToString(privateKey *rsa.PrivateKey) string { + privateKeyBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + } + + return string(pem.EncodeToMemory(&privateKeyBlock)) +} diff --git a/vendor/github.com/s-rah/go-ricochet/utils/error.go b/vendor/github.com/s-rah/go-ricochet/utils/error.go index 7b21265..d65fb0d 100644 --- a/vendor/github.com/s-rah/go-ricochet/utils/error.go +++ b/vendor/github.com/s-rah/go-ricochet/utils/error.go @@ -1,17 +1,48 @@ package utils -import "fmt" -import "log" +import ( + "fmt" +) -// RecoverFromError doesn't really recover from anything....see comment below -func RecoverFromError() { - if r := recover(); r != nil { - // This should only really happen if there is a failure de/serializing. If - // this does happen then we currently error. In the future we might be - // able to make this nicer. - log.Fatalf("Recovered from panic() - this really shouldn't happen. Reason: %v", r) - } -} +// Error captures various common ricochet errors +type Error string + +func (e Error) Error() string { return string(e) } + +// Defining Versions +const ( + VersionNegotiationError = Error("VersionNegotiationError") + VersionNegotiationFailed = Error("VersionNegotiationFailed") + + RicochetConnectionClosed = Error("RicochetConnectionClosed") + RicochetProtocolError = Error("RicochetProtocolError") + + UnknownChannelTypeError = Error("UnknownChannelTypeError") + UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError") + + // Timeout Errors + ActionTimedOutError = Error("ActionTimedOutError") + PeerTimedOutError = Error("PeerTimedOutError") + + // Authentication Errors + ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError") + ServerRejectedClientConnectionError = Error("ServerRejectedClientConnectionError") + + UnauthorizedActionError = Error("UnauthorizedActionError") + ChannelClosedByPeerError = Error("ChannelClosedByPeerError") + + // Channel Management Errors + ServerAttemptedToOpenEvenNumberedChannelError = Error("ServerAttemptedToOpenEvenNumberedChannelError") + ClientAttemptedToOpenOddNumberedChannelError = Error("ClientAttemptedToOpenOddNumberedChannelError") + ChannelIDIsAlreadyInUseError = Error("ChannelIDIsAlreadyInUseError") + AttemptToOpenMoreThanOneSingletonChannelError = Error("AttemptToOpenMoreThanOneSingletonChannelError") + + // Library Use Errors + PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError") + + // Connection Errors + ConnectionClosedError = Error("ConnectionClosedError") +) // CheckError is a helper function for panicing on errors which we need to handle // but should be very rare e.g. failures deserializing a protobuf object that diff --git a/vendor/github.com/s-rah/go-ricochet/messagebuilder.go b/vendor/github.com/s-rah/go-ricochet/utils/messagebuilder.go similarity index 69% rename from vendor/github.com/s-rah/go-ricochet/messagebuilder.go rename to vendor/github.com/s-rah/go-ricochet/utils/messagebuilder.go index 874f213..bd5616f 100644 --- a/vendor/github.com/s-rah/go-ricochet/messagebuilder.go +++ b/vendor/github.com/s-rah/go-ricochet/utils/messagebuilder.go @@ -1,12 +1,11 @@ -package goricochet +package utils import ( "github.com/golang/protobuf/proto" - "github.com/s-rah/go-ricochet/auth" - "github.com/s-rah/go-ricochet/chat" - "github.com/s-rah/go-ricochet/contact" - "github.com/s-rah/go-ricochet/control" - "github.com/s-rah/go-ricochet/utils" + "github.com/s-rah/go-ricochet/wire/auth" + "github.com/s-rah/go-ricochet/wire/chat" + "github.com/s-rah/go-ricochet/wire/contact" + "github.com/s-rah/go-ricochet/wire/control" ) // MessageBuilder allows a client to construct specific data packets for the @@ -16,7 +15,7 @@ type MessageBuilder struct { // OpenChannel contructs a message which will request to open a channel for // chat on the given channelID. -func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]byte, error) { +func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) []byte { oc := &Protocol_Data_Control.OpenChannel{ ChannelIdentifier: proto.Int32(channelID), ChannelType: proto.String(channelType), @@ -24,11 +23,13 @@ func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]by pc := &Protocol_Data_Control.Packet{ OpenChannel: oc, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // AckOpenChannel constructs a message to acknowledge a previous open channel operation. -func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) { +func (mb *MessageBuilder) AckOpenChannel(channelID int32) []byte { cr := &Protocol_Data_Control.ChannelResult{ ChannelIdentifier: proto.Int32(channelID), Opened: proto.Bool(true), @@ -36,11 +37,13 @@ func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) { pc := &Protocol_Data_Control.Packet{ ChannelResult: cr, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // RejectOpenChannel constructs a channel result message, stating the channel failed to open and a reason -func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) ([]byte, error) { +func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) []byte { errorNum := Protocol_Data_Control.ChannelResult_CommonError_value[error] commonError := Protocol_Data_Control.ChannelResult_CommonError(errorNum) @@ -53,28 +56,32 @@ func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) ([]by pc := &Protocol_Data_Control.Packet{ ChannelResult: cr, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation. -func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) ([]byte, error) { +func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) []byte { cr := &Protocol_Data_Control.ChannelResult{ ChannelIdentifier: proto.Int32(channelID), Opened: proto.Bool(true), } err := proto.SetExtension(cr, Protocol_Data_AuthHiddenService.E_ServerCookie, serverCookie[:]) - utils.CheckError(err) + CheckError(err) pc := &Protocol_Data_Control.Packet{ ChannelResult: cr, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // OpenContactRequestChannel contructs a message which will reuqest to open a channel for // a contact request on the given channelID, with the given nick and message. -func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) ([]byte, error) { +func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) []byte { // Construct a Contact Request Channel oc := &Protocol_Data_Control.OpenChannel{ ChannelIdentifier: proto.Int32(channelID), @@ -87,16 +94,18 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string } err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest) - utils.CheckError(err) + CheckError(err) pc := &Protocol_Data_Control.Packet{ OpenChannel: oc, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // ReplyToContactRequestOnResponse constructs a message to acknowledge contact request -func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) ([]byte, error) { +func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) []byte { cr := &Protocol_Data_Control.ChannelResult{ ChannelIdentifier: proto.Int32(channelID), Opened: proto.Bool(true), @@ -109,42 +118,49 @@ func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, statu } err := proto.SetExtension(cr, Protocol_Data_ContactRequest.E_Response, contactRequest) - utils.CheckError(err) + CheckError(err) pc := &Protocol_Data_Control.Packet{ ChannelResult: cr, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // ReplyToContactRequest constructs a message to acknowledge a contact request -func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) ([]byte, error) { +func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) []byte { statusNum := Protocol_Data_ContactRequest.Response_Status_value[status] responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum) contactRequest := &Protocol_Data_ContactRequest.Response{ Status: &responseStatus, } - return proto.Marshal(contactRequest) + + ret, err := proto.Marshal(contactRequest) + CheckError(err) + return ret } // OpenAuthenticationChannel constructs a message which will reuqest to open a channel for // authentication on the given channelID, with the given cookie -func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) ([]byte, error) { +func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) []byte { oc := &Protocol_Data_Control.OpenChannel{ ChannelIdentifier: proto.Int32(channelID), ChannelType: proto.String("im.ricochet.auth.hidden-service"), } err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:]) - utils.CheckError(err) + CheckError(err) pc := &Protocol_Data_Control.Packet{ OpenChannel: oc, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } // Proof constructs a proof message with the given public key and signature. -func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([]byte, error) { +func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) []byte { proof := &Protocol_Data_AuthHiddenService.Proof{ PublicKey: publicKeyBytes, Signature: signatureBytes, @@ -155,11 +171,13 @@ func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([ Result: nil, } - return proto.Marshal(ahsPacket) + ret, err := proto.Marshal(ahsPacket) + CheckError(err) + return ret } // AuthResult constructs a response to a Proof -func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte, error) { +func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte { // Construct a Result Message result := &Protocol_Data_AuthHiddenService.Result{ Accepted: proto.Bool(accepted), @@ -171,29 +189,74 @@ func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte Result: result, } - return proto.Marshal(ahsPacket) + ret, err := proto.Marshal(ahsPacket) + CheckError(err) + return ret } // ChatMessage constructs a chat message with the given content. -func (mb *MessageBuilder) ChatMessage(message string, messageID int32) ([]byte, error) { +func (mb *MessageBuilder) ChatMessage(message string, messageID uint32) []byte { cm := &Protocol_Data_Chat.ChatMessage{ - MessageId: proto.Uint32(uint32(messageID)), + MessageId: proto.Uint32(messageID), MessageText: proto.String(message), } chatPacket := &Protocol_Data_Chat.Packet{ ChatMessage: cm, } - return proto.Marshal(chatPacket) + ret, err := proto.Marshal(chatPacket) + CheckError(err) + return ret } // AckChatMessage constructs a chat message acknowledgement. -func (mb *MessageBuilder) AckChatMessage(messageID int32) ([]byte, error) { +func (mb *MessageBuilder) AckChatMessage(messageID uint32) []byte { cr := &Protocol_Data_Chat.ChatAcknowledge{ - MessageId: proto.Uint32(uint32(messageID)), + MessageId: proto.Uint32(messageID), Accepted: proto.Bool(true), } pc := &Protocol_Data_Chat.Packet{ ChatAcknowledge: cr, } - return proto.Marshal(pc) + ret, err := proto.Marshal(pc) + CheckError(err) + return ret +} + +// KeepAlive ... +func (mb *MessageBuilder) KeepAlive(responseRequested bool) []byte { + ka := &Protocol_Data_Control.KeepAlive{ + ResponseRequested: proto.Bool(responseRequested), + } + pc := &Protocol_Data_Control.Packet{ + KeepAlive: ka, + } + ret, err := proto.Marshal(pc) + CheckError(err) + return ret +} + +// EnableFeatures ... +func (mb *MessageBuilder) EnableFeatures(features []string) []byte { + ef := &Protocol_Data_Control.EnableFeatures{ + Feature: features, + } + pc := &Protocol_Data_Control.Packet{ + EnableFeatures: ef, + } + ret, err := proto.Marshal(pc) + CheckError(err) + return ret +} + +// FeaturesEnabled ... +func (mb *MessageBuilder) FeaturesEnabled(features []string) []byte { + fe := &Protocol_Data_Control.FeaturesEnabled{ + Feature: features, + } + pc := &Protocol_Data_Control.Packet{ + FeaturesEnabled: fe, + } + ret, err := proto.Marshal(pc) + CheckError(err) + return ret } diff --git a/vendor/github.com/s-rah/go-ricochet/utils/networking.go b/vendor/github.com/s-rah/go-ricochet/utils/networking.go index 20343c9..b41018d 100644 --- a/vendor/github.com/s-rah/go-ricochet/utils/networking.go +++ b/vendor/github.com/s-rah/go-ricochet/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/vendor/github.com/s-rah/go-ricochet/utils/networkresolver.go b/vendor/github.com/s-rah/go-ricochet/utils/networkresolver.go index e2873d8..a571efc 100644 --- a/vendor/github.com/s-rah/go-ricochet/utils/networkresolver.go +++ b/vendor/github.com/s-rah/go-ricochet/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,8 +50,8 @@ 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 } - //conn.SetDeadline(time.Now().Add(5 * time.Second)) + return conn, resolvedHostname, nil } diff --git a/vendor/github.com/s-rah/go-ricochet/auth/auth_message.go b/vendor/github.com/s-rah/go-ricochet/wire/auth/auth_message.go similarity index 97% rename from vendor/github.com/s-rah/go-ricochet/auth/auth_message.go rename to vendor/github.com/s-rah/go-ricochet/wire/auth/auth_message.go index 9f20266..053abfe 100644 --- a/vendor/github.com/s-rah/go-ricochet/auth/auth_message.go +++ b/vendor/github.com/s-rah/go-ricochet/wire/auth/auth_message.go @@ -18,7 +18,7 @@ package Protocol_Data_AuthHiddenService import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import Protocol_Data_Control "github.com/s-rah/go-ricochet/control" +import Protocol_Data_Control "github.com/s-rah/go-ricochet/wire/control" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal diff --git a/vendor/github.com/s-rah/go-ricochet/chat/chat.go b/vendor/github.com/s-rah/go-ricochet/wire/chat/chat.go similarity index 100% rename from vendor/github.com/s-rah/go-ricochet/chat/chat.go rename to vendor/github.com/s-rah/go-ricochet/wire/chat/chat.go diff --git a/vendor/github.com/s-rah/go-ricochet/contact/request.go b/vendor/github.com/s-rah/go-ricochet/wire/contact/request.go similarity index 98% rename from vendor/github.com/s-rah/go-ricochet/contact/request.go rename to vendor/github.com/s-rah/go-ricochet/wire/contact/request.go index f1996e8..1479913 100644 --- a/vendor/github.com/s-rah/go-ricochet/contact/request.go +++ b/vendor/github.com/s-rah/go-ricochet/wire/contact/request.go @@ -17,7 +17,7 @@ package Protocol_Data_ContactRequest import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import Protocol_Data_Control "github.com/s-rah/go-ricochet/control" +import Protocol_Data_Control "github.com/s-rah/go-ricochet/wire/control" // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal diff --git a/vendor/github.com/s-rah/go-ricochet/control/control_message.go b/vendor/github.com/s-rah/go-ricochet/wire/control/control_message.go similarity index 100% rename from vendor/github.com/s-rah/go-ricochet/control/control_message.go rename to vendor/github.com/s-rah/go-ricochet/wire/control/control_message.go diff --git a/vendor/manifest b/vendor/manifest index 5764b47..2684816 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -61,10 +61,10 @@ }, { "importpath": "github.com/s-rah/go-ricochet", - "repository": "https://github.com/s-rah/go-ricochet", + "repository": "https://github.com/special/go-ricochet-protocol", "vcs": "git", - "revision": "5a720a08d052019a113e9c2a94d6f1a3f582f09e", - "branch": "master", + "revision": "5b54d50bf4611a36c23dd732bd1e9d0dad441980", + "branch": "api-rework-fixes", "notests": true }, {