github.com/jcmturner/gokrb5/v8@v8.4.4/service/cache.go (about) 1 // Package service provides server side integrations for Kerberos authentication. 2 package service 3 4 import ( 5 "github.com/jcmturner/gokrb5/v8/types" 6 "sync" 7 "time" 8 ) 9 10 // Replay cache is required as specified in RFC 4120 section 3.2.3 11 12 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets. 13 type Cache struct { 14 entries map[string]clientEntries 15 mux sync.RWMutex 16 } 17 18 // clientEntries holds entries of client details sent to the service. 19 type clientEntries struct { 20 replayMap map[time.Time]replayCacheEntry 21 seqNumber int64 22 subKey types.EncryptionKey 23 } 24 25 // Cache entry tracking client time values of tickets sent to the service. 26 type replayCacheEntry struct { 27 presentedTime time.Time 28 sName types.PrincipalName 29 cTime time.Time // This combines the ticket's CTime and Cusec 30 } 31 32 func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) { 33 c.mux.RLock() 34 defer c.mux.RUnlock() 35 ce, ok := c.entries[cname.PrincipalNameString()] 36 return ce, ok 37 } 38 39 func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) { 40 if ce, ok := c.getClientEntries(cname); ok { 41 c.mux.RLock() 42 defer c.mux.RUnlock() 43 if e, ok := ce.replayMap[t]; ok { 44 return e, true 45 } 46 } 47 return replayCacheEntry{}, false 48 } 49 50 // Instance of the ServiceCache. This needs to be a singleton. 51 var replayCache Cache 52 var once sync.Once 53 54 // GetReplayCache returns a pointer to the Cache singleton. 55 func GetReplayCache(d time.Duration) *Cache { 56 // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries 57 once.Do(func() { 58 replayCache = Cache{ 59 entries: make(map[string]clientEntries), 60 } 61 go func() { 62 for { 63 // TODO consider using a context here. 64 time.Sleep(d) 65 replayCache.ClearOldEntries(d) 66 } 67 }() 68 }) 69 return &replayCache 70 } 71 72 // AddEntry adds an entry to the Cache. 73 func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) { 74 ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) 75 if ce, ok := c.getClientEntries(a.CName); ok { 76 c.mux.Lock() 77 defer c.mux.Unlock() 78 ce.replayMap[ct] = replayCacheEntry{ 79 presentedTime: time.Now().UTC(), 80 sName: sname, 81 cTime: ct, 82 } 83 ce.seqNumber = a.SeqNumber 84 ce.subKey = a.SubKey 85 } else { 86 c.mux.Lock() 87 defer c.mux.Unlock() 88 c.entries[a.CName.PrincipalNameString()] = clientEntries{ 89 replayMap: map[time.Time]replayCacheEntry{ 90 ct: { 91 presentedTime: time.Now().UTC(), 92 sName: sname, 93 cTime: ct, 94 }, 95 }, 96 seqNumber: a.SeqNumber, 97 subKey: a.SubKey, 98 } 99 } 100 } 101 102 // ClearOldEntries clears entries from the Cache that are older than the duration provided. 103 func (c *Cache) ClearOldEntries(d time.Duration) { 104 c.mux.Lock() 105 defer c.mux.Unlock() 106 for ke, ce := range c.entries { 107 for k, e := range ce.replayMap { 108 if time.Now().UTC().Sub(e.presentedTime) > d { 109 delete(ce.replayMap, k) 110 } 111 } 112 if len(ce.replayMap) == 0 { 113 delete(c.entries, ke) 114 } 115 } 116 } 117 118 // IsReplay tests if the Authenticator provided is a replay within the duration defined. If this is not a replay add the entry to the cache for tracking. 119 func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool { 120 ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond) 121 if e, ok := c.getClientEntry(a.CName, ct); ok { 122 if e.sName.Equal(sname) { 123 return true 124 } 125 } 126 c.AddEntry(sname, a) 127 return false 128 }