Merge "runtime/internal/flow/conn: Send Mtu and SharedTokens as setup options. https://github.com/vanadium/issues/issues/998"
diff --git a/runtime/internal/flow/conn/auth.go b/runtime/internal/flow/conn/auth.go
index 93396d7..8c9b5ba 100644
--- a/runtime/internal/flow/conn/auth.go
+++ b/runtime/internal/flow/conn/auth.go
@@ -32,7 +32,7 @@
 )
 
 func (c *Conn) dialHandshake(ctx *context.T, versions version.RPCVersionRange, auth flow.PeerAuthorizer) error {
-	binding, remoteEndpoint, err := c.setup(ctx, versions)
+	binding, remoteEndpoint, err := c.setup(ctx, versions, true)
 	if err != nil {
 		return err
 	}
@@ -83,7 +83,7 @@
 }
 
 func (c *Conn) acceptHandshake(ctx *context.T, versions version.RPCVersionRange, authorizedPeers []security.BlessingPattern) error {
-	binding, remoteEndpoint, err := c.setup(ctx, versions)
+	binding, remoteEndpoint, err := c.setup(ctx, versions, false)
 	if err != nil {
 		return err
 	}
@@ -110,7 +110,7 @@
 	return err
 }
 
-func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange) ([]byte, naming.Endpoint, error) {
+func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange, dialer bool) ([]byte, naming.Endpoint, error) {
 	pk, sk, err := box.GenerateKey(rand.Reader)
 	if err != nil {
 		return nil, nil, err
@@ -167,6 +167,28 @@
 		return nil, nil, NewErrMissingSetupOption(ctx, "peerNaClPublicKey")
 	}
 	binding := c.mp.setupEncryption(ctx, pk, sk, rSetup.PeerNaClPublicKey)
+	if c.version >= version.RPCVersion14 {
+		// We include the setup messages in the channel binding to prevent attacks
+		// where a man in the middle changes fields in the Setup message (e.g. a
+		// downgrade attack wherein a MITM attacker changes the Version field of
+		// the Setup message to a lower-security version.)
+		// We always put the dialer first in the binding.
+		if dialer {
+			if binding, err = message.Append(ctx, lSetup, nil); err != nil {
+				return nil, nil, err
+			}
+			if binding, err = message.Append(ctx, rSetup, nil); err != nil {
+				return nil, nil, err
+			}
+		} else {
+			if binding, err = message.Append(ctx, rSetup, nil); err != nil {
+				return nil, nil, err
+			}
+			if binding, err = message.Append(ctx, lSetup, nil); err != nil {
+				return nil, nil, err
+			}
+		}
+	}
 	// if we're encapsulated in another flow, tell that flow to stop
 	// encrypting now that we've started.
 	if f, ok := c.mp.rw.(*flw); ok {
@@ -180,7 +202,10 @@
 	if dialer {
 		tag = authAcceptorTag
 	}
-	var rauth *message.Auth
+	var (
+		rauth *message.Auth
+		err   error
+	)
 	for {
 		msg, err := c.mp.readMsg(ctx)
 		if err != nil {
@@ -199,7 +224,6 @@
 	var rBlessings security.Blessings
 	var rDischarges map[string]security.Discharge
 	if rauth.BlessingsKey != 0 {
-		var err error
 		// TODO(mattr): Make sure we cancel out of this at some point.
 		rBlessings, rDischarges, err = c.blessingsFlow.getRemote(ctx, rauth.BlessingsKey, rauth.DischargeKey)
 		if err != nil {
diff --git a/runtime/internal/flow/conn/conn.go b/runtime/internal/flow/conn/conn.go
index 59a17fc..55a8197 100644
--- a/runtime/internal/flow/conn/conn.go
+++ b/runtime/internal/flow/conn/conn.go
@@ -403,6 +403,12 @@
 	return blessings
 }
 
+func (c *Conn) RemoteDischarges() map[string]security.Discharge {
+	// Its safe to ignore this error. It means that this conn is closed.
+	_, discharges, _ := c.blessingsFlow.getLatestRemote(nil, c.rBKey)
+	return discharges
+}
+
 // CommonVersion returns the RPCVersion negotiated between the local and remote endpoints.
 func (c *Conn) CommonVersion() version.RPCVersion { return c.version }
 
diff --git a/runtime/internal/flow/flowtest/flowtest.go b/runtime/internal/flow/flowtest/flowtest.go
index 5c3a996..5685a03 100644
--- a/runtime/internal/flow/flowtest/flowtest.go
+++ b/runtime/internal/flow/flowtest/flowtest.go
@@ -5,6 +5,7 @@
 package flowtest
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
@@ -53,3 +54,42 @@
 	security.Blessings, map[string]security.Discharge, error) {
 	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
 }
+
+type peersAuthorizer []string
+
+func NewPeerAuthorizer(names []string) flow.PeerAuthorizer {
+	if len(names) == 0 {
+		return AllowAllPeersAuthorizer{}
+	}
+	return peersAuthorizer(names)
+}
+
+func (p peersAuthorizer) AuthorizePeer(
+	ctx *context.T,
+	localEndpoint, remoteEndpoint naming.Endpoint,
+	remoteBlessings security.Blessings,
+	remoteDischarges map[string]security.Discharge,
+) ([]string, []security.RejectedBlessing, error) {
+	call := security.NewCall(&security.CallParams{
+		Timestamp:        time.Now(),
+		LocalPrincipal:   v23.GetPrincipal(ctx),
+		LocalEndpoint:    localEndpoint,
+		RemoteBlessings:  remoteBlessings,
+		RemoteDischarges: remoteDischarges,
+		RemoteEndpoint:   remoteEndpoint,
+	})
+	peerNames, rejectedPeerNames := security.RemoteBlessingNames(ctx, call)
+	ctx.Infof("validating against %v", peerNames)
+	for _, pattern := range p {
+		if security.BlessingPattern(pattern).MatchedBy(peerNames...) {
+			return peerNames, rejectedPeerNames, nil
+		}
+		ctx.Infof("no match %v", pattern)
+	}
+	return peerNames, rejectedPeerNames, fmt.Errorf("not authorized")
+}
+
+func (peersAuthorizer) BlessingsForPeer(ctx *context.T, _ []string) (
+	security.Blessings, map[string]security.Discharge, error) {
+	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
diff --git a/runtime/internal/flow/manager/conncache.go b/runtime/internal/flow/manager/conncache.go
index 78a5365..0e4467d 100644
--- a/runtime/internal/flow/manager/conncache.go
+++ b/runtime/internal/flow/manager/conncache.go
@@ -5,10 +5,13 @@
 package manager
 
 import (
+	"bytes"
+	"fmt"
 	"sort"
 	"sync"
 
 	"v.io/v23/context"
+	"v.io/v23/flow"
 	"v.io/v23/naming"
 	"v.io/x/ref/runtime/internal/flow/conn"
 )
@@ -26,11 +29,10 @@
 }
 
 type connEntry struct {
-	conn          *conn.Conn
-	rid           naming.RoutingID
-	addrKey       string
-	blessingNames []string
-	proxy         bool
+	conn    *conn.Conn
+	rid     naming.RoutingID
+	addrKey string
+	proxy   bool
 }
 
 func NewConnCache() *ConnCache {
@@ -46,6 +48,19 @@
 	}
 }
 
+func (c *ConnCache) String() string {
+	buf := &bytes.Buffer{}
+	fmt.Fprintln(buf, "AddressCache:")
+	for k, v := range c.addrCache {
+		fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+	}
+	fmt.Fprintln(buf, "RIDCache:")
+	for k, v := range c.ridCache {
+		fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+	}
+	return buf.String()
+}
+
 // Insert adds conn to the cache, keyed by both (protocol, address) and (routingID)
 // An error will be returned iff the cache has been closed.
 func (c *ConnCache) Insert(conn *conn.Conn, protocol, address string, proxy bool) error {
@@ -62,9 +77,6 @@
 		addrKey: k,
 		proxy:   proxy,
 	}
-	if !entry.proxy {
-		entry.blessingNames = ep.BlessingNames()
-	}
 	if old := c.ridCache[entry.rid]; old != nil {
 		c.unmappedConns[old] = true
 	}
@@ -86,9 +98,6 @@
 		rid:   ep.RoutingID(),
 		proxy: proxy,
 	}
-	if !entry.proxy {
-		entry.blessingNames = ep.BlessingNames()
-	}
 	if old := c.ridCache[entry.rid]; old != nil {
 		c.unmappedConns[old] = true
 	}
@@ -96,19 +105,19 @@
 	return nil
 }
 
-func (c *ConnCache) Find(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) Find(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
 	defer c.mu.Unlock()
 	c.mu.Lock()
 	if c.addrCache == nil {
 		return nil, NewErrCacheClosed(nil)
 	}
 	if rid != naming.NullRoutingID {
-		if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+		if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
 			return entry, nil
 		}
 	}
 	k := key(protocol, address)
-	return c.removeUndialable(c.addrCache[k], blessingNames), nil
+	return c.removeUndialable(ctx, c.addrCache[k], auth), nil
 }
 
 // ReservedFind returns a Conn based on the input remoteEndpoint.
@@ -120,14 +129,14 @@
 // the arguments provided to ReservedFind.
 // All new ReservedFind calls for the (protocol, address) will Block
 // until the corresponding Unreserve call is made.
-func (c *ConnCache) ReservedFind(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) ReservedFind(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
 	defer c.mu.Unlock()
 	c.mu.Lock()
 	if c.addrCache == nil {
 		return nil, NewErrCacheClosed(nil)
 	}
 	if rid != naming.NullRoutingID {
-		if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+		if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
 			return entry, nil
 		}
 	}
@@ -139,7 +148,7 @@
 		}
 	}
 	c.started[k] = true
-	return c.removeUndialable(c.addrCache[k], blessingNames), nil
+	return c.removeUndialable(ctx, c.addrCache[k], auth), nil
 }
 
 // Unreserve marks the status of the (protocol, address) as no longer started, and
@@ -166,8 +175,8 @@
 }
 
 // removeUndialable filters connections that are closed, lameducked, or non-proxied
-// connections whose blessings dont match blessingNames.
-func (c *ConnCache) removeUndialable(e *connEntry, blessingNames []string) *conn.Conn {
+// connections that do not authorize.
+func (c *ConnCache) removeUndialable(ctx *context.T, e *connEntry, auth flow.PeerAuthorizer) *conn.Conn {
 	if e == nil {
 		return nil
 	}
@@ -179,8 +188,15 @@
 		}
 		return nil
 	}
-	if !e.proxy && len(blessingNames) > 0 && !matchBlessings(e.blessingNames, blessingNames) {
-		return nil
+	if !e.proxy && auth != nil {
+		_, _, err := auth.AuthorizePeer(ctx,
+			e.conn.LocalEndpoint(),
+			e.conn.RemoteEndpoint(),
+			e.conn.RemoteBlessings(),
+			e.conn.RemoteDischarges())
+		if err != nil {
+			return nil
+		}
 	}
 	return e.conn
 }
@@ -200,7 +216,7 @@
 	err := NewErrConnKilledToFreeResources(ctx)
 	pq := make(connEntries, 0, len(c.ridCache))
 	for _, e := range c.ridCache {
-		if c.removeUndialable(e, nil) == nil {
+		if c.removeUndialable(ctx, e, nil) == nil {
 			continue
 		}
 		if e.conn.IsEncapsulated() {
diff --git a/runtime/internal/flow/manager/conncache_test.go b/runtime/internal/flow/manager/conncache_test.go
index bf1a739..260a4a4 100644
--- a/runtime/internal/flow/manager/conncache_test.go
+++ b/runtime/internal/flow/manager/conncache_test.go
@@ -14,16 +14,18 @@
 	"v.io/v23/flow"
 	"v.io/v23/naming"
 	"v.io/v23/rpc/version"
+	"v.io/v23/security"
 	connpackage "v.io/x/ref/runtime/internal/flow/conn"
 	"v.io/x/ref/runtime/internal/flow/flowtest"
 	inaming "v.io/x/ref/runtime/internal/naming"
 	_ "v.io/x/ref/runtime/protocols/local"
+	"v.io/x/ref/test"
 	"v.io/x/ref/test/goroutines"
 )
 
 func TestCache(t *testing.T) {
 	defer goroutines.NoLeaks(t, leakWaitTime)()
-	ctx, shutdown := v23.Init()
+	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
 	c := NewConnCache()
@@ -31,8 +33,10 @@
 		Protocol:  "tcp",
 		Address:   "127.0.0.1:1111",
 		RID:       naming.FixedRoutingID(0x5555),
-		Blessings: []string{"A", "B", "C"},
+		Blessings: unionBlessing(ctx, "A", "B", "C"),
 	}
+
+	auth := flowtest.NewPeerAuthorizer(remote.Blessings)
 	caf := makeConnAndFlow(t, ctx, remote)
 	defer caf.stop(ctx)
 	conn := caf.c
@@ -40,32 +44,32 @@
 		t.Fatal(err)
 	}
 	// We should be able to find the conn in the cache.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != conn {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, auth); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
 	// Changing the protocol should fail.
-	if got, err := c.ReservedFind("wrong", remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, "wrong", remote.Address, naming.NullRoutingID, auth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve("wrong", remote.Address)
 	// Changing the address should fail.
-	if got, err := c.ReservedFind(remote.Protocol, "wrong", naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, "wrong", naming.NullRoutingID, auth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(remote.Protocol, "wrong")
 	// Changing the blessingNames should fail.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
-	// But finding a set of blessings that has at least on blessings in remote.Blessings should succeed.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"foo", "A"}); err != nil || got != conn {
+	// But finding a set of blessings that has at least one blessings in remote.Blessings should succeed.
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"foo", remote.Blessings[0]})); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
 	// Finding by routing ID should work.
-	if got, err := c.ReservedFind("wrong", "wrong", remote.RID, remote.Blessings); err != nil || got != conn {
+	if got, err := c.ReservedFind(ctx, "wrong", "wrong", remote.RID, auth); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve("wrong", "wrong")
@@ -76,7 +80,7 @@
 		Protocol:  "tcp",
 		Address:   "127.0.0.1:2222",
 		RID:       naming.FixedRoutingID(0x5555),
-		Blessings: []string{"A", "B", "C"},
+		Blessings: unionBlessing(ctx, "A", "B", "C"),
 	}
 	caf = makeConnAndFlow(t, ctx, proxyep)
 	defer caf.stop(ctx)
@@ -85,7 +89,7 @@
 		t.Fatal(err)
 	}
 	// Wrong blessingNames should still work
-	if got, err := c.ReservedFind(proxyep.Protocol, proxyep.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != proxyConn {
+	if got, err := c.ReservedFind(ctx, proxyep.Protocol, proxyep.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != proxyConn {
 		t.Errorf("got %v, want %v, err: %v", got, proxyConn, err)
 	}
 	c.Unreserve(proxyep.Protocol, proxyep.Address)
@@ -95,20 +99,21 @@
 		Protocol:  "ridonly",
 		Address:   "ridonly",
 		RID:       naming.FixedRoutingID(0x1111),
-		Blessings: []string{"ridonly"},
+		Blessings: unionBlessing(ctx, "ridonly"),
 	}
+	ridauth := flowtest.NewPeerAuthorizer(ridEP.Blessings)
 	caf = makeConnAndFlow(t, ctx, ridEP)
 	defer caf.stop(ctx)
 	ridConn := caf.c
 	if err := c.InsertWithRoutingID(ridConn, false); err != nil {
 		t.Fatal(err)
 	}
-	if got, err := c.ReservedFind(ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridEP.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridauth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(ridEP.Protocol, ridEP.Address)
 	// Finding by routing ID should work.
-	if got, err := c.ReservedFind("wrong", "wrong", ridEP.RID, ridEP.Blessings); err != nil || got != ridConn {
+	if got, err := c.ReservedFind(ctx, "wrong", "wrong", ridEP.RID, ridauth); err != nil || got != ridConn {
 		t.Errorf("got %v, want %v, err: %v", got, ridConn, err)
 	}
 	c.Unreserve("wrong", "wrong")
@@ -117,20 +122,21 @@
 		Protocol:  "other",
 		Address:   "other",
 		RID:       naming.FixedRoutingID(0x2222),
-		Blessings: []string{"other"},
+		Blessings: unionBlessing(ctx, "other"),
 	}
+	otherAuth := flowtest.NewPeerAuthorizer(otherEP.Blessings)
 	caf = makeConnAndFlow(t, ctx, otherEP)
 	defer caf.stop(ctx)
 	otherConn := caf.c
 
 	// Looking up a not yet inserted endpoint should fail.
-	if got, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	// Looking it up again should block until a matching Unreserve call is made.
 	ch := make(chan *connpackage.Conn, 1)
 	go func(ch chan *connpackage.Conn) {
-		conn, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings)
+		conn, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -177,7 +183,7 @@
 
 func TestLRU(t *testing.T) {
 	defer goroutines.NoLeaks(t, leakWaitTime)()
-	ctx, shutdown := v23.Init()
+	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
 	// Ensure that the least recently created conns are killed by KillConnections.
@@ -202,13 +208,14 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
-			t.Errorf("conn %v should still be in cache", conn)
+		if !isInCache(t, ctx, c, conn.c) {
+			t.Errorf("conn %v(%p) should still be in cache:\n%s",
+				conn.c.RemoteEndpoint(), conn.c, c)
 		}
 	}
 	for _, conn := range conns[:3] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
@@ -238,13 +245,13 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
+		if !isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should still be in cache", conn)
 		}
 	}
 	for _, conn := range conns[7:] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
@@ -274,21 +281,22 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
-			t.Errorf("conn %v should still be in cache", conn)
+		if !isInCache(t, ctx, c, conn.c) {
+			t.Errorf("conn %v(%p) should still be in cache:\n%s",
+				conn.c.RemoteEndpoint(), conn.c, c)
 		}
 	}
 	for _, conn := range conns[7:] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
 }
 
-func isInCache(t *testing.T, c *ConnCache, conn *connpackage.Conn) bool {
+func isInCache(t *testing.T, ctx *context.T, c *ConnCache, conn *connpackage.Conn) bool {
 	rep := conn.RemoteEndpoint()
-	rfconn, err := c.ReservedFind(rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), rep.BlessingNames())
+	rfconn, err := c.ReservedFind(ctx, rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), flowtest.NewPeerAuthorizer(rep.BlessingNames()))
 	if err != nil {
 		t.Error(err)
 	}
@@ -392,3 +400,25 @@
 	}()
 	return nil
 }
+
+func unionBlessing(ctx *context.T, names ...string) []string {
+	principal := v23.GetPrincipal(ctx)
+	blessings := make([]security.Blessings, len(names))
+	for i, name := range names {
+		var err error
+		if blessings[i], err = principal.BlessSelf(name); err != nil {
+			panic(err)
+		}
+	}
+	union, err := security.UnionOfBlessings(blessings...)
+	if err != nil {
+		panic(err)
+	}
+	if err := security.AddToRoots(principal, union); err != nil {
+		panic(err)
+	}
+	if err := principal.BlessingStore().SetDefault(union); err != nil {
+		panic(err)
+	}
+	return security.BlessingNames(principal, principal.BlessingStore().Default())
+}
diff --git a/runtime/internal/flow/manager/manager.go b/runtime/internal/flow/manager/manager.go
index 8ac1742..e35abc8 100644
--- a/runtime/internal/flow/manager/manager.go
+++ b/runtime/internal/flow/manager/manager.go
@@ -657,8 +657,8 @@
 
 func (m *manager) internalDial(ctx *context.T, remote naming.Endpoint, auth flow.PeerAuthorizer, channelTimeout time.Duration, proxy bool) (flow.Flow, error) {
 	// Fast path, look for the conn based on unresolved network, address, and routingId first.
-	addr, rid, blessingNames := remote.Addr(), remote.RoutingID(), remote.BlessingNames()
-	c, err := m.cache.Find(addr.Network(), addr.String(), rid, blessingNames)
+	addr, rid := remote.Addr(), remote.RoutingID()
+	c, err := m.cache.Find(ctx, addr.Network(), addr.String(), rid, auth)
 	if err != nil {
 		return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
 	}
@@ -680,7 +680,7 @@
 		if err != nil {
 			return nil, iflow.MaybeWrapError(flow.ErrResolveFailed, ctx, err)
 		}
-		c, err = m.cache.ReservedFind(network, address, rid, blessingNames)
+		c, err = m.cache.ReservedFind(ctx, network, address, rid, auth)
 		if err != nil {
 			return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
 		}
diff --git a/runtime/internal/rpc/test/full_test.go b/runtime/internal/rpc/test/full_test.go
index 855c601..85d9c47 100644
--- a/runtime/internal/rpc/test/full_test.go
+++ b/runtime/internal/rpc/test/full_test.go
@@ -19,11 +19,13 @@
 
 	"v.io/v23"
 	"v.io/v23/context"
+	"v.io/v23/flow"
 	"v.io/v23/flow/message"
 	"v.io/v23/i18n"
 	"v.io/v23/naming"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
+	"v.io/v23/rpc/version"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/vdl"
@@ -36,6 +38,7 @@
 	"v.io/x/ref/runtime/internal/lib/tcputil"
 	inaming "v.io/x/ref/runtime/internal/naming"
 	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/protocols/debug"
 	"v.io/x/ref/test"
 	"v.io/x/ref/test/testutil"
 )
@@ -1262,3 +1265,48 @@
 		time.Sleep(50 * time.Millisecond)
 	}
 }
+
+type manInMiddleConn struct {
+	flow.Conn
+	ctx *context.T
+}
+
+// manInMiddleConn changes the versions in any setup message sent over the wire.
+func (c *manInMiddleConn) ReadMsg() ([]byte, error) {
+	b, err := c.Conn.ReadMsg()
+	if len(b) > 0 {
+		m, _ := message.Read(c.ctx, b)
+		switch msg := m.(type) {
+		case *message.Setup:
+			// The malicious man in the middle changes the max version to a bad version.
+			msg.Versions = version.RPCVersionRange{Min: version.RPCVersion10, Max: 100}
+			b, err = message.Append(c.ctx, msg, nil)
+		}
+	}
+	return b, err
+}
+
+func TestSetupAttack(t *testing.T) {
+	ctx, shutdown := test.V23InitWithMounttable()
+	defer shutdown()
+
+	ctx = debug.WithFilter(ctx, func(c flow.Conn) flow.Conn {
+		return &manInMiddleConn{c, ctx}
+	})
+
+	ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{{Protocol: "debug", Address: "tcp/127.0.0.1:0"}},
+	})
+	ctx, cancel := context.WithCancel(ctx)
+	_, server, err := v23.WithNewServer(ctx, "mountpoint/server", &testServer{}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() { <-server.Closed() }()
+	defer cancel()
+	// Connection establishment should fail during the RPC because the channel binding
+	// check should fail since the Setup message has been altered.
+	if err := v23.GetClient(ctx).Call(ctx, "mountpoint/server", "Closure", nil, nil, options.NoRetry{}); err == nil {
+		t.Errorf("expected error but got <nil>")
+	}
+}
diff --git a/runtime/internal/rpc/version/version.go b/runtime/internal/rpc/version/version.go
index f08e1ef..25d02ef 100644
--- a/runtime/internal/rpc/version/version.go
+++ b/runtime/internal/rpc/version/version.go
@@ -8,16 +8,10 @@
 	"fmt"
 
 	"v.io/v23/rpc/version"
-	"v.io/v23/verror"
 	"v.io/x/lib/metadata"
 )
 
-// Range represents a range of RPC versions.
-type Range struct {
-	Min, Max version.RPCVersion
-}
-
-// SupportedRange represents the range of protocol verions supported by this
+// Supported represents the range of protocol verions supported by this
 // implementation.
 //
 // Max is incremented whenever we make a protocol change that's not both forward
@@ -25,77 +19,9 @@
 //
 // Min is incremented whenever we want to remove support for old protocol
 // versions.
-var SupportedRange = &Range{Min: version.RPCVersion10, Max: version.RPCVersion12}
-var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion13}
+var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion14}
 
 func init() {
-	metadata.Insert("v23.RPCVersionMax", fmt.Sprint(SupportedRange.Max))
-	metadata.Insert("v23.RPCVersionMin", fmt.Sprint(SupportedRange.Min))
-}
-
-const pkgPath = "v.io/x/ref/runtime/internal/rpc/version"
-
-func reg(id, msg string) verror.IDAction {
-	return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
-}
-
-var (
-	// These errors are intended to be used as arguments to higher
-	// level errors and hence {1}{2} is omitted from their format
-	// strings to avoid repeating these n-times in the final error
-	// message visible to the user.
-	ErrNoCompatibleVersion = reg(".errNoCompatibleVersionErr", "no compatible RPC version available{:3} not in range {4}..{5}")
-	ErrUnknownVersion      = reg(".errUnknownVersionErr", "there was not enough information to determine a version")
-	ErrDeprecatedVersion   = reg(".errDeprecatedVersionError", "some of the provided version information is deprecated")
-)
-
-// IsVersionError returns true if err is a versioning related error.
-func IsVersionError(err error) bool {
-	id := verror.ErrorID(err)
-	return id == ErrNoCompatibleVersion.ID || id == ErrUnknownVersion.ID || id == ErrDeprecatedVersion.ID
-}
-
-// intersectRanges finds the intersection between ranges
-// supported by two endpoints.  We make an assumption here that if one
-// of the endpoints has an UnknownVersion we assume it has the same
-// extent as the other endpoint. If both endpoints have Unknown for a
-// version number, an error is produced.
-// For example:
-//   a == (2, 4) and b == (Unknown, Unknown), intersect(a,b) == (2, 4)
-//   a == (2, Unknown) and b == (3, 4), intersect(a,b) == (3, 4)
-func intersectRanges(amin, amax, bmin, bmax version.RPCVersion) (min, max version.RPCVersion, err error) {
-	// TODO(mattr): this may be incorrect.  Ensure that when we talk to a server who
-	// advertises (5,8) and we support (5, 9) but v5 EPs (so we may get d, d here) that
-	// we use v8 and don't send setupVC.
-	d := version.DeprecatedRPCVersion
-	if amin == d || amax == d || bmin == d || bmax == d {
-		return d, d, verror.New(ErrDeprecatedVersion, nil)
-	}
-
-	u := version.UnknownRPCVersion
-
-	min = amin
-	if min == u || (bmin != u && bmin > min) {
-		min = bmin
-	}
-	max = amax
-	if max == u || (bmax != u && bmax < max) {
-		max = bmax
-	}
-
-	if min == u || max == u {
-		err = verror.New(ErrUnknownVersion, nil)
-	} else if min > max {
-		err = verror.New(ErrNoCompatibleVersion, nil, u, min, max)
-	}
-	return
-}
-
-func (r1 *Range) Intersect(r2 *Range) (*Range, error) {
-	min, max, err := intersectRanges(r1.Min, r1.Max, r2.Min, r2.Max)
-	if err != nil {
-		return nil, err
-	}
-	r := &Range{Min: min, Max: max}
-	return r, nil
+	metadata.Insert("v23.RPCVersionMax", fmt.Sprint(Supported.Max))
+	metadata.Insert("v23.RPCVersionMin", fmt.Sprint(Supported.Min))
 }
diff --git a/runtime/internal/rpc/version/version_test.go b/runtime/internal/rpc/version/version_test.go
deleted file mode 100644
index 652baf8..0000000
--- a/runtime/internal/rpc/version/version_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-package version
-
-import (
-	"testing"
-
-	"v.io/v23/rpc/version"
-	"v.io/v23/verror"
-)
-
-func TestIntersect(t *testing.T) {
-	type testCase struct {
-		localMin, localMax   version.RPCVersion
-		remoteMin, remoteMax version.RPCVersion
-		expected             *Range
-		expectedErr          verror.IDAction
-	}
-	tests := []testCase{
-		{0, 0, 0, 0, nil, ErrUnknownVersion},
-		{0, 2, 3, 4, nil, ErrNoCompatibleVersion},
-		{3, 4, 0, 2, nil, ErrNoCompatibleVersion},
-		{0, 6, 6, 7, nil, ErrNoCompatibleVersion},
-		{0, 3, 3, 5, &Range{3, 3}, verror.ErrUnknown},
-		{0, 3, 2, 4, &Range{2, 3}, verror.ErrUnknown},
-		{2, 4, 2, 4, &Range{2, 4}, verror.ErrUnknown},
-		{4, 4, 4, 4, &Range{4, 4}, verror.ErrUnknown},
-	}
-	for _, tc := range tests {
-		local := &Range{
-			Min: tc.localMin,
-			Max: tc.localMax,
-		}
-		remote := &Range{
-			Min: tc.remoteMin,
-			Max: tc.remoteMax,
-		}
-		intersection, err := local.Intersect(remote)
-
-		if (tc.expected != nil && *tc.expected != *intersection) ||
-			(err != nil && verror.ErrorID(err) != tc.expectedErr.ID) {
-			t.Errorf("Unexpected result for local: %v, remote: %v.  Got (%v, %v) wanted (%v, %v)",
-				local, remote, intersection, err,
-				tc.expected, tc.expectedErr)
-		}
-		if err != nil {
-			t.Logf("%s", err)
-		}
-	}
-}