Update go-ricochet to new API (plus fixes)
go-ricochet upstream is in the midst of a massive API rewrite, moving towards something that is more flexible and robust. It's a work in progress, but already better than what we were on before. This updates the vendored go-ricochet to a fork off of the latest master, including a series of patches that were needed to fix or add behavior needed by ricochet-go. All of these are submitted upstream, and the goal is to point back there as soon as possible. Current upstream is https://github.com/special/go-ricochet-protocol on the api-rework-fixes branch. The go-ricochet/vendor folder was manually deleted during the import, because it vendors only dependencies that are also vendored by this repository.
This commit is contained in:
parent
48aab2536a
commit
9ae0eac4f3
|
@ -61,10 +61,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
The go-ricochet logo is based on an image by Olga Shalakhina
|
|
||||||
<osshalakhina@gmail.com> who in turn modified the original gopher images made by
|
|
||||||
Renee French. The image is licensed under Creative Commons 3.0 Attributions.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project.
|
go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project.
|
||||||
|
|
19
vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go
generated
vendored
Normal file
19
vendor/github.com/s-rah/go-ricochet/application/acceptallcontactmanager.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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) ContactRequest(name string, message string) string {
|
||||||
|
return "Accepted"
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/s-rah/go-ricochet"
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/connection"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RicochetApplication bundles many useful constructs that are
|
||||||
|
// likely standard in a ricochet application
|
||||||
|
type RicochetApplication struct {
|
||||||
|
contactManager ContactManagerInterface
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
|
||||||
|
chatMessageAckHandler func(*RicochetApplicationInstance, uint32)
|
||||||
|
l net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
type RicochetApplicationInstance struct {
|
||||||
|
connection.AutoConnectionHandler
|
||||||
|
connection *connection.Connection
|
||||||
|
RemoteHostname string
|
||||||
|
ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
|
||||||
|
ChatMessageAckHandler func(*RicochetApplicationInstance, uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
rai.connection.Do(func() error {
|
||||||
|
// Technically this errors afte the second time but we can ignore it.
|
||||||
|
rai.connection.RequestOpenChannel("im.ricochet.chat", rai)
|
||||||
|
|
||||||
|
channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound)
|
||||||
|
if channel != nil {
|
||||||
|
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
|
||||||
|
if ok {
|
||||||
|
chatchannel.SendMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) Shutdown () {
|
||||||
|
log.Printf("Closing")
|
||||||
|
ra.l.Close()
|
||||||
|
log.Printf("Closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *RicochetApplication) Run(l net.Listener) {
|
||||||
|
if ra.privateKey == nil || ra.contactManager == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ra.l = l
|
||||||
|
var err error
|
||||||
|
for err == nil {
|
||||||
|
conn, err := ra.l.Accept()
|
||||||
|
if err == nil {
|
||||||
|
go ra.handleConnection(conn)
|
||||||
|
} else {
|
||||||
|
log.Printf("Closing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
vendor/github.com/s-rah/go-ricochet/application/contactmanagerinterface.go
generated
vendored
Normal file
11
vendor/github.com/s-rah/go-ricochet/application/contactmanagerinterface.go
generated
vendored
Normal 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)
|
||||||
|
}
|
31
vendor/github.com/s-rah/go-ricochet/application/examples/echobot/main.go
generated
vendored
Normal file
31
vendor/github.com/s-rah/go-ricochet/application/examples/echobot/main.go
generated
vendored
Normal 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)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/yawning/bulb"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// "127.0.0.1:9051" "tcp4"
|
||||||
|
// "/var/run/tor/control" "unix"
|
||||||
|
func SetupOnion(torControlAddress string, torControlSocketType string, authentication string, pk *rsa.PrivateKey, onionport uint16) (net.Listener, error) {
|
||||||
|
c, err := bulb.Dial(torControlSocketType, torControlAddress)
|
||||||
|
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)
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go
generated
vendored
Normal file
162
vendor/github.com/s-rah/go-ricochet/channels/contactrequestchannel.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defining Versions
|
||||||
|
const (
|
||||||
|
InvalidContactNameError = utils.Error("InvalidContactNameError")
|
||||||
|
InvalidContactMessageError = utils.Error("InvalidContactMessageError")
|
||||||
|
InvalidContactRequestError = utils.Error("InvalidContactRequestError")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContactRequestChannel implements the ChannelHandler interface for a channel of
|
||||||
|
// type "im.ricochet.contact.request". The channel may be inbound or outbound.
|
||||||
|
type ContactRequestChannel struct {
|
||||||
|
// Methods of Handler are called for chat events on this channel
|
||||||
|
Handler ContactRequestChannelHandler
|
||||||
|
channel *Channel
|
||||||
|
|
||||||
|
// Properties of the request
|
||||||
|
Name string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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, InvalidContactNameError
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contactRequest.GetMessageText()) > int(Protocol_Data_ContactRequest.Limits_MessageMaxCharacters) {
|
||||||
|
// Violation of the Protocol
|
||||||
|
return nil, InvalidContactMessageError
|
||||||
|
}
|
||||||
|
|
||||||
|
crc.Name = contactRequest.GetNickname()
|
||||||
|
crc.Message = contactRequest.GetMessageText()
|
||||||
|
result := crc.Handler.ContactRequest(contactRequest.GetNickname(), contactRequest.GetMessageText())
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
return messageBuilder.ReplyToContactRequestOnResponse(channel.ID, result), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, InvalidContactRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
return messageBuilder.OpenContactRequestChannel(channel.ID, crc.Name, crc.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) SendResponse(status string) {
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
crc.channel.SendMessage(messageBuilder.ReplyToContactRequest(crc.channel.ID, status))
|
||||||
|
}
|
||||||
|
|
||||||
|
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,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)
|
||||||
|
}
|
260
vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go
generated
vendored
Normal file
260
vendor/github.com/s-rah/go-ricochet/channels/hiddenserviceauthchannel.go
generated
vendored
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/asn1"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvalidClientCookieError = utils.Error("InvalidClientCookieError")
|
||||||
|
)
|
||||||
|
|
||||||
|
// HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
|
||||||
|
type HiddenServiceAuthChannel struct {
|
||||||
|
// PrivateKey must be set for client-side authentication channels
|
||||||
|
PrivateKey *rsa.PrivateKey
|
||||||
|
// Server Hostname must be set for client-side authentication channels
|
||||||
|
ServerHostname string
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
ClientAuthResult func(accepted, isKnownContact bool)
|
||||||
|
ServerAuthValid func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
|
||||||
|
ServerAuthInvalid func(err error)
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
clientCookie, serverCookie [16]byte
|
||||||
|
channel *Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
if ah.PrivateKey == nil {
|
||||||
|
return nil, utils.PrivateKeyNotSetError
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.channel = channel
|
||||||
|
clientCookie, _ := proto.GetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie)
|
||||||
|
if len(clientCookie.([]byte)[:]) != 16 {
|
||||||
|
// reutrn without opening channel.
|
||||||
|
return nil, InvalidClientCookieError
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
|
||||||
|
if ah.PrivateKey == nil {
|
||||||
|
return nil, utils.PrivateKeyNotSetError
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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.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.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.ServerAuthInvalid(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if res.GetResult() != nil && ah.channel.Direction == Outbound {
|
||||||
|
if ah.ClientAuthResult != nil {
|
||||||
|
ah.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[:])
|
||||||
|
|
||||||
|
value := []byte(clientHostname + serverHostname)
|
||||||
|
mac := hmac.New(sha256.New, key)
|
||||||
|
mac.Write(value)
|
||||||
|
hmac := mac.Sum(nil)
|
||||||
|
return hmac
|
||||||
|
}
|
53
vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go
generated
vendored
Normal file
53
vendor/github.com/s-rah/go-ricochet/connection/autoconnectionhandler.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init ...
|
||||||
|
// TODO: Split this into client and server init
|
||||||
|
func (ach *AutoConnectionHandler) Init() {
|
||||||
|
ach.handlerMap = make(map[string]func() channels.Handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
return nil, utils.UnknownChannelTypeError
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChannelManager encapsulates the logic for server and client side assignment
|
||||||
|
// and removal of channels.
|
||||||
|
type ChannelManager struct {
|
||||||
|
channels map[int32]*channels.Channel
|
||||||
|
nextFreeChannel int32
|
||||||
|
isClient bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientChannelManager construsts a new channel manager enforcing behaviour
|
||||||
|
// of a ricochet client
|
||||||
|
func NewClientChannelManager() *ChannelManager {
|
||||||
|
channelManager := new(ChannelManager)
|
||||||
|
channelManager.channels = make(map[int32]*channels.Channel)
|
||||||
|
channelManager.nextFreeChannel = 1
|
||||||
|
channelManager.isClient = true
|
||||||
|
return channelManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerChannelManager construsts a new channel manager enforcing behaviour
|
||||||
|
// from a ricochet server
|
||||||
|
func NewServerChannelManager() *ChannelManager {
|
||||||
|
channelManager := new(ChannelManager)
|
||||||
|
channelManager.channels = make(map[int32]*channels.Channel)
|
||||||
|
channelManager.nextFreeChannel = 2
|
||||||
|
channelManager.isClient = false
|
||||||
|
return channelManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenChannelRequest constructs a channel type ready for processing given a request
|
||||||
|
// from the client.
|
||||||
|
func (cm *ChannelManager) OpenChannelRequest(chandler channels.Handler) (*channels.Channel, error) {
|
||||||
|
// Some channels only allow us to open one of them per connection
|
||||||
|
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Outbound) != nil {
|
||||||
|
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := new(channels.Channel)
|
||||||
|
channel.ID = cm.nextFreeChannel
|
||||||
|
cm.nextFreeChannel += 2
|
||||||
|
channel.Type = chandler.Type()
|
||||||
|
channel.Handler = chandler
|
||||||
|
channel.Pending = true
|
||||||
|
channel.Direction = channels.Outbound
|
||||||
|
cm.channels[channel.ID] = channel
|
||||||
|
return channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenChannelRequestFromPeer constructs a channel type ready for processing given a request
|
||||||
|
// from the remote peer.
|
||||||
|
func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler channels.Handler) (*channels.Channel, error) {
|
||||||
|
if cm.isClient && (channelID%2) != 0 {
|
||||||
|
// Server is trying to open odd numbered channels
|
||||||
|
return nil, utils.ServerAttemptedToOpenEvenNumberedChannelError
|
||||||
|
} else if !cm.isClient && (channelID%2) == 0 {
|
||||||
|
// Server is trying to open odd numbered channels
|
||||||
|
return nil, utils.ClientAttemptedToOpenOddNumberedChannelError
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := cm.channels[channelID]
|
||||||
|
if exists {
|
||||||
|
return nil, utils.ChannelIDIsAlreadyInUseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some channels only allow us to open one of them per connection
|
||||||
|
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
|
||||||
|
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := new(channels.Channel)
|
||||||
|
channel.ID = channelID
|
||||||
|
channel.Type = chandler.Type()
|
||||||
|
channel.Handler = chandler
|
||||||
|
|
||||||
|
channel.Pending = true
|
||||||
|
channel.Direction = channels.Inbound
|
||||||
|
cm.channels[channelID] = channel
|
||||||
|
return channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel finds an open or pending `type` channel in the direction `way` (Inbound
|
||||||
|
// or Outbound), and returns the associated state. Returns nil if no matching channel
|
||||||
|
// exists or if multiple matching channels exist.
|
||||||
|
func (cm *ChannelManager) Channel(ctype string, way channels.Direction) *channels.Channel {
|
||||||
|
var foundChannel *channels.Channel
|
||||||
|
for _, channel := range cm.channels {
|
||||||
|
if channel.Handler.Type() == ctype && channel.Direction == way {
|
||||||
|
if foundChannel == nil {
|
||||||
|
foundChannel = channel
|
||||||
|
} else {
|
||||||
|
// we have found multiple channels.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannel finds and returns a given channel if it is found
|
||||||
|
func (cm *ChannelManager) GetChannel(channelID int32) (*channels.Channel, bool) {
|
||||||
|
channel, found := cm.channels[channelID]
|
||||||
|
return channel, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChannel removes a given channel id.
|
||||||
|
func (cm *ChannelManager) RemoveChannel(channelID int32) {
|
||||||
|
delete(cm.channels, channelID)
|
||||||
|
}
|
|
@ -0,0 +1,498 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
"github.com/s-rah/go-ricochet/wire/control"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connection encapsulates the state required to maintain a connection to
|
||||||
|
// a ricochet service.
|
||||||
|
type Connection struct {
|
||||||
|
utils.RicochetNetwork
|
||||||
|
|
||||||
|
channelManager *ChannelManager
|
||||||
|
|
||||||
|
// Ricochet Network Loop
|
||||||
|
packetChannel chan utils.RicochetData
|
||||||
|
errorChannel chan error
|
||||||
|
|
||||||
|
breakChannel chan bool
|
||||||
|
breakResultChannel chan error
|
||||||
|
|
||||||
|
unlockChannel chan bool
|
||||||
|
unlockResponseChannel chan bool
|
||||||
|
|
||||||
|
messageBuilder utils.MessageBuilder
|
||||||
|
trace bool
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
closing bool
|
||||||
|
// This mutex is exclusively for preventing races during blocking
|
||||||
|
// interactions with Process; specifically Do and Break. Don't use
|
||||||
|
// it for anything else. See those functions for an explanation.
|
||||||
|
processBlockMutex sync.Mutex
|
||||||
|
|
||||||
|
Conn io.ReadWriteCloser
|
||||||
|
IsInbound bool
|
||||||
|
Authentication map[string]bool
|
||||||
|
RemoteHostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Connection) init() {
|
||||||
|
|
||||||
|
rc.packetChannel = make(chan utils.RicochetData)
|
||||||
|
rc.errorChannel = make(chan error)
|
||||||
|
|
||||||
|
rc.breakChannel = make(chan bool)
|
||||||
|
rc.breakResultChannel = make(chan error)
|
||||||
|
|
||||||
|
rc.unlockChannel = make(chan bool)
|
||||||
|
rc.unlockResponseChannel = make(chan bool)
|
||||||
|
|
||||||
|
rc.Authentication = make(map[string]bool)
|
||||||
|
go rc.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInboundConnection creates a new Connection struct
|
||||||
|
// modelling an Inbound Connection
|
||||||
|
func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
|
||||||
|
rc := new(Connection)
|
||||||
|
rc.Conn = conn
|
||||||
|
rc.IsInbound = true
|
||||||
|
rc.init()
|
||||||
|
rc.channelManager = NewServerChannelManager()
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutboundConnection creates a new Connection struct
|
||||||
|
// modelling an Inbound Connection
|
||||||
|
func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection {
|
||||||
|
rc := new(Connection)
|
||||||
|
rc.Conn = conn
|
||||||
|
rc.IsInbound = false
|
||||||
|
rc.init()
|
||||||
|
rc.RemoteHostname = remoteHostname
|
||||||
|
rc.channelManager = NewClientChannelManager()
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Connection) TraceLog(enabled bool) {
|
||||||
|
rc.trace = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// start
|
||||||
|
func (rc *Connection) start() {
|
||||||
|
for {
|
||||||
|
packet, err := rc.RecvRicochetPacket(rc.Conn)
|
||||||
|
if err != nil {
|
||||||
|
rc.errorChannel <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rc.packetChannel <- packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do allows any function utilizing Connection to be run safely, if you're
|
||||||
|
// careful. All operations which require access (directly or indirectly) to
|
||||||
|
// Connection while Process is running need to use Do. Calls to Do without
|
||||||
|
// Process running will block unless the connection is closed, which is
|
||||||
|
// returned as ConnectionClosedError.
|
||||||
|
//
|
||||||
|
// Like a mutex, Do cannot be called recursively. This will deadlock. As
|
||||||
|
// a result, no API in this library that can be reached from the application
|
||||||
|
// should use Do, with few exceptions. This would make the API impossible
|
||||||
|
// to use safely in many cases.
|
||||||
|
//
|
||||||
|
// Do is safe to call from methods of connection.Handler and channel.Handler
|
||||||
|
// that are called by Process.
|
||||||
|
func (rc *Connection) Do(do func() error) error {
|
||||||
|
// There's a complicated little dance here to prevent a race when the
|
||||||
|
// Process call is returning for a connection error. The problem is
|
||||||
|
// that if Do simply checked rc.closed and then tried to send, it's
|
||||||
|
// possible for Process to change rc.closed and stop reading before the
|
||||||
|
// send statement is executed, creating a deadlock.
|
||||||
|
//
|
||||||
|
// To prevent this, all of the functions that block on Process should
|
||||||
|
// do so by acquiring processBlockMutex, aborting if rc.closed is true,
|
||||||
|
// performing their blocking channel operations, and then releasing the
|
||||||
|
// mutex.
|
||||||
|
//
|
||||||
|
// This works because Process will always use a separate goroutine to
|
||||||
|
// acquire processBlockMutex before changing rc.closed, and the mutex
|
||||||
|
// guarantees that no blocking channel operation can happen during or
|
||||||
|
// after the value is changed. Since these operations block the Process
|
||||||
|
// loop, the behavior of multiple concurrent calls to Do/Break doesn't
|
||||||
|
// change: they just end up blocking on the mutex before blocking on the
|
||||||
|
// channel.
|
||||||
|
rc.processBlockMutex.Lock()
|
||||||
|
defer rc.processBlockMutex.Unlock()
|
||||||
|
if rc.closed {
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force process to soft-break so we can lock
|
||||||
|
rc.traceLog("request unlocking of process loop for do()")
|
||||||
|
rc.unlockChannel <- true
|
||||||
|
rc.traceLog("process loop is unlocked for do()")
|
||||||
|
defer func() {
|
||||||
|
rc.traceLog("giving up lock process loop after do() ")
|
||||||
|
rc.unlockResponseChannel <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Process sets rc.closing when it's trying to acquire the mutex and
|
||||||
|
// close down the connection. Behave as if the connection was already
|
||||||
|
// closed.
|
||||||
|
if rc.closing {
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
return do()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoContext behaves in the same way as Do, but also respects the provided
|
||||||
|
// context when blocked, and passes the context to the callback function.
|
||||||
|
//
|
||||||
|
// DoContext should be used when any call to Do may need to be cancelled
|
||||||
|
// or timed out.
|
||||||
|
func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) error) error {
|
||||||
|
// .. see above
|
||||||
|
rc.processBlockMutex.Lock()
|
||||||
|
defer rc.processBlockMutex.Unlock()
|
||||||
|
if rc.closed {
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force process to soft-break so we can lock
|
||||||
|
rc.traceLog("request unlocking of process loop for do()")
|
||||||
|
select {
|
||||||
|
case rc.unlockChannel <- true:
|
||||||
|
break
|
||||||
|
case <-ctx.Done():
|
||||||
|
rc.traceLog("giving up on unlocking process loop for do() because context cancelled")
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.traceLog("process loop is unlocked for do()")
|
||||||
|
defer func() {
|
||||||
|
rc.traceLog("giving up lock process loop after do() ")
|
||||||
|
rc.unlockResponseChannel <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
if rc.closing {
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
return do(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestOpenChannel sends an OpenChannel message to the remote client.
|
||||||
|
// An error is returned only if the requirements for opening this channel
|
||||||
|
// are not met on the local side (a nil error return does not mean the
|
||||||
|
// channel was opened successfully, because channels open asynchronously).
|
||||||
|
func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler) (*channels.Channel, error) {
|
||||||
|
rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
|
||||||
|
|
||||||
|
// Check that we have the authentication already
|
||||||
|
if handler.RequiresAuthentication() != "none" {
|
||||||
|
// Enforce Authentication Check.
|
||||||
|
_, authed := rc.Authentication[handler.RequiresAuthentication()]
|
||||||
|
if !authed {
|
||||||
|
return nil, utils.UnauthorizedActionError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := rc.channelManager.OpenChannelRequest(handler)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.SendMessage = func(message []byte) {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, channel.ID, message)
|
||||||
|
}
|
||||||
|
channel.DelegateAuthorization = func() {
|
||||||
|
rc.Authentication[handler.Type()] = true
|
||||||
|
}
|
||||||
|
channel.CloseChannel = func() {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
|
||||||
|
rc.channelManager.RemoveChannel(channel.ID)
|
||||||
|
}
|
||||||
|
response, err := handler.OpenOutbound(channel)
|
||||||
|
if err == nil {
|
||||||
|
rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype))
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
||||||
|
} else {
|
||||||
|
rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
|
||||||
|
rc.channelManager.RemoveChannel(channel.ID)
|
||||||
|
}
|
||||||
|
return channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUserCallback should be used to wrap any calls into handlers or
|
||||||
|
// application code from the Process goroutine. It handles calls to Do
|
||||||
|
// from within that code to prevent deadlocks.
|
||||||
|
func (rc *Connection) processUserCallback(cb func()) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
cb()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-rc.unlockChannel:
|
||||||
|
<-rc.unlockResponseChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process receives socket and protocol events for the connection. Methods
|
||||||
|
// of the application-provided `handler` will be called from this goroutine
|
||||||
|
// for all events.
|
||||||
|
//
|
||||||
|
// Process must be running in order to handle any events on the connection,
|
||||||
|
// including connection close.
|
||||||
|
//
|
||||||
|
// Process blocks until the connection is closed or until Break() is called.
|
||||||
|
// If the connection is closed, a non-nil error is returned.
|
||||||
|
func (rc *Connection) Process(handler Handler) error {
|
||||||
|
if rc.closed {
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
rc.traceLog("entering process loop")
|
||||||
|
rc.processUserCallback(func() { handler.OnReady(rc) })
|
||||||
|
|
||||||
|
// There are exactly two ways out of this loop: a signal on breakChannel
|
||||||
|
// caused by a call to Break, or a connection-fatal error on errorChannel.
|
||||||
|
//
|
||||||
|
// In the Break case, no particular care is necessary; it is the caller's
|
||||||
|
// responsibility to make sure there aren't e.g. concurrent calls to Do.
|
||||||
|
//
|
||||||
|
// Because connection errors can happen spontaneously, they must carefully
|
||||||
|
// prevent concurrent calls to Break or Do that could deadlock when Process
|
||||||
|
// returns.
|
||||||
|
for {
|
||||||
|
|
||||||
|
var packet utils.RicochetData
|
||||||
|
select {
|
||||||
|
case <-rc.unlockChannel:
|
||||||
|
<-rc.unlockResponseChannel
|
||||||
|
continue
|
||||||
|
case <-rc.breakChannel:
|
||||||
|
rc.traceLog("process has ended after break")
|
||||||
|
rc.breakResultChannel <- nil
|
||||||
|
return nil
|
||||||
|
case packet = <-rc.packetChannel:
|
||||||
|
break
|
||||||
|
case err := <-rc.errorChannel:
|
||||||
|
rc.Conn.Close()
|
||||||
|
rc.closing = true
|
||||||
|
|
||||||
|
// In order to safely close down concurrent calls to Do or Break,
|
||||||
|
// processBlockMutex must be held before setting rc.closed. That cannot
|
||||||
|
// happen in this goroutine, because one of those calls may already hold
|
||||||
|
// the mutex and be blocking on a channel send to this method. So the
|
||||||
|
// process here is to have a goroutine acquire the lock, set rc.closed, and
|
||||||
|
// signal back. Meanwhile, this one keeps handling unlockChannel and
|
||||||
|
// breakChannel.
|
||||||
|
closedChan := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
rc.processBlockMutex.Lock()
|
||||||
|
defer rc.processBlockMutex.Unlock()
|
||||||
|
rc.closed = true
|
||||||
|
close(closedChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Keep accepting calls from Do or Break until closedChan signals that they're
|
||||||
|
// safely shut down.
|
||||||
|
clearLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-rc.unlockChannel:
|
||||||
|
<-rc.unlockResponseChannel
|
||||||
|
case <-rc.breakChannel:
|
||||||
|
rc.breakResultChannel <- utils.ConnectionClosedError
|
||||||
|
case <-closedChan:
|
||||||
|
break clearLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the one case where processUserCallback isn't necessary, because
|
||||||
|
// all calls to Do immediately return ConnectionClosedError now.
|
||||||
|
handler.OnClosed(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Channel == 0 {
|
||||||
|
rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
|
||||||
|
res := new(Protocol_Data_Control.Packet)
|
||||||
|
err := proto.Unmarshal(packet.Data[:], res)
|
||||||
|
if err == nil {
|
||||||
|
// Wrap controlPacket in processUserCallback, since it calls out in many
|
||||||
|
// places, and wrapping the rest is harmless.
|
||||||
|
rc.processUserCallback(func() { rc.controlPacket(handler, res) })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Let's check to see if we have defined this channel.
|
||||||
|
channel, found := rc.channelManager.GetChannel(packet.Channel)
|
||||||
|
if found {
|
||||||
|
if len(packet.Data) == 0 {
|
||||||
|
rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
|
||||||
|
rc.channelManager.RemoveChannel(packet.Channel)
|
||||||
|
rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) })
|
||||||
|
} else {
|
||||||
|
rc.traceLog(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel))
|
||||||
|
// Send The Ricochet Packet to the Handler
|
||||||
|
rc.processUserCallback(func() { channel.Handler.Packet(packet.Data[:]) })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When a non-zero packet is received for an unknown
|
||||||
|
// channel, the recipient responds by closing
|
||||||
|
// that channel.
|
||||||
|
rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
|
||||||
|
if len(packet.Data) != 0 {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.Packet) {
|
||||||
|
|
||||||
|
if res.GetOpenChannel() != nil {
|
||||||
|
|
||||||
|
opm := res.GetOpenChannel()
|
||||||
|
chandler, err := handler.OnOpenChannelRequest(opm.GetChannelType())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "UnknownTypeError")
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we have the authentication already
|
||||||
|
if chandler.RequiresAuthentication() != "none" {
|
||||||
|
rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication()))
|
||||||
|
// Enforce Authentication Check.
|
||||||
|
_, authed := rc.Authentication[chandler.RequiresAuthentication()]
|
||||||
|
if !authed {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
|
||||||
|
rc.traceLog(fmt.Sprintf("do not have required authorization to open channel type %v", chandler.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rc.traceLog("succeeded authorization check")
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
channel.SendMessage = func(message []byte) {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, channel.ID, message)
|
||||||
|
}
|
||||||
|
channel.DelegateAuthorization = func() {
|
||||||
|
rc.Authentication[chandler.Type()] = true
|
||||||
|
}
|
||||||
|
channel.CloseChannel = func() {
|
||||||
|
rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
|
||||||
|
rc.channelManager.RemoveChannel(channel.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := chandler.OpenInbound(channel, opm)
|
||||||
|
if err == nil && channel.Pending == false {
|
||||||
|
rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID))
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
||||||
|
} else {
|
||||||
|
rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID))
|
||||||
|
rc.channelManager.RemoveChannel(channel.ID)
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send Error Packet
|
||||||
|
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "GenericError")
|
||||||
|
rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if res.GetChannelResult() != nil {
|
||||||
|
cr := res.GetChannelResult()
|
||||||
|
id := cr.GetChannelIdentifier()
|
||||||
|
|
||||||
|
channel, found := rc.channelManager.GetChannel(id)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cr.GetOpened() {
|
||||||
|
rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
|
||||||
|
channel.Handler.OpenOutboundResult(nil, cr)
|
||||||
|
} else {
|
||||||
|
rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
|
||||||
|
channel.Handler.OpenOutboundResult(errors.New(""), cr)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if res.GetKeepAlive() != nil {
|
||||||
|
// XXX Though not currently part of the protocol
|
||||||
|
// We should likely put these calls behind
|
||||||
|
// authentication.
|
||||||
|
rc.traceLog("received keep alive packet")
|
||||||
|
if res.GetKeepAlive().GetResponseRequested() {
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
raw := messageBuilder.KeepAlive(true)
|
||||||
|
rc.traceLog("sending keep alive response")
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, raw)
|
||||||
|
}
|
||||||
|
} else if res.GetEnableFeatures() != nil {
|
||||||
|
rc.traceLog("received features enabled packet")
|
||||||
|
messageBuilder := new(utils.MessageBuilder)
|
||||||
|
raw := messageBuilder.FeaturesEnabled([]string{})
|
||||||
|
rc.traceLog("sending featured enabled empty response")
|
||||||
|
rc.SendRicochetPacket(rc.Conn, 0, raw)
|
||||||
|
} else if res.GetFeaturesEnabled() != nil {
|
||||||
|
// TODO We should never send out an enabled features
|
||||||
|
// request.
|
||||||
|
rc.traceLog("sending unsolicited features enabled response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Connection) traceLog(message string) {
|
||||||
|
if rc.trace {
|
||||||
|
log.Printf(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break causes Process() to return, but does not close the underlying connection
|
||||||
|
// Break returns an error if it would not be valid to call Process() again for
|
||||||
|
// the connection now. Currently, the only such error is ConnectionClosedError.
|
||||||
|
func (rc *Connection) Break() error {
|
||||||
|
// See Do() for an explanation of the concurrency here; it's complicated.
|
||||||
|
// The summary is that this mutex prevents races on connection close that
|
||||||
|
// could lead to deadlocks in Block().
|
||||||
|
rc.processBlockMutex.Lock()
|
||||||
|
defer rc.processBlockMutex.Unlock()
|
||||||
|
if rc.closed {
|
||||||
|
rc.traceLog("ignoring break because connection is already closed")
|
||||||
|
return utils.ConnectionClosedError
|
||||||
|
}
|
||||||
|
rc.traceLog("breaking out of process loop")
|
||||||
|
rc.breakChannel <- true
|
||||||
|
return <-rc.breakResultChannel // Wait for Process to End
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel is a convienciance method for returning a given channel to the caller
|
||||||
|
// of Process() - TODO - this is kind of ugly.
|
||||||
|
func (rc *Connection) Channel(ctype string, way channels.Direction) *channels.Channel {
|
||||||
|
return rc.channelManager.Channel(ctype, way)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler reacts to low-level events on a protocol connection.
|
||||||
|
// There should be a unique instance of a ConnectionHandler type per
|
||||||
|
// OpenConnection.
|
||||||
|
type Handler interface {
|
||||||
|
// OnReady is called when the connection begins using this handler.
|
||||||
|
OnReady(oc *Connection)
|
||||||
|
|
||||||
|
// OnClosed is called when the OpenConnection has closed for any reason.
|
||||||
|
OnClosed(err error)
|
||||||
|
|
||||||
|
// OpenChannelRequest is called when the peer asks to open a channel of
|
||||||
|
// `type`. `raw` contains the protocol OpenChannel message including any
|
||||||
|
// extension data. If this channel type is recognized and allowed by this
|
||||||
|
// connection in this state, return a type implementing ChannelHandler for
|
||||||
|
// events related to this channel. Returning an error or nil rejects the
|
||||||
|
// channel.
|
||||||
|
//
|
||||||
|
// Channel type handlers may implement additional state and sanity checks.
|
||||||
|
// A non-nil return from this function does not guarantee that the channel
|
||||||
|
// will be opened.
|
||||||
|
OnOpenChannelRequest(ctype string) (channels.Handler, error)
|
||||||
|
}
|
89
vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go
generated
vendored
Normal file
89
vendor/github.com/s-rah/go-ricochet/connection/inboundconnectionhandler.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/policies"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InboundConnectionHandler is a convieniance wrapper for handling inbound
|
||||||
|
// connections
|
||||||
|
type InboundConnectionHandler struct {
|
||||||
|
connection *Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleInboundConnection returns an InboundConnectionHandler given a connection
|
||||||
|
func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
|
||||||
|
ich := new(InboundConnectionHandler)
|
||||||
|
ich.connection = c
|
||||||
|
return ich
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessAuthAsServer blocks until authentication has succeeded, failed, or the
|
||||||
|
// connection is closed. A non-nil error is returned in all cases other than successful
|
||||||
|
// and accepted authentication.
|
||||||
|
//
|
||||||
|
// ProcessAuthAsServer cannot be called at the same time as any other call to a Process
|
||||||
|
// function. Another Process function must be called after this function successfully
|
||||||
|
// returns to continue handling connection events.
|
||||||
|
//
|
||||||
|
// The acceptCallback function is called after receiving a valid authentication proof
|
||||||
|
// with the client's authenticated hostname and public key. acceptCallback must return
|
||||||
|
// true to accept authentication and allow the connection to continue, and also returns a
|
||||||
|
// boolean indicating whether the contact is known and recognized. Unknown contacts will
|
||||||
|
// 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 {
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return utils.PrivateKeyNotSetError
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakOnce sync.Once
|
||||||
|
|
||||||
|
var authAllowed, authKnown bool
|
||||||
|
var authHostname string
|
||||||
|
|
||||||
|
onAuthValid := func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
|
||||||
|
authAllowed, authKnown = sach(hostname, publicKey)
|
||||||
|
if authAllowed {
|
||||||
|
authHostname = hostname
|
||||||
|
}
|
||||||
|
breakOnce.Do(func() { go ich.connection.Break() })
|
||||||
|
return authAllowed, authKnown
|
||||||
|
}
|
||||||
|
onAuthInvalid := func(err error) {
|
||||||
|
// err is ignored at the moment
|
||||||
|
breakOnce.Do(func() { go ich.connection.Break() })
|
||||||
|
}
|
||||||
|
|
||||||
|
ach := new(AutoConnectionHandler)
|
||||||
|
ach.Init()
|
||||||
|
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service",
|
||||||
|
func() channels.Handler {
|
||||||
|
return &channels.HiddenServiceAuthChannel{
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
ServerAuthValid: onAuthValid,
|
||||||
|
ServerAuthInvalid: onAuthInvalid,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensure that the call to Process() cannot outlive this function,
|
||||||
|
// particularly for the case where the policy timeout expires
|
||||||
|
defer breakOnce.Do(func() { ich.connection.Break() })
|
||||||
|
policy := policies.UnknownPurposeTimeout
|
||||||
|
err := policy.ExecuteAction(func() error {
|
||||||
|
return ich.connection.Process(ach)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if authAllowed == true {
|
||||||
|
ich.connection.RemoteHostname = authHostname
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return utils.ClientFailedToAuthenticateError
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
90
vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go
generated
vendored
Normal file
90
vendor/github.com/s-rah/go-ricochet/connection/outboundconnectionhandler.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/policies"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutboundConnectionHandler is a convieniance wrapper for handling outbound
|
||||||
|
// connections
|
||||||
|
type OutboundConnectionHandler struct {
|
||||||
|
connection *Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleOutboundConnection returns an OutboundConnectionHandler given a connection
|
||||||
|
func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
|
||||||
|
och := new(OutboundConnectionHandler)
|
||||||
|
och.connection = c
|
||||||
|
return och
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessAuthAsClient blocks until authentication has succeeded or failed with the
|
||||||
|
// provided privateKey, or the connection is closed. A non-nil error is returned in all
|
||||||
|
// cases other than successful authentication.
|
||||||
|
//
|
||||||
|
// ProcessAuthAsClient cannot be called at the same time as any other call to a Porcess
|
||||||
|
// function. Another Process function must be called after this function successfully
|
||||||
|
// returns to continue handling connection events.
|
||||||
|
//
|
||||||
|
// For successful authentication, the `known` return value indicates whether the peer
|
||||||
|
// accepts us as a known contact. Unknown contacts will generally need to send a contact
|
||||||
|
// request before any other activity.
|
||||||
|
func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.PrivateKey) (bool, error) {
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return false, utils.PrivateKeyNotSetError
|
||||||
|
}
|
||||||
|
|
||||||
|
ach := new(AutoConnectionHandler)
|
||||||
|
ach.Init()
|
||||||
|
|
||||||
|
// Make sure that calls to Break in this function cannot race
|
||||||
|
var breakOnce sync.Once
|
||||||
|
|
||||||
|
var accepted, isKnownContact bool
|
||||||
|
authCallback := func(accept, known bool) {
|
||||||
|
accepted = accept
|
||||||
|
isKnownContact = known
|
||||||
|
// Cause the Process() call below to return.
|
||||||
|
// If Break() is called from here, it _must_ use go, because this will
|
||||||
|
// execute in the Process goroutine, and Break() will deadlock.
|
||||||
|
breakOnce.Do(func() { go och.connection.Break() })
|
||||||
|
}
|
||||||
|
|
||||||
|
processResult := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// Break Process() if timed out; no-op if Process returned a conn error
|
||||||
|
defer func() { breakOnce.Do(func() { och.connection.Break() }) }()
|
||||||
|
policy := policies.UnknownPurposeTimeout
|
||||||
|
err := policy.ExecuteAction(func() error {
|
||||||
|
return och.connection.Process(ach)
|
||||||
|
})
|
||||||
|
processResult <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := och.connection.Do(func() error {
|
||||||
|
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service",
|
||||||
|
&channels.HiddenServiceAuthChannel{
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
ServerHostname: och.connection.RemoteHostname,
|
||||||
|
ClientAuthResult: authCallback,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
breakOnce.Do(func() { och.connection.Break() })
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = <-processResult; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if accepted == true {
|
||||||
|
return isKnownContact, nil
|
||||||
|
}
|
||||||
|
return false, utils.ServerRejectedClientConnectionError
|
||||||
|
}
|
|
@ -2,49 +2,104 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/s-rah/go-ricochet"
|
"github.com/s-rah/go-ricochet"
|
||||||
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
|
"github.com/s-rah/go-ricochet/connection"
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EchoBotService is an example service which simply echoes back what a client
|
// EchoBotService is an example service which simply echoes back what a client
|
||||||
// sends it.
|
// sends it.
|
||||||
type EchoBotService struct {
|
type RicochetEchoBot struct {
|
||||||
goricochet.StandardRicochetService
|
connection.AutoConnectionHandler
|
||||||
|
messages chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ebs *EchoBotService) OnNewConnection(oc *goricochet.OpenConnection) {
|
func (echobot *RicochetEchoBot) ContactRequest(name string, message string) string {
|
||||||
ebs.StandardRicochetService.OnNewConnection(oc)
|
return "Pending"
|
||||||
go oc.Process(&EchoBotConnection{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EchoBotConnection struct {
|
func (echobot *RicochetEchoBot) ContactRequestRejected() {
|
||||||
goricochet.StandardRicochetConnection
|
}
|
||||||
|
func (echobot *RicochetEchoBot) ContactRequestAccepted() {
|
||||||
|
}
|
||||||
|
func (echobot *RicochetEchoBot) ContactRequestError() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsKnownContact is configured to always accept Contact Requests
|
func (echobot *RicochetEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool {
|
||||||
func (ebc *EchoBotConnection) IsKnownContact(hostname string) bool {
|
echobot.messages <- message
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnContactRequest - we always accept new contact request.
|
func (echobot *RicochetEchoBot) ChatMessageAck(messageID uint32) {
|
||||||
func (ebc *EchoBotConnection) OnContactRequest(channelID int32, nick string, message string) {
|
|
||||||
ebc.StandardRicochetConnection.OnContactRequest(channelID, nick, message)
|
|
||||||
ebc.Conn.AckContactRequestOnResponse(channelID, "Accepted")
|
|
||||||
ebc.Conn.CloseChannel(channelID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnChatMessage we acknowledge the message, grab the message content and send it back - opening
|
func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string) {
|
||||||
// a new channel if necessary.
|
|
||||||
func (ebc *EchoBotConnection) OnChatMessage(channelID int32, messageID int32, message string) {
|
privateKey, _ := utils.LoadPrivateKeyFromFile(privateKeyFile)
|
||||||
log.Printf("Received Message from %s: %s", ebc.Conn.OtherHostname, message)
|
echobot.messages = make(chan string)
|
||||||
ebc.Conn.AckChatMessage(channelID, messageID)
|
|
||||||
if ebc.Conn.GetChannelType(6) == "none" {
|
echobot.Init()
|
||||||
ebc.Conn.OpenChatChannel(6)
|
echobot.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler {
|
||||||
|
contact := new(channels.ContactRequestChannel)
|
||||||
|
contact.Handler = echobot
|
||||||
|
return contact
|
||||||
|
})
|
||||||
|
echobot.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
|
||||||
|
chat := new(channels.ChatChannel)
|
||||||
|
chat.Handler = echobot
|
||||||
|
return chat
|
||||||
|
})
|
||||||
|
|
||||||
|
rc, _ := goricochet.Open(hostname)
|
||||||
|
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(privateKey)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
go rc.Process(echobot)
|
||||||
|
|
||||||
|
if !known {
|
||||||
|
err := rc.Do(func() error {
|
||||||
|
_, err := rc.RequestOpenChannel("im.ricochet.contact.request",
|
||||||
|
&channels.ContactRequestChannel{
|
||||||
|
Handler: echobot,
|
||||||
|
Name: "EchoBot",
|
||||||
|
Message: "I LIVE 😈😈!!!!",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not contact %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Do(func() error {
|
||||||
|
_, err := rc.RequestOpenChannel("im.ricochet.chat", &channels.ChatChannel{Handler: echobot})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
for {
|
||||||
|
message := <-echobot.messages
|
||||||
|
log.Printf("Received Message: %s", message)
|
||||||
|
rc.Do(func() error {
|
||||||
|
log.Printf("Finding Chat Channel")
|
||||||
|
channel := rc.Channel("im.ricochet.chat", channels.Outbound)
|
||||||
|
if channel != nil {
|
||||||
|
log.Printf("Found Chat Channel")
|
||||||
|
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
|
||||||
|
if ok {
|
||||||
|
chatchannel.SendMessage(message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Could not find chat channel")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ebc.Conn.SendMessage(6, message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ricochetService := new(EchoBotService)
|
echoBot := new(RicochetEchoBot)
|
||||||
ricochetService.Init("./private_key")
|
echoBot.Connect("private_key", "oqf7z4ot6kuejgam")
|
||||||
ricochetService.Listen(ricochetService, 12345)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
package goricochet
|
|
||||||
|
|
||||||
// ServiceHandler is the interface to handle events for an inbound connection listener
|
|
||||||
type ServiceHandler interface {
|
|
||||||
// OnNewConnection is called for inbound connections to the service after protocol
|
|
||||||
// version negotiation has completed successfully.
|
|
||||||
OnNewConnection(oc *OpenConnection)
|
|
||||||
// OnFailedConnection is called for inbound connections to the service which fail
|
|
||||||
// to successfully complete version negotiation for any reason.
|
|
||||||
OnFailedConnection(err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionHandler is the interface to handle events for an open protocol connection,
|
|
||||||
// whether inbound or outbound. Each OpenConnection will need its own instance of an
|
|
||||||
// application type implementing ConnectionHandler, which could also be used to store
|
|
||||||
// application state related to the connection.
|
|
||||||
type ConnectionHandler interface {
|
|
||||||
// OnReady is called before OpenConnection.Process() begins from the connection
|
|
||||||
OnReady(oc *OpenConnection)
|
|
||||||
// OnDisconnect is called when the connection is closed, just before
|
|
||||||
// OpenConnection.Process() returns
|
|
||||||
OnDisconnect()
|
|
||||||
|
|
||||||
// Authentication Management
|
|
||||||
OnAuthenticationRequest(channelID int32, clientCookie [16]byte)
|
|
||||||
OnAuthenticationChallenge(channelID int32, serverCookie [16]byte)
|
|
||||||
OnAuthenticationProof(channelID int32, publicKey []byte, signature []byte)
|
|
||||||
OnAuthenticationResult(channelID int32, result bool, isKnownContact bool)
|
|
||||||
|
|
||||||
// Contact Management
|
|
||||||
IsKnownContact(hostname string) bool
|
|
||||||
OnContactRequest(channelID int32, nick string, message string)
|
|
||||||
OnContactRequestAck(channelID int32, status string)
|
|
||||||
|
|
||||||
// Managing Channels
|
|
||||||
OnOpenChannelRequest(channelID int32, channelType string)
|
|
||||||
OnOpenChannelRequestSuccess(channelID int32)
|
|
||||||
OnChannelClosed(channelID int32)
|
|
||||||
|
|
||||||
// Chat Messages
|
|
||||||
OnChatMessage(channelID int32, messageID int32, message string)
|
|
||||||
OnChatMessageAck(channelID int32, messageID int32)
|
|
||||||
|
|
||||||
// Handle Errors
|
|
||||||
OnFailedChannelOpen(channelID int32, errorType string)
|
|
||||||
OnGenericError(channelID int32)
|
|
||||||
OnUnknownTypeError(channelID int32)
|
|
||||||
OnUnauthorizedError(channelID int32)
|
|
||||||
OnBadUsageError(channelID int32)
|
|
||||||
OnFailedError(channelID int32)
|
|
||||||
}
|
|
|
@ -1,534 +0,0 @@
|
||||||
package goricochet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/asn1"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/s-rah/go-ricochet/auth"
|
|
||||||
"github.com/s-rah/go-ricochet/chat"
|
|
||||||
"github.com/s-rah/go-ricochet/contact"
|
|
||||||
"github.com/s-rah/go-ricochet/control"
|
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenConnection encapsulates the state required to maintain a connection to
|
|
||||||
// a ricochet service.
|
|
||||||
// Notably OpenConnection does not enforce limits on the channelIDs, channel Assignments
|
|
||||||
// or the direction of messages. These are considered to be service enforced rules.
|
|
||||||
// (and services are considered to be the best to define them).
|
|
||||||
type OpenConnection struct {
|
|
||||||
conn net.Conn
|
|
||||||
authHandler map[int32]*AuthenticationHandler
|
|
||||||
channels map[int32]string
|
|
||||||
rni utils.RicochetNetworkInterface
|
|
||||||
|
|
||||||
Client bool
|
|
||||||
IsAuthed bool
|
|
||||||
MyHostname string
|
|
||||||
OtherHostname string
|
|
||||||
Closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes a OpenConnection object to a default state.
|
|
||||||
func (oc *OpenConnection) Init(outbound bool, conn net.Conn) {
|
|
||||||
oc.conn = conn
|
|
||||||
oc.authHandler = make(map[int32]*AuthenticationHandler)
|
|
||||||
oc.channels = make(map[int32]string)
|
|
||||||
oc.rni = new(utils.RicochetNetwork)
|
|
||||||
|
|
||||||
oc.Client = outbound
|
|
||||||
oc.IsAuthed = false
|
|
||||||
oc.MyHostname = ""
|
|
||||||
oc.OtherHostname = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsetChannel removes a type association from the channel.
|
|
||||||
func (oc *OpenConnection) UnsetChannel(channel int32) {
|
|
||||||
oc.channels[channel] = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChannelType returns the type of the channel on this connection
|
|
||||||
func (oc *OpenConnection) GetChannelType(channel int32) string {
|
|
||||||
if val, ok := oc.channels[channel]; ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oc *OpenConnection) setChannel(channel int32, channelType string) {
|
|
||||||
oc.channels[channel] = channelType
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasChannel returns true if the connection has a channel of an associated type, false otherwise
|
|
||||||
func (oc *OpenConnection) HasChannel(channelType string) bool {
|
|
||||||
for _, val := range oc.channels {
|
|
||||||
if val == channelType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseChannel closes a given channel
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
func (oc *OpenConnection) CloseChannel(channel int32) {
|
|
||||||
oc.UnsetChannel(channel)
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, []byte{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the entire connection
|
|
||||||
func (oc *OpenConnection) Close() {
|
|
||||||
oc.conn.Close()
|
|
||||||
oc.Closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate opens an Authentication Channel and send a client cookie
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
func (oc *OpenConnection) Authenticate(channel int32) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
oc.authHandler[channel] = new(AuthenticationHandler)
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.OpenAuthenticationChannel(channel, oc.authHandler[channel].GenClientCookie())
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.auth.hidden-service")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfirmAuthChannel responds to a new authentication request.
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
func (oc *OpenConnection) ConfirmAuthChannel(channel int32, clientCookie [16]byte) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
oc.authHandler[channel] = new(AuthenticationHandler)
|
|
||||||
oc.authHandler[channel].AddClientCookie(clientCookie[:])
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.ConfirmAuthChannel(channel, oc.authHandler[channel].GenServerCookie())
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.auth.hidden-service")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendProof sends an authentication proof in response to a challenge.
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
// * channel must be of type auth
|
|
||||||
func (oc *OpenConnection) SendProof(channel int32, serverCookie [16]byte, publicKeyBytes []byte, privateKey *rsa.PrivateKey) {
|
|
||||||
|
|
||||||
if oc.authHandler[channel] == nil {
|
|
||||||
return // NoOp
|
|
||||||
}
|
|
||||||
|
|
||||||
oc.authHandler[channel].AddServerCookie(serverCookie[:])
|
|
||||||
|
|
||||||
challenge := oc.authHandler[channel].GenChallenge(oc.MyHostname, oc.OtherHostname)
|
|
||||||
signature, _ := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA256, challenge)
|
|
||||||
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.Proof(publicKeyBytes, signature)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateProof determines if the given public key and signature align with the
|
|
||||||
// already established challenge vector for this communication
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
// * Client and Server must have already sent their respective cookies (Authenticate and ConfirmAuthChannel)
|
|
||||||
func (oc *OpenConnection) ValidateProof(channel int32, publicKeyBytes []byte, signature []byte) bool {
|
|
||||||
|
|
||||||
if oc.authHandler[channel] == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionalHostname := utils.GetTorHostname(publicKeyBytes)
|
|
||||||
publicKey := new(rsa.PublicKey)
|
|
||||||
_, err := asn1.Unmarshal(publicKeyBytes, publicKey)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
challenge := oc.authHandler[channel].GenChallenge(provisionalHostname, oc.MyHostname)
|
|
||||||
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, challenge[:], signature)
|
|
||||||
if err == nil {
|
|
||||||
oc.OtherHostname = provisionalHostname
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendAuthenticationResult responds to an existed authentication Proof
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
// * channel must be of type auth
|
|
||||||
func (oc *OpenConnection) SendAuthenticationResult(channel int32, accepted bool, isKnownContact bool) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.AuthResult(accepted, isKnownContact)
|
|
||||||
utils.CheckError(err)
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenChatChannel opens a new chat channel with the given id
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
// * If acting as the client, id must be odd, else even
|
|
||||||
func (oc *OpenConnection) OpenChatChannel(channel int32) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.OpenChannel(channel, "im.ricochet.chat")
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.chat")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenChannel opens a new chat channel with the given id
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected to a service
|
|
||||||
// * If acting as the client, id must be odd, else even
|
|
||||||
func (oc *OpenConnection) OpenChannel(channel int32, channelType string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.OpenChannel(channel, channelType)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, channelType)
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AckOpenChannel acknowledges a previously received open channel message
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
func (oc *OpenConnection) AckOpenChannel(channel int32, channeltype string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
|
|
||||||
data, err := messageBuilder.AckOpenChannel(channel)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, channeltype)
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RejectOpenChannel acknowledges a rejects a previously received open channel message
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected
|
|
||||||
func (oc *OpenConnection) RejectOpenChannel(channel int32, errortype string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.RejectOpenChannel(channel, errortype)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendContactRequest initiates a contact request to the server.
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
func (oc *OpenConnection) SendContactRequest(channel int32, nick string, message string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.OpenContactRequestChannel(channel, nick, message)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.contact.request")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AckContactRequestOnResponse responds a contact request from a client
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
// * Must have previously received a Contact Request
|
|
||||||
func (oc *OpenConnection) AckContactRequestOnResponse(channel int32, status string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.ReplyToContactRequestOnResponse(channel, status)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.contact.request")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AckContactRequest responds to contact request from a client
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
// * Must have previously received a Contact Request
|
|
||||||
func (oc *OpenConnection) AckContactRequest(channel int32, status string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.ReplyToContactRequest(channel, status)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.setChannel(channel, "im.ricochet.contact.request")
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AckChatMessage acknowledges a previously received chat message.
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
// * Must have established a known contact status with the other service
|
|
||||||
// * Must have received a Chat message on an open im.ricochet.chat channel with the messageID
|
|
||||||
func (oc *OpenConnection) AckChatMessage(channel int32, messageID int32) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.AckChatMessage(messageID)
|
|
||||||
utils.CheckError(err)
|
|
||||||
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMessage sends a Chat Message (message) to a give Channel (channel).
|
|
||||||
// Prerequisites:
|
|
||||||
// * Must have previously connected and authenticated to a service
|
|
||||||
// * Must have established a known contact status with the other service
|
|
||||||
// * Must have previously opened channel with OpenChanel of type im.ricochet.chat
|
|
||||||
func (oc *OpenConnection) SendMessage(channel int32, message string) {
|
|
||||||
defer utils.RecoverFromError()
|
|
||||||
messageBuilder := new(MessageBuilder)
|
|
||||||
data, err := messageBuilder.ChatMessage(message, 0)
|
|
||||||
utils.CheckError(err)
|
|
||||||
oc.rni.SendRicochetPacket(oc.conn, channel, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process waits for new messages to arrive from the connection and uses the given
|
|
||||||
// ConnectionHandler to process them.
|
|
||||||
func (oc *OpenConnection) Process(handler ConnectionHandler) {
|
|
||||||
handler.OnReady(oc)
|
|
||||||
defer oc.Close()
|
|
||||||
defer handler.OnDisconnect()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if oc.Closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := oc.rni.RecvRicochetPacket(oc.conn)
|
|
||||||
if err != nil {
|
|
||||||
oc.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(packet.Data) == 0 {
|
|
||||||
handler.OnChannelClosed(packet.Channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Channel == 0 {
|
|
||||||
|
|
||||||
res := new(Protocol_Data_Control.Packet)
|
|
||||||
err := proto.Unmarshal(packet.Data[:], res)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
handler.OnGenericError(packet.Channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.GetOpenChannel() != nil {
|
|
||||||
opm := res.GetOpenChannel()
|
|
||||||
|
|
||||||
if oc.GetChannelType(opm.GetChannelIdentifier()) != "none" {
|
|
||||||
// Channel is already in use.
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If I am a Client, the server can only open even numbered channels
|
|
||||||
if oc.Client && opm.GetChannelIdentifier()%2 != 0 {
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If I am a Server, the client can only open odd numbered channels
|
|
||||||
if !oc.Client && opm.GetChannelIdentifier()%2 != 1 {
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch opm.GetChannelType() {
|
|
||||||
case "im.ricochet.auth.hidden-service":
|
|
||||||
if oc.Client {
|
|
||||||
// Servers are authed by default and can't auth with hidden-service
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
} else if oc.IsAuthed {
|
|
||||||
// Can't auth if already authed
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
} else if oc.HasChannel("im.ricochet.auth.hidden-service") {
|
|
||||||
// Can't open more than 1 auth channel
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
} else {
|
|
||||||
clientCookie, err := proto.GetExtension(opm, Protocol_Data_AuthHiddenService.E_ClientCookie)
|
|
||||||
if err == nil {
|
|
||||||
clientCookieB := [16]byte{}
|
|
||||||
copy(clientCookieB[:], clientCookie.([]byte)[:])
|
|
||||||
handler.OnAuthenticationRequest(opm.GetChannelIdentifier(), clientCookieB)
|
|
||||||
} else {
|
|
||||||
// Must include Client Cookie
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "im.ricochet.chat":
|
|
||||||
if !oc.IsAuthed {
|
|
||||||
// Can't open chat channel if not authorized
|
|
||||||
handler.OnUnauthorizedError(opm.GetChannelIdentifier())
|
|
||||||
} else if !handler.IsKnownContact(oc.OtherHostname) {
|
|
||||||
// Can't open chat channel if not a known contact
|
|
||||||
handler.OnUnauthorizedError(opm.GetChannelIdentifier())
|
|
||||||
} else {
|
|
||||||
handler.OnOpenChannelRequest(opm.GetChannelIdentifier(), "im.ricochet.chat")
|
|
||||||
}
|
|
||||||
case "im.ricochet.contact.request":
|
|
||||||
if oc.Client {
|
|
||||||
// Servers are not allowed to send contact requests
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
} else if !oc.IsAuthed {
|
|
||||||
// Can't open a contact channel if not authed
|
|
||||||
handler.OnUnauthorizedError(opm.GetChannelIdentifier())
|
|
||||||
} else if oc.HasChannel("im.ricochet.contact.request") {
|
|
||||||
// Only 1 contact channel is allowed to be open at a time
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
} else {
|
|
||||||
contactRequestI, err := proto.GetExtension(opm, Protocol_Data_ContactRequest.E_ContactRequest)
|
|
||||||
if err == nil {
|
|
||||||
contactRequest, check := contactRequestI.(*Protocol_Data_ContactRequest.ContactRequest)
|
|
||||||
if check {
|
|
||||||
handler.OnContactRequest(opm.GetChannelIdentifier(), contactRequest.GetNickname(), contactRequest.GetMessageText())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.OnBadUsageError(opm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
handler.OnUnknownTypeError(opm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
} else if res.GetChannelResult() != nil {
|
|
||||||
crm := res.GetChannelResult()
|
|
||||||
if crm.GetOpened() {
|
|
||||||
switch oc.GetChannelType(crm.GetChannelIdentifier()) {
|
|
||||||
case "im.ricochet.auth.hidden-service":
|
|
||||||
serverCookie, err := proto.GetExtension(crm, Protocol_Data_AuthHiddenService.E_ServerCookie)
|
|
||||||
if err == nil {
|
|
||||||
serverCookieB := [16]byte{}
|
|
||||||
copy(serverCookieB[:], serverCookie.([]byte)[:])
|
|
||||||
handler.OnAuthenticationChallenge(crm.GetChannelIdentifier(), serverCookieB)
|
|
||||||
} else {
|
|
||||||
handler.OnBadUsageError(crm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
case "im.ricochet.chat":
|
|
||||||
handler.OnOpenChannelRequestSuccess(crm.GetChannelIdentifier())
|
|
||||||
case "im.ricochet.contact.request":
|
|
||||||
responseI, err := proto.GetExtension(res.GetChannelResult(), Protocol_Data_ContactRequest.E_Response)
|
|
||||||
if err == nil {
|
|
||||||
response, check := responseI.(*Protocol_Data_ContactRequest.Response)
|
|
||||||
if check {
|
|
||||||
handler.OnContactRequestAck(crm.GetChannelIdentifier(), response.GetStatus().String())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.OnBadUsageError(crm.GetChannelIdentifier())
|
|
||||||
default:
|
|
||||||
handler.OnBadUsageError(crm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if oc.GetChannelType(crm.GetChannelIdentifier()) != "none" {
|
|
||||||
handler.OnFailedChannelOpen(crm.GetChannelIdentifier(), crm.GetCommonError().String())
|
|
||||||
} else {
|
|
||||||
oc.CloseChannel(crm.GetChannelIdentifier())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown Message
|
|
||||||
oc.CloseChannel(packet.Channel)
|
|
||||||
}
|
|
||||||
} else if oc.GetChannelType(packet.Channel) == "im.ricochet.auth.hidden-service" {
|
|
||||||
res := new(Protocol_Data_AuthHiddenService.Packet)
|
|
||||||
err := proto.Unmarshal(packet.Data[:], res)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
oc.CloseChannel(packet.Channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.GetProof() != nil && !oc.Client { // Only Clients Send Proofs
|
|
||||||
handler.OnAuthenticationProof(packet.Channel, res.GetProof().GetPublicKey(), res.GetProof().GetSignature())
|
|
||||||
} else if res.GetResult() != nil && oc.Client { // Only Servers Send Results
|
|
||||||
handler.OnAuthenticationResult(packet.Channel, res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact())
|
|
||||||
} else {
|
|
||||||
// If neither of the above are satisfied we just close the connection
|
|
||||||
oc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if oc.GetChannelType(packet.Channel) == "im.ricochet.chat" {
|
|
||||||
|
|
||||||
// NOTE: These auth checks should be redundant, however they
|
|
||||||
// are included here for defense-in-depth if for some reason
|
|
||||||
// a previously authed connection becomes untrusted / not known and
|
|
||||||
// the state is not cleaned up.
|
|
||||||
if !oc.IsAuthed {
|
|
||||||
// Can't send chat messages if not authorized
|
|
||||||
handler.OnUnauthorizedError(packet.Channel)
|
|
||||||
} else if !handler.IsKnownContact(oc.OtherHostname) {
|
|
||||||
// Can't send chat message if not a known contact
|
|
||||||
handler.OnUnauthorizedError(packet.Channel)
|
|
||||||
} else {
|
|
||||||
res := new(Protocol_Data_Chat.Packet)
|
|
||||||
err := proto.Unmarshal(packet.Data[:], res)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
oc.CloseChannel(packet.Channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.GetChatMessage() != nil {
|
|
||||||
handler.OnChatMessage(packet.Channel, int32(res.GetChatMessage().GetMessageId()), res.GetChatMessage().GetMessageText())
|
|
||||||
} else if res.GetChatAcknowledge() != nil {
|
|
||||||
handler.OnChatMessageAck(packet.Channel, int32(res.GetChatMessage().GetMessageId()))
|
|
||||||
} else {
|
|
||||||
// If neither of the above are satisfied we just close the connection
|
|
||||||
oc.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if oc.GetChannelType(packet.Channel) == "im.ricochet.contact.request" {
|
|
||||||
|
|
||||||
// NOTE: These auth checks should be redundant, however they
|
|
||||||
// are included here for defense-in-depth if for some reason
|
|
||||||
// a previously authed connection becomes untrusted / not known and
|
|
||||||
// the state is not cleaned up.
|
|
||||||
if !oc.Client {
|
|
||||||
// Clients are not allowed to send contact request responses
|
|
||||||
handler.OnBadUsageError(packet.Channel)
|
|
||||||
} else if !oc.IsAuthed {
|
|
||||||
// Can't send a contact request if not authed
|
|
||||||
handler.OnBadUsageError(packet.Channel)
|
|
||||||
} else {
|
|
||||||
res := new(Protocol_Data_ContactRequest.Response)
|
|
||||||
err := proto.Unmarshal(packet.Data[:], res)
|
|
||||||
log.Printf("%v", res)
|
|
||||||
if err != nil {
|
|
||||||
oc.CloseChannel(packet.Channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
handler.OnContactRequestAck(packet.Channel, res.GetStatus().String())
|
|
||||||
}
|
|
||||||
} else if oc.GetChannelType(packet.Channel) == "none" {
|
|
||||||
// Invalid Channel Assignment
|
|
||||||
oc.CloseChannel(packet.Channel)
|
|
||||||
} else {
|
|
||||||
oc.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package policies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeoutPolicy is a convieance interface for enforcing common timeout patterns
|
||||||
|
type TimeoutPolicy time.Duration
|
||||||
|
|
||||||
|
// Selection of common timeout policies
|
||||||
|
const (
|
||||||
|
UnknownPurposeTimeout TimeoutPolicy = TimeoutPolicy(15 * time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExecuteAction runs a function and returns an error if it hasn't returned
|
||||||
|
// by the time specified by TimeoutPolicy
|
||||||
|
func (tp *TimeoutPolicy) ExecuteAction(action func() error) error {
|
||||||
|
|
||||||
|
c := make(chan error)
|
||||||
|
go func() {
|
||||||
|
c <- action()
|
||||||
|
}()
|
||||||
|
|
||||||
|
tick := time.Tick(time.Duration(*tp))
|
||||||
|
select {
|
||||||
|
case <-tick:
|
||||||
|
return utils.ActionTimedOutError
|
||||||
|
case err := <-c:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,124 +1,58 @@
|
||||||
package goricochet
|
package goricochet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"github.com/s-rah/go-ricochet/connection"
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect sets up a client ricochet connection to host e.g. qn6uo4cmsrfv4kzq.onion. If this
|
|
||||||
// function finished successfully then the connection can be assumed to
|
|
||||||
// be open and authenticated.
|
|
||||||
// To specify a local port using the format "127.0.0.1:[port]|ricochet-id".
|
|
||||||
func Connect(host string) (*OpenConnection, error) {
|
|
||||||
networkResolver := utils.NetworkResolver{}
|
|
||||||
conn, host, err := networkResolver.Resolve(host)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Open(conn, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(conn net.Conn, remoteHostname string) (*OpenConnection, error) {
|
func Open(remoteHostname string) (*connection.Connection, error) {
|
||||||
oc, err := negotiateVersion(conn, true)
|
networkResolver := utils.NetworkResolver{}
|
||||||
|
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := NegotiateVersionOutbound(conn, remoteHostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
oc.OtherHostname = remoteHostname
|
return rc, nil
|
||||||
return oc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve accepts incoming connections on a net.Listener, negotiates protocol,
|
// negotiate version takes an open network connection and executes
|
||||||
// and calls methods of the ServiceHandler to handle inbound connections. All
|
// the ricochet version negotiation procedure.
|
||||||
// calls to ServiceHandler happen on the caller's goroutine. The listener can
|
func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection.Connection, error) {
|
||||||
// be closed at any time to close the service.
|
|
||||||
func Serve(ln net.Listener, handler ServiceHandler) error {
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
connChannel := make(chan interface{})
|
|
||||||
listenErrorChannel := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var pending sync.WaitGroup
|
|
||||||
for {
|
|
||||||
conn, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
// Wait for pending connections before returning an error; this
|
|
||||||
// prevents abandoned goroutines when the outer loop stops reading
|
|
||||||
// from connChannel.
|
|
||||||
pending.Wait()
|
|
||||||
listenErrorChannel <- err
|
|
||||||
close(connChannel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
oc, err := negotiateVersion(conn, false)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
connChannel <- err
|
|
||||||
} else {
|
|
||||||
connChannel <- oc
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var listenErr error
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err := <-listenErrorChannel:
|
|
||||||
// Remember error, wait for connChannel to close
|
|
||||||
listenErr = err
|
|
||||||
|
|
||||||
case result, ok := <-connChannel:
|
|
||||||
if !ok {
|
|
||||||
return listenErr
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := result.(type) {
|
|
||||||
case *OpenConnection:
|
|
||||||
handler.OnNewConnection(v)
|
|
||||||
case error:
|
|
||||||
handler.OnFailedConnection(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform version negotiation on the connection, and create an OpenConnection if successful
|
|
||||||
func negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) {
|
|
||||||
versions := []byte{0x49, 0x4D, 0x01, 0x01}
|
versions := []byte{0x49, 0x4D, 0x01, 0x01}
|
||||||
|
|
||||||
// Outbound side of the connection sends a list of supported versions
|
|
||||||
if outbound {
|
|
||||||
if n, err := conn.Write(versions); err != nil || n < len(versions) {
|
if n, err := conn.Write(versions); err != nil || n < len(versions) {
|
||||||
return nil, err
|
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, err
|
return nil, utils.VersionNegotiationError
|
||||||
}
|
}
|
||||||
|
|
||||||
if res[0] != 0x01 {
|
if res[0] != 0x01 {
|
||||||
return nil, errors.New("unsupported protocol version")
|
return nil, utils.VersionNegotiationFailed
|
||||||
}
|
}
|
||||||
} else {
|
rc := connection.NewOutboundConnection(conn, remoteHostname)
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateVersionInbound takes in a connection and performs version negotiation
|
||||||
|
// as if that connection was a client. Returns a ricochet connection if successful
|
||||||
|
// error otherwise.
|
||||||
|
func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
|
||||||
|
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 {
|
||||||
|
@ -126,13 +60,13 @@ func negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if header[0] != versions[0] || header[1] != versions[1] || header[2] < 1 {
|
if header[0] != versions[0] || header[1] != versions[1] || header[2] < 1 {
|
||||||
return nil, errors.New("invalid protocol response")
|
return nil, utils.VersionNegotiationError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read list of supported versions (which is header[2] bytes long)
|
// Read list of supported versions (which is header[2] bytes long)
|
||||||
versionList := make([]byte, header[2])
|
versionList := make([]byte, header[2])
|
||||||
if _, err := io.ReadAtLeast(conn, versionList, len(versionList)); err != nil {
|
if _, err := io.ReadAtLeast(conn, versionList, len(versionList)); err != nil {
|
||||||
return nil, err
|
return nil, utils.VersionNegotiationError
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedVersion := byte(0xff)
|
selectedVersion := byte(0xff)
|
||||||
|
@ -144,15 +78,13 @@ func negotiateVersion(conn net.Conn, outbound bool) (*OpenConnection, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 {
|
if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 {
|
||||||
return nil, err
|
return nil, utils.VersionNegotiationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedVersion == 0xff {
|
if selectedVersion == 0xff {
|
||||||
return nil, errors.New("no supported protocol version")
|
return nil, utils.VersionNegotiationFailed
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oc := new(OpenConnection)
|
rc := connection.NewInboundConnection(conn)
|
||||||
oc.Init(outbound, conn)
|
return rc, nil
|
||||||
return oc, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
package goricochet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StandardRicochetService implements all the necessary flows to implement a
|
|
||||||
// minimal, protocol compliant Ricochet Service. It can be built on by other
|
|
||||||
// applications to produce automated riochet applications, and is a useful
|
|
||||||
// example for other implementations.
|
|
||||||
type StandardRicochetService struct {
|
|
||||||
PrivateKey *rsa.PrivateKey
|
|
||||||
serverHostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
// StandardRicochetConnection implements the ConnectionHandler interface
|
|
||||||
// to handle events on connections. An instance of StandardRicochetConnection
|
|
||||||
// is created for each OpenConnection by the HandleConnection method.
|
|
||||||
type StandardRicochetConnection struct {
|
|
||||||
Conn *OpenConnection
|
|
||||||
PrivateKey *rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes a StandardRicochetService with the cryptographic key given
|
|
||||||
// by filename.
|
|
||||||
func (srs *StandardRicochetService) Init(filename string) error {
|
|
||||||
pemData, err := ioutil.ReadFile(filename)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Could not setup ricochet service: could not read private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
block, _ := pem.Decode(pemData)
|
|
||||||
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
|
||||||
return errors.New("Could not setup ricochet service: no valid PEM data found")
|
|
||||||
}
|
|
||||||
|
|
||||||
srs.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Could not setup ricochet service: could not parse private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
|
||||||
N: srs.PrivateKey.PublicKey.N,
|
|
||||||
E: srs.PrivateKey.PublicKey.E,
|
|
||||||
})
|
|
||||||
|
|
||||||
srs.serverHostname = utils.GetTorHostname(publicKeyBytes)
|
|
||||||
log.Printf("Initialised ricochet service for %s", srs.serverHostname)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen starts listening for service connections on localhost `port`.
|
|
||||||
func (srs *StandardRicochetService) Listen(handler ServiceHandler, port int) {
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Cannot Listen on Port %v", port)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Serve(ln, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect initiates a new client connection to `hostname`, which must be in one
|
|
||||||
// of the forms accepted by the goricochet.Connect() method.
|
|
||||||
func (srs *StandardRicochetService) Connect(hostname string) (*OpenConnection, error) {
|
|
||||||
log.Printf("Connecting to...%s", hostname)
|
|
||||||
oc, err := Connect(hostname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Could not connect to: " + hostname + " " + err.Error())
|
|
||||||
}
|
|
||||||
oc.MyHostname = srs.serverHostname
|
|
||||||
return oc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnNewConnection is called for new inbound connections to our service. This
|
|
||||||
// method implements the ServiceHandler interface.
|
|
||||||
func (srs *StandardRicochetService) OnNewConnection(oc *OpenConnection) {
|
|
||||||
oc.MyHostname = srs.serverHostname
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnFailedConnection is called for inbound connections that fail to successfully
|
|
||||||
// complete version negotiation for any reason. This method implements the
|
|
||||||
// ServiceHandler interface.
|
|
||||||
func (srs *StandardRicochetService) OnFailedConnection(err error) {
|
|
||||||
log.Printf("Inbound connection failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------
|
|
||||||
|
|
||||||
// OnReady is called when a client or server sucessfully passes Version Negotiation.
|
|
||||||
func (src *StandardRicochetConnection) OnReady(oc *OpenConnection) {
|
|
||||||
src.Conn = oc
|
|
||||||
if oc.Client {
|
|
||||||
log.Printf("Successfully connected to %s", oc.OtherHostname)
|
|
||||||
oc.IsAuthed = true // Connections to Servers are Considered Authenticated by Default
|
|
||||||
oc.Authenticate(1)
|
|
||||||
} else {
|
|
||||||
log.Printf("Inbound connection received")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnDisconnect is called when a connection is closed
|
|
||||||
func (src *StandardRicochetConnection) OnDisconnect() {
|
|
||||||
log.Printf("Disconnected from %s", src.Conn.OtherHostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnAuthenticationRequest is called when a client requests Authentication
|
|
||||||
func (src *StandardRicochetConnection) OnAuthenticationRequest(channelID int32, clientCookie [16]byte) {
|
|
||||||
src.Conn.ConfirmAuthChannel(channelID, clientCookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnAuthenticationChallenge constructs a valid authentication challenge to the serverCookie
|
|
||||||
func (src *StandardRicochetConnection) OnAuthenticationChallenge(channelID int32, serverCookie [16]byte) {
|
|
||||||
// DER Encode the Public Key
|
|
||||||
publickeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
|
||||||
N: src.PrivateKey.PublicKey.N,
|
|
||||||
E: src.PrivateKey.PublicKey.E,
|
|
||||||
})
|
|
||||||
src.Conn.SendProof(1, serverCookie, publickeyBytes, src.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnAuthenticationProof is called when a client sends Proof for an existing authentication challenge
|
|
||||||
func (src *StandardRicochetConnection) OnAuthenticationProof(channelID int32, publicKey []byte, signature []byte) {
|
|
||||||
result := src.Conn.ValidateProof(channelID, publicKey, signature)
|
|
||||||
// This implementation always sends 'true', indicating that the contact is known
|
|
||||||
src.Conn.SendAuthenticationResult(channelID, result, true)
|
|
||||||
src.Conn.IsAuthed = result
|
|
||||||
src.Conn.CloseChannel(channelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnAuthenticationResult is called once a server has returned the result of the Proof Verification
|
|
||||||
func (src *StandardRicochetConnection) OnAuthenticationResult(channelID int32, result bool, isKnownContact bool) {
|
|
||||||
src.Conn.IsAuthed = result
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsKnownContact allows a caller to determine if a hostname an authorized contact.
|
|
||||||
func (src *StandardRicochetConnection) IsKnownContact(hostname string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnContactRequest is called when a client sends a new contact request
|
|
||||||
func (src *StandardRicochetConnection) OnContactRequest(channelID int32, nick string, message string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnContactRequestAck is called when a server sends a reply to an existing contact request
|
|
||||||
func (src *StandardRicochetConnection) OnContactRequestAck(channelID int32, status string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnOpenChannelRequest is called when a client or server requests to open a new channel
|
|
||||||
func (src *StandardRicochetConnection) OnOpenChannelRequest(channelID int32, channelType string) {
|
|
||||||
src.Conn.AckOpenChannel(channelID, channelType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnOpenChannelRequestSuccess is called when a client or server responds to an open channel request
|
|
||||||
func (src *StandardRicochetConnection) OnOpenChannelRequestSuccess(channelID int32) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnChannelClosed is called when a client or server closes an existing channel
|
|
||||||
func (src *StandardRicochetConnection) OnChannelClosed(channelID int32) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnChatMessage is called when a new chat message is received.
|
|
||||||
func (src *StandardRicochetConnection) OnChatMessage(channelID int32, messageID int32, message string) {
|
|
||||||
src.Conn.AckChatMessage(channelID, messageID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnChatMessageAck is called when a new chat message is ascknowledged.
|
|
||||||
func (src *StandardRicochetConnection) OnChatMessageAck(channelID int32, messageID int32) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnFailedChannelOpen is called when a server fails to open a channel
|
|
||||||
func (src *StandardRicochetConnection) OnFailedChannelOpen(channelID int32, errorType string) {
|
|
||||||
src.Conn.UnsetChannel(channelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnGenericError is called when a generalized error is returned from the peer
|
|
||||||
func (src *StandardRicochetConnection) OnGenericError(channelID int32) {
|
|
||||||
src.Conn.RejectOpenChannel(channelID, "GenericError")
|
|
||||||
}
|
|
||||||
|
|
||||||
//OnUnknownTypeError is called when an unknown type error is returned from the peer
|
|
||||||
func (src *StandardRicochetConnection) OnUnknownTypeError(channelID int32) {
|
|
||||||
src.Conn.RejectOpenChannel(channelID, "UnknownTypeError")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnUnauthorizedError is called when an unathorized error is returned from the peer
|
|
||||||
func (src *StandardRicochetConnection) OnUnauthorizedError(channelID int32) {
|
|
||||||
src.Conn.RejectOpenChannel(channelID, "UnauthorizedError")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnBadUsageError is called when a bad usage error is returned from the peer
|
|
||||||
func (src *StandardRicochetConnection) OnBadUsageError(channelID int32) {
|
|
||||||
src.Conn.RejectOpenChannel(channelID, "BadUsageError")
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnFailedError is called when a failed error is returned from the peer
|
|
||||||
func (src *StandardRicochetConnection) OnFailedError(channelID int32) {
|
|
||||||
src.Conn.RejectOpenChannel(channelID, "FailedError")
|
|
||||||
}
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"errors"
|
||||||
|
"crypto/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvalidPrivateKeyFileError = Error("InvalidPrivateKeyFileError")
|
||||||
|
RICOCHET_KEY_SIZE = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate a private key for use
|
||||||
|
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, RICOCHET_KEY_SIZE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Could not generate key: " + err.Error())
|
||||||
|
}
|
||||||
|
privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||||
|
return x509.ParsePKCS1PrivateKey(privateKeyDer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPrivateKeyFromFile loads a private key from a file...
|
||||||
|
func LoadPrivateKeyFromFile(filename string) (*rsa.PrivateKey, error) {
|
||||||
|
pemData, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParsePrivateKey(pemData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a private key string to a usable private key
|
||||||
|
func ParsePrivateKey(pemData []byte) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode(pemData)
|
||||||
|
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
||||||
|
return nil, InvalidPrivateKeyFileError
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn a private key into storable string
|
||||||
|
func PrivateKeyToString(privateKey *rsa.PrivateKey) string {
|
||||||
|
privateKeyBlock := pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pem.EncodeToMemory(&privateKeyBlock))
|
||||||
|
}
|
|
@ -1,17 +1,48 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
import "log"
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// RecoverFromError doesn't really recover from anything....see comment below
|
// Error captures various common ricochet errors
|
||||||
func RecoverFromError() {
|
type Error string
|
||||||
if r := recover(); r != nil {
|
|
||||||
// This should only really happen if there is a failure de/serializing. If
|
func (e Error) Error() string { return string(e) }
|
||||||
// this does happen then we currently error. In the future we might be
|
|
||||||
// able to make this nicer.
|
// Defining Versions
|
||||||
log.Fatalf("Recovered from panic() - this really shouldn't happen. Reason: %v", r)
|
const (
|
||||||
}
|
VersionNegotiationError = Error("VersionNegotiationError")
|
||||||
}
|
VersionNegotiationFailed = Error("VersionNegotiationFailed")
|
||||||
|
|
||||||
|
RicochetConnectionClosed = Error("RicochetConnectionClosed")
|
||||||
|
RicochetProtocolError = Error("RicochetProtocolError")
|
||||||
|
|
||||||
|
UnknownChannelTypeError = Error("UnknownChannelTypeError")
|
||||||
|
UnauthorizedChannelTypeError = Error("UnauthorizedChannelTypeError")
|
||||||
|
|
||||||
|
// Timeout Errors
|
||||||
|
ActionTimedOutError = Error("ActionTimedOutError")
|
||||||
|
PeerTimedOutError = Error("PeerTimedOutError")
|
||||||
|
|
||||||
|
// Authentication Errors
|
||||||
|
ClientFailedToAuthenticateError = Error("ClientFailedToAuthenticateError")
|
||||||
|
ServerRejectedClientConnectionError = Error("ServerRejectedClientConnectionError")
|
||||||
|
|
||||||
|
UnauthorizedActionError = Error("UnauthorizedActionError")
|
||||||
|
ChannelClosedByPeerError = Error("ChannelClosedByPeerError")
|
||||||
|
|
||||||
|
// Channel Management Errors
|
||||||
|
ServerAttemptedToOpenEvenNumberedChannelError = Error("ServerAttemptedToOpenEvenNumberedChannelError")
|
||||||
|
ClientAttemptedToOpenOddNumberedChannelError = Error("ClientAttemptedToOpenOddNumberedChannelError")
|
||||||
|
ChannelIDIsAlreadyInUseError = Error("ChannelIDIsAlreadyInUseError")
|
||||||
|
AttemptToOpenMoreThanOneSingletonChannelError = Error("AttemptToOpenMoreThanOneSingletonChannelError")
|
||||||
|
|
||||||
|
// Library Use Errors
|
||||||
|
PrivateKeyNotSetError = Error("ClientFailedToAuthenticateError")
|
||||||
|
|
||||||
|
// Connection Errors
|
||||||
|
ConnectionClosedError = Error("ConnectionClosedError")
|
||||||
|
)
|
||||||
|
|
||||||
// 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
|
||||||
// but should be very rare e.g. failures deserializing a protobuf object that
|
// but should be very rare e.g. failures deserializing a protobuf object that
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package goricochet
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/s-rah/go-ricochet/auth"
|
"github.com/s-rah/go-ricochet/wire/auth"
|
||||||
"github.com/s-rah/go-ricochet/chat"
|
"github.com/s-rah/go-ricochet/wire/chat"
|
||||||
"github.com/s-rah/go-ricochet/contact"
|
"github.com/s-rah/go-ricochet/wire/contact"
|
||||||
"github.com/s-rah/go-ricochet/control"
|
"github.com/s-rah/go-ricochet/wire/control"
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageBuilder allows a client to construct specific data packets for the
|
// MessageBuilder allows a client to construct specific data packets for the
|
||||||
|
@ -16,7 +15,7 @@ type MessageBuilder struct {
|
||||||
|
|
||||||
// OpenChannel contructs a message which will request to open a channel for
|
// OpenChannel contructs a message which will request to open a channel for
|
||||||
// chat on the given channelID.
|
// chat on the given channelID.
|
||||||
func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]byte, error) {
|
func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) []byte {
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
ChannelType: proto.String(channelType),
|
ChannelType: proto.String(channelType),
|
||||||
|
@ -24,11 +23,13 @@ func (mb *MessageBuilder) OpenChannel(channelID int32, channelType string) ([]by
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
OpenChannel: oc,
|
OpenChannel: oc,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// AckOpenChannel constructs a message to acknowledge a previous open channel operation.
|
// AckOpenChannel constructs a message to acknowledge a previous open channel operation.
|
||||||
func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) {
|
func (mb *MessageBuilder) AckOpenChannel(channelID int32) []byte {
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
cr := &Protocol_Data_Control.ChannelResult{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
Opened: proto.Bool(true),
|
Opened: proto.Bool(true),
|
||||||
|
@ -36,11 +37,13 @@ func (mb *MessageBuilder) AckOpenChannel(channelID int32) ([]byte, error) {
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
ChannelResult: cr,
|
ChannelResult: cr,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// RejectOpenChannel constructs a channel result message, stating the channel failed to open and a reason
|
// RejectOpenChannel constructs a channel result message, stating the channel failed to open and a reason
|
||||||
func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) ([]byte, error) {
|
func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) []byte {
|
||||||
|
|
||||||
errorNum := Protocol_Data_Control.ChannelResult_CommonError_value[error]
|
errorNum := Protocol_Data_Control.ChannelResult_CommonError_value[error]
|
||||||
commonError := Protocol_Data_Control.ChannelResult_CommonError(errorNum)
|
commonError := Protocol_Data_Control.ChannelResult_CommonError(errorNum)
|
||||||
|
@ -53,28 +56,32 @@ func (mb *MessageBuilder) RejectOpenChannel(channelID int32, error string) ([]by
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
ChannelResult: cr,
|
ChannelResult: cr,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation.
|
// ConfirmAuthChannel constructs a message to acknowledge a previous open channel operation.
|
||||||
func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) ([]byte, error) {
|
func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]byte) []byte {
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
cr := &Protocol_Data_Control.ChannelResult{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
Opened: proto.Bool(true),
|
Opened: proto.Bool(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := proto.SetExtension(cr, Protocol_Data_AuthHiddenService.E_ServerCookie, serverCookie[:])
|
err := proto.SetExtension(cr, Protocol_Data_AuthHiddenService.E_ServerCookie, serverCookie[:])
|
||||||
utils.CheckError(err)
|
CheckError(err)
|
||||||
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
ChannelResult: cr,
|
ChannelResult: cr,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenContactRequestChannel contructs a message which will reuqest to open a channel for
|
// OpenContactRequestChannel contructs a message which will reuqest to open a channel for
|
||||||
// a contact request on the given channelID, with the given nick and message.
|
// a contact request on the given channelID, with the given nick and message.
|
||||||
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) ([]byte, error) {
|
func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) []byte {
|
||||||
// Construct a Contact Request Channel
|
// Construct a Contact Request Channel
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
|
@ -87,16 +94,18 @@ func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string
|
||||||
}
|
}
|
||||||
|
|
||||||
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
|
err := proto.SetExtension(oc, Protocol_Data_ContactRequest.E_ContactRequest, contactRequest)
|
||||||
utils.CheckError(err)
|
CheckError(err)
|
||||||
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
OpenChannel: oc,
|
OpenChannel: oc,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyToContactRequestOnResponse constructs a message to acknowledge contact request
|
// ReplyToContactRequestOnResponse constructs a message to acknowledge contact request
|
||||||
func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) ([]byte, error) {
|
func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, status string) []byte {
|
||||||
cr := &Protocol_Data_Control.ChannelResult{
|
cr := &Protocol_Data_Control.ChannelResult{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
Opened: proto.Bool(true),
|
Opened: proto.Bool(true),
|
||||||
|
@ -109,42 +118,49 @@ func (mb *MessageBuilder) ReplyToContactRequestOnResponse(channelID int32, statu
|
||||||
}
|
}
|
||||||
|
|
||||||
err := proto.SetExtension(cr, Protocol_Data_ContactRequest.E_Response, contactRequest)
|
err := proto.SetExtension(cr, Protocol_Data_ContactRequest.E_Response, contactRequest)
|
||||||
utils.CheckError(err)
|
CheckError(err)
|
||||||
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
ChannelResult: cr,
|
ChannelResult: cr,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyToContactRequest constructs a message to acknowledge a contact request
|
// ReplyToContactRequest constructs a message to acknowledge a contact request
|
||||||
func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) ([]byte, error) {
|
func (mb *MessageBuilder) ReplyToContactRequest(channelID int32, status string) []byte {
|
||||||
statusNum := Protocol_Data_ContactRequest.Response_Status_value[status]
|
statusNum := Protocol_Data_ContactRequest.Response_Status_value[status]
|
||||||
responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum)
|
responseStatus := Protocol_Data_ContactRequest.Response_Status(statusNum)
|
||||||
contactRequest := &Protocol_Data_ContactRequest.Response{
|
contactRequest := &Protocol_Data_ContactRequest.Response{
|
||||||
Status: &responseStatus,
|
Status: &responseStatus,
|
||||||
}
|
}
|
||||||
return proto.Marshal(contactRequest)
|
|
||||||
|
ret, err := proto.Marshal(contactRequest)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAuthenticationChannel constructs a message which will reuqest to open a channel for
|
// OpenAuthenticationChannel constructs a message which will reuqest to open a channel for
|
||||||
// authentication on the given channelID, with the given cookie
|
// authentication on the given channelID, with the given cookie
|
||||||
func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) ([]byte, error) {
|
func (mb *MessageBuilder) OpenAuthenticationChannel(channelID int32, clientCookie [16]byte) []byte {
|
||||||
oc := &Protocol_Data_Control.OpenChannel{
|
oc := &Protocol_Data_Control.OpenChannel{
|
||||||
ChannelIdentifier: proto.Int32(channelID),
|
ChannelIdentifier: proto.Int32(channelID),
|
||||||
ChannelType: proto.String("im.ricochet.auth.hidden-service"),
|
ChannelType: proto.String("im.ricochet.auth.hidden-service"),
|
||||||
}
|
}
|
||||||
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
|
err := proto.SetExtension(oc, Protocol_Data_AuthHiddenService.E_ClientCookie, clientCookie[:])
|
||||||
utils.CheckError(err)
|
CheckError(err)
|
||||||
|
|
||||||
pc := &Protocol_Data_Control.Packet{
|
pc := &Protocol_Data_Control.Packet{
|
||||||
OpenChannel: oc,
|
OpenChannel: oc,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proof constructs a proof message with the given public key and signature.
|
// Proof constructs a proof message with the given public key and signature.
|
||||||
func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([]byte, error) {
|
func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) []byte {
|
||||||
proof := &Protocol_Data_AuthHiddenService.Proof{
|
proof := &Protocol_Data_AuthHiddenService.Proof{
|
||||||
PublicKey: publicKeyBytes,
|
PublicKey: publicKeyBytes,
|
||||||
Signature: signatureBytes,
|
Signature: signatureBytes,
|
||||||
|
@ -155,11 +171,13 @@ func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) ([
|
||||||
Result: nil,
|
Result: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto.Marshal(ahsPacket)
|
ret, err := proto.Marshal(ahsPacket)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResult constructs a response to a Proof
|
// AuthResult constructs a response to a Proof
|
||||||
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte, error) {
|
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
|
||||||
// Construct a Result Message
|
// Construct a Result Message
|
||||||
result := &Protocol_Data_AuthHiddenService.Result{
|
result := &Protocol_Data_AuthHiddenService.Result{
|
||||||
Accepted: proto.Bool(accepted),
|
Accepted: proto.Bool(accepted),
|
||||||
|
@ -171,29 +189,74 @@ func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) ([]byte
|
||||||
Result: result,
|
Result: result,
|
||||||
}
|
}
|
||||||
|
|
||||||
return proto.Marshal(ahsPacket)
|
ret, err := proto.Marshal(ahsPacket)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatMessage constructs a chat message with the given content.
|
// ChatMessage constructs a chat message with the given content.
|
||||||
func (mb *MessageBuilder) ChatMessage(message string, messageID int32) ([]byte, error) {
|
func (mb *MessageBuilder) ChatMessage(message string, messageID uint32) []byte {
|
||||||
cm := &Protocol_Data_Chat.ChatMessage{
|
cm := &Protocol_Data_Chat.ChatMessage{
|
||||||
MessageId: proto.Uint32(uint32(messageID)),
|
MessageId: proto.Uint32(messageID),
|
||||||
MessageText: proto.String(message),
|
MessageText: proto.String(message),
|
||||||
}
|
}
|
||||||
chatPacket := &Protocol_Data_Chat.Packet{
|
chatPacket := &Protocol_Data_Chat.Packet{
|
||||||
ChatMessage: cm,
|
ChatMessage: cm,
|
||||||
}
|
}
|
||||||
return proto.Marshal(chatPacket)
|
ret, err := proto.Marshal(chatPacket)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// AckChatMessage constructs a chat message acknowledgement.
|
// AckChatMessage constructs a chat message acknowledgement.
|
||||||
func (mb *MessageBuilder) AckChatMessage(messageID int32) ([]byte, error) {
|
func (mb *MessageBuilder) AckChatMessage(messageID uint32) []byte {
|
||||||
cr := &Protocol_Data_Chat.ChatAcknowledge{
|
cr := &Protocol_Data_Chat.ChatAcknowledge{
|
||||||
MessageId: proto.Uint32(uint32(messageID)),
|
MessageId: proto.Uint32(messageID),
|
||||||
Accepted: proto.Bool(true),
|
Accepted: proto.Bool(true),
|
||||||
}
|
}
|
||||||
pc := &Protocol_Data_Chat.Packet{
|
pc := &Protocol_Data_Chat.Packet{
|
||||||
ChatAcknowledge: cr,
|
ChatAcknowledge: cr,
|
||||||
}
|
}
|
||||||
return proto.Marshal(pc)
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeepAlive ...
|
||||||
|
func (mb *MessageBuilder) KeepAlive(responseRequested bool) []byte {
|
||||||
|
ka := &Protocol_Data_Control.KeepAlive{
|
||||||
|
ResponseRequested: proto.Bool(responseRequested),
|
||||||
|
}
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
KeepAlive: ka,
|
||||||
|
}
|
||||||
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFeatures ...
|
||||||
|
func (mb *MessageBuilder) EnableFeatures(features []string) []byte {
|
||||||
|
ef := &Protocol_Data_Control.EnableFeatures{
|
||||||
|
Feature: features,
|
||||||
|
}
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
EnableFeatures: ef,
|
||||||
|
}
|
||||||
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeaturesEnabled ...
|
||||||
|
func (mb *MessageBuilder) FeaturesEnabled(features []string) []byte {
|
||||||
|
fe := &Protocol_Data_Control.FeaturesEnabled{
|
||||||
|
Feature: features,
|
||||||
|
}
|
||||||
|
pc := &Protocol_Data_Control.Packet{
|
||||||
|
FeaturesEnabled: fe,
|
||||||
|
}
|
||||||
|
ret, err := proto.Marshal(pc)
|
||||||
|
CheckError(err)
|
||||||
|
return ret
|
||||||
}
|
}
|
|
@ -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]))
|
||||||
|
|
|
@ -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,8 +50,8 @@ 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
|
||||||
}
|
}
|
||||||
//conn.SetDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
return conn, resolvedHostname, nil
|
return conn, resolvedHostname, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package Protocol_Data_AuthHiddenService
|
||||||
import proto "github.com/golang/protobuf/proto"
|
import proto "github.com/golang/protobuf/proto"
|
||||||
import fmt "fmt"
|
import fmt "fmt"
|
||||||
import math "math"
|
import math "math"
|
||||||
import Protocol_Data_Control "github.com/s-rah/go-ricochet/control"
|
import Protocol_Data_Control "github.com/s-rah/go-ricochet/wire/control"
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ = proto.Marshal
|
var _ = proto.Marshal
|
|
@ -17,7 +17,7 @@ package Protocol_Data_ContactRequest
|
||||||
import proto "github.com/golang/protobuf/proto"
|
import proto "github.com/golang/protobuf/proto"
|
||||||
import fmt "fmt"
|
import fmt "fmt"
|
||||||
import math "math"
|
import math "math"
|
||||||
import Protocol_Data_Control "github.com/s-rah/go-ricochet/control"
|
import Protocol_Data_Control "github.com/s-rah/go-ricochet/wire/control"
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ = proto.Marshal
|
var _ = proto.Marshal
|
|
@ -61,10 +61,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/s-rah/go-ricochet",
|
"importpath": "github.com/s-rah/go-ricochet",
|
||||||
"repository": "https://github.com/s-rah/go-ricochet",
|
"repository": "https://github.com/special/go-ricochet-protocol",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "5a720a08d052019a113e9c2a94d6f1a3f582f09e",
|
"revision": "5b54d50bf4611a36c23dd732bd1e9d0dad441980",
|
||||||
"branch": "master",
|
"branch": "api-rework-fixes",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue