forked from openprivacy/libricochet-go
Compare commits
124 Commits
Author | SHA1 | Date |
---|---|---|
Dan Ballard | 7cb1272cc4 | |
Sarah Jamie Lewis | 7281acea2e | |
Dan Ballard | 57ea15ec68 | |
Sarah Jamie Lewis | 463ba297ce | |
Sarah Jamie Lewis | ee8882c39e | |
Dan Ballard | 03b9ff1fe8 | |
Sarah Jamie Lewis | 5ec0bc8e1d | |
Dan Ballard | 2fd0aa67bd | |
Sarah Jamie Lewis | f82c2f9da4 | |
Dan Ballard | 7c828d3916 | |
Dan Ballard | 79a1ff9161 | |
Sarah Jamie Lewis | 5a1fc1b94d | |
Dan Ballard | 9ba39b93b7 | |
Sarah Jamie Lewis | 29540dcf71 | |
Dan Ballard | 0fdcfd1553 | |
Dan Ballard | ac4993adb7 | |
Dan Ballard | 881ca5c6c2 | |
Dan Ballard | 61f89d7b2c | |
Sarah Jamie Lewis | 725f64020a | |
Dan Ballard | b7cca3fa83 | |
Sarah Jamie Lewis | 07747c4dd2 | |
Dan Ballard | 59ea2902e8 | |
Sarah Jamie Lewis | 4ccdc79526 | |
Dan Ballard | 6517665498 | |
Sarah Jamie Lewis | cd872e9e0a | |
Dan Ballard | b534ecd04e | |
Sarah Jamie Lewis | aca0f63dd2 | |
Dan Ballard | bf57db657a | |
Sarah Jamie Lewis | 8db3c09fce | |
Dan Ballard | fa12b3dcb9 | |
erinn | 9e4e042ffb | |
Dan Ballard | d78488a200 | |
Sarah Jamie Lewis | b3d6e0e019 | |
Dan Ballard | bf28d6574f | |
Sarah Jamie Lewis | 0739119a4d | |
Sarah Jamie Lewis | 527ba61de0 | |
erinn | af195d186c | |
Sarah Jamie Lewis | 5bfb17588c | |
Dan Ballard | 1131bc930e | |
Sarah Jamie Lewis | 877f01a358 | |
Dan Ballard | 46e3eeeb7d | |
Sarah Jamie Lewis | 96fc03580b | |
Sarah Jamie Lewis | 7a4350f0c1 | |
Sarah Jamie Lewis | b2f6b314fc | |
Sarah Jamie Lewis | 64ce11d436 | |
erinn | 8f00e26b81 | |
Sarah Jamie Lewis | a96f682e77 | |
Dan Ballard | 487b1e9ae0 | |
Sarah Jamie Lewis | 9a680cd257 | |
Dan Ballard | e068de0ef8 | |
Dan Ballard | 1f0e87b3c4 | |
Sarah Jamie Lewis | f4fbd52d4b | |
Sarah Jamie Lewis | d87a0fcb52 | |
Dan Ballard | 859cdf95e1 | |
Dan Ballard | 3137689de8 | |
Sarah Jamie Lewis | 2cf7113f80 | |
Sarah Jamie Lewis | 4c23e439f0 | |
Dan Ballard | 540e68b8ef | |
Sarah Jamie Lewis | b05567fd81 | |
Dan Ballard | 8d1cba2b0b | |
Sarah Jamie Lewis | 90231b0be9 | |
erinn | 328ffc9d76 | |
Dan Ballard | 489bf62ff7 | |
Dan Ballard | b34fe84917 | |
Sarah Jamie Lewis | b6caf3fbc4 | |
Dan Ballard | 2815e29704 | |
Sarah Jamie Lewis | fa720940d8 | |
Dan Ballard | 5697a1c03d | |
Sarah Jamie Lewis | 232849e304 | |
Dan Ballard | 38cff4212d | |
Sarah Jamie Lewis | b8a7cd702a | |
erinn | 65fb3992a3 | |
erinn | 52bbc23251 | |
Dan Ballard | 53e6f90925 | |
Dan Ballard | b1fb04a880 | |
Dan Ballard | 0d080e4332 | |
Dan Ballard | 4da220b223 | |
Sarah Jamie Lewis | d54ed0b106 | |
Dan Ballard | 52ce7615c3 | |
Sarah Jamie Lewis | aba5fa4609 | |
Dan Ballard | 6c0d17667b | |
Sarah Jamie Lewis | 69e6644eb2 | |
Sarah Jamie Lewis | 353ef38a54 | |
Dan Ballard | 8fc60a0495 | |
Dan Ballard | 880bd2e020 | |
Dan Ballard | 493c2f5ec0 | |
Sarah Jamie Lewis | 18bc23d5c1 | |
Dan Ballard | dfba540973 | |
Dan Ballard | 3900552e06 | |
Sarah Jamie Lewis | 49faf95ee7 | |
erinn | 4d3f52102f | |
erinn | ad9d0efb02 | |
Dan Ballard | 5b914aaf3a | |
erinn | 1abbb874cc | |
erinn | 8960be643c | |
erinn | 1056fce116 | |
Dan Ballard | 29e3a42cc9 | |
Sarah Jamie Lewis | bce6496829 | |
erinn | e825e52a7c | |
erinn | fb8c0cac27 | |
erinn | 5c98fd575b | |
Sarah Jamie Lewis | dd2af9f059 | |
erinn | 8a7895a359 | |
erinn | fd01dca056 | |
Sarah Jamie Lewis | 3ffe8ad0e4 | |
erinn | aa7b398646 | |
Sarah Jamie Lewis | 6c37867ed1 | |
erinn | a6ca1571ad | |
erinn | 06381579e5 | |
Dan Ballard | 530fa1f39f | |
Sarah Jamie Lewis | 5066380655 | |
Dan Ballard | 1667868d8c | |
Sarah Jamie Lewis | 18dcf5c2ae | |
Dan Ballard | 90e39869a9 | |
Sarah Jamie Lewis | 930b94e0be | |
Dan Ballard | b3c09e5409 | |
Sarah Jamie Lewis | cc6076760f | |
Dan Ballard | cdb458647f | |
Sarah Jamie Lewis | 69c1dc18fb | |
Dan Ballard | e0411ecb9a | |
Sarah Jamie Lewis | c1907d5a9f | |
Dan Ballard | a852bc6678 | |
Sarah Jamie Lewis | 2dc3739f56 | |
Dan Ballard | 4442ab6691 |
|
@ -0,0 +1,51 @@
|
|||
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
|
|
@ -1,3 +1,9 @@
|
|||
go-ricochet-coverage.out
|
||||
*~
|
||||
*.out
|
||||
.idea
|
||||
.reviewboardrc
|
||||
/vendor/
|
||||
/testing/tor/
|
||||
/connectivity/tor/
|
||||
/tor/
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
4
LICENSE
4
LICENSE
|
@ -25,7 +25,7 @@ SOFTWARE.
|
|||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Autogenerated protobuf code was generated using the proto file from Ricochet.
|
||||
Autogenerated protobuf code was generated using the proto files 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.
|
|||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
go-ricochet is not affiliated with or endorsed by Ricochet.im or the Tor Project.
|
||||
libricochet-go is not affiliated with or endorsed by Ricochet.im or the Tor Project.
|
||||
|
|
49
README.md
49
README.md
|
@ -1,51 +1,16 @@
|
|||
# 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 [![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 is an experimental implementation of the [Ricochet Protocol](https://ricochet.im)
|
||||
in Go.
|
||||
written in Go.
|
||||
|
||||
## Features
|
||||
## Differences to Ricochet IM
|
||||
|
||||
* A simple API that you can use to build Automated Ricochet Applications
|
||||
* A suite of regression tests that test protocol compliance.
|
||||
* *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.
|
||||
|
||||
## Building an Automated Ricochet Application
|
||||
## Using libricochet-go
|
||||
|
||||
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.
|
||||
Checkout our [EchoBot Example](https://git.openprivacy.ca/openprivacy/libricochet-go/src/master/application/examples/echobot) to get started.
|
||||
|
||||
## Security and Usage Note
|
||||
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
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() {
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@ 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 {
|
||||
}
|
||||
|
||||
|
@ -14,6 +17,12 @@ 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"
|
||||
}
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"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"
|
||||
"log"
|
||||
"git.openprivacy.ca/openprivacy/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
|
||||
privateKey *rsa.PrivateKey
|
||||
v3identity identity.Identity
|
||||
name string
|
||||
l net.Listener
|
||||
instances []*ApplicationInstance
|
||||
ls connectivity.ListenService
|
||||
acn connectivity.ACN
|
||||
instances []*Instance
|
||||
lock sync.Mutex
|
||||
aif ApplicationInstanceFactory
|
||||
aif InstanceFactory
|
||||
}
|
||||
|
||||
func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af ApplicationInstanceFactory, cm ContactManagerInterface) {
|
||||
// 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
|
||||
ra.name = name
|
||||
ra.privateKey = pk
|
||||
ra.v3identity = v3identity
|
||||
ra.aif = af
|
||||
ra.contactManager = cm
|
||||
}
|
||||
|
@ -34,66 +41,71 @@ func (ra *RicochetApplication) Init(name string, pk *rsa.PrivateKey, af Applicat
|
|||
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
||||
rc, err := goricochet.NegotiateVersionInbound(conn)
|
||||
if err != nil {
|
||||
log.Printf("There was an error")
|
||||
log.Errorln("There was an error")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
ich := connection.HandleInboundConnection(rc)
|
||||
|
||||
err = ich.ProcessAuthAsServer(identity.Initialize(ra.name, ra.privateKey), ra.contactManager.LookupContact)
|
||||
err = ich.ProcessAuthAsV3Server(ra.v3identity, ra.contactManager.LookupContactV3)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("There was an error")
|
||||
log.Errorf("There was an error authenticating the connection: %v", err)
|
||||
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()
|
||||
}
|
||||
|
||||
func (ra *RicochetApplication) HandleApplicationInstance(rai *ApplicationInstance) {
|
||||
// HandleApplicationInstance delegates handling of a given Instance to the Application.
|
||||
func (ra *RicochetApplication) HandleApplicationInstance(rai *Instance) {
|
||||
ra.lock.Lock()
|
||||
ra.instances = append(ra.instances, rai)
|
||||
ra.lock.Unlock()
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
if err != nil {
|
||||
log.Printf("Error in application.Open(): %v\n", err)
|
||||
log.Errorf("Error in application.Open(): %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
known, err := connection.HandleOutboundConnection(rc).ProcessAuthAsClient(identity.Initialize(ra.name, ra.privateKey))
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (ra *RicochetApplication) Broadcast(do func(rai *ApplicationInstance)) {
|
||||
// Broadcast performs the given function do() over all application instance (all connected peers)
|
||||
func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) {
|
||||
ra.lock.Lock()
|
||||
for _, rai := range ra.instances {
|
||||
do(rai)
|
||||
|
@ -101,21 +113,45 @@ func (ra *RicochetApplication) Broadcast(do func(rai *ApplicationInstance)) {
|
|||
ra.lock.Unlock()
|
||||
}
|
||||
|
||||
// Shutdown stops a RicochetApplication, terminating all child processes and resources
|
||||
func (ra *RicochetApplication) Shutdown() {
|
||||
ra.l.Close()
|
||||
ra.lock.Lock()
|
||||
ra.ls.Close()
|
||||
for _, instance := range ra.instances {
|
||||
instance.Connection.Conn.Close()
|
||||
instance.Connection.Close()
|
||||
}
|
||||
ra.lock.Unlock()
|
||||
}
|
||||
|
||||
func (ra *RicochetApplication) Run(l net.Listener) {
|
||||
if ra.privateKey == nil || ra.contactManager == nil {
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
ra.l = l
|
||||
ra.lock.Lock()
|
||||
ra.ls = ls
|
||||
ra.lock.Unlock()
|
||||
var err error
|
||||
for err == nil {
|
||||
conn, err := ra.l.Accept()
|
||||
conn, err := ra.ls.Accept()
|
||||
if err == nil {
|
||||
go ra.handleConnection(conn)
|
||||
} else {
|
||||
|
|
|
@ -5,31 +5,49 @@ import (
|
|||
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
|
||||
)
|
||||
|
||||
// ApplicationInstance is a concrete instance of a ricochet application, encapsulating a connection
|
||||
type ApplicationInstance struct {
|
||||
// Instance is a concrete instance of a ricochet application, encapsulating a connection
|
||||
type Instance struct {
|
||||
connection.AutoConnectionHandler
|
||||
Connection *connection.Connection
|
||||
RemoteHostname string
|
||||
}
|
||||
|
||||
// ApplicationInstanceFactory
|
||||
type ApplicationInstanceFactory struct {
|
||||
handlerMap map[string]func(*ApplicationInstance) func() channels.Handler
|
||||
// InstanceFactory generates ApplicationInstances on a specific connection.
|
||||
type InstanceFactory struct {
|
||||
handlerMap map[string]func(*Instance) func() channels.Handler
|
||||
}
|
||||
|
||||
// Init sets up an Application Factory
|
||||
func (af *ApplicationInstanceFactory) Init() {
|
||||
af.handlerMap = make(map[string]func(*ApplicationInstance) func() channels.Handler)
|
||||
func (af *InstanceFactory) Init() {
|
||||
af.handlerMap = make(map[string]func(*Instance) func() channels.Handler)
|
||||
}
|
||||
|
||||
// AddHandler defines a channel type -> handler construct function
|
||||
func (af *ApplicationInstanceFactory) AddHandler(ctype string, chandler func(*ApplicationInstance) func() channels.Handler) {
|
||||
func (af *InstanceFactory) AddHandler(ctype string, chandler func(*Instance) 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 *ApplicationInstanceFactory) GetApplicationInstance(rc *connection.Connection) *ApplicationInstance {
|
||||
rai := new(ApplicationInstance)
|
||||
func (af *InstanceFactory) GetApplicationInstance(rc *connection.Connection) *Instance {
|
||||
rai := new(Instance)
|
||||
rai.Init()
|
||||
rai.RemoteHostname = rc.RemoteHostname
|
||||
rai.Connection = rc
|
||||
|
|
|
@ -2,10 +2,12 @@ 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)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
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])
|
||||
}
|
|
@ -1,26 +1,35 @@
|
|||
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/utils"
|
||||
"log"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||
"git.openprivacy.ca/openprivacy/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.ApplicationInstance
|
||||
rai *application.Instance
|
||||
ra *application.RicochetApplication
|
||||
}
|
||||
|
||||
func (ebi *EchoBotInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
|
||||
// Init establishes an EchoBotInstance
|
||||
func (ebi *EchoBotInstance) Init(rai *application.Instance, ra *application.RicochetApplication) {
|
||||
ebi.rai = rai
|
||||
ebi.ra = ra
|
||||
}
|
||||
|
||||
// We always want bidirectional chat channels
|
||||
// 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.
|
||||
func (ebi *EchoBotInstance) OpenInbound() {
|
||||
log.Println("OpenInbound() ChatChannel handler called...")
|
||||
log.Debugln("OpenInbound() ChatChannel handler called...")
|
||||
outboutChatChannel := ebi.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
|
||||
if outboutChatChannel == nil {
|
||||
ebi.rai.Connection.Do(func() error {
|
||||
|
@ -33,48 +42,67 @@ 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.Printf("message from %v - %v", ebi.rai.RemoteHostname, message)
|
||||
go ebi.ra.Broadcast(func(rai *application.ApplicationInstance) {
|
||||
ebi.SendChatMessage(rai, ebi.rai.RemoteHostname+" "+message)
|
||||
})
|
||||
log.Infof("message from %v - %v", ebi.rai.RemoteHostname, message)
|
||||
ebi.SendChatMessage(ebi.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) {
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
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)
|
||||
pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key")
|
||||
cpubk, cprivk, err := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("error reading private key file: %v", err)
|
||||
log.Errorf("Error generating keys: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", pk, 9878)
|
||||
|
||||
// Turn on the echobot onion service in Tor.
|
||||
listenService, err := acn.Listen(cprivk, application.RicochetPort)
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up onion service: %v", err)
|
||||
log.Errorf("error setting up onion service: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
af := application.ApplicationInstanceFactory{}
|
||||
// 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.Init()
|
||||
af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
|
||||
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) func() channels.Handler {
|
||||
ebi := new(EchoBotInstance)
|
||||
ebi.Init(rai, echobot)
|
||||
return func() channels.Handler {
|
||||
|
@ -84,7 +112,21 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
echobot.Init("echobot", pk, af, new(application.AcceptAllContactManager))
|
||||
log.Printf("echobot listening on %s", l.Addr().String())
|
||||
echobot.Run(l)
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -25,4 +25,5 @@ type Channel struct {
|
|||
SendMessage func([]byte)
|
||||
CloseChannel func()
|
||||
DelegateAuthorization func()
|
||||
DelegateEncryption func([32]byte)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -57,6 +58,19 @@ 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) {
|
||||
|
@ -91,7 +105,7 @@ func (cc *ChatChannel) Bidirectional() bool {
|
|||
|
||||
// RequiresAuthentication - chat channels require hidden service auth
|
||||
func (cc *ChatChannel) RequiresAuthentication() string {
|
||||
return "im.ricochet.auth.hidden-service"
|
||||
return "im.ricochet.auth.3dh"
|
||||
}
|
||||
|
||||
// OpenInbound is the first method called for an inbound channel request.
|
||||
|
|
|
@ -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.hidden-service" {
|
||||
t.Errorf("ChatChannel should require im.ricochet.auth.hidden-service. Instead requires: %s", chatChannel.RequiresAuthentication())
|
||||
if chatChannel.RequiresAuthentication() != "im.ricochet.auth.3dh" {
|
||||
t.Errorf("ChatChannel should require im.ricochet.auth.3dh. Instead requires: %s", chatChannel.RequiresAuthentication())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,250 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
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())
|
||||
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
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()
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
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()
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
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"
|
||||
)
|
||||
|
||||
|
@ -12,24 +13,23 @@ import (
|
|||
func TestInit(t *testing.T) {
|
||||
ach := new(AutoConnectionHandler)
|
||||
ach.Init()
|
||||
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service", func() channels.Handler {
|
||||
return &channels.HiddenServiceAuthChannel{}
|
||||
ach.RegisterChannelHandler("im.ricochet.auth.3dh", func() channels.Handler {
|
||||
return &inbound.Server3DHAuthChannel{}
|
||||
})
|
||||
|
||||
// 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})
|
||||
ocm := messageBuilder.Open3EDHAuthenticationChannel(1, [32]byte{}, [32]byte{})
|
||||
|
||||
// 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.hidden-service" {
|
||||
if handler.Type() != "im.ricochet.auth.3dh" {
|
||||
t.Errorf("Failed to authentication handler: %v", handler.Type())
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -75,7 +75,6 @@ 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
|
||||
|
|
|
@ -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
|
||||
closing bool
|
||||
closed bool
|
||||
closingLock sync.Mutex
|
||||
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,17 +88,10 @@ 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
|
||||
|
@ -146,11 +139,11 @@ func (rc *Connection) Do(do func() error) error {
|
|||
}
|
||||
|
||||
// Force process to soft-break so we can lock
|
||||
rc.traceLog("request unlocking of process loop for do()")
|
||||
log.Debugln("request unlocking of process loop for do()")
|
||||
rc.unlockChannel <- true
|
||||
rc.traceLog("process loop is unlocked for do()")
|
||||
log.Debugln("process loop is unlocked for do()")
|
||||
defer func() {
|
||||
rc.traceLog("giving up lock process loop after do() ")
|
||||
log.Debugln("giving up lock process loop after do() ")
|
||||
rc.unlockResponseChannel <- true
|
||||
}()
|
||||
|
||||
|
@ -177,18 +170,18 @@ func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) er
|
|||
}
|
||||
|
||||
// Force process to soft-break so we can lock
|
||||
rc.traceLog("request unlocking of process loop for do()")
|
||||
log.Debugln("request unlocking of process loop for do()")
|
||||
select {
|
||||
case rc.unlockChannel <- true:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
rc.traceLog("giving up on unlocking process loop for do() because context cancelled")
|
||||
log.Debugln("giving up on unlocking process loop for do() because context cancelled")
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
rc.traceLog("process loop is unlocked for do()")
|
||||
log.Debugln("process loop is unlocked for do()")
|
||||
defer func() {
|
||||
rc.traceLog("giving up lock process loop after do() ")
|
||||
log.Debugln("giving up lock process loop after do() ")
|
||||
rc.unlockResponseChannel <- true
|
||||
}()
|
||||
|
||||
|
@ -203,7 +196,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) {
|
||||
rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
|
||||
log.Debugln(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)
|
||||
|
@ -214,10 +207,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
|
||||
}
|
||||
rc.traceLog(fmt.Sprintf("failed to request open channel: %v", err))
|
||||
log.Debugln(fmt.Sprintf("failed to request open channel: %v", err))
|
||||
rc.channelManager.RemoveChannel(channel.ID)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -228,15 +221,18 @@ 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
|
||||
|
@ -271,12 +267,12 @@ func (rc *Connection) processUserCallback(cb func()) {
|
|||
// including connection close.
|
||||
//
|
||||
// Process blocks until the connection is closed or until Break() is called.
|
||||
// If the connection is closed, a non-nil error is returned.
|
||||
// Infof the connection is closed, a non-nil error is returned.
|
||||
func (rc *Connection) Process(handler Handler) error {
|
||||
if rc.closed {
|
||||
return utils.ConnectionClosedError
|
||||
}
|
||||
rc.traceLog("entering process loop")
|
||||
log.Debugln("entering process loop")
|
||||
rc.processUserCallback(func() { handler.OnReady(rc) })
|
||||
|
||||
// There are exactly two ways out of this loop: a signal on breakChannel
|
||||
|
@ -296,13 +292,13 @@ func (rc *Connection) Process(handler Handler) error {
|
|||
<-rc.unlockResponseChannel
|
||||
continue
|
||||
case <-rc.breakChannel:
|
||||
rc.traceLog("process has ended after break")
|
||||
log.Debugln("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,
|
||||
|
@ -316,6 +312,8 @@ 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)
|
||||
}()
|
||||
|
@ -341,7 +339,7 @@ func (rc *Connection) Process(handler Handler) error {
|
|||
}
|
||||
|
||||
if packet.Channel == 0 {
|
||||
rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
|
||||
log.Debugln(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 {
|
||||
|
@ -354,11 +352,11 @@ func (rc *Connection) Process(handler Handler) error {
|
|||
channel, found := rc.channelManager.GetChannel(packet.Channel)
|
||||
if found {
|
||||
if len(packet.Data) == 0 {
|
||||
rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
|
||||
log.Debugln(fmt.Sprintf("removing channel %d", packet.Channel))
|
||||
rc.channelManager.RemoveChannel(packet.Channel)
|
||||
rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) })
|
||||
} else {
|
||||
rc.traceLog(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel))
|
||||
log.Debugln(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[:]) })
|
||||
}
|
||||
|
@ -366,9 +364,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.
|
||||
rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
|
||||
log.Debugln(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{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -386,10 +384,16 @@ 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
|
||||
}
|
||||
|
@ -403,8 +407,8 @@ func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.
|
|||
}
|
||||
// Send Error Packet
|
||||
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), errorText)
|
||||
rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
|
||||
rc.SendRicochetPacket(rc.Conn, 0, response)
|
||||
log.Debugln(fmt.Sprintf("sending reject open channel for %v: %v", opm.GetChannelIdentifier(), errorText))
|
||||
rc.SendRicochetPacket(rc.conn, 0, response)
|
||||
|
||||
} else if res.GetChannelResult() != nil {
|
||||
rc.ctrlChannel.ProcessChannelResult(res.GetChannelResult())
|
||||
|
@ -412,20 +416,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.
|
||||
rc.traceLog("received keep alive packet")
|
||||
log.Debugln("received keep alive packet")
|
||||
respond, data := rc.ctrlChannel.ProcessKeepAlive(res.GetKeepAlive())
|
||||
if respond {
|
||||
rc.traceLog("sending keep alive response")
|
||||
rc.SendRicochetPacket(rc.Conn, 0, data)
|
||||
log.Debugln("sending keep alive response")
|
||||
rc.SendRicochetPacket(rc.conn, 0, data)
|
||||
}
|
||||
} else if res.GetEnableFeatures() != nil {
|
||||
rc.traceLog("received enable features packet")
|
||||
log.Debugln("received enable features packet")
|
||||
data := rc.ctrlChannel.ProcessEnableFeatures(handler, res.GetEnableFeatures())
|
||||
rc.traceLog(fmt.Sprintf("sending featured enabled: %v", data))
|
||||
rc.SendRicochetPacket(rc.Conn, 0, data)
|
||||
log.Debugln(fmt.Sprintf("sending featured enabled: %v", data))
|
||||
rc.SendRicochetPacket(rc.conn, 0, data)
|
||||
} else if res.GetFeaturesEnabled() != nil {
|
||||
rc.SupportChannels = res.GetFeaturesEnabled().GetFeature()
|
||||
rc.traceLog(fmt.Sprintf("connection supports: %v", rc.SupportChannels))
|
||||
log.Debugln(fmt.Sprintf("connection supports: %v", rc.SupportChannels))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,15 +438,7 @@ 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
rc.SendRicochetPacket(rc.conn, 0, raw)
|
||||
}
|
||||
|
||||
// Break causes Process() to return, but does not close the underlying connection
|
||||
|
@ -455,10 +451,10 @@ func (rc *Connection) Break() error {
|
|||
rc.processBlockMutex.Lock()
|
||||
defer rc.processBlockMutex.Unlock()
|
||||
if rc.closed {
|
||||
rc.traceLog("ignoring break because connection is already closed")
|
||||
log.Debugln("ignoring break because connection is already closed")
|
||||
return utils.ConnectionClosedError
|
||||
}
|
||||
rc.traceLog("breaking out of process loop")
|
||||
log.Debugln("breaking out of process loop")
|
||||
rc.breakChannel <- true
|
||||
return <-rc.breakResultChannel // Wait for Process to End
|
||||
}
|
||||
|
@ -468,3 +464,14 @@ 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()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
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"
|
||||
|
@ -14,18 +16,26 @@ func ServerAuthValid(hostname string, publicKey rsa.PublicKey) (allowed, known b
|
|||
return true, true
|
||||
}
|
||||
|
||||
func TestProcessAuthAsServer(t *testing.T) {
|
||||
// Server
|
||||
func ServerAuthValid3DH(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
func TestProcessAuthAs3DHServer(t *testing.T) {
|
||||
|
||||
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||
|
||||
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
go func() {
|
||||
cconn, _ := net.Dial("tcp", ln.Addr().String())
|
||||
|
||||
orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
|
||||
orc.TraceLog(true)
|
||||
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
|
||||
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
known, err := HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
|
||||
hostname := utils.GetTorV3Hostname(pub)
|
||||
orc := NewOutboundConnection(cconn, hostname)
|
||||
|
||||
known, err := HandleOutboundConnection(orc).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
|
||||
if err != nil {
|
||||
t.Errorf("Error while testing ProcessAuthAsClient (in ProcessAuthAsServer) %v", err)
|
||||
return
|
||||
|
@ -36,36 +46,72 @@ func TestProcessAuthAsServer(t *testing.T) {
|
|||
}()
|
||||
|
||||
conn, _ := ln.Accept()
|
||||
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
|
||||
|
||||
rc := NewInboundConnection(conn)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||
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 TestProcessServerAuthFail(t *testing.T) {
|
||||
func TestProcessAuthAsV3ServerFail(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())
|
||||
|
||||
orc := NewOutboundConnection(cconn, "kwke2hntvyfqm7dr")
|
||||
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
|
||||
cpub, cpriv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
HandleOutboundConnection(orc).ProcessAuthAsClient(identity.Initialize("", privateKey))
|
||||
// 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).ProcessAuthAsV3Client(identity.InitializeV3("", &cpriv, &cpub))
|
||||
}()
|
||||
|
||||
conn, _ := ln.Accept()
|
||||
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key_auth_fail_test")
|
||||
|
||||
rc := NewInboundConnection(conn)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||
if err == nil {
|
||||
t.Errorf("Error while testing ProcessAuthAsServer - should have failed %v", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,16 +121,16 @@ func TestProcessAuthTimeout(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
net.Dial("tcp", ln.Addr().String())
|
||||
time.Sleep(16 * time.Second)
|
||||
time.Sleep(17 * time.Second)
|
||||
|
||||
}()
|
||||
|
||||
conn, _ := ln.Accept()
|
||||
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
|
||||
|
||||
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
rc := NewInboundConnection(conn)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsServer(identity.Initialize("", privateKey), ServerAuthValid)
|
||||
err := HandleInboundConnection(rc).ProcessAuthAsV3Server(identity.InitializeV3("", &priv, &pub), ServerAuthValid3DH)
|
||||
if err != utils.ActionTimedOutError {
|
||||
t.Errorf("Error while testing TestProcessAuthTimeout - Should have timed out after 15 seconds")
|
||||
t.Errorf("Error while testing TestProcessAuthTimeout - Should have timed out after 15 seconds, instead ERR was %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ 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
|
||||
|
@ -27,11 +29,11 @@ func (ctrl *ControlChannel) ProcessChannelResult(cr *Protocol_Data_Control.Chann
|
|||
}
|
||||
|
||||
if cr.GetOpened() {
|
||||
//rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
|
||||
log.Debugln(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
|
||||
channel.Handler.OpenOutboundResult(nil, cr)
|
||||
return true, nil
|
||||
}
|
||||
//rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
|
||||
log.Debugln(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
|
||||
channel.Handler.OpenOutboundResult(errors.New(cr.GetCommonError().String()), cr)
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -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,6 +23,10 @@ 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),
|
||||
|
@ -40,6 +44,10 @@ 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),
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
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"
|
||||
)
|
||||
|
||||
|
@ -22,11 +23,11 @@ func HandleInboundConnection(c *Connection) *InboundConnectionHandler {
|
|||
return ich
|
||||
}
|
||||
|
||||
// ProcessAuthAsServer blocks until authentication has succeeded, failed, or the
|
||||
// ProcessAuthAsV3Server blocks until authentication has succeeded, failed, or the
|
||||
// connection is closed. A non-nil error is returned in all cases other than successful
|
||||
// and accepted authentication.
|
||||
//
|
||||
// ProcessAuthAsServer cannot be called at the same time as any other call to a Process
|
||||
// ProcessAuthAsV3Server 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.
|
||||
//
|
||||
|
@ -35,18 +36,14 @@ 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) ProcessAuthAsServer(identity identity.Identity, sach func(hostname string, publicKey rsa.PublicKey) (allowed, known bool)) error {
|
||||
|
||||
if !identity.Initialized() {
|
||||
return utils.PrivateKeyNotSetError
|
||||
}
|
||||
func (ich *InboundConnectionHandler) ProcessAuthAsV3Server(v3identity identity.Identity, sach func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool)) error {
|
||||
|
||||
var breakOnce sync.Once
|
||||
|
||||
var authAllowed, authKnown bool
|
||||
var authHostname string
|
||||
|
||||
onAuthValid := func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
|
||||
onAuthValid := func(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
|
||||
authAllowed, authKnown = sach(hostname, publicKey)
|
||||
if authAllowed {
|
||||
authHostname = hostname
|
||||
|
@ -61,10 +58,10 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(identity identity.Ident
|
|||
|
||||
ach := new(AutoConnectionHandler)
|
||||
ach.Init()
|
||||
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service",
|
||||
ach.RegisterChannelHandler("im.ricochet.auth.3dh",
|
||||
func() channels.Handler {
|
||||
return &channels.HiddenServiceAuthChannel{
|
||||
Identity: identity,
|
||||
return &inbound.Server3DHAuthChannel{
|
||||
ServerIdentity: v3identity,
|
||||
ServerAuthValid: onAuthValid,
|
||||
ServerAuthInvalid: onAuthInvalid,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"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/policies"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
||||
|
@ -21,23 +21,18 @@ func HandleOutboundConnection(c *Connection) *OutboundConnectionHandler {
|
|||
return och
|
||||
}
|
||||
|
||||
// ProcessAuthAsClient blocks until authentication has succeeded or failed with the
|
||||
// ProcessAuthAsV3Client 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.
|
||||
//
|
||||
// ProcessAuthAsClient cannot be called at the same time as any other call to a Porcess
|
||||
// ProcessAuthAsV3Client 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.
|
||||
//
|
||||
// For successful authentication, the `known` return value indicates whether the peer
|
||||
// accepts us as a known contact. Unknown contacts will generally need to send a contact
|
||||
// request before any other activity.
|
||||
func (och *OutboundConnectionHandler) ProcessAuthAsClient(identity identity.Identity) (bool, error) {
|
||||
|
||||
if !identity.Initialized() {
|
||||
return false, utils.PrivateKeyNotSetError
|
||||
}
|
||||
|
||||
func (och *OutboundConnectionHandler) ProcessAuthAsV3Client(v3identity identity.Identity) (bool, error) {
|
||||
ach := new(AutoConnectionHandler)
|
||||
ach.Init()
|
||||
|
||||
|
@ -66,9 +61,9 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(identity identity.Iden
|
|||
}()
|
||||
|
||||
err := och.connection.Do(func() error {
|
||||
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service",
|
||||
&channels.HiddenServiceAuthChannel{
|
||||
Identity: identity,
|
||||
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.3dh",
|
||||
&outbound.Client3DHAuthChannel{
|
||||
ClientIdentity: v3identity,
|
||||
ServerHostname: och.connection.RemoteHostname,
|
||||
ClientAuthResult: authCallback,
|
||||
})
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
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")
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,66 @@
|
|||
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=
|
|
@ -3,8 +3,10 @@ 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
|
||||
|
@ -12,8 +14,10 @@ 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
|
||||
Name string
|
||||
pk *rsa.PrivateKey
|
||||
edpk *ed25519.PrivateKey
|
||||
edpubk *ed25519.PublicKey
|
||||
}
|
||||
|
||||
// Init loads an identity from a file. Currently file should be a private_key
|
||||
|
@ -21,28 +25,40 @@ type Identity struct {
|
|||
func Init(filename string) Identity {
|
||||
pk, err := utils.LoadPrivateKeyFromFile(filename)
|
||||
if err == nil {
|
||||
return Identity{"", pk}
|
||||
return Identity{"", pk, nil, nil}
|
||||
}
|
||||
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}
|
||||
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}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false
|
||||
if i.edpk == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
|
||||
// format. //TODO Not sure I like this.
|
||||
// format.
|
||||
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,
|
||||
|
@ -51,9 +67,18 @@ 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 {
|
||||
return utils.GetTorHostname(i.PublicKeyBytes())
|
||||
if i.pk != nil {
|
||||
return utils.GetTorHostname(i.PublicKeyBytes())
|
||||
}
|
||||
return utils.GetTorV3Hostname(*i.edpubk)
|
||||
}
|
||||
|
||||
// Sign produces a cryptographic signature using this Identities private key.
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestNegotiateInboundVersions(t *testing.T) {
|
|||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.Write([]byte{0x49, 0x4D, 0x01, 0x01})
|
||||
conn.Write([]byte{0x49, 0x4D, 0x01, 0x03})
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", ":4000")
|
||||
|
@ -26,6 +26,11 @@ 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)
|
||||
|
@ -52,6 +57,11 @@ 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)
|
||||
|
@ -78,6 +88,11 @@ 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)
|
||||
|
@ -104,6 +119,11 @@ 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)
|
||||
|
@ -130,6 +150,11 @@ 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)
|
||||
|
|
|
@ -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{0x01})
|
||||
conn.Write([]byte{0x03})
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
|
16
ricochet.go
16
ricochet.go
|
@ -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(remoteHostname string) (*connection.Connection, error) {
|
||||
networkResolver := utils.NetworkResolver{}
|
||||
conn, remoteHostname, err := networkResolver.Resolve(remoteHostname)
|
||||
func Open(acn connectivity.ACN, remoteHostname string) (*connection.Connection, error) {
|
||||
conn, remoteHostname, err := acn.Open(remoteHostname)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -31,7 +31,7 @@ func Open(remoteHostname string) (*connection.Connection, error) {
|
|||
// 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, 0x01}
|
||||
versions := []byte{0x49, 0x4D, 0x01, 0x03}
|
||||
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] != 0x01 {
|
||||
if res[0] != 0x03 {
|
||||
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, 0x01}
|
||||
versions := []byte{0x49, 0x4D, 0x01, 0x03}
|
||||
// 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 == 0x01 {
|
||||
if v == 0x03 {
|
||||
selectedVersion = v
|
||||
break
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package goricochet
|
||||
|
||||
import (
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -12,17 +13,18 @@ func SimpleServer() {
|
|||
b := make([]byte, 4)
|
||||
n, err := conn.Read(b)
|
||||
if n == 4 && err == nil {
|
||||
conn.Write([]byte{0x01})
|
||||
conn.Write([]byte{0x03})
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestRicochetOpen(t *testing.T) {
|
||||
acn := connectivity.NewLocalACN()
|
||||
go SimpleServer()
|
||||
// Wait for Server to Initialize
|
||||
time.Sleep(time.Second)
|
||||
|
||||
rc, err := Open("127.0.0.1:11000|abcdefghijklmno.onion")
|
||||
rc, err := Open(acn, "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")
|
||||
|
@ -44,17 +46,19 @@ func BadServer() {
|
|||
}
|
||||
|
||||
func TestRicochetOpenWithError(t *testing.T) {
|
||||
acn := connectivity.NewLocalACN()
|
||||
go BadServer()
|
||||
// Wait for Server to Initialize
|
||||
time.Sleep(time.Second)
|
||||
_, err := Open("127.0.0.1:11001|abcdefghijklmno.onion")
|
||||
_, err := Open(acn, "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) {
|
||||
_, err := Open("127.0.0.1:11002|abcdefghijklmno.onion")
|
||||
acn := connectivity.NewLocalACN()
|
||||
_, err := Open(acn, "127.0.0.1:11002|abcdefghijklmno.onion")
|
||||
if err == nil {
|
||||
t.Errorf("Open should have failed because of bad version negotiation.")
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ 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"
|
||||
"log"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
@ -44,14 +46,14 @@ func (messages *Messages) Get() []Message {
|
|||
|
||||
type ChatEchoBot struct {
|
||||
onion string
|
||||
rai *application.ApplicationInstance
|
||||
rai *application.Instance
|
||||
n int
|
||||
Messages MessageStack
|
||||
}
|
||||
|
||||
// We always want bidirectional chat channels
|
||||
func (bot *ChatEchoBot) OpenInbound() {
|
||||
log.Println("OpenInbound() ChatChannel handler called...")
|
||||
log.Infoln("OpenInbound() ChatChannel handler called...")
|
||||
outboutChatChannel := bot.rai.Connection.Channel("im.ricochet.chat", channels.Outbound)
|
||||
if outboutChatChannel == nil {
|
||||
bot.rai.Connection.Do(func() error {
|
||||
|
@ -65,27 +67,22 @@ func (bot *ChatEchoBot) OpenInbound() {
|
|||
}
|
||||
|
||||
func (bot *ChatEchoBot) ChatMessage(messageID uint32, when time.Time, message string) bool {
|
||||
log.Printf("ChatMessage(from: %v, %v", bot.rai.RemoteHostname, message)
|
||||
log.Infof("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 += 1
|
||||
bot.n++
|
||||
return true
|
||||
}
|
||||
|
||||
func SendMessage(rai *application.ApplicationInstance, message string) {
|
||||
log.Printf("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message)
|
||||
rai.Connection.Do(func() error {
|
||||
func SendMessage(rai *application.Instance, message string) error {
|
||||
log.Infof("SendMessage(to: %v, %v)\n", rai.RemoteHostname, message)
|
||||
return rai.Connection.Do(func() error {
|
||||
|
||||
log.Printf("Finding Chat Channel")
|
||||
log.Infof("Finding Chat Channel")
|
||||
channel := rai.Connection.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")
|
||||
_, err := channels.SendMessageOnChatChannel(channel, message)
|
||||
if err != nil {
|
||||
log.Infof("Could not find chat channel")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -96,15 +93,25 @@ 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.ApplicationInstanceFactory{}
|
||||
af := application.InstanceFactory{}
|
||||
af.Init()
|
||||
|
||||
af.AddHandler("im.ricochet.contact.request", func(rai *application.ApplicationInstance) func() channels.Handler {
|
||||
af.AddHandler("im.ricochet.contact.request", func(rai *application.Instance) func() channels.Handler {
|
||||
return func() channels.Handler {
|
||||
contact := new(channels.ContactRequestChannel)
|
||||
contact.Handler = new(application.AcceptAllContactHandler)
|
||||
|
@ -115,43 +122,43 @@ func TestApplicationIntegration(t *testing.T) {
|
|||
fmt.Println("Starting alice...")
|
||||
alice := new(application.RicochetApplication)
|
||||
fmt.Println("Generating alice's pk...")
|
||||
apk, _ := utils.GeneratePrivateKey()
|
||||
aliceAddr, _ := utils.GetOnionAddress(apk)
|
||||
apubk, apk, _ := utils.GeneratePrivateKeyV3()
|
||||
aliceAddr := utils.GetTorV3Hostname(apubk)
|
||||
fmt.Println("Seting up alice's onion " + aliceAddr + "...")
|
||||
al, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", apk, 9878)
|
||||
al, err := acn.Listen(apk, application.RicochetPort)
|
||||
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.ApplicationInstance) func() channels.Handler {
|
||||
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) 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("Alice", apk, af, new(application.AcceptAllContactManager))
|
||||
alice.Init(acn, "Alice", identity.InitializeV3("Alice", &apk, &apubk), af, new(application.AcceptAllContactManager))
|
||||
fmt.Println("Running alice...")
|
||||
go alice.Run(al)
|
||||
|
||||
fmt.Println("Starting bob...")
|
||||
bob := new(application.RicochetApplication)
|
||||
bpk, err := utils.GeneratePrivateKey()
|
||||
bpubk, bpk, err := utils.GeneratePrivateKeyV3()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not setup Onion for Alice: %v", err)
|
||||
}
|
||||
bobAddr, _ := utils.GetOnionAddress(bpk)
|
||||
bobAddr := utils.GetTorV3Hostname(bpubk)
|
||||
fmt.Println("Seting up bob's onion " + bobAddr + "...")
|
||||
bl, _ := application.SetupOnion("127.0.0.1:9051", "tcp4", "", bpk, 9878)
|
||||
af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler {
|
||||
bl, _ := acn.Listen(bpk, application.RicochetPort)
|
||||
af.AddHandler("im.ricochet.chat", func(rai *application.Instance) 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("Bob", bpk, af, new(application.AcceptAllContactManager))
|
||||
bob.Init(acn, "Bob", identity.InitializeV3("Bob", &bpk, &bpubk), af, new(application.AcceptAllContactManager))
|
||||
go bob.Run(bl)
|
||||
|
||||
fmt.Println("Waiting for alice and bob hidden services to percolate...")
|
||||
|
@ -164,26 +171,31 @@ func TestApplicationIntegration(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error Alice connecting to Bob: %v", err)
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
fmt.Println("Alice request open chat channel...")
|
||||
// TODO: opening a channel should be easier?
|
||||
alicei.Connection.Do(func() error {
|
||||
err = alicei.Connection.Do(func() error {
|
||||
handler, err := alicei.OnOpenChannelRequest("im.ricochet.chat")
|
||||
if err != nil {
|
||||
log.Printf("Could not get chat handler!\n")
|
||||
log.Infof("Could not get chat handler!\n")
|
||||
return err
|
||||
}
|
||||
_, err = alicei.Connection.RequestOpenChannel("im.ricochet.chat", handler)
|
||||
return err
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
fmt.Println("Alice sending message to Bob...")
|
||||
SendMessage(alicei, "Hello Bob!")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Error dialing from Alice to Bob: ", err)
|
||||
t.Errorf("Error Opening a Channel: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
fmt.Println("Alice sending message to Bob...")
|
||||
err = SendMessage(alicei, "Hello Bob!")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error dialing from Alice to Bob: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
|
@ -201,14 +213,16 @@ 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\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)
|
||||
}
|
||||
fmt.Printf("startGoRoutines: %v\nacnStartedGoRoutines: %v\nrunningGoRoutines: %v\nconnectedGoRoutines: %v\nBobShutdownGoRoutines: %v\naliceShutdownGoRoutines: %v\nfinalGoRoutines: %v\n", startGoRoutines, acnStartGoRoutines, runningGoRoutines, connectedGoRoutines, bobShutdownGoRoutines, aliceShutdownGoRoutines, finalGoRoutines)
|
||||
|
||||
if finalGoRoutines != startGoRoutines {
|
||||
t.Errorf("After shutting alice and bob down, go routines were not at start value. Expected: %v Actual: %v", startGoRoutines, finalGoRoutines)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#!/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
|
|
@ -1,13 +1,18 @@
|
|||
#!/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
|
||||
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
|
||||
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 ./
|
||||
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
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ import (
|
|||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"github.com/yawning/bulb/utils/pkcs1"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
|
@ -30,14 +29,9 @@ func GetRandNumber() *big.Int {
|
|||
return num
|
||||
}
|
||||
|
||||
// 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)
|
||||
// GeneratePrivateKeyV3 cryptographically creats a new ed25519 key pair.
|
||||
func GeneratePrivateKeyV3() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||
return ed25519.GenerateKey(rand.Reader)
|
||||
}
|
||||
|
||||
// LoadPrivateKeyFromFile loads a private key from a file...
|
||||
|
@ -70,14 +64,3 @@ 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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/tapir/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
@ -9,13 +12,6 @@ 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 {
|
||||
|
@ -23,20 +19,19 @@ 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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
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
|
||||
|
@ -79,6 +80,63 @@ 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 {
|
||||
|
@ -176,6 +234,24 @@ 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
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -2,8 +2,13 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,7 +26,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)
|
||||
|
@ -36,6 +41,20 @@ 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
|
||||
|
@ -52,6 +71,19 @@ 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 {
|
||||
|
@ -59,16 +91,18 @@ 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, 4)
|
||||
header := make([]byte, 2)
|
||||
if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil {
|
||||
return packet, err
|
||||
}
|
||||
|
@ -78,12 +112,32 @@ func (rn *RicochetNetwork) RecvRicochetPacket(reader io.Reader) (RicochetData, e
|
|||
return packet, InvalidPacketLengthError
|
||||
}
|
||||
|
||||
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 {
|
||||
packetBytes := make([]byte, size-2)
|
||||
_, err := io.ReadAtLeast(reader, packetBytes, size-2)
|
||||
if 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
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
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
|
||||
}
|
58
utils/tor.go
58
utils/tor.go
|
@ -2,7 +2,11 @@ package utils
|
|||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha512"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -17,3 +21,57 @@ 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
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -41,3 +43,15 @@ 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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
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;
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
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];
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
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;
|
||||
}
|
|
@ -130,7 +130,7 @@ func (m *OpenChannel) String() string { return proto.CompactTextString(m) }
|
|||
func (*OpenChannel) ProtoMessage() {}
|
||||
|
||||
var extRange_OpenChannel = []proto.ExtensionRange{
|
||||
{100, 536870911},
|
||||
{Start: 100, End: 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{
|
||||
{100, 536870911},
|
||||
{Start: 100, End: 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{
|
||||
{100, 536870911},
|
||||
{Start: 100, End: 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{
|
||||
{100, 536870911},
|
||||
{Start: 100, End: 536870911},
|
||||
}
|
||||
|
||||
func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange {
|
||||
|
|
Loading…
Reference in New Issue