Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

58 changed files with 997 additions and 1820 deletions

View File

@ -1,51 +0,0 @@
workspace:
base: /go
path: src/git.openprivacy.ca/openprivacy/libricochet-go
pipeline:
fetch:
image: golang
environment:
- GO111MODULE=on
commands:
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
- chmod a+x tor
- go get -u golang.org/x/lint/golint
- go mod download
quality:
image: golang
environment:
- GO111MODULE=on
commands:
- go list ./... | xargs go vet
- go list ./... | grep -v "/wire/" | xargs golint -set_exit_status
units-tests:
image: golang
environment:
- GO111MODULE=on
commands:
- sh testing/tests.sh
integ-test:
image: golang
environment:
- GO111MODULE=on
commands:
- ./tor -f ./torrc
- sleep 15
- go test -race -v git.openprivacy.ca/openprivacy/libricochet-go/testing
notify-email:
image: drillster/drone-email
host: build.openprivacy.ca
port: 25
skip_verify: true
from: drone@openprivacy.ca
when:
status: [ failure ]
notify-gogs:
image: openpriv/drone-gogs
when:
event: pull_request
status: [ success, changed, failure ]
secrets: [gogs_account_token]
gogs_url: https://git.openprivacy.ca

6
.gitignore vendored
View File

@ -1,9 +1,3 @@
go-ricochet-coverage.out go-ricochet-coverage.out
*~ *~
*.out *.out
.idea
.reviewboardrc
/vendor/
/testing/tor/
/connectivity/tor/
/tor/

30
Godeps/Godeps.json generated Normal file
View File

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

5
Godeps/Readme generated Normal file
View File

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

View File

@ -25,7 +25,7 @@ SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Autogenerated protobuf code was generated using the proto files 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/
@ -61,4 +61,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
libricochet-go 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

@ -1,16 +1,51 @@
# libricochet-go [![Go Report Card](https://goreportcard.com/badge/git.openprivacy.ca/openprivacy/libricochet-go)](https://goreportcard.com/report/git.openprivacy.ca/openprivacy/libricochet-go) # libricochet-go [![Build Status](https://travis-ci.org/s-rah/go-ricochet.svg?branch=master)](https://travis-ci.org/s-rah/go-ricochet) [![Go Report Card](https://goreportcard.com/badge/github.com/s-rah/go-ricochet)](https://goreportcard.com/report/github.com/s-rah/go-ricochet) [![Coverage Status](https://coveralls.io/repos/github/s-rah/go-ricochet/badge.svg?branch=master)](https://coveralls.io/github/s-rah/go-ricochet?branch=master)
libricochet-go is an experimental implementation of the [Ricochet Protocol](https://ricochet.im) libricochet-go is an experimental implementation of the [Ricochet Protocol](https://ricochet.im)
written in Go. in Go.
## Differences to Ricochet IM ## Features
* *V3 Onion Support* - libricochet-go updates the Ricochet protocol to use V3 tor onion service addresses, and implements a new authentication protocol providing greater deniability. * A simple API that you can use to build Automated Ricochet Applications
* *Library* - libricochet-go is designed to be integrated within your application, allowing your application to communicate with other peers or programs in a way that is privacy preserving and metadata resistant. * A suite of regression tests that test protocol compliance.
## Using libricochet-go ## Building an Automated Ricochet Application
Checkout our [EchoBot Example](https://git.openprivacy.ca/openprivacy/libricochet-go/src/master/application/examples/echobot) to get started. Below is a simple echo bot, which responds to any chat message. You can also find this code under `examples/echobot`
package main
import (
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"log"
"time"
)
func main() {
echobot := new(application.RicochetApplication)
pk, err := utils.LoadPrivateKeyFromFile("./private_key")
if err != nil {
log.Fatalf("error reading private key file: %v", err)
}
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", 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)
}
Each automated ricochet service can extend of the `StandardRicochetService`. From there
certain functions can be extended to fully build out a complete application.
## Security and Usage Note ## Security and Usage Note

View File

@ -1,22 +1,13 @@
package application package application
// AcceptAllContactHandler is a pass through Contact Handler. It is currently only used by the integration test.
// TODO: DEPRECATE
type AcceptAllContactHandler struct{} type AcceptAllContactHandler struct{}
// ContactRequest returns "Pending" for everything
func (aach *AcceptAllContactHandler) ContactRequest(name string, message string) string { func (aach *AcceptAllContactHandler) ContactRequest(name string, message string) string {
return "Pending" return "Pending"
} }
// ContactRequestRejected is a noop
func (aach *AcceptAllContactHandler) ContactRequestRejected() { func (aach *AcceptAllContactHandler) ContactRequestRejected() {
} }
// ContactRequestAccepted is a noop
func (aach *AcceptAllContactHandler) ContactRequestAccepted() { func (aach *AcceptAllContactHandler) ContactRequestAccepted() {
} }
// ContactRequestError is a noop
func (aach *AcceptAllContactHandler) ContactRequestError() { func (aach *AcceptAllContactHandler) ContactRequestError() {
} }

View File

@ -2,13 +2,10 @@ package application
import ( import (
"crypto/rsa" "crypto/rsa"
"golang.org/x/crypto/ed25519"
) )
// AcceptAllContactManager implements the contact manager interface an presumes // AcceptAllContactManager implements the contact manager interface an presumes
// all connections are allowed. // all connections are allowed.
// It is currently used by the Cwtch Server.
// TODO Deprecate
type AcceptAllContactManager struct { type AcceptAllContactManager struct {
} }
@ -17,12 +14,6 @@ func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rs
return true, true return true, true
} }
// LookupContactV3 returns that a contact is known and allowed to communicate for all cases.
func (aacm *AcceptAllContactManager) LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
return true, true
}
// ContactRequest accepts every single Contact Request
func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string { func (aacm *AcceptAllContactManager) ContactRequest(name string, message string) string {
return "Accepted" return "Accepted"
} }

View File

@ -1,38 +1,31 @@
package application package application
import ( import (
"git.openprivacy.ca/openprivacy/connectivity" "crypto/rsa"
"git.openprivacy.ca/openprivacy/libricochet-go" "git.openprivacy.ca/openprivacy/libricochet-go"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/log" "log"
"net" "net"
"sync" "sync"
) )
const (
// RicochetPort is the default port used by ricochet applications
RicochetPort = 9878
)
// RicochetApplication bundles many useful constructs that are // RicochetApplication bundles many useful constructs that are
// likely standard in a ricochet application // likely standard in a ricochet application
type RicochetApplication struct { type RicochetApplication struct {
contactManager ContactManagerInterface contactManager ContactManagerInterface
v3identity identity.Identity privateKey *rsa.PrivateKey
name string name string
ls connectivity.ListenService l net.Listener
acn connectivity.ACN instances []*ApplicationInstance
instances []*Instance
lock sync.Mutex lock sync.Mutex
aif InstanceFactory aif ApplicationInstanceFactory
} }
// Init initializes the underlying RicochetApplication datastructure, making it ready for use func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af ApplicationInstanceFactory, cm ContactManagerInterface) {
func (ra *RicochetApplication) Init(acn connectivity.ACN, name string, v3identity identity.Identity, af InstanceFactory, cm ContactManagerInterface) {
ra.acn = acn
ra.name = name ra.name = name
ra.v3identity = v3identity ra.privateKey = pk
ra.aif = af ra.aif = af
ra.contactManager = cm ra.contactManager = cm
} }
@ -41,71 +34,66 @@ func (ra *RicochetApplication) Init(acn connectivity.ACN, name string, v3identit
func (ra *RicochetApplication) handleConnection(conn net.Conn) { func (ra *RicochetApplication) handleConnection(conn net.Conn) {
rc, err := goricochet.NegotiateVersionInbound(conn) rc, err := goricochet.NegotiateVersionInbound(conn)
if err != nil { if err != nil {
log.Errorln("There was an error") log.Printf("There was an error")
conn.Close() conn.Close()
return return
} }
ich := connection.HandleInboundConnection(rc) ich := connection.HandleInboundConnection(rc)
err = ich.ProcessAuthAsV3Server(ra.v3identity, ra.contactManager.LookupContactV3) err = ich.ProcessAuthAsServer(identity.Initialize(ra.name, ra.privateKey), ra.contactManager.LookupContact)
if err != nil { if err != nil {
log.Errorf("There was an error authenticating the connection: %v", err) log.Printf("There was an error")
conn.Close() conn.Close()
return return
} }
rc.TraceLog(true)
rai := ra.aif.GetApplicationInstance(rc) rai := ra.aif.GetApplicationInstance(rc)
ra.lock.Lock() ra.lock.Lock()
ra.instances = append(ra.instances, rai) ra.instances = append(ra.instances, rai)
ra.lock.Unlock() ra.lock.Unlock()
rc.Process(rai) rc.Process(rai)
// rc.Process ends when the connection ends.
// Remove it from the application's list of instances
ra.lock.Lock()
for i, x := range ra.instances {
if x == rai {
ra.instances = append(ra.instances[:i], ra.instances[i+1:]...)
break
}
}
ra.lock.Unlock()
} }
// HandleApplicationInstance delegates handling of a given Instance to the Application. func (ra *RicochetApplication) HandleApplicationInstance(rai *ApplicationInstance) {
func (ra *RicochetApplication) HandleApplicationInstance(rai *Instance) {
ra.lock.Lock() ra.lock.Lock()
ra.instances = append(ra.instances, rai) ra.instances = append(ra.instances, rai)
ra.lock.Unlock() ra.lock.Unlock()
} }
// Open a connection to another Ricochet peer at onionAddress. Infof they are unknown to use, use requestMessage (otherwise can be blank) // Open a connection to another Ricochet peer at onionAddress. If they are unknown to use, use requestMessage (otherwise can be blank)
func (ra *RicochetApplication) Open(onionAddress string, requestMessage string) (*Instance, error) { func (ra *RicochetApplication) Open(onionAddress string, requestMessage string) (*ApplicationInstance, error) {
rc, err := goricochet.Open(ra.acn, onionAddress) rc, err := goricochet.Open(onionAddress)
rc.TraceLog(true)
if err != nil { if err != nil {
log.Errorf("Error in application.Open(): %v\n", err) log.Printf("Error in application.Open(): %v\n", err)
return nil, err return nil, err
} }
och := connection.HandleOutboundConnection(rc) known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(identity.Initialize(ra.name, ra.privateKey))
_, err = och.ProcessAuthAsV3Client(ra.v3identity)
if err != nil {
log.Errorf("There was an error authenticating the connection: %v", err)
return nil, err
}
rai := ra.aif.GetApplicationInstance(rc) rai := ra.aif.GetApplicationInstance(rc)
go rc.Process(rai) go rc.Process(rai)
if !known {
err := rc.Do(func() error {
_, err := rc.RequestOpenChannel("im.ricochet.contact.request",
&channels.ContactRequestChannel{
Handler: new(AcceptAllContactHandler),
Name: ra.name,
Message: requestMessage,
})
return err
})
if err != nil {
log.Printf("could not contact %s", err)
}
}
ra.HandleApplicationInstance(rai) ra.HandleApplicationInstance(rai)
return rai, nil return rai, nil
} }
// Broadcast performs the given function do() over all application instance (all connected peers) func (ra *RicochetApplication) Broadcast(do func(rai *ApplicationInstance)) {
func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) {
ra.lock.Lock() ra.lock.Lock()
for _, rai := range ra.instances { for _, rai := range ra.instances {
do(rai) do(rai)
@ -113,45 +101,21 @@ func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) {
ra.lock.Unlock() ra.lock.Unlock()
} }
// Shutdown stops a RicochetApplication, terminating all child processes and resources
func (ra *RicochetApplication) Shutdown() { func (ra *RicochetApplication) Shutdown() {
ra.lock.Lock() ra.l.Close()
ra.ls.Close()
for _, instance := range ra.instances { for _, instance := range ra.instances {
instance.Connection.Close() instance.Connection.Conn.Close()
} }
ra.lock.Unlock()
} }
// Close kills a connection by a given Onion Address func (ra *RicochetApplication) Run(l net.Listener) {
func (ra *RicochetApplication) Close(onion string) { if ra.privateKey == nil || ra.contactManager == nil {
ra.lock.Lock()
for _, instance := range ra.instances {
if instance.RemoteHostname == onion {
instance.Connection.Close()
}
}
ra.lock.Unlock()
}
// ConnectionCount returns the number of concurrent connections to the application
func (ra *RicochetApplication) ConnectionCount() int {
ra.lock.Lock()
defer ra.lock.Unlock()
return len(ra.instances)
}
// Run handles a Listen object and Accepts and handles new connections
func (ra *RicochetApplication) Run(ls connectivity.ListenService) {
if !ra.v3identity.Initialized() || ra.contactManager == nil {
return return
} }
ra.lock.Lock() ra.l = l
ra.ls = ls
ra.lock.Unlock()
var err error var err error
for err == nil { for err == nil {
conn, err := ra.ls.Accept() conn, err := ra.l.Accept()
if err == nil { if err == nil {
go ra.handleConnection(conn) go ra.handleConnection(conn)
} else { } else {

View File

@ -5,49 +5,31 @@ import (
"git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/connection"
) )
// Instance is a concrete instance of a ricochet application, encapsulating a connection // ApplicationInstance is a concrete instance of a ricochet application, encapsulating a connection
type Instance struct { type ApplicationInstance struct {
connection.AutoConnectionHandler connection.AutoConnectionHandler
Connection *connection.Connection Connection *connection.Connection
RemoteHostname string RemoteHostname string
} }
// InstanceFactory generates ApplicationInstances on a specific connection. // ApplicationInstanceFactory
type InstanceFactory struct { type ApplicationInstanceFactory struct {
handlerMap map[string]func(*Instance) func() channels.Handler handlerMap map[string]func(*ApplicationInstance) func() channels.Handler
} }
// Init sets up an Application Factory // Init sets up an Application Factory
func (af *InstanceFactory) Init() { func (af *ApplicationInstanceFactory) Init() {
af.handlerMap = make(map[string]func(*Instance) func() channels.Handler) af.handlerMap = make(map[string]func(*ApplicationInstance) func() channels.Handler)
} }
// AddHandler defines a channel type -> handler construct function // AddHandler defines a channel type -> handler construct function
func (af *InstanceFactory) AddHandler(ctype string, chandler func(*Instance) func() channels.Handler) { func (af *ApplicationInstanceFactory) AddHandler(ctype string, chandler func(*ApplicationInstance) func() channels.Handler) {
af.handlerMap[ctype] = chandler af.handlerMap[ctype] = chandler
} }
// GetHandlers returns all handlers
func (af *InstanceFactory) GetHandlers() []string {
keys := make([]string, len(af.handlerMap))
i := 0
for k := range af.handlerMap {
keys[i] = k
i++
}
return keys
}
// GetHandler returns a set handler for the channel type.
func (af *InstanceFactory) GetHandler(ctype string) func(*Instance) func() channels.Handler {
return af.handlerMap[ctype]
}
// GetApplicationInstance builds a new application instance using a connection as a base. // GetApplicationInstance builds a new application instance using a connection as a base.
func (af *InstanceFactory) GetApplicationInstance(rc *connection.Connection) *Instance { func (af *ApplicationInstanceFactory) GetApplicationInstance(rc *connection.Connection) *ApplicationInstance {
rai := new(Instance) rai := new(ApplicationInstance)
rai.Init() rai.Init()
rai.RemoteHostname = rc.RemoteHostname rai.RemoteHostname = rc.RemoteHostname
rai.Connection = rc rai.Connection = rc

View File

@ -2,12 +2,10 @@ package application
import ( import (
"crypto/rsa" "crypto/rsa"
"golang.org/x/crypto/ed25519"
) )
// ContactManagerInterface provides a mechanism for autonous applications // ContactManagerInterface provides a mechanism for autonous applications
// to make decisions on what connections to accept or reject. // to make decisions on what connections to accept or reject.
type ContactManagerInterface interface { type ContactManagerInterface interface {
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)
} }

View File

@ -1,112 +0,0 @@
package alicebot
import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/log"
"golang.org/x/crypto/ed25519"
"os"
"time"
)
// NewAliceBot creates a new AliceBot and establishes a connection to the given onion server.
func NewAliceBot(acn connectivity.ACN, onion string) AliceBot {
alice := new(alicebot)
alice.messages = make(map[uint32]string)
var err error
alice.pub, alice.priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Errorf("[alice] error generating key: %v", err)
os.Exit(1)
}
rc, err := goricochet.Open(acn, onion)
if err != nil {
log.Errorf("[alice] error connecting to echobot: %v", err)
os.Exit(1)
}
_, err = connection.HandleOutboundConnection(rc).ProcessAuthAsV3Client(identity.InitializeV3("alice", &alice.priv, &alice.pub))
if err != nil {
log.Errorf("[alice] failed to authenticate connection: %v", err)
os.Exit(1)
}
alice.rc = rc
ach := connection.AutoConnectionHandler{}
ach.Init()
ach.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = alice
return chat
})
go alice.rc.Process(&ach)
log.Infof("[alice] requesting channel...")
alice.rc.Do(func() error {
chatchannel := channels.ChatChannel{}
chatchannel.Handler = alice
_, err := alice.rc.RequestOpenChannel("im.ricochet.chat", &chatchannel)
if err != nil {
log.Errorf("failed requestopenchannel: %v", err)
os.Exit(1)
}
return nil
})
return alice
}
type alicebot struct {
messages map[uint32]string
pub ed25519.PublicKey
priv ed25519.PrivateKey
mID int
rc *connection.Connection
}
// AliceBot is an interface for alicebot, allowing callers to send and receive messages.
type AliceBot interface {
channels.ChatChannelHandler
SendMessage(string)
}
// SendMessage can be called to send a message to EchoBot
func (ab *alicebot) SendMessage(message string) {
// The following code opens (or creates) a new im.ricochet.chat channel to the connected service
// and sends a message.
log.Infof("[alice] sending...")
ab.rc.Do(func() error {
channel := ab.rc.Channel("im.ricochet.chat", channels.Outbound)
id, err := channels.SendMessageOnChatChannel(channel, message)
if err == nil {
ab.messages[id] = message
}
return err
})
}
// OpenInbound is called when EchoBot attempts to open a channel with AliceBot
func (ab *alicebot) OpenInbound() {
log.Infof("[alice] inbound connection established")
}
// ChatMessage is called whenever AliceBot receives a message from EchoBot
func (ab *alicebot) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("[alice] got message from echobot: %s", message)
return true
}
// ChatMessageAck is called whenever AliceBot received an acknowledgement of a previously sent message.
func (ab *alicebot) ChatMessageAck(messageID uint32, accepted bool) {
log.Infof("[alice] message \"%s\" ack'd", ab.messages[messageID])
}

View File

@ -1,35 +1,26 @@
package main package main
import ( import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/application/examples/echobot/alicebot"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/log" "log"
"time" "time"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"golang.org/x/crypto/ed25519"
"os"
) )
// EchoBotInstance is an Instance of the EchoBot Application. One is created for every connected peer.
type EchoBotInstance struct { type EchoBotInstance struct {
rai *application.Instance rai *application.ApplicationInstance
ra *application.RicochetApplication ra *application.RicochetApplication
} }
// Init establishes an EchoBotInstance func (ebi *EchoBotInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
func (ebi *EchoBotInstance) Init(rai *application.Instance, ra *application.RicochetApplication) {
ebi.rai = rai ebi.rai = rai
ebi.ra = ra ebi.ra = ra
} }
// OpenInbound is called when AliceBot opens a ChatChannel. In this case, because we want EchoBot to respond we // We always want bidirectional chat channels
// need to open a new channel in the other direction.
func (ebi *EchoBotInstance) OpenInbound() { func (ebi *EchoBotInstance) OpenInbound() {
log.Debugln("OpenInbound() ChatChannel handler called...") log.Println("OpenInbound() ChatChannel handler called...")
outboutChatChannel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound) outboutChatChannel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
if outboutChatChannel == nil { if outboutChatChannel == nil {
ebi.rai.Connection.Do(func() error { ebi.rai.Connection.Do(func() error {
@ -42,67 +33,48 @@ func (ebi *EchoBotInstance) OpenInbound() {
} }
} }
// ChatMessage is called whenever a connected peer sends a message to EchoBot
func (ebi *EchoBotInstance) ChatMessage(messageID uint32, when time.Time, message string) bool { func (ebi *EchoBotInstance) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("message from %v - %v", ebi.rai.RemoteHostname, message) log.Printf("message from %v - %v", ebi.rai.RemoteHostname, message)
ebi.SendChatMessage(ebi.rai, ebi.rai.RemoteHostname+" "+message) go ebi.ra.Broadcast(func(rai *application.ApplicationInstance) {
ebi.SendChatMessage(rai, ebi.rai.RemoteHostname+" "+message)
})
return true return true
} }
// ChatMessageAck is called whenever a connected peer acknowledges a message that EchoBot sent.
func (ebi *EchoBotInstance) ChatMessageAck(messageID uint32, accepted bool) { func (ebi *EchoBotInstance) ChatMessageAck(messageID uint32, accepted bool) {
} }
// SendChatMessage sends a chat message to the given echobot instance func (ebi *EchoBotInstance) SendChatMessage(rai *application.ApplicationInstance, message string) {
func (ebi *EchoBotInstance) SendChatMessage(rai *application.Instance, message string) { rai.Connection.Do(func() error {
ebi.rai.Connection.Do(func() error { channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
channel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound) if channel != nil {
// We are swallowing the message id and the error here, in reality you will want to handle it. chatchannel, ok := channel.Handler.(*channels.ChatChannel)
channels.SendMessageOnChatChannel(channel, message) if ok {
chatchannel.SendMessage(message)
}
}
return nil return nil
}) })
} }
// main() encapsulates an entire ricochet ecosystem, from starting tor, to generating onion service keys to managing
// the launching of both the echobot server and the alicebot peer.
// In most systems you will only be handling one or two of these subsystems at any given time so this example might seem
// bloated, but we have tried to highlight the most interesting aspects to allow easy application to new domains.
func main() { func main() {
// Set up Logging.
log.SetLevel(log.LevelInfo)
log.AddEverythingFromPattern("connectivity")
// Set up Tor
acn, err := tor.NewTorACN(".", "")
if err != nil {
log.Errorf("Unable to start Tor: %v", err)
os.Exit(1)
}
defer acn.Close()
// Set up the Echobot Server
echobot := new(application.RicochetApplication) echobot := new(application.RicochetApplication)
cpubk, cprivk, err := ed25519.GenerateKey(rand.Reader) pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key")
if err != nil { if err != nil {
log.Errorf("Error generating keys: %v", err) log.Fatalf("error reading private key file: %v", err)
os.Exit(1)
} }
// Turn on the echobot onion service in Tor. l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", pk, 9878)
listenService, err := acn.Listen(cprivk, application.RicochetPort)
if err != nil { if err != nil {
log.Errorf("error setting up onion service: %v", err) log.Fatalf("error setting up onion service: %v", err)
os.Exit(1)
} }
// This next section looks complicated (and it is a little), but all it is doing is allowing echobot to handle af := application.ApplicationInstanceFactory{}
// im.ricochet.chat type channels.
af := application.InstanceFactory{}
af.Init() af.Init()
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler { af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
ebi := new(EchoBotInstance) ebi := new(EchoBotInstance)
ebi.Init(rai, echobot) ebi.Init(rai, echobot)
return func() channels.Handler { return func() channels.Handler {
@ -112,21 +84,7 @@ func main() {
} }
}) })
// Thee next few lines turn on echobot and make it available to listen to new connections. echobot.Init("echobot", pk, af, new(application.AcceptAllContactManager))
// Note that we initialize a V3 identity for echobot. log.Printf("echobot listening on %s", l.Addr().String())
echobot.Init(acn, "echobot", identity.InitializeV3("echobot", &cprivk, &cpubk), af, new(application.AcceptAllContactManager)) echobot.Run(l)
log.Infof("echobot listening on %v", listenService.AddressFull())
go echobot.Run(listenService)
// Now we wait a little bit for everything to wire itself together.
log.Infoln("counting to five ...")
time.Sleep(time.Second * 5)
// Finally, in these last few lines we setup an AliceBot who simply sends messages to echobot
alice := alicebot.NewAliceBot(acn, listenService.AddressIdentity())
alice.SendMessage("be gay")
alice.SendMessage("do crime")
// stick around and see what happens
time.Sleep(time.Second * 30)
} }

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

@ -25,5 +25,4 @@ type Channel struct {
SendMessage func([]byte) SendMessage func([]byte)
CloseChannel func() CloseChannel func()
DelegateAuthorization func() DelegateAuthorization func()
DelegateEncryption func([32]byte)
} }

View File

@ -1,11 +1,10 @@
package channels package channels
import ( import (
"errors" "github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat" "git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"time" "time"
) )
@ -58,19 +57,6 @@ func (cc *ChatChannel) SendMessageWithTime(message string, when time.Time) uint3
return messageID return messageID
} }
// SendMessageOnChatChannel is a wrapper function which performs some necessary boilerplate
// to make sending messages easier.
func SendMessageOnChatChannel(channel *Channel, message string) (uint32, error) {
if channel != nil {
peerchannel, ok := channel.Handler.(*ChatChannel)
if ok {
return peerchannel.SendMessage(message), nil
}
return 0, errors.New("channel is not an im.ricochet.chat channel")
}
return 0, errors.New("channel pointer is nil")
}
// Acknowledge indicates that the given messageID was received, and whether // Acknowledge indicates that the given messageID was received, and whether
// it was accepted. // it was accepted.
func (cc *ChatChannel) Acknowledge(messageID uint32, accepted bool) { func (cc *ChatChannel) Acknowledge(messageID uint32, accepted bool) {
@ -105,7 +91,7 @@ func (cc *ChatChannel) Bidirectional() bool {
// RequiresAuthentication - chat channels require hidden service auth // RequiresAuthentication - chat channels require hidden service auth
func (cc *ChatChannel) RequiresAuthentication() string { func (cc *ChatChannel) RequiresAuthentication() string {
return "im.ricochet.auth.3dh" return "im.ricochet.auth.hidden-service"
} }
// OpenInbound is the first method called for an inbound channel request. // OpenInbound is the first method called for an inbound channel request.

View File

@ -1,10 +1,10 @@
package channels package channels
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat" "git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing" "testing"
"time" "time"
) )
@ -25,8 +25,8 @@ func TestChatChannelOptions(t *testing.T) {
if chatChannel.Bidirectional() { if chatChannel.Bidirectional() {
t.Errorf("ChatChannel should not be bidirectional") t.Errorf("ChatChannel should not be bidirectional")
} }
if chatChannel.RequiresAuthentication() != "im.ricochet.auth.3dh" { if chatChannel.RequiresAuthentication() != "im.ricochet.auth.hidden-service" {
t.Errorf("ChatChannel should require im.ricochet.auth.3dh. Instead requires: %s", chatChannel.RequiresAuthentication()) t.Errorf("ChatChannel should require im.ricochet.auth.hidden-service. Instead requires: %s", chatChannel.RequiresAuthentication())
} }
} }

View File

@ -1,10 +1,10 @@
package channels package channels
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact" "git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
) )
// Defining Versions // Defining Versions

View File

@ -1,10 +1,10 @@
package channels package channels
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact" "git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing" "testing"
) )

View File

@ -0,0 +1,250 @@
package channels
import (
"crypto"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/asn1"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"io"
)
const (
// InvalidClientCookieError - returned when the client provides a cookie with the wrong length
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
Identity identity.Identity
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.Identity.Initialized() {
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.Identity.Initialized() {
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)[:])
challenge := ah.GenChallenge(ah.Identity.Hostname(), ah.ServerHostname)
signature, err := ah.Identity.Sign(challenge)
if err != nil {
ah.channel.SendMessage([]byte{})
return
}
messageBuilder := new(utils.MessageBuilder)
proof := messageBuilder.Proof(ah.Identity.PublicKeyBytes(), signature)
ah.channel.SendMessage(proof)
}
}
}
// Packet is called for each raw packet received on this channel.
// Input: Client -> [Proof] -> Remote
// 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())
if err != nil {
ah.ServerAuthInvalid(err)
ah.channel.SendMessage([]byte{})
return
}
serverHostname := ah.Identity.Hostname()
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,150 @@
package channels
import (
"bytes"
"crypto/rsa"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"testing"
)
func TestGenChallenge(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
authHandler.AddClientCookie([]byte("abcdefghijklmnop"))
authHandler.AddServerCookie([]byte("qrstuvwxyz012345"))
challenge := authHandler.GenChallenge("test.onion", "notareal.onion")
expectedChallenge := []byte{0xf5, 0xdb, 0xfd, 0xf0, 0x3d, 0x94, 0x14, 0xf1, 0x4b, 0x37, 0x93, 0xe2, 0xa5, 0x11, 0x4a, 0x98, 0x31, 0x90, 0xea, 0xb8, 0x95, 0x7a, 0x2e, 0xaa, 0xd0, 0xd2, 0x0c, 0x74, 0x95, 0xba, 0xab, 0x73}
t.Log(challenge, expectedChallenge)
if bytes.Compare(challenge[:], expectedChallenge[:]) != 0 {
t.Errorf("HiddenServiceAuthChannel Challenge Is Invalid, Got %x, Expected %x", challenge, expectedChallenge)
}
}
func TestGenClientCookie(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
clientCookie := authHandler.GenClientCookie()
if clientCookie != authHandler.clientCookie {
t.Errorf("HiddenServiceAuthChannel Client Cookies are Different %x %x", clientCookie, authHandler.clientCookie)
}
}
func TestGenServerCookie(t *testing.T) {
authHandler := new(HiddenServiceAuthChannel)
serverCookie := authHandler.GenServerCookie()
if serverCookie != authHandler.serverCookie {
t.Errorf("HiddenServiceAuthChannel Server Cookies are Different %x %x", serverCookie, authHandler.serverCookie)
}
}
func TestHiddenServiceAuthChannelOptions(t *testing.T) {
hiddenServiceAuthChannel := new(HiddenServiceAuthChannel)
if hiddenServiceAuthChannel.Type() != "im.ricochet.auth.hidden-service" {
t.Errorf("AuthHiddenService has wrong type %s", hiddenServiceAuthChannel.Type())
}
if !hiddenServiceAuthChannel.OnlyClientCanOpen() {
t.Errorf("AuthHiddenService Should be Client Open Only")
}
if !hiddenServiceAuthChannel.Singleton() {
t.Errorf("AuthHiddenService Should be a Singelton")
}
if hiddenServiceAuthChannel.Bidirectional() {
t.Errorf("AuthHiddenService Should not be bidirectional")
}
if hiddenServiceAuthChannel.RequiresAuthentication() != "none" {
t.Errorf("AuthHiddenService should require no authorization. Instead requires: %s", hiddenServiceAuthChannel.RequiresAuthentication())
}
}
func GetOpenAuthenticationChannelMessage() *Protocol_Data_Control.OpenChannel {
// Construct the Open Authentication Channel Message
messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.OpenAuthenticationChannel(1, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// We have just constructed this so there is little
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
return res.GetOpenChannel()
}
func TestAuthenticationOpenInbound(t *testing.T) {
id := identity.Init("../testing/private_key")
opm := GetOpenAuthenticationChannelMessage()
authHandler := new(HiddenServiceAuthChannel)
authHandler.Identity = id
channel := Channel{ID: 1}
response, err := authHandler.OpenInbound(&channel, opm)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetChannelResult() == nil || !res.GetChannelResult().GetOpened() {
t.Errorf("Response not a Open Channel Result %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err)
}
}
func TestAuthenticationOpenOutbound(t *testing.T) {
id := identity.Init("../testing/private_key")
authHandler := new(HiddenServiceAuthChannel)
authHandler.Identity = id
authHandler.ServerHostname = "kwke2hntvyfqm7dr"
channel := Channel{ID: 1}
response, err := authHandler.OpenOutbound(&channel)
if err == nil {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
if res.GetOpenChannel() == nil {
t.Errorf("Open Channel Packet not included %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err)
}
}
func TestAuthenticationOpenOutboundResult(t *testing.T) {
id := identity.Init("../testing/private_key")
authHandlerA := new(HiddenServiceAuthChannel)
authHandlerB := new(HiddenServiceAuthChannel)
authHandlerA.Identity = id
authHandlerA.ServerHostname = "kwke2hntvyfqm7dr"
authHandlerA.ClientAuthResult = func(accepted, known bool) {}
channelA := Channel{ID: 1, Direction: Outbound}
channelA.SendMessage = func(message []byte) {
authHandlerB.Packet(message)
}
channelA.DelegateAuthorization = func() {}
channelA.CloseChannel = func() {}
response, _ := authHandlerA.OpenOutbound(&channelA)
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
authHandlerB.Identity = id
authHandlerB.ServerAuthValid = func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { return true, true }
authHandlerB.ServerAuthInvalid = func(err error) { t.Error("server received invalid auth") }
channelB := Channel{ID: 1, Direction: Inbound}
channelB.SendMessage = func(message []byte) {
authHandlerA.Packet(message)
}
channelB.DelegateAuthorization = func() {}
channelB.CloseChannel = func() {}
response, _ = authHandlerB.OpenInbound(&channelB, res.GetOpenChannel())
res = new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
authHandlerA.OpenOutboundResult(nil, res.GetChannelResult())
}

View File

@ -1,185 +0,0 @@
package inbound
import (
"crypto/rand"
tapirutils "cwtch.im/tapir/utils"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
"github.com/golang/protobuf/proto"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/sha3"
)
// Server3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
type Server3DHAuthChannel struct {
// PrivateKey must be set for client-side authentication channels
ServerIdentity identity.Identity
// Callbacks
ServerAuthValid func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)
ServerAuthInvalid func(err error)
// Internal state
clientPubKey, clientEphmeralPublicKey, serverEphemeralPublicKey ed25519.PublicKey
serverEphemeralPrivateKey ed25519.PrivateKey
channel *channels.Channel
}
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
func (ah *Server3DHAuthChannel) Type() string {
return "im.ricochet.auth.3dh"
}
// Singleton Returns whether or not the given channel type is a singleton
func (ah *Server3DHAuthChannel) Singleton() bool {
return true
}
// OnlyClientCanOpen ...
func (ah *Server3DHAuthChannel) OnlyClientCanOpen() bool {
return true
}
// Bidirectional Returns whether or not the given channel allows anyone to send messages
func (ah *Server3DHAuthChannel) Bidirectional() bool {
return false
}
// RequiresAuthentication Returns whether or not the given channel type requires authentication
func (ah *Server3DHAuthChannel) RequiresAuthentication() string {
return "none"
}
// Closed is called when the channel is closed for any reason.
func (ah *Server3DHAuthChannel) Closed(err error) {
}
// OpenInbound is the first method called for an inbound channel request.
// Infof an error is returned, the channel is rejected. Infof a RawMessage is
// returned, it will be sent as the ChannelResult message.
// Remote -> [Open Authentication Channel] -> Local
func (ah *Server3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
ah.channel = channel
clientPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientPublicKey)
clientEphmeralPublicKey, _ := proto.GetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientEphmeralPublicKey)
clientPubKeyBytes := clientPublicKey.([]byte)
ah.clientPubKey = ed25519.PublicKey(clientPubKeyBytes[:])
clientEphmeralPublicKeyBytes := clientEphmeralPublicKey.([]byte)
ah.clientEphmeralPublicKey = ed25519.PublicKey(clientEphmeralPublicKeyBytes[:])
clientHostname := utils.GetTorV3Hostname(clientPubKeyBytes)
log.Debugf("Received inbound auth 3DH request from %v", clientHostname)
// Generate Ephemeral Keys
pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader)
ah.serverEphemeralPublicKey = pubkey
ah.serverEphemeralPrivateKey = privkey
var serverPubKeyBytes, serverEphemeralPubKeyBytes [32]byte
copy(serverPubKeyBytes[:], ah.ServerIdentity.PublicKeyBytes()[:])
copy(serverEphemeralPubKeyBytes[:], ah.serverEphemeralPublicKey[:])
messageBuilder := new(utils.MessageBuilder)
channel.Pending = false
return messageBuilder.Confirm3EDHAuthChannel(ah.channel.ID, serverPubKeyBytes, serverEphemeralPubKeyBytes), nil
}
// OpenOutbound is the first method called for an outbound channel request.
// Infof an error is returned, the channel is not opened. Infof a RawMessage is
// returned, it will be sent as the OpenChannel message.
// Local -> [Open Authentication Channel] -> Remote
func (ah *Server3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
return nil, errors.New("server is not allowed to open 3dh channels")
}
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. Infof `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 *Server3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
//
}
// Packet is called for each raw packet received on this channel.
// Input: Client -> [Proof] -> Remote
// OR
// Input: Remote -> [Result] -> Client
func (ah *Server3DHAuthChannel) Packet(data []byte) {
res := new(Protocol_Data_Auth_TripleEDH.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
ah.channel.CloseChannel()
return
}
if res.GetProof() != nil && ah.channel.Direction == channels.Inbound {
// Server Identity <-> Client Ephemeral
secret1 := ah.ServerIdentity.EDH(ah.clientEphmeralPublicKey)
// Server Ephemeral <-> Client Identity
secret2, err := tapirutils.EDH(ah.serverEphemeralPrivateKey, ah.clientPubKey)
if err != nil {
ah.channel.CloseChannel()
return
}
// Ephemeral <-> Ephemeral
secret3, err := tapirutils.EDH(ah.serverEphemeralPrivateKey, ah.clientEphmeralPublicKey)
if err != nil {
ah.channel.CloseChannel()
return
}
var secret [96]byte
copy(secret[0:32], secret1[:])
copy(secret[32:64], secret2[:])
copy(secret[64:96], secret3[:])
pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512)
var key [32]byte
copy(key[:], pkey[:])
var decryptNonce [24]byte
ciphertext := res.GetProof().GetProof()
if len(ciphertext) > 24 {
copy(decryptNonce[:], ciphertext[:24])
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &key)
if ok && string(decrypted) == "Hello World" {
allowed, known := ah.ServerAuthValid(utils.GetTorV3Hostname(ah.clientPubKey), ah.clientPubKey)
ah.channel.DelegateAuthorization()
ah.channel.DelegateEncryption(key)
log.Debugf("3DH Session Decrypted OK. Authenticating Connection!")
messageBuilder := new(utils.MessageBuilder)
result := messageBuilder.AuthResult3DH(allowed, known)
ah.channel.SendMessage(result)
ah.channel.CloseChannel()
return
}
}
messageBuilder := new(utils.MessageBuilder)
result := messageBuilder.AuthResult3DH(false, false)
ah.channel.SendMessage(result)
}
// Any other combination of packets is completely invalid
// Fail the Authorization right here.
ah.channel.CloseChannel()
}

View File

@ -1,126 +0,0 @@
package inbound
import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"golang.org/x/crypto/ed25519"
"testing"
)
func TestServer3DHAuthChannel(t *testing.T) {
cc := new(channels.Channel)
cc.ID = 1
closed := false
cc.CloseChannel = func() { closed = true }
cc.DelegateEncryption = func([32]byte) {}
clientChannel := new(outbound.Client3DHAuthChannel)
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
cid := identity.InitializeV3("", &priv, &pub)
clientChannel.ClientIdentity = cid
ocb, _ := clientChannel.OpenOutbound(cc)
packet := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocb, packet)
s3dhchannel := new(Server3DHAuthChannel)
pub, priv, _ = ed25519.GenerateKey(rand.Reader)
sid := identity.InitializeV3("", &priv, &pub)
s3dhchannel.ServerIdentity = sid
clientChannel.ServerHostname = utils.GetTorV3Hostname(pub)
cr, _ := s3dhchannel.OpenInbound(cc, packet.GetOpenChannel())
proto.Unmarshal(cr, packet)
if packet.GetChannelResult() != nil {
authPacket := new(Protocol_Data_Auth_TripleEDH.Packet)
var lastMessage []byte
cc.SendMessage = func(message []byte) {
t.Logf("Received: %x", message)
proto.Unmarshal(message, authPacket)
lastMessage = message
}
clientChannel.OpenOutboundResult(nil, packet.GetChannelResult())
if closed == true {
t.Fatalf("Should not have closed channel!")
}
if authPacket.Proof == nil {
t.Errorf("Was expected a Proof Packet, instead %v", authPacket)
}
s3dhchannel.ServerAuthValid = func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
if hostname != clientChannel.ClientIdentity.Hostname() {
t.Errorf("Hostname and public key did not match %v %v", hostname, pub)
}
return true, true
}
cc.DelegateAuthorization = func() {}
s3dhchannel.Packet(lastMessage)
} else {
t.Errorf("Should have received a Channel Response from OpenInbound: %v", packet)
}
}
func TestServer3DHAuthChannelReject(t *testing.T) {
cc := new(channels.Channel)
cc.ID = 1
cc.CloseChannel = func() {}
cc.DelegateEncryption = func([32]byte) {}
clientChannel := new(outbound.Client3DHAuthChannel)
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
cid := identity.InitializeV3("", &priv, &pub)
clientChannel.ClientIdentity = cid
ocb, _ := clientChannel.OpenOutbound(cc)
packet := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocb, packet)
s3dhchannel := new(Server3DHAuthChannel)
pub, priv, _ = ed25519.GenerateKey(rand.Reader)
sid := identity.InitializeV3("", &priv, &pub)
s3dhchannel.ServerIdentity = sid
clientChannel.ServerHostname = utils.GetTorV3Hostname(pub)
cr, _ := s3dhchannel.OpenInbound(cc, packet.GetOpenChannel())
proto.Unmarshal(cr, packet)
if packet.GetChannelResult() != nil {
authPacket := new(Protocol_Data_Auth_TripleEDH.Packet)
var lastMessage []byte
cc.SendMessage = func(message []byte) {
proto.Unmarshal(message, authPacket)
// Replace the Auth Proof Packet to cause this to fail.
if authPacket.GetProof() != nil {
authPacket.GetProof().Proof = []byte{}
lastMessage, _ = proto.Marshal(authPacket)
}
}
clientChannel.OpenOutboundResult(nil, packet.GetChannelResult())
if authPacket.Proof == nil {
t.Errorf("Was expected a Proof Packet, instead %v", authPacket)
}
s3dhchannel.ServerAuthInvalid = func(err error) {
if err == nil {
t.Fatal("Server Auth Should have been invalid!")
}
}
cc.DelegateAuthorization = func() {}
s3dhchannel.Packet(lastMessage)
} else {
t.Errorf("Should have received a Channel Response from OpenInbound: %v", packet)
}
}

View File

@ -1,184 +0,0 @@
package outbound
import (
"crypto/rand"
tapirutils "cwtch.im/tapir/utils"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
"github.com/golang/protobuf/proto"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/sha3"
"io"
)
// Client3DHAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
type Client3DHAuthChannel struct {
// PrivateKey must be set for client-side authentication channels
ClientIdentity identity.Identity
ServerHostname string
ClientAuthResult func(bool, bool)
// Internal state
serverPubKey, serverEphemeralPublicKey, clientEphemeralPublicKey ed25519.PublicKey
clientEphemeralPrivateKey ed25519.PrivateKey
channel *channels.Channel
key [32]byte
}
// Type returns the type string for this channel, e.g. "im.ricochet.chat".
func (ah *Client3DHAuthChannel) Type() string {
return "im.ricochet.auth.3dh"
}
// Singleton Returns whether or not the given channel type is a singleton
func (ah *Client3DHAuthChannel) Singleton() bool {
return true
}
// OnlyClientCanOpen ...
func (ah *Client3DHAuthChannel) OnlyClientCanOpen() bool {
return true
}
// Bidirectional Returns whether or not the given channel allows anyone to send messages
func (ah *Client3DHAuthChannel) Bidirectional() bool {
return false
}
// RequiresAuthentication Returns whether or not the given channel type requires authentication
func (ah *Client3DHAuthChannel) RequiresAuthentication() string {
return "none"
}
// Closed is called when the channel is closed for any reason.
func (ah *Client3DHAuthChannel) Closed(err error) {
}
// OpenInbound is the first method called for an inbound channel request.
// Infof an error is returned, the channel is rejected. Infof a RawMessage is
// returned, it will be sent as the ChannelResult message.
// Remote -> [Open Authentication Channel] -> Local
func (ah *Client3DHAuthChannel) OpenInbound(channel *channels.Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
return nil, errors.New("server is not allowed to open inbound auth.3dh channels")
}
// OpenOutbound is the first method called for an outbound channel request.
// Infof an error is returned, the channel is not opened. Infof a RawMessage is
// returned, it will be sent as the OpenChannel message.
// Local -> [Open Authentication Channel] -> Remote
func (ah *Client3DHAuthChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) {
ah.channel = channel
log.Debugf("Opening an outbound connection to %v", ah.ServerHostname)
// Generate Ephemeral Keys
pubkey, privkey, _ := ed25519.GenerateKey(rand.Reader)
ah.clientEphemeralPublicKey = pubkey
ah.clientEphemeralPrivateKey = privkey
messageBuilder := new(utils.MessageBuilder)
channel.Pending = false
var clientPubKeyBytes, clientEphemeralPubKeyBytes [32]byte
copy(clientPubKeyBytes[:], ah.ClientIdentity.PublicKeyBytes()[:])
copy(clientEphemeralPubKeyBytes[:], ah.clientEphemeralPublicKey[:])
return messageBuilder.Open3EDHAuthenticationChannel(ah.channel.ID, clientPubKeyBytes, clientEphemeralPubKeyBytes), nil
}
// OpenOutboundResult is called when a response is received for an
// outbound OpenChannel request. Infof `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 *Client3DHAuthChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) {
serverPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerPublicKey)
serverEphemeralPublicKey, _ := proto.GetExtension(crm, Protocol_Data_Auth_TripleEDH.E_ServerEphmeralPublicKey)
serverPubKeyBytes := serverPublicKey.([]byte)
ah.serverPubKey = ed25519.PublicKey(serverPubKeyBytes[:])
if utils.GetTorV3Hostname(ah.serverPubKey) != ah.ServerHostname {
ah.channel.CloseChannel()
return
}
serverEphmeralPublicKeyBytes := serverEphemeralPublicKey.([]byte)
ah.serverEphemeralPublicKey = ed25519.PublicKey(serverEphmeralPublicKeyBytes[:])
log.Debugf("Public Keys Exchanged. Deriving Encryption Keys and Sending Encrypted Test Message")
// Server Ephemeral <-> Client Identity
secret1, err := tapirutils.EDH(ah.clientEphemeralPrivateKey, ah.serverPubKey)
if err != nil {
ah.channel.CloseChannel()
return
}
// Server Identity <-> Client Ephemeral
secret2 := ah.ClientIdentity.EDH(ah.serverEphemeralPublicKey)
// Ephemeral <-> Ephemeral
secret3, err := tapirutils.EDH(ah.clientEphemeralPrivateKey, ah.serverEphemeralPublicKey)
if err != nil {
ah.channel.CloseChannel()
return
}
var secret [96]byte
copy(secret[0:32], secret1[:])
copy(secret[32:64], secret2[:])
copy(secret[64:96], secret3[:])
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
pkey := pbkdf2.Key(secret[:], secret[:], 4096, 32, sha3.New512)
copy(ah.key[:], pkey[:])
encrypted := secretbox.Seal(nonce[:], []byte("Hello World"), &nonce, &ah.key)
messageBuilder := new(utils.MessageBuilder)
proof := messageBuilder.Proof3DH(encrypted)
ah.channel.SendMessage(proof)
ah.channel.DelegateEncryption(ah.key)
}
// Packet is called for each raw packet received on this channel.
// Input: Client -> [Proof] -> Remote
// OR
// Input: Remote -> [Result] -> Client
func (ah *Client3DHAuthChannel) Packet(data []byte) {
res := new(Protocol_Data_Auth_TripleEDH.Packet)
err := proto.Unmarshal(data[:], res)
if err != nil {
ah.channel.CloseChannel()
return
}
if res.GetResult() != nil {
ah.ClientAuthResult(res.GetResult().GetAccepted(), res.GetResult().GetIsKnownContact())
if res.GetResult().GetAccepted() {
log.Debugf("3DH Session Accepted OK. Authenticated! Connection!")
ah.channel.DelegateAuthorization()
}
return
}
// Any other combination of packets is completely invalid
// Fail the Authorization right here.
ah.channel.CloseChannel()
}

View File

@ -1,11 +1,10 @@
package connection package connection
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/inbound"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing" "testing"
) )
@ -13,23 +12,24 @@ import (
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
ach := new(AutoConnectionHandler) ach := new(AutoConnectionHandler)
ach.Init() ach.Init()
ach.RegisterChannelHandler("im.ricochet.auth.3dh", func() channels.Handler { ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler {
return &inbound.Server3DHAuthChannel{} return &channels.HiddenServiceAuthChannel{}
}) })
// Construct the Open Authentication Channel Message // Construct the Open Authentication Channel Message
messageBuilder := new(utils.MessageBuilder) messageBuilder := new(utils.MessageBuilder)
ocm := messageBuilder.Open3EDHAuthenticationChannel(1, [32]byte{}, [32]byte{}) ocm := messageBuilder.OpenAuthenticationChannel(1, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// We have just constructed this so there is little // We have just constructed this so there is little
// point in doing error checking here in the test // point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet) res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res) proto.Unmarshal(ocm[:], res)
opm := res.GetOpenChannel() opm := res.GetOpenChannel()
//ocmessage, _ := proto.Marshal(opm)
handler, err := ach.OnOpenChannelRequest(opm.GetChannelType()) handler, err := ach.OnOpenChannelRequest(opm.GetChannelType())
if err == nil { if err == nil {
if handler.Type() != "im.ricochet.auth.3dh" { if handler.Type() != "im.ricochet.auth.hidden-service" {
t.Errorf("Failed to authentication handler: %v", handler.Type()) t.Errorf("Failed to authentication handler: %v", handler.Type())
} }
} else { } else {

View File

@ -75,6 +75,7 @@ func (cm *ChannelManager) OpenChannelRequestFromPeer(channelID int32, chandler c
} }
cm.lock.Unlock() cm.lock.Unlock()
// Some channels only allow us to open one of them per connection // Some channels only allow us to open one of them per connection
if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil { if chandler.Singleton() && cm.Channel(chandler.Type(), channels.Inbound) != nil {
return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError return nil, utils.AttemptToOpenMoreThanOneSingletonChannelError

View File

@ -3,12 +3,12 @@ package connection
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
"github.com/golang/protobuf/proto"
"io" "io"
"log"
"sync" "sync"
) )
@ -31,16 +31,16 @@ type Connection struct {
unlockResponseChannel chan bool unlockResponseChannel chan bool
messageBuilder utils.MessageBuilder messageBuilder utils.MessageBuilder
trace bool
closed bool closed bool
closingLock sync.Mutex closing bool
closing bool
// This mutex is exclusively for preventing races during blocking // This mutex is exclusively for preventing races during blocking
// interactions with Process; specifically Do and Break. Don't use // interactions with Process; specifically Do and Break. Don't use
// it for anything else. See those functions for an explanation. // it for anything else. See those functions for an explanation.
processBlockMutex sync.Mutex processBlockMutex sync.Mutex
conn io.ReadWriteCloser Conn io.ReadWriteCloser
IsInbound bool IsInbound bool
am AuthorizationManager am AuthorizationManager
RemoteHostname string RemoteHostname string
@ -67,7 +67,7 @@ func (rc *Connection) init() {
// modelling an Inbound Connection // modelling an Inbound Connection
func NewInboundConnection(conn io.ReadWriteCloser) *Connection { func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
rc := new(Connection) rc := new(Connection)
rc.conn = conn rc.Conn = conn
rc.IsInbound = true rc.IsInbound = true
rc.init() rc.init()
rc.channelManager = NewServerChannelManager() rc.channelManager = NewServerChannelManager()
@ -79,7 +79,7 @@ func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
// modelling an Inbound Connection // modelling an Inbound Connection
func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection { func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection {
rc := new(Connection) rc := new(Connection)
rc.conn = conn rc.Conn = conn
rc.IsInbound = false rc.IsInbound = false
rc.init() rc.init()
rc.RemoteHostname = remoteHostname rc.RemoteHostname = remoteHostname
@ -88,10 +88,17 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn
return rc return rc
} }
// TraceLog turns on debug logging, you shouldn't need to do this but if for some
// reason ricochet isn't working, you can use this to see at what point in the
// protcol trace ricochet is failing.
func (rc *Connection) TraceLog(enabled bool) {
rc.trace = enabled
}
// start // start
func (rc *Connection) start() { func (rc *Connection) start() {
for { for {
packet, err := rc.RecvRicochetPacket(rc.conn) packet, err := rc.RecvRicochetPacket(rc.Conn)
if err != nil { if err != nil {
rc.errorChannel <- err rc.errorChannel <- err
return return
@ -139,11 +146,11 @@ func (rc *Connection) Do(do func() error) error {
} }
// Force process to soft-break so we can lock // Force process to soft-break so we can lock
log.Debugln("request unlocking of process loop for do()") rc.traceLog("request unlocking of process loop for do()")
rc.unlockChannel <- true rc.unlockChannel <- true
log.Debugln("process loop is unlocked for do()") rc.traceLog("process loop is unlocked for do()")
defer func() { defer func() {
log.Debugln("giving up lock process loop after do() ") rc.traceLog("giving up lock process loop after do() ")
rc.unlockResponseChannel <- true rc.unlockResponseChannel <- true
}() }()
@ -170,18 +177,18 @@ func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) er
} }
// Force process to soft-break so we can lock // Force process to soft-break so we can lock
log.Debugln("request unlocking of process loop for do()") rc.traceLog("request unlocking of process loop for do()")
select { select {
case rc.unlockChannel <- true: case rc.unlockChannel <- true:
break break
case <-ctx.Done(): case <-ctx.Done():
log.Debugln("giving up on unlocking process loop for do() because context cancelled") rc.traceLog("giving up on unlocking process loop for do() because context cancelled")
return ctx.Err() return ctx.Err()
} }
log.Debugln("process loop is unlocked for do()") rc.traceLog("process loop is unlocked for do()")
defer func() { defer func() {
log.Debugln("giving up lock process loop after do() ") rc.traceLog("giving up lock process loop after do() ")
rc.unlockResponseChannel <- true rc.unlockResponseChannel <- true
}() }()
@ -196,7 +203,7 @@ func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) er
// are not met on the local side (a nil error return does not mean the // are not met on the local side (a nil error return does not mean the
// channel was opened successfully, because channels open asynchronously). // channel was opened successfully, because channels open asynchronously).
func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler) (*channels.Channel, error) { func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler) (*channels.Channel, error) {
log.Debugln(fmt.Sprintf("requesting open channel of type %s", ctype)) rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
channel, err := rc.buildChannel(handler, rc.channelManager.OpenChannelRequest) channel, err := rc.buildChannel(handler, rc.channelManager.OpenChannelRequest)
if err == nil { if err == nil {
response, err := handler.OpenOutbound(channel) response, err := handler.OpenOutbound(channel)
@ -207,10 +214,10 @@ func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler)
func (rc *Connection) handleChannelOpening(channel *channels.Channel, response []byte, err error) (*channels.Channel, error) { func (rc *Connection) handleChannelOpening(channel *channels.Channel, response []byte, err error) (*channels.Channel, error) {
if err == nil { if err == nil {
rc.SendRicochetPacket(rc.conn, 0, response) rc.SendRicochetPacket(rc.Conn, 0, response)
return channel, nil return channel, nil
} }
log.Debugln(fmt.Sprintf("failed to request open channel: %v", err)) rc.traceLog(fmt.Sprintf("failed to request open channel: %v", err))
rc.channelManager.RemoveChannel(channel.ID) rc.channelManager.RemoveChannel(channel.ID)
return nil, err return nil, err
} }
@ -221,18 +228,15 @@ func (rc *Connection) buildChannel(handler channels.Handler, openChannelFunc fun
channel, err := openChannelFunc(handler) channel, err := openChannelFunc(handler)
if err == nil { if err == nil {
channel.SendMessage = func(message []byte) { channel.SendMessage = func(message []byte) {
rc.SendRicochetPacket(rc.conn, channel.ID, message) rc.SendRicochetPacket(rc.Conn, channel.ID, message)
} }
channel.DelegateAuthorization = func() { channel.DelegateAuthorization = func() {
rc.am.AddAuthorization(handler.Type()) rc.am.AddAuthorization(handler.Type())
} }
channel.CloseChannel = func() { channel.CloseChannel = func() {
rc.SendRicochetPacket(rc.conn, channel.ID, []byte{}) rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
rc.channelManager.RemoveChannel(channel.ID) rc.channelManager.RemoveChannel(channel.ID)
} }
channel.DelegateEncryption = func(key [32]byte) {
rc.SetEncryptionKey(key)
}
return channel, nil return channel, nil
} }
return nil, err return nil, err
@ -267,12 +271,12 @@ func (rc *Connection) processUserCallback(cb func()) {
// including connection close. // including connection close.
// //
// Process blocks until the connection is closed or until Break() is called. // Process blocks until the connection is closed or until Break() is called.
// Infof the connection is closed, a non-nil error is returned. // If the connection is closed, a non-nil error is returned.
func (rc *Connection) Process(handler Handler) error { func (rc *Connection) Process(handler Handler) error {
if rc.closed { if rc.closed {
return utils.ConnectionClosedError return utils.ConnectionClosedError
} }
log.Debugln("entering process loop") rc.traceLog("entering process loop")
rc.processUserCallback(func() { handler.OnReady(rc) }) rc.processUserCallback(func() { handler.OnReady(rc) })
// There are exactly two ways out of this loop: a signal on breakChannel // There are exactly two ways out of this loop: a signal on breakChannel
@ -292,13 +296,13 @@ func (rc *Connection) Process(handler Handler) error {
<-rc.unlockResponseChannel <-rc.unlockResponseChannel
continue continue
case <-rc.breakChannel: case <-rc.breakChannel:
log.Debugln("process has ended after break") rc.traceLog("process has ended after break")
rc.breakResultChannel <- nil rc.breakResultChannel <- nil
return nil return nil
case packet = <-rc.packetChannel: case packet = <-rc.packetChannel:
break break
case err := <-rc.errorChannel: case err := <-rc.errorChannel:
rc.conn.Close() rc.Conn.Close()
rc.closing = true rc.closing = true
// In order to safely close down concurrent calls to Do or Break, // In order to safely close down concurrent calls to Do or Break,
@ -312,8 +316,6 @@ func (rc *Connection) Process(handler Handler) error {
go func() { go func() {
rc.processBlockMutex.Lock() rc.processBlockMutex.Lock()
defer rc.processBlockMutex.Unlock() defer rc.processBlockMutex.Unlock()
rc.closingLock.Lock()
defer rc.closingLock.Unlock()
rc.closed = true rc.closed = true
close(closedChan) close(closedChan)
}() }()
@ -339,7 +341,7 @@ func (rc *Connection) Process(handler Handler) error {
} }
if packet.Channel == 0 { if packet.Channel == 0 {
log.Debugln(fmt.Sprintf("received control packet on channel %d", packet.Channel)) rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
res := new(Protocol_Data_Control.Packet) res := new(Protocol_Data_Control.Packet)
err := proto.Unmarshal(packet.Data[:], res) err := proto.Unmarshal(packet.Data[:], res)
if err == nil { if err == nil {
@ -352,11 +354,11 @@ func (rc *Connection) Process(handler Handler) error {
channel, found := rc.channelManager.GetChannel(packet.Channel) channel, found := rc.channelManager.GetChannel(packet.Channel)
if found { if found {
if len(packet.Data) == 0 { if len(packet.Data) == 0 {
log.Debugln(fmt.Sprintf("removing channel %d", packet.Channel)) rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
rc.channelManager.RemoveChannel(packet.Channel) rc.channelManager.RemoveChannel(packet.Channel)
rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) }) rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) })
} else { } else {
log.Debugln(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel)) rc.traceLog(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel))
// Send The Ricochet Packet to the Handler // Send The Ricochet Packet to the Handler
rc.processUserCallback(func() { channel.Handler.Packet(packet.Data[:]) }) rc.processUserCallback(func() { channel.Handler.Packet(packet.Data[:]) })
} }
@ -364,9 +366,9 @@ func (rc *Connection) Process(handler Handler) error {
// When a non-zero packet is received for an unknown // When a non-zero packet is received for an unknown
// channel, the recipient responds by closing // channel, the recipient responds by closing
// that channel. // that channel.
log.Debugln(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel)) rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
if len(packet.Data) != 0 { if len(packet.Data) != 0 {
rc.SendRicochetPacket(rc.conn, packet.Channel, []byte{}) rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{})
} }
} }
} }
@ -384,16 +386,10 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
return rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler) return rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler)
} }
channel, err := rc.buildChannel(chandler, openChannel) channel, err := rc.buildChannel(chandler, openChannel)
if err != nil {
log.Errorf("Failed to build channel from Control Packet: %v", err)
return
}
response, err := chandler.OpenInbound(channel, opm) response, err := chandler.OpenInbound(channel, opm)
_, err = rc.handleChannelOpening(channel, response, err) _, err = rc.handleChannelOpening(channel, response, err)
if err != nil { if err != nil {
rc.SendRicochetPacket(rc.conn, 0, []byte{}) rc.SendRicochetPacket(rc.Conn, 0, []byte{})
} }
return return
} }
@ -407,8 +403,8 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
} }
// Send Error Packet // Send Error Packet
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), errorText) response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), errorText)
log.Debugln(fmt.Sprintf("sending reject open channel for %v: %v", opm.GetChannelIdentifier(), errorText)) rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
rc.SendRicochetPacket(rc.conn, 0, response) rc.SendRicochetPacket(rc.Conn, 0, response)
} else if res.GetChannelResult() != nil { } else if res.GetChannelResult() != nil {
rc.ctrlChannel.ProcessChannelResult(res.GetChannelResult()) rc.ctrlChannel.ProcessChannelResult(res.GetChannelResult())
@ -416,20 +412,20 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
// XXX Though not currently part of the protocol // XXX Though not currently part of the protocol
// We should likely put these calls behind // We should likely put these calls behind
// authentication. // authentication.
log.Debugln("received keep alive packet") rc.traceLog("received keep alive packet")
respond, data := rc.ctrlChannel.ProcessKeepAlive(res.GetKeepAlive()) respond, data := rc.ctrlChannel.ProcessKeepAlive(res.GetKeepAlive())
if respond { if respond {
log.Debugln("sending keep alive response") rc.traceLog("sending keep alive response")
rc.SendRicochetPacket(rc.conn, 0, data) rc.SendRicochetPacket(rc.Conn, 0, data)
} }
} else if res.GetEnableFeatures() != nil { } else if res.GetEnableFeatures() != nil {
log.Debugln("received enable features packet") rc.traceLog("received enable features packet")
data := rc.ctrlChannel.ProcessEnableFeatures(handler, res.GetEnableFeatures()) data := rc.ctrlChannel.ProcessEnableFeatures(handler, res.GetEnableFeatures())
log.Debugln(fmt.Sprintf("sending featured enabled: %v", data)) rc.traceLog(fmt.Sprintf("sending featured enabled: %v", data))
rc.SendRicochetPacket(rc.conn, 0, data) rc.SendRicochetPacket(rc.Conn, 0, data)
} else if res.GetFeaturesEnabled() != nil { } else if res.GetFeaturesEnabled() != nil {
rc.SupportChannels = res.GetFeaturesEnabled().GetFeature() rc.SupportChannels = res.GetFeaturesEnabled().GetFeature()
log.Debugln(fmt.Sprintf("connection supports: %v", rc.SupportChannels)) rc.traceLog(fmt.Sprintf("connection supports: %v", rc.SupportChannels))
} }
} }
@ -438,7 +434,15 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
func (rc *Connection) EnableFeatures(features []string) { func (rc *Connection) EnableFeatures(features []string) {
messageBuilder := new(utils.MessageBuilder) messageBuilder := new(utils.MessageBuilder)
raw := messageBuilder.EnableFeatures(features) raw := messageBuilder.EnableFeatures(features)
rc.SendRicochetPacket(rc.conn, 0, raw) rc.SendRicochetPacket(rc.Conn, 0, raw)
}
// traceLog is an internal function which only logs messages
// if the connection is configured to log.
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 causes Process() to return, but does not close the underlying connection
@ -451,10 +455,10 @@ func (rc *Connection) Break() error {
rc.processBlockMutex.Lock() rc.processBlockMutex.Lock()
defer rc.processBlockMutex.Unlock() defer rc.processBlockMutex.Unlock()
if rc.closed { if rc.closed {
log.Debugln("ignoring break because connection is already closed") rc.traceLog("ignoring break because connection is already closed")
return utils.ConnectionClosedError return utils.ConnectionClosedError
} }
log.Debugln("breaking out of process loop") rc.traceLog("breaking out of process loop")
rc.breakChannel <- true rc.breakChannel <- true
return <-rc.breakResultChannel // Wait for Process to End return <-rc.breakResultChannel // Wait for Process to End
} }
@ -464,14 +468,3 @@ func (rc *Connection) Break() error {
func (rc *Connection) Channel(ctype string, way channels.Direction) *channels.Channel { func (rc *Connection) Channel(ctype string, way channels.Direction) *channels.Channel {
return rc.channelManager.Channel(ctype, way) return rc.channelManager.Channel(ctype, way)
} }
// Close tearsdown a Process() and explicitly Closes the connection.
// Note, that if Process() is holding a connection this will trigger an Error
func (rc *Connection) Close() {
// Kill the Ricochet Connection.
log.Debugf("Closing Ricochet Connection for %v", rc.RemoteHostname)
rc.conn.Close()
rc.closingLock.Lock()
rc.closed = true
rc.closingLock.Unlock()
}

View File

@ -1,11 +1,9 @@
package connection package connection
import ( import (
"crypto/rand"
"crypto/rsa" "crypto/rsa"
"git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"net" "net"
"testing" "testing"
"time" "time"
@ -16,26 +14,18 @@ func ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known b
return true, true return true, true
} }
// Server func TestProcessAuthAsServer(t *testing.T) {
func ServerAuthValid3DH(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
return true, true
}
func TestProcessAuthAs3DHServer(t *testing.T) {
ln, _ := net.Listen("tcp", "127.0.0.1:0") ln, _ := net.Listen("tcp", "127.0.0.1:0")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
go func() { go func() {
cconn, _ := net.Dial("tcp", ln.Addr().String()) cconn, _ := net.Dial("tcp", ln.Addr().String())
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader) orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
orc.TraceLog(true)
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
hostname := utils.GetTorV3Hostname(pub) known, err := HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
orc := NewOutboundConnection(cconn, hostname)
known, err := HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
if err != nil { if err != nil {
t.Errorf("Error while testing ProcessAuthAsClient (in ProcessAuthAsServer) %v", err) t.Errorf("Error while testing ProcessAuthAsClient (in ProcessAuthAsServer) %v", err)
return return
@ -46,72 +36,36 @@ func TestProcessAuthAs3DHServer(t *testing.T) {
}() }()
conn, _ := ln.Accept() conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
rc := NewInboundConnection(conn) rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH) err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
if err != nil { if err != nil {
t.Errorf("Error while testing ProcessAuthAsServer: %v", err) t.Errorf("Error while testing ProcessAuthAsServer: %v", err)
} }
// Wait for server to finish
time.Sleep(time.Second * 2)
// Test Close
rc.Close()
} }
func TestProcessAuthAsV3ServerFail(t *testing.T) { func TestProcessServerAuthFail(t *testing.T) {
ln, _ := net.Listen("tcp", "127.0.0.1:0") ln, _ := net.Listen("tcp", "127.0.0.1:0")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
go func() { go func() {
cconn, _ := net.Dial("tcp", ln.Addr().String()) cconn, _ := net.Dial("tcp", ln.Addr().String())
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader) orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
// Setting the RemoteHostname to the client pub key approximates a server sending the wrong public key. HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
hostname := utils.GetTorV3Hostname(cpub)
orc := NewOutboundConnection(cconn, hostname)
HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
}() }()
conn, _ := ln.Accept() conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key_auth_fail_test")
rc := NewInboundConnection(conn) rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH) err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
if err == nil { if err == nil {
t.Errorf("Error while testing ProcessAuthAsServer: %v", err) t.Errorf("Error while testing ProcessAuthAsServer - should have failed %v", err)
}
}
func TestProcessAuthAsV3ClientFail(t *testing.T) {
ln, _ := net.Listen("tcp", "127.0.0.1:0")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
go func() {
cconn, _ := net.Dial("tcp", ln.Addr().String())
// Giving the client inconsistent keypair to make EDH fail
cpub, _, _ := ed25519.GenerateKey(rand.Reader)
_, cpriv, _ := ed25519.GenerateKey(rand.Reader)
hostname := utils.GetTorV3Hostname(pub)
orc := NewOutboundConnection(cconn, hostname)
HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
}()
conn, _ := ln.Accept()
rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
if err == nil {
t.Errorf("Error while testing ProcessAuthAsServer: %v", err)
} }
} }
@ -121,16 +75,16 @@ func TestProcessAuthTimeout(t *testing.T) {
go func() { go func() {
net.Dial("tcp", ln.Addr().String()) net.Dial("tcp", ln.Addr().String())
time.Sleep(17 * time.Second) time.Sleep(16 * time.Second)
}() }()
conn, _ := ln.Accept() conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
rc := NewInboundConnection(conn) rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH) err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
if err != utils.ActionTimedOutError { if err != utils.ActionTimedOutError {
t.Errorf("Error while testing TestProcessAuthTimeout - Should have timed out after 15 seconds, instead ERR was %v", err) t.Errorf("Error while testing TestProcessAuthTimeout - Should have timed out after 15 seconds")
} }
} }

View File

@ -2,10 +2,8 @@ package connection
import ( import (
"errors" "errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
) )
// ControlChannel encapsulates logic for the control channel processing // ControlChannel encapsulates logic for the control channel processing
@ -29,11 +27,11 @@ func (ctrl *ControlChannel) ProcessChannelResult(cr *Protocol_Data_Control.Chann
} }
if cr.GetOpened() { if cr.GetOpened() {
log.Debugln(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id)) //rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
channel.Handler.OpenOutboundResult(nil, cr) channel.Handler.OpenOutboundResult(nil, cr)
return true, nil return true, nil
} }
log.Debugln(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id)) //rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
channel.Handler.OpenOutboundResult(errors.New(cr.GetCommonError().String()), cr) channel.Handler.OpenOutboundResult(errors.New(cr.GetCommonError().String()), cr)
return false, nil return false, nil
} }

View File

@ -1,10 +1,10 @@
package connection package connection
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing" "testing"
) )
@ -23,10 +23,6 @@ func TestChannelResultNotOpened(t *testing.T) {
chatChannel := new(channels.ChatChannel) chatChannel := new(channels.ChatChannel)
_, err := ccm.OpenChannelRequestFromPeer(2, chatChannel) _, err := ccm.OpenChannelRequestFromPeer(2, chatChannel)
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
cr := &Protocol_Data_Control.ChannelResult{ cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(2), ChannelIdentifier: proto.Int32(2),
Opened: proto.Bool(false), Opened: proto.Bool(false),
@ -44,10 +40,6 @@ func TestChannelResultError(t *testing.T) {
chatChannel := new(channels.ChatChannel) chatChannel := new(channels.ChatChannel)
_, err := ccm.OpenChannelRequestFromPeer(2, chatChannel) _, err := ccm.OpenChannelRequestFromPeer(2, chatChannel)
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
cr := &Protocol_Data_Control.ChannelResult{ cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(3), ChannelIdentifier: proto.Int32(3),
Opened: proto.Bool(false), Opened: proto.Bool(false),

View File

@ -1,12 +1,11 @@
package connection package connection
import ( import (
"crypto/rsa"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/inbound"
"git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/policies" "git.openprivacy.ca/openprivacy/libricochet-go/policies"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"sync" "sync"
) )
@ -23,11 +22,11 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
return ich return ich
} }
// ProcessAuthAsV3Server blocks until authentication has succeeded, failed, or the // ProcessAuthAsServer blocks until authentication has succeeded, failed, or the
// connection is closed. A non-nil error is returned in all cases other than successful // connection is closed. A non-nil error is returned in all cases other than successful
// and accepted authentication. // and accepted authentication.
// //
// ProcessAuthAsV3Server cannot be called at the same time as any other call to a Process // 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 // function. Another Process function must be called after this function successfully
// returns to continue handling connection events. // returns to continue handling connection events.
// //
@ -36,14 +35,18 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
// true to accept authentication and allow the connection to continue, and also returns a // 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 // 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. // assume they are required to send a contact request before any other activity.
func (ich *InboundConnectionHandler) ProcessAuthAsV3Server(v3identity identity.Identity, sach func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)) error { func (ich *InboundConnectionHandler) ProcessAuthAsServer(identity identity.Identity, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error {
if !identity.Initialized() {
return utils.PrivateKeyNotSetError
}
var breakOnce sync.Once var breakOnce sync.Once
var authAllowed, authKnown bool var authAllowed, authKnown bool
var authHostname string var authHostname string
onAuthValid := func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) { onAuthValid := func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
authAllowed, authKnown = sach(hostname, publicKey) authAllowed, authKnown = sach(hostname, publicKey)
if authAllowed { if authAllowed {
authHostname = hostname authHostname = hostname
@ -58,10 +61,10 @@ func (ich *InboundConnectionHandler) ProcessAuthAsV3Server(v3identity identity.I
ach := new(AutoConnectionHandler) ach := new(AutoConnectionHandler)
ach.Init() ach.Init()
ach.RegisterChannelHandler("im.ricochet.auth.3dh", ach.RegisterChannelHandler("im.ricochet.auth.hidden-service",
func() channels.Handler { func() channels.Handler {
return &inbound.Server3DHAuthChannel{ return &channels.HiddenServiceAuthChannel{
ServerIdentity: v3identity, Identity: identity,
ServerAuthValid: onAuthValid, ServerAuthValid: onAuthValid,
ServerAuthInvalid: onAuthInvalid, ServerAuthInvalid: onAuthInvalid,
} }

View File

@ -1,7 +1,7 @@
package connection package connection
import ( import (
"git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/policies" "git.openprivacy.ca/openprivacy/libricochet-go/policies"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
@ -21,18 +21,23 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
return och return och
} }
// ProcessAuthAsV3Client blocks until authentication has succeeded or failed with the // ProcessAuthAsClient blocks until authentication has succeeded or failed with the
// provided identity, or the connection is closed. A non-nil error is returned in all // provided identity, or the connection is closed. A non-nil error is returned in all
// cases other than successful authentication. // cases other than successful authentication.
// //
// ProcessAuthAsV3Client cannot be called at the same time as any other call to a Process // 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 // function. Another Process function must be called after this function successfully
// returns to continue handling connection events. // returns to continue handling connection events.
// //
// For successful authentication, the `known` return value indicates whether the peer // 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 // accepts us as a known contact. Unknown contacts will generally need to send a contact
// request before any other activity. // request before any other activity.
func (och *OutboundConnectionHandler) ProcessAuthAsV3Client(v3identity identity.Identity) (bool, error) { func (och *OutboundConnectionHandler) ProcessAuthAsClient(identity identity.Identity) (bool, error) {
if !identity.Initialized() {
return false, utils.PrivateKeyNotSetError
}
ach := new(AutoConnectionHandler) ach := new(AutoConnectionHandler)
ach.Init() ach.Init()
@ -61,9 +66,9 @@ func (och *OutboundConnectionHandler) ProcessAuthAsV3Client(v3identity identity.
}() }()
err := och.connection.Do(func() error { err := och.connection.Do(func() error {
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.3dh", _, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service",
&outbound.Client3DHAuthChannel{ &channels.HiddenServiceAuthChannel{
ClientIdentity: v3identity, Identity: identity,
ServerHostname: och.connection.RemoteHostname, ServerHostname: och.connection.RemoteHostname,
ClientAuthResult: authCallback, ClientAuthResult: authCallback,
}) })

115
examples/echobot/main.go Normal file
View File

@ -0,0 +1,115 @@
package main
import (
"git.openprivacy.ca/openprivacy/libricochet-go"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"log"
"time"
)
// EchoBotService is an example service which simply echoes back what a client
// sends it.
type RicochetEchoBot struct {
connection.AutoConnectionHandler
messages chan string
}
func (echobot *RicochetEchoBot) ContactRequest(name string, message string) string {
return "Pending"
}
func (echobot *RicochetEchoBot) ContactRequestRejected() {
}
func (echobot *RicochetEchoBot) ContactRequestAccepted() {
}
func (echobot *RicochetEchoBot) ContactRequestError() {
}
func (echobot *RicochetEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool {
echobot.messages <- message
return true
}
func (echobot *RicochetEchoBot) OpenInbound() {
}
func (echobot *RicochetEchoBot) ChatMessageAck(messageID uint32, accepted bool) {
}
func (echobot *RicochetEchoBot) Connect(privateKeyFile string, hostname string) {
privateKey, _ := utils.LoadPrivateKeyFromFile(privateKeyFile)
echobot.messages = make(chan string)
echobot.Init()
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, err := goricochet.Open(hostname)
if err != nil {
log.Fatalf("could not connect to %s: %v", hostname, err)
}
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(identity.Initialize("echobot", 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
})
}
}
}
func main() {
echoBot := new(RicochetEchoBot)
echoBot.Connect("private_key", "flkjmgvjloyyzlpe")
}

11
go.mod
View File

@ -1,11 +0,0 @@
module git.openprivacy.ca/openprivacy/libricochet-go
go 1.14
require (
cwtch.im/tapir v0.1.18
git.openprivacy.ca/openprivacy/connectivity v1.1.1
git.openprivacy.ca/openprivacy/log v1.0.0
github.com/golang/protobuf v1.3.5
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877
)

66
go.sum
View File

@ -1,66 +0,0 @@
cwtch.im/tapir v0.1.18 h1:Fs/jL9ZRyel/A1D/BYzIPEVQau8y5BJg44yA+GQDbSM=
cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg=
git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w=
git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y=
git.openprivacy.ca/openprivacy/log v1.0.0/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw=
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877 h1:IhZPbxNd1UjBCaD5AfpSSbJTRlp+ZSuyuH5uoksNS04=
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -3,10 +3,8 @@ package identity
import ( import (
"crypto" "crypto"
"crypto/rsa" "crypto/rsa"
tapirutils "cwtch.im/tapir/utils"
"encoding/asn1" "encoding/asn1"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
) )
// Identity is an encapsulation of Name, PrivateKey and other features // Identity is an encapsulation of Name, PrivateKey and other features
@ -14,10 +12,8 @@ import (
// The purpose of Identity is to prevent other classes directly accessing private key // The purpose of Identity is to prevent other classes directly accessing private key
// and to ensure the integrity of security-critical functions. // and to ensure the integrity of security-critical functions.
type Identity struct { type Identity struct {
Name string Name string
pk *rsa.PrivateKey pk *rsa.PrivateKey
edpk *ed25519.PrivateKey
edpubk *ed25519.PublicKey
} }
// Init loads an identity from a file. Currently file should be a private_key // Init loads an identity from a file. Currently file should be a private_key
@ -25,40 +21,28 @@ type Identity struct {
func Init(filename string) Identity { func Init(filename string) Identity {
pk, err := utils.LoadPrivateKeyFromFile(filename) pk, err := utils.LoadPrivateKeyFromFile(filename)
if err == nil { if err == nil {
return Identity{"", pk, nil, nil} return Identity{"", pk}
} }
return Identity{} return Identity{}
} }
// Initialize is a courtesy function for initializing an Identity in-code. // Initialize is a courtesy function for initializing an Identity in-code.
func Initialize(name string, pk *rsa.PrivateKey) Identity { func Initialize(name string, pk *rsa.PrivateKey) Identity {
return Identity{name, pk, nil, nil} return Identity{name, pk}
}
// InitializeV3 is a courtesy function for initializing a V3 Identity in-code.
func InitializeV3(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
return Identity{name, nil, pk, pubk}
} }
// Initialized ensures that an Identity has been assigned a private_key and // Initialized ensures that an Identity has been assigned a private_key and
// is ready to perform operations. // is ready to perform operations.
func (i *Identity) Initialized() bool { func (i *Identity) Initialized() bool {
if i.pk == nil { if i.pk == nil {
if i.edpk == nil { return false
return false
}
} }
return true return true
} }
// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly // PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
// format. // format. //TODO Not sure I like this.
func (i *Identity) PublicKeyBytes() []byte { func (i *Identity) PublicKeyBytes() []byte {
if i.edpk != nil {
return *i.edpubk
}
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{ publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: i.pk.PublicKey.N, N: i.pk.PublicKey.N,
E: i.pk.PublicKey.E, E: i.pk.PublicKey.E,
@ -67,18 +51,9 @@ func (i *Identity) PublicKeyBytes() []byte {
return publicKeyBytes return publicKeyBytes
} }
// EDH performs a diffie helman operation on this identities private key with the given public key.
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
secret, _ := tapirutils.EDH(*i.edpk, key)
return secret[:]
}
// Hostname provides the onion address associated with this Identity. // Hostname provides the onion address associated with this Identity.
func (i *Identity) Hostname() string { func (i *Identity) Hostname() string {
if i.pk != nil { return utils.GetTorHostname(i.PublicKeyBytes())
return utils.GetTorHostname(i.PublicKeyBytes())
}
return utils.GetTorV3Hostname(*i.edpubk)
} }
// Sign produces a cryptographic signature using this Identities private key. // Sign produces a cryptographic signature using this Identities private key.

View File

@ -16,7 +16,7 @@ func TestNegotiateInboundVersions(t *testing.T) {
} }
defer conn.Close() defer conn.Close()
conn.Write([]byte{0x49, 0x4D, 0x01, 0x03}) conn.Write([]byte{0x49, 0x4D, 0x01, 0x01})
} }
l, err := net.Listen("tcp", ":4000") l, err := net.Listen("tcp", ":4000")
@ -26,11 +26,6 @@ func TestNegotiateInboundVersions(t *testing.T) {
defer l.Close() defer l.Close()
go connect() go connect()
conn, err := l.Accept() conn, err := l.Accept()
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
_, err = NegotiateVersionInbound(conn) _, err = NegotiateVersionInbound(conn)
if err != nil { if err != nil {
t.Errorf("Expected Success Got %v", err) t.Errorf("Expected Success Got %v", err)
@ -57,11 +52,6 @@ func TestBadProtcolLength(t *testing.T) {
defer l.Close() defer l.Close()
go connect() go connect()
conn, err := l.Accept() conn, err := l.Accept()
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
_, err = NegotiateVersionInbound(conn) _, err = NegotiateVersionInbound(conn)
if err != io.ErrUnexpectedEOF { if err != io.ErrUnexpectedEOF {
t.Errorf("Invalid Error Received. Expected ErrUnexpectedEOF. Got %v", err) t.Errorf("Invalid Error Received. Expected ErrUnexpectedEOF. Got %v", err)
@ -88,11 +78,6 @@ func TestNoSupportedVersions(t *testing.T) {
defer l.Close() defer l.Close()
go connect() go connect()
conn, err := l.Accept() conn, err := l.Accept()
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
_, err = NegotiateVersionInbound(conn) _, err = NegotiateVersionInbound(conn)
if err != utils.VersionNegotiationError { if err != utils.VersionNegotiationError {
t.Errorf("Invalid Error Received. Expected VersionNegotiationError. Got %v", err) t.Errorf("Invalid Error Received. Expected VersionNegotiationError. Got %v", err)
@ -119,11 +104,6 @@ func TestInvalidVersionList(t *testing.T) {
defer l.Close() defer l.Close()
go connect() go connect()
conn, err := l.Accept() conn, err := l.Accept()
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
_, err = NegotiateVersionInbound(conn) _, err = NegotiateVersionInbound(conn)
if err != utils.VersionNegotiationError { if err != utils.VersionNegotiationError {
t.Errorf("Invalid Error Received. Expected VersionNegotiationError. Got %v", err) t.Errorf("Invalid Error Received. Expected VersionNegotiationError. Got %v", err)
@ -150,11 +130,6 @@ func TestNoCompatibleVersions(t *testing.T) {
defer l.Close() defer l.Close()
go connect() go connect()
conn, err := l.Accept() conn, err := l.Accept()
if err != nil {
t.Errorf("Error setting up test: %v", err)
}
_, err = NegotiateVersionInbound(conn) _, err = NegotiateVersionInbound(conn)
if err != utils.VersionNegotiationFailed { if err != utils.VersionNegotiationFailed {
t.Errorf("Invalid Error Received. Expected VersionNegotiationFailed. Got %v", err) t.Errorf("Invalid Error Received. Expected VersionNegotiationFailed. Got %v", err)

View File

@ -14,7 +14,7 @@ func TestOutboundVersionNegotiation(t *testing.T) {
b := make([]byte, 4) b := make([]byte, 4)
n, err := conn.Read(b) n, err := conn.Read(b)
if n == 4 && err == nil { if n == 4 && err == nil {
conn.Write([]byte{0x03}) conn.Write([]byte{0x01})
} }
conn.Close() conn.Close()
}() }()

View File

@ -1,20 +1,20 @@
package goricochet package goricochet
import ( import (
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"io" "io"
"net" "net"
) )
// Open establishes a protocol session on an established net.conn, and returns a new // Open establishes a protocol session on an established net.Conn, and returns a new
// OpenConnection instance representing this connection. On error, the connection // OpenConnection instance representing this connection. On error, the connection
// will be closed. This function blocks until version negotiation has completed. // will be closed. This function blocks until version negotiation has completed.
// The application should call Process() on the returned OpenConnection to continue // The application should call Process() on the returned OpenConnection to continue
// handling protocol messages. // handling protocol messages.
func Open(acn connectivity.ACN, remoteHostname string) (*connection.Connection, error) { func Open(remoteHostname string) (*connection.Connection, error) {
conn, remoteHostname, err := acn.Open(remoteHostname) networkResolver := utils.NetworkResolver{}
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
if err != nil { if err != nil {
return nil, err return nil, err
@ -31,7 +31,7 @@ func Open(acn connectivity.ACN, remoteHostname string) (*connection.Connection,
// NegotiateVersionOutbound takes an open network connection and executes // NegotiateVersionOutbound takes an open network connection and executes
// the ricochet version negotiation procedure. // the ricochet version negotiation procedure.
func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection.Connection, error) { func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x03} versions := []byte{0x49, 0x4D, 0x01, 0x01}
if n, err := conn.Write(versions); err != nil || n < len(versions) { if n, err := conn.Write(versions); err != nil || n < len(versions) {
return nil, utils.VersionNegotiationError return nil, utils.VersionNegotiationError
} }
@ -41,7 +41,7 @@ func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection
return nil, utils.VersionNegotiationError return nil, utils.VersionNegotiationError
} }
if res[0] != 0x03 { if res[0] != 0x01 {
return nil, utils.VersionNegotiationFailed return nil, utils.VersionNegotiationFailed
} }
rc := connection.NewOutboundConnection(conn, remoteHostname) rc := connection.NewOutboundConnection(conn, remoteHostname)
@ -52,7 +52,7 @@ func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection
// as if that connection was a client. Returns a ricochet connection if successful // as if that connection was a client. Returns a ricochet connection if successful
// error otherwise. // error otherwise.
func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) { func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x03} versions := []byte{0x49, 0x4D, 0x01, 0x01}
// Read version response header // Read version response header
header := make([]byte, 3) header := make([]byte, 3)
if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil { if _, err := io.ReadAtLeast(conn, header, len(header)); err != nil {
@ -71,7 +71,7 @@ func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
selectedVersion := byte(0xff) selectedVersion := byte(0xff)
for _, v := range versionList { for _, v := range versionList {
if v == 0x03 { if v == 0x01 {
selectedVersion = v selectedVersion = v
break break
} }

View File

@ -1,7 +1,6 @@
package goricochet package goricochet
import ( import (
"git.openprivacy.ca/openprivacy/connectivity"
"net" "net"
"testing" "testing"
"time" "time"
@ -13,18 +12,17 @@ func SimpleServer() {
b := make([]byte, 4) b := make([]byte, 4)
n, err := conn.Read(b) n, err := conn.Read(b)
if n == 4 && err == nil { if n == 4 && err == nil {
conn.Write([]byte{0x03}) conn.Write([]byte{0x01})
} }
conn.Close() conn.Close()
} }
func TestRicochetOpen(t *testing.T) { func TestRicochetOpen(t *testing.T) {
acn := connectivity.NewLocalACN()
go SimpleServer() go SimpleServer()
// Wait for Server to Initialize // Wait for Server to Initialize
time.Sleep(time.Second) time.Sleep(time.Second)
rc, err := Open(acn, "127.0.0.1:11000|abcdefghijklmno.onion") rc, err := Open("127.0.0.1:11000|abcdefghijklmno.onion")
if err == nil { if err == nil {
if rc.IsInbound { if rc.IsInbound {
t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen") t.Errorf("RicochetConnection declares itself as an Inbound connection after an Outbound attempt...that shouldn't happen")
@ -46,19 +44,17 @@ func BadServer() {
} }
func TestRicochetOpenWithError(t *testing.T) { func TestRicochetOpenWithError(t *testing.T) {
acn := connectivity.NewLocalACN()
go BadServer() go BadServer()
// Wait for Server to Initialize // Wait for Server to Initialize
time.Sleep(time.Second) time.Sleep(time.Second)
_, err := Open(acn, "127.0.0.1:11001|abcdefghijklmno.onion") _, err := Open("127.0.0.1:11001|abcdefghijklmno.onion")
if err == nil { if err == nil {
t.Errorf("Open should have failed because of bad version negotiation.") t.Errorf("Open should have failed because of bad version negotiation.")
} }
} }
func TestRicochetOpenWithNoServer(t *testing.T) { func TestRicochetOpenWithNoServer(t *testing.T) {
acn := connectivity.NewLocalACN() _, err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
_, err := Open(acn, "127.0.0.1:11002|abcdefghijklmno.onion")
if err == nil { if err == nil {
t.Errorf("Open should have failed because of bad version negotiation.") t.Errorf("Open should have failed because of bad version negotiation.")
} }

View File

@ -2,12 +2,10 @@ package testing
import ( import (
"fmt" "fmt"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/log" "log"
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
@ -46,14 +44,14 @@ func (messages *Messages) Get() []Message {
type ChatEchoBot struct { type ChatEchoBot struct {
onion string onion string
rai *application.Instance rai *application.ApplicationInstance
n int n int
Messages MessageStack Messages MessageStack
} }
// We always want bidirectional chat channels // We always want bidirectional chat channels
func (bot *ChatEchoBot) OpenInbound() { func (bot *ChatEchoBot) OpenInbound() {
log.Infoln("OpenInbound() ChatChannel handler called...") log.Println("OpenInbound() ChatChannel handler called...")
outboutChatChannel := bot.rai.Connection.Channel("im.ricochet.chat", channels.Outbound) outboutChatChannel := bot.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
if outboutChatChannel == nil { if outboutChatChannel == nil {
bot.rai.Connection.Do(func() error { bot.rai.Connection.Do(func() error {
@ -67,22 +65,27 @@ func (bot *ChatEchoBot) OpenInbound() {
} }
func (bot *ChatEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool { func (bot *ChatEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool {
log.Infof("ChatMessage(from: %v, %v", bot.rai.RemoteHostname, message) log.Printf("ChatMessage(from: %v, %v", bot.rai.RemoteHostname, message)
bot.Messages.Add(bot.rai.RemoteHostname, bot.onion, message) bot.Messages.Add(bot.rai.RemoteHostname, bot.onion, message)
SendMessage(bot.rai, strconv.Itoa(bot.n)+" witty response") SendMessage(bot.rai, strconv.Itoa(bot.n)+" witty response")
bot.n++ bot.n += 1
return true return true
} }
func SendMessage(rai *application.Instance, message string) error { func SendMessage(rai *application.ApplicationInstance, message string) {
log.Infof("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message) log.Printf("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message)
return rai.Connection.Do(func() error { rai.Connection.Do(func() error {
log.Infof("Finding Chat Channel") log.Printf("Finding Chat Channel")
channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound) channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
_, err := channels.SendMessageOnChatChannel(channel, message) if channel != nil {
if err != nil { log.Printf("Found Chat Channel")
log.Infof("Could not find chat channel") chatchannel, ok := channel.Handler.(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
} else {
log.Printf("Could not find chat channel")
} }
return nil return nil
}) })
@ -93,25 +96,15 @@ func (bot *ChatEchoBot) ChatMessageAck(messageID uint32, accepted bool) {
} }
func TestApplicationIntegration(t *testing.T) { func TestApplicationIntegration(t *testing.T) {
log.SetLevel(log.LevelDebug)
startGoRoutines := runtime.NumGoroutine() startGoRoutines := runtime.NumGoroutine()
acn, err := tor.NewTorACN(".", "")
if err != nil {
t.Fatalf("Could not start tor: %v", err)
}
time.Sleep(1 * time.Second)
acnStartGoRoutines := runtime.NumGoroutine()
messageStack := &Messages{} messageStack := &Messages{}
messageStack.Init() messageStack.Init()
fmt.Println("Initializing application factory...") fmt.Println("Initializing application factory...")
af := application.InstanceFactory{} af := application.ApplicationInstanceFactory{}
af.Init() af.Init()
af.AddHandler("im.ricochet.contact.request", func(rai *application.Instance) func() channels.Handler { af.AddHandler("im.ricochet.contact.request", func(rai *application.ApplicationInstance) func() channels.Handler {
return func() channels.Handler { return func() channels.Handler {
contact := new(channels.ContactRequestChannel) contact := new(channels.ContactRequestChannel)
contact.Handler = new(application.AcceptAllContactHandler) contact.Handler = new(application.AcceptAllContactHandler)
@ -122,43 +115,43 @@ func TestApplicationIntegration(t *testing.T) {
fmt.Println("Starting alice...") fmt.Println("Starting alice...")
alice := new(application.RicochetApplication) alice := new(application.RicochetApplication)
fmt.Println("Generating alice's pk...") fmt.Println("Generating alice's pk...")
apubk, apk, _ := utils.GeneratePrivateKeyV3() apk, _ := utils.GeneratePrivateKey()
aliceAddr := utils.GetTorV3Hostname(apubk) aliceAddr, _ := utils.GetOnionAddress(apk)
fmt.Println("Seting up alice's onion " + aliceAddr + "...") fmt.Println("Seting up alice's onion " + aliceAddr + "...")
al, err := acn.Listen(apk, application.RicochetPort) al, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", apk, 9878)
if err != nil { if err != nil {
t.Fatalf("Could not setup Onion for Alice: %v", err) t.Fatalf("Could not setup Onion for Alice: %v", err)
} }
fmt.Println("Initializing alice...") fmt.Println("Initializing alice...")
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler { af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
return func() channels.Handler { return func() channels.Handler {
chat := new(channels.ChatChannel) chat := new(channels.ChatChannel)
chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: aliceAddr} chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: aliceAddr}
return chat return chat
} }
}) })
alice.Init(acn, "Alice", identity.InitializeV3("Alice", &apk, &apubk), af, new(application.AcceptAllContactManager)) alice.Init("Alice", apk, af, new(application.AcceptAllContactManager))
fmt.Println("Running alice...") fmt.Println("Running alice...")
go alice.Run(al) go alice.Run(al)
fmt.Println("Starting bob...") fmt.Println("Starting bob...")
bob := new(application.RicochetApplication) bob := new(application.RicochetApplication)
bpubk, bpk, err := utils.GeneratePrivateKeyV3() bpk, err := utils.GeneratePrivateKey()
if err != nil { if err != nil {
t.Fatalf("Could not setup Onion for Alice: %v", err) t.Fatalf("Could not setup Onion for Alice: %v", err)
} }
bobAddr := utils.GetTorV3Hostname(bpubk) bobAddr, _ := utils.GetOnionAddress(bpk)
fmt.Println("Seting up bob's onion " + bobAddr + "...") fmt.Println("Seting up bob's onion " + bobAddr + "...")
bl, _ := acn.Listen(bpk, application.RicochetPort) bl, _ := application.SetupOnion("127.0.0.1:9051", "tcp4", "", bpk, 9878)
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler { af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
return func() channels.Handler { return func() channels.Handler {
chat := new(channels.ChatChannel) chat := new(channels.ChatChannel)
chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: bobAddr} chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: bobAddr}
return chat return chat
} }
}) })
bob.Init(acn, "Bob", identity.InitializeV3("Bob", &bpk, &bpubk), af, new(application.AcceptAllContactManager)) bob.Init("Bob", bpk, af, new(application.AcceptAllContactManager))
go bob.Run(bl) go bob.Run(bl)
fmt.Println("Waiting for alice and bob hidden services to percolate...") fmt.Println("Waiting for alice and bob hidden services to percolate...")
@ -171,31 +164,26 @@ func TestApplicationIntegration(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error Alice connecting to Bob: %v", err) t.Fatalf("Error Alice connecting to Bob: %v", err)
} }
time.Sleep(30 * time.Second) time.Sleep(10 * time.Second)
fmt.Println("Alice request open chat channel...") fmt.Println("Alice request open chat channel...")
// TODO: opening a channel should be easier? // TODO: opening a channel should be easier?
err = alicei.Connection.Do(func() error { alicei.Connection.Do(func() error {
handler, err := alicei.OnOpenChannelRequest("im.ricochet.chat") handler, err := alicei.OnOpenChannelRequest("im.ricochet.chat")
if err != nil { if err != nil {
log.Infof("Could not get chat handler!\n") log.Printf("Could not get chat handler!\n")
return err return err
} }
_, err = alicei.Connection.RequestOpenChannel("im.ricochet.chat", handler) _, err = alicei.Connection.RequestOpenChannel("im.ricochet.chat", handler)
return err return err
}) })
time.Sleep(5 * time.Second)
if err != nil {
t.Errorf("Error Opening a Channel: %v", err)
}
time.Sleep(30 * time.Second)
fmt.Println("Alice sending message to Bob...") fmt.Println("Alice sending message to Bob...")
err = SendMessage(alicei, "Hello Bob!") SendMessage(alicei, "Hello Bob!")
if err != nil { if err != nil {
t.Errorf("Error dialing from Alice to Bob: %v", err) log.Fatal("Error dialing from Alice to Bob: ", err)
} }
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
@ -213,16 +201,14 @@ func TestApplicationIntegration(t *testing.T) {
fmt.Println("Shutting alice down...") fmt.Println("Shutting alice down...")
alice.Shutdown() alice.Shutdown()
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)
aliceShutdownGoRoutines := runtime.NumGoroutine()
fmt.Println("Shutting down bine/tor")
acn.Close()
time.Sleep(5 * time.Second)
finalGoRoutines := runtime.NumGoroutine() finalGoRoutines := runtime.NumGoroutine()
fmt.Printf("startGoRoutines: %v\nacnStartedGoRoutines: %v\nrunningGoRoutines: %v\nconnectedGoRoutines: %v\nBobShutdownGoRoutines: %v\naliceShutdownGoRoutines: %v\nfinalGoRoutines: %v\n", startGoRoutines, acnStartGoRoutines, runningGoRoutines, connectedGoRoutines, bobShutdownGoRoutines, aliceShutdownGoRoutines, finalGoRoutines) fmt.Printf("startGoRoutines: %v\nrunningGoROutines: %v\nconnectedGoRoutines: %v\nBobShutdownGoRoutines: %v\nfinalGoRoutines: %v\n", startGoRoutines, runningGoRoutines, connectedGoRoutines, bobShutdownGoRoutines, finalGoRoutines)
if bobShutdownGoRoutines != startGoRoutines+1 {
t.Errorf("After shutting down bob, go routines were not start + 1 (alice) value. Expected: %v Actual %v", startGoRoutines+1, bobShutdownGoRoutines)
}
if finalGoRoutines != startGoRoutines { if finalGoRoutines != startGoRoutines {
t.Errorf("After shutting alice and bob down, go routines were not at start value. Expected: %v Actual: %v", startGoRoutines, finalGoRoutines) t.Errorf("After shutting alice and bob down, go routines were not at start value. Expected: %v Actual: %v", startGoRoutines, finalGoRoutines)

View File

@ -1,23 +0,0 @@
#!/bin/sh
echo "Checking code quality (you want to see no output here)"
echo "Formatting:"
gofmt -s -w -l .
echo "Vetting:"
go list ./... | xargs go vet
echo ""
echo "Linting:"
# Ignore wire packages as they are autogenerated
go list ./... | grep -v "/wire/" | xargs golint
# ineffassign (https://github.com/gordonklaus/ineffassign)
echo "Checking for ineffectual assignment of errors (unchecked errors...)"
ineffassign .
# misspell (https://github.com/client9/misspell)
echo "Checking for misspelled words..."
go list ./... | grep -v "/wire/" | grep -v "/vendor/" | xargs misspell

View File

@ -1,18 +1,13 @@
#!/bin/bash #!/bin/bash
#go test ${1} -coverprofile=coverage.out -coverpkg="git.openprivacy.ca/openprivacy/libricochet-go/channels,git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/inbound,git.openprivacy.ca/openprivacy/libricochet-go/channels/v3/outbound,git.openprivacy.ca/openprivacy/libricochet-go/identity,git.openprivacy.ca/openprivacy/libricochet-go/utils,git.openprivacy.ca/openprivacy/libricochet-go/policies,git.openprivacy.ca/openprivacy/libricochet-go/connection,git.openprivacy.ca/openprivacy/libricochet-go" -v . ./utils/ ./channels/ ./channels/v3/inbound ./connection ./policies ./identity ./utils
set -e set -e
pwd pwd
GORACE="haltonerror=1" go test ${1} -coverprofile=main.cover.out -v .
go test -race ${1} -coverprofile=utils.cover.out -v ./utils go test ${1} -coverprofile=utils.cover.out -v ./utils
go test -race ${1} -coverprofile=channels.cover.out -v ./channels go test ${1} -coverprofile=channels.cover.out -v ./channels
go test -race ${1} -coverprofile=channels.v3.inbound.cover.out -v ./channels/v3/inbound go test ${1} -coverprofile=connection.cover.out -v ./connection
go test -race ${1} -coverprofile=connection.cover.out -v ./connection go test ${1} -coverprofile=policies.cover.out -v ./policies
go test -race ${1} -coverprofile=policies.cover.out -v ./policies go test ${1} -coverprofile=policies.cover.out -v ./identity
go test -race ${1} -coverprofile=identity.cover.out -v ./identity
go test -race ${1} -coverprofile=root.cover.out -v ./
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
rm -rf *.cover.out rm -rf *.cover.out

View File

@ -5,7 +5,8 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"golang.org/x/crypto/ed25519" "errors"
"github.com/yawning/bulb/utils/pkcs1"
"io/ioutil" "io/ioutil"
"math" "math"
"math/big" "math/big"
@ -29,9 +30,14 @@ func GetRandNumber() *big.Int {
return num return num
} }
// GeneratePrivateKeyV3 cryptographically creats a new ed25519 key pair. // GeneratePrivateKey generates a new private key for use
func GeneratePrivateKeyV3() (ed25519.PublicKey, ed25519.PrivateKey, error) { func GeneratePrivateKey() (*rsa.PrivateKey, error) {
return ed25519.GenerateKey(rand.Reader) privateKey, err := rsa.GenerateKey(rand.Reader, RicochetKeySize)
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... // LoadPrivateKeyFromFile loads a private key from a file...
@ -64,3 +70,14 @@ func PrivateKeyToString(privateKey *rsa.PrivateKey) string {
return string(pem.EncodeToMemory(&privateKeyBlock)) return string(pem.EncodeToMemory(&privateKeyBlock))
} }
// return an onion address from a private key
func GetOnionAddress(privateKey *rsa.PrivateKey) (string, error) {
addr, err := pkcs1.OnionAddr(&privateKey.PublicKey)
if err != nil {
return "", err
} else if addr == "" {
return "", OnionAddressGenerationError
}
return addr, nil
}

View File

@ -1,9 +1,6 @@
package utils package utils
import ( import (
"crypto/rand"
"cwtch.im/tapir/utils"
"golang.org/x/crypto/ed25519"
"math" "math"
"testing" "testing"
) )
@ -12,6 +9,13 @@ const (
privateKeyFile = "./../testing/private_key" privateKeyFile = "./../testing/private_key"
) )
func TestGeneratePrivateKey(t *testing.T) {
_, err := GeneratePrivateKey()
if err != nil {
t.Errorf("Error while generating private key: %v", err)
}
}
func TestLoadPrivateKey(t *testing.T) { func TestLoadPrivateKey(t *testing.T) {
_, err := LoadPrivateKeyFromFile(privateKeyFile) _, err := LoadPrivateKeyFromFile(privateKeyFile)
if err != nil { if err != nil {
@ -19,19 +23,20 @@ func TestLoadPrivateKey(t *testing.T) {
} }
} }
func TestEDH(t *testing.T) {
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
spub, spriv, _ := ed25519.GenerateKey(rand.Reader)
cedh, _ := utils.EDH(cpriv, spub)
sedh, _ := utils.EDH(spriv, cpub)
if string(cedh[:]) != string(sedh[:]) {
t.Errorf("Client and Server should see the same secret %v %v", cedh, sedh)
}
}
func TestGetRandNumber(t *testing.T) { func TestGetRandNumber(t *testing.T) {
num := GetRandNumber() num := GetRandNumber()
if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) { if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) {
t.Errorf("Error random number outside of expected bounds %v", num) t.Errorf("Error random number outside of expected bounds %v", num)
} }
} }
func TestGetOnionAddress(t *testing.T) {
privateKey, _ := LoadPrivateKeyFromFile(privateKeyFile)
address, err := GetOnionAddress(privateKey)
if err != nil {
t.Errorf("Error generating onion address from private key: %v", err)
}
if address != "kwke2hntvyfqm7dr" {
t.Errorf("Error: onion address for private key not expected value")
}
}

View File

@ -1,12 +1,11 @@
package utils package utils
import ( import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth" "git.openprivacy.ca/openprivacy/libricochet-go/wire/auth"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/auth/3edh"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat" "git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact" "git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control" "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
) )
// MessageBuilder allows a client to construct specific data packets for the // MessageBuilder allows a client to construct specific data packets for the
@ -80,63 +79,6 @@ func (mb *MessageBuilder) ConfirmAuthChannel(channelID int32, serverCookie [16]b
return ret return ret
} }
// Open3EDHAuthenticationChannel constructs a message which will reuqest to open a channel for
// authentication on the given channelID, with the given cookie
func (mb *MessageBuilder) Open3EDHAuthenticationChannel(channelID int32, pubkey [32]byte, ephemeralKey [32]byte) []byte {
oc := &Protocol_Data_Control.OpenChannel{
ChannelIdentifier: proto.Int32(channelID),
ChannelType: proto.String("im.ricochet.auth.3dh"),
}
err := proto.SetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientPublicKey, pubkey[:])
CheckError(err)
err = proto.SetExtension(oc, Protocol_Data_Auth_TripleEDH.E_ClientEphmeralPublicKey, ephemeralKey[:])
CheckError(err)
pc := &Protocol_Data_Control.Packet{
OpenChannel: oc,
}
ret, err := proto.Marshal(pc)
CheckError(err)
return ret
}
// Confirm3EDHAuthChannel constructs a message to acknowledge a previous open channel operation.
func (mb *MessageBuilder) Confirm3EDHAuthChannel(channelID int32, pubkey [32]byte, ephemeralKey [32]byte) []byte {
cr := &Protocol_Data_Control.ChannelResult{
ChannelIdentifier: proto.Int32(channelID),
Opened: proto.Bool(true),
}
err := proto.SetExtension(cr, Protocol_Data_Auth_TripleEDH.E_ServerPublicKey, pubkey[:])
CheckError(err)
err = proto.SetExtension(cr, Protocol_Data_Auth_TripleEDH.E_ServerEphmeralPublicKey, ephemeralKey[:])
CheckError(err)
pc := &Protocol_Data_Control.Packet{
ChannelResult: cr,
}
ret, err := proto.Marshal(pc)
CheckError(err)
return ret
}
// Proof3DH constructs a proof message with the given public key and signature.
func (mb *MessageBuilder) Proof3DH(proofBytes []byte) []byte {
proof := &Protocol_Data_Auth_TripleEDH.Proof{
Proof: proofBytes,
}
ahsPacket := &Protocol_Data_Auth_TripleEDH.Packet{
Proof: proof,
}
ret, err := proto.Marshal(ahsPacket)
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 { func (mb *MessageBuilder) OpenContactRequestChannel(channelID int32, nick string, message string) []byte {
@ -234,24 +176,6 @@ func (mb *MessageBuilder) Proof(publicKeyBytes []byte, signatureBytes []byte) []
return ret return ret
} }
// AuthResult3DH constructs a response to a Proof
func (mb *MessageBuilder) AuthResult3DH(accepted bool, isKnownContact bool) []byte {
// Construct a Result Message
result := &Protocol_Data_Auth_TripleEDH.Result{
Accepted: proto.Bool(accepted),
IsKnownContact: proto.Bool(isKnownContact),
}
ahsPacket := &Protocol_Data_Auth_TripleEDH.Packet{
Proof: nil,
Result: result,
}
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 { func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
// Construct a Result Message // Construct a Result Message

View File

@ -1,8 +1,8 @@
package utils package utils
import ( import (
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"testing" "testing"
) )

View File

@ -2,13 +2,8 @@ package utils
import ( import (
"bytes" "bytes"
"crypto/rand"
"encoding/binary" "encoding/binary"
"errors"
"git.openprivacy.ca/openprivacy/log"
"golang.org/x/crypto/nacl/secretbox"
"io" "io"
"sync"
) )
const ( const (
@ -26,7 +21,7 @@ type RicochetData struct {
Data []byte Data []byte
} }
// Equals compares a RicochetData object to another and returns true if contain //Equals compares a RicochetData object to another and returns true if contain
// the same data. // the same data.
func (rd RicochetData) Equals(other RicochetData) bool { func (rd RicochetData) Equals(other RicochetData) bool {
return rd.Channel == other.Channel && bytes.Equal(rd.Data, other.Data) return rd.Channel == other.Channel && bytes.Equal(rd.Data, other.Data)
@ -41,20 +36,6 @@ type RicochetNetworkInterface interface {
// RicochetNetwork is a concrete implementation of the RicochetNetworkInterface // RicochetNetwork is a concrete implementation of the RicochetNetworkInterface
type RicochetNetwork struct { type RicochetNetwork struct {
// Derived ephemeral session key for connection
key [32]byte
encrypt bool
lock sync.Mutex
}
// SetEncryptionKey sets the ephemeral encryption key for this session.
func (rn *RicochetNetwork) SetEncryptionKey(key [32]byte) {
rn.lock.Lock()
defer rn.lock.Unlock()
log.Debugf("turning on ephemeral session encryption for connection")
copy(rn.key[:], key[:])
rn.encrypt = true
} }
// SendRicochetPacket places the data into a structure needed for the client to // SendRicochetPacket places the data into a structure needed for the client to
@ -71,19 +52,6 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data
binary.BigEndian.PutUint16(packet[2:4], uint16(channel)) binary.BigEndian.PutUint16(packet[2:4], uint16(channel))
copy(packet[4:], data[:]) copy(packet[4:], data[:])
rn.lock.Lock()
if rn.encrypt {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
encrypted := secretbox.Seal(nonce[:], packet[2:], &nonce, &rn.key)
binary.BigEndian.PutUint16(packet[0:2], uint16(len(encrypted)+2))
packet = append(packet[0:2], encrypted...)
}
rn.lock.Unlock()
for pos := 0; pos < len(packet); { for pos := 0; pos < len(packet); {
n, err := dst.Write(packet[pos:]) n, err := dst.Write(packet[pos:])
if err != nil { if err != nil {
@ -91,18 +59,16 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data
} }
pos += n pos += n
} }
return nil return nil
} }
// RecvRicochetPacket returns the next packet from reader as a RicochetData // RecvRicochetPacket returns the next packet from reader as a RicochetData
// structure, or an error. // structure, or an error.
func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, error) { func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, error) {
packet := RicochetData{} packet := RicochetData{}
// Read the four-byte header to get packet length // Read the four-byte header to get packet length
header := make([]byte, 2) header := make([]byte, 4)
if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil { if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil {
return packet, err return packet, err
} }
@ -112,32 +78,12 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e
return packet, InvalidPacketLengthError return packet, InvalidPacketLengthError
} }
packetBytes := make([]byte, size-2) packet.Channel = int32(binary.BigEndian.Uint16(header[2:4]))
_, err := io.ReadAtLeast(reader, packetBytes, size-2) packet.Data = make([]byte, size-4)
if err != nil {
if _, err := io.ReadAtLeast(reader, packet.Data, len(packet.Data)); err != nil {
return packet, err return packet, err
} }
rn.lock.Lock()
if rn.encrypt {
var decryptNonce [24]byte
if len(packetBytes) > 24 {
copy(decryptNonce[:], packetBytes[:24])
decrypted, ok := secretbox.Open(nil, packetBytes[24:], &decryptNonce, &rn.key)
if ok {
packetBytes = decrypted
} else {
return packet, errors.New("failed to decrypt encrypted ricochet packet")
}
} else {
return packet, errors.New("ciphertext length was too short")
}
}
rn.lock.Unlock()
packet.Channel = int32(binary.BigEndian.Uint16(packetBytes[0:2]))
packet.Data = make([]byte, len(packetBytes)-2)
copy(packet.Data[:], packetBytes[2:])
return packet, nil return packet, nil
} }

60
utils/networkresolver.go Normal file
View File

@ -0,0 +1,60 @@
package utils
import (
"golang.org/x/net/proxy"
"net"
"strings"
)
const (
// CannotResolveLocalTCPAddressError is thrown when a local ricochet connection has the wrong format.
CannotResolveLocalTCPAddressError = Error("CannotResolveLocalTCPAddressError")
// CannotDialLocalTCPAddressError is thrown when a connection to a local ricochet address fails.
CannotDialLocalTCPAddressError = Error("CannotDialLocalTCPAddressError")
// CannotDialRicochetAddressError is thrown when a connection to a ricochet address fails.
CannotDialRicochetAddressError = Error("CannotDialRicochetAddressError")
)
// NetworkResolver allows a client to resolve various hostnames to connections
// The supported types are onions address are:
// * ricochet:jlq67qzo6s4yp3sp
// * jlq67qzo6s4yp3sp
// * 127.0.0.1:55555|jlq67qzo6s4yp3sp - Localhost Connection
type NetworkResolver struct {
}
// Resolve takes a hostname and returns a net.Conn to the derived endpoint
func (nr *NetworkResolver) Resolve(hostname string) (net.Conn, string, error) {
if strings.HasPrefix(hostname, "127.0.0.1") {
addrParts := strings.Split(hostname, "|")
tcpAddr, err := net.ResolveTCPAddr("tcp", addrParts[0])
if err != nil {
return nil, "", CannotResolveLocalTCPAddressError
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
return nil, "", CannotDialLocalTCPAddressError
}
// return just the onion address, not the local override for the hostname
return conn, addrParts[1], nil
}
resolvedHostname := hostname
if strings.HasPrefix(hostname, "ricochet:") {
addrParts := strings.Split(hostname, ":")
resolvedHostname = addrParts[1]
}
torDialer, err := proxy.SOCKS5("tcp", "127.0.0.1:9050", nil, proxy.Direct)
if err != nil {
return nil, "", err
}
conn, err := torDialer.Dial("tcp", resolvedHostname+".onion:9878")
if err != nil {
return nil, "", CannotDialRicochetAddressError
}
return conn, resolvedHostname, nil
}

View File

@ -2,11 +2,7 @@ package utils
import ( import (
"crypto/sha1" "crypto/sha1"
"crypto/sha512"
"encoding/base32" "encoding/base32"
"encoding/base64"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
"strings" "strings"
) )
@ -21,57 +17,3 @@ func GetTorHostname(publicKeyBytes []byte) string {
data := base32.StdEncoding.EncodeToString(sha1bytes) data := base32.StdEncoding.EncodeToString(sha1bytes)
return strings.ToLower(data[0:16]) return strings.ToLower(data[0:16])
} }
// Expand ed25519.PrivateKey to (a || RH) form, return base64
func expandKey(pri ed25519.PrivateKey) string {
h := sha512.Sum512(pri[:32])
// Set bits so that h[:32] is private scalar "a"
h[0] &= 248
h[31] &= 127
h[31] |= 64
// Since h[32:] is RH, h is now (a || RH)
return base64.StdEncoding.EncodeToString(h[:])
}
// V3HostnameLength is the length of a Tor V3 Onion Address (without the .onion suffix)
const V3HostnameLength = 56
// Hidden service version
const version = byte(0x03)
// Salt used to create checkdigits
const salt = ".onion checksum"
func getCheckdigits(pub ed25519.PublicKey) []byte {
// Calculate checksum sha3(".onion checksum" || publicKey || version)
checkstr := []byte(salt)
checkstr = append(checkstr, pub...)
checkstr = append(checkstr, version)
checksum := sha3.Sum256(checkstr)
return checksum[:2]
}
// GetTorV3Hostname converts an ed25519 public key to a valid tor onion hostname
func GetTorV3Hostname(pub ed25519.PublicKey) string {
// Construct onion address base32(publicKey || checkdigits || version)
checkdigits := getCheckdigits(pub)
combined := pub[:]
combined = append(combined, checkdigits...)
combined = append(combined, version)
serviceID := base32.StdEncoding.EncodeToString(combined)
return strings.ToLower(serviceID)
}
// IsValidHostname returns true if the given address is a valid onion v3 address
func IsValidHostname(address string) bool {
if len(address) == V3HostnameLength {
data, err := base32.StdEncoding.DecodeString(strings.ToUpper(address))
if err == nil {
pubkey := data[0:ed25519.PublicKeySize]
if GetTorV3Hostname(ed25519.PublicKey(pubkey)) == address {
return true
}
}
}
return false
}

View File

@ -1,12 +1,10 @@
package utils package utils
import ( import (
"crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"golang.org/x/crypto/ed25519"
"testing" "testing"
) )
@ -43,15 +41,3 @@ func TestGetTorHostname(t *testing.T) {
t.Errorf("Hostname %s does not equal %s", hostname, "kwke2hntvyfqm7dr") t.Errorf("Hostname %s does not equal %s", hostname, "kwke2hntvyfqm7dr")
} }
} }
func TestV3(t *testing.T) {
pub, _, _ := ed25519.GenerateKey(rand.Reader)
hostname := GetTorV3Hostname(pub)
if !IsValidHostname(hostname) {
t.Errorf("Generated V3 Hostname was invalid")
}
if IsValidHostname(hostname[0:34]) {
t.Errorf("Invalid V3 Hostname was marked valid")
}
}

View File

@ -1,135 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: Auth3EDH.proto
/*
Package Protocol_Data_Auth_TripleEDH is a generated protocol buffer package.
It is generated from these files:
Auth3EDH.proto
It has these top-level messages:
Packet
Proof
Result
*/
package Protocol_Data_Auth_TripleEDH
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import Protocol_Data_Control "git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type Packet struct {
Proof *Proof `protobuf:"bytes,1,opt,name=proof" json:"proof,omitempty"`
Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Packet) Reset() { *m = Packet{} }
func (m *Packet) String() string { return proto.CompactTextString(m) }
func (*Packet) ProtoMessage() {}
func (m *Packet) GetProof() *Proof {
if m != nil {
return m.Proof
}
return nil
}
func (m *Packet) GetResult() *Result {
if m != nil {
return m.Result
}
return nil
}
type Proof struct {
Proof []byte `protobuf:"bytes,1,opt,name=proof" json:"proof,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Proof) Reset() { *m = Proof{} }
func (m *Proof) String() string { return proto.CompactTextString(m) }
func (*Proof) ProtoMessage() {}
func (m *Proof) GetProof() []byte {
if m != nil {
return m.Proof
}
return nil
}
type Result struct {
Accepted *bool `protobuf:"varint,1,req,name=accepted" json:"accepted,omitempty"`
IsKnownContact *bool `protobuf:"varint,2,opt,name=is_known_contact,json=isKnownContact" json:"is_known_contact,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Result) Reset() { *m = Result{} }
func (m *Result) String() string { return proto.CompactTextString(m) }
func (*Result) ProtoMessage() {}
func (m *Result) GetAccepted() bool {
if m != nil && m.Accepted != nil {
return *m.Accepted
}
return false
}
func (m *Result) GetIsKnownContact() bool {
if m != nil && m.IsKnownContact != nil {
return *m.IsKnownContact
}
return false
}
var E_ClientPublicKey = &proto.ExtensionDesc{
ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil),
ExtensionType: ([]byte)(nil),
Field: 9200,
Name: "Protocol.Data.Auth.TripleEDH.client_public_key",
Tag: "bytes,9200,opt,name=client_public_key,json=clientPublicKey",
Filename: "Auth3EDH.proto",
}
var E_ClientEphmeralPublicKey = &proto.ExtensionDesc{
ExtendedType: (*Protocol_Data_Control.OpenChannel)(nil),
ExtensionType: ([]byte)(nil),
Field: 9300,
Name: "Protocol.Data.Auth.TripleEDH.client_ephmeral_public_key",
Tag: "bytes,9300,opt,name=client_ephmeral_public_key,json=clientEphmeralPublicKey",
Filename: "Auth3EDH.proto",
}
var E_ServerPublicKey = &proto.ExtensionDesc{
ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil),
ExtensionType: ([]byte)(nil),
Field: 9200,
Name: "Protocol.Data.Auth.TripleEDH.server_public_key",
Tag: "bytes,9200,opt,name=server_public_key,json=serverPublicKey",
Filename: "Auth3EDH.proto",
}
var E_ServerEphmeralPublicKey = &proto.ExtensionDesc{
ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil),
ExtensionType: ([]byte)(nil),
Field: 9300,
Name: "Protocol.Data.Auth.TripleEDH.server_ephmeral_public_key",
Tag: "bytes,9300,opt,name=server_ephmeral_public_key,json=serverEphmeralPublicKey",
Filename: "Auth3EDH.proto",
}
func init() {
proto.RegisterType((*Packet)(nil), "Protocol.Data.Auth.TripleEDH.Packet")
proto.RegisterType((*Proof)(nil), "Protocol.Data.Auth.TripleEDH.Proof")
proto.RegisterType((*Result)(nil), "Protocol.Data.Auth.TripleEDH.Result")
proto.RegisterExtension(E_ClientPublicKey)
proto.RegisterExtension(E_ClientEphmeralPublicKey)
proto.RegisterExtension(E_ServerPublicKey)
proto.RegisterExtension(E_ServerEphmeralPublicKey)
}

View File

@ -1,28 +0,0 @@
syntax = "proto2";
package Protocol.Data.Auth.TripleEDH;
import "ControlChannel.proto";
extend Control.OpenChannel {
optional bytes client_public_key = 9200;
optional bytes client_ephmeral_public_key = 9300;
}
extend Control.ChannelResult {
optional bytes server_public_key = 9200;
optional bytes server_ephmeral_public_key = 9300;
}
message Packet {
optional Proof proof = 1;
optional Result result = 2;
}
message Proof {
optional bytes proof = 1; // Encrypted Onion Address
}
message Result {
required bool accepted = 1;
optional bool is_known_contact = 2;
}

View File

@ -1,25 +0,0 @@
package Protocol.Data.AuthHiddenService;
import "ControlChannel.proto";
extend Control.OpenChannel {
optional bytes client_cookie = 7200; // 16 random bytes
}
extend Control.ChannelResult {
optional bytes server_cookie = 7200; // 16 random bytes
}
message Packet {
optional Proof proof = 1;
optional Result result = 2;
}
message Proof {
optional bytes public_key = 1; // DER encoded public key
optional bytes signature = 2; // RSA signature
}
message Result {
required bool accepted = 1;
optional bool is_known_contact = 2;
}

View File

@ -1,18 +0,0 @@
package Protocol.Data.Chat;
message Packet {
optional ChatMessage chat_message = 1;
optional ChatAcknowledge chat_acknowledge = 2;
}
message ChatMessage {
required string message_text = 1;
optional uint32 message_id = 2; // Random ID for ack
optional int64 time_delta = 3; // Delta in seconds between now and when message was written
}
message ChatAcknowledge {
optional uint32 message_id = 1;
optional bool accepted = 2 [default = true];
}

View File

@ -1,35 +0,0 @@
package Protocol.Data.ContactRequest;
import "ControlChannel.proto";
enum Limits {
MessageMaxCharacters = 2000;
NicknameMaxCharacters = 30;
}
extend Control.OpenChannel {
optional ContactRequest contact_request = 200;
}
extend Control.ChannelResult {
optional Response response = 201;
}
// Sent only as an attachment to OpenChannel
message ContactRequest {
optional string nickname = 1;
optional string message_text = 2;
}
// Response is the only valid message to send on the channel
message Response {
enum Status {
Undefined = 0; // Not valid on the wire
Pending = 1;
Accepted = 2;
Rejected = 3;
Error = 4;
}
required Status status = 1;
}

View File

@ -1,52 +0,0 @@
package Protocol.Data.Control;
message Packet {
// Must contain exactly one field
optional OpenChannel open_channel = 1;
optional ChannelResult channel_result = 2;
optional KeepAlive keep_alive = 3;
optional EnableFeatures enable_features = 4;
optional FeaturesEnabled features_enabled = 5;
}
message OpenChannel {
required int32 channel_identifier = 1; // Arbitrary unique identifier for this channel instance
required string channel_type = 2; // String identifying channel type; e.g. im.ricochet.chat
// It is valid to extend the OpenChannel message to add fields specific
// to the requested channel_type.
extensions 100 to max;
}
message ChannelResult {
required int32 channel_identifier = 1; // Matching the value from OpenChannel
required bool opened = 2; // If the channel is now open
enum CommonError {
GenericError = 0;
UnknownTypeError = 1;
UnauthorizedError = 2;
BadUsageError = 3;
FailedError = 4;
}
optional CommonError common_error = 3;
// As with OpenChannel, it is valid to extend this message with fields specific
// to the channel type.
extensions 100 to max;
}
message KeepAlive {
required bool response_requested = 1;
}
message EnableFeatures {
repeated string feature = 1;
extensions 100 to max;
}
message FeaturesEnabled {
repeated string feature = 1;
extensions 100 to max;
}

View File

@ -130,7 +130,7 @@ func (m *OpenChannel) String() string { return proto.CompactTextString(m) }
func (*OpenChannel) ProtoMessage() {} func (*OpenChannel) ProtoMessage() {}
var extRange_OpenChannel = []proto.ExtensionRange{ var extRange_OpenChannel = []proto.ExtensionRange{
{Start: 100, End: 536870911}, {100, 536870911},
} }
func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange { func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange {
@ -170,7 +170,7 @@ func (m *ChannelResult) String() string { return proto.CompactTextString(m) }
func (*ChannelResult) ProtoMessage() {} func (*ChannelResult) ProtoMessage() {}
var extRange_ChannelResult = []proto.ExtensionRange{ var extRange_ChannelResult = []proto.ExtensionRange{
{Start: 100, End: 536870911}, {100, 536870911},
} }
func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange { func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange {
@ -231,7 +231,7 @@ func (m *EnableFeatures) String() string { return proto.CompactTextString(m) }
func (*EnableFeatures) ProtoMessage() {} func (*EnableFeatures) ProtoMessage() {}
var extRange_EnableFeatures = []proto.ExtensionRange{ var extRange_EnableFeatures = []proto.ExtensionRange{
{Start: 100, End: 536870911}, {100, 536870911},
} }
func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange { func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange {
@ -262,7 +262,7 @@ func (m *FeaturesEnabled) String() string { return proto.CompactTextString(m) }
func (*FeaturesEnabled) ProtoMessage() {} func (*FeaturesEnabled) ProtoMessage() {}
var extRange_FeaturesEnabled = []proto.ExtensionRange{ var extRange_FeaturesEnabled = []proto.ExtensionRange{
{Start: 100, End: 536870911}, {100, 536870911},
} }
func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange { func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange {