forked from ropnop/gokrb5
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
128 lines (116 loc) · 3.41 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Package service provides server side integrations for Kerberos authentication.
package service
import (
"github.com/jd580/gokrb5/v8/types"
"sync"
"time"
)
// 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 (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) {
c.mux.RLock()
defer c.mux.RUnlock()
ce, ok := c.entries[cname.PrincipalNameString()]
return ce, ok
}
func (c *Cache) getClientEntry(cname types.PrincipalName, t time.Time) (replayCacheEntry, bool) {
if ce, ok := c.getClientEntries(cname); ok {
c.mux.RLock()
defer c.mux.RUnlock()
if e, ok := ce.replayMap[t]; ok {
return e, 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 GetReplayCache(d 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(d)
replayCache.ClearOldEntries(d)
}
}()
})
return &replayCache
}
// AddEntry adds an entry to the Cache.
func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
if ce, ok := c.getClientEntries(a.CName); ok {
c.mux.Lock()
defer c.mux.Unlock()
ce.replayMap[ct] = replayCacheEntry{
presentedTime: time.Now().UTC(),
sName: sname,
cTime: ct,
}
ce.seqNumber = a.SeqNumber
ce.subKey = a.SubKey
} else {
c.mux.Lock()
defer c.mux.Unlock()
c.entries[a.CName.PrincipalNameString()] = clientEntries{
replayMap: map[time.Time]replayCacheEntry{
ct: {
presentedTime: time.Now().UTC(),
sName: sname,
cTime: ct,
},
},
seqNumber: a.SeqNumber,
subKey: a.SubKey,
}
}
}
// ClearOldEntries clears entries from the Cache that are older than the duration provided.
func (c *Cache) ClearOldEntries(d time.Duration) {
c.mux.Lock()
defer c.mux.Unlock()
for ke, ce := range c.entries {
for k, e := range ce.replayMap {
if time.Now().UTC().Sub(e.presentedTime) > d {
delete(ce.replayMap, k)
}
}
if len(ce.replayMap) == 0 {
delete(c.entries, ke)
}
}
}
// 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 (c *Cache) IsReplay(sname types.PrincipalName, a types.Authenticator) bool {
ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
if e, ok := c.getClientEntry(a.CName, ct); ok {
if e.sName.Equal(sname) {
return true
}
}
c.AddEntry(sname, a)
return false
}