cache.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. /*The server MUST utilize a replay cache to remember any authenticator
  9. presented within the allowable clock skew.
  10. The replay cache will store at least the server name, along with the
  11. client name, time, and microsecond fields from the recently-seen
  12. authenticators, and if a matching tuple is found, the
  13. KRB_AP_ERR_REPEAT error is returned. Note that the rejection here is
  14. restricted to authenticators from the same principal to the same
  15. server. Other client principals communicating with the same server
  16. principal should not have their authenticators rejected if the time
  17. and microsecond fields happen to match some other client's
  18. authenticator.
  19. If a server loses track of authenticators presented within the
  20. allowable clock skew, it MUST reject all requests until the clock
  21. skew interval has passed, providing assurance that any lost or
  22. replayed authenticators will fall outside the allowable clock skew
  23. and can no longer be successfully replayed. If this were not done,
  24. an attacker could subvert the authentication by recording the ticket
  25. and authenticator sent over the network to a server and replaying
  26. them following an event that caused the server to lose track of
  27. recently seen authenticators.*/
  28. // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets.
  29. type Cache struct {
  30. entries map[string]clientEntries
  31. mux sync.RWMutex
  32. }
  33. // clientEntries holds entries of client details sent to the service.
  34. type clientEntries struct {
  35. replayMap map[time.Time]replayCacheEntry
  36. seqNumber int64
  37. subKey types.EncryptionKey
  38. }
  39. // Cache entry tracking client time values of tickets sent to the service.
  40. type replayCacheEntry struct {
  41. presentedTime time.Time
  42. sName types.PrincipalName
  43. cTime time.Time // This combines the ticket's CTime and Cusec
  44. }
  45. func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) {
  46. c.mux.RLock()
  47. defer c.mux.RUnlock()
  48. ce, ok := c.entries[cname.PrincipalNameString()]
  49. return ce, ok
  50. }
  51. func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) {
  52. if ce, ok := c.getClientEntries(cname); ok {
  53. c.mux.RLock()
  54. defer c.mux.RUnlock()
  55. if e, ok := ce.replayMap[t]; ok {
  56. return e, true
  57. }
  58. }
  59. return replayCacheEntry{}, false
  60. }
  61. // Instance of the ServiceCache. This needs to be a singleton.
  62. var replayCache Cache
  63. var once sync.Once
  64. // GetReplayCache returns a pointer to the Cache singleton.
  65. func GetReplayCache(d time.Duration) *Cache {
  66. // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries
  67. once.Do(func() {
  68. replayCache = Cache{
  69. entries: make(map[string]clientEntries),
  70. }
  71. go func() {
  72. for {
  73. // TODO consider using a context here.
  74. time.Sleep(d)
  75. replayCache.ClearOldEntries(d)
  76. }
  77. }()
  78. })
  79. return &replayCache
  80. }
  81. // AddEntry adds an entry to the Cache.
  82. func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
  83. ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
  84. if ce, ok := c.getClientEntries(a.CName); ok {
  85. c.mux.Lock()
  86. defer c.mux.Unlock()
  87. ce.replayMap[ct] = replayCacheEntry{
  88. presentedTime: time.Now().UTC(),
  89. sName: sname,
  90. cTime: ct,
  91. }
  92. ce.seqNumber = a.SeqNumber
  93. ce.subKey = a.SubKey
  94. } else {
  95. c.mux.Lock()
  96. defer c.mux.Unlock()
  97. c.entries[a.CName.PrincipalNameString()] = clientEntries{
  98. replayMap: map[time.Time]replayCacheEntry{
  99. ct: {
  100. presentedTime: time.Now().UTC(),
  101. sName: sname,
  102. cTime: ct,
  103. },
  104. },
  105. seqNumber: a.SeqNumber,
  106. subKey: a.SubKey,
  107. }
  108. }
  109. }
  110. // ClearOldEntries clears entries from the Cache that are older than the duration provided.
  111. func (c *Cache) ClearOldEntries(d time.Duration) {
  112. c.mux.Lock()
  113. defer c.mux.Unlock()
  114. for ke, ce := range c.entries {
  115. for k, e := range ce.replayMap {
  116. if time.Now().UTC().Sub(e.presentedTime) > d {
  117. delete(ce.replayMap, k)
  118. }
  119. }
  120. if len(ce.replayMap) == 0 {
  121. delete(c.entries, ke)
  122. }
  123. }
  124. }
  125. // 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.
  126. func (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool {
  127. ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
  128. if e, ok := c.getClientEntry(a.CName, ct); ok {
  129. if e.sName.Equal(sname) {
  130. return true
  131. }
  132. }
  133. c.AddEntry(sname, a)
  134. return false
  135. }