// Package service provides server side integrations for Kerberos authentication.
package service import ( ) // Replay cache is required as specified in RFC 4120 section 3.2.3 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets. type Cache struct { entries map[string]clientEntries mux sync.RWMutex } // clientEntries holds entries of client details sent to the service. type clientEntries struct { replayMap map[time.Time]replayCacheEntry seqNumber int64 subKey types.EncryptionKey } // Cache entry tracking client time values of tickets sent to the service. type replayCacheEntry struct { presentedTime time.Time sName types.PrincipalName cTime time.Time // This combines the ticket's CTime and Cusec } func ( *Cache) ( types.PrincipalName) (clientEntries, bool) { .mux.RLock() defer .mux.RUnlock() , := .entries[.PrincipalNameString()] return , } func ( *Cache) ( types.PrincipalName, time.Time) (replayCacheEntry, bool) { if , := .getClientEntries(); { .mux.RLock() defer .mux.RUnlock() if , := .replayMap[]; { return , true } } return replayCacheEntry{}, false } // Instance of the ServiceCache. This needs to be a singleton. var replayCache Cache var once sync.Once // GetReplayCache returns a pointer to the Cache singleton. func ( time.Duration) *Cache { // Create a singleton of the ReplayCache and start a background thread to regularly clean out old entries once.Do(func() { replayCache = Cache{ entries: make(map[string]clientEntries), } go func() { for { // TODO consider using a context here. time.Sleep() replayCache.ClearOldEntries() } }() }) return &replayCache } // AddEntry adds an entry to the Cache. func ( *Cache) ( types.PrincipalName, types.Authenticator) { := .CTime.Add(time.Duration(.Cusec) * time.Microsecond) if , := .getClientEntries(.CName); { .mux.Lock() defer .mux.Unlock() .replayMap[] = replayCacheEntry{ presentedTime: time.Now().UTC(), sName: , cTime: , } .seqNumber = .SeqNumber .subKey = .SubKey } else { .mux.Lock() defer .mux.Unlock() .entries[.CName.PrincipalNameString()] = clientEntries{ replayMap: map[time.Time]replayCacheEntry{ : { presentedTime: time.Now().UTC(), sName: , cTime: , }, }, seqNumber: .SeqNumber, subKey: .SubKey, } } } // ClearOldEntries clears entries from the Cache that are older than the duration provided. func ( *Cache) ( time.Duration) { .mux.Lock() defer .mux.Unlock() for , := range .entries { for , := range .replayMap { if time.Now().UTC().Sub(.presentedTime) > { delete(.replayMap, ) } } if len(.replayMap) == 0 { delete(.entries, ) } } } // 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. func ( *Cache) ( types.PrincipalName, types.Authenticator) bool { := .CTime.Add(time.Duration(.Cusec) * time.Microsecond) if , := .getClientEntry(.CName, ); { if .sName.Equal() { return true } } .AddEntry(, ) return false }