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:
John Brooks 2017-08-11 18:10:41 -06:00
parent 48aab2536a
commit 9ae0eac4f3
34 changed files with 2141 additions and 1074 deletions

View File

@ -25,8 +25,8 @@ SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Autogenerated protobuf code was generated using the proto file from Ricochet. Autogenerated protobuf code was generated using the proto file from Ricochet.
They are covered under the following license. They are covered under the following license.
Ricochet - https://ricochet.im/ Ricochet - https://ricochet.im/
Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net> Copyright (C) 2014, John Brooks <john.brooks@dereferenced.net>
@ -61,10 +61,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
The go-ricochet logo is based on an image by Olga Shalakhina
<osshalakhina@gmail.com> who in turn modified the original gopher images made by
Renee French. The image is licensed under Creative Commons 3.0 Attributions.
--------------------------------------------------------------------------------
go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project. go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project.

View 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"
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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)
}

View File

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

View File

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

View File

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

View 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{})
}

View File

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

View 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
}

View 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
}

View File

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

View File

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

View File

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

View 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
}

View 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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,158 +1,90 @@
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}
if n, err := conn.Write(versions); err != nil || n < len(versions) {
return nil, utils.VersionNegotiationError
}
// Outbound side of the connection sends a list of supported versions res := make([]byte, 1)
if outbound { if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil {
if n, err := conn.Write(versions); err != nil || n < len(versions) { return nil, utils.VersionNegotiationError
return nil, err }
}
res := make([]byte, 1) if res[0] != 0x01 {
if _, err := io.ReadAtLeast(conn, res, len(res)); err != nil { return nil, utils.VersionNegotiationFailed
return nil, err }
} rc := connection.NewOutboundConnection(conn, remoteHostname)
return rc, nil
}
if res[0] != 0x01 { // NegotiateVersionInbound takes in a connection and performs version negotiation
return nil, errors.New("unsupported protocol version") // as if that connection was a client. Returns a ricochet connection if successful
} // error otherwise.
} else { func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
// Read version response header versions := []byte{0x49, 0x4D, 0x01, 0x01}
header := make([]byte, 3) // Read version response header
if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { header := make([]byte, 3)
return nil, err if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil {
} return nil, err
}
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)
for _, v := range versionList { for _, v := range versionList {
if v == 0x01 { if v == 0x01 {
selectedVersion = v selectedVersion = v
break break
}
}
if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 {
return nil, err
}
if selectedVersion == 0xff {
return nil, errors.New("no supported protocol version")
} }
} }
oc := new(OpenConnection) if n, err := conn.Write([]byte{selectedVersion}); err != nil || n < 1 {
oc.Init(outbound, conn) return nil, utils.VersionNegotiationFailed
return oc, nil }
if selectedVersion == 0xff {
return nil, utils.VersionNegotiationFailed
}
rc := connection.NewInboundConnection(conn)
return rc, nil
} }

View File

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

56
vendor/github.com/s-rah/go-ricochet/utils/crypto.go generated vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6
vendor/manifest vendored
View File

@ -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
}, },
{ {