From 9c91a4e00040dbe41e10592695fcc7437e611839 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 12 Aug 2019 13:04:39 -0700 Subject: [PATCH] Force Failure if remote attempts to authenticate with a different identity. --- README.md | 2 +- applications/auth.go | 9 ++ applications/auth_test.go | 4 +- networks/tor/BaseOnionService.go | 1 + primitives/3DH.go | 2 +- service.go | 3 +- ...tapir_malicious_remote_integration_test.go | 84 +++++++++++++++++++ 7 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 testing/tapir_malicious_remote_integration_test.go diff --git a/README.md b/README.md index 3625a6b..dbef3fe 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 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** \ No newline at end of file diff --git a/applications/auth.go b/applications/auth.go index 238aaf2..8460afc 100644 --- a/applications/auth.go +++ b/applications/auth.go @@ -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) diff --git a/applications/auth_test.go b/applications/auth_test.go index 26c7884..da1888a 100644 --- a/applications/auth_test.go +++ b/applications/auth_test.go @@ -20,8 +20,8 @@ func (mc *MockConnection) Init(outbound bool) { return } -func (MockConnection) Hostname() string { - panic("implement me") +func (mc MockConnection) Hostname() string { + return mc.id.Hostname() } func (mc MockConnection) IsOutbound() bool { diff --git a/networks/tor/BaseOnionService.go b/networks/tor/BaseOnionService.go index 7dc5ecf..5d1a3ec 100644 --- a/networks/tor/BaseOnionService.go +++ b/networks/tor/BaseOnionService.go @@ -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() { diff --git a/primitives/3DH.go b/primitives/3DH.go index 084a295..a84cbd0 100644 --- a/primitives/3DH.go +++ b/primitives/3DH.go @@ -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 diff --git a/service.go b/service.go index 0fbfcf9..e85333a 100644 --- a/service.go +++ b/service.go @@ -113,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() } @@ -162,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 } diff --git a/testing/tapir_malicious_remote_integration_test.go b/testing/tapir_malicious_remote_integration_test.go new file mode 100644 index 0000000..e18a56d --- /dev/null +++ b/testing/tapir_malicious_remote_integration_test.go @@ -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() +}