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  }