blob: 552e73e41e13a95cd16077e7108cf27588030af5 [file] [log] [blame]
khenaidoo106c61a2021-08-11 18:05:46 -04001package client
2
3import (
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.
15type Cache struct {
16 Entries map[string]CacheEntry
17 mux sync.RWMutex
18}
19
20// CacheEntry holds details for a cache entry.
21type 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.
32func NewCache() *Cache {
33 return &Cache{
34 Entries: map[string]CacheEntry{},
35 }
36}
37
38// getEntry returns a cache entry that matches the SPN.
39func (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.
47func (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.
67func (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
84func (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.
93func (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.
101func (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
122func (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}