cache.go 3.4 KB

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