blob: 116f638ea2ef165db6ac937a3d70ad68d5fb8441 [file] [log] [blame]
// 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 rpc
import (
"crypto/sha256"
"sync"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/verror"
"v.io/x/ref/profiles/internal/rpc/stream"
)
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.
errMissingBlessingsKey = reg(".blessingsKey", "key {3} was not in blessings cache")
errInvalidClientBlessings = reg("invalidClientBlessings", "client sent invalid Blessings")
)
// clientEncodeBlessings gets or inserts the blessings into the cache.
func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) rpc.BlessingsRequest {
blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
return blessingsCache.getOrInsert(blessings)
}
// clientAckBlessings verifies that the server has updated its cache to include blessings.
// This means that subsequent rpcs from the client with blessings can send only a cache key.
func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) {
blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
blessingsCache.acknowledge(blessings)
}
// serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only
// key is provided in req.
func serverDecodeBlessings(cache stream.VCDataCache, req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
blessingsCacheAny := cache.GetOrInsert(serverBlessingsCacheKey{}, newServerBlessingsCache)
blessingsCache := blessingsCacheAny.(*serverBlessingsCache)
return blessingsCache.getOrInsert(req, stats)
}
// IMPLEMENTATION DETAILS BELOW
// clientBlessingsCache is a thread-safe map from blessings to cache id.
type clientBlessingsCache struct {
sync.RWMutex
m map[[sha256.Size]byte]clientCacheValue
curId uint64
}
type clientCacheValue struct {
id uint64
// ack is set to true once the server has confirmed receipt of the cache id.
// Clients that insert into the cache when ack is false must send both the id
// and the blessings.
ack bool
}
// clientBlessingsCacheKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
type clientBlessingsCacheKey struct{}
func newClientBlessingsCache() interface{} {
return &clientBlessingsCache{m: make(map[[sha256.Size]byte]clientCacheValue)}
}
func getBlessingsHashKey(blessings security.Blessings) (key [sha256.Size]byte) {
h := sha256.New()
for _, chain := range security.MarshalBlessings(blessings).CertificateChains {
if len(chain) == 0 {
continue
}
cert := chain[len(chain)-1]
s := sha256.Sum256(cert.Signature.R)
h.Write(s[:])
s = sha256.Sum256(cert.Signature.S)
h.Write(s[:])
}
copy(key[:], h.Sum(nil))
return
}
func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) rpc.BlessingsRequest {
key := getBlessingsHashKey(blessings)
c.RLock()
val, exists := c.m[key]
c.RUnlock()
if exists {
return c.makeBlessingsRequest(val, blessings)
}
// If the val doesn't exist we must create a new id, update the cache, and send the id and blessings.
c.Lock()
// We must check that the val wasn't inserted in the time we changed locks.
val, exists = c.m[key]
if !exists {
val = clientCacheValue{id: c.nextIdLocked()}
c.m[key] = val
}
c.Unlock()
return c.makeBlessingsRequest(val, blessings)
}
func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) {
key := getBlessingsHashKey(blessings)
c.Lock()
val := c.m[key]
val.ack = true
c.m[key] = val
c.Unlock()
}
func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) rpc.BlessingsRequest {
if val.ack {
// when the value is acknowledged, only send the key, since the server has confirmed that it knows the key.
return rpc.BlessingsRequest{Key: val.id}
}
// otherwise we still need to send both key and blessings, but we must ensure that we send the same key.
return rpc.BlessingsRequest{val.id, &blessings}
}
// nextIdLocked creates a new id for inserting blessings. It must be called after acquiring a writer lock.
func (c *clientBlessingsCache) nextIdLocked() uint64 {
c.curId++
return c.curId
}
// serverBlessingsCache is a thread-safe map from cache key to blessings.
type serverBlessingsCache struct {
sync.RWMutex
m map[uint64]security.Blessings
}
// serverBlessingsCacheKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
type serverBlessingsCacheKey struct{}
func newServerBlessingsCache() interface{} {
return &serverBlessingsCache{m: make(map[uint64]security.Blessings)}
}
func (c *serverBlessingsCache) getOrInsert(req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
// In the case that the key sent is 0, we are running in SecurityNone
// and should return the zero value.
if req.Key == 0 {
return security.Blessings{}, nil
}
if req.Blessings == nil {
// Fastpath, lookup based on the key.
c.RLock()
cached, exists := c.m[req.Key]
c.RUnlock()
if !exists {
return security.Blessings{}, verror.New(errMissingBlessingsKey, nil, req.Key)
}
stats.recordBlessingCache(true)
return cached, nil
}
// Always count the slow path as a cache miss since we do not get the benefit of sending only the cache key.
stats.recordBlessingCache(false)
// Slowpath, might need to update the cache, or check that the received blessings are
// the same as what's in the cache.
recv := *req.Blessings
c.Lock()
defer c.Unlock()
if cached, exists := c.m[req.Key]; exists {
if !cached.Equivalent(recv) {
return security.Blessings{}, verror.New(errInvalidClientBlessings, nil)
}
return cached, nil
}
c.m[req.Key] = recv
return recv, nil
}