Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 5 | package rpc |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 6 | |
| 7 | import ( |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 8 | "crypto/sha256" |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 9 | "sync" |
| 10 | |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 11 | "v.io/v23/rpc" |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 12 | "v.io/v23/security" |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 13 | "v.io/v23/verror" |
| 14 | |
Suharsh Sivakumar | dcc11d7 | 2015-05-11 12:19:20 -0700 | [diff] [blame] | 15 | "v.io/x/ref/runtime/internal/rpc/stream" |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 16 | ) |
| 17 | |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 18 | var ( |
| 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 Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 27 | // clientEncodeBlessings gets or inserts the blessings into the cache. |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 28 | func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) rpc.BlessingsRequest { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 29 | blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 30 | 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. |
| 36 | func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 37 | blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 38 | 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 Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 44 | func serverDecodeBlessings(cache stream.VCDataCache, req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 45 | blessingsCacheAny := cache.GetOrInsert(serverBlessingsCacheKey{}, newServerBlessingsCache) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 46 | blessingsCache := blessingsCacheAny.(*serverBlessingsCache) |
| 47 | return blessingsCache.getOrInsert(req, stats) |
| 48 | } |
| 49 | |
| 50 | // IMPLEMENTATION DETAILS BELOW |
| 51 | |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 52 | // clientBlessingsCache is a thread-safe map from blessings to cache id. |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 53 | type clientBlessingsCache struct { |
| 54 | sync.RWMutex |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 55 | m map[[sha256.Size]byte]clientCacheValue |
| 56 | curId uint64 |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | type clientCacheValue struct { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 60 | 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 Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 63 | // and the blessings. |
| 64 | ack bool |
| 65 | } |
| 66 | |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 67 | // clientBlessingsCacheKey is the key used to retrieve the clientBlessingsCache from the VCDataCache. |
| 68 | type clientBlessingsCacheKey struct{} |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 69 | |
| 70 | func newClientBlessingsCache() interface{} { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 71 | return &clientBlessingsCache{m: make(map[[sha256.Size]byte]clientCacheValue)} |
| 72 | } |
| 73 | |
| 74 | func 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 Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 88 | } |
| 89 | |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 90 | func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) rpc.BlessingsRequest { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 91 | key := getBlessingsHashKey(blessings) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 92 | c.RLock() |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 93 | val, exists := c.m[key] |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 94 | c.RUnlock() |
| 95 | if exists { |
| 96 | return c.makeBlessingsRequest(val, blessings) |
| 97 | } |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 98 | // If the val doesn't exist we must create a new id, update the cache, and send the id and blessings. |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 99 | c.Lock() |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 100 | // 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 Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 105 | } |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 106 | c.Unlock() |
| 107 | return c.makeBlessingsRequest(val, blessings) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) { |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 111 | key := getBlessingsHashKey(blessings) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 112 | c.Lock() |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 113 | val := c.m[key] |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 114 | val.ack = true |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 115 | c.m[key] = val |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 116 | c.Unlock() |
| 117 | } |
| 118 | |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 119 | func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) rpc.BlessingsRequest { |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 120 | if val.ack { |
| 121 | // when the value is acknowledged, only send the key, since the server has confirmed that it knows the key. |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 122 | return rpc.BlessingsRequest{Key: val.id} |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 123 | } |
| 124 | // otherwise we still need to send both key and blessings, but we must ensure that we send the same key. |
Jiri Simsa | d9a7b3c | 2015-08-12 16:38:27 -0700 | [diff] [blame] | 125 | return rpc.BlessingsRequest{Key: val.id, Blessings: &blessings} |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 126 | } |
| 127 | |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 128 | // nextIdLocked creates a new id for inserting blessings. It must be called after acquiring a writer lock. |
| 129 | func (c *clientBlessingsCache) nextIdLocked() uint64 { |
| 130 | c.curId++ |
| 131 | return c.curId |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | // serverBlessingsCache is a thread-safe map from cache key to blessings. |
| 135 | type serverBlessingsCache struct { |
| 136 | sync.RWMutex |
| 137 | m map[uint64]security.Blessings |
| 138 | } |
| 139 | |
Jungho Ahn | 44d8daf | 2015-01-16 10:39:15 -0800 | [diff] [blame] | 140 | // serverBlessingsCacheKey is the key used to retrieve the serverBlessingsCache from the VCDataCache. |
| 141 | type serverBlessingsCacheKey struct{} |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 142 | |
| 143 | func newServerBlessingsCache() interface{} { |
| 144 | return &serverBlessingsCache{m: make(map[uint64]security.Blessings)} |
| 145 | } |
| 146 | |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 147 | func (c *serverBlessingsCache) getOrInsert(req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) { |
Suharsh Sivakumar | 2c5d810 | 2015-03-23 08:49:12 -0700 | [diff] [blame] | 148 | // In the case that the key sent is 0, we are running in SecurityNone |
Asim Shankar | 2bf7b1e | 2015-02-27 00:45:12 -0800 | [diff] [blame] | 149 | // and should return the zero value. |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 150 | if req.Key == 0 { |
Asim Shankar | 2bf7b1e | 2015-02-27 00:45:12 -0800 | [diff] [blame] | 151 | return security.Blessings{}, nil |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 152 | } |
| 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 Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 159 | return security.Blessings{}, verror.New(errMissingBlessingsKey, nil, req.Key) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 160 | } |
| 161 | stats.recordBlessingCache(true) |
| 162 | return cached, nil |
| 163 | } |
Suharsh Sivakumar | defea2b | 2015-01-13 09:33:50 -0800 | [diff] [blame] | 164 | // 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 Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 166 | // Slowpath, might need to update the cache, or check that the received blessings are |
| 167 | // the same as what's in the cache. |
Asim Shankar | b07ec69 | 2015-02-27 23:40:44 -0800 | [diff] [blame] | 168 | recv := *req.Blessings |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 169 | c.Lock() |
| 170 | defer c.Unlock() |
| 171 | if cached, exists := c.m[req.Key]; exists { |
Suharsh Sivakumar | 07b57f2 | 2015-03-30 18:55:24 -0700 | [diff] [blame] | 172 | if !cached.Equivalent(recv) { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 173 | return security.Blessings{}, verror.New(errInvalidClientBlessings, nil) |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 174 | } |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 175 | return cached, nil |
| 176 | } |
| 177 | c.m[req.Key] = recv |
Suharsh Sivakumar | 720b704 | 2014-12-22 17:33:23 -0800 | [diff] [blame] | 178 | return recv, nil |
| 179 | } |