Adding Integration Test

- Converted cmd/main to integ test
- Factored in Identity from libricochet-go
- Breaking up Auth run into smaller contained functions
This commit is contained in:
Sarah Jamie Lewis 2019-08-08 11:11:31 -07:00
parent f664afb021
commit 42d3cb196a
9 changed files with 167 additions and 71 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@ -1,11 +1,10 @@
package applications package applications
import ( import (
"crypto/rand"
"crypto/subtle" "crypto/subtle"
"cwtch.im/tapir" "cwtch.im/tapir"
"cwtch.im/tapir/primitives"
"encoding/json" "encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log" "git.openprivacy.ca/openprivacy/libricochet-go/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
@ -35,11 +34,8 @@ 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())
epk, esk, _ := ed25519.GenerateKey(rand.Reader) ephemeralIdentity, _ := primitives.InitializeEphemeral()
ephemeralPublicKey := ed25519.PublicKey(epk) authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
ephemeralPrivateKey := ed25519.PrivateKey(esk)
ephemeralIdentity := identity.InitializeV3("", &ephemeralPrivateKey, &ephemeralPublicKey)
authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralPublicKey}
serialized, _ := json.Marshal(authMessage) serialized, _ := json.Marshal(authMessage)
connection.Send(serialized) connection.Send(serialized)
message := connection.Expect() message := connection.Expect()
@ -51,23 +47,8 @@ func (ea AuthApp) Init(connection tapir.Connection) {
return return
} }
// 3DH Handshake key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
l2e := connection.ID().EDH(remoteAuthMessage.EphemeralPublicKey) connection.SetEncryptionKey(key)
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[:]))
// Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on // Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on
// otherwise our next Send will fail. // otherwise our next Send will fail.

View File

@ -2,22 +2,19 @@ package applications
import ( import (
"crypto/rand" "crypto/rand"
"cwtch.im/tapir/primitives"
"encoding/json" "encoding/json"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"testing" "testing"
) )
type MockConnection struct { type MockConnection struct {
id identity.Identity id primitives.Identity
outbound bool outbound bool
} }
func (mc *MockConnection) Init(outbound bool) { func (mc *MockConnection) Init(outbound bool) {
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader) mc.id, _ = primitives.InitializeEphemeral()
sk := ed25519.PrivateKey(privateKey)
pk := ed25519.PublicKey(pubkey)
mc.id = identity.InitializeV3("", &sk, &pk)
mc.outbound = outbound mc.outbound = outbound
return return
} }
@ -30,7 +27,7 @@ func (mc MockConnection) IsOutbound() bool {
return mc.outbound return mc.outbound
} }
func (mc MockConnection) ID() *identity.Identity { func (mc MockConnection) ID() *primitives.Identity {
return &mc.id return &mc.id
} }

View File

@ -3,10 +3,10 @@ package tor
import ( import (
"crypto/rand" "crypto/rand"
"cwtch.im/tapir" "cwtch.im/tapir"
"cwtch.im/tapir/primitives"
"encoding/base64" "encoding/base64"
"errors" "errors"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "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/log"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"sync" "sync"
@ -17,14 +17,14 @@ import (
type BaseOnionService struct { type BaseOnionService struct {
connections sync.Map connections sync.Map
acn connectivity.ACN acn connectivity.ACN
id identity.Identity id *primitives.Identity
privateKey ed25519.PrivateKey privateKey ed25519.PrivateKey
ls connectivity.ListenService ls connectivity.ListenService
} }
// Init initializes a BaseOnionService with a given private key and identity // 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. // 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 // run add onion
// get listen context // get listen context
s.acn = acn s.acn = acn

35
primitives/3DH.go Normal file
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
primitives/identity.go Normal file
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)
}

View File

@ -2,9 +2,9 @@ package tapir
import ( import (
"crypto/rand" "crypto/rand"
"cwtch.im/tapir/primitives"
"encoding/binary" "encoding/binary"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "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/log"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/nacl/secretbox"
@ -15,7 +15,7 @@ import (
// Service defines the interface for a Tapir Service // Service defines the interface for a Tapir Service
type Service interface { 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) Connect(hostname string, application Application) (bool, error)
Listen(application Application) error Listen(application Application) error
GetConnection(connectionID string) (Connection, error) GetConnection(connectionID string) (Connection, error)
@ -27,7 +27,7 @@ type Service interface {
type Connection interface { type Connection interface {
Hostname() string Hostname() string
IsOutbound() bool IsOutbound() bool
ID() *identity.Identity ID() *primitives.Identity
Expect() []byte Expect() []byte
SetHostname(hostname string) SetHostname(hostname string)
HasCapability(name string) bool HasCapability(name string) bool
@ -46,19 +46,19 @@ type connection struct {
encrypted bool encrypted bool
key [32]byte key [32]byte
App Application App Application
identity *identity.Identity identity *primitives.Identity
outbound bool outbound bool
closed bool closed bool
MaxLength int MaxLength int
} }
// NewConnection creates a new Connection // 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 := new(connection)
connection.hostname = hostname connection.hostname = hostname
connection.conn = conn connection.conn = conn
connection.App = app connection.App = app
connection.identity = &id connection.identity = id
connection.outbound = outbound connection.outbound = outbound
connection.MaxLength = 1024 connection.MaxLength = 1024
go connection.App.Init(connection) 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) // 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 return c.identity
} }

View File

@ -1,16 +1,17 @@
package main package testing
import ( import (
"crypto/rand"
"cwtch.im/tapir" "cwtch.im/tapir"
"cwtch.im/tapir/applications" "cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor" "cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "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/log"
"git.openprivacy.ca/openprivacy/libricochet-go/utils" "git.openprivacy.ca/openprivacy/libricochet-go/utils"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"os" "runtime"
"sync"
"testing"
"time" "time"
) )
@ -37,60 +38,82 @@ func (ea SimpleApp) Init(connection tapir.Connection) {
} }
} }
var AuthSuccess = false
// CheckConnection is a simple test that GetConnection is working. // 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 { for {
_, err := service.GetConnection(hostname) _, err := service.GetConnection(hostname)
if err == nil { if err == nil {
log.Infof("Authed!") log.Infof("Authed!")
group.Done()
return return
} }
log.Errorf("Error %v", err) log.Infof("Waiting for Authentication...%v", err)
time.Sleep(time.Second) time.Sleep(time.Second * 5)
} }
} }
func main() { func TestTapir(t *testing.T) {
numRoutinesStart := runtime.NumGoroutine()
log.SetLevel(log.LevelDebug) log.SetLevel(log.LevelDebug)
log.Infof("Number of goroutines open at start: %d", runtime.NumGoroutine())
// Connect to Tor // Connect to Tor
var acn connectivity.ACN var acn connectivity.ACN
acn, _ = connectivity.StartTor("./", "") acn, _ = connectivity.StartTor("./", "")
acn.WaitTillBootstrapped() acn.WaitTillBootstrapped()
// Generate Server Keys // Generate Server Keys
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader) id, sk := primitives.InitializeEphemeral()
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)
// Init the Server running the Simple App. // Init the Server running the Simple App.
var service tapir.Service var service tapir.Service
service = new(tor.BaseOnionService) service = new(tor.BaseOnionService)
service.Init(acn, sk, id) service.Init(acn, sk, &id)
go CheckConnection(service, clienthostname)
service.Listen(SimpleApp{}) // 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) { func genclient(acn connectivity.ACN) (tapir.Service, string) {
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader) id, sk := primitives.InitializeEphemeral()
sk := ed25519.PrivateKey(privateKey)
pk := ed25519.PublicKey(pubkey)
id := identity.InitializeV3("client", &sk, &pk)
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)
return client, utils.GetTorV3Hostname(pk) return client, id.Hostname()
} }
// Client will Connect and launch it's own Echo App goroutine. // 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{}) 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 // 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)) conn, _ := client.GetConnection(utils.GetTorV3Hostname(key))
log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability)) log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability))
AuthSuccess = true
os.Exit(0) group.Done()
} }