github.com/jcmturner/gokrb5/v8@v8.4.4/client/cache.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "errors" 6 "sort" 7 "sync" 8 "time" 9 10 "github.com/jcmturner/gokrb5/v8/messages" 11 "github.com/jcmturner/gokrb5/v8/types" 12 ) 13 14 // Cache for service tickets held by the client. 15 type Cache struct { 16 Entries map[string]CacheEntry 17 mux sync.RWMutex 18 } 19 20 // CacheEntry holds details for a cache entry. 21 type CacheEntry struct { 22 SPN string 23 Ticket messages.Ticket `json:"-"` 24 AuthTime time.Time 25 StartTime time.Time 26 EndTime time.Time 27 RenewTill time.Time 28 SessionKey types.EncryptionKey `json:"-"` 29 } 30 31 // NewCache creates a new client ticket cache instance. 32 func NewCache() *Cache { 33 return &Cache{ 34 Entries: map[string]CacheEntry{}, 35 } 36 } 37 38 // getEntry returns a cache entry that matches the SPN. 39 func (c *Cache) getEntry(spn string) (CacheEntry, bool) { 40 c.mux.RLock() 41 defer c.mux.RUnlock() 42 e, ok := (*c).Entries[spn] 43 return e, ok 44 } 45 46 // JSON returns information about the cached service tickets in a JSON format. 47 func (c *Cache) JSON() (string, error) { 48 c.mux.RLock() 49 defer c.mux.RUnlock() 50 var es []CacheEntry 51 keys := make([]string, 0, len(c.Entries)) 52 for k := range c.Entries { 53 keys = append(keys, k) 54 } 55 sort.Strings(keys) 56 for _, k := range keys { 57 es = append(es, c.Entries[k]) 58 } 59 b, err := json.MarshalIndent(&es, "", " ") 60 if err != nil { 61 return "", err 62 } 63 return string(b), nil 64 } 65 66 // addEntry adds a ticket to the cache. 67 func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry { 68 spn := tkt.SName.PrincipalNameString() 69 c.mux.Lock() 70 defer c.mux.Unlock() 71 (*c).Entries[spn] = CacheEntry{ 72 SPN: spn, 73 Ticket: tkt, 74 AuthTime: authTime, 75 StartTime: startTime, 76 EndTime: endTime, 77 RenewTill: renewTill, 78 SessionKey: sessionKey, 79 } 80 return c.Entries[spn] 81 } 82 83 // clear deletes all the cache entries 84 func (c *Cache) clear() { 85 c.mux.Lock() 86 defer c.mux.Unlock() 87 for k := range c.Entries { 88 delete(c.Entries, k) 89 } 90 } 91 92 // RemoveEntry removes the cache entry for the defined SPN. 93 func (c *Cache) RemoveEntry(spn string) { 94 c.mux.Lock() 95 defer c.mux.Unlock() 96 delete(c.Entries, spn) 97 } 98 99 // GetCachedTicket returns a ticket from the cache for the SPN. 100 // Only a ticket that is currently valid will be returned. 101 func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) { 102 if e, ok := cl.cache.getEntry(spn); ok { 103 //If within time window of ticket return it 104 if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) { 105 cl.Log("ticket received from cache for %s", spn) 106 return e.Ticket, e.SessionKey, true 107 } else if time.Now().UTC().Before(e.RenewTill) { 108 e, err := cl.renewTicket(e) 109 if err != nil { 110 return e.Ticket, e.SessionKey, false 111 } 112 return e.Ticket, e.SessionKey, true 113 } 114 } 115 var tkt messages.Ticket 116 var key types.EncryptionKey 117 return tkt, key, false 118 } 119 120 // renewTicket renews a cache entry ticket. 121 // To renew from outside the client package use GetCachedTicket 122 func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) { 123 spn := e.Ticket.SName 124 _, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true) 125 if err != nil { 126 return e, err 127 } 128 e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString()) 129 if !ok { 130 return e, errors.New("ticket was not added to cache") 131 } 132 cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime) 133 return e, nil 134 }