parent
5a720a08d0
commit
5d767174b1
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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.
|
@ -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)
|
||||
}
|
||||
}
|