diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77c8c94 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +go-ricochet-coverage.out diff --git a/authhandler.go b/authhandler.go index 4fd67a0..1291120 100644 --- a/authhandler.go +++ b/authhandler.go @@ -7,35 +7,44 @@ import ( "io" ) +// AuthenticationHandler manages the stae 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[:]) diff --git a/authhandler_test.go b/authhandler_test.go index e934a3d..d0dc943 100644 --- a/authhandler_test.go +++ b/authhandler_test.go @@ -3,7 +3,7 @@ package goricochet import "testing" import "bytes" -func TestAuthHandler(t *testing.T) { +func TestGenChallenge(t *testing.T) { authHandler := new(AuthenticationHandler) authHandler.AddClientCookie([]byte("abcdefghijklmnop")) authHandler.AddServerCookie([]byte("qrstuvwxyz012345")) @@ -14,3 +14,19 @@ func TestAuthHandler(t *testing.T) { t.Errorf("AuthenticationHandler Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge) } } + +func TestGenClientCookie(t *testing.T) { + authHandler := new(AuthenticationHandler) + clientCookie := authHandler.GenClientCookie() + if clientCookie != authHandler.clientCookie { + t.Errorf("AuthenticationHandler Client Cookies are Different", clientCookie, authHandler.clientCookie) + } +} + +func TestGenServerCookie(t *testing.T) { + authHandler := new(AuthenticationHandler) + serverCookie := authHandler.GenServerCookie() + if serverCookie != authHandler.serverCookie { + t.Errorf("AuthenticationHandler Server Cookies are Different", serverCookie, authHandler.serverCookie) + } +} diff --git a/controlbuilder.go b/controlbuilder.go deleted file mode 100644 index 74c30a8..0000000 --- a/controlbuilder.go +++ /dev/null @@ -1,37 +0,0 @@ -package goricochet - -import ( - "github.com/s-rah/go-ricochet/control" - "github.com/s-rah/go-ricochet/auth" - "github.com/golang/protobuf/proto" -) - -type ControlBuilder struct { - -} - -func (cb *ControlBuilder) OpenChatChannel(channelId int32) ([]byte,error) { - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(channelId), - ChannelType: proto.String("im.ricochet.chat"), - } - pc := &Protocol_Data_Control.Packet{ - OpenChannel: oc, - } - return proto.Marshal(pc) -} - -func (cb* ControlBuilder) OpenAuthenticationChannel(channelId int32, clientCookie [16]byte) ([]byte,error) { - 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[:]) - if err != nil { - return nil, err - } - pc := &Protocol_Data_Control.Packet{ - OpenChannel: oc, - } - return proto.Marshal(pc) -} diff --git a/examples/echobot/main.go b/examples/echobot/main.go index 940f481..cd6bc16 100644 --- a/examples/echobot/main.go +++ b/examples/echobot/main.go @@ -20,13 +20,13 @@ func main() { go ricochet.ListenAndWait() ricochet.OpenChatChannel(5) time.Sleep(time.Second * 1) - ricochet.SendMessage("Hi I'm an echo bot, I echo what you say! ", 5) + ricochet.SendMessage(5, "Hi I'm an echo bot, I echo what you say!") for true { message,channel,_ := ricochet.Listen() - fmt.Print(message, channel) + fmt.Print(channel, message) if message != "" { - ricochet.SendMessage(message, 5) + ricochet.SendMessage(5, message) } } } diff --git a/messagebuilder.go b/messagebuilder.go new file mode 100644 index 0000000..b4c414b --- /dev/null +++ b/messagebuilder.go @@ -0,0 +1,81 @@ +package goricochet + +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" +) + +// MessageBuilder allows a client to construct specific data packets for the +// ricochet protocol. +type MessageBuilder struct { +} + +// OpenChatChannel contructs a message which will request to open a channel for +// chat on the given channelID. +func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) { + oc := &Protocol_Data_Control.OpenChannel{ + ChannelIdentifier: proto.Int32(channelID), + ChannelType: proto.String("im.ricochet.chat"), + } + pc := &Protocol_Data_Control.Packet{ + OpenChannel: oc, + } + return proto.Marshal(pc) +} + +// 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) { + // Construct a Contact Request Channel + oc := &Protocol_Data_Control.OpenChannel{ + ChannelIdentifier: proto.Int32(channelID), + ChannelType: proto.String("im.ricochet.contact.request"), + } + + contactRequest := &Protocol_Data_ContactRequest.ContactRequest{ + Nickname: proto.String(nick), + MessageText: proto.String(message), + } + + err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest) + + if err != nil { + return nil, err + } + + pc := &Protocol_Data_Control.Packet{ + OpenChannel: oc, + } + return proto.Marshal(pc) +} + +// 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) { + 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[:]) + if err != nil { + return nil, err + } + pc := &Protocol_Data_Control.Packet{ + OpenChannel: oc, + } + return proto.Marshal(pc) +} + +// ChatMessage constructs a chat message with the given content. +func (mb *MessageBuilder) ChatMessage(message string) ([]byte, error) { + cm := &Protocol_Data_Chat.ChatMessage{ + MessageText: proto.String(message), + } + chatPacket := &Protocol_Data_Chat.Packet{ + ChatMessage: cm, + } + return proto.Marshal(chatPacket) +} diff --git a/messagebuilder_test.go b/messagebuilder_test.go new file mode 100644 index 0000000..0ee8a78 --- /dev/null +++ b/messagebuilder_test.go @@ -0,0 +1,40 @@ +package goricochet + +import "testing" + +func TestOpenChatChannel(t *testing.T) { + messageBuilder := new(MessageBuilder) + _, err := messageBuilder.OpenChatChannel(1) + if err != nil { + t.Errorf("Error building open chat channel message: %s", err) + } + // TODO: More Indepth Test Of Output +} + + +func TestOpenContactRequestChannel(t *testing.T) { + messageBuilder := new(MessageBuilder) + _, err := messageBuilder.OpenContactRequestChannel(3,"Nickname","Message") + if err != nil { + t.Errorf("Error building open contact request channel message: %s", err) + } + // TODO: More Indepth Test Of Output +} + +func TestOpenAuthenticationChannel(t *testing.T) { + messageBuilder := new(MessageBuilder) + _, err := messageBuilder.OpenAuthenticationChannel(1,[16]byte{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}) + if err != nil { + t.Errorf("Error building open authentication channel message: %s", err) + } + // TODO: More Indepth Test Of Output +} + +func TestChatMessage(t *testing.T) { + messageBuilder := new(MessageBuilder) + _, err := messageBuilder.ChatMessage("Hello World") + if err != nil { + t.Errorf("Error building chat message: %s", err) + } + // TODO: More Indepth Test Of Output +} diff --git a/networkresolver.go b/networkresolver.go index 884d932..3ba408c 100644 --- a/networkresolver.go +++ b/networkresolver.go @@ -30,12 +30,12 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) { // return just the onion address, not the local override for the hostname return conn, addrParts[1], nil - } - + } + resolvedHostname := hostname if strings.HasPrefix(hostname, "ricochet:") { - addrParts := strings.Split(hostname, ":") - resolvedHostname = addrParts[1] + addrParts := strings.Split(hostname, ":") + resolvedHostname = addrParts[1] } dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, "127.0.0.1:9050") diff --git a/ricochet.go b/ricochet.go index e0350b7..a786475 100644 --- a/ricochet.go +++ b/ricochet.go @@ -12,7 +12,6 @@ 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" "io/ioutil" "log" @@ -45,13 +44,13 @@ type Ricochet struct { // RicochetData is a structure containing the raw data and the channel it the // message originated on. type RicochetData struct { - Channel int + Channel int32 Data []byte } // RicochetMessage is a Wrapper Around Common Ricochet Protocol Strucutres type RicochetMessage struct { - Channel int + Channel int32 ControlPacket *Protocol_Data_Control.Packet DataPacket *Protocol_Data_Chat.Packet AuthPacket *Protocol_Data_AuthHiddenService.Packet @@ -120,12 +119,11 @@ func (r *Ricochet) Connect(from string, to string) error { r.negotiateVersion() - authHandler := new(AuthenticationHandler) clientCookie := authHandler.GenClientCookie() - controlBuilder := new(ControlBuilder) - data, err := controlBuilder.OpenAuthenticationChannel(1, clientCookie) + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.OpenAuthenticationChannel(1, clientCookie) if err != nil { return errors.New("Cannot Marshal Open Channel Message") @@ -192,11 +190,11 @@ func (r *Ricochet) Connect(from string, to string) error { // * Must have Previously issued a successful Connect() // * If acting as the client, id must be odd, else even func (r *Ricochet) OpenChatChannel(id int32) error { - controlBuilder := new(ControlBuilder) - data,err := controlBuilder.OpenChatChannel(id) + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.OpenChatChannel(id) if err != nil { - return errors.New("error constructing control channel message to open channel") + return errors.New("error constructing control channel message to open channel") } r.logger.Printf("Opening Chat Channel: %d", id) @@ -207,26 +205,12 @@ func (r *Ricochet) OpenChatChannel(id int32) error { // SendContactRequest initiates a contact request to the server. // Prerequisites: // * Must have Previously issued a successful Connect() -func (r *Ricochet) SendContactRequest(nick string, message string) error { - // Construct a Contact Request Channel - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(3), - ChannelType: proto.String("im.ricochet.contact.request"), - } - - contactRequest := &Protocol_Data_ContactRequest.ContactRequest{ - Nickname: proto.String(nick), - MessageText: proto.String(message), - } - - err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest) - pc := &Protocol_Data_Control.Packet{ - OpenChannel: oc, - } - data, err := proto.Marshal(pc) +func (r *Ricochet) SendContactRequest(channel int32, nick string, message string) error { + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message) if err != nil { - return errors.New("Cannot Marshal Open Channel Message") + return errors.New("error constructing control channel message to send contact request") } r.sendPacket(data, 0) @@ -237,18 +221,17 @@ func (r *Ricochet) SendContactRequest(nick string, message string) error { // Prerequisites: // * Must have previously issued a successful Connect() // * Must have previously opened channel with OpenChanel -func (r *Ricochet) SendMessage(message string, channel int) { - // Construct a Contact Request Channel - cm := &Protocol_Data_Chat.ChatMessage{ - MessageText: proto.String(message), - } - chatPacket := &Protocol_Data_Chat.Packet{ - ChatMessage: cm, +func (r *Ricochet) SendMessage(channel int32, message string) error { + messageBuilder := new(MessageBuilder) + data, err := messageBuilder.ChatMessage(message) + + if err != nil { + return errors.New("error constructing control channel message to send chat message") } - data, _ := proto.Marshal(chatPacket) r.logger.Printf("Sending Message on Channel: %d", channel) r.sendPacket(data, channel) + return nil } // negotiateVersion Perform version negotiation with the connected host. @@ -277,7 +260,7 @@ func (r *Ricochet) negotiateVersion() error { // sendPacket places the data into a structure needed for the client to // decode the packet and writes the packet to the network. -func (r *Ricochet) sendPacket(data []byte, channel int) { +func (r *Ricochet) sendPacket(data []byte, channel int32) { header := make([]byte, 4+len(data)) header[0] = byte(len(header) >> 8) header[1] = byte(len(header) & 0x00FF) @@ -294,7 +277,7 @@ func (r *Ricochet) sendPacket(data []byte, channel int) { // Prerequisites: // * Must have previously issued a successful Connect() // * Must have previously ran "go ricochet.ListenAndWait()" -func (r *Ricochet) Listen() (string, int, error) { +func (r *Ricochet) Listen() (string, int32, error) { var message RicochetMessage message = <-r.channel r.logger.Printf("Received Chat Message on Channel %d", message.Channel) @@ -439,7 +422,7 @@ func (r *Ricochet) getMessages() ([]RicochetData, error) { } data := RicochetData{ - Channel: int(channel), + Channel: int32(channel), Data: buf[pos+4 : pos+size], }