First Cut of Applications + Bugs, Formatting

This commit is contained in:
Sarah Jamie Lewis 2017-07-04 11:29:11 -07:00
parent 1cf7c2b7c7
commit 22cbf5d738
20 changed files with 380 additions and 170 deletions

View File

@ -0,0 +1,23 @@
package application
import (
"crypto/rsa"
)
// AcceptAllContactManager implements the contact manager interface an presumes
// all connections are allowed.
type AcceptAllContactManager struct {
}
// LookupContact returns that a contact is known and allowed to communicate for all cases.
func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
return true, true
}
func (aacm *AcceptAllContactManager) GetContactDetails() (string, string) {
return "", ""
}
func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string {
return "Accepted"
}

View File

@ -1,36 +1,134 @@
package application package application
import ( import (
"errors" "crypto/rsa"
"github.com/s-rah/go-ricochet"
"github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/connection" "github.com/s-rah/go-ricochet/connection"
"log"
"net"
"time"
) )
// RicochetApplication bundles many useful constructs that are // RicochetApplication bundles many useful constructs that are
// likely standard in a ricochet application // likely standard in a ricochet application
type RicochetApplication struct { type RicochetApplication struct {
connection *connection.Connection contactManager ContactManagerInterface
privateKey *rsa.PrivateKey
chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
chatMessageAckHandler func(*RicochetApplicationInstance, uint32)
} }
// NewRicochetApplication ... type RicochetApplicationInstance struct {
func NewRicochetApplication(connection *connection.Connection) *RicochetApplication { connection.AutoConnectionHandler
ra := new(RicochetApplication) connection *connection.Connection
ra.connection = connection RemoteHostname string
return ra ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
ChatMessageAckHandler func(*RicochetApplicationInstance, uint32)
} }
// SendMessage ... func (rai *RicochetApplicationInstance) GetContactDetails() (string, string) {
func (ra *RicochetApplication) SendChatMessage(message string) error { return "EchoBot", "I LIVE 😈😈!!!!"
return ra.connection.Do(func() error { }
channel := ra.connection.Channel("im.ricochet.chat", channels.Outbound)
func (rai *RicochetApplicationInstance) ContactRequest(name string, message string) string {
return "Accepted"
}
func (rai *RicochetApplicationInstance) ContactRequestRejected() {
}
func (rai *RicochetApplicationInstance) ContactRequestAccepted() {
}
func (rai *RicochetApplicationInstance) ContactRequestError() {
}
func (rai *RicochetApplicationInstance) SendChatMessage(message string) {
// Technically this errors afte the second time but we can ignore it.
rai.connection.RequestOpenChannel("im.ricochet.chat", rai)
rai.connection.Do(func() error {
channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil { if channel != nil {
chatchannel, ok := (*channel.Handler).(*channels.ChatChannel) chatchannel, ok := (*channel.Handler).(*channels.ChatChannel)
if ok { if ok {
chatchannel.SendMessage(message) chatchannel.SendMessage(message)
} }
} else {
return errors.New("")
} }
return nil return nil
}) })
} }
func (rai *RicochetApplicationInstance) ChatMessage(messageID uint32, when time.Time, message string) bool {
go rai.ChatMessageHandler(rai, messageID, when, message)
return true
}
func (rai *RicochetApplicationInstance) ChatMessageAck(messageID uint32) {
rai.ChatMessageAckHandler(rai, messageID)
}
func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, cm ContactManagerInterface) {
ra.privateKey = pk
ra.contactManager = cm
ra.chatMessageHandler = func(*RicochetApplicationInstance, uint32, time.Time, string) {}
ra.chatMessageAckHandler = func(*RicochetApplicationInstance, uint32) {}
}
func (ra *RicochetApplication) OnChatMessage(call func(*RicochetApplicationInstance, uint32, time.Time, string)) {
ra.chatMessageHandler = call
}
func (ra *RicochetApplication) OnChatMessageAck(call func(*RicochetApplicationInstance, uint32)) {
ra.chatMessageAckHandler = call
}
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
rc, err := goricochet.NegotiateVersionInbound(conn)
if err != nil {
log.Printf("There was an error")
conn.Close()
return
}
ich := connection.HandleInboundConnection(rc)
err = ich.ProcessAuthAsServer(ra.privateKey, ra.contactManager.LookupContact)
if err != nil {
log.Printf("There was an error")
conn.Close()
return
}
rai := new(RicochetApplicationInstance)
rai.Init(ra.privateKey, "")
rai.RemoteHostname = rc.RemoteHostname
rai.connection = rc
rai.ChatMessageHandler = ra.chatMessageHandler
rai.ChatMessageAckHandler = ra.chatMessageAckHandler
rai.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler {
contact := new(channels.ContactRequestChannel)
contact.Handler = rai
return contact
})
rai.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = rai
return chat
})
rc.Process(rai)
}
func (ra *RicochetApplication) Run(l net.Listener) {
if ra.privateKey == nil || ra.contactManager == nil {
return
}
for {
conn, err := l.Accept()
if err == nil {
go ra.handleConnection(conn)
}
}
}

View File

@ -0,0 +1,11 @@
package application
import (
"crypto/rsa"
)
// ContactManagerInterface provides a mechanism for autonous applications
// to make decisions on what connections to accept or reject.
type ContactManagerInterface interface {
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
}

View File

@ -0,0 +1,31 @@
package main
import (
"github.com/s-rah/go-ricochet/application"
"github.com/s-rah/go-ricochet/utils"
"log"
"time"
)
func main() {
echobot := new(application.RicochetApplication)
pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key")
if err != nil {
log.Fatalf("error reading private key file: %v", err)
}
l, err := application.SetupOnion("127.0.0.1:9051", "", pk, 9878)
if err != nil {
log.Fatalf("error setting up onion service: %v", err)
}
echobot.Init(pk, new(application.AcceptAllContactManager))
echobot.OnChatMessage(func(rai *application.RicochetApplicationInstance, id uint32, timestamp time.Time, message string) {
log.Printf("message from %v - %v", rai.RemoteHostname, message)
rai.SendChatMessage(message)
})
log.Printf("echobot listening on %s", l.Addr().String())
echobot.Run(l)
}

View File

@ -0,0 +1,25 @@
package application
import (
"crypto/rsa"
"github.com/yawning/bulb"
"net"
)
func SetupOnion(proxyServer string, authentication string, pk *rsa.PrivateKey, onionport uint16) (net.Listener, error) {
c, err := bulb.Dial("tcp4", proxyServer)
if err != nil {
return nil, err
}
if err := c.Authenticate(authentication); err != nil {
return nil, err
}
cfg := &bulb.NewOnionConfig{
DiscardPK: true,
PrivateKey: pk,
}
return c.NewListener(cfg, onionport)
}

View File

@ -12,6 +12,7 @@ const (
// AuthChannelResult captures the result of an authentication flow // AuthChannelResult captures the result of an authentication flow
type AuthChannelResult struct { type AuthChannelResult struct {
Hostname string
Accepted bool Accepted bool
IsKnownContact bool IsKnownContact bool
} }

View File

@ -1,13 +1,19 @@
package channels package channels
import ( import (
"errors"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/contact" "github.com/s-rah/go-ricochet/wire/contact"
"github.com/s-rah/go-ricochet/wire/control" "github.com/s-rah/go-ricochet/wire/control"
) )
// Defining Versions
const (
InvalidContactNameError = utils.Error("InvalidContactNameError")
InvalidContactMessageError = utils.Error("InvalidContactMessageError")
InvalidContactRequestError = utils.Error("InvalidContactRequestError")
)
// ContactRequestChannel implements the ChannelHandler interface for a channel of // ContactRequestChannel implements the ChannelHandler interface for a channel of
// type "im.ricochet.contact.request". The channel may be inbound or outbound. // type "im.ricochet.contact.request". The channel may be inbound or outbound.
// a ContactRequestChannelHandler implementation to handle chat events. // a ContactRequestChannelHandler implementation to handle chat events.
@ -73,12 +79,12 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat
if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) { if len(contactRequest.GetNickname()) > int(Protocol_Data_ContactRequest.Limits_NicknameMaxCharacters) {
// Violation of the Protocol // Violation of the Protocol
return nil, errors.New("invalid nickname") return nil, InvalidContactNameError
} }
if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) { if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) {
// Violation of the Protocol // Violation of the Protocol
return nil, errors.New("invalid message") return nil, InvalidContactMessageError
} }
result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText()) result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText())
@ -86,7 +92,7 @@ func (crc *ContactRequestChannel) OpenInbound(channel *Channel, oc *Protocol_Dat
return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil
} }
} }
return nil, errors.New("could not parse contact request extension") return nil, InvalidContactRequestError
} }
// OpenOutbound is the first method called for an outbound channel request. // OpenOutbound is the first method called for an outbound channel request.

View File

@ -7,7 +7,6 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"encoding/asn1" "encoding/asn1"
"errors"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/auth" "github.com/s-rah/go-ricochet/wire/auth"
@ -15,6 +14,10 @@ import (
"io" "io"
) )
const (
InvalidClientCookieError = utils.Error("InvalidClientCookieError")
)
// HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service" // HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
type HiddenServiceAuthChannel struct { type HiddenServiceAuthChannel struct {
// Methods of Handler are called for events on this channel // Methods of Handler are called for events on this channel
@ -75,15 +78,15 @@ func (ah *HiddenServiceAuthChannel) Closed(err error) {
// Remote -> [Open Authentication Channel] -> Local // Remote -> [Open Authentication Channel] -> Local
func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) { func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
if ah.PrivateKey == nil { if ah.PrivateKey == nil {
return nil, utils.PrivateKeyNotSetError return nil, utils.PrivateKeyNotSetError
} }
ah.channel = channel ah.channel = channel
clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie) clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie)
if len(clientCookie.([]byte)[:]) != 16 { if len(clientCookie.([]byte)[:]) != 16 {
// reutrn without opening channel. // reutrn without opening channel.
return nil, errors.New("invalid client cookie") return nil, InvalidClientCookieError
} }
ah.AddClientCookie(clientCookie.([]byte)[:]) ah.AddClientCookie(clientCookie.([]byte)[:])
messageBuilder := new(utils.MessageBuilder) messageBuilder := new(utils.MessageBuilder)
@ -97,10 +100,9 @@ func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_D
// Local -> [Open Authentication Channel] -> Remote // Local -> [Open Authentication Channel] -> Remote
func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) { func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) {
if ah.PrivateKey == nil { if ah.PrivateKey == nil {
return nil, utils.PrivateKeyNotSetError return nil, utils.PrivateKeyNotSetError
} }
ah.channel = channel ah.channel = channel
messageBuilder := new(utils.MessageBuilder) messageBuilder := new(utils.MessageBuilder)

View File

@ -65,7 +65,7 @@ func (ach *AutoConnectionHandler) ClientAuthResult(accepted bool, isKnownContact
func (ach *AutoConnectionHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { func (ach *AutoConnectionHandler) ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
// Do something // Do something
accepted, isKnownContact := ach.sach(hostname, publicKey) accepted, isKnownContact := ach.sach(hostname, publicKey)
ach.authResultChannel <- channels.AuthChannelResult{Accepted: accepted, IsKnownContact: isKnownContact} ach.authResultChannel <- channels.AuthChannelResult{Hostname: hostname, Accepted: accepted, IsKnownContact: isKnownContact}
return accepted, isKnownContact return accepted, isKnownContact
} }

View File

@ -1,8 +1,8 @@
package connection package connection
import ( import (
"errors"
"github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
) )
// ChannelManager encapsulates the logic for server and client side assignment // ChannelManager encapsulates the logic for server and client side assignment
@ -38,7 +38,7 @@ func NewServerChannelManager() *ChannelManager {
func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channels.Channel, error) { func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channels.Channel, error) {
// Some channels only allow us to open one of them per connection // Some channels only allow us to open one of them per connection
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Outbound) != nil { if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Outbound) != nil {
return nil, errors.New("Connection already has channel of type " + chandler.Type()) return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
} }
channel := new(channels.Channel) channel := new(channels.Channel)
@ -57,20 +57,20 @@ func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channe
func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler channels.Handler) (*channels.Channel, error) { func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler channels.Handler) (*channels.Channel, error) {
if cm.isClient && (channelID%2) != 0 { if cm.isClient && (channelID%2) != 0 {
// Server is trying to open odd numbered channels // Server is trying to open odd numbered channels
return nil, errors.New("server may only open even numbered channels") return nil, utils.ServerAttemptedToOpenEvenNumberedChannelError
} else if !cm.isClient && (channelID%2) == 0 { } else if !cm.isClient && (channelID%2) == 0 {
// Server is trying to open odd numbered channels // Server is trying to open odd numbered channels
return nil, errors.New("client may only open odd numbered channels") return nil, utils.ClientAttemptedToOpenOddNumberedChannelError
} }
_, exists := cm.channels[channelID] _, exists := cm.channels[channelID]
if exists { if exists {
return nil, errors.New("channel id is already in use") return nil, utils.ChannelIDIsAlreadyInUseError
} }
// Some channels only allow us to open one of them per connection // Some channels only allow us to open one of them per connection
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil { if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
return nil, errors.New("Connection already has channel of type " + chandler.Type()) return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
} }
channel := new(channels.Channel) channel := new(channels.Channel)

View File

@ -2,14 +2,13 @@ package connection
import ( import (
"errors" "errors"
"fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/control" "github.com/s-rah/go-ricochet/wire/control"
"io" "io"
"log" "log"
"time"
"fmt"
) )
// Connection encapsulates the state required to maintain a connection to // Connection encapsulates the state required to maintain a connection to
@ -30,7 +29,7 @@ type Connection struct {
unlockResponseChannel chan bool unlockResponseChannel chan bool
messageBuilder utils.MessageBuilder messageBuilder utils.MessageBuilder
trace bool trace bool
Conn io.ReadWriteCloser Conn io.ReadWriteCloser
IsInbound bool IsInbound bool
@ -77,7 +76,7 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn
} }
func (rc *Connection) TraceLog(enabled bool) { func (rc *Connection) TraceLog(enabled bool) {
rc.trace = enabled rc.trace = enabled
} }
// start // start
@ -111,12 +110,12 @@ func (rc *Connection) Do(do func() error) error {
// are not met on the local side (a nill error return does not mean the // are not met on the local side (a nill error return does not mean the
// channel was opened successfully) // channel was opened successfully)
func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error { func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype)) rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
return rc.Do(func() error { return rc.Do(func() error {
chandler, err := handler.OnOpenChannelRequest(ctype) chandler, err := handler.OnOpenChannelRequest(ctype)
if err != nil { if err != nil {
rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
return err return err
} }
@ -125,14 +124,14 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
// Enforce Authentication Check. // Enforce Authentication Check.
_, authed := rc.Authentication[chandler.RequiresAuthentication()] _, authed := rc.Authentication[chandler.RequiresAuthentication()]
if !authed { if !authed {
return errors.New("connection is not auth'd") return utils.UnauthorizedActionError
} }
} }
channel, err := rc.channelManager.OpenChannelRequest(chandler) channel, err := rc.channelManager.OpenChannelRequest(chandler)
if err != nil { if err != nil {
rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err))
return err return err
} }
@ -148,10 +147,10 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler Handler) error {
} }
response, err := chandler.OpenOutbound(channel) response, err := chandler.OpenOutbound(channel)
if err == nil { if err == nil {
rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype)) rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype))
rc.SendRicochetPacket(rc.Conn, 0, response) rc.SendRicochetPacket(rc.Conn, 0, response)
} else { } else {
rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err)) rc.traceLog(fmt.Sprintf("failed to reqeust open channel of type %v", err))
rc.channelManager.RemoveChannel(channel.ID) rc.channelManager.RemoveChannel(channel.ID)
} }
return nil return nil
@ -174,7 +173,6 @@ func (rc *Connection) Process(handler Handler) error {
for !breaked { for !breaked {
var packet utils.RicochetData var packet utils.RicochetData
tick := time.Tick(30 * time.Second)
select { select {
case <-rc.unlockChannel: case <-rc.unlockChannel:
<-rc.unlockResponseChannel <-rc.unlockResponseChannel
@ -189,14 +187,10 @@ func (rc *Connection) Process(handler Handler) error {
rc.Conn.Close() rc.Conn.Close()
handler.OnClosed(err) handler.OnClosed(err)
return err return err
case <-tick:
rc.traceLog("peer timed out")
return errors.New("peer timed out")
} }
if packet.Channel == 0 { if packet.Channel == 0 {
rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel)) rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
res := new(Protocol_Data_Control.Packet) res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(packet.Data[:], res) err := proto.Unmarshal(packet.Data[:], res)
if err == nil { if err == nil {
@ -209,9 +203,9 @@ func (rc *Connection) Process(handler Handler) error {
if len(packet.Data) == 0 { if len(packet.Data) == 0 {
rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel)) rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
rc.channelManager.RemoveChannel(packet.Channel) rc.channelManager.RemoveChannel(packet.Channel)
(*channel.Handler).Closed(errors.New("channel closed by peer")) (*channel.Handler).Closed(utils.ChannelClosedByPeerError)
} else { } else {
rc.traceLog(fmt.Sprintf("received packet on %v channel %d", (*channel.Handler).Type(), packet.Channel)) rc.traceLog(fmt.Sprintf("received packet on %v channel %d", (*channel.Handler).Type(), packet.Channel))
// Send The Ricochet Packet to the Handler // Send The Ricochet Packet to the Handler
(*channel.Handler).Packet(packet.Data[:]) (*channel.Handler).Packet(packet.Data[:])
} }
@ -219,7 +213,7 @@ func (rc *Connection) Process(handler Handler) error {
// When a non-zero packet is received for an unknown // When a non-zero packet is received for an unknown
// channel, the recipient responds by closing // channel, the recipient responds by closing
// that channel. // that channel.
rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel)) rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
if len(packet.Data) != 0 { if len(packet.Data) != 0 {
rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{}) rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{})
} }
@ -248,7 +242,7 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
// Check that we have the authentication already // Check that we have the authentication already
if chandler.RequiresAuthentication() != "none" { if chandler.RequiresAuthentication() != "none" {
rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication())) rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication()))
// Enforce Authentication Check. // Enforce Authentication Check.
_, authed := rc.Authentication[chandler.RequiresAuthentication()] _, authed := rc.Authentication[chandler.RequiresAuthentication()]
if !authed { if !authed {
@ -276,10 +270,10 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
response, err := chandler.OpenInbound(channel, opm) response, err := chandler.OpenInbound(channel, opm)
if err == nil && channel.Pending == false { if err == nil && channel.Pending == false {
rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID)) rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID))
rc.SendRicochetPacket(rc.Conn, 0, response) rc.SendRicochetPacket(rc.Conn, 0, response)
} else { } else {
rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID)) rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID))
rc.channelManager.RemoveChannel(channel.ID) rc.channelManager.RemoveChannel(channel.ID)
rc.SendRicochetPacket(rc.Conn, 0, []byte{}) rc.SendRicochetPacket(rc.Conn, 0, []byte{})
} }
@ -297,15 +291,15 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
channel, found := rc.channelManager.GetChannel(id) channel, found := rc.channelManager.GetChannel(id)
if !found { if !found {
rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id)) rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id))
return return
} }
if cr.GetOpened() { if cr.GetOpened() {
rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id)) rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
(*channel.Handler).OpenOutboundResult(nil, cr) (*channel.Handler).OpenOutboundResult(nil, cr)
} else { } else {
rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id)) rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
(*channel.Handler).OpenOutboundResult(errors.New(""), cr) (*channel.Handler).OpenOutboundResult(errors.New(""), cr)
} }
@ -321,27 +315,27 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
rc.SendRicochetPacket(rc.Conn, 0, raw) rc.SendRicochetPacket(rc.Conn, 0, raw)
} }
} else if res.GetEnableFeatures() != nil { } else if res.GetEnableFeatures() != nil {
rc.traceLog("received features enabled packet") rc.traceLog("received features enabled packet")
messageBuilder := new(utils.MessageBuilder) messageBuilder := new(utils.MessageBuilder)
raw := messageBuilder.FeaturesEnabled([]string{}) raw := messageBuilder.FeaturesEnabled([]string{})
rc.traceLog("sending featured enabled empty response") rc.traceLog("sending featured enabled empty response")
rc.SendRicochetPacket(rc.Conn, 0, raw) rc.SendRicochetPacket(rc.Conn, 0, raw)
} else if res.GetFeaturesEnabled() != nil { } else if res.GetFeaturesEnabled() != nil {
// TODO We should never send out an enabled features // TODO We should never send out an enabled features
// request. // request.
rc.traceLog("sending unsolicited features enabled response") rc.traceLog("sending unsolicited features enabled response")
} }
} }
func (rc *Connection) traceLog(message string) { func (rc *Connection) traceLog(message string) {
if rc.trace { if rc.trace {
log.Printf(message) log.Printf(message)
} }
} }
// Break causes Process() to return, but does not close the underlying connection // Break causes Process() to return, but does not close the underlying connection
func (rc *Connection) Break() { func (rc *Connection) Break() {
rc.traceLog("breaking out of process loop") rc.traceLog("breaking out of process loop")
rc.breakChannel <- true rc.breakChannel <- true
<-rc.breakResultChannel // Wait for Process to End <-rc.breakResultChannel // Wait for Process to End
} }

View File

@ -35,9 +35,9 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
// assume they are required to send a contact request before any other activity. // assume they are required to send a contact request before any other activity.
func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.PrivateKey, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error { func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.PrivateKey, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error {
if privateKey == nil { if privateKey == nil {
return utils.PrivateKeyNotSetError return utils.PrivateKeyNotSetError
} }
ach := new(AutoConnectionHandler) ach := new(AutoConnectionHandler)
ach.Init(privateKey, ich.connection.RemoteHostname) ach.Init(privateKey, ich.connection.RemoteHostname)
@ -56,6 +56,7 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.Private
if err == nil { if err == nil {
if authResult.Accepted == true { if authResult.Accepted == true {
ich.connection.RemoteHostname = authResult.Hostname
return nil return nil
} }
return utils.ClientFailedToAuthenticateError return utils.ClientFailedToAuthenticateError

View File

@ -2,10 +2,9 @@ package connection
import ( import (
"crypto/rsa" "crypto/rsa"
"errors"
"github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/policies" "github.com/s-rah/go-ricochet/policies"
"github.com/s-rah/go-ricochet/utils"
) )
// OutboundConnectionHandler is a convieniance wrapper for handling outbound // OutboundConnectionHandler is a convieniance wrapper for handling outbound
@ -34,9 +33,9 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
// request before any other activity. // request before any other activity.
func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) { func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) {
if privateKey == nil { if privateKey == nil {
return false, utils.PrivateKeyNotSetError return false, utils.PrivateKeyNotSetError
} }
ach := new(AutoConnectionHandler) ach := new(AutoConnectionHandler)
ach.Init(privateKey, och.connection.RemoteHostname) ach.Init(privateKey, och.connection.RemoteHostname)
@ -61,5 +60,5 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat
return result.IsKnownContact, nil return result.IsKnownContact, nil
} }
} }
return false, errors.New("authentication was not accepted by the server") return false, utils.ServerRejectedClientConnectionError
} }

View File

@ -58,7 +58,7 @@ func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string)
}) })
rc, _ := goricochet.Open(hostname) rc, _ := goricochet.Open(hostname)
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey) known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey)
if err == nil { if err == nil {
go rc.Process(echobot) go rc.Process(echobot)

View File

@ -1,60 +1,59 @@
package goricochet package goricochet
import ( import (
"github.com/s-rah/go-ricochet/utils" "github.com/s-rah/go-ricochet/connection"
"github.com/s-rah/go-ricochet/connection" "github.com/s-rah/go-ricochet/utils"
"io" "io"
"net" "net"
) )
// Open establishes a protocol session on an established net.Conn, and returns a new // Open establishes a protocol session on an established net.Conn, and returns a new
// OpenConnection instance representing this connection. On error, the connection // OpenConnection instance representing this connection. On error, the connection
// will be closed. This function blocks until version negotiation has completed. // will be closed. This function blocks until version negotiation has completed.
// The application should call Process() on the returned OpenConnection to continue // The application should call Process() on the returned OpenConnection to continue
// handling protocol messages. // handling protocol messages.
func Open(remoteHostname string) (*connection.Connection, error) { func Open(remoteHostname string) (*connection.Connection, error) {
networkResolver := utils.NetworkResolver{} networkResolver := utils.NetworkResolver{}
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname) conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rc, err := negotiateVersion(conn, remoteHostname) rc, err := negotiateVersion(conn, remoteHostname)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, err
} }
return rc, nil return rc, nil
} }
// negotiate version takes an open network connection and executes // negotiate version takes an open network connection and executes
// the ricochet version negotiation procedure. // the ricochet version negotiation procedure.
func negotiateVersion(conn net.Conn, remoteHostname string) (*connection.Connection, error) { func negotiateVersion(conn net.Conn, remoteHostname string) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x01} versions := []byte{0x49, 0x4D, 0x01, 0x01}
if n, err := conn.Write(versions); err != nil || n < len(versions) { if n, err := conn.Write(versions); err != nil || n < len(versions) {
return nil, utils.VersionNegotiationError return nil, utils.VersionNegotiationError
} }
res := make([]byte, 1) res := make([]byte, 1)
if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil { if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil {
return nil, utils.VersionNegotiationError return nil, utils.VersionNegotiationError
} }
if res[0] != 0x01 { if res[0] != 0x01 {
return nil, utils.VersionNegotiationFailed return nil, utils.VersionNegotiationFailed
} }
rc := connection.NewOutboundConnection(conn,remoteHostname) rc := connection.NewOutboundConnection(conn, remoteHostname)
return rc, nil return rc, nil
} }
// NegotiateVersionInbound takes in a connection and performs version negotiation // NegotiateVersionInbound takes in a connection and performs version negotiation
// as if that connection was a client. Returns a ricochet connection if successful // as if that connection was a client. Returns a ricochet connection if successful
// error otherwise. // error otherwise.
func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) { func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x01} versions := []byte{0x49, 0x4D, 0x01, 0x01}
// Read version response header // Read version response header
header := make([]byte, 3) header := make([]byte, 3)
if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil {
return nil, err return nil, err
@ -85,10 +84,7 @@ func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
if selectedVersion == 0xff { if selectedVersion == 0xff {
return nil, utils.VersionNegotiationFailed return nil, utils.VersionNegotiationFailed
} }
rc := connection.NewInboundConnection(conn) rc := connection.NewInboundConnection(conn)
return rc, nil return rc, nil
} }

View File

@ -1,70 +1,68 @@
package goricochet package goricochet
import ( import (
"testing" "github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/utils" "net"
"net" "testing"
"time" "time"
) )
func SimpleServer() { func SimpleServer() {
ln,_ := net.Listen("tcp", "127.0.0.1:11000") ln, _ := net.Listen("tcp", "127.0.0.1:11000")
conn,_ := ln.Accept() conn, _ := ln.Accept()
b := make([]byte, 4) b := make([]byte, 4)
n,err := conn.Read(b) n, err := conn.Read(b)
if n == 4 && err == nil { if n == 4 && err == nil {
conn.Write([]byte{0x01}) conn.Write([]byte{0x01})
} }
conn.Close() conn.Close()
} }
func BadVersionNegotiation() { func BadVersionNegotiation() {
ln,_ := net.Listen("tcp", "127.0.0.1:11001") ln, _ := net.Listen("tcp", "127.0.0.1:11001")
conn,_ := ln.Accept() conn, _ := ln.Accept()
// We are already testing negotiation bytes, we don't care, just send a termination. // We are already testing negotiation bytes, we don't care, just send a termination.
conn.Write([]byte{0x00}) conn.Write([]byte{0x00})
conn.Close() conn.Close()
} }
func NotRicochetServer() { func NotRicochetServer() {
ln,_ := net.Listen("tcp", "127.0.0.1:11002") ln, _ := net.Listen("tcp", "127.0.0.1:11002")
conn,_ := ln.Accept() conn, _ := ln.Accept()
conn.Close() conn.Close()
} }
func TestRicochet(t *testing.T) { func TestRicochet(t *testing.T) {
go SimpleServer() go SimpleServer()
// Wait for Server to Initialize // Wait for Server to Initialize
time.Sleep(time.Second) time.Sleep(time.Second)
rc,err := Open("127.0.0.1:11000|abcdefghijklmno.onion") rc, err := Open("127.0.0.1:11000|abcdefghijklmno.onion")
if err == nil { if err == nil {
if rc.IsInbound { if rc.IsInbound {
t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen") t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen")
} }
return return
} }
t.Errorf("RicochetProtocol: Open Failed: %v", err) t.Errorf("RicochetProtocol: Open Failed: %v", err)
} }
func TestBadVersionNegotiation(t*testing.T) { func TestBadVersionNegotiation(t *testing.T) {
go BadVersionNegotiation() go BadVersionNegotiation()
time.Sleep(time.Second) time.Sleep(time.Second)
_,err := Open("127.0.0.1:11001|abcdefghijklmno.onion") _, err := Open("127.0.0.1:11001|abcdefghijklmno.onion")
if err != utils.VersionNegotiationFailed { if err != utils.VersionNegotiationFailed {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err) t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
} }
} }
func TestNotARicochetServer(t *testing.T) {
go NotRicochetServer()
time.Sleep(time.Second)
func TestNotARicochetServer(t*testing.T) { _, err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
go NotRicochetServer() if err != utils.VersionNegotiationError {
time.Sleep(time.Second) t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
_,err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
if err != utils.VersionNegotiationError {
t.Errorf("RicochetProtocol: Server Had No Correct Version - Should Have Failed: err = %v", err)
}
} }

View File

@ -4,10 +4,13 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"io/ioutil" "io/ioutil"
) )
const (
InvalidPrivateKeyFileError = Error("InvalidPrivateKeyFileError")
)
// LoadPrivateKeyFromFile loads a private key from a file... // LoadPrivateKeyFromFile loads a private key from a file...
func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) { func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) {
pemData, err := ioutil.ReadFile(filename) pemData, err := ioutil.ReadFile(filename)
@ -18,7 +21,7 @@ func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemData) block, _ := pem.Decode(pemData)
if block == nil || block.Type != "RSA PRIVATE KEY" { if block == nil || block.Type != "RSA PRIVATE KEY" {
return nil, errors.New("not a private key") return nil, InvalidPrivateKeyFileError
} }
return x509.ParsePKCS1PrivateKey(block.Bytes) return x509.ParsePKCS1PrivateKey(block.Bytes)

View File

@ -20,12 +20,25 @@ const (
UnknownChannelTypeError = Error("UnknownChannelTypeError") UnknownChannelTypeError = Error("UnknownChannelTypeError")
UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError") UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError")
// Timeout Errors
ActionTimedOutError = Error("ActionTimedOutError") ActionTimedOutError = Error("ActionTimedOutError")
PeerTimedOutError = Error("PeerTimedOutError")
ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError") // Authentication Errors
ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError")
ServerRejectedClientConnectionError = Error("ServerRejectedClientConnectionError")
UnauthorizedActionError = Error("UnauthorizedActionError")
ChannelClosedByPeerError = Error("ChannelClosedByPeerError")
PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError") // Channel Management Errors
ServerAttemptedToOpenEvenNumberedChannelError = Error("ServerAttemptedToOpenEvenNumberedChannelError")
ClientAttemptedToOpenOddNumberedChannelError = Error("ClientAttemptedToOpenOddNumberedChannelError")
ChannelIDIsAlreadyInUseError = Error("ChannelIDIsAlreadyInUseError")
AttemptToOpenMoreThanOneSingletonChannelError = Error("AttemptToOpenMoreThanOneSingletonChannelError")
// Library Use Errors
PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError")
) )
// CheckError is a helper function for panicing on errors which we need to handle // CheckError is a helper function for panicing on errors which we need to handle

View File

@ -3,10 +3,14 @@ package utils
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
) )
const (
InvalidPacketLengthError = Error("InvalidPacketLengthError")
InvalidChannelIDError = Error("InvalidChannelIDError")
)
// RicochetData is a structure containing the raw data and the channel it the // RicochetData is a structure containing the raw data and the channel it the
// message originated on. // message originated on.
type RicochetData struct { type RicochetData struct {
@ -36,11 +40,11 @@ type RicochetNetwork struct {
func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data []byte) error { func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data []byte) error {
packet := make([]byte, 4+len(data)) packet := make([]byte, 4+len(data))
if len(packet) > 65535 { if len(packet) > 65535 {
return errors.New("packet too large") return InvalidPacketLengthError
} }
binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet))) binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)))
if channel < 0 || channel > 65535 { if channel < 0 || channel > 65535 {
return errors.New("invalid channel ID") return InvalidChannelIDError
} }
binary.BigEndian.PutUint16(packet[2:4], uint16(channel)) binary.BigEndian.PutUint16(packet[2:4], uint16(channel))
copy(packet[4:], data[:]) copy(packet[4:], data[:])
@ -68,7 +72,7 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e
size := int(binary.BigEndian.Uint16(header[0:2])) size := int(binary.BigEndian.Uint16(header[0:2]))
if size < 4 { if size < 4 {
return packet, errors.New("invalid packet length") return packet, InvalidPacketLengthError
} }
packet.Channel = int32(binary.BigEndian.Uint16(header[2:4])) packet.Channel = int32(binary.BigEndian.Uint16(header[2:4]))

View File

@ -1,12 +1,17 @@
package utils package utils
import ( import (
"errors"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"net" "net"
"strings" "strings"
) )
const (
CannotResolveLocalTCPAddressError = Error("CannotResolveLocalTCPAddressError")
CannotDialLocalTCPAddressError = Error("CannotDialLocalTCPAddressError")
CannotDialRicochetAddressError = Error("CannotDialRicochetAddressError")
)
// NetworkResolver allows a client to resolve various hostnames to connections // NetworkResolver allows a client to resolve various hostnames to connections
// The supported types are onions address are: // The supported types are onions address are:
// * ricochet:jlq67qzo6s4yp3sp // * ricochet:jlq67qzo6s4yp3sp
@ -21,11 +26,11 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
addrParts := strings.Split(hostname, "|") addrParts := strings.Split(hostname, "|")
tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0]) tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0])
if err != nil { if err != nil {
return nil, "", errors.New("Cannot Resolve Local TCP Address") return nil, "", CannotResolveLocalTCPAddressError
} }
conn, err := net.DialTCP("tcp", nil, tcpAddr) conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil { if err != nil {
return nil, "", errors.New("Cannot Dial Local TCP Address") return nil, "", CannotDialLocalTCPAddressError
} }
// return just the onion address, not the local override for the hostname // return just the onion address, not the local override for the hostname
@ -45,7 +50,7 @@ func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
conn, err := torDialer.Dial("tcp", resolvedHostname+".onion:9878") conn, err := torDialer.Dial("tcp", resolvedHostname+".onion:9878")
if err != nil { if err != nil {
return nil, "", errors.New("Cannot Dial Remote Ricochet Address") return nil, "", CannotDialRicochetAddressError
} }
return conn, resolvedHostname, nil return conn, resolvedHostname, nil