Browse Source

Adding Integration Test

- Converted cmd/main to integ test
- Factored in Identity from libricochet-go
- Breaking up Auth run into smaller contained functions
tags/v0.1.7
Sarah Jamie Lewis 4 months ago
parent
commit
42d3cb196a

+ 6
- 0
.drone.yml View File

@@ -21,6 +21,12 @@ pipeline:
commands:
- export PATH=$PATH:/go/src/cwtch.im/tapir
- sh testing/tests.sh
integ-test:
image: golang
commands:
- ./tor -f ./torrc
- sleep 15
- go test -v cwtch.im/tapir/testing
notify-email:
image: drillster/drone-email
host: build.openprivacy.ca

+ 1
- 0
.gitignore View File

@@ -2,3 +2,4 @@ vendor/
.idea
/tor/
coverage.out
/testing/tor/

+ 5
- 24
applications/auth.go View File

@@ -1,11 +1,10 @@
package applications

import (
"crypto/rand"
"crypto/subtle"
"cwtch.im/tapir"
"cwtch.im/tapir/primitives"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
@@ -35,11 +34,8 @@ func (ea AuthApp) NewInstance() tapir.Application {
// or the connection is closed.
func (ea AuthApp) Init(connection tapir.Connection) {
longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
ephemeralPublicKey := ed25519.PublicKey(epk)
ephemeralPrivateKey := ed25519.PrivateKey(esk)
ephemeralIdentity := identity.InitializeV3("", &ephemeralPrivateKey, &ephemeralPublicKey)
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralPublicKey}
ephemeralIdentity, _ := primitives.InitializeEphemeral()
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
serialized, _ := json.Marshal(authMessage)
connection.Send(serialized)
message := connection.Expect()
@@ -51,23 +47,8 @@ func (ea AuthApp) Init(connection tapir.Connection) {
return
}

// 3DH Handshake
l2e := connection.ID().EDH(remoteAuthMessage.EphemeralPublicKey)
e2l := ephemeralIdentity.EDH(remoteAuthMessage.LongTermPublicKey)
e2e := ephemeralIdentity.EDH(remoteAuthMessage.EphemeralPublicKey)

// We need to define an order for the result concatenation so that both sides derive the same key.
var result [96]byte
if connection.IsOutbound() {
copy(result[0:32], l2e)
copy(result[32:64], e2l)
copy(result[64:96], e2e)
} else {
copy(result[0:32], e2l)
copy(result[32:64], l2e)
copy(result[64:96], e2e)
}
connection.SetEncryptionKey(sha3.Sum256(result[:]))
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
connection.SetEncryptionKey(key)

// Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on
// otherwise our next Send will fail.

+ 4
- 7
applications/auth_test.go View File

@@ -2,22 +2,19 @@ package applications

import (
"crypto/rand"
"cwtch.im/tapir/primitives"
"encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"golang.org/x/crypto/ed25519"
"testing"
)

type MockConnection struct {
id identity.Identity
id primitives.Identity
outbound bool
}

func (mc *MockConnection) Init(outbound bool) {
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
sk := ed25519.PrivateKey(privateKey)
pk := ed25519.PublicKey(pubkey)
mc.id = identity.InitializeV3("", &sk, &pk)
mc.id, _ = primitives.InitializeEphemeral()
mc.outbound = outbound
return
}
@@ -30,7 +27,7 @@ func (mc MockConnection) IsOutbound() bool {
return mc.outbound
}

func (mc MockConnection) ID() *identity.Identity {
func (mc MockConnection) ID() *primitives.Identity {
return &mc.id
}


+ 3
- 3
networks/tor/BaseOnionService.go View File

@@ -3,10 +3,10 @@ package tor
import (
"crypto/rand"
"cwtch.im/tapir"
"cwtch.im/tapir/primitives"
"encoding/base64"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"sync"
@@ -17,14 +17,14 @@ import (
type BaseOnionService struct {
connections sync.Map
acn connectivity.ACN
id identity.Identity
id *primitives.Identity
privateKey ed25519.PrivateKey
ls connectivity.ListenService
}

// Init initializes a BaseOnionService with a given private key and identity
// The private key is needed to initialize the Onion listen socket, ideally we could just pass an Identity in here.
func (s *BaseOnionService) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) {
func (s *BaseOnionService) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id *primitives.Identity) {
// run add onion
// get listen context
s.acn = acn

+ 35
- 0
primitives/3DH.go View File

@@ -0,0 +1,35 @@
package primitives

import (
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
)

// 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:
// Alice Long Term <-> Bob Ephemeral
// Alice Ephemeral <-> Bob Long Term
// Alice Ephemeral <-> Bob Ephemeral
//
// Through this, a unique session key is derived. The exchange is offline-deniable (in the context of Tapir and Onion Service)
func Perform3DH(longtermIdentity *Identity, ephemeralIdentity *Identity, remoteLongTermPublicKey ed25519.PublicKey, remoteEphemeralPublicKey ed25519.PublicKey, outbound bool) [32]byte {
// 3DH Handshake
l2e := longtermIdentity.EDH(remoteEphemeralPublicKey)
e2l := ephemeralIdentity.EDH(remoteLongTermPublicKey)
e2e := ephemeralIdentity.EDH(remoteEphemeralPublicKey)

// We need to define an order for the result concatenation so that both sides derive the same key.
var result [96]byte
if outbound {
copy(result[0:32], l2e)
copy(result[32:64], e2l)
copy(result[64:96], e2e)
} else {
copy(result[0:32], e2l)
copy(result[32:64], l2e)
copy(result[64:96], e2e)
}
return sha3.Sum256(result[:])
}

+ 53
- 0
primitives/identity.go View File

@@ -0,0 +1,53 @@
package primitives

import (
"crypto/rand"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
)

// Identity is an encapsulation of Name, PrivateKey and other features
// that make up a Tapir client.
// 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
edpk *ed25519.PrivateKey
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 {
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) {
epk, esk, _ := ed25519.GenerateKey(rand.Reader)
ephemeralPublicKey := ed25519.PublicKey(epk)
ephemeralPrivateKey := ed25519.PrivateKey(esk)
ephemeralIdentity := Initialize("", &ephemeralPrivateKey, &ephemeralPublicKey)
return ephemeralIdentity, ephemeralPrivateKey
}

// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
// format.
func (i *Identity) PublicKeyBytes() []byte {
return *i.edpubk
}

// PublicKey returns the public key associated with this Identity
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.
func (i *Identity) EDH(key ed25519.PublicKey) []byte {
secret := utils.EDH(*i.edpk, key)
return secret[:]
}

// Hostname provides the onion address associated with this Identity.
func (i *Identity) Hostname() string {
return utils.GetTorV3Hostname(*i.edpubk)
}

+ 7
- 7
service.go View File

@@ -2,9 +2,9 @@ package tapir

import (
"crypto/rand"
"cwtch.im/tapir/primitives"
"encoding/binary"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/secretbox"
@@ -15,7 +15,7 @@ import (

// Service defines the interface for a Tapir Service
type Service interface {
Init(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity)
Init(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity *primitives.Identity)
Connect(hostname string, application Application) (bool, error)
Listen(application Application) error
GetConnection(connectionID string) (Connection, error)
@@ -27,7 +27,7 @@ type Service interface {
type Connection interface {
Hostname() string
IsOutbound() bool
ID() *identity.Identity
ID() *primitives.Identity
Expect() []byte
SetHostname(hostname string)
HasCapability(name string) bool
@@ -46,19 +46,19 @@ type connection struct {
encrypted bool
key [32]byte
App Application
identity *identity.Identity
identity *primitives.Identity
outbound bool
closed bool
MaxLength int
}

// NewConnection creates a new Connection
func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) Connection {
func NewConnection(id *primitives.Identity, hostname string, outbound bool, conn net.Conn, app Application) Connection {
connection := new(connection)
connection.hostname = hostname
connection.conn = conn
connection.App = app
connection.identity = &id
connection.identity = id
connection.outbound = outbound
connection.MaxLength = 1024
go connection.App.Init(connection)
@@ -66,7 +66,7 @@ func NewConnection(id identity.Identity, hostname string, outbound bool, conn ne
}

// ID returns an identity.Identity encapsulation (for the purposes of cryptographic protocols)
func (c *connection) ID() *identity.Identity {
func (c *connection) ID() *primitives.Identity {
return c.identity
}


cmd/main.go → testing/tapir_integration_test.go View File

@@ -1,16 +1,17 @@
package main
package testing

import (
"crypto/rand"
"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/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519"
"os"
"runtime"
"sync"
"testing"
"time"
)

@@ -37,60 +38,82 @@ func (ea SimpleApp) Init(connection tapir.Connection) {
}
}

var AuthSuccess = false

// CheckConnection is a simple test that GetConnection is working.
func CheckConnection(service tapir.Service, hostname string) {
func CheckConnection(service tapir.Service, hostname string, group *sync.WaitGroup) {
for {
_, err := service.GetConnection(hostname)
if err == nil {
log.Infof("Authed!")
group.Done()
return
}
log.Errorf("Error %v", err)
time.Sleep(time.Second)
log.Infof("Waiting for Authentication...%v", err)
time.Sleep(time.Second * 5)
}
}

func main() {
func TestTapir(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
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
sk := ed25519.PrivateKey(privateKey)
pk := ed25519.PublicKey(pubkey)
id := identity.InitializeV3("server", &sk, &pk)

// Init a Client to Connect to the Server
client, clienthostname := genclient(acn)
go connectclient(client, pubkey)
id, sk := primitives.InitializeEphemeral()

// Init the Server running the Simple App.
var service tapir.Service
service = new(tor.BaseOnionService)
service.Init(acn, sk, id)
go CheckConnection(service, clienthostname)
service.Listen(SimpleApp{})
service.Init(acn, sk, &id)

// Goroutine Management
sg := new(sync.WaitGroup)
sg.Add(1)
go func() {
service.Listen(SimpleApp{})
sg.Done()
}()

// Wait for server to come online
time.Sleep(time.Second * 30)
wg := new(sync.WaitGroup)
wg.Add(2)
// Init a Client to Connect to the Server
client, clienthostname := genclient(acn)
go connectclient(client, id.PublicKey(), wg)
CheckConnection(service, clienthostname, wg)
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())
}
if !AuthSuccess {
t.Fatalf("Integration Test FAILED, client did not auth with server")
}
}

func genclient(acn connectivity.ACN) (tapir.Service, string) {
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
sk := ed25519.PrivateKey(privateKey)
pk := ed25519.PublicKey(pubkey)
id := identity.InitializeV3("client", &sk, &pk)
id, sk := primitives.InitializeEphemeral()
var client tapir.Service
client = new(tor.BaseOnionService)
client.Init(acn, sk, id)
return client, utils.GetTorV3Hostname(pk)
client.Init(acn, sk, &id)
return client, id.Hostname()
}

// Client will Connect and launch it's own Echo App goroutine.
func connectclient(client tapir.Service, key ed25519.PublicKey) {

func connectclient(client tapir.Service, key ed25519.PublicKey, group *sync.WaitGroup) {
client.Connect(utils.GetTorV3Hostname(key), SimpleApp{})

// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
@@ -99,6 +122,6 @@ func connectclient(client tapir.Service, key ed25519.PublicKey) {

conn, _ := client.GetConnection(utils.GetTorV3Hostname(key))
log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability))
os.Exit(0)
AuthSuccess = true
group.Done()
}

Loading…
Cancel
Save