Browse Source

Refactor GoRicochet

* New Service Interface
* Server functionality
* 90% Code Coverage
* Regression Testing of Protocol Compliance
getters
Sarah Jamie Lewis 6 years ago
parent
commit
bfe5b74364
  1. 1
      .gitignore
  2. 2
      .travis.yml
  3. 51
      CONTRIBUTING.md
  4. 51
      LICENSE
  5. 9
      Makefile
  6. 68
      README.md
  7. 2
      authhandler.go
  8. 4
      authhandler_test.go
  9. 37
      examples/echobot/main.go
  10. BIN
      logo.png
  11. 118
      messagebuilder.go
  12. 4
      messagebuilder_test.go
  13. 110
      messagedecoder.go
  14. 300
      openconnection.go
  15. 7
      openconnection_test.go
  16. 15
      private_key
  17. 527
      ricochet.go
  18. 37
      ricochetservice.go
  19. 161
      standardricochetservice.go
  20. 108
      standardricochetservice_bad_usage_error_test.go
  21. 107
      standardricochetservice_test.go
  22. 63
      standardricochetservice_unauth_test.go
  23. 60
      standardricochetservice_unknown_contact_test.go
  24. 19
      utils/error.go
  25. 92
      utils/networking.go
  26. 171
      utils/networking_test.go
  27. 3
      utils/networkresolver.go
  28. 19
      utils/tor.go
  29. 43
      utils/tor_test.go

1
.gitignore

@ -1 +1,2 @@
go-ricochet-coverage.out
*~

2
.travis.yml

@ -1,3 +1,3 @@
language: go
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test -cover
script: go get github.com/golang/protobuf/proto && go get h12.me/socks && go test -v -cover

51
CONTRIBUTING.md

@ -0,0 +1,51 @@
# How To Contribute to GoRicochet
This document highlights some useful tips for contributing to this project. Feel
free to submit pull requests to update this document as needed.
# Requesting a New Feature
You can request a new feature by submitting an issue to the [Github Repository](https://github.com/s-rah/go-ricochet).
# Writing New Code
So you want to dig in? Awesome! Here are a few steps to consider:
## 1. Before working on a Change
First, check to see if your proposed change is already in active development. This
can be done by searching issues and pull requests on Github.
If no issue exists for your change then please open one. You **do not** need to wait
for the maintainers or the community to discuss your change before starting work but, for
larger changes, we suggest attaching some design notes and requesting feedback from the
maintainers to avoid the change being rejected after all that hard work!
## 2. Make the Change
Crack open the editor of your choice and start programming. As you go along ensure
to run `go test github.com/s-rah/go-ricochet` to ensure that everything is working!
Please write tests for any new functionality. As a rule, aim for >80% code coverage. You
can check coverage `with go test --cover github.com/s-rah/go-ricochet`
## 3. Before Submitting a Pull Request
Format your code (the path might be slightly different):
* `gofmt -l=true -s -w src/github.com/s-rah/go-ricochet/`
Run the following commands, and address any issues which arise:
* `go vet github.com/s-rah/go-ricochet/...`
* `golint github.com/s-rah/go-ricochet`
* `golint github.com/s-rah/go-ricochet/utils`
## 4. Code Review
Once you submit the pull request it will be reviewed by one of the maintainers
of the project. At this point there are 3 possible paths:
1. Your change is accepted!
2. Your change is acknowledged as necessary, but requires some rework before being accepted.
3. Your change is rejected - this can happen for a number of reasons. To minimize the chances of this happening, please see step #1

51
LICENSE

@ -1,6 +1,9 @@
All code in this project, unless explicitly stated elsewhere in this document is
covered under the following license.
The MIT License (MIT)
Copyright (c) 2015 Sarah Jamie Lewis
Copyright (c) 2016 Sarah Jamie Lewis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,3 +22,49 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
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 <john.brooks@dereferenced.net>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the names of the copyright owners nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
The go-ricochet logo is based on an image by Olga Shalakhina
<osshalakhina@gmail.com> 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.

9
Makefile

@ -0,0 +1,9 @@
all:
go install github.com/go-ricochet
test:
go test -v github.com/s-rah/go-ricochet/...
cover:
go test github.com/s-rah/go-ricochet/... -cover

68
README.md

@ -1,19 +1,63 @@
# GoRicochet [![Build Status](https://travis-ci.org/s-rah/go-ricochet.svg?branch=master)](https://travis-ci.org/s-rah/go-ricochet)
GoRicochet is an implementation of the [Ricochet Protocol](https://ricochet.im)
![GoRicochet](logo.png)
GoRicochet is an experimental implementation of the [Ricochet Protocol](https://ricochet.im)
in Go.
**NOTE:** This project is in the very early stages and is in no way meant to be
a replacement for the core Ricochet implementation. This version exists for
the purpose of writing testing tools for Ricochet in Go.
## Features
* A simple API that you can use to build Automated Ricochet Applications
* A suite of regression tests that test protocol compliance.
## Building an Automated Ricochet Application
Below is a simple echo bot, which responds to any chat message. You can also find this code under `examples/echobot`
package main
import (
"github.com/s-rah/go-ricochet"
"log"
)
type EchoBotService struct {
goricochet.StandardRicochetService
}
// Always Accept Contact Requests
func (ts *EchoBotService) IsKnownContact(hostname string) bool {
return true
}
func (ts *EchoBotService) OnContactRequest(oc *goricochet.OpenConnection, channelID int32, nick string, message string) {
ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
oc.AckContactRequestOnResponse(channelID, "Accepted")
oc.CloseChannel(channelID)
}
func (ebs *EchoBotService) OnChatMessage(oc *goricochet.OpenConnection, channelID int32, messageId int32, message string) {
log.Printf("Received Message from %s: %s", oc.OtherHostname, message)
oc.AckChatMessage(channelID, messageId)
if oc.GetChannelType(6) == "none" {
oc.OpenChatChannel(6)
}
oc.SendMessage(6, message)
}
func main() {
ricochetService := new(EchoBotService)
ricochetService.Init("./private_key")
ricochetService.Listen(ricochetService, 12345)
}
Each automated ricochet service can extend of the `StandardRicochetService`. From there
certain functions can be extended to fully build out a complete application.
## Current Features
Currently GoRicochet does not establish a hidden service, so to make this service
available to the world you will have to [set up a hidden service](https://www.torproject.org/docs/tor-hidden-service.html.en)
* Connect to a Local Ricochet Client
* Issue an Authentication Request
* Issue a Contact Request
* Open a new Channel
* Send a Chat Message
## Security and Usage Note
If you have questions or want to contribute please contact Sarah @
`ricochet:qn6uo4cmsrfv4kzq`
This project is experimental and has not been independently reviewed. If you are
looking for a quick and easy way to use ricochet please check out [Ricochet Protocol](https://ricochet.im).

2
authhandler.go

@ -7,7 +7,7 @@ import (
"io"
)
// AuthenticationHandler manages the stae required for the AuthHiddenService
// AuthenticationHandler manages the state required for the AuthHiddenService
// authentication scheme for ricochet.
type AuthenticationHandler struct {
clientCookie [16]byte

4
authhandler_test.go

@ -19,7 +19,7 @@ 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)
t.Errorf("AuthenticationHandler Client Cookies are Different %x %x", clientCookie, authHandler.clientCookie)
}
}
@ -27,6 +27,6 @@ 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)
t.Errorf("AuthenticationHandler Server Cookies are Different %x %x", serverCookie, authHandler.serverCookie)
}
}

37
examples/echobot/main.go

@ -2,30 +2,35 @@ package main
import (
"github.com/s-rah/go-ricochet"
"log"
)
type EchoBotService struct {
goricochet.StandardRicochetService
goricochet.StandardRicochetService
}
func (ebs * EchoBotService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) {
if true {
ebs.Ricochet().OpenChatChannel(5)
ebs.Ricochet().SendMessage(5, "Hi I'm an echo bot, I echo what you say!")
}
// Always Accept Contact Requests
func (ts *EchoBotService) IsKnownContact(hostname string) bool {
return true
}
func (ebs * EchoBotService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) {
ebs.Ricochet().AckChatMessage(channelID, messageId)
ebs.Ricochet().SendMessage(5, message)
func (ts *EchoBotService) OnContactRequest(oc *goricochet.OpenConnection, channelID int32, nick string, message string) {
ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
oc.AckContactRequestOnResponse(channelID, "Accepted")
oc.CloseChannel(channelID)
}
func main() {
ricochetService := new(EchoBotService)
ricochetService.Init("./private_key", "kwke2hntvyfqm7dr")
err := ricochetService.Ricochet().Connect("kwke2hntvyfqm7dr", "127.0.0.1:55555|jlq67qzo6s4yp3sp")
if err == nil {
ricochetService.OnConnect("jlq67qzo6s4yp3sp")
ricochetService.Ricochet().ListenAndWait("jlq67qzo6s4yp3sp", ricochetService)
func (ebs *EchoBotService) OnChatMessage(oc *goricochet.OpenConnection, channelID int32, messageId int32, message string) {
log.Printf("Received Message from %s: %s", oc.OtherHostname, message)
oc.AckChatMessage(channelID, messageId)
if oc.GetChannelType(6) == "none" {
oc.OpenChatChannel(6)
}
oc.SendMessage(6, message)
}
func main() {
ricochetService := new(EchoBotService)
ricochetService.Init("./private_key")
ricochetService.Listen(ricochetService, 12345)
}

BIN
logo.png

After

Width: 106  |  Height: 200  |  Size: 12 KiB

118
messagebuilder.go

@ -6,6 +6,7 @@ import (
"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"
)
// MessageBuilder allows a client to construct specific data packets for the
@ -13,12 +14,12 @@ import (
type MessageBuilder struct {
}
// OpenChatChannel contructs a message which will request to open a channel for
// OpenChannel contructs a message which will request to open a channel for
// chat on the given channelID.
func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) {
func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]byte, error) {
oc := &Protocol_Data_Control.OpenChannel{
ChannelIdentifier: proto.Int32(channelID),
ChannelType: proto.String("im.ricochet.chat"),
ChannelType: proto.String(channelType),
}
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
@ -27,10 +28,10 @@ func (mb *MessageBuilder) OpenChatChannel(channelID int32) ([]byte, error) {
}
// AckOpenChannel constructs a message to acknowledge a previous open channel operation.
func (mb *MessageBuilder) AckOpenChannel(channelID int32, opened bool) ([]byte, error) {
func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) {
cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(channelID),
Opened: proto.Bool(opened),
Opened: proto.Bool(true),
}
pc := &Protocol_Data_Control.Packet{
ChannelResult: cr,
@ -38,6 +39,39 @@ func (mb *MessageBuilder) AckOpenChannel(channelID int32, opened bool) ([]byte,
return proto.Marshal(pc)
}
// 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) {
errorNum := Protocol_Data_Control.ChannelResult_CommonError_value[error]
commonError := Protocol_Data_Control.ChannelResult_CommonError(errorNum)
cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(channelID),
Opened: proto.Bool(false),
CommonError: &commonError,
}
pc := &Protocol_Data_Control.Packet{
ChannelResult: cr,
}
return proto.Marshal(pc)
}
// ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation.
func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) ([]byte, error) {
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)
pc := &Protocol_Data_Control.Packet{
ChannelResult: cr,
}
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) {
@ -53,17 +87,46 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
}
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
utils.CheckError(err)
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
}
return proto.Marshal(pc)
}
if err != nil {
return nil, err
// ReplyToContactRequestOnResponse constructs a message to acknowledge contact request
func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) ([]byte, error) {
cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(channelID),
Opened: proto.Bool(true),
}
statusNum := Protocol_Data_ContactRequest.Response_Status_value[status]
responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum)
contactRequest := &Protocol_Data_ContactRequest.Response{
Status: &responseStatus,
}
err := proto.SetExtension(cr, Protocol_Data_ContactRequest.E_Response, contactRequest)
utils.CheckError(err)
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
ChannelResult: cr,
}
return proto.Marshal(pc)
}
// ReplyToContactRequest constructs a message to acknowledge a contact request
func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) ([]byte, error) {
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)
}
// 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) {
@ -72,18 +135,49 @@ func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCooki
ChannelType: proto.String("im.ricochet.auth.hidden-service"),
}
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
if err != nil {
return nil, err
}
utils.CheckError(err)
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
}
return proto.Marshal(pc)
}
// Proof constructs a proof message with the given public key and signature.
func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([]byte, error) {
proof := &Protocol_Data_AuthHiddenService.Proof{
PublicKey: publicKeyBytes,
Signature: signatureBytes,
}
ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
Proof: proof,
Result: nil,
}
return proto.Marshal(ahsPacket)
}
// AuthResult constructs a response to a Proof
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte, error) {
// Construct a Result Message
result := &Protocol_Data_AuthHiddenService.Result{
Accepted: proto.Bool(accepted),
IsKnownContact: proto.Bool(isKnownContact),
}
ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
Proof: nil,
Result: result,
}
return proto.Marshal(ahsPacket)
}
// ChatMessage constructs a chat message with the given content.
func (mb *MessageBuilder) ChatMessage(message string) ([]byte, error) {
func (mb *MessageBuilder) ChatMessage(message string, messageID int32) ([]byte, error) {
cm := &Protocol_Data_Chat.ChatMessage{
MessageId: proto.Uint32(uint32(messageID)),
MessageText: proto.String(message),
}
chatPacket := &Protocol_Data_Chat.Packet{

4
messagebuilder_test.go

@ -4,7 +4,7 @@ import "testing"
func TestOpenChatChannel(t *testing.T) {
messageBuilder := new(MessageBuilder)
_, err := messageBuilder.OpenChatChannel(1)
_, err := messageBuilder.OpenChannel(1, "im.ricochet.chat")
if err != nil {
t.Errorf("Error building open chat channel message: %s", err)
}
@ -31,7 +31,7 @@ func TestOpenAuthenticationChannel(t *testing.T) {
func TestChatMessage(t *testing.T) {
messageBuilder := new(MessageBuilder)
_, err := messageBuilder.ChatMessage("Hello World")
_, err := messageBuilder.ChatMessage("Hello World", 0)
if err != nil {
t.Errorf("Error building chat message: %s", err)
}

110
messagedecoder.go

@ -1,110 +0,0 @@
package goricochet
import (
"errors"
"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/control"
)
type MessageDecoder struct {
}
// Conceptual Chat Message - we construct this to avoid polluting the
// the main ricochet code with protobuf cruft - and enable us to minimise the
// code that may break in the future.
type RicochetChatMessage struct {
Ack bool
MessageID int32
Message string
Accepted bool
}
// Conceptual Control Message - we construct this to avoid polluting the
// the main ricochet code with protobuf cruft - and enable us to minimise the
// code that may break in the future.
type RicochetControlMessage struct {
Ack bool
Type string
ChannelID int32
Accepted bool
ClientCookie [16]byte
ServerCookie [16]byte
}
// DecodeAuthMessage
func (md *MessageDecoder) DecodeAuthMessage(data []byte) (bool, error) {
res := new(Protocol_Data_AuthHiddenService.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
return false, errors.New("error unmarshalling control message type")
}
return res.GetResult().GetAccepted(), nil
}
// DecodeControlMessage
func (md *MessageDecoder) DecodeControlMessage(data []byte) (*RicochetControlMessage, error) {
res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
return nil, errors.New("error unmarshalling control message type")
}
if res.GetOpenChannel() != nil {
ricochetControlMessage := new(RicochetControlMessage)
ricochetControlMessage.Ack = false
if res.GetOpenChannel().GetChannelType() == "im.ricochet.auth.hidden-service" {
ricochetControlMessage.Type = "openauthchannel"
}
ricochetControlMessage.Type = "openchannel"
ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier())
return ricochetControlMessage, nil
} else if res.GetChannelResult() != nil {
ricochetControlMessage := new(RicochetControlMessage)
ricochetControlMessage.Ack = true
ricochetControlMessage.ChannelID = int32(res.GetOpenChannel().GetChannelIdentifier())
serverCookie, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_AuthHiddenService.E_ServerCookie)
if err == nil {
ricochetControlMessage.Type = "openauthchannel"
copy(ricochetControlMessage.ServerCookie[:], serverCookie.([]byte))
} else {
ricochetControlMessage.Type = "openchannel"
}
return ricochetControlMessage, nil
}
return nil, errors.New("unknown control message type")
}
// DecodeChatMessage takes a byte representing a data packet and returns a
// constructed RicochetControlMessage
func (md *MessageDecoder) DecodeChatMessage(data []byte) (*RicochetChatMessage, error) {
res := new(Protocol_Data_Chat.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
return nil, err
}
if res.GetChatMessage() != nil {
ricochetChatMessage := new(RicochetChatMessage)
ricochetChatMessage.Ack = false
ricochetChatMessage.MessageID = int32(res.GetChatMessage().GetMessageId())
ricochetChatMessage.Message = res.GetChatMessage().GetMessageText()
return ricochetChatMessage, nil
} else if res.GetChatAcknowledge != nil {
ricochetChatMessage := new(RicochetChatMessage)
ricochetChatMessage.Ack = true
ricochetChatMessage.MessageID = int32(res.GetChatAcknowledge().GetMessageId())
ricochetChatMessage.Accepted = res.GetChatAcknowledge().GetAccepted()
return ricochetChatMessage, nil
}
return nil, errors.New("chat message type not supported")
}

300
openconnection.go

@ -0,0 +1,300 @@
package goricochet
import (
"crypto"
"crypto/rsa"
"encoding/asn1"
"github.com/s-rah/go-ricochet/utils"
"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 intializes 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 {
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)
}

7
openconnection_test.go

@ -0,0 +1,7 @@
package goricochet
import "testing"
func TestOpenConnectionAuth(t *testing.T) {
}

15
private_key

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQC3xEJBH4oVFaotPJw6dezx67Gv4Xukw8CZRGqNFO8yF7Rejtcj
/0RTqqZwj6H6FjxY60dgYnN6IphW0juemNZhxOXeM/5Gb5xO+kWGi5Qt87aSDxnA
MDLgqw79ihuD3m1C1TBz0olmjXPU1VtadZuZcVBST7SLs2/k55GNNr7BoQIDAQAB
AoGBAK3ybVCdnSQWLM7DJ5LC23Wnx7sXceVlkiLCOyWuYjiFbatwBD/DupaD2yaD
HyzN7XOxyg93QZ2jr5XHTL30KEAn/3akNBsX3sjHZnjVfTwD5+oZKd7HYMMxekWf
87TIx2IHvGEo2NaFMLkEZ5TX3Gre8CYOofjFcpj4661ZfYp9AkEA9I0EmQX26ibs
CRGkwPuEj5q5N/PmIHgMWr1pepOlmzJjnxy6SI3NUwmzKrqM6YUM8loSywqfVMrJ
RVzA5jp76wJBAMBeu2hS8KcUTIu66j0pXMhI5wDA3yLiO53TEMwufCPXcaWUMH+e
5AIPL7aZ8ouf895OH0TZKxPNMnbrJ+5F0aMCQDoi/CDUxipMLnjJdP1bzdvF0Jp4
pRC6+VTpCpZVW11V0VEWJ0LwUwuWlr1ls/If60ACIc2bLN2fh9Gxhzo0VRkCQQCS
nKCAVhYLgLEGHaLAknGgQ8+rB1QIphuBoYc/1n3OYzi+VT7RRSvJVgGrTZFJUNLw
LuIt+sWWBeHcOETqmFO5AkEAwwfcxs8QZtX6hCj2MTPi8Q28LIoA/M6eAqYc2I0B
eXxf2J2Qco7sMmBLr1Jp3jZNd5W2fMtlhUZAomOj4piVOA==
-----END RSA PRIVATE KEY-----

527
ricochet.go

@ -1,319 +1,372 @@
package goricochet
import (
"encoding/binary"
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/auth"
"io/ioutil"
"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"
"os"
"strconv"
)
// Ricochet is a protocol to conducting anonymous IM.
type Ricochet struct {
conn net.Conn
logger *log.Logger
}
// RicochetData is a structure containing the raw data and the channel it the
// message originated on.
type RicochetData struct {
Channel int32
Data []byte
newconns chan *OpenConnection
networkResolver utils.NetworkResolver
rni utils.RicochetNetworkInterface
}
// Init sets up the Ricochet object.
func (r *Ricochet) Init(debugLog bool) {
if debugLog {
r.logger = log.New(os.Stdout, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
} else {
r.logger = log.New(ioutil.Discard, "[Ricochet]: ", log.Ltime|log.Lmicroseconds)
}
func (r *Ricochet) Init() {
r.newconns = make(chan *OpenConnection)
r.networkResolver = utils.NetworkResolver{}
r.rni = new(utils.RicochetNetwork)
}
// Connect sets up a ricochet connection between from and to which are
// both ricochet formated hostnames e.g. qn6uo4cmsrfv4kzq.onion. If this
// 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 (r *Ricochet) Connect(from string, to string) error {
func (r *Ricochet) Connect(host string) (*OpenConnection, error) {
var err error
networkResolver := new(NetworkResolver)
r.conn, to, err = networkResolver.Resolve(to)
if err != nil {
return err
}
return r.negotiateVersion()
}
// Authenticate opens an Authentication Channel and send a client cookie
func (r *Ricochet) Authenticate(channelID int32, clientCookie [16]byte) error {
messageBuilder := new(MessageBuilder)
data, err := messageBuilder.OpenAuthenticationChannel(channelID, clientCookie)
conn, host, err := r.networkResolver.Resolve(host)
if err != nil {
return errors.New("Cannot Marshal Open Channel Message")
}
r.logger.Printf("Sending Open Channel with Auth Request (channel:%d)", channelID)
r.sendPacket(data, 0)
return nil
}
// SendProof sends an authentication proof in response to a challenge.
func (r *Ricochet) SendProof(channelID int32, publickeyBytes []byte, signatureBytes []byte) error {
// Construct a Proof Message
proof := &Protocol_Data_AuthHiddenService.Proof{
PublicKey: publickeyBytes,
Signature: signatureBytes,
}
ahsPacket := &Protocol_Data_AuthHiddenService.Packet{
Proof: proof,
Result: nil,
return nil, err
}
data, err := proto.Marshal(ahsPacket)
oc, err := r.negotiateVersion(conn, true)
if err != nil {
return err
}
r.logger.Printf("Sending Proof Auth Request (channel:%d)", channelID)
r.sendPacket(data, channelID)
return nil
}
// OpenChannel opens a new chat channel with the given id
// Prerequisites:
// * 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 {
messageBuilder := new(MessageBuilder)
data, err := messageBuilder.OpenChatChannel(id)
if err != nil {
return errors.New("error constructing control channel message to open channel")
return nil, err
}
r.logger.Printf("Opening Chat Channel: %d", id)
r.sendPacket(data, 0)
return nil
oc.OtherHostname = host
r.newconns <- oc
return oc, nil
}
// SendContactRequest initiates a contact request to the server.
// Prerequisites:
// * Must have Previously issued a successful Connect()
func (r *Ricochet) SendContactRequest(channel int32, nick string, message string) error {
messageBuilder := new(MessageBuilder)
data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
// Server launches a new server listening on port
func (r *Ricochet) Server(service RicochetService, port int) {
ln, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port))
if err != nil {
return errors.New("error constructing control channel message to send contact request")
log.Printf("Cannot Listen on Port %v", port)
return
}
r.sendPacket(data, 0)
return nil
}
// AckOpenChannel acknowledges a previously received open channel message
// Prerequisites:
// * Must have Previously issued a successful Connect()
func (r *Ricochet) AckOpenChannel(channel int32, result bool) error {
messageBuilder := new(MessageBuilder)
data, err := messageBuilder.AckOpenChannel(channel, result)
if err != nil {
return errors.New("Failed to serialize open channel ack")
go r.ProcessMessages(service)
service.OnReady()
for {
// accept connection on port
conn, err := ln.Accept()
if err != nil {
return
}
go r.processNewConnection(conn, service)
}
r.sendPacket(data, 0)
return nil
}
// AckChatMessage acknowledges a previously received chat message.
// Prerequisites:
// * Must have Previously issued a successful Connect()
func (r *Ricochet) AckChatMessage(channel int32, messageID int32) error {
messageBuilder := new(MessageBuilder)
data, err := messageBuilder.AckChatMessage(messageID)
if err != nil {
return errors.New("Failed to serialize chat message ack")
// processNewConnection sets up a new connection
func (r *Ricochet) processNewConnection(conn net.Conn, service RicochetService) {
oc, err := r.negotiateVersion(conn, false)
if err == nil {
r.newconns <- oc
service.OnConnect(oc)
}
r.sendPacket(data, channel)
return nil
}
// SendMessage sends a Chat Message (message) to a give Channel (channel).
// ProcessMessages is intended to be a background thread listening for all messages
// a client will send. The given RicochetService will be used to respond to messages.
// Prerequisites:
// * Must have previously issued a successful Connect()
// * Must have previously opened channel with OpenChanel
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")
func (r *Ricochet) ProcessMessages(service RicochetService) {
for {
oc := <-r.newconns
go r.processConnection(oc, service)
}
r.logger.Printf("Sending Message on Channel: %d", channel)
r.sendPacket(data, channel)
return nil
}
// negotiateVersion Perform version negotiation with the connected host.
func (r *Ricochet) negotiateVersion() error {
version := make([]byte, 4)
version[0] = 0x49
version[1] = 0x4D
version[2] = 0x01
version[3] = 0x01
fmt.Fprintf(r.conn, "%s", version)
r.logger.Print("Negotiating Version ", version)
res, err := r.recv()
if len(res) != 1 || err != nil {
return errors.New("Failed Version Negotiating")
}
if res[0] != 1 {
return errors.New("Failed Version Negotiating - Invalid Version ")
}
r.logger.Print("Successfully Negotiated Version ", res[0])
return nil
}
// 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 int32) {
header := make([]byte, 4+len(data))
header[0] = byte(len(header) >> 8)
header[1] = byte(len(header) & 0x00FF)
header[2] = 0x00
header[3] = byte(channel)
copy(header[4:], data[:])
fmt.Fprintf(r.conn, "%s", header)
}
// ProcessConnection starts a blocking process loop which continually waits for
// new messages to arrive from the connection and uses the given RicochetService
// to process them.
func (r *Ricochet) processConnection(oc *OpenConnection, service RicochetService) {
service.OnConnect(oc)
for {
if oc.Closed {
return
}
// ListenAndWait is intended to be a background thread listening for all messages
// a client will send, automaticall responding to some, and making the others available to
// Listen()
// Prerequisites:
// * Must have previously issued a successful Connect()
func (r *Ricochet) ListenAndWait(serverHostname string, service RicochetService) error {
for true {
packets, err := r.getMessages()
r.handleFatal(err, "Error attempted to get new messages")
packets, err := r.rni.RecvRicochetPackets(oc.conn)
messageDecoder := new(MessageDecoder)
if err != nil {
return
}
for _, packet := range packets {
if len(packet.Data) == 0 {
r.logger.Printf("Closing Channel %d", packet.Channel)
service.OnChannelClose(packet.Channel, serverHostname)
break
service.OnChannelClosed(oc, packet.Channel)
continue
}
if packet.Channel == 0 {
message, err := messageDecoder.DecodeControlMessage(packet.Data)
res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(packet.Data[:], res)
if err != nil {
r.logger.Printf("Failed to decode data packet, discarding")
break
service.OnGenericError(oc, packet.Channel)
continue
}
if message.Type == "openchannel" && message.Ack == false {
r.logger.Printf("new open channel request %d %s", message.ChannelID, serverHostname)
service.OnOpenChannelRequest(message.ChannelID, serverHostname)
} else if message.Type == "openchannel" && message.Ack == true {
r.logger.Printf("new open channel request ack %d %s", message.ChannelID, serverHostname)
service.OnOpenChannelRequestAck(message.ChannelID, serverHostname, message.Accepted)
} else if message.Type == "openauthchannel" && message.Ack == true {
r.logger.Printf("new authentication challenge %d %s", message.ChannelID, serverHostname)
service.OnAuthenticationChallenge(message.ChannelID, serverHostname, message.ServerCookie)
if res.GetOpenChannel() != nil {
opm := res.GetOpenChannel()
if oc.GetChannelType(opm.GetChannelIdentifier()) != "none" {
// Channel is already in use.
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
continue
}
// If I am a Client, the server can only open even numbered channels
if oc.Client && opm.GetChannelIdentifier()%2 != 0 {
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
continue
}
// If I am a Server, the client can only open odd numbered channels
if !oc.Client && opm.GetChannelIdentifier()%2 != 1 {
service.OnBadUsageError(oc, 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
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
} else if oc.IsAuthed {
// Can't auth if already authed
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
} else if oc.HasChannel("im.ricochet.auth.hidden-service") {
// Can't open more than 1 auth channel
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
} else {
clientCookie, err := proto.GetExtension(opm, Protocol_Data_AuthHiddenService.E_ClientCookie)
if err == nil {
clientCookieB := [16]byte{}
copy(clientCookieB[:], clientCookie.([]byte)[:])
service.OnAuthenticationRequest(oc, opm.GetChannelIdentifier(), clientCookieB)
} else {
// Must include Client Cookie
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
}
}
case "im.ricochet.chat":
if !oc.IsAuthed {
// Can't open chat channel if not authorized
service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
} else if !service.IsKnownContact(oc.OtherHostname) {
// Can't open chat channel if not a known contact
service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
} else {
service.OnOpenChannelRequest(oc, opm.GetChannelIdentifier(), "im.ricochet.chat")
}
case "im.ricochet.contact.request":
if oc.Client {
// Servers are not allowed to send contact requests
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
} else if !oc.IsAuthed {
// Can't open a contact channel if not authed
service.OnUnauthorizedError(oc, opm.GetChannelIdentifier())
} else if oc.HasChannel("im.ricochet.contact.request") {
// Only 1 contact channel is allowed to be open at a time
service.OnBadUsageError(oc, 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 {
service.OnContactRequest(oc, opm.GetChannelIdentifier(), contactRequest.GetNickname(), contactRequest.GetMessageText())
break
}
}
service.OnBadUsageError(oc, opm.GetChannelIdentifier())
}
default:
service.OnUnknownTypeError(oc, 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)[:])
service.OnAuthenticationChallenge(oc, crm.GetChannelIdentifier(), serverCookieB)
} else {
service.OnBadUsageError(oc, crm.GetChannelIdentifier())
}
case "im.ricochet.chat":
service.OnOpenChannelRequestSuccess(oc, 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 {
service.OnContactRequestAck(oc, crm.GetChannelIdentifier(), response.GetStatus().String())
break
}
}
service.OnBadUsageError(oc, crm.GetChannelIdentifier())
default:
service.OnBadUsageError(oc, crm.GetChannelIdentifier())
}
} else {
if oc.GetChannelType(crm.GetChannelIdentifier()) != "none" {
service.OnFailedChannelOpen(oc, crm.GetChannelIdentifier(), crm.GetCommonError().String())
} else {
oc.CloseChannel(crm.GetChannelIdentifier())
}
}
} else {
r.logger.Printf("Received Unknown Control Message\n", message)
// Unknown Message
oc.CloseChannel(packet.Channel)
}
} else if packet.Channel == 1 {
result, _ := messageDecoder.DecodeAuthMessage(packet.Data)
r.logger.Printf("newreceived auth result %d", packet.Channel)
service.OnAuthenticationResult(1, serverHostname, result)
} else {
} else if oc.GetChannelType(packet.Channel) == "im.ricochet.auth.hidden-service" {
res := new(Protocol_Data_AuthHiddenService.Packet)
err := proto.Unmarshal(packet.Data[:], res)
// At this point the only other expected type of message is a Chat Message
messageDecoder := new(MessageDecoder)
message, err := messageDecoder.DecodeChatMessage(packet.Data)
if err != nil {
r.logger.Printf("Failed to decode data packet, discarding on channel %d", packet.Channel)
break
oc.CloseChannel(packet.Channel)
continue
}
if message.Ack == true {
service.OnChatMessageAck(packet.Channel, serverHostname, message.MessageID)
if res.GetProof() != nil && !oc.Client { // Only Clients Send Proofs
service.OnAuthenticationProof(oc, packet.Channel, res.GetProof().GetPublicKey(), res.GetProof().GetSignature(), service.IsKnownContact(oc.OtherHostname))
} else if res.GetResult() != nil && oc.Client { // Only Servers Send Results
service.OnAuthenticationResult(oc, 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
service.OnUnauthorizedError(oc, packet.Channel)
} else if !service.IsKnownContact(oc.OtherHostname) {
// Can't send chat message if not a known contact
service.OnUnauthorizedError(oc, 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 {
service.OnChatMessage(oc, packet.Channel, int32(res.GetChatMessage().GetMessageId()), res.GetChatMessage().GetMessageText())
} else if res.GetChatAcknowledge() != nil {
service.OnChatMessageAck(oc, 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
service.OnBadUsageError(oc, packet.Channel)
} else if !oc.IsAuthed {
// Can't send a contact request if not authed
service.OnBadUsageError(oc, packet.Channel)
} else {
service.OnChatMessage(packet.Channel, serverHostname, message.MessageID, message.Message)
res := new(Protocol_Data_ContactRequest.Response)
err := proto.Unmarshal(packet.Data[:], res)
log.Printf("%v", res)
if err != nil {
oc.CloseChannel(packet.Channel)
continue
}
service.OnContactRequestAck(oc, packet.Channel, res.GetStatus().String())
}
} else if oc.GetChannelType(packet.Channel) == "none" {
// Invalid Channel Assignment
oc.CloseChannel(packet.Channel)
} else {
oc.Close()
}
}
}
return nil
}
// getMessages returns an array of new messages received from the ricochet client
func (r *Ricochet) getMessages() ([]RicochetData, error) {
buf, err := r.recv()
if err != nil {
return nil, errors.New("Failed to retrieve new messages from the client")
}
// negotiateVersion Perform version negotiation with the connected host.
func (r *Ricochet) negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) {
version := make([]byte, 4)
version[0] = 0x49
version[1] = 0x4D
version[2] = 0x01
version[3] = 0x01
pos := 0
finished := false
datas := []RicochetData{}
// If this was initiated by us then we need to initiate the version info.
if outbound {
// Send Version String
for !finished {
size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
conn.Write(version)
res, err := r.rni.Recv(conn)
if pos+size > len(buf) {
return datas, errors.New("Partial data packet received")
if len(res) != 1 || err != nil {
return nil, errors.New("Failed Version Negotiating")
}
data := RicochetData{
Channel: int32(channel),
Data: buf[pos+4 : pos+size],
if res[0] != 1 {
return nil, errors.New("Failed Version Negotiating - Invalid Version ")
}
} else {
// Do Version Negotiation
datas = append(datas, data)
pos += size
if pos >= len(buf) {
finished = true
buf := make([]byte, 10)
n, err := conn.Read(buf)
if err != nil && n >= 4 {
return nil, err
}
}
r.logger.Printf("Got %d Packets", len(datas))
return datas, nil
}
// recv reads data from the client, and returns the raw byte array, else error.
func (r *Ricochet) recv() ([]byte, error) {
buf := make([]byte, 4096)
n, err := r.conn.Read(buf)
if err != nil {
return nil, err
if buf[0] == version[0] && buf[1] == version[1] {
foundVersion := false
if buf[2] >= 1 {
for i := 3; i < n; i++ {
if buf[i] == 0x01 {
conn.Write([]byte{0x01})
foundVersion = true
}
}
}
if !foundVersion {
return nil, errors.New("Failed Version Negotiating - No Available Version")
}
} else {
return nil, errors.New("Failed Version Negotiating - Invalid Version Header")
}
}
ret := make([]byte, n)
copy(ret[:], buf[:])
return ret, nil
}
func (r *Ricochet) handleFatal(err error, message string) {
if err != nil {
r.logger.Fatal(message)
}
oc := new(OpenConnection)
oc.Init(outbound, conn)
return oc, nil
}

37
ricochetservice.go

@ -1,16 +1,35 @@
package goricochet