forked from globalsign/est
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cacert_cache.go
171 lines (143 loc) · 4.65 KB
/
cacert_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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
Copyright (c) 2020 GMO GlobalSign, Inc.
Licensed under the MIT License (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package est
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"net/http"
"sync"
"time"
)
// cacertCache contains a cache of CA certificates for each "CA" (identified
// by the optional additional path segment) to enable client certificate
// validation during reenrollment without excessive network calls to /cacerts.
type cacertCache struct {
ca CA
mutex sync.RWMutex
cache map[string]cacheEntry
}
// cacheEntry is an entry in the CA certificates cache.
type cacheEntry struct {
roots *x509.CertPool
inters *x509.CertPool
updated time.Time
}
const (
// assumeFresh is the amount of time for which cached CA certificates will
// be assumed to be fresh, i.e. a new call to /cacerts will not be made if
// the cached CA certs are younger than this time period.
assumeFresh = time.Minute * 5
)
// Add adds a set of CA certificates to the cache. The operation is performed
// asynchronously and the method returns immediately..
func (c *cacertCache) Add(aps string, certs []*x509.Certificate) {
go c.addSync(aps, certs)
}
// Verify verifies a certificate against the cached CA certificates for the
// specified CA. If the CA certificates for that CA are not in the cache, or
// if they are not fresh has expired, an attempt is made to retrieve them.
func (c *cacertCache) Verify(
ctx context.Context,
aps string,
cert *x509.Certificate,
r *http.Request,
) error {
current, err := c.get(ctx, aps, r)
if err != nil {
return err
}
opts := x509.VerifyOptions{
Roots: current.roots,
Intermediates: current.inters,
CurrentTime: time.Now(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
if _, err := cert.Verify(opts); err != nil {
return errInvalidClientCert
}
return nil
}
// addSync synchonrously adds a set of CA certificates to the cache. If
// a sufficiently fresh entry is already in the cache, it is returned,
// otherwise a new entry is added and returned.
func (c *cacertCache) addSync(aps string, certs []*x509.Certificate) cacheEntry {
// Acquire a read lock to check for an existing entry.
c.mutex.RLock()
current, ok := c.cache[aps]
c.mutex.RUnlock()
// Do nothing if entry exists and was updated sufficiently recently.
if ok && time.Since(current.updated) < assumeFresh {
return current
}
// Build new certificate pools before acquiring the mutex, to minimize
// time holding it.
var roots *x509.CertPool
var inters *x509.CertPool
for _, cert := range certs {
if bytes.Equal(cert.RawSubject, cert.RawIssuer) {
if roots == nil {
roots = x509.NewCertPool()
}
roots.AddCert(cert)
} else {
if inters == nil {
inters = x509.NewCertPool()
}
inters.AddCert(cert)
}
}
// Acquire a write mutex.
c.mutex.Lock()
defer c.mutex.Unlock()
// Check again in case entry was updated since we released the read mutex.
current, ok = c.cache[aps]
if ok && time.Since(current.updated) < assumeFresh {
return current
}
// Add the new/updated entry to the cache and return it.
newEntry := cacheEntry{
roots: roots,
inters: inters,
updated: time.Now(),
}
c.cache[aps] = newEntry
return newEntry
}
// get retrieves the CA certificates for the specified CA from the cache.
// If the CA certificates for that CA are not in the cache, or if their
// freshness has expired, an attempt is made to retrieve them.
func (c *cacertCache) get(ctx context.Context, aps string, r *http.Request) (cacheEntry, error) {
// Acquire a read lock to check for an existing entry.
c.mutex.RLock()
current, ok := c.cache[aps]
c.mutex.RUnlock()
// If entry exists and was updated sufficiently recently, return it.
if ok && time.Since(current.updated) < assumeFresh {
return current, nil
}
// Request latest CA certificates and return them.
certs, err := c.ca.CACerts(ctx, aps, r)
if err != nil {
LoggerFromContext(r.Context()).Errorf("failed to retrieve CA certificates: %v", err)
return cacheEntry{}, fmt.Errorf("failed to retrieve CA certificates: %w", err)
}
return c.addSync(aps, certs), nil
}
// newCACertCache creates a new CA certificate cache.
func newCACertCache(ca CA) *cacertCache {
return &cacertCache{
ca: ca,
cache: make(map[string]cacheEntry),
}
}