forked from cwtch.im/tapir
Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Dan Ballard | f38efc6bf2 | |
Dan Ballard | 3a6cd3bd79 | |
Sarah Jamie Lewis | 5af71324b2 | |
Sarah Jamie Lewis | 9c91a4e000 | |
Dan Ballard | 5e4c386847 | |
Sarah Jamie Lewis | f2958b21ca | |
Dan Ballard | 6515a4e160 | |
Sarah Jamie Lewis | 875690aff2 |
|
@ -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/tor
|
||||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
|
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
|
||||||
- chmod a+x tor
|
- chmod a+x tor
|
||||||
- go list ./... | xargs go get
|
- export GO111MODULE=on
|
||||||
|
- go mod vendor
|
||||||
- go get -u golang.org/x/lint/golint
|
- go get -u golang.org/x/lint/golint
|
||||||
quality:
|
quality:
|
||||||
image: golang
|
image: golang
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# TAPir: Tiny Anonymous Peer
|
# 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 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.
|
Tapir has been designed as a replacement to the Ricochet protocol.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* New Authenticaiton Protocol based on v3 Onion Services
|
* New Authentication Protocol based on v3 Onion Services
|
||||||
* Bidirectional Application Channels.
|
* Bidirectional Application Channels.
|
||||||
|
|
||||||
**Work In Progress**
|
**Work In Progress**
|
|
@ -34,7 +34,7 @@ func (ea AuthApp) NewInstance() tapir.Application {
|
||||||
// or the connection is closed.
|
// or the connection is closed.
|
||||||
func (ea AuthApp) Init(connection tapir.Connection) {
|
func (ea AuthApp) Init(connection tapir.Connection) {
|
||||||
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
|
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
|
||||||
ephemeralIdentity, _ := primitives.InitializeEphemeral()
|
ephemeralIdentity, _ := primitives.InitializeEphemeralIdentity()
|
||||||
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
|
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
|
||||||
serialized, _ := json.Marshal(authMessage)
|
serialized, _ := json.Marshal(authMessage)
|
||||||
connection.Send(serialized)
|
connection.Send(serialized)
|
||||||
|
@ -47,6 +47,15 @@ func (ea AuthApp) Init(connection tapir.Connection) {
|
||||||
return
|
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())
|
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
|
||||||
connection.SetEncryptionKey(key)
|
connection.SetEncryptionKey(key)
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,13 @@ type MockConnection struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MockConnection) Init(outbound bool) {
|
func (mc *MockConnection) Init(outbound bool) {
|
||||||
mc.id, _ = primitives.InitializeEphemeral()
|
mc.id, _ = primitives.InitializeEphemeralIdentity()
|
||||||
mc.outbound = outbound
|
mc.outbound = outbound
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (MockConnection) Hostname() string {
|
func (mc MockConnection) Hostname() string {
|
||||||
panic("implement me")
|
return mc.id.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc MockConnection) IsOutbound() bool {
|
func (mc MockConnection) IsOutbound() bool {
|
||||||
|
|
|
@ -54,6 +54,7 @@ func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name string) (ta
|
||||||
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
|
func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
|
||||||
var conn tapir.Connection
|
var conn tapir.Connection
|
||||||
s.connections.Range(func(key, value interface{}) bool {
|
s.connections.Range(func(key, value interface{}) bool {
|
||||||
|
log.Debugf("Checking %v", key)
|
||||||
connection := value.(tapir.Connection)
|
connection := value.(tapir.Connection)
|
||||||
if connection.Hostname() == hostname {
|
if connection.Hostname() == hostname {
|
||||||
if !connection.IsClosed() {
|
if !connection.IsClosed() {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
// Perform3DH encapsulates a triple-diffie-hellman key exchange.
|
// Perform3DH encapsulates a triple-diffie-hellman key exchange.
|
||||||
// In this exchange Alice and Bob both hold longterm identity keypairs
|
// In this exchange Alice and Bob both hold longterm identity keypairs
|
||||||
// Both Alice and Bob generate an additional ephemeral key pair:
|
// 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 Long Term <-> Bob Ephemeral
|
||||||
// Alice Ephemeral <-> Bob Long Term
|
// Alice Ephemeral <-> Bob Long Term
|
||||||
// Alice Ephemeral <-> Bob Ephemeral
|
// Alice Ephemeral <-> Bob Ephemeral
|
||||||
|
|
|
@ -16,17 +16,17 @@ type Identity struct {
|
||||||
edpubk *ed25519.PublicKey
|
edpubk *ed25519.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is a courtesy function for initializing a V3 Identity in-code.
|
// InitializeIdentity is a courtesy function for initializing a V3 Identity in-code.
|
||||||
func Initialize(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
|
func InitializeIdentity(name string, pk *ed25519.PrivateKey, pubk *ed25519.PublicKey) Identity {
|
||||||
return Identity{name, pk, pubk}
|
return Identity{name, pk, pubk}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeEphemeral generates a new ephemeral identity, the private key of this identity is provided in the response.
|
// InitializeEphemeralIdentity generates a new ephemeral identity, the private key of this identity is provided in the response.
|
||||||
func InitializeEphemeral() (Identity, ed25519.PrivateKey) {
|
func InitializeEphemeralIdentity() (Identity, ed25519.PrivateKey) {
|
||||||
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
|
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
|
||||||
ephemeralPublicKey := ed25519.PublicKey(epk)
|
ephemeralPublicKey := ed25519.PublicKey(epk)
|
||||||
ephemeralPrivateKey := ed25519.PrivateKey(esk)
|
ephemeralPrivateKey := ed25519.PrivateKey(esk)
|
||||||
ephemeralIdentity := Initialize("", &ephemeralPrivateKey, &ephemeralPublicKey)
|
ephemeralIdentity := InitializeIdentity("", &ephemeralPrivateKey, &ephemeralPublicKey)
|
||||||
return ephemeralIdentity, ephemeralPrivateKey
|
return ephemeralIdentity, ephemeralPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func (i *Identity) PublicKey() ed25519.PublicKey {
|
||||||
return *i.edpubk
|
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 {
|
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
|
||||||
secret := utils.EDH(*i.edpk, key)
|
secret := utils.EDH(*i.edpk, key)
|
||||||
return secret[:]
|
return secret[:]
|
||||||
|
|
|
@ -113,6 +113,7 @@ func (c *connection) HasCapability(name string) bool {
|
||||||
|
|
||||||
// Close forcibly closes the connection
|
// Close forcibly closes the connection
|
||||||
func (c *connection) Close() {
|
func (c *connection) Close() {
|
||||||
|
c.closed = true
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@ func (c *connection) Send(message []byte) {
|
||||||
if c.encrypted {
|
if c.encrypted {
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
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.conn.Close()
|
||||||
c.closed = true
|
c.closed = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestTapir(t *testing.T) {
|
||||||
acn.WaitTillBootstrapped()
|
acn.WaitTillBootstrapped()
|
||||||
|
|
||||||
// Generate Server Keys
|
// Generate Server Keys
|
||||||
id, sk := primitives.InitializeEphemeral()
|
id, sk := primitives.InitializeEphemeralIdentity()
|
||||||
|
|
||||||
// Init the Server running the Simple App.
|
// Init the Server running the Simple App.
|
||||||
var service tapir.Service
|
var service tapir.Service
|
||||||
|
@ -105,7 +105,7 @@ func TestTapir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func genclient(acn connectivity.ACN) (tapir.Service, string) {
|
func genclient(acn connectivity.ACN) (tapir.Service, string) {
|
||||||
id, sk := primitives.InitializeEphemeral()
|
id, sk := primitives.InitializeEphemeralIdentity()
|
||||||
var client tapir.Service
|
var client tapir.Service
|
||||||
client = new(tor.BaseOnionService)
|
client = new(tor.BaseOnionService)
|
||||||
client.Init(acn, sk, &id)
|
client.Init(acn, sk, &id)
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in New Issue