Brand new API v0.2

new_api-fix_app
Sarah Jamie Lewis 6 years ago
parent 5a720a08d0
commit 5d767174b1

@ -16,6 +16,6 @@ install:
script:
- cd $TRAVIS_BUILD_DIR && ./tests.sh
- cd $TRAVIS_BUILD_DIR && ./testing/tests.sh
- test -z "$GOFMT"
- goveralls -coverprofile=./coverage.out -service travis-ci

15
Godeps/Godeps.json generated

@ -0,0 +1,15 @@
{
"ImportPath": "github.com/s-rah/go-ricochet",
"GoVersion": "go1.7",
"GodepVersion": "v79",
"Deps": [
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "8ee79997227bf9b34611aee7946ae64735e6fd93"
},
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "60c41d1de8da134c05b7b40154a9a82bf5b7edb9"
}
]
}

5
Godeps/Readme generated

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

@ -25,8 +25,8 @@ SOFTWARE.
--------------------------------------------------------------------------------
Autogenerated protobuf code was generated using the proto file from Ricochet.
They are covered under the following license.
Autogenerated protobuf code was generated using the proto file from Ricochet.
They are covered under the following license.
Ricochet - https://ricochet.im/
Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net>
@ -61,10 +61,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
The go-ricochet logo is based on an image by Olga Shalakhina
<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.

@ -1,7 +1,5 @@
# GoRicochet [![Build Status](https://travis-ci.org/s-rah/go-ricochet.svg?branch=master)](https://travis-ci.org/s-rah/go-ricochet) [![Go Report Card](https://goreportcard.com/badge/github.com/s-rah/go-ricochet)](https://goreportcard.com/report/github.com/s-rah/go-ricochet) [![Coverage Status](https://coveralls.io/repos/github/s-rah/go-ricochet/badge.svg?branch=master)](https://coveralls.io/github/s-rah/go-ricochet?branch=master)
![GoRicochet](logo.png)
GoRicochet is an experimental implementation of the [Ricochet Protocol](https://ricochet.im)
in Go.

@ -0,0 +1,36 @@
package application
import (
"errors"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/connection"
)
// RicochetApplication bundles many useful constructs that are
// likely standard in a ricochet application
type RicochetApplication struct {
connection *Connection
}
// NewRicochetApplication ...
func NewRicochetApplication(connection *Connection) *RicochetApplication {
ra := new(RicochetApplication)
ra.connection = connection
return ra
}
// SendMessage ...
func (ra *RicochetApplication) SendChatMessage(message []string) error {
return ra.connection.Do(func() error {
channel := ra.connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
chatchannel, ok := (*channel.Handler).(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
} else {
return errors.New("")
}
return nil
})
}

@ -1,57 +0,0 @@
package goricochet
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
)
// AuthenticationHandler manages the state required for the AuthHiddenService
// authentication scheme for ricochet.
type AuthenticationHandler struct {
clientCookie [16]byte
serverCookie [16]byte
}
// AddClientCookie adds a client cookie to the state.
func (ah *AuthenticationHandler) AddClientCookie(cookie []byte) {
copy(ah.clientCookie[:], cookie[:16])
}
// AddServerCookie adds a server cookie to the state.
func (ah *AuthenticationHandler) AddServerCookie(cookie []byte) {
copy(ah.serverCookie[:], cookie[:16])
}
// GenRandom generates a random 16byte cookie string.
func (ah *AuthenticationHandler) GenRandom() [16]byte {
var cookie [16]byte
io.ReadFull(rand.Reader, cookie[:])
return cookie
}
// GenClientCookie generates and adds a client cookie to the state.
func (ah *AuthenticationHandler) GenClientCookie() [16]byte {
ah.clientCookie = ah.GenRandom()
return ah.clientCookie
}
// GenServerCookie generates and adds a server cookie to the state.
func (ah *AuthenticationHandler) GenServerCookie() [16]byte {
ah.serverCookie = ah.GenRandom()
return ah.serverCookie
}
// GenChallenge constructs the challenge parameter for the AuthHiddenService session.
// The challenge is the a Sha256HMAC(clientHostname+serverHostname, key=clientCookie+serverCookie)
func (ah *AuthenticationHandler) GenChallenge(clientHostname string, serverHostname string) []byte {
key := make([]byte, 32)
copy(key[0:16], ah.clientCookie[:])
copy(key[16:], ah.serverCookie[:])
value := []byte(clientHostname + serverHostname)
mac := hmac.New(sha256.New, key)
mac.Write(value)
hmac := mac.Sum(nil)
return hmac
}

@ -1,32 +0,0 @@
package goricochet
import "testing"
import "bytes"
func TestGenChallenge(t *testing.T) {
authHandler := new(AuthenticationHandler)
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
challenge := authHandler.GenChallenge("test.onion", "notareal.onion")
expectedChallenge := []byte{0xf5, 0xdb, 0xfd, 0xf0, 0x3d, 0x94, 0x14, 0xf1, 0x4b, 0x37, 0x93, 0xe2, 0xa5, 0x11, 0x4a, 0x98, 0x31, 0x90, 0xea, 0xb8, 0x95, 0x7a, 0x2e, 0xaa, 0xd0, 0xd2, 0x0c, 0x74, 0x95, 0xba, 0xab, 0x73}
t.Log(challenge, expectedChallenge)
if bytes.Compare(challenge[:], expectedChallenge[:]) != 0 {
t.Errorf("AuthenticationHandler Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
}
}
func TestGenClientCookie(t *testing.T) {
authHandler := new(AuthenticationHandler)
clientCookie := authHandler.GenClientCookie()
if clientCookie != authHandler.clientCookie {
t.Errorf("AuthenticationHandler Client Cookies are Different %x %x", clientCookie, authHandler.clientCookie)
}
}
func TestGenServerCookie(t *testing.T) {
authHandler := new(AuthenticationHandler)
serverCookie := authHandler.GenServerCookie()
if serverCookie != authHandler.serverCookie {
t.Errorf("AuthenticationHandler Server Cookies are Different %x %x", serverCookie, authHandler.serverCookie)
}
}

@ -0,0 +1,34 @@
package channels
// Direction indicated whether we or the remote peer opened the channel
type Direction int
const (
// Inbound indcates the channel was opened by the remote peer
Inbound Direction = iota
// Outbound indicated the channel was opened by us
Outbound
)
// AuthChannelResult captures the result of an authentication flow
type AuthChannelResult struct {
Accepted bool
IsKnownContact bool
}
// Channel holds the state of a channel on an open connection
type Channel struct {
ID int32
Type string
Direction Direction
Handler *Handler
Pending bool
ServerHostname string
ClientHostname string
// Functions for updating the underlying Connection
SendMessage func([]byte)
CloseChannel func()
DelegateAuthorization func()
}

@ -0,0 +1,146 @@
package channels
import (
"crypto/rand"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/chat"
"github.com/s-rah/go-ricochet/wire/control"
"math"
"math/big"
"time"
)
// ChatChannel implements the ChannelHandler interface for a channel of
// type "im.ricochet.chat". The channel may be inbound or outbound.
//
// ChatChannel implements protocol-level sanity and state validation, but
// does not handle or acknowledge chat messages. The application must provide
// a ChatChannelHandler implementation to handle chat events.
type ChatChannel struct {
// Methods of Handler are called for chat events on this channel
Handler ChatChannelHandler
channel *Channel
lastMessageID uint32
}
// ChatChannelHandler is implemented by an application type to receive
// events from a ChatChannel.
//
// Note that ChatChannelHandler is composable with other interfaces, including
// ConnectionHandler; there is no need to use a distinct type as a
// ChatChannelHandler.
type ChatChannelHandler interface {
// ChatMessage is called when a chat message is received. Return true to acknowledge
// the message successfully, and false to NACK and refuse the message.
ChatMessage(messageID uint32, when time.Time, message string) bool
// ChatMessageAck is called when an acknowledgement of a sent message is received.
ChatMessageAck(messageID uint32)
}
// SendMessage sends a given message using this channe
func (cc *ChatChannel) SendMessage(message string) {
messageBuilder := new(utils.MessageBuilder)
//TODO Implement Chat Number
data := messageBuilder.ChatMessage(message, cc.lastMessageID)
cc.lastMessageID++
cc.channel.SendMessage(data)
}
// Acknowledge indicates the given messageID was received
func (cc *ChatChannel) Acknowledge(messageID uint32) {
messageBuilder := new(utils.MessageBuilder)
cc.channel.SendMessage(messageBuilder.AckChatMessage(messageID))
}
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
func (cc *ChatChannel) Type() string {
return "im.ricochet.chat"
}
// Closed is called when the channel is closed for any reason.
func (cc *ChatChannel) Closed(err error) {
}
// OnlyClientCanOpen - for chat channels any side can open
func (cc *ChatChannel) OnlyClientCanOpen() bool {
return false
}
// Singleton - for chat channels there can only be one instance per direction
func (cc *ChatChannel) Singleton() bool {
return true
}
// Bidirectional - for chat channels are not bidrectional
func (cc *ChatChannel) Bidirectional() bool {
return false
}
// RequiresAuthentication - chat channels require hidden service auth
func (cc *ChatChannel) RequiresAuthentication() string {
return "im.ricochet.auth.hidden-service"
}
// OpenInbound is the first method called for an inbound channel request.
// If an error is returned, the channel is rejected. If a RawMessage is
// returned, it will be sent as the ChannelResult message.
func (cc *ChatChannel) OpenInbound(channel *Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) {
cc.channel = channel
id, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32))
if err != nil {
return nil, err
}
cc.lastMessageID = uint32(id.Uint64())
cc.channel.Pending = false
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.AckOpenChannel(channel.ID), nil
}
// OpenOutbound is the first method called for an outbound channel request.
// If an error is returned, the channel is not opened. If a RawMessage is
// returned, it will be sent as the OpenChannel message.
func (cc *ChatChannel) OpenOutbound(channel *Channel) ([]byte, error) {
cc.channel = channel
id, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32))
if err != nil {
return nil, err
}
cc.lastMessageID = uint32(id.Uint64())
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.OpenChannel(channel.ID, cc.Type()), nil
}
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. If `err` is non-nil, the channel was
// rejected and Closed will be called immediately afterwards. `raw`
// contains the raw protocol message including any extension data.
func (cc *ChatChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
if err == nil {
if crm.GetOpened() {
cc.channel.Pending = false
}
}
}
// Packet is called for each raw packet received on this channel.
func (cc *ChatChannel) Packet(data []byte) {
if !cc.channel.Pending {
res := new(Protocol_Data_Chat.Packet)
err := proto.Unmarshal(data, res)
if err == nil {
if res.GetChatMessage() != nil {
ack := cc.Handler.ChatMessage(res.GetChatMessage().GetMessageId(), time.Now(), res.GetChatMessage().GetMessageText())
if ack {
cc.Acknowledge(res.GetChatMessage().GetMessageId())
} else {
//XXX
}
} else if res.GetChatAcknowledge() != nil {
cc.Handler.ChatMessageAck(res.GetChatMessage().GetMessageId())
}
// XXX?
}
}
}

@ -0,0 +1,124 @@
package channels
import (
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/chat"
"github.com/s-rah/go-ricochet/wire/control"
"testing"
"time"
)
func TestChatChannelOptions(t *testing.T) {
chatChannel := new(ChatChannel)
if chatChannel.Type() != "im.ricochet.chat" {
t.Errorf("ChatChannel has wrong type %s", chatChannel.Type())
}
if chatChannel.OnlyClientCanOpen() {
t.Errorf("ChatChannel should be able to be opened by everyone")
}
if !chatChannel.Singleton() {
t.Errorf("ChatChannel should be a Singelton")
}
if chatChannel.Bidirectional() {
t.Errorf("ChatChannel should not be bidirectional")
}
if chatChannel.RequiresAuthentication() != "im.ricochet.auth.hidden-service" {
t.Errorf("ChatChannel should require im.ricochet.auth.hidden-service. Instead requires: %s", chatChannel.RequiresAuthentication())
}
}
func TestChatChannelOpenInbound(t *testing.T) {
messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.OpenChannel(2, "im.ricochet.chat")
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
opm := res.GetOpenChannel()
chatChannel := new(ChatChannel)
channel := Channel{ID: 1}
response, err := chatChannel.OpenInbound(&channel, opm)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
} else {
t.Errorf("Error while parsing chatchannel openinbound output: %v", err)
}
}
func TestChatChannelOpenOutbound(t *testing.T) {
chatChannel := new(ChatChannel)
channel := Channel{ID: 1}
response, err := chatChannel.OpenOutbound(&channel)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetOpenChannel() != nil {
// XXX
} else {
t.Errorf("ChatChannel OpenOutbound was not an OpenChannelRequest %v", err)
}
} else {
t.Errorf("Error while parsing openputput output: %v", err)
}
}
type TestChatChannelHandler struct {
}
func (tcch *TestChatChannelHandler) ChatMessage(messageID uint32, when time.Time, message string) bool {
return true
}
func (tcch *TestChatChannelHandler) ChatMessageAck(messageID uint32) {
}
func TestChatChannelOperations(t *testing.T) {
// We test OpenOutboundElsewhere
chatChannel := new(ChatChannel)
chatChannel.Handler = new(TestChatChannelHandler)
channel := Channel{ID: 5}
channel.SendMessage = func(data []byte) {
res := new(Protocol_Data_Chat.Packet)
err := proto.Unmarshal(data, res)
if res.GetChatMessage() != nil {
if err == nil {
if res.GetChatMessage().GetMessageId() != 0 {
t.Log("Got Message ID:", res.GetChatMessage().GetMessageId())
return
}
t.Errorf("message id was 0 should be random")
return
}
t.Errorf("error sending chat message: %v", err)
}
}
chatChannel.OpenOutbound(&channel)
messageBuilder := new(utils.MessageBuilder)
ack := messageBuilder.AckOpenChannel(5)
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ack[:], res)
cr := res.GetChannelResult()
chatChannel.OpenOutboundResult(nil, cr)
if channel.Pending {
t.Errorf("After Successful Result ChatChannel Is Still Pending")
}
chat := messageBuilder.ChatMessage("message text", 0)
chatChannel.Packet(chat)
chatChannel.SendMessage("hello")
}

@ -0,0 +1,148 @@
package channels
import (
"errors"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/contact"
"github.com/s-rah/go-ricochet/wire/control"
)
// ContactRequestChannel implements the ChannelHandler interface for a channel of
// type "im.ricochet.contact.request". The channel may be inbound or outbound.
// a ContactRequestChannelHandler implementation to handle chat events.
type ContactRequestChannel struct {
// Methods of Handler are called for chat events on this channel
Handler ContactRequestChannelHandler
channel *Channel
}
// ContactRequestChannelHandler is implemented by an application type to receive
// events from a ContactRequestChannel.
//
// Note that ContactRequestChannelHandler is composable with other interfaces, including
// ConnectionHandler; there is no need to use a distinct type as a
// ContactRequestChannelHandler.
type ContactRequestChannelHandler interface {
GetContactDetails() (string, string)
ContactRequest(name string, message string) string
ContactRequestRejected()
ContactRequestAccepted()
ContactRequestError()
}
// OnlyClientCanOpen - only clients can open contact requests
func (crc *ContactRequestChannel) OnlyClientCanOpen() bool {
return true
}
// Singleton - only one contact request can be opened per side
func (crc *ContactRequestChannel) Singleton() bool {
return true
}
// Bidirectional - only clients can send messages
func (crc *ContactRequestChannel) Bidirectional() bool {
return false
}
// RequiresAuthentication - contact requests require hidden service auth
func (crc *ContactRequestChannel) RequiresAuthentication() string {
return "im.ricochet.auth.hidden-service"
}
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
func (crc *ContactRequestChannel) Type() string {
return "im.ricochet.contact.request"
}
// Closed is called when the channel is closed for any reason.
func (crc *ContactRequestChannel) Closed(err error) {
}
// OpenInbound is the first method called for an inbound channel request.
// If an error is returned, the channel is rejected. If a RawMessage is
// returned, it will be sent as the ChannelResult message.
func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
crc.channel = channel
contactRequestI, err := proto.GetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest)
if err == nil {
contactRequest, check := contactRequestI.(*Protocol_Data_ContactRequest.ContactRequest)
if check {
if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) {
// Violation of the Protocol
return nil, errors.New("invalid nickname")
}
if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) {
// Violation of the Protocol
return nil, errors.New("invalid message")
}
result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText())
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil
}
}
return nil, errors.New("could not parse contact request extension")
}
// OpenOutbound is the first method called for an outbound channel request.
// If an error is returned, the channel is not opened. If a RawMessage is
// returned, it will be sent as the OpenChannel message.
func (crc *ContactRequestChannel) OpenOutbound(channel *Channel) ([]byte, error) {
crc.channel = channel
name, message := crc.Handler.GetContactDetails()
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.OpenContactRequestChannel(channel.ID, name, message), nil
}
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. If `err` is non-nil, the channel was
// rejected and Closed will be called immediately afterwards. `raw`
// contains the raw protocol message including any extension data.
func (crc *ContactRequestChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
if err == nil {
if crm.GetOpened() {
responseI, err := proto.GetExtension(crm, Protocol_Data_ContactRequest.E_Response)
if err == nil {
response, check := responseI.(*Protocol_Data_ContactRequest.Response)
if check {
crc.handleStatus(response.GetStatus().String())
return
}
}
}
}
crc.channel.SendMessage([]byte{})
}
func (crc *ContactRequestChannel) handleStatus(status string) {
switch status {
case "Accepted":
crc.Handler.ContactRequestAccepted()
case "Pending":
break
case "Rejected":
crc.Handler.ContactRequestRejected()
break
case "Error":
crc.Handler.ContactRequestError()
break
}
}
// Packet is called for each raw packet received on this channel.
func (crc *ContactRequestChannel) Packet(data []byte) {
if !crc.channel.Pending {
response := new(Protocol_Data_ContactRequest.Response)
err := proto.Unmarshal(data, response)
if err == nil {
crc.handleStatus(response.GetStatus().String())
return
}
}
crc.channel.SendMessage([]byte{})
}

@ -0,0 +1,226 @@
package channels
import (
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/contact"
"github.com/s-rah/go-ricochet/wire/control"
"testing"
)
type TestContactRequestHandler struct {
Received bool
}
func (tcrh *TestContactRequestHandler) GetContactDetails() (string, string) {
return "", ""
}
func (tcrh *TestContactRequestHandler) ContactRequest(name string, message string) string {
if name == "test_nickname" && message == "test_message" {
tcrh.Received = true
}
return "Pending"
}
func (tcrh *TestContactRequestHandler) ContactRequestRejected() {
}
func (tcrh *TestContactRequestHandler) ContactRequestAccepted() {
}
func (tcrh *TestContactRequestHandler) ContactRequestError() {
}
func TestContactRequestOptions(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
if contactRequestChannel.Type() != "im.ricochet.contact.request" {
t.Errorf("ContactRequestChannel has wrong type %s", contactRequestChannel.Type())
}
if !contactRequestChannel.OnlyClientCanOpen() {
t.Errorf("ContactRequestChannel Should be Client Open Only")
}
if !contactRequestChannel.Singleton() {
t.Errorf("ContactRequestChannel Should be a Singelton")
}
if contactRequestChannel.Bidirectional() {
t.Errorf("ContactRequestChannel Should not be bidirectional")
}
if contactRequestChannel.RequiresAuthentication() != "im.ricochet.auth.hidden-service" {
t.Errorf("ContactRequestChannel should requires im.ricochet.auth.hidden-service Authentication. Instead defines: %s", contactRequestChannel.RequiresAuthentication())
}
}
func TestContactRequestOpenOutbound(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
response, err := contactRequestChannel.OpenOutbound(&channel)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetOpenChannel() != nil {
// XXX
} else {
t.Errorf("ContactReuqest OpenOutbound was not an OpenChannelRequest %v", err)
}
} else {
t.Errorf("Error while parsing openputput output: %v", err)
}
}
func TestContactRequestOpenOutboundResult(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
contactRequestChannel.OpenOutbound(&channel)
messageBuilder := new(utils.MessageBuilder)
ack := messageBuilder.ReplyToContactRequestOnResponse(1, "Accepted")
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ack[:], res)
cr := res.GetChannelResult()
contactRequestChannel.OpenOutboundResult(nil, cr)
}
func TestContactRequestOpenInbound(t *testing.T) {
opm := BuildOpenChannel("test_nickname", "test_message")
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
response, err := contactRequestChannel.OpenInbound(&channel, opm)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
responseI, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_ContactRequest.E_Response)
if err == nil {
response, check := responseI.(*Protocol_Data_ContactRequest.Response)
if check {
if response.GetStatus().String() != "Pending" {
t.Errorf("Contact Request Response should have been Pending, but instead was: %v", response.GetStatus().String())
}
} else {
t.Errorf("Error while parsing openinbound output: %v", err)
}
} else {
t.Errorf("Error while parsing openinbound output: %v", err)
}
} else {
t.Errorf("Error while parsing openinbound output: %v", err)
}
if !handler.Received {
t.Errorf("Contact Request was not received by Handler")
}
}
func TestContactRequestPacket(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
contactRequestChannel.OpenOutbound(&channel)
messageBuilder := new(utils.MessageBuilder)
ack := messageBuilder.ReplyToContactRequestOnResponse(1, "Pending")
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ack[:], res)
cr := res.GetChannelResult()
contactRequestChannel.OpenOutboundResult(nil, cr)
ackp := messageBuilder.ReplyToContactRequest(1, "Accepted")
contactRequestChannel.Packet(ackp)
}
func TestContactRequestRejected(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
contactRequestChannel.OpenOutbound(&channel)
messageBuilder := new(utils.MessageBuilder)
ack := messageBuilder.ReplyToContactRequestOnResponse(1, "Pending")
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ack[:], res)
cr := res.GetChannelResult()
contactRequestChannel.OpenOutboundResult(nil, cr)
ackp := messageBuilder.ReplyToContactRequest(1, "Rejected")
contactRequestChannel.Packet(ackp)
}
func TestContactRequestError(t *testing.T) {
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
contactRequestChannel.OpenOutbound(&channel)
messageBuilder := new(utils.MessageBuilder)
ack := messageBuilder.ReplyToContactRequestOnResponse(1, "Pending")
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ack[:], res)
cr := res.GetChannelResult()
contactRequestChannel.OpenOutboundResult(nil, cr)
ackp := messageBuilder.ReplyToContactRequest(1, "Error")
contactRequestChannel.Packet(ackp)
}
func BuildOpenChannel(nickname string, message string) *Protocol_Data_Control.OpenChannel {
// Construct the Open Authentication Channel Message
messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.OpenContactRequestChannel(1, nickname, message)
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
return res.GetOpenChannel()
}
func TestInvalidNickname(t *testing.T) {
opm := BuildOpenChannel("this nickname is far too long at well over the limit of 30 characters", "test_message")
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
_, err := contactRequestChannel.OpenInbound(&channel, opm)
if err == nil {
t.Errorf("Open Inbound should have failed because of invalid nickname")
}
}
func TestInvalidMessage(t *testing.T) {
var message string
for i := 0; i < 2001; i++ {
message += "a"
}
opm := BuildOpenChannel("test_nickname", message)
contactRequestChannel := new(ContactRequestChannel)
handler := new(TestContactRequestHandler)
contactRequestChannel.Handler = handler
channel := Channel{ID: 1}
_, err := contactRequestChannel.OpenInbound(&channel, opm)
if err == nil {
t.Errorf("Open Inbound should have failed because of invalid message")
}
}

@ -0,0 +1,51 @@
package channels
import (
"github.com/s-rah/go-ricochet/wire/control"
)
// Handler reacts to low-level events on a protocol channel. There
// should be a unique instance of a ChannelHandler type per channel.
//
// Applications generally don't need to implement ChannelHandler directly;
// instead, use the built-in implementations for common channel types, and
// their individual callback interfaces. ChannelHandler is useful when
// implementing new channel types, or modifying low level default behavior.
type Handler interface {
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
Type() string
// Closed is called when the channel is closed for any reason.
Closed(err error)
// OnlyClientCanOpen indicates if only a client can open a given channel
OnlyClientCanOpen() bool
// Singleton indicates if a channel can only have one instance per direction
Singleton() bool
// Bidirectional indicates if messages can be send by either side
Bidirectional() bool
// RequiresAuthentication describes what authentication is needed for the channel
RequiresAuthentication() string
// OpenInbound is the first method called for an inbound channel request.
// If an error is returned, the channel is rejected. If a RawMessage is
// returned, it will be sent as the ChannelResult message.
OpenInbound(channel *Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error)
// OpenOutbound is the first method called for an outbound channel request.
// If an error is returned, the channel is not opened. If a RawMessage is
// returned, it will be sent as the OpenChannel message.
OpenOutbound(channel *Channel) ([]byte, error)
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. If `err` is non-nil, the channel was
// rejected and Closed will be called immediately afterwards. `raw`
// contains the raw protocol message including any extension data.
OpenOutboundResult(err error, raw *Protocol_Data_Control.ChannelResult)
// Packet is called for each raw packet received on this channel.
Packet(data []byte)
}

@ -0,0 +1,253 @@
package channels
import (
"crypto"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/asn1"
"errors"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/auth"
"github.com/s-rah/go-ricochet/wire/control"
"io"
"log"
)
// HiddenServiceAuthChannel wraps implementation o fim.ricochet.auth.hidden-service"
type HiddenServiceAuthChannel struct {
// Methods of Handler are called for events on this channel
Handler AuthChannelHandler
// PrivateKey must be set for client-side authentication channels
PrivateKey *rsa.PrivateKey
// Server Hostname must be set for client-side authentication channels
ServerHostname string
// Internal state
clientCookie, serverCookie [16]byte
channel *Channel
}
// AuthChannelHandler ...
type AuthChannelHandler interface {
// Client
ClientAuthResult(accepted bool, isKnownContact bool)
// Server
ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
ServerAuthInvalid(err error)
}
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
func (ah *HiddenServiceAuthChannel) Type() string {
return "im.ricochet.auth.hidden-service"
}
// Singleton Returns whether or not the given channel type is a singleton
func (ah *HiddenServiceAuthChannel) Singleton() bool {
return true
}
// OnlyClientCanOpen ...
func (ah *HiddenServiceAuthChannel) OnlyClientCanOpen() bool {
return true
}
// Bidirectional Returns whether or not the given channel allows anyone to send messages
func (ah *HiddenServiceAuthChannel) Bidirectional() bool {
return false
}
// RequiresAuthentication Returns whether or not the given channel type requires authentication
func (ah *HiddenServiceAuthChannel) RequiresAuthentication() string {
return "none"
}
// Closed is called when the channel is closed for any reason.
func (ah *HiddenServiceAuthChannel) Closed(err error) {
}
// OpenInbound is the first method called for an inbound channel request.
// If an error is returned, the channel is rejected. If a RawMessage is
// returned, it will be sent as the ChannelResult message.
// Remote -> [Open Authentication Channel] -> Local
func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
ah.channel = channel
clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie)
if len(clientCookie.([]byte)[:]) != 16 {
// reutrn without opening channel.
return nil, errors.New("invalid client cookie")
}
ah.AddClientCookie(clientCookie.([]byte)[:])
messageBuilder := new(utils.MessageBuilder)
channel.Pending = false
return messageBuilder.ConfirmAuthChannel(ah.channel.ID, ah.GenServerCookie()), nil
}
// OpenOutbound is the first method called for an outbound channel request.
// If an error is returned, the channel is not opened. If a RawMessage is
// returned, it will be sent as the OpenChannel message.
// Local -> [Open Authentication Channel] -> Remote
func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) {
ah.channel = channel
messageBuilder := new(utils.MessageBuilder)
return messageBuilder.OpenAuthenticationChannel(ah.channel.ID, ah.GenClientCookie()), nil
}
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. If `err` is non-nil, the channel was
// rejected and Closed will be called immediately afterwards. `raw`
// contains the raw protocol message including any extension data.
// Input: Remote -> [ChannelResult] -> {Client}
// Output: {Client} -> [Proof] -> Remote
func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
if err == nil {
if crm.GetOpened() {
serverCookie, _ := proto.GetExtension(crm, Protocol_Data_AuthHiddenService.E_ServerCookie)
if len(serverCookie.([]byte)[:]) != 16 {
ah.channel.SendMessage([]byte{})
return
}
ah.AddServerCookie(serverCookie.([]byte)[:])
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: ah.PrivateKey.PublicKey.N,
E: ah.PrivateKey.PublicKey.E,
})
clientHostname := utils.GetTorHostname(publicKeyBytes)
challenge := ah.GenChallenge(clientHostname, ah.ServerHostname)
signature, err := rsa.SignPKCS1v15(nil, ah.PrivateKey, crypto.SHA256, challenge)
if err != nil {
ah.channel.SendMessage([]byte{})
return
}
messageBuilder := new(utils.MessageBuilder)
proof := messageBuilder.Proof(publicKeyBytes, signature)
ah.channel.SendMessage(proof)
}
}
}
// Packet is called for each raw packet received on this channel.
// Input: Remote -> [Proof] -> Client
// OR
// Input: Remote -> [Result] -> Client
func (ah *HiddenServiceAuthChannel) Packet(data []byte) {
res := new(Protocol_Data_AuthHiddenService.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
ah.channel.CloseChannel()
return
}
if res.GetProof() != nil && ah.channel.Direction == Inbound {
provisionalClientHostname := utils.GetTorHostname(res.GetProof().GetPublicKey())
publicKeyBytes, err := asn1.Marshal(rsa.PublicKey{
N: ah.PrivateKey.PublicKey.N,
E: ah.PrivateKey.PublicKey.E,
})
if err != nil {
ah.Handler.ServerAuthInvalid(err)
ah.channel.SendMessage([]byte{})
return
}
serverHostname := utils.GetTorHostname(publicKeyBytes)
publicKey := rsa.PublicKey{}
_, err = asn1.Unmarshal(res.GetProof().GetPublicKey(), &publicKey)
if err != nil {
ah.Handler.ServerAuthInvalid(err)
ah.channel.SendMessage([]byte{})
return
}
challenge := ah.GenChallenge(provisionalClientHostname, serverHostname)
err = rsa.VerifyPKCS1v15(&publicKey, crypto.SHA256, challenge[:], res.GetProof().GetSignature())
if err == nil {
// Signature is Good
accepted, isKnownContact := ah.Handler.ServerAuthValid(provisionalClientHostname, publicKey)
// Send Result
messageBuilder := new(utils.MessageBuilder)
result := messageBuilder.AuthResult(accepted, isKnownContact)
ah.channel.DelegateAuthorization()
ah.channel.SendMessage(result)
} else {
// Auth Failed
messageBuilder := new(utils.MessageBuilder)
result := messageBuilder.AuthResult(false, false)
ah.channel.SendMessage(result)
ah.Handler.ServerAuthInvalid(err)
}
} else if res.GetResult() != nil && ah.channel.Direction == Outbound {
ah.Handler.ClientAuthResult(res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact())
if res.GetResult().GetAccepted() {
ah.channel.DelegateAuthorization()
}
}
// Any other combination of packets is completely invalid
// Fail the Authorization right here.
ah.channel.CloseChannel()
}
// AddClientCookie adds a client cookie to the state.
func (ah *HiddenServiceAuthChannel) AddClientCookie(cookie []byte) {
copy(ah.clientCookie[:], cookie[:16])
}
// AddServerCookie adds a server cookie to the state.
func (ah *HiddenServiceAuthChannel) AddServerCookie(cookie []byte) {
copy(ah.serverCookie[:], cookie[:16])
}
// GenRandom generates a random 16byte cookie string.
func (ah *HiddenServiceAuthChannel) GenRandom() [16]byte {
var cookie [16]byte
io.ReadFull(rand.Reader, cookie[:])
return cookie
}
// GenClientCookie generates and adds a client cookie to the state.
func (ah *HiddenServiceAuthChannel) GenClientCookie() [16]byte {
ah.clientCookie = ah.GenRandom()
return ah.clientCookie
}
// GenServerCookie generates and adds a server cookie to the state.
func (ah *HiddenServiceAuthChannel) GenServerCookie() [16]byte {
ah.serverCookie = ah.GenRandom()
return ah.serverCookie
}
// GenChallenge constructs the challenge parameter for the AuthHiddenService session.
// The challenge is the a Sha256HMAC(clientHostname+serverHostname, key=clientCookie+serverCookie)
func (ah *HiddenServiceAuthChannel) GenChallenge(clientHostname string, serverHostname string) []byte {
key := make([]byte, 32)
copy(key[0:16], ah.clientCookie[:])
copy(key[16:], ah.serverCookie[:])
log.Printf("CHALLENGE: %s %s %v", clientHostname, serverHostname, key)
value := []byte(clientHostname + serverHostname)
mac := hmac.New(sha256.New, key)
mac.Write(value)
hmac := mac.Sum(nil)
return hmac
}

@ -0,0 +1,163 @@
package channels
import (
"bytes"
"crypto/rsa"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/control"
"testing"
)
func TestGenChallenge(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
challenge := authHandler.GenChallenge("test.onion", "notareal.onion")
expectedChallenge := []byte{0xf5, 0xdb, 0xfd, 0xf0, 0x3d, 0x94, 0x14, 0xf1, 0x4b, 0x37, 0x93, 0xe2, 0xa5, 0x11, 0x4a, 0x98, 0x31, 0x90, 0xea, 0xb8, 0x95, 0x7a, 0x2e, 0xaa, 0xd0, 0xd2, 0x0c, 0x74, 0x95, 0xba, 0xab, 0x73}
t.Log(challenge, expectedChallenge)
if bytes.Compare(challenge[:], expectedChallenge[:]) != 0 {
t.Errorf("HiddenServiceAuthChannel Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
}
}
func TestGenClientCookie(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
clientCookie := authHandler.GenClientCookie()
if clientCookie != authHandler.clientCookie {
t.Errorf("HiddenServiceAuthChannel Client Cookies are Different %x %x", clientCookie, authHandler.clientCookie)
}
}
func TestGenServerCookie(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
serverCookie := authHandler.GenServerCookie()
if serverCookie != authHandler.serverCookie {
t.Errorf("HiddenServiceAuthChannel Server Cookies are Different %x %x", serverCookie, authHandler.serverCookie)
}
}
func TestHiddenServiceAuthChannelOptions(t *testing.T) {
hiddenServiceAuthChannel := new(HiddenServiceAuthChannel)
if hiddenServiceAuthChannel.Type() != "im.ricochet.auth.hidden-service" {
t.Errorf("AuthHiddenService has wrong type %s", hiddenServiceAuthChannel.Type())
}
if !hiddenServiceAuthChannel.OnlyClientCanOpen() {
t.Errorf("AuthHiddenService Should be Client Open Only")
}
if !hiddenServiceAuthChannel.Singleton() {
t.Errorf("AuthHiddenService Should be a Singelton")
}
if hiddenServiceAuthChannel.Bidirectional() {
t.Errorf("AuthHiddenService Should not be bidirectional")
}
if hiddenServiceAuthChannel.RequiresAuthentication() != "none" {
t.Errorf("AuthHiddenService should require no authorization. Instead requires: %s", hiddenServiceAuthChannel.RequiresAuthentication())
}
}
func GetOpenAuthenticationChannelMessage() *Protocol_Data_Control.OpenChannel {
// Construct the Open Authentication Channel Message
messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.OpenAuthenticationChannel(1, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
return res.GetOpenChannel()
}
func TestAuthenticationOpenInbound(t *testing.T) {
opm := GetOpenAuthenticationChannelMessage()
authHandler := new(HiddenServiceAuthChannel)
channel := Channel{ID: 1}
response, err := authHandler.OpenInbound(&channel, opm)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetChannelResult() == nil || !res.GetChannelResult().GetOpened() {
t.Errorf("Response not a Open Channel Result %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err)
}
}
func TestAuthenticationOpenOutbound(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
channel := Channel{ID: 1}
response, err := authHandler.OpenOutbound(&channel)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetOpenChannel() == nil {
t.Errorf("Open Channel Packet not included %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err)
}
}
type SimpleTestAuthHandler struct {
}
// Client
func (stah *SimpleTestAuthHandler) ClientAuthResult(accepted bool, isKnownContact bool) {
}
// Server
func (stah *SimpleTestAuthHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
return true, true
}
func (stah *SimpleTestAuthHandler) ServerAuthInvalid(err error) {
}
func TestAuthenticationOpenOutboundResult(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
authHandlerA := new(HiddenServiceAuthChannel)
authHandlerB := new(HiddenServiceAuthChannel)
simpleTestAuthHandler := new(SimpleTestAuthHandler)
authHandlerA.ServerHostname = "kwke2hntvyfqm7dr"
authHandlerA.PrivateKey = privateKey
authHandlerA.Handler = simpleTestAuthHandler
channelA := Channel{ID: 1, Direction: Outbound}
channelA.SendMessage = func(message []byte) {
authHandlerB.Packet(message)
}
channelA.DelegateAuthorization = func() {}
channelA.CloseChannel = func() {}
response, _ := authHandlerA.OpenOutbound(&channelA)
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
authHandlerB.ServerHostname = "kwke2hntvyfqm7dr"
authHandlerB.PrivateKey = privateKey
authHandlerB.Handler = simpleTestAuthHandler
channelB := Channel{ID: 1, Direction: Inbound}
channelB.SendMessage = func(message []byte) {
authHandlerA.Packet(message)
}
channelB.DelegateAuthorization = func() {}
channelB.CloseChannel = func() {}
response, _ = authHandlerB.OpenInbound(&channelB, res.GetOpenChannel())
res = new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
authHandlerA.OpenOutboundResult(nil, res.GetChannelResult())
}

@ -0,0 +1,94 @@
package connection
import (
"crypto/rsa"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
"log"
)
// AutoConnectionHandler implements the ConnectionHandler interface on behalf of
// the provided application type by automatically providing support for any
// built-in channel type whose high level interface is implemented by the
// application. For example, if the application's type implements the
// ChatChannelHandler interface, `im.ricochet.chat` will be available to the peer.
//
// The application handler can be any other type. To override or augment any of
// AutoConnectionHandler's behavior (such as adding new channel types, or reacting
// to connection close events), this type can be embedded in the type that it serves.
type AutoConnectionHandler struct {
handlerMap map[string]func() channels.Handler
connection *Connection
authResultChannel chan channels.AuthChannelResult
sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
}
// Init ...
func (ach *AutoConnectionHandler) Init(privateKey *rsa.PrivateKey, serverHostname string) {
ach.handlerMap = make(map[string]func() channels.Handler)
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler {
hsau := new(channels.HiddenServiceAuthChannel)
hsau.PrivateKey = privateKey
hsau.Handler = ach
hsau.ServerHostname = serverHostname
return hsau
})
ach.authResultChannel = make(chan channels.AuthChannelResult)
}
// SetServerAuthHandler ...
func (ach *AutoConnectionHandler) SetServerAuthHandler(sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) {
ach.sach = sach
}
// OnReady ...
func (ach *AutoConnectionHandler) OnReady(oc *Connection) {
ach.connection = oc
}
// OnClosed is called when the OpenConnection has closed for any reason.
func (ach *AutoConnectionHandler) OnClosed(err error) {
}
// WaitForAuthenticationEvent ...
func (ach *AutoConnectionHandler) WaitForAuthenticationEvent() channels.AuthChannelResult {
return <-ach.authResultChannel
}
// ClientAuthResult ...
func (ach *AutoConnectionHandler) ClientAuthResult(accepted bool, isKnownContact bool) {
log.Printf("Got auth result %v %v", accepted, isKnownContact)
ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact}
}
// ServerAuthValid ...
func (ach *AutoConnectionHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
// Do something
accepted, isKnownContact := ach.sach(hostname, publicKey)
ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact}
return accepted, isKnownContact
}
// ServerAuthInvalid ...
func (ach *AutoConnectionHandler) ServerAuthInvalid(err error) {
ach.authResultChannel <- channels.AuthChannelResult{Accepted: false, IsKnownContact: false}
}
// RegisterChannelHandler ...
func (ach *AutoConnectionHandler) RegisterChannelHandler(ctype string, handler func() channels.Handler) {
_, exists := ach.handlerMap[ctype]
if !exists {
ach.handlerMap[ctype] = handler
}
}
// OnOpenChannelRequest ...
func (ach *AutoConnectionHandler) OnOpenChannelRequest(ctype string) (channels.Handler, error) {
handler, ok := ach.handlerMap[ctype]
if ok {
h := handler()
log.Printf("Got Channel Handler")
return h, nil
}
return nil, utils.UnknownChannelTypeError
}

@ -0,0 +1,36 @@
package connection
import (
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/control"
"testing"
)
// Test sending valid packets
func TestInit(t *testing.T) {
ach := new(AutoConnectionHandler)
privateKey, err := utils.LoadPrivateKeyFromFile("../testing/private_key")
ach.Init(privateKey, "")
// Construct the Open Authentication Channel Message
messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.OpenAuthenticationChannel(1, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
opm := res.GetOpenChannel()
//ocmessage, _ := proto.Marshal(opm)
handler, err := ach.OnOpenChannelRequest(opm.GetChannelType())
if err == nil {
if handler.Type() != "im.ricochet.auth.hidden-service" {
t.Errorf("Failed to authentication handler: %v", handler.Type())
}
} else {
t.Errorf("Failed to build handler: %v", err)
}
}