"ref/profiles/iternal/ipc": Support LocalDischarges
This CL accompanies: https://vanadium-review.googlesource.com/#/c/6765/
MultiPart: 2/2
Change-Id: Ia41e9c9c5fea07d16d2b56950e8d2ee0740b080d
diff --git a/profiles/internal/ipc/full_test.go b/profiles/internal/ipc/full_test.go
index 06ed262..bef9dd3 100644
--- a/profiles/internal/ipc/full_test.go
+++ b/profiles/internal/ipc/full_test.go
@@ -110,7 +110,21 @@
}
func (*testServer) EchoBlessings(call ipc.ServerCall) (server, client string, _ error) {
- local, _ := call.LocalBlessings().ForCall(call)
+ // TODO(ataly, ashankar): This is a HACK to create a context
+ // that can be used to validate caveats on the LocalBlessings.
+ // It will go away once we have a security.BlessingNames function
+ // that takes a call and an IPCSide argument.
+ localCall := security.NewCall(&security.CallParams{
+ Context: call.Context(),
+ LocalPrincipal: call.LocalPrincipal(),
+ RemoteBlessings: call.LocalBlessings(),
+ RemoteDischarges: call.LocalDischarges(),
+ LocalEndpoint: call.LocalEndpoint(),
+ RemoteEndpoint: call.RemoteEndpoint(),
+ Method: call.Method(),
+ Suffix: call.Suffix(),
+ })
+ local, _ := call.LocalBlessings().ForCall(localCall)
remote, _ := call.RemoteBlessings().ForCall(call)
return fmt.Sprintf("%v", local), fmt.Sprintf("%v", remote), nil
}
@@ -150,10 +164,34 @@
type testServerAuthorizer struct{}
func (testServerAuthorizer) Authorize(c security.Call) error {
- if c.Method() != "Unauthorized" {
- return nil
+ // Verify that the Call object seen by the authorizer
+ // has the necessary fields.
+ lb := c.LocalBlessings()
+ if lb.IsZero() {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalBlessings", c)
}
- return fmt.Errorf("testServerAuthorizer denied access")
+ if tpcavs := lb.ThirdPartyCaveats(); len(tpcavs) > 0 && c.LocalDischarges() == nil {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalDischarges even when LocalBlessings have third-party caveats", c)
+
+ }
+ if c.LocalPrincipal() == nil {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalPrincipal", c)
+ }
+ if c.Method() == "" {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no Method", c)
+ }
+ if c.LocalEndpoint() == nil {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalEndpoint", c)
+ }
+ if c.RemoteEndpoint() == nil {
+ return fmt.Errorf("testServerAuthorzer: Call object %v has no RemoteEndpoint", c)
+ }
+
+ // Do not authorize the method "Unauthorized".
+ if c.Method() == "Unauthorized" {
+ return fmt.Errorf("testServerAuthorizer denied access")
+ }
+ return nil
}
type testServerDisp struct{ server interface{} }
@@ -988,7 +1026,7 @@
var (
// Principals
pclient, pserver = tsecurity.NewPrincipal("client"), tsecurity.NewPrincipal("server")
- pdischarger = pserver
+ pdischarger = tsecurity.NewPrincipal("discharger")
now = time.Now()
@@ -999,8 +1037,8 @@
cavOnlyEcho = mkCaveat(security.MethodCaveat("Echo"))
cavExpired = mkCaveat(security.ExpiryCaveat(now.Add(-1 * time.Second)))
// Caveats on blessings to the client: Third-party caveats
- cavTPValid = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/server/discharger", mkCaveat(security.ExpiryCaveat(now.Add(24*time.Hour))))
- cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/server/discharger", mkCaveat(security.ExpiryCaveat(now.Add(-1*time.Second))))
+ cavTPValid = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.ExpiryCaveat(now.Add(24*time.Hour))))
+ cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.ExpiryCaveat(now.Add(-1*time.Second))))
// Client blessings that will be tested.
bServerClientOnlyEcho = bless(pserver, pclient, "onlyecho", cavOnlyEcho)
@@ -1088,11 +1126,14 @@
// And the client needs to recognize the server's and discharger's blessings to decide which of its
// own blessings to share.
pclient.AddToRoots(pserver.BlessingStore().Default())
+ pclient.AddToRoots(pdischarger.BlessingStore().Default())
+ // Set a blessing on the client's blessing store to be presented to the discharge server.
+ pclient.BlessingStore().Set(pclient.BlessingStore().Default(), "discharger")
// tsecurity.NewPrincipal sets up a principal that shares blessings with all servers, undo that.
pclient.BlessingStore().Set(security.Blessings{}, security.AllPrincipals)
- for _, test := range tests {
- name := fmt.Sprintf("%q.%s(%v) by %v", test.name, test.method, test.args, test.blessings)
+ for i, test := range tests {
+ name := fmt.Sprintf("#%d: %q.%s(%v) by %v", i, test.name, test.method, test.args, test.blessings)
client, err := InternalNewClient(mgr, ns, vc.LocalPrincipal{pclient})
if err != nil {
t.Fatalf("InternalNewClient failed: %v", err)
@@ -1209,6 +1250,60 @@
}
}
+func TestServerLocalBlessings(t *testing.T) {
+ var (
+ pprovider, pclient, pserver = tsecurity.NewPrincipal("root"), tsecurity.NewPrincipal(), tsecurity.NewPrincipal()
+ pdischarger = pprovider
+
+ mgr = imanager.InternalNew(naming.FixedRoutingID(0x1111111))
+ ns = tnaming.NewSimpleNamespace()
+
+ tpCav = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.ExpiryCaveat(time.Now().Add(time.Hour))))
+
+ bserver = bless(pprovider, pserver, "server", tpCav)
+ bclient = bless(pprovider, pclient, "client")
+ )
+
+ // Start the server and the discharger.
+ _, server := startServer(t, pserver, mgr, ns, "mountpoint/server", testServerDisp{&testServer{}})
+ defer stopServer(t, server, ns, "mountpoint/server")
+
+ _, dischargeServer := startServer(t, pdischarger, mgr, ns, "mountpoint/dischargeserver", testutil.LeafDispatcher(&dischargeServer{}, &acceptAllAuthorizer{}))
+ defer stopServer(t, dischargeServer, ns, "mountpoint/dischargeserver")
+
+ // Make the client and server principals trust root certificates from
+ // pprovider.
+ pclient.AddToRoots(pprovider.BlessingStore().Default())
+ pserver.AddToRoots(pprovider.BlessingStore().Default())
+
+ // Make the server present bserver to all clients.
+ pserver.BlessingStore().SetDefault(bserver)
+
+ // Make the client present bclient to all servers that are blessed
+ // by pprovider.
+ pclient.BlessingStore().Set(bclient, "root")
+
+ client, err := InternalNewClient(mgr, ns, vc.LocalPrincipal{pclient})
+ if err != nil {
+ t.Fatalf("InternalNewClient failed: %v", err)
+ }
+ defer client.Close()
+
+ call, err := client.StartCall(testContext(), "mountpoint/server/suffix", "EchoBlessings", nil)
+ if err != nil {
+ t.Fatalf("StartCall failed: %v", err)
+ }
+
+ type v []interface{}
+ var gotServer, gotClient string
+ if err := call.Finish(&gotServer, &gotClient); err != nil {
+ t.Fatalf("Finish failed: %v", err)
+ }
+ if wantServer, wantClient := "[root/server]", "[root/client]"; gotServer != wantServer || gotClient != wantClient {
+ t.Fatalf("EchoBlessings: got %v, %v want %v, %v", gotServer, gotClient, wantServer, wantClient)
+ }
+}
+
func TestDischargePurgeFromCache(t *testing.T) {
var (
pserver = tsecurity.NewPrincipal("server")
diff --git a/profiles/internal/ipc/reserved.go b/profiles/internal/ipc/reserved.go
index 95589c5..3eeca20 100644
--- a/profiles/internal/ipc/reserved.go
+++ b/profiles/internal/ipc/reserved.go
@@ -360,6 +360,9 @@
func (c *mutableServerCall) RemoteBlessings() security.Blessings { return c.M.RemoteBlessings }
func (c *mutableServerCall) LocalEndpoint() naming.Endpoint { return c.M.LocalEndpoint }
func (c *mutableServerCall) RemoteEndpoint() naming.Endpoint { return c.M.RemoteEndpoint }
+func (c *mutableServerCall) LocalDischarges() map[string]security.Discharge {
+ return c.M.LocalDischarges
+}
func (c *mutableServerCall) RemoteDischarges() map[string]security.Discharge {
return c.M.RemoteDischarges
}
diff --git a/profiles/internal/ipc/server.go b/profiles/internal/ipc/server.go
index 9439086..7dddf07 100644
--- a/profiles/internal/ipc/server.go
+++ b/profiles/internal/ipc/server.go
@@ -1238,6 +1238,10 @@
// Implementations of ipc.ServerCall methods.
+func (fs *flowServer) LocalDischarges() map[string]security.Discharge {
+ //nologcall
+ return fs.flow.LocalDischarges()
+}
func (fs *flowServer) RemoteDischarges() map[string]security.Discharge {
//nologcall
return fs.discharges
diff --git a/profiles/internal/ipc/stream/model.go b/profiles/internal/ipc/stream/model.go
index b98e3db..630fe1e 100644
--- a/profiles/internal/ipc/stream/model.go
+++ b/profiles/internal/ipc/stream/model.go
@@ -26,7 +26,11 @@
LocalBlessings() security.Blessings
// RemoteBlessings returns the blessings presented by the remote end of the flow during authentication.
RemoteBlessings() security.Blessings
- // RemoteDischarges() returns the discharges presented by the remote end of the flow during authentication.
+ // LocalDischarges returns the discharges presented by the local end of the flow during authentication.
+ //
+ // The discharges are organized in a map keyed by the discharge-identifier.
+ LocalDischarges() map[string]security.Discharge
+ // RemoteDischarges returns the discharges presented by the remote end of the flow during authentication.
//
// The discharges are organized in a map keyed by the discharge-identifier.
RemoteDischarges() map[string]security.Discharge
diff --git a/profiles/internal/ipc/stream/vc/auth.go b/profiles/internal/ipc/stream/vc/auth.go
index beb8773..c467555 100644
--- a/profiles/internal/ipc/stream/vc/auth.go
+++ b/profiles/internal/ipc/stream/vc/auth.go
@@ -28,55 +28,61 @@
errSingleCertificateRequired = errors.New("exactly one X.509 certificate chain with exactly one certificate is required")
)
-// AuthenticateAsServer executes the authentication protocol at the server and
-// returns the blessings used to authenticate the client.
-func AuthenticateAsServer(conn io.ReadWriteCloser, principal security.Principal, server security.Blessings, dc DischargeClient, crypter crypto.Crypter, v version.IPCVersion) (client security.Blessings, err error) {
+// AuthenticateAsServer executes the authentication protocol at the server.
+// It returns the blessings shared by the client, and the discharges shared
+// by the server.
+func AuthenticateAsServer(conn io.ReadWriteCloser, principal security.Principal, server security.Blessings, dc DischargeClient, crypter crypto.Crypter, v version.IPCVersion) (security.Blessings, map[string]security.Discharge, error) {
if server.IsZero() {
- return security.Blessings{}, errors.New("no blessings to present as a server")
+ return security.Blessings{}, nil, errors.New("no blessings to present as a server")
}
- var discharges []security.Discharge
+ var serverDischarges []security.Discharge
if tpcavs := server.ThirdPartyCaveats(); len(tpcavs) > 0 && dc != nil {
- discharges = dc.PrepareDischarges(nil, tpcavs, security.DischargeImpetus{})
+ serverDischarges = dc.PrepareDischarges(nil, tpcavs, security.DischargeImpetus{})
}
- if err = writeBlessings(conn, authServerContextTag, crypter, principal, server, discharges, v); err != nil {
- return
+ if err := writeBlessings(conn, authServerContextTag, crypter, principal, server, serverDischarges, v); err != nil {
+ return security.Blessings{}, nil, err
}
- if client, _, err = readBlessings(conn, authClientContextTag, crypter, v); err != nil {
- return
+ // Note that since the client uses a self-signed blessing to authenticate
+ // during VC setup, it does not share any discharges.
+ client, _, err := readBlessings(conn, authClientContextTag, crypter, v)
+ if err != nil {
+ return security.Blessings{}, nil, err
}
- return
+ return client, mkDischargeMap(serverDischarges), nil
}
-// AuthenticateAsClient executes the authentication protocol at the client and
-// returns the blessings used to authenticate both ends.
+// AuthenticateAsClient executes the authentication protocol at the client.
+// It returns the blessing shared by the server, the blessings shared by the
+// client, and any discharges shared by the server.
//
-// The client will only share its blessings if the server (who shares its blessings first)
-// is authorized as per the authorizer for this RPC.
-func AuthenticateAsClient(conn io.ReadWriteCloser, crypter crypto.Crypter, params security.CallParams, auth *ServerAuthorizer, v version.IPCVersion) (server, client security.Blessings, serverDischarges map[string]security.Discharge, err error) {
- if server, serverDischarges, err = readBlessings(conn, authServerContextTag, crypter, v); err != nil {
- return
+// The client will only share its blessings if the server (who shares its
+// blessings first) is authorized as per the authorizer for this RPC.
+func AuthenticateAsClient(conn io.ReadWriteCloser, crypter crypto.Crypter, params security.CallParams, auth *ServerAuthorizer, v version.IPCVersion) (security.Blessings, security.Blessings, map[string]security.Discharge, error) {
+ server, serverDischarges, err := readBlessings(conn, authServerContextTag, crypter, v)
+ if err != nil {
+ return security.Blessings{}, security.Blessings{}, nil, err
}
// Authorize the server based on the provided authorizer.
if auth != nil {
params.RemoteBlessings = server
params.RemoteDischarges = serverDischarges
- if err = auth.Authorize(params); err != nil {
- return
+ if err := auth.Authorize(params); err != nil {
+ return security.Blessings{}, security.Blessings{}, nil, err
}
}
- // The client shares its blessings at RPC time (as the blessings may vary across
- // RPCs). During VC handshake, the client simply sends a self-signed blessing
- // in order to reveal its public key to the server.
+ // The client shares its blessings at RPC time (as the blessings may vary
+ // across RPCs). During VC handshake, the client simply sends a self-signed
+ // blessing in order to reveal its public key to the server.
principal := params.LocalPrincipal
- client, err = principal.BlessSelf("vcauth")
+ client, err := principal.BlessSelf("vcauth")
if err != nil {
return security.Blessings{}, security.Blessings{}, nil, fmt.Errorf("failed to created self blessing: %v", err)
}
- if err = writeBlessings(conn, authClientContextTag, crypter, principal, client, nil, v); err != nil {
- return
+ if err := writeBlessings(conn, authClientContextTag, crypter, principal, client, nil, v); err != nil {
+ return security.Blessings{}, security.Blessings{}, nil, err
}
- return
+ return server, client, serverDischarges, nil
}
func writeBlessings(w io.Writer, tag []byte, crypter crypto.Crypter, p security.Principal, b security.Blessings, discharges []security.Discharge, v version.IPCVersion) error {
@@ -142,21 +148,25 @@
if err = dec.Decode(&blessings); err != nil {
return noBlessings, nil, err
}
- var discharges map[string]security.Discharge
+ var discharges []security.Discharge
if v >= version.IPCVersion5 {
- var list []security.Discharge
- if err := dec.Decode(&list); err != nil {
+ if err := dec.Decode(&discharges); err != nil {
return noBlessings, nil, err
}
- if len(list) > 0 {
- discharges = make(map[string]security.Discharge)
- for _, d := range list {
- discharges[d.ID()] = d
- }
- }
}
if !sig.Verify(blessings.PublicKey(), append(tag, crypter.ChannelBinding()...)) {
return noBlessings, nil, errInvalidSignatureInMessage
}
- return blessings, discharges, nil
+ return blessings, mkDischargeMap(discharges), nil
+}
+
+func mkDischargeMap(discharges []security.Discharge) map[string]security.Discharge {
+ if len(discharges) == 0 {
+ return nil
+ }
+ m := make(map[string]security.Discharge, len(discharges))
+ for _, d := range discharges {
+ m[d.ID()] = d
+ }
+ return m
}
diff --git a/profiles/internal/ipc/stream/vc/flow.go b/profiles/internal/ipc/stream/vc/flow.go
index e57ddc7..da1d64a 100644
--- a/profiles/internal/ipc/stream/vc/flow.go
+++ b/profiles/internal/ipc/stream/vc/flow.go
@@ -18,6 +18,7 @@
LocalPrincipal() security.Principal
LocalBlessings() security.Blessings
RemoteBlessings() security.Blessings
+ LocalDischarges() map[string]security.Discharge
RemoteDischarges() map[string]security.Discharge
}
diff --git a/profiles/internal/ipc/stream/vc/listener_test.go b/profiles/internal/ipc/stream/vc/listener_test.go
index f240b3f..15a3bb5 100644
--- a/profiles/internal/ipc/stream/vc/listener_test.go
+++ b/profiles/internal/ipc/stream/vc/listener_test.go
@@ -24,6 +24,7 @@
func (*noopFlow) LocalPrincipal() security.Principal { return nil }
func (*noopFlow) LocalBlessings() security.Blessings { return security.Blessings{} }
func (*noopFlow) RemoteBlessings() security.Blessings { return security.Blessings{} }
+func (*noopFlow) LocalDischarges() map[string]security.Discharge { return nil }
func (*noopFlow) RemoteDischarges() map[string]security.Discharge { return nil }
func (*noopFlow) SetDeadline(<-chan struct{}) {}
func (*noopFlow) VCDataCache() stream.VCDataCache { return nil }
diff --git a/profiles/internal/ipc/stream/vc/vc.go b/profiles/internal/ipc/stream/vc/vc.go
index 15cf5c2..0860870 100644
--- a/profiles/internal/ipc/stream/vc/vc.go
+++ b/profiles/internal/ipc/stream/vc/vc.go
@@ -56,7 +56,8 @@
localEP, remoteEP naming.Endpoint
localPrincipal security.Principal
localBlessings, remoteBlessings security.Blessings
- remoteDischarges map[string]security.Discharge
+ localDischarges map[string]security.Discharge // Discharges shared by the local end of the VC.
+ remoteDischarges map[string]security.Discharge // Discharges shared by the remote end of the VC.
pool *iobuf.Pool
reserveBytes uint
@@ -589,7 +590,8 @@
vc.mu.Lock()
vc.authFID = vc.findFlowLocked(authConn)
vc.mu.Unlock()
- rBlessings, err := AuthenticateAsServer(authConn, principal, lBlessings, dischargeClient, crypter, vc.version)
+
+ rBlessings, lDischarges, err := AuthenticateAsServer(authConn, principal, lBlessings, dischargeClient, crypter, vc.version)
if err != nil {
authConn.Close()
sendErr(fmt.Errorf("authentication failed: %v", err))
@@ -601,6 +603,7 @@
vc.localPrincipal = principal
vc.localBlessings = lBlessings
vc.remoteBlessings = rBlessings
+ vc.localDischarges = lDischarges
close(vc.acceptHandshakeDone)
vc.acceptHandshakeDone = nil
vc.mu.Unlock()
@@ -635,6 +638,17 @@
vlog.Errorf("encoding discharges on VC %v failed: %v", vc, err)
return
}
+ if len(discharges) == 0 {
+ continue
+ }
+ vc.mu.Lock()
+ if vc.localDischarges == nil {
+ vc.localDischarges = make(map[string]security.Discharge)
+ }
+ for _, d := range discharges {
+ vc.localDischarges[d.ID()] = d
+ }
+ vc.mu.Unlock()
case <-vc.closeCh:
vlog.VI(3).Infof("closing sendDischargesLoop on VC %v", vc)
return
@@ -676,7 +690,13 @@
vlog.VI(3).Infof("decoding discharges on %v failed: %v", vc, err)
return
}
+ if len(discharges) == 0 {
+ continue
+ }
vc.mu.Lock()
+ if vc.remoteDischarges == nil {
+ vc.remoteDischarges = make(map[string]security.Discharge)
+ }
for _, d := range discharges {
vc.remoteDischarges[d.ID()] = d
}
@@ -761,6 +781,19 @@
return vc.remoteBlessings
}
+// LocalDischarges returns the discharges presented by the local end of the VC during
+// authentication.
+func (vc *VC) LocalDischarges() map[string]security.Discharge {
+ vc.mu.Lock()
+ defer vc.mu.Unlock()
+ vc.waitForHandshakeLocked()
+ if len(vc.localDischarges) == 0 {
+ return nil
+ }
+ // Return a copy of the map to prevent racy reads.
+ return copyDischargeMap(vc.localDischarges)
+}
+
// RemoteDischarges returns the discharges presented by the remote end of the VC during
// authentication.
func (vc *VC) RemoteDischarges() map[string]security.Discharge {
@@ -770,12 +803,8 @@
if len(vc.remoteDischarges) == 0 {
return nil
}
- // Copy the map to prevent racy reads.
- ret := make(map[string]security.Discharge)
- for k, v := range vc.remoteDischarges {
- ret[k] = v
- }
- return ret
+ // Return a copy of the map to prevent racy reads.
+ return copyDischargeMap(vc.remoteDischarges)
}
// waitForHandshakeLocked blocks until an in-progress handshake (encryption
@@ -832,3 +861,12 @@
func (r readHandlerImpl) HandleRead(bytes uint) {
r.vc.helper.AddReceiveBuffers(r.vc.vci, r.fid, bytes)
}
+
+func copyDischargeMap(m map[string]security.Discharge) map[string]security.Discharge {
+ ret := make(map[string]security.Discharge)
+ for id, d := range m {
+ ret[id] = d
+ }
+ return ret
+
+}
diff --git a/profiles/internal/ipc/stream/vif/auth.go b/profiles/internal/ipc/stream/vif/auth.go
index 1edf8e1..62914ab 100644
--- a/profiles/internal/ipc/stream/vif/auth.go
+++ b/profiles/internal/ipc/stream/vif/auth.go
@@ -180,7 +180,7 @@
c := crypto.NewControlCipherIPC6(&box.PublicKey, &pvt.naclBoxPrivateKey, true)
sconn := newSetupConn(writer, reader, c)
// TODO(jyh): act upon authentication results.
- _, err := vc.AuthenticateAsServer(sconn, principal, lBlessings, dc, crypto.NewNullCrypter(), version)
+ _, _, err := vc.AuthenticateAsServer(sconn, principal, lBlessings, dc, crypto.NewNullCrypter(), version)
if err != nil {
return nil, fmt.Errorf("authentication failed: %v", err)
}
diff --git a/profiles/internal/ipc/testutil_test.go b/profiles/internal/ipc/testutil_test.go
index 5bff981..533d8d8 100644
--- a/profiles/internal/ipc/testutil_test.go
+++ b/profiles/internal/ipc/testutil_test.go
@@ -87,6 +87,7 @@
func (c *mockSecurityContext) Method() string { return "" }
func (c *mockSecurityContext) MethodTags() []*vdl.Value { return nil }
func (c *mockSecurityContext) Suffix() string { return "" }
+func (c *mockSecurityContext) LocalDischarges() map[string]security.Discharge { return nil }
func (c *mockSecurityContext) RemoteDischarges() map[string]security.Discharge { return nil }
func (c *mockSecurityContext) LocalEndpoint() naming.Endpoint { return nil }
func (c *mockSecurityContext) RemoteEndpoint() naming.Endpoint { return nil }
diff --git a/services/wsprd/app/app.go b/services/wsprd/app/app.go
index cdbfded..ad05bb6 100644
--- a/services/wsprd/app/app.go
+++ b/services/wsprd/app/app.go
@@ -391,6 +391,7 @@
func (l *localCall) MethodTags() []*vdl.Value { return l.tags }
func (l *localCall) Name() string { return l.vrpc.Name }
func (l *localCall) Suffix() string { return "" }
+func (l *localCall) LocalDischarges() map[string]security.Discharge { return nil }
func (l *localCall) RemoteDischarges() map[string]security.Discharge { return nil }
func (l *localCall) LocalPrincipal() security.Principal { return nil }
func (l *localCall) LocalBlessings() security.Blessings { return security.Blessings{} }