blob: 7b201bb5c74300acc3055d13d32c9abfc986f793 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Matt Rosencrantz94502cf2015-03-18 09:43:44 -07005package rpc
Suharsh Sivakumar720b7042014-12-22 17:33:23 -08006
7import (
Jungho Ahn44d8daf2015-01-16 10:39:15 -08008 "crypto/sha256"
Suharsh Sivakumar720b7042014-12-22 17:33:23 -08009 "sync"
10
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070011 "v.io/v23/rpc"
Jiri Simsa6ac95222015-02-23 16:11:49 -080012 "v.io/v23/security"
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070013 "v.io/v23/verror"
14
Suharsh Sivakumardcc11d72015-05-11 12:19:20 -070015 "v.io/x/ref/runtime/internal/rpc/stream"
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080016)
17
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -070018var (
19 // These errors are intended to be used as arguments to higher
20 // level errors and hence {1}{2} is omitted from their format
21 // strings to avoid repeating these n-times in the final error
22 // message visible to the user.
23 errMissingBlessingsKey = reg(".blessingsKey", "key {3} was not in blessings cache")
24 errInvalidClientBlessings = reg("invalidClientBlessings", "client sent invalid Blessings")
25)
26
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080027// clientEncodeBlessings gets or inserts the blessings into the cache.
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070028func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) rpc.BlessingsRequest {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080029 blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080030 blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
31 return blessingsCache.getOrInsert(blessings)
32}
33
34// clientAckBlessings verifies that the server has updated its cache to include blessings.
35// This means that subsequent rpcs from the client with blessings can send only a cache key.
36func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080037 blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080038 blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
39 blessingsCache.acknowledge(blessings)
40}
41
42// serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only
43// key is provided in req.
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070044func serverDecodeBlessings(cache stream.VCDataCache, req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080045 blessingsCacheAny := cache.GetOrInsert(serverBlessingsCacheKey{}, newServerBlessingsCache)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080046 blessingsCache := blessingsCacheAny.(*serverBlessingsCache)
47 return blessingsCache.getOrInsert(req, stats)
48}
49
50// IMPLEMENTATION DETAILS BELOW
51
Jungho Ahn44d8daf2015-01-16 10:39:15 -080052// clientBlessingsCache is a thread-safe map from blessings to cache id.
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080053type clientBlessingsCache struct {
54 sync.RWMutex
Jungho Ahn44d8daf2015-01-16 10:39:15 -080055 m map[[sha256.Size]byte]clientCacheValue
56 curId uint64
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080057}
58
59type clientCacheValue struct {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080060 id uint64
61 // ack is set to true once the server has confirmed receipt of the cache id.
62 // Clients that insert into the cache when ack is false must send both the id
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080063 // and the blessings.
64 ack bool
65}
66
Jungho Ahn44d8daf2015-01-16 10:39:15 -080067// clientBlessingsCacheKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
68type clientBlessingsCacheKey struct{}
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080069
70func newClientBlessingsCache() interface{} {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080071 return &clientBlessingsCache{m: make(map[[sha256.Size]byte]clientCacheValue)}
72}
73
74func getBlessingsHashKey(blessings security.Blessings) (key [sha256.Size]byte) {
75 h := sha256.New()
76 for _, chain := range security.MarshalBlessings(blessings).CertificateChains {
77 if len(chain) == 0 {
78 continue
79 }
80 cert := chain[len(chain)-1]
81 s := sha256.Sum256(cert.Signature.R)
82 h.Write(s[:])
83 s = sha256.Sum256(cert.Signature.S)
84 h.Write(s[:])
85 }
86 copy(key[:], h.Sum(nil))
87 return
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080088}
89
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070090func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) rpc.BlessingsRequest {
Jungho Ahn44d8daf2015-01-16 10:39:15 -080091 key := getBlessingsHashKey(blessings)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080092 c.RLock()
Jungho Ahn44d8daf2015-01-16 10:39:15 -080093 val, exists := c.m[key]
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080094 c.RUnlock()
95 if exists {
96 return c.makeBlessingsRequest(val, blessings)
97 }
Jungho Ahn44d8daf2015-01-16 10:39:15 -080098 // If the val doesn't exist we must create a new id, update the cache, and send the id and blessings.
Suharsh Sivakumar720b7042014-12-22 17:33:23 -080099 c.Lock()
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800100 // We must check that the val wasn't inserted in the time we changed locks.
101 val, exists = c.m[key]
102 if !exists {
103 val = clientCacheValue{id: c.nextIdLocked()}
104 c.m[key] = val
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800105 }
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800106 c.Unlock()
107 return c.makeBlessingsRequest(val, blessings)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800108}
109
110func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) {
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800111 key := getBlessingsHashKey(blessings)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800112 c.Lock()
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800113 val := c.m[key]
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800114 val.ack = true
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800115 c.m[key] = val
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800116 c.Unlock()
117}
118
Matt Rosencrantz94502cf2015-03-18 09:43:44 -0700119func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) rpc.BlessingsRequest {
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800120 if val.ack {
121 // when the value is acknowledged, only send the key, since the server has confirmed that it knows the key.
Matt Rosencrantz94502cf2015-03-18 09:43:44 -0700122 return rpc.BlessingsRequest{Key: val.id}
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800123 }
124 // otherwise we still need to send both key and blessings, but we must ensure that we send the same key.
Jiri Simsad9a7b3c2015-08-12 16:38:27 -0700125 return rpc.BlessingsRequest{Key: val.id, Blessings: &blessings}
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800126}
127
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800128// nextIdLocked creates a new id for inserting blessings. It must be called after acquiring a writer lock.
129func (c *clientBlessingsCache) nextIdLocked() uint64 {
130 c.curId++
131 return c.curId
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800132}
133
134// serverBlessingsCache is a thread-safe map from cache key to blessings.
135type serverBlessingsCache struct {
136 sync.RWMutex
137 m map[uint64]security.Blessings
138}
139
Jungho Ahn44d8daf2015-01-16 10:39:15 -0800140// serverBlessingsCacheKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
141type serverBlessingsCacheKey struct{}
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800142
143func newServerBlessingsCache() interface{} {
144 return &serverBlessingsCache{m: make(map[uint64]security.Blessings)}
145}
146
Matt Rosencrantz94502cf2015-03-18 09:43:44 -0700147func (c *serverBlessingsCache) getOrInsert(req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
Suharsh Sivakumar2c5d8102015-03-23 08:49:12 -0700148 // In the case that the key sent is 0, we are running in SecurityNone
Asim Shankar2bf7b1e2015-02-27 00:45:12 -0800149 // and should return the zero value.
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800150 if req.Key == 0 {
Asim Shankar2bf7b1e2015-02-27 00:45:12 -0800151 return security.Blessings{}, nil
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800152 }
153 if req.Blessings == nil {
154 // Fastpath, lookup based on the key.
155 c.RLock()
156 cached, exists := c.m[req.Key]
157 c.RUnlock()
158 if !exists {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700159 return security.Blessings{}, verror.New(errMissingBlessingsKey, nil, req.Key)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800160 }
161 stats.recordBlessingCache(true)
162 return cached, nil
163 }
Suharsh Sivakumardefea2b2015-01-13 09:33:50 -0800164 // Always count the slow path as a cache miss since we do not get the benefit of sending only the cache key.
165 stats.recordBlessingCache(false)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800166 // Slowpath, might need to update the cache, or check that the received blessings are
167 // the same as what's in the cache.
Asim Shankarb07ec692015-02-27 23:40:44 -0800168 recv := *req.Blessings
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800169 c.Lock()
170 defer c.Unlock()
171 if cached, exists := c.m[req.Key]; exists {
Suharsh Sivakumar07b57f22015-03-30 18:55:24 -0700172 if !cached.Equivalent(recv) {
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700173 return security.Blessings{}, verror.New(errInvalidClientBlessings, nil)
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800174 }
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800175 return cached, nil
176 }
177 c.m[req.Key] = recv
Suharsh Sivakumar720b7042014-12-22 17:33:23 -0800178 return recv, nil
179}