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
*~
*.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.
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)
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.
* *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 simple API that you can use to build Automated Ricochet Applications
* 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

View File

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

View File

@ -2,13 +2,10 @@ package application
import (
"crypto/rsa"
"golang.org/x/crypto/ed25519"
)
// AcceptAllContactManager implements the contact manager interface an presumes
// all connections are allowed.
// It is currently used by the Cwtch Server.
// TODO Deprecate
type AcceptAllContactManager struct {
}
@ -17,12 +14,6 @@ func (aacm *AcceptAllContactManager) LookupContact(hostname string, publicKey rs
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 {
return "Accepted"
}

View File

@ -1,38 +1,31 @@
package application
import (
"git.openprivacy.ca/openprivacy/connectivity"
"crypto/rsa"
"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"
"log"
"net"
"sync"
)
const (
// RicochetPort is the default port used by ricochet applications
RicochetPort = 9878
)
// RicochetApplication bundles many useful constructs that are
// likely standard in a ricochet application
type RicochetApplication struct {
contactManager ContactManagerInterface
v3identity identity.Identity
privateKey *rsa.PrivateKey
name string
ls connectivity.ListenService
acn connectivity.ACN
instances []*Instance
l net.Listener
instances []*ApplicationInstance
lock sync.Mutex
aif InstanceFactory
aif ApplicationInstanceFactory
}
// Init initializes the underlying RicochetApplication datastructure, making it ready for use
func (ra *RicochetApplication) Init(acn connectivity.ACN, name string, v3identity identity.Identity, af InstanceFactory, cm ContactManagerInterface) {
ra.acn = acn
func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af ApplicationInstanceFactory, cm ContactManagerInterface) {
ra.name = name
ra.v3identity = v3identity
ra.privateKey = pk
ra.aif = af
ra.contactManager = cm
}
@ -41,71 +34,66 @@ func (ra *RicochetApplication) Init(acn connectivity.ACN, name string, v3identit
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
rc, err := goricochet.NegotiateVersionInbound(conn)
if err != nil {
log.Errorln("There was an error")
log.Printf("There was an error")
conn.Close()
return
}
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 {
log.Errorf("There was an error authenticating the connection: %v", err)
log.Printf("There was an error")
conn.Close()
return
}
rc.TraceLog(true)
rai := ra.aif.GetApplicationInstance(rc)
ra.lock.Lock()
ra.instances = append(ra.instances, rai)
ra.lock.Unlock()
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 *Instance) {
func (ra *RicochetApplication) HandleApplicationInstance(rai *ApplicationInstance) {
ra.lock.Lock()
ra.instances = append(ra.instances, rai)
ra.lock.Unlock()
}
// Open a connection to another Ricochet peer at onionAddress. Infof they are unknown to use, use requestMessage (otherwise can be blank)
func (ra *RicochetApplication) Open(onionAddress string, requestMessage string) (*Instance, error) {
rc, err := goricochet.Open(ra.acn, onionAddress)
// 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) (*ApplicationInstance, error) {
rc, err := goricochet.Open(onionAddress)
rc.TraceLog(true)
if err != nil {
log.Errorf("Error in application.Open(): %v\n", err)
log.Printf("Error in application.Open(): %v\n", err)
return nil, err
}
och := connection.HandleOutboundConnection(rc)
_, err = och.ProcessAuthAsV3Client(ra.v3identity)
if err != nil {
log.Errorf("There was an error authenticating the connection: %v", err)
return nil, err
}
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(identity.Initialize(ra.name, ra.privateKey))
rai := ra.aif.GetApplicationInstance(rc)
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)
return rai, nil
}
// Broadcast performs the given function do() over all application instance (all connected peers)
func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) {
func (ra *RicochetApplication) Broadcast(do func(rai *ApplicationInstance)) {
ra.lock.Lock()
for _, rai := range ra.instances {
do(rai)
@ -113,45 +101,21 @@ func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) {
ra.lock.Unlock()
}
// Shutdown stops a RicochetApplication, terminating all child processes and resources
func (ra *RicochetApplication) Shutdown() {
ra.lock.Lock()
ra.ls.Close()
ra.l.Close()
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) Close(onion string) {
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 {
func (ra *RicochetApplication) Run(l net.Listener) {
if ra.privateKey == nil || ra.contactManager == nil {
return
}
ra.lock.Lock()
ra.ls = ls
ra.lock.Unlock()
ra.l = l
var err error
for err == nil {
conn, err := ra.ls.Accept()
conn, err := ra.l.Accept()
if err == nil {
go ra.handleConnection(conn)
} else {

View File

@ -5,49 +5,31 @@ import (
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
)
// Instance is a concrete instance of a ricochet application, encapsulating a connection
type Instance struct {
// ApplicationInstance is a concrete instance of a ricochet application, encapsulating a connection
type ApplicationInstance struct {
connection.AutoConnectionHandler
Connection *connection.Connection
RemoteHostname string
}
// InstanceFactory generates ApplicationInstances on a specific connection.
type InstanceFactory struct {
handlerMap map[string]func(*Instance) func() channels.Handler
// ApplicationInstanceFactory
type ApplicationInstanceFactory struct {
handlerMap map[string]func(*ApplicationInstance) func() channels.Handler
}
// Init sets up an Application Factory
func (af *InstanceFactory) Init() {
af.handlerMap = make(map[string]func(*Instance) func() channels.Handler)
func (af *ApplicationInstanceFactory) Init() {
af.handlerMap = make(map[string]func(*ApplicationInstance) func() channels.Handler)
}
// 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
}
// 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.
func (af *InstanceFactory) GetApplicationInstance(rc *connection.Connection) *Instance {
rai := new(Instance)
func (af *ApplicationInstanceFactory) GetApplicationInstance(rc *connection.Connection) *ApplicationInstance {
rai := new(ApplicationInstance)
rai.Init()
rai.RemoteHostname = rc.RemoteHostname
rai.Connection = rc

View File

@ -2,12 +2,10 @@ package application
import (
"crypto/rsa"
"golang.org/x/crypto/ed25519"
)
// ContactManagerInterface provides a mechanism for autonous applications
// to make decisions on what connections to accept or reject.
type ContactManagerInterface interface {
LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool)
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
import (
"crypto/rand"
"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/identity"
"git.openprivacy.ca/openprivacy/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"log"
"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 {
rai *application.Instance
rai *application.ApplicationInstance
ra *application.RicochetApplication
}
// Init establishes an EchoBotInstance
func (ebi *EchoBotInstance) Init(rai *application.Instance, ra *application.RicochetApplication) {
func (ebi *EchoBotInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
ebi.rai = rai
ebi.ra = ra
}
// OpenInbound is called when AliceBot opens a ChatChannel. In this case, because we want EchoBot to respond we
// need to open a new channel in the other direction.
// We always want bidirectional chat channels
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)
if outboutChatChannel == nil {
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 {
log.Infof("message from %v - %v", ebi.rai.RemoteHostname, message)
ebi.SendChatMessage(ebi.rai, ebi.rai.RemoteHostname+" "+message)
log.Printf("message from %v - %v", ebi.rai.RemoteHostname, message)
go ebi.ra.Broadcast(func(rai *application.ApplicationInstance) {
ebi.SendChatMessage(rai, ebi.rai.RemoteHostname+" "+message)
})
return true
}
// ChatMessageAck is called whenever a connected peer acknowledges a message that EchoBot sent.
func (ebi *EchoBotInstance) ChatMessageAck(messageID uint32, accepted bool) {
}
// SendChatMessage sends a chat message to the given echobot instance
func (ebi *EchoBotInstance) SendChatMessage(rai *application.Instance, message string) {
ebi.rai.Connection.Do(func() error {
channel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
// We are swallowing the message id and the error here, in reality you will want to handle it.
channels.SendMessageOnChatChannel(channel, message)
func (ebi *EchoBotInstance) SendChatMessage(rai *application.ApplicationInstance, message string) {
rai.Connection.Do(func() error {
channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
if channel != nil {
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
if ok {
chatchannel.SendMessage(message)
}
}
return nil
})
}
// 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() {
// 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)
cpubk, cprivk, err := ed25519.GenerateKey(rand.Reader)
pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key")
if err != nil {
log.Errorf("Error generating keys: %v", err)
os.Exit(1)
log.Fatalf("error reading private key file: %v", err)
}
// Turn on the echobot onion service in Tor.
listenService, err := acn.Listen(cprivk, application.RicochetPort)
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", pk, 9878)
if err != nil {
log.Errorf("error setting up onion service: %v", err)
os.Exit(1)
log.Fatalf("error setting up onion service: %v", err)
}
// This next section looks complicated (and it is a little), but all it is doing is allowing echobot to handle
// im.ricochet.chat type channels.
af := application.InstanceFactory{}
af := application.ApplicationInstanceFactory{}
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.Init(rai, echobot)
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.
// Note that we initialize a V3 identity for echobot.
echobot.Init(acn, "echobot", identity.InitializeV3("echobot", &cprivk, &cpubk), af, new(application.AcceptAllContactManager))
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)
echobot.Init("echobot", pk, af, new(application.AcceptAllContactManager))
log.Printf("echobot listening on %s", l.Addr().String())
echobot.Run(l)
}

View File

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

View File

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

View File

@ -1,11 +1,10 @@
package channels
import (
"errors"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"time"
)
@ -58,19 +57,6 @@ func (cc *ChatChannel) SendMessageWithTime(message string, when time.Time) uint3
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
// it was accepted.
func (cc *ChatChannel) Acknowledge(messageID uint32, accepted bool) {
@ -105,7 +91,7 @@ func (cc *ChatChannel) Bidirectional() bool {
// RequiresAuthentication - chat channels require hidden service auth
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.

View File

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

View File

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

View File

@ -1,10 +1,10 @@
package channels
import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/contact"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"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
import (
"github.com/golang/protobuf/proto"
"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/wire/control"
"github.com/golang/protobuf/proto"
"testing"
)
@ -13,23 +12,24 @@ import (
func TestInit(t *testing.T) {
ach := new(AutoConnectionHandler)
ach.Init()
ach.RegisterChannelHandler("im.ricochet.auth.3dh", func() channels.Handler {
return &inbound.Server3DHAuthChannel{}
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler {
return &channels.HiddenServiceAuthChannel{}
})
// Construct the Open Authentication Channel Message
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
// point in doing error checking here in the test
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(ocm[:], res)
opm := res.GetOpenChannel()
//ocmessage, _ := proto.Marshal(opm)
handler, err := ach.OnOpenChannelRequest(opm.GetChannelType())
if err == nil {
if handler.Type() != "im.ricochet.auth.3dh" {
if handler.Type() != "im.ricochet.auth.hidden-service" {
t.Errorf("Failed to authentication handler: %v", handler.Type())
}
} else {

View File

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

View File

@ -3,12 +3,12 @@ package connection
import (
"context"
"fmt"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
"github.com/golang/protobuf/proto"
"io"
"log"
"sync"
)
@ -31,16 +31,16 @@ type Connection struct {
unlockResponseChannel chan bool
messageBuilder utils.MessageBuilder
trace bool
closed bool
closingLock sync.Mutex
closing bool
closed bool
closing bool
// This mutex is exclusively for preventing races during blocking
// interactions with Process; specifically Do and Break. Don't use
// it for anything else. See those functions for an explanation.
processBlockMutex sync.Mutex
conn io.ReadWriteCloser
Conn io.ReadWriteCloser
IsInbound bool
am AuthorizationManager
RemoteHostname string
@ -67,7 +67,7 @@ func (rc *Connection) init() {
// modelling an Inbound Connection
func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
rc := new(Connection)
rc.conn = conn
rc.Conn = conn
rc.IsInbound = true
rc.init()
rc.channelManager = NewServerChannelManager()
@ -79,7 +79,7 @@ func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
// modelling an Inbound Connection
func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection {
rc := new(Connection)
rc.conn = conn
rc.Conn = conn
rc.IsInbound = false
rc.init()
rc.RemoteHostname = remoteHostname
@ -88,10 +88,17 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn
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
func (rc *Connection) start() {
for {
packet, err := rc.RecvRicochetPacket(rc.conn)
packet, err := rc.RecvRicochetPacket(rc.Conn)
if err != nil {
rc.errorChannel <- err
return
@ -139,11 +146,11 @@ func (rc *Connection) Do(do func() error) error {
}
// 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
log.Debugln("process loop is unlocked for do()")
rc.traceLog("process loop is unlocked for do()")
defer func() {
log.Debugln("giving up lock process loop after do() ")
rc.traceLog("giving up lock process loop after do() ")
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
log.Debugln("request unlocking of process loop for do()")
rc.traceLog("request unlocking of process loop for do()")
select {
case rc.unlockChannel <- true:
break
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()
}
log.Debugln("process loop is unlocked for do()")
rc.traceLog("process loop is unlocked for do()")
defer func() {
log.Debugln("giving up lock process loop after do() ")
rc.traceLog("giving up lock process loop after do() ")
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
// channel was opened successfully, because channels open asynchronously).
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)
if err == nil {
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) {
if err == nil {
rc.SendRicochetPacket(rc.conn, 0, response)
rc.SendRicochetPacket(rc.Conn, 0, response)
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)
return nil, err
}
@ -221,18 +228,15 @@ func (rc *Connection) buildChannel(handler channels.Handler, openChannelFunc fun
channel, err := openChannelFunc(handler)
if err == nil {
channel.SendMessage = func(message []byte) {
rc.SendRicochetPacket(rc.conn, channel.ID, message)
rc.SendRicochetPacket(rc.Conn, channel.ID, message)
}
channel.DelegateAuthorization = func() {
rc.am.AddAuthorization(handler.Type())
}
channel.CloseChannel = func() {
rc.SendRicochetPacket(rc.conn, channel.ID, []byte{})
rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
rc.channelManager.RemoveChannel(channel.ID)
}
channel.DelegateEncryption = func(key [32]byte) {
rc.SetEncryptionKey(key)
}
return channel, nil
}
return nil, err
@ -267,12 +271,12 @@ func (rc *Connection) processUserCallback(cb func()) {
// including connection close.
//
// 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 {
if rc.closed {
return utils.ConnectionClosedError
}
log.Debugln("entering process loop")
rc.traceLog("entering process loop")
rc.processUserCallback(func() { handler.OnReady(rc) })
// 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
continue
case <-rc.breakChannel:
log.Debugln("process has ended after break")
rc.traceLog("process has ended after break")
rc.breakResultChannel <- nil
return nil
case packet = <-rc.packetChannel:
break
case err := <-rc.errorChannel:
rc.conn.Close()
rc.Conn.Close()
rc.closing = true
// 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() {
rc.processBlockMutex.Lock()
defer rc.processBlockMutex.Unlock()
rc.closingLock.Lock()
defer rc.closingLock.Unlock()
rc.closed = true
close(closedChan)
}()
@ -339,7 +341,7 @@ func (rc *Connection) Process(handler Handler) error {
}
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)
err := proto.Unmarshal(packet.Data[:], res)
if err == nil {
@ -352,11 +354,11 @@ func (rc *Connection) Process(handler Handler) error {
channel, found := rc.channelManager.GetChannel(packet.Channel)
if found {
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.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) })
} 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
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
// channel, the recipient responds by closing
// 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 {
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)
}
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)
_, err = rc.handleChannelOpening(channel, response, err)
if err != nil {
rc.SendRicochetPacket(rc.conn, 0, []byte{})
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
}
return
}
@ -407,8 +403,8 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
}
// Send Error Packet
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), errorText)
log.Debugln(fmt.Sprintf("sending reject open channel for %v: %v", opm.GetChannelIdentifier(), errorText))
rc.SendRicochetPacket(rc.conn, 0, response)
rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
rc.SendRicochetPacket(rc.Conn, 0, response)
} else if res.GetChannelResult() != nil {
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
// We should likely put these calls behind
// authentication.
log.Debugln("received keep alive packet")
rc.traceLog("received keep alive packet")
respond, data := rc.ctrlChannel.ProcessKeepAlive(res.GetKeepAlive())
if respond {
log.Debugln("sending keep alive response")
rc.SendRicochetPacket(rc.conn, 0, data)
rc.traceLog("sending keep alive response")
rc.SendRicochetPacket(rc.Conn, 0, data)
}
} else if res.GetEnableFeatures() != nil {
log.Debugln("received enable features packet")
rc.traceLog("received enable features packet")
data := rc.ctrlChannel.ProcessEnableFeatures(handler, res.GetEnableFeatures())
log.Debugln(fmt.Sprintf("sending featured enabled: %v", data))
rc.SendRicochetPacket(rc.conn, 0, data)
rc.traceLog(fmt.Sprintf("sending featured enabled: %v", data))
rc.SendRicochetPacket(rc.Conn, 0, data)
} else if res.GetFeaturesEnabled() != nil {
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) {
messageBuilder := new(utils.MessageBuilder)
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
@ -451,10 +455,10 @@ func (rc *Connection) Break() error {
rc.processBlockMutex.Lock()
defer rc.processBlockMutex.Unlock()
if rc.closed {
log.Debugln("ignoring break because connection is already closed")
rc.traceLog("ignoring break because connection is already closed")
return utils.ConnectionClosedError
}
log.Debugln("breaking out of process loop")
rc.traceLog("breaking out of process loop")
rc.breakChannel <- true
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 {
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
import (
"crypto/rand"
"crypto/rsa"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"net"
"testing"
"time"
@ -16,26 +14,18 @@ func ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known b
return true, true
}
// Server
func ServerAuthValid3DH(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
return true, true
}
func TestProcessAuthAs3DHServer(t *testing.T) {
func TestProcessAuthAsServer(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())
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
orc.TraceLog(true)
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
hostname := utils.GetTorV3Hostname(pub)
orc := NewOutboundConnection(cconn, hostname)
known, err := HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
known, err := HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
if err != nil {
t.Errorf("Error while testing ProcessAuthAsClient (in ProcessAuthAsServer) %v", err)
return
@ -46,72 +36,36 @@ func TestProcessAuthAs3DHServer(t *testing.T) {
}()
conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
if err != nil {
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")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
go func() {
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.
hostname := utils.GetTorV3Hostname(cpub)
orc := NewOutboundConnection(cconn, hostname)
HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
}()
conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key_auth_fail_test")
rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
if err == nil {
t.Errorf("Error while testing ProcessAuthAsServer: %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)
t.Errorf("Error while testing ProcessAuthAsServer - should have failed %v", err)
}
}
@ -121,16 +75,16 @@ func TestProcessAuthTimeout(t *testing.T) {
go func() {
net.Dial("tcp", ln.Addr().String())
time.Sleep(17 * time.Second)
time.Sleep(16 * time.Second)
}()
conn, _ := ln.Accept()
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
rc := NewInboundConnection(conn)
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
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 (
"errors"
"fmt"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
)
// ControlChannel encapsulates logic for the control channel processing
@ -29,11 +27,11 @@ func (ctrl *ControlChannel) ProcessChannelResult(cr *Protocol_Data_Control.Chann
}
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)
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)
return false, nil
}

View File

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

View File

@ -1,12 +1,11 @@
package connection
import (
"crypto/rsa"
"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/policies"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"sync"
)
@ -23,11 +22,11 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
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
// 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
// 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
// boolean indicating whether the contact is known and recognized. Unknown contacts will
// assume they are required to send a contact request before any other activity.
func (ich *InboundConnectionHandler) 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 authAllowed, authKnown bool
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)
if authAllowed {
authHostname = hostname
@ -58,10 +61,10 @@ func (ich *InboundConnectionHandler) ProcessAuthAsV3Server(v3identity identity.I
ach := new(AutoConnectionHandler)
ach.Init()
ach.RegisterChannelHandler("im.ricochet.auth.3dh",
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service",
func() channels.Handler {
return &inbound.Server3DHAuthChannel{
ServerIdentity: v3identity,
return &channels.HiddenServiceAuthChannel{
Identity: identity,
ServerAuthValid: onAuthValid,
ServerAuthInvalid: onAuthInvalid,
}

View File

@ -1,7 +1,7 @@
package connection
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/policies"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
@ -21,18 +21,23 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
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
// 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
// returns to continue handling connection events.
//
// For successful authentication, the `known` return value indicates whether the peer
// accepts us as a known contact. Unknown contacts will generally need to send a contact
// request before any other activity.
func (och *OutboundConnectionHandler) 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.Init()
@ -61,9 +66,9 @@ func (och *OutboundConnectionHandler) ProcessAuthAsV3Client(v3identity identity.
}()
err := och.connection.Do(func() error {
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.3dh",
&outbound.Client3DHAuthChannel{
ClientIdentity: v3identity,
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service",
&channels.HiddenServiceAuthChannel{
Identity: identity,
ServerHostname: och.connection.RemoteHostname,
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 (
"crypto"
"crypto/rsa"
tapirutils "cwtch.im/tapir/utils"
"encoding/asn1"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
)
// 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
// and to ensure the integrity of security-critical functions.
type Identity struct {
Name string
pk *rsa.PrivateKey
edpk *ed25519.PrivateKey
edpubk *ed25519.PublicKey
Name string
pk *rsa.PrivateKey
}
// 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 {
pk, err := utils.LoadPrivateKeyFromFile(filename)
if err == nil {
return Identity{"", pk, nil, nil}
return Identity{"", pk}
}
return Identity{}
}
// Initialize is a courtesy function for initializing an Identity in-code.
func Initialize(name string, pk *rsa.PrivateKey) Identity {
return Identity{name, pk, nil, nil}
}
// 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}
return Identity{name, pk}
}
// Initialized ensures that an Identity has been assigned a private_key and
// is ready to perform operations.
func (i *Identity) Initialized() bool {
if i.pk == nil {
if i.edpk == nil {
return false
}
return false
}
return true
}
// 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 {
if i.edpk != nil {
return *i.edpubk
}
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: i.pk.PublicKey.N,
E: i.pk.PublicKey.E,
@ -67,18 +51,9 @@ func (i *Identity) PublicKeyBytes() []byte {
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.
func (i *Identity) Hostname() string {
if i.pk != nil {
return utils.GetTorHostname(i.PublicKeyBytes())
}
return utils.GetTorV3Hostname(*i.edpubk)
return utils.GetTorHostname(i.PublicKeyBytes())
}
// Sign produces a cryptographic signature using this Identities private key.

View File

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

View File

@ -1,20 +1,20 @@
package goricochet
import (
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"io"
"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
// will be closed. This function blocks until version negotiation has completed.
// The application should call Process() on the returned OpenConnection to continue
// handling protocol messages.
func Open(acn connectivity.ACN, remoteHostname string) (*connection.Connection, error) {
conn, remoteHostname, err := acn.Open(remoteHostname)
func Open(remoteHostname string) (*connection.Connection, error) {
networkResolver := utils.NetworkResolver{}
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
if err != nil {
return nil, err
@ -31,7 +31,7 @@ func Open(acn connectivity.ACN, remoteHostname string) (*connection.Connection,
// NegotiateVersionOutbound takes an open network connection and executes
// the ricochet version negotiation procedure.
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) {
return nil, utils.VersionNegotiationError
}
@ -41,7 +41,7 @@ func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection
return nil, utils.VersionNegotiationError
}
if res[0] != 0x03 {
if res[0] != 0x01 {
return nil, utils.VersionNegotiationFailed
}
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
// error otherwise.
func NegotiateVersionInbound(conn net.Conn) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x03}
versions := []byte{0x49, 0x4D, 0x01, 0x01}
// Read version response header
header := make([]byte, 3)
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)
for _, v := range versionList {
if v == 0x03 {
if v == 0x01 {
selectedVersion = v
break
}

View File

@ -1,7 +1,6 @@
package goricochet
import (
"git.openprivacy.ca/openprivacy/connectivity"
"net"
"testing"
"time"
@ -13,18 +12,17 @@ func SimpleServer() {
b := make([]byte, 4)
n, err := conn.Read(b)
if n == 4 && err == nil {
conn.Write([]byte{0x03})
conn.Write([]byte{0x01})
}
conn.Close()
}
func TestRicochetOpen(t *testing.T) {
acn := connectivity.NewLocalACN()
go SimpleServer()
// Wait for Server to Initialize
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 rc.IsInbound {
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) {
acn := connectivity.NewLocalACN()
go BadServer()
// Wait for Server to Initialize
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 {
t.Errorf("Open should have failed because of bad version negotiation.")
}
}
func TestRicochetOpenWithNoServer(t *testing.T) {
acn := connectivity.NewLocalACN()
_, err := Open(acn, "127.0.0.1:11002|abcdefghijklmno.onion")
_, err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
if err == nil {
t.Errorf("Open should have failed because of bad version negotiation.")
}

View File

@ -2,12 +2,10 @@ package testing
import (
"fmt"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"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"
"runtime"
"strconv"
"sync"
@ -46,14 +44,14 @@ func (messages *Messages) Get() []Message {
type ChatEchoBot struct {
onion string
rai *application.Instance
rai *application.ApplicationInstance
n int
Messages MessageStack
}
// We always want bidirectional chat channels
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)
if outboutChatChannel == nil {
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 {
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)
SendMessage(bot.rai, strconv.Itoa(bot.n)+" witty response")
bot.n++
bot.n += 1
return true
}
func SendMessage(rai *application.Instance, message string) error {
log.Infof("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message)
return rai.Connection.Do(func() error {
func SendMessage(rai *application.ApplicationInstance, message string) {
log.Printf("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message)
rai.Connection.Do(func() error {
log.Infof("Finding Chat Channel")
log.Printf("Finding Chat Channel")
channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
_, err := channels.SendMessageOnChatChannel(channel, message)
if err != nil {
log.Infof("Could not find chat channel")
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
})
@ -93,25 +96,15 @@ func (bot *ChatEchoBot) ChatMessageAck(messageID uint32, accepted bool) {
}
func TestApplicationIntegration(t *testing.T) {
log.SetLevel(log.LevelDebug)
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.Init()
fmt.Println("Initializing application factory...")
af := application.InstanceFactory{}
af := application.ApplicationInstanceFactory{}
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 {
contact := new(channels.ContactRequestChannel)
contact.Handler = new(application.AcceptAllContactHandler)
@ -122,43 +115,43 @@ func TestApplicationIntegration(t *testing.T) {
fmt.Println("Starting alice...")
alice := new(application.RicochetApplication)
fmt.Println("Generating alice's pk...")
apubk, apk, _ := utils.GeneratePrivateKeyV3()
aliceAddr := utils.GetTorV3Hostname(apubk)
apk, _ := utils.GeneratePrivateKey()
aliceAddr, _ := utils.GetOnionAddress(apk)
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 {
t.Fatalf("Could not setup Onion for Alice: %v", err)
}
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 {
chat := new(channels.ChatChannel)
chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: aliceAddr}
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...")
go alice.Run(al)
fmt.Println("Starting bob...")
bob := new(application.RicochetApplication)
bpubk, bpk, err := utils.GeneratePrivateKeyV3()
bpk, err := utils.GeneratePrivateKey()
if err != nil {
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 + "...")
bl, _ := acn.Listen(bpk, application.RicochetPort)
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler {
bl, _ := application.SetupOnion("127.0.0.1:9051", "tcp4", "", bpk, 9878)
af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
return func() channels.Handler {
chat := new(channels.ChatChannel)
chat.Handler = &ChatEchoBot{rai: rai, n: 0, Messages: messageStack, onion: bobAddr}
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)
fmt.Println("Waiting for alice and bob hidden services to percolate...")
@ -171,31 +164,26 @@ func TestApplicationIntegration(t *testing.T) {
if err != nil {
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...")
// 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")
if err != nil {
log.Infof("Could not get chat handler!\n")
log.Printf("Could not get chat handler!\n")
return err
}
_, err = alicei.Connection.RequestOpenChannel("im.ricochet.chat", handler)
return err
})
if err != nil {
t.Errorf("Error Opening a Channel: %v", err)
}
time.Sleep(30 * time.Second)
time.Sleep(5 * time.Second)
fmt.Println("Alice sending message to Bob...")
err = SendMessage(alicei, "Hello Bob!")
SendMessage(alicei, "Hello Bob!")
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)
@ -213,16 +201,14 @@ func TestApplicationIntegration(t *testing.T) {
fmt.Println("Shutting alice down...")
alice.Shutdown()
time.Sleep(15 * time.Second)
aliceShutdownGoRoutines := runtime.NumGoroutine()
fmt.Println("Shutting down bine/tor")
acn.Close()
time.Sleep(5 * time.Second)
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 {
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
#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
pwd
GORACE="haltonerror=1"
go test -race ${1} -coverprofile=utils.cover.out -v ./utils
go test -race ${1} -coverprofile=channels.cover.out -v ./channels
go test -race ${1} -coverprofile=channels.v3.inbound.cover.out -v ./channels/v3/inbound
go test -race ${1} -coverprofile=connection.cover.out -v ./connection
go test -race ${1} -coverprofile=policies.cover.out -v ./policies
go test -race ${1} -coverprofile=identity.cover.out -v ./identity
go test -race ${1} -coverprofile=root.cover.out -v ./
go test ${1} -coverprofile=main.cover.out -v .
go test ${1} -coverprofile=utils.cover.out -v ./utils
go test ${1} -coverprofile=channels.cover.out -v ./channels
go test ${1} -coverprofile=connection.cover.out -v ./connection
go test ${1} -coverprofile=policies.cover.out -v ./policies
go test ${1} -coverprofile=policies.cover.out -v ./identity
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out
rm -rf *.cover.out

View File

@ -5,7 +5,8 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"golang.org/x/crypto/ed25519"
"errors"
"github.com/yawning/bulb/utils/pkcs1"
"io/ioutil"
"math"
"math/big"
@ -29,9 +30,14 @@ func GetRandNumber() *big.Int {
return num
}
// GeneratePrivateKeyV3 cryptographically creats a new ed25519 key pair.
func GeneratePrivateKeyV3() (ed25519.PublicKey, ed25519.PrivateKey, error) {
return ed25519.GenerateKey(rand.Reader)
// GeneratePrivateKey generates a new private key for use
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
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...
@ -64,3 +70,14 @@ func PrivateKeyToString(privateKey *rsa.PrivateKey) string {
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
import (
"crypto/rand"
"cwtch.im/tapir/utils"
"golang.org/x/crypto/ed25519"
"math"
"testing"
)
@ -12,6 +9,13 @@ const (
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) {
_, err := LoadPrivateKeyFromFile(privateKeyFile)
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) {
num := GetRandNumber()
if !num.IsUint64() || num.Uint64() > uint64(math.MaxUint32) {
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
import (
"github.com/golang/protobuf/proto"
"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/contact"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
)
// 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
}
// 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
// a contact request on the given channelID, with the given nick and message.
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
}
// 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
func (mb *MessageBuilder) AuthResult(accepted bool, isKnownContact bool) []byte {
// Construct a Result Message

View File

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

View File

@ -2,13 +2,8 @@ package utils
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"git.openprivacy.ca/openprivacy/log"
"golang.org/x/crypto/nacl/secretbox"
"io"
"sync"
)
const (
@ -26,7 +21,7 @@ type RicochetData struct {
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.
func (rd RicochetData) Equals(other RicochetData) bool {
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
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
@ -71,19 +52,6 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data
binary.BigEndian.PutUint16(packet[2:4], uint16(channel))
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); {
n, err := dst.Write(packet[pos:])
if err != nil {
@ -91,18 +59,16 @@ func (rn *RicochetNetwork) SendRicochetPacket(dst io.Writer, channel int32, data
}
pos += n
}
return nil
}
// RecvRicochetPacket returns the next packet from reader as a RicochetData
// structure, or an error.
func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, error) {
packet := RicochetData{}
// 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 {
return packet, err
}
@ -112,32 +78,12 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e
return packet, InvalidPacketLengthError
}
packetBytes := make([]byte, size-2)
_, err := io.ReadAtLeast(reader, packetBytes, size-2)
if err != nil {
packet.Channel = int32(binary.BigEndian.Uint16(header[2:4]))
packet.Data = make([]byte, size-4)
if _, err := io.ReadAtLeast(reader, packet.Data, len(packet.Data)); err != nil {
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
}

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 (
"crypto/sha1"
"crypto/sha512"
"encoding/base32"
"encoding/base64"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
"strings"
)
@ -21,57 +17,3 @@ func GetTorHostname(publicKeyBytes []byte) string {
data := base32.StdEncoding.EncodeToString(sha1bytes)
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
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"golang.org/x/crypto/ed25519"
"testing"
)
@ -43,15 +41,3 @@ func TestGetTorHostname(t *testing.T) {
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() {}
var extRange_OpenChannel = []proto.ExtensionRange{
{Start: 100, End: 536870911},
{100, 536870911},
}
func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange {
@ -170,7 +170,7 @@ func (m *ChannelResult) String() string { return proto.CompactTextString(m) }
func (*ChannelResult) ProtoMessage() {}
var extRange_ChannelResult = []proto.ExtensionRange{
{Start: 100, End: 536870911},
{100, 536870911},
}
func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange {
@ -231,7 +231,7 @@ func (m *EnableFeatures) String() string { return proto.CompactTextString(m) }
func (*EnableFeatures) ProtoMessage() {}
var extRange_EnableFeatures = []proto.ExtensionRange{
{Start: 100, End: 536870911},
{100, 536870911},
}
func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange {
@ -262,7 +262,7 @@ func (m *FeaturesEnabled) String() string { return proto.CompactTextString(m) }
func (*FeaturesEnabled) ProtoMessage() {}
var extRange_FeaturesEnabled = []proto.ExtensionRange{
{Start: 100, End: 536870911},
{100, 536870911},
}
func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange {