Browse Source
* New Service Interface * Server functionality * 90% Code Coverage * Regression Testing of Protocol Compliancegetters
29 changed files with 1749 additions and 440 deletions
@ -1 +1,2 @@ |
|||
go-ricochet-coverage.out |
|||
*~ |
|||
|
@ -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 |
|||
|
@ -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 |
@ -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 |
|||
|
@ -1,19 +1,63 @@ |
|||
# GoRicochet [](https://travis-ci.org/s-rah/go-ricochet) |
|||
|
|||
GoRicochet is an implementation of the [Ricochet Protocol](https://ricochet.im) |
|||
 |
|||
|
|||
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). |
|||
|
After Width: | Height: | Size: 12 KiB |
@ -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") |
|||
} |
@ -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) |
|||
} |
@ -0,0 +1,7 @@ |
|||
package goricochet |
|||
|
|||
import "testing" |
|||
|
|||
func TestOpenConnectionAuth(t *testing.T) { |
|||
|
|||
} |
@ -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----- |
@ -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 |
|||