forked from cwtch.im/tapir
1
0
Fork 0

Compare commits

...

9 Commits

11 changed files with 129 additions and 19 deletions

View File

@ -9,7 +9,8 @@ pipeline:
- 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 list ./... | xargs go get
- export GO111MODULE=on
- go mod vendor
- go get -u golang.org/x/lint/golint
quality:
image: golang

View File

@ -1,12 +1,14 @@
# TAPir: Tiny Anonymous Peer
![](tapir.png)
Tapir is a small library for building p2p applications over anonymous communication systems (right now Tapir only supports Tor v3 Onion Services).
Tapir has been designed as a replacement to the Ricochet protocol.
## Features
* New Authenticaiton Protocol based on v3 Onion Services
* New Authentication Protocol based on v3 Onion Services
* Bidirectional Application Channels.
**Work In Progress**

View File

@ -34,7 +34,7 @@ func (ea AuthApp) NewInstance() tapir.Application {
// or the connection is closed.
func (ea AuthApp) Init(connection tapir.Connection) {
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
ephemeralIdentity, _ := primitives.InitializeEphemeral()
ephemeralIdentity, _ := primitives.InitializeEphemeralIdentity()
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
serialized, _ := json.Marshal(authMessage)
connection.Send(serialized)
@ -47,6 +47,15 @@ func (ea AuthApp) Init(connection tapir.Connection) {
return
}
// If we are an outbound connection we can perform an additional check to ensure that the server sent us back the correct long term
// public key
if connection.IsOutbound() && utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey) != connection.Hostname() {
log.Errorf("The remote server (%v) has attempted to authenticate with a different public key %v", connection.Hostname(), utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
connection.Close()
return
}
// Perform the triple-diffie-hellman exchange.
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
connection.SetEncryptionKey(key)

View File

@ -2,6 +2,7 @@ package applications
import (
"crypto/rand"
"cwtch.im/tapir"
"cwtch.im/tapir/primitives"
"encoding/json"
"golang.org/x/crypto/ed25519"
@ -14,13 +15,13 @@ type MockConnection struct {
}
func (mc *MockConnection) Init(outbound bool) {
mc.id, _ = primitives.InitializeEphemeral()
mc.id, _ = primitives.InitializeEphemeralIdentity()
mc.outbound = outbound
return
}
func (MockConnection) Hostname() string {
panic("implement me")
func (mc MockConnection) Hostname() string {
return mc.id.Hostname()
}
func (mc MockConnection) IsOutbound() bool {
@ -66,6 +67,11 @@ func (MockConnection) Close() {
// no op
}
func (MockConnection) App() tapir.Application {
// no op
return nil
}
func (MockConnection) IsClosed() bool {
panic("implement me")
}

View File

@ -54,6 +54,7 @@ func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name string) (ta
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
var conn tapir.Connection
s.connections.Range(func(key, value interface{}) bool {
log.Debugf("Checking %v", key)
connection := value.(tapir.Connection)
if connection.Hostname() == hostname {
if !connection.IsClosed() {

View File

@ -8,7 +8,7 @@ import (
// Perform3DH encapsulates a triple-diffie-hellman key exchange.
// In this exchange Alice and Bob both hold longterm identity keypairs
// Both Alice and Bob generate an additional ephemeral key pair:
// 3 Diffie Hellman exchanges are then performed:
// Three Diffie Hellman exchanges are then performed:
// Alice Long Term <-> Bob Ephemeral
// Alice Ephemeral <-> Bob Long Term
// Alice Ephemeral <-> Bob Ephemeral

View File

@ -16,17 +16,17 @@ type Identity struct {
edpubk *ed25519.PublicKey
}
// Initialize is a courtesy function for initializing a V3 Identity in-code.
func Initialize(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
// InitializeIdentity is a courtesy function for initializing a V3 Identity in-code.
func InitializeIdentity(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
return Identity{name, pk, pubk}
}
// InitializeEphemeral generates a new ephemeral identity, the private key of this identity is provided in the response.
func InitializeEphemeral() (Identity, ed25519.PrivateKey) {
// InitializeEphemeralIdentity generates a new ephemeral identity, the private key of this identity is provided in the response.
func InitializeEphemeralIdentity() (Identity, ed25519.PrivateKey) {
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
ephemeralPublicKey := ed25519.PublicKey(epk)
ephemeralPrivateKey := ed25519.PrivateKey(esk)
ephemeralIdentity := Initialize("", &ephemeralPrivateKey, &ephemeralPublicKey)
ephemeralIdentity := InitializeIdentity("", &ephemeralPrivateKey, &ephemeralPublicKey)
return ephemeralIdentity, ephemeralPrivateKey
}
@ -41,7 +41,7 @@ func (i *Identity) PublicKey() ed25519.PublicKey {
return *i.edpubk
}
// EDH performs a diffie helman operation on this identities private key with the given public key.
// 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 := utils.EDH(*i.edpk, key)
return secret[:]

View File

@ -35,6 +35,7 @@ type Connection interface {
SetEncryptionKey(key [32]byte)
Send(message []byte)
Close()
App() Application
IsClosed() bool
}
@ -45,7 +46,7 @@ type connection struct {
capabilities sync.Map
encrypted bool
key [32]byte
App Application
app Application
identity *primitives.Identity
outbound bool
closed bool
@ -57,11 +58,11 @@ func NewConnection(id *primitives.Identity, hostname string, outbound bool, conn
connection := new(connection)
connection.hostname = hostname
connection.conn = conn
connection.App = app
connection.app = app
connection.identity = id
connection.outbound = outbound
connection.MaxLength = 1024
go connection.App.Init(connection)
go connection.app.Init(connection)
return connection
}
@ -70,6 +71,11 @@ func (c *connection) ID() *primitives.Identity {
return c.identity
}
// App returns the overarching application using this Connection.
func (c *connection) App() Application {
return c.app
}
// Hostname returns the hostname of the connection (if the connection has not been authorized it will return the
// temporary hostname identifier)
func (c *connection) Hostname() string {
@ -107,6 +113,7 @@ func (c *connection) HasCapability(name string) bool {
// Close forcibly closes the connection
func (c *connection) Close() {
c.closed = true
c.conn.Close()
}
@ -156,7 +163,7 @@ func (c *connection) Send(message []byte) {
if c.encrypted {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
// TODO: Surface is Error
log.Errorf("Could not read sufficient randomness %v. Closing connection", err)
c.conn.Close()
c.closed = true
}

BIN
tapir.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -65,7 +65,7 @@ func TestTapir(t *testing.T) {
acn.WaitTillBootstrapped()
// Generate Server Keys
id, sk := primitives.InitializeEphemeral()
id, sk := primitives.InitializeEphemeralIdentity()
// Init the Server running the Simple App.
var service tapir.Service
@ -105,7 +105,7 @@ func TestTapir(t *testing.T) {
}
func genclient(acn connectivity.ACN) (tapir.Service, string) {
id, sk := primitives.InitializeEphemeral()
id, sk := primitives.InitializeEphemeralIdentity()
var client tapir.Service
client = new(tor.BaseOnionService)
client.Init(acn, sk, &id)

View File

@ -0,0 +1,84 @@
package testing
import (
"cwtch.im/tapir"
"cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"runtime"
"sync"
"testing"
"time"
)
func TestTapirMaliciousRemote(t *testing.T) {
numRoutinesStart := runtime.NumGoroutine()
log.SetLevel(log.LevelDebug)
log.Infof("Number of goroutines open at start: %d", runtime.NumGoroutine())
// Connect to Tor
var acn connectivity.ACN
acn, _ = connectivity.StartTor("./", "")
acn.WaitTillBootstrapped()
// Generate Server Keys, not we generate two sets
id, _ := primitives.InitializeEphemeralIdentity()
id2, sk2 := primitives.InitializeEphemeralIdentity()
// Init the Server running the Simple App.
var service tapir.Service
service = new(tor.BaseOnionService)
// Initialize an onion service with one identity, but the auth app with another, this should
// trigger a failure in authentication protocol
service.Init(acn, sk2, &id)
// Goroutine Management
sg := new(sync.WaitGroup)
sg.Add(1)
go func() {
service.Listen(applications.AuthApp{})
sg.Done()
}()
// Wait for server to come online
time.Sleep(time.Second * 30)
wg := new(sync.WaitGroup)
wg.Add(1)
// Init a Client to Connect to the Server
log.Infof("initializing the client....")
client, _ := genclient(acn)
go connectclientandfail(client, id2.PublicKey(), wg, t)
wg.Wait()
// Wait for Server to Sync
time.Sleep(time.Second * 2)
log.Infof("closing ACN...")
acn.Close()
sg.Wait()
time.Sleep(time.Second * 2)
log.Infof("Number of goroutines open at close: %d", runtime.NumGoroutine())
if numRoutinesStart != runtime.NumGoroutine() {
t.Errorf("Potential goroutine leak: Num Start:%v NumEnd: %v", numRoutinesStart, runtime.NumGoroutine())
}
}
// Client will Connect and launch it's own Echo App goroutine.
func connectclientandfail(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup, t *testing.T) {
client.Connect(utils.GetTorV3Hostname(key), applications.AuthApp{})
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
// we will wait a little while then exit.
time.Sleep(time.Second * 5)
log.Infof("Checking connection status...")
conn, err := client.GetConnection(utils.GetTorV3Hostname(key))
if err == nil {
group.Done()
t.Fatalf("Connection should have failed! %v %v", conn, err)
}
log.Infof("Successfully failed to authenticate...")
group.Done()
}