Compare commits

..

124 Commits

Author SHA1 Message Date
Dan Ballard 7cb1272cc4 Merge pull request 'Downgrading protobuf to v1.3.5' (#79) from replace-extra25519 into master
the build was successful Details
2020-04-20 11:46:27 -07:00
Sarah Jamie Lewis 7281acea2e Downgrading protobuf to v1.3.5
the build was successful Details
2020-04-20 11:34:44 -07:00
Dan Ballard 57ea15ec68 Merge pull request 'Removing extra25519 dependency (original was purged)' (#78) from replace-extra25519 into master
the build was successful Details
2020-04-20 11:08:02 -07:00
Sarah Jamie Lewis 463ba297ce Removing extra25519 dependency (original was purged)
the build was successful Details
2020-04-20 10:47:53 -07:00
Sarah Jamie Lewis ee8882c39e Merge branch 'logconn' of dan/libricochet-go into master
the build was successful Details
2020-02-10 11:51:32 -08:00
Dan Ballard 03b9ff1fe8 move log and conectivity packages to standalone versions
the build was successful Details
2020-02-10 14:30:32 -05:00
Sarah Jamie Lewis 5ec0bc8e1d Merge branch 'race' of dan/libricochet-go into master
the build was successful Details
2020-02-03 16:01:19 -08:00
Dan Ballard 2fd0aa67bd fix race condition around connection count getting
the build was successful Details
2020-02-03 18:40:17 -05:00
Sarah Jamie Lewis f82c2f9da4 Merge branch 'windows' of openprivacy/libricochet-go into master
the build was successful Details
2020-01-27 11:24:29 -08:00
Dan Ballard 7c828d3916 Use new Bine CmdCreatorFunc to make a ProcessCreator to hide Tor dos window.
the build was successful Details
Also hide it in checkTorVersion
2020-01-24 18:19:36 -05:00
Dan Ballard 79a1ff9161 Merge branch 'race' of openprivacy/libricochet-go into master
the build was successful Details
2019-11-07 16:26:35 -08:00
Sarah Jamie Lewis 5a1fc1b94d Fixing Race Conditions
the build was successful Details
2019-11-07 16:21:15 -08:00
Dan Ballard 9ba39b93b7 Merge branch 'restart-new' of openprivacy/libricochet-go into master
the build was successful Details
2019-11-06 12:07:47 -08:00
Sarah Jamie Lewis 29540dcf71 Expose Reboot
the build was successful Details
2019-11-06 11:59:29 -08:00
Dan Ballard 0fdcfd1553 drone liny
the build was successful Details
2019-11-06 11:57:14 -08:00
Dan Ballard ac4993adb7 drone get/download
the build failed Details
2019-11-06 11:52:22 -08:00
Dan Ballard 881ca5c6c2 drone use go mod instead of list xargs for fetch
the build failed Details
2019-11-06 11:50:25 -08:00
Dan Ballard 61f89d7b2c drone force modules
the build failed Details
2019-11-06 11:40:58 -08:00
Sarah Jamie Lewis 725f64020a Merge branch 'logfile' of dan/libricochet-go into master 2019-08-12 14:47:10 -07:00
Dan Ballard b7cca3fa83 log now suports creating a logger around a file 2019-08-12 14:17:17 -07:00
Sarah Jamie Lewis 07747c4dd2 Merge branch 'torFix' of dan/libricochet-go into master 2019-07-31 15:09:46 -07:00
Dan Ballard 59ea2902e8 move .Dial outside of lock to stop throtteling one conn per time; get Dialer once only 2019-07-31 14:41:07 -07:00
Sarah Jamie Lewis 4ccdc79526 Merge branch 'torStatus' of dan/libricochet-go into master 2019-07-10 13:16:21 -07:00
Dan Ballard 6517665498 ACN and tor provider: take callback for status change 2019-07-10 13:08:49 -07:00
Sarah Jamie Lewis cd872e9e0a Merge branch 'logExclude' of dan/libricochet-go into master 2019-06-21 15:41:32 -07:00
Dan Ballard b534ecd04e add log exclude 2019-06-21 15:34:46 -07:00
Sarah Jamie Lewis aca0f63dd2 Merge branch 'nonewnym' of dan/libricochet-go into master 2019-02-20 19:45:31 +00:00
Dan Ballard bf57db657a stop issueing tor NEWNYM commands on errrors on open() 2019-02-20 11:36:42 -08:00
Sarah Jamie Lewis 8db3c09fce Merge branch 'tornetwork' of dan/libricochet-go into master 2019-02-14 19:50:29 +00:00
Dan Ballard fa12b3dcb9 when strting tor, dont let the network be disabled 2019-02-14 11:40:57 -08:00
erinn 9e4e042ffb Merge branch 'master' of dan/libricochet-go into master 2019-02-05 19:57:33 +00:00
Dan Ballard d78488a200 torprovide more nil checks 2019-02-05 11:54:52 -08:00
Sarah Jamie Lewis b3d6e0e019 Merge branch 'master' of dan/libricochet-go into master 2019-02-04 22:19:53 +00:00
Dan Ballard bf28d6574f prevent sigfault when tor is closed 2019-02-04 14:12:16 -08:00
Sarah Jamie Lewis 0739119a4d Merge branch 'v1-define1' of openprivacy/libricochet-go into master 2019-01-28 22:22:05 +00:00
Sarah Jamie Lewis 527ba61de0 Fixing V1 Error Issue with go mod 2019-01-28 14:20:14 -08:00
erinn af195d186c Merge branch 'v1-define' of openprivacy/libricochet-go into master 2019-01-28 22:07:20 +00:00
Sarah Jamie Lewis 5bfb17588c Defining Version 1.0 2019-01-28 14:01:57 -08:00
Dan Ballard 1131bc930e Merge branch 'v1-cleanup' of openprivacy/libricochet-go into master 2019-01-28 19:56:29 +00:00
Sarah Jamie Lewis 877f01a358 Cleaning up ineffectual error checking and misspellings 2019-01-26 14:05:09 -08:00
Dan Ballard 46e3eeeb7d Merge branch 'v1-cleanup' of openprivacy/libricochet-go into master 2019-01-23 21:10:36 +00:00
Sarah Jamie Lewis 96fc03580b Officially Break backwards-compatitbility with Ricochet-IM 2019-01-23 12:26:32 -08:00
Sarah Jamie Lewis 7a4350f0c1 Delete last reminants of V2 Onion Handling 2019-01-23 11:55:42 -08:00
Sarah Jamie Lewis b2f6b314fc Merge branch 'v1-cleanup' of openprivacy/libricochet-go into master 2019-01-23 19:47:30 +00:00
Sarah Jamie Lewis 64ce11d436 Cleaning up and documenting examples 2019-01-23 11:38:54 -08:00
erinn 8f00e26b81 Merge branch 'v1-encrypt' of openprivacy/libricochet-go into master 2019-01-23 19:38:13 +00:00
Sarah Jamie Lewis a96f682e77 Start using the derived ephemeral session key for encrypting 2019-01-23 11:31:44 -08:00
Dan Ballard 487b1e9ae0 Merge branch 'v1' of openprivacy/libricochet-go into master
<3
2019-01-23 01:19:46 +00:00
Sarah Jamie Lewis 9a680cd257 Renaming Instance and InstanceFactory
Now up to standard with go lint
2019-01-21 14:52:26 -08:00
Dan Ballard e068de0ef8 Merge branch 'close' of openprivacy/libricochet-go into master 2019-01-21 20:30:21 +00:00
Dan Ballard 1f0e87b3c4 Merge branch 'module_update' of openprivacy/libricochet-go into master 2019-01-21 20:28:52 +00:00
Sarah Jamie Lewis f4fbd52d4b go mod tidy 2019-01-21 11:34:27 -08:00
Sarah Jamie Lewis d87a0fcb52 Add Close() Method to Connection.
Explicitly Close Connection
2019-01-21 11:19:49 -08:00
Dan Ballard 859cdf95e1 Merge branch 'module' of openprivacy/libricochet-go into master 2019-01-14 20:03:12 +00:00
Dan Ballard 3137689de8 Merge branch 'update_readme' of openprivacy/libricochet-go into master 2019-01-14 19:51:42 +00:00
Sarah Jamie Lewis 2cf7113f80 Defining a Go Module 2019-01-13 14:20:42 -08:00
Sarah Jamie Lewis 4c23e439f0 First cut at 1.0 README 2019-01-11 20:46:31 +00:00
Dan Ballard 540e68b8ef Merge branch 'cleanup' of openprivacy/libricochet-go into master 2019-01-11 20:01:07 +00:00
Sarah Jamie Lewis b05567fd81 Fixing Linting, Vetting & Formatting Issues 2019-01-09 15:02:09 -08:00
Dan Ballard 8d1cba2b0b Merge branch 'remove-v3-contact-request' of openprivacy/libricochet-go into master 2019-01-09 22:08:40 +00:00
Sarah Jamie Lewis 90231b0be9 Remove V3 Contact Request 2019-01-08 11:09:23 -08:00
erinn 328ffc9d76 Merge branch 'tor-attach' of dan/libricochet-go into master 2018-12-18 19:49:29 +00:00
Dan Ballard 489bf62ff7 torProvider / listen now generates determinitic local ports, and can handle tor 550 detached onion exists, and connect to it 2018-12-07 10:41:38 -08:00
Dan Ballard b34fe84917 log api typo 2018-12-03 13:27:21 -08:00
Sarah Jamie Lewis b6caf3fbc4 Merge branch 'logging' of dan/libricochet-go into master 2018-12-03 20:19:59 +00:00
Dan Ballard 2815e29704 adding new filterable logging system 2018-12-03 11:59:21 -08:00
Sarah Jamie Lewis fa720940d8 Merge branch 'tor-retry' of dan/libricochet-go into master 2018-11-26 21:43:16 +00:00
Dan Ballard 5697a1c03d torProvider now gracefuly handles tor process fails 2018-11-26 12:51:08 -08:00
Sarah Jamie Lewis 232849e304 Merge branch 'acn2' of dan/libricochet-go into master 2018-11-23 03:18:24 +00:00
Dan Ballard 38cff4212d rename local variables acn; Add bootstrap status support to ACN/torprovider 2018-11-21 22:15:35 -08:00
Sarah Jamie Lewis b8a7cd702a Merge branch 'errmsg' of openprivacy/libricochet-go into master 2018-11-22 00:21:42 +00:00
erinn 65fb3992a3 Merge branch 'acn' of dan/libricochet-go into master 2018-11-22 00:20:19 +00:00
erinn 52bbc23251 include error message on rejected channel 2018-11-21 16:18:07 -08:00
Dan Ballard 53e6f90925 Merge branch 'master' of https://git.openprivacy.ca/openprivacy/libricochet-go 2018-11-21 15:55:21 -08:00
Dan Ballard b1fb04a880 drone only email on fail 2018-11-21 15:55:11 -08:00
Dan Ballard 0d080e4332 name update 2018-11-21 15:07:57 -08:00
Dan Ballard 4da220b223 Merge branch 'validhostname' of openprivacy/libricochet-go into master 2018-11-21 22:10:33 +00:00
Sarah Jamie Lewis d54ed0b106 Adding IsValidHostname function 2018-11-21 14:07:13 -08:00
Dan Ballard 52ce7615c3 Merge branch 'test_fix' of openprivacy/libricochet-go into master 2018-11-21 04:43:14 +00:00
Sarah Jamie Lewis aba5fa4609 Fixing Regex Bug 2018-11-20 20:33:11 -08:00
Dan Ballard 6c0d17667b Merge branch 'test_fix' of openprivacy/libricochet-go into master 2018-11-20 19:49:10 +00:00
Sarah Jamie Lewis 69e6644eb2 Fixing 3DH Auth Tests 2018-11-20 11:35:15 -08:00
Sarah Jamie Lewis 353ef38a54 Merge branch 'bine' of dan/libricochet-go into master 2018-11-20 19:19:34 +00:00
Dan Ballard 8fc60a0495 Mirating from bulb/asaur to bine, adding a generic Mixnet interface 2018-11-20 09:14:14 -08:00
Dan Ballard 880bd2e020 drone use proper path for test 2018-11-19 16:57:56 -08:00
Dan Ballard 493c2f5ec0 drone use proper path for repo 2018-11-19 15:14:58 -08:00
Sarah Jamie Lewis 18bc23d5c1 Merge branch 'test-fix' of dan/libricochet-go into master 2018-11-10 21:07:45 +00:00
Dan Ballard dfba540973 drone gogs notify more 2018-11-09 15:49:36 -08:00
Dan Ballard 3900552e06 testing script fail on fail; fix chatchannel test - update for 3dh 2018-11-09 15:47:21 -08:00
Sarah Jamie Lewis 49faf95ee7 Merge branch 'echobotv3' of openprivacy/libricochet-go into master 2018-10-27 17:37:02 +00:00
erinn 4d3f52102f allow retrieving handlers from an aif so we can merge them in cwtch peers 2018-10-27 02:17:35 -07:00
erinn ad9d0efb02 updating chatchannels and echobot to use v3 2018-10-25 19:20:58 -07:00
Dan Ballard 5b914aaf3a update drone to use new buildfiles/tor location 2018-10-10 15:42:55 -07:00
erinn 1abbb874cc Merge branch 'bugfix' of openprivacy/libricochet-go into master 2018-10-10 02:25:27 +00:00
erinn 8960be643c bugfix: the uncached connection attempt wasnt being returned 2018-10-09 19:24:22 -07:00
erinn 1056fce116 Merge branch 'asaur' of dan/libricochet-go into master 2018-10-09 23:21:39 +00:00
Dan Ballard 29e3a42cc9 asaur-ificate package and imports 2018-10-09 16:03:20 -07:00
Sarah Jamie Lewis bce6496829 Merge branch 'detports' of openprivacy/libricochet-go into master 2018-10-09 21:41:00 +00:00
erinn e825e52a7c check current onion descriptors on old versions of tor to see if they're out-of-sync 2018-10-09 12:55:42 -07:00
erinn fb8c0cac27 tidying up code paths and making detport selection a little better 2018-10-09 10:14:28 -07:00
erinn 5c98fd575b make local port selection deterministic and detach from the control port to improve performance 2018-10-08 20:19:19 -07:00
Sarah Jamie Lewis dd2af9f059 Merge branch 'bulbmigration' of openprivacy/libricochet-go into master 2018-10-09 01:54:13 +00:00
erinn 8a7895a359 oops, accidentally included a future change 2018-10-08 18:50:58 -07:00
erinn fd01dca056 forking bulb 2018-10-08 18:48:37 -07:00
Sarah Jamie Lewis 3ffe8ad0e4 Merge branch 'newnym' of openprivacy/libricochet-go into master 2018-10-08 16:50:01 +00:00
erinn aa7b398646 bugfix: actually attempt to connect again after a failed first attempt 2018-10-08 09:44:52 -07:00
Sarah Jamie Lewis 6c37867ed1 Merge branch 'newnym' of openprivacy/libricochet-go into master 2018-10-08 02:45:01 +00:00
erinn a6ca1571ad gofmt 2018-10-07 19:43:55 -07:00
erinn 06381579e5 after failing a new connection, flush the onion descriptor cache and try again in case the destination has cycled since its last use 2018-10-07 19:31:32 -07:00
Dan Ballard 530fa1f39f Merge branch 'v3onions' of openprivacy/libricochet-go into master 2018-10-05 20:24:04 +00:00
Sarah Jamie Lewis 5066380655 v3 onions 2018-10-05 13:06:54 -07:00
Dan Ballard 1667868d8c .drone: use openpriv version of drone-gogs 2018-07-17 12:06:55 -05:00
Sarah Jamie Lewis 18dcf5c2ae Merge branch 'drone-gogs' of dan/libricochet-go into master 2018-07-08 14:31:20 +00:00
Dan Ballard 90e39869a9 drone: move to drone-gogs 2018-07-07 20:16:43 -05:00
Sarah Jamie Lewis 930b94e0be Merge branch 'proto' of dan/libricochet-go into master 2018-07-05 21:54:47 +00:00
Dan Ballard b3c09e5409 ricochet protobuf files, for completeness 2018-07-05 16:49:52 -05:00
Sarah Jamie Lewis cc6076760f Merge branch 'drone' of dan/libricochet-go into master 2018-07-02 18:27:04 +00:00
Dan Ballard cdb458647f drone build file 2018-07-02 13:16:43 -05:00
Sarah Jamie Lewis 69c1dc18fb Merge branch 'master' of dan/libricochet-go into master 2018-06-25 16:19:58 +00:00
Dan Ballard e0411ecb9a fix: off by one error removing dead ricochet instances 2018-06-25 09:14:24 -07:00
Sarah Jamie Lewis c1907d5a9f Merge branch 'appinstance-cleanup' of dan/libricochet-go into master 2018-06-23 16:02:58 +00:00
Dan Ballard a852bc6678 application deletes application instance when it exit's process; adds CountConnections() 2018-06-23 08:57:22 -07:00
Sarah Jamie Lewis 2dc3739f56 Merge branch 'quality' of dan/libricochet-go into master 2018-06-23 15:47:56 +00:00
Dan Ballard 4442ab6691 code quality vet and lint script 2018-06-23 08:45:17 -07:00
58 changed files with 1822 additions and 999 deletions

51
.drone.yml Normal file
View File

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

6
.gitignore vendored
View File

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

30
Godeps/Godeps.json generated
View File

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

5
Godeps/Readme generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
package channels
import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/chat"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing"
"time"
)
@ -25,8 +25,8 @@ func TestChatChannelOptions(t *testing.T) {
if chatChannel.Bidirectional() {
t.Errorf("ChatChannel should not be bidirectional")
}
if chatChannel.RequiresAuthentication() != "im.ricochet.auth.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())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,12 +3,12 @@ package connection
import (
"context"
"fmt"
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"git.openprivacy.ca/openprivacy/log"
"github.com/golang/protobuf/proto"
"io"
"log"
"sync"
)
@ -31,16 +31,16 @@ type Connection struct {
unlockResponseChannel chan bool
messageBuilder utils.MessageBuilder
trace bool
closed bool
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()
}

View File

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

View File

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

View File

@ -1,10 +1,10 @@
package connection
import (
"github.com/golang/protobuf/proto"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"git.openprivacy.ca/openprivacy/libricochet-go/wire/control"
"github.com/golang/protobuf/proto"
"testing"
)
@ -23,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),

View File

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

View File

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

View File

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

11
go.mod Normal file
View File

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

66
go.sum Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,20 @@
package goricochet
import (
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/connection"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"io"
"net"
)
// Open establishes a protocol session on an established net.Conn, and returns a new
// Open establishes a protocol session on an established net.conn, and returns a new
// OpenConnection instance representing this connection. On error, the connection
// will be closed. This function blocks until version negotiation has completed.
// The application should call Process() on the returned OpenConnection to continue
// handling protocol messages.
func Open(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
}

View File

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

View File

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

23
testing/quality.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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