Refactor GoRicochet

* New Service Interface
* Server functionality
* 90% Code Coverage
* Regression Testing of Protocol Compliance
This commit is contained in:
Sarah Jamie Lewis 2016-06-26 18:56:23 -07:00
parent 93754f2916
commit bfe5b74364
29 changed files with 1773 additions and 464 deletions

1
.gitignore vendored
View File

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

View File

@ -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 Normal file
View File

@ -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
View File

@ -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 Normal file
View File

@ -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

View File

@ -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
## Current Features
* A simple API that you can use to build Automated Ricochet Applications
* A suite of regression tests that test protocol compliance.
* Connect to a Local Ricochet Client
* Issue an Authentication Request
* Issue a Contact Request
* Open a new Channel
* Send a Chat Message
## Building an Automated Ricochet Application
If you have questions or want to contribute please contact Sarah @
`ricochet:qn6uo4cmsrfv4kzq`
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.
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)
## Security and Usage Note
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).

View File

@ -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

View File

@ -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)
}
}

View File

@ -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 (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", "kwke2hntvyfqm7dr")
err := ricochetService.Ricochet().Connect("kwke2hntvyfqm7dr", "127.0.0.1:55555|jlq67qzo6s4yp3sp")
if err == nil {
ricochetService.OnConnect("jlq67qzo6s4yp3sp")
ricochetService.Ricochet().ListenAndWait("jlq67qzo6s4yp3sp", ricochetService)
}
ricochetService := new(EchoBotService)
ricochetService.Init("./private_key")
ricochetService.Listen(ricochetService, 12345)
}

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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,10 +87,7 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
}
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
if err != nil {
return nil, err
}
utils.CheckError(err)
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
@ -64,6 +95,38 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
return proto.Marshal(pc)
}
// 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{
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{

View File

@ -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)
}

View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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

15
private_key Normal file
View File

@ -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-----

View File

@ -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)
conn, host, err := r.networkResolver.Resolve(host)
if err != nil {
return err
return nil, 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)
oc, err := r.negotiateVersion(conn, true)
if err != nil {
return errors.New("Cannot Marshal Open Channel Message")
return nil, err
}
r.logger.Printf("Sending Open Channel with Auth Request (channel:%d)", channelID)
r.sendPacket(data, 0)
return nil
oc.OtherHostname = host
r.newconns <- oc
return oc, 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,
}
data, err := proto.Marshal(ahsPacket)
// 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 err
log.Printf("Cannot Listen on Port %v", port)
return
}
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")
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.logger.Printf("Opening Chat Channel: %d", id)
r.sendPacket(data, 0)
return 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)
if err != nil {
return errors.New("error constructing control channel message to send contact request")
// 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, 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")
}
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")
}
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
// 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
}
packets, err := r.rni.RecvRicochetPackets(oc.conn)
if err != nil {
return
}
for _, packet := range packets {
if len(packet.Data) == 0 {
service.OnChannelClosed(oc, packet.Channel)
continue
}
if packet.Channel == 0 {
res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(packet.Data[:], res)
if err != nil {
service.OnGenericError(oc, packet.Channel)
continue
}
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 {
// Unknown Message
oc.CloseChannel(packet.Channel)
}
} else if oc.GetChannelType(packet.Channel) == "im.ricochet.auth.hidden-service" {
res := new(Protocol_Data_AuthHiddenService.Packet)
err := proto.Unmarshal(packet.Data[:], res)
if err != nil {
oc.CloseChannel(packet.Channel)
continue
}
if res.GetProof() != nil && !oc.Client { // Only Clients Send Proofs
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 {
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()
}
}
}
}
// negotiateVersion Perform version negotiation with the connected host.
func (r *Ricochet) negotiateVersion() error {
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
fmt.Fprintf(r.conn, "%s", version)
r.logger.Print("Negotiating Version ", version)
res, err := r.recv()
// If this was initiated by us then we need to initiate the version info.
if outbound {
// Send Version String
if len(res) != 1 || err != nil {
return errors.New("Failed Version Negotiating")
}
conn.Write(version)
res, err := r.rni.Recv(conn)
if res[0] != 1 {
return errors.New("Failed Version Negotiating - Invalid Version ")
}
if len(res) != 1 || err != nil {
return nil, errors.New("Failed Version Negotiating")
}
r.logger.Print("Successfully Negotiated Version ", res[0])
return nil
}
if res[0] != 1 {
return nil, errors.New("Failed Version Negotiating - Invalid Version ")
}
} else {
// Do Version Negotiation
// 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)
}
buf := make([]byte, 10)
n, err := conn.Read(buf)
if err != nil && n >= 4 {
return nil, err
}
// 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")
messageDecoder := new(MessageDecoder)
for _, packet := range packets {
if len(packet.Data) == 0 {
r.logger.Printf("Closing Channel %d", packet.Channel)
service.OnChannelClose(packet.Channel, serverHostname)
break
}
if packet.Channel == 0 {
message, err := messageDecoder.DecodeControlMessage(packet.Data)
if err != nil {
r.logger.Printf("Failed to decode data packet, discarding")
break
}
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)
} else {
r.logger.Printf("Received Unknown Control Message\n", message)
}
} 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 {
// 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
}
if message.Ack == true {
service.OnChatMessageAck(packet.Channel, serverHostname, message.MessageID)
} else {
service.OnChatMessage(packet.Channel, serverHostname, message.MessageID, message.Message)
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")
}
}
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")
}
pos := 0
finished := false
datas := []RicochetData{}
for !finished {
size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
if pos+size > len(buf) {
return datas, errors.New("Partial data packet received")
}
data := RicochetData{
Channel: int32(channel),
Data: buf[pos+4 : pos+size],
}
datas = append(datas, data)
pos += size
if pos >= len(buf) {
finished = true
}
}
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
}
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
}

View File

@ -1,16 +1,35 @@
package goricochet
// RicochetService provides an interface for building automated ricochet applications.
type RicochetService interface {
OnConnect(serverHostname string)
OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte)
OnAuthenticationResult(channelID int32, serverHostname string, result bool)
OnReady()
OnConnect(oc *OpenConnection)
OnOpenChannelRequest(channelID int32, serverHostname string)
OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool)
OnChannelClose(channelID int32, serverHostname string)
// Authentication Management
OnAuthenticationRequest(oc *OpenConnection, channelID int32, clientCookie [16]byte)
OnAuthenticationChallenge(oc *OpenConnection, channelID int32, serverCookie [16]byte)
OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool)
OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool)
OnContactRequest(channelID string, serverHostname string, nick string, message string)
// Contact Management
IsKnownContact(hostname string) bool
OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string)
OnContactRequestAck(oc *OpenConnection, channelID int32, status string)
OnChatMessage(channelID int32, serverHostname string, messageID int32, message string)
OnChatMessageAck(channelID int32, serverHostname string, messageID int32)
// Managing Channels
OnOpenChannelRequest(oc *OpenConnection, channelID int32, channelType string)
OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32)
OnChannelClosed(oc *OpenConnection, channelID int32)
// Chat Messages
OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string)
OnChatMessageAck(oc *OpenConnection, channelID int32, messageID int32)
// Handle Errors
OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string)
OnGenericError(oc *OpenConnection, channelID int32)
OnUnknownTypeError(oc *OpenConnection, channelID int32)
OnUnauthorizedError(oc *OpenConnection, channelID int32)
OnBadUsageError(oc *OpenConnection, channelID int32)
OnFailedError(oc *OpenConnection, channelID int32)
}

View File

@ -1,89 +1,178 @@
package goricochet
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
//"encoding/binary"
"encoding/pem"
"errors"
"github.com/s-rah/go-ricochet/utils"
"io/ioutil"
"log"
)
// StandardRicochetService implements all the necessary flows to implement a
// minimal, protocol compliant Ricochet Service. It can be built on by other
// applications to produce automated riochet applications.
type StandardRicochetService struct {
ricochet *Ricochet
authHandler map[string]*AuthenticationHandler
privateKey *rsa.PrivateKey
hostname string
ricochet *Ricochet
privateKey *rsa.PrivateKey
serverHostname string
}
func (srs *StandardRicochetService) Init(filename string, hostname string) {
// Init initializes a StandardRicochetService with the cryptographic key given
// by filename.
func (srs *StandardRicochetService) Init(filename string) error {
srs.ricochet = new(Ricochet)
srs.ricochet.Init(true)
srs.authHandler = make(map[string]*AuthenticationHandler)
srs.hostname = hostname
srs.ricochet.Init()
pemData, err := ioutil.ReadFile(filename)
if err != nil {
// r.logger.Print("Error Reading Private Key: ", err)
return errors.New("Could not setup ricochet service: could not read private key")
}
block, _ := pem.Decode(pemData)
if block == nil || block.Type != "RSA PRIVATE KEY" {
// r.logger.Print("No valid PEM data found")
return errors.New("Could not setup ricochet service: no valid PEM data found")
}
srs.privateKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
srs.privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return errors.New("Could not setup ricochet service: could not parse private key")
}
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: srs.privateKey.PublicKey.N,
E: srs.privateKey.PublicKey.E,
})
srs.serverHostname = utils.GetTorHostname(publicKeyBytes)
log.Printf("Initialised ricochet service for %s", srs.serverHostname)
return nil
}
func (srs *StandardRicochetService) OnConnect(serverHostname string) {
srs.authHandler[serverHostname] = new(AuthenticationHandler)
clientCookie := srs.authHandler[serverHostname].GenClientCookie()
srs.ricochet.Authenticate(1, clientCookie)
// OnReady is called once a Server has been established (by calling Listen)
func (srs *StandardRicochetService) OnReady() {
}
// Listen starts the ricochet service. Listen must be called before any other method (apart from Init)
func (srs *StandardRicochetService) Listen(service RicochetService, port int) {
srs.ricochet.Server(service, port)
}
// Connect can be called to initiate a new client connection to a server
func (srs *StandardRicochetService) Connect(hostname string) error {
log.Printf("Connecting to...%s", hostname)
oc, err := srs.ricochet.Connect(hostname)
if err != nil {
return errors.New("Could not connect to: " + hostname)
}
oc.MyHostname = srs.serverHostname
return nil
}
// OnConnect is called when a client or server sucessfully passes Version Negotiation.
func (srs *StandardRicochetService) OnConnect(oc *OpenConnection) {
if oc.Client {
log.Printf("Sucessefully Connected to %s", oc.OtherHostname)
oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default
oc.Authenticate(1)
} else {
oc.MyHostname = srs.serverHostname
}
}
// OnAuthenticationRequest is called when a client requests Authentication
func (srs *StandardRicochetService) OnAuthenticationRequest(oc *OpenConnection, channelID int32, clientCookie [16]byte) {
oc.ConfirmAuthChannel(channelID, clientCookie)
}
// OnAuthenticationChallenge constructs a valid authentication challenge to the serverCookie
func (srs *StandardRicochetService) OnAuthenticationChallenge(channelID int32, serverHostname string, serverCookie [16]byte) {
srs.authHandler[serverHostname].AddServerCookie(serverCookie[:])
func (srs *StandardRicochetService) OnAuthenticationChallenge(oc *OpenConnection, channelID int32, serverCookie [16]byte) {
// DER Encode the Public Key
publickeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: srs.privateKey.PublicKey.N,
E: srs.privateKey.PublicKey.E,
})
signature, _ := rsa.SignPKCS1v15(nil, srs.privateKey, crypto.SHA256, srs.authHandler[serverHostname].GenChallenge(srs.hostname, serverHostname))
// TODO Handle Errors
signatureBytes := make([]byte, 128)
copy(signatureBytes[:], signature[:])
srs.ricochet.SendProof(1, publickeyBytes, signatureBytes)
oc.SendProof(1, serverCookie, publickeyBytes, srs.privateKey)
}
func (srs *StandardRicochetService) Ricochet() *Ricochet {
return srs.ricochet
// OnAuthenticationProof is called when a client sends Proof for an existing authentication challenge
func (srs *StandardRicochetService) OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) {
result := oc.ValidateProof(channelID, publicKey, signature)
oc.SendAuthenticationResult(channelID, result, isKnownContact)
oc.IsAuthed = result
oc.CloseChannel(channelID)
}
func (srs *StandardRicochetService) OnAuthenticationResult(channelID int32, serverHostname string, result bool) {
// OnAuthenticationResult is called once a server has returned the result of the Proof Verification
func (srs *StandardRicochetService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
oc.IsAuthed = result
}
func (srs *StandardRicochetService) OnOpenChannelRequest(channelID int32, serverHostname string) {
srs.ricochet.AckOpenChannel(channelID, true)
// IsKnownContact allows a caller to determine if a hostname an authorized contact.
func (srs *StandardRicochetService) IsKnownContact(hostname string) bool {
return false
}
func (srs *StandardRicochetService) OnOpenChannelRequestAck(channelID int32, serverHostname string, result bool) {
// OnContactRequest is called when a client sends a new contact request
func (srs *StandardRicochetService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
}
func (srs *StandardRicochetService) OnChannelClose(channelID int32, serverHostname string) {
// OnContactRequestAck is called when a server sends a reply to an existing contact request
func (srs *StandardRicochetService) OnContactRequestAck(oc *OpenConnection, channelID int32, status string) {
}
func (srs *StandardRicochetService) OnContactRequest(channelID string, serverHostname string, nick string, message string) {
// OnOpenChannelRequest is called when a client or server requests to open a new channel
func (srs *StandardRicochetService) OnOpenChannelRequest(oc *OpenConnection, channelID int32, channelType string) {
oc.AckOpenChannel(channelID, channelType)
}
func (srs *StandardRicochetService) OnChatMessage(channelID int32, serverHostname string, messageId int32, message string) {
srs.ricochet.AckChatMessage(channelID, messageId)
// OnOpenChannelRequestSuccess is called when a client or server responds to an open channel request
func (srs *StandardRicochetService) OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32) {
}
func (srs *StandardRicochetService) OnChatMessageAck(channelID int32, serverHostname string, messageId int32) {
// OnChannelClose is called when a client or server closes an existing channel
func (srs *StandardRicochetService) OnChannelClosed(oc *OpenConnection, channelID int32) {
}
// OnChatMessage is called when a new chat message is received.
func (srs *StandardRicochetService) OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string) {
oc.AckChatMessage(channelID, messageID)
}
// OnChatMessageAck is called when a new chat message is ascknowledged.
func (srs *StandardRicochetService) OnChatMessageAck(oc *OpenConnection, channelID int32, messageID int32) {
}
// OnFailedChannelOpen is called when a server fails to open a channel
func (srs *StandardRicochetService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
oc.UnsetChannel(channelID)
}
// OnGenericError is called when a generalized error is returned from the peer
func (srs *StandardRicochetService) OnGenericError(oc *OpenConnection, channelID int32) {
oc.RejectOpenChannel(channelID, "GenericError")
}
//OnUnknownTypeError is called when an unknown type error is returned from the peer
func (srs *StandardRicochetService) OnUnknownTypeError(oc *OpenConnection, channelID int32) {
oc.RejectOpenChannel(channelID, "UnknownTypeError")
}
// OnUnauthorizedError is called when an unathorized error is returned from the peer
func (srs *StandardRicochetService) OnUnauthorizedError(oc *OpenConnection, channelID int32) {
oc.RejectOpenChannel(channelID, "UnauthorizedError")
}
// OnBadUsageError is called when a bad usage error is returned from the peer
func (srs *StandardRicochetService) OnBadUsageError(oc *OpenConnection, channelID int32) {
oc.RejectOpenChannel(channelID, "BadUsageError")
}
// OnFailedError is called when a failed error is returned from the peer
func (srs *StandardRicochetService) OnFailedError(oc *OpenConnection, channelID int32) {
oc.RejectOpenChannel(channelID, "FailedError")
}

View File

@ -0,0 +1,108 @@
package goricochet
import "testing"
import "time"
import "log"
type TestBadUsageService struct {
StandardRicochetService
BadUsageErrorCount int
UnknownTypeErrorCount int
ChannelClosed int
}
func (ts *TestBadUsageService) OnConnect(oc *OpenConnection) {
if oc.Client {
oc.OpenChannel(17, "im.ricochet.auth.hidden-service") // Fail because no Extension
}
ts.StandardRicochetService.OnConnect(oc)
if oc.Client {
oc.Authenticate(103) // Should Fail because cannot open more than one auth-hidden-service channel at once
}
}
func (ts *TestBadUsageService) OnAuthenticationProof(oc *OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) {
oc.Authenticate(2) // Try to authenticate again...will fail servers don't auth
oc.SendContactRequest(4, "test", "test") // Only clients can send contact requests
ts.StandardRicochetService.OnAuthenticationProof(oc, channelID, publicKey, signature, isKnownContact)
oc.OpenChatChannel(5) // Fail because server can only open even numbered channels
oc.OpenChatChannel(3) // Fail because already in use...
}
// OnContactRequest is called when a client sends a new contact request
func (ts *TestBadUsageService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
oc.AckContactRequestOnResponse(channelID, "Pending") // Done to keep the contact request channel open
}
func (ts *TestBadUsageService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
oc.OpenChatChannel(3) // Succeed
oc.OpenChatChannel(3) // Should fail as duplicate (channel already in use)
oc.OpenChatChannel(6) // Should fail because clients are not allowed to open even numbered channels
oc.SendMessage(101, "test") // Should fail as 101 doesn't exist
oc.Authenticate(1) // Try to authenticate again...will fail because we have already authenticated
oc.OpenChannel(19, "im.ricochet.contact.request") // Will Fail
oc.SendContactRequest(11, "test", "test") // Succeed
oc.SendContactRequest(13, "test", "test") // Trigger singleton contact request check
oc.OpenChannel(15, "im.ricochet.not-a-real-type") // Fail UnknownType
}
// OnChannelClose is called when a client or server closes an existing channel
func (ts *TestBadUsageService) OnChannelClosed(oc *OpenConnection, channelID int32) {
if channelID == 101 {
log.Printf("Received Channel Closed: %v", channelID)
ts.ChannelClosed++
}
}
func (ts *TestBadUsageService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
log.Printf("Failed Channel Open %v %v", channelID, errorType)
ts.StandardRicochetService.OnFailedChannelOpen(oc, channelID, errorType)
if errorType == "BadUsageError" {
ts.BadUsageErrorCount++
} else if errorType == "UnknownTypeError" {
ts.UnknownTypeErrorCount++
}
}
func (ts *TestBadUsageService) IsKnownContact(hostname string) bool {
return true
}
func TestBadUsageServer(t *testing.T) {
ricochetService := new(TestBadUsageService)
err := ricochetService.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService.Listen(ricochetService, 9884)
time.Sleep(time.Second * 2)
ricochetService2 := new(TestBadUsageService)
err = ricochetService2.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService2.Listen(ricochetService2, 9885)
err = ricochetService2.Connect("127.0.0.1:9884|kwke2hntvyfqm7dr")
if err != nil {
t.Errorf("Could not connect to ricochet service: %v", err)
}
time.Sleep(time.Second * 3)
if ricochetService2.ChannelClosed != 1 || ricochetService2.BadUsageErrorCount != 7 || ricochetService.BadUsageErrorCount != 4 || ricochetService2.UnknownTypeErrorCount != 1 {
t.Errorf("Invalid number of errors seen Closed:%v, Client Bad Usage:%v UnknownTypeErrorCount: %v, Server Bad Usage: %v ", ricochetService2.ChannelClosed, ricochetService2.BadUsageErrorCount, ricochetService2.UnknownTypeErrorCount, ricochetService.BadUsageErrorCount)
}
}

View File

@ -0,0 +1,107 @@
package goricochet
import "testing"
import "time"
import "log"
type TestService struct {
StandardRicochetService
ReceivedMessage bool
KnownContact bool // Mocking contact request
}
func (ts *TestService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
if !isKnownContact {
log.Printf("Sending Contact Request")
oc.SendContactRequest(3, "test", "test")
}
}
func (ts *TestService) OnContactRequest(oc *OpenConnection, channelID int32, nick string, message string) {
ts.StandardRicochetService.OnContactRequest(oc, channelID, nick, message)
oc.AckContactRequestOnResponse(channelID, "Pending")
oc.AckContactRequest(channelID, "Accepted")
ts.KnownContact = true
oc.CloseChannel(channelID)
}
func (ts *TestService) OnOpenChannelRequestSuccess(oc *OpenConnection, channelID int32) {
ts.StandardRicochetService.OnOpenChannelRequestSuccess(oc, channelID)
oc.SendMessage(channelID, "TEST MESSAGE")
}
func (ts *TestService) OnContactRequestAck(oc *OpenConnection, channelID int32, status string) {
ts.StandardRicochetService.OnContactRequestAck(oc, channelID, status)
if status == "Accepted" {
log.Printf("Got accepted contact request")
ts.KnownContact = true
oc.OpenChatChannel(5)
} else if status == "Pending" {
log.Printf("Got pending contact request")
}
}
func (ts *TestService) OnChatMessage(oc *OpenConnection, channelID int32, messageID int32, message string) {
ts.StandardRicochetService.OnChatMessage(oc, channelID, messageID, message)
if message == "TEST MESSAGE" {
ts.ReceivedMessage = true
}
}
func (ts *TestService) IsKnownContact(hostname string) bool {
return ts.KnownContact
}
func TestServer(t *testing.T) {
ricochetService := new(TestService)
err := ricochetService.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService.Listen(ricochetService, 9878)
time.Sleep(time.Second * 2)
ricochetService2 := new(TestService)
err = ricochetService2.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService2.Listen(ricochetService2, 9879)
err = ricochetService2.Connect("127.0.0.1:9878|kwke2hntvyfqm7dr")
if err != nil {
t.Errorf("Could not connect to ricochet service: %v", err)
}
time.Sleep(time.Second * 5) // Wait a bit longer
if !ricochetService.ReceivedMessage {
t.Errorf("Test server did not receive message")
}
}
func TestServerInvalidKey(t *testing.T) {
ricochetService := new(TestService)
err := ricochetService.Init("./private_key.does.not.exist")
if err == nil {
t.Errorf("Should not have initate ricochet service, private key should not exist")
}
}
func TestServerCouldNotConnect(t *testing.T) {
ricochetService := new(TestService)
err := ricochetService.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
err = ricochetService.Connect("127.0.0.1:65535|kwke2hntvyfqm7dr")
if err == nil {
t.Errorf("Should not have been been able to connect to 127.0.0.1:65535|kwke2hntvyfqm7dr")
}
}

View File

@ -0,0 +1,63 @@
package goricochet
import "testing"
import "time"
import "log"
// The purpose of this test is to exercise the Unauthorized Error flows that occur
// when a client attempts to open a Chat Channel or Send a Contact Reuqest before Authentication
// itself with the Service.
type TestUnauthorizedService struct {
StandardRicochetService
FailedToOpen int
}
func (ts *TestUnauthorizedService) OnConnect(oc *OpenConnection) {
if oc.Client {
log.Printf("Attempting Authentication Not Authorized")
oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default
// REMOVED Authenticate
oc.OpenChatChannel(5)
oc.SendContactRequest(3, "test", "test")
}
}
func (ts *TestUnauthorizedService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
oc.UnsetChannel(channelID)
if errorType == "UnauthorizedError" {
ts.FailedToOpen++
}
}
func TestUnauthorizedClientReject(t *testing.T) {
ricochetService := new(TestService)
err := ricochetService.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService.Listen(ricochetService, 9880)
time.Sleep(time.Second * 2)
ricochetService2 := new(TestUnauthorizedService)
err = ricochetService2.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService2.Listen(ricochetService2, 9881)
err = ricochetService2.Connect("127.0.0.1:9880|kwke2hntvyfqm7dr")
if err != nil {
t.Errorf("Could not connect to ricochet service: %v", err)
}
time.Sleep(time.Second * 2)
if ricochetService2.FailedToOpen != 2 {
t.Errorf("Test server did not reject open channels with unauthorized error")
}
}

View File

@ -0,0 +1,60 @@
package goricochet
import "testing"
import "time"
import "log"
type TestUnknownContactService struct {
StandardRicochetService
FailedToOpen bool
}
func (ts *TestUnknownContactService) OnAuthenticationResult(oc *OpenConnection, channelID int32, result bool, isKnownContact bool) {
log.Printf("Authentication Result")
ts.StandardRicochetService.OnAuthenticationResult(oc, channelID, result, isKnownContact)
oc.OpenChatChannel(5)
}
func (ts *TestUnknownContactService) OnFailedChannelOpen(oc *OpenConnection, channelID int32, errorType string) {
log.Printf("Failed Channel Open %v", errorType)
oc.UnsetChannel(channelID)
if errorType == "UnauthorizedError" {
ts.FailedToOpen = true
}
}
func (ts *TestUnknownContactService) IsKnownContact(hostname string) bool {
return false
}
func TestUnknownContactServer(t *testing.T) {
ricochetService := new(StandardRicochetService)
err := ricochetService.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService.Listen(ricochetService, 9882)
time.Sleep(time.Second * 2)
ricochetService2 := new(TestUnknownContactService)
err = ricochetService2.Init("./private_key")
if err != nil {
t.Errorf("Could not initate ricochet service: %v", err)
}
go ricochetService2.Listen(ricochetService2, 9883)
err = ricochetService2.Connect("127.0.0.1:9882|kwke2hntvyfqm7dr")
if err != nil {
t.Errorf("Could not connect to ricochet service: %v", err)
}
time.Sleep(time.Second * 2)
if !ricochetService2.FailedToOpen {
t.Errorf("Test server did receive message should have failed")
}
}

19
utils/error.go Normal file
View File

@ -0,0 +1,19 @@
package utils
import "fmt"
import "log"
func RecoverFromError() {
if r := recover(); r != nil {
// This should only really happen if there is a failure de/serializing. If
// this does happen then we currently error. In the future we might be
// able to make this nicer.
log.Fatalf("Recovered from panic() - this really shouldn't happen. Reason: %v", r)
}
}
func CheckError(err error) {
if err != nil {
panic(fmt.Sprintf("%v", err))
}
}

92
utils/networking.go Normal file
View File

@ -0,0 +1,92 @@
package utils
import (
"encoding/binary"
"errors"
"net"
"strconv"
)
// RicochetData is a structure containing the raw data and the channel it the
// message originated on.
type RicochetData struct {
Channel int32
Data []byte
}
// RicochetNetworkInterface abstract operations that interact with ricochet's
// packet layer.
type RicochetNetworkInterface interface {
Recv(conn net.Conn) ([]byte, error)
SendRicochetPacket(conn net.Conn, channel int32, data []byte)
RecvRicochetPackets(conn net.Conn) ([]RicochetData, error)
}
// RicochetNetwork is a concrete implementation of the RicochetNetworkInterface
type RicochetNetwork struct {
}
// Recv reads data from the client, and returns the raw byte array, else error.
func (rn *RicochetNetwork) Recv(conn net.Conn) ([]byte, error) {
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
return nil, err
}
ret := make([]byte, n)
copy(ret[:], buf[:])
return ret, nil
}
// SendRicochetPacket places the data into a structure needed for the client to
// decode the packet and writes the packet to the network.
func (rn *RicochetNetwork) SendRicochetPacket(conn net.Conn, channel int32, data []byte) {
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[:])
conn.Write(header)
}
// RecvRicochetPackets returns an array of new messages received from the ricochet client
func (rn *RicochetNetwork) RecvRicochetPackets(conn net.Conn) ([]RicochetData, error) {
buf, err := rn.Recv(conn)
if err != nil && len(buf) < 4 {
return nil, errors.New("failed to retrieve new messages from the client")
}
pos := 0
finished := false
var datas []RicochetData
for !finished {
size := int(binary.BigEndian.Uint16(buf[pos+0 : pos+2]))
channel := int(binary.BigEndian.Uint16(buf[pos+2 : pos+4]))
if size < 4 {
return datas, errors.New("invalid ricochet packet received (size=" + strconv.Itoa(size) + ")")
}
if pos+size > len(buf) {
return datas, errors.New("partial data packet received")
}
data := RicochetData{}
data.Channel = int32(channel)
if pos+4 >= len(buf) {
data.Data = make([]byte, 0)
} else {
data.Data = buf[pos+4 : pos+size]
}
datas = append(datas, data)
pos += size
if pos >= len(buf) {
finished = true
}
}
return datas, nil
}

171
utils/networking_test.go Normal file
View File

@ -0,0 +1,171 @@
package utils
import "testing"
import "net"
import "time"
type MockConn struct {
Written []byte
MockOutput []byte
}
func (mc *MockConn) Read(b []byte) (int, error) {
copy(b[:], mc.MockOutput[:])
return len(mc.MockOutput), nil
}
func (mc *MockConn) Write(written []byte) (int, error) {
mc.Written = written
return 0, nil
}
func (mc *MockConn) LocalAddr() net.Addr {
return nil
}
func (mc *MockConn) RemoteAddr() net.Addr {
return nil
}
func (mc *MockConn) Close() error {
return nil
}
func (mc *MockConn) SetDeadline(t time.Time) error {
return nil
}
func (mc *MockConn) SetReadDeadline(t time.Time) error {
return nil
}
func (mc *MockConn) SetWriteDeadline(t time.Time) error {
return nil
}
func TestSentRicochetPacket(t *testing.T) {
conn := new(MockConn)
rni := RicochetNetwork{}
rni.SendRicochetPacket(conn, 1, []byte{})
if len(conn.Written) != 4 && conn.Written[0] != 0x00 && conn.Written[1] != 0x00 && conn.Written[2] != 0x01 && conn.Written[3] != 0x00 {
t.Errorf("Output of SentRicochetPacket was Unexpected: %x", conn.Written)
}
}
func TestRecv(t *testing.T) {
conn := new(MockConn)
conn.MockOutput = []byte{0xDE, 0xAD, 0xBE, 0xEF}
rni := RicochetNetwork{}
buf, err := rni.Recv(conn)
if err != nil || len(buf) != 4 || buf[0] != 0xDE || buf[1] != 0xAD || buf[2] != 0xBE || buf[3] != 0xEF {
t.Errorf("Output of Recv was Unexpected: %x", buf)
}
}
func TestRecvRicochetPacket(t *testing.T) {
conn := new(MockConn)
conn.MockOutput = []byte{00, 0x04, 0x00, 0x01}
rni := RicochetNetwork{}
rp, err := rni.RecvRicochetPackets(conn)
if err != nil {
t.Errorf("error extracting ricochet packets: %v", err)
return
}
if len(rp) != 1 {
t.Errorf("unexpected number of ricochet packets: %d", len(rp))
} else {
if rp[0].Channel != 1 {
t.Errorf("channel number is Unexpected expected 1: %d", rp[0].Channel)
}
if len(rp[0].Data) != 0 {
t.Errorf("expected emptry packet, instead got %x", rp[0].Data)
}
}
}
func TestRecvRicochetPacketInvalid(t *testing.T) {
conn := new(MockConn)
conn.MockOutput = []byte{00, 0x01, 0x00, 0x01}
rni := RicochetNetwork{}
_, err := rni.RecvRicochetPackets(conn)
if err == nil {
t.Errorf("recv should have errored due to invalid packets %v", err)
}
conn.MockOutput = []byte{00, 0x0A, 0x00, 0x01}
_, err = rni.RecvRicochetPackets(conn)
if err == nil {
t.Errorf("recv should have errored due to invalid packets %v", err)
}
}
func TestRecvRicochetPacketLong(t *testing.T) {
conn := new(MockConn)
conn.MockOutput = []byte{0x00, 0x08, 0x00, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF}
rni := RicochetNetwork{}
rp, err := rni.RecvRicochetPackets(conn)
if err != nil {
t.Errorf("error extracting ricochet packets: %v", err)
return
}
if len(rp) != 1 {
t.Errorf("unexpected number of ricochet packets: %d", len(rp))
} else {
if rp[0].Channel != 255 {
t.Errorf("channel number is Unexpected expected 255 got: %d", rp[0].Channel)
}
if len(rp[0].Data) != 4 || rp[0].Data[0] != 0xDE || rp[0].Data[1] != 0xAD || rp[0].Data[2] != 0xBE || rp[0].Data[3] != 0xEF {
t.Errorf("expected 0xDEADBEEF packet, instead got %x", rp[0].Data)
}
}
}
func TestRecvRicochetPacketMultiplex(t *testing.T) {
conn := new(MockConn)
conn.MockOutput = []byte{0x00, 0x04, 0x00, 0x01, 0x00, 0x08, 0x00, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF}
rni := RicochetNetwork{}
rp, err := rni.RecvRicochetPackets(conn)
if err != nil {
t.Errorf("error extracting ricochet packets: %v", err)
return
}
if len(rp) != 2 {
t.Errorf("unexpected number of ricochet packets, expected 2 gt: %d", len(rp))
} else {
if rp[0].Channel != 1 {
t.Errorf("channel number is Unexpected expected 1: %d", rp[0].Channel)
}
if len(rp[0].Data) != 0 {
t.Errorf("expected empty packet, instead got %x", rp[0].Data)
}
if rp[1].Channel != 255 {
t.Errorf("channel number is Unexpected expected 255 got: %d", rp[0].Channel)
}
if len(rp[1].Data) != 4 || rp[1].Data[0] != 0xDE || rp[1].Data[1] != 0xAD || rp[1].Data[2] != 0xBE || rp[1].Data[3] != 0xEF {
t.Errorf("expected 0xDEADBEEF packet, instead got %x", rp[0].Data)
}
}
}

View File

@ -1,4 +1,4 @@
package goricochet
package utils
import (
"errors"
@ -43,5 +43,6 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
if err != nil {
return nil, "", errors.New("Cannot Dial Remote Ricochet Address")
}
//conn.SetDeadline(time.Now().Add(5 * time.Second))
return conn, resolvedHostname, nil
}

19
utils/tor.go Normal file
View File

@ -0,0 +1,19 @@
package utils
import (
"crypto/sha1"
"encoding/base32"
"strings"
)
// GetTorHostname takes a []byte contained a DER-encoded RSA public key
// and returns the first 16 bytes of the base32 encoded sha1 hash of the key.
// This is the onion hostname of the tor service represented by the public key.
func GetTorHostname(publicKeyBytes []byte) string {
h := sha1.New()
h.Write(publicKeyBytes)
sha1bytes := h.Sum(nil)
data := base32.StdEncoding.EncodeToString(sha1bytes)
return strings.ToLower(data[0:16])
}

43
utils/tor_test.go Normal file
View File

@ -0,0 +1,43 @@
package utils
import (
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"testing"
)
const privateKeyData = `-----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-----`
func TestGetTorHostname(t *testing.T) {
block, _ := pem.Decode([]byte(privateKeyData))
privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
// DER Encode the Public Key
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: privateKey.PublicKey.N,
E: privateKey.PublicKey.E,
})
hostname := GetTorHostname(publicKeyBytes)
t.Log(hostname)
if hostname != "kwke2hntvyfqm7dr" {
t.Errorf("Hostname %s does not equal %s", hostname, "kwke2hntvyfqm7dr")
}
}