-
Notifications
You must be signed in to change notification settings - Fork 249
/
cache.go
148 lines (135 loc) · 4.53 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Package service provides server side integrations for Kerberos authentication.
package service
import (
"gopkg.in/jcmturner/gokrb5.v5/types"
"sync"
"time"
)
/*The server MUST utilize a replay cache to remember any authenticator
presented within the allowable clock skew.
The replay cache will store at least the server name, along with the
client name, time, and microsecond fields from the recently-seen
authenticators, and if a matching tuple is found, the
KRB_AP_ERR_REPEAT error is returned. Note that the rejection here is
restricted to authenticators from the same principal to the same
server. Other client principals communicating with the same server
principal should not have their authenticators rejected if the time
and microsecond fields happen to match some other client's
authenticator.
If a server loses track of authenticators presented within the
allowable clock skew, it MUST reject all requests until the clock
skew interval has passed, providing assurance that any lost or
replayed authenticators will fall outside the allowable clock skew
and can no longer be successfully replayed. If this were not done,
an attacker could subvert the authentication by recording the ticket
and authenticator sent over the network to a server and replaying
them following an event that caused the server to lose track of
recently seen authenticators.*/
// 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.GetPrincipalNameString()]
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.GetPrincipalNameString()] = 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
}