-
Notifications
You must be signed in to change notification settings - Fork 1
/
crlManagement.go
353 lines (323 loc) · 13.2 KB
/
crlManagement.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
package vermouth
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"github.com/ipifony/vermouth/logger"
"github.com/ipifony/vermouth/stivs"
"io"
"net/http"
"net/url"
"strconv"
"time"
)
const CrlProxyDev string = "https://wfe.dev.martinisecurity.com/v1/stipa/proxy?url="
const CrlProxyProd string = "https://wfe.prod.martinisecurity.com/v1/stipa/proxy?url="
const X5UProxyDev string = "https://wfe.dev.martinisecurity.com/v1/stipa/signer?x5u="
const X5UProxyProd string = "https://wfe.prod.martinisecurity.com/v1/stipa/signer?x5u="
func beginCrlWorkerQueue(responseChan chan Message, crlWorkChan chan string) {
stivs.InitCrlCache()
for {
select {
case uri := <-crlWorkChan:
refreshCrl(responseChan, uri)
}
}
}
func getCrlFromUrl(url string) ([]byte, error) {
r, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
r.Header.Add("Accept", "application/pkix-crl")
r.Header.Set("User-Agent", UserAgent)
resp, err := httpClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Limit to 2 megs (2097152 bytes)
body, err := io.ReadAll(io.LimitReader(resp.Body, 2097152))
if err != nil {
return nil, err
}
return body, nil
}
func refreshCrl(responseChan chan<- Message, crlDistPoint string) {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: About to Refresh " + crlDistPoint}
statusMsg := Message{
MessageSender: CrlWorker,
MessageType: Failure,
MsgStr: crlDistPoint + ";10m",
}
shouldTryProxy := false
if crlDistPoint == "https://authenticate-api.iconectiv.com/download/v1/crl" || crlDistPoint == "https://authenticate-api-stg.iconectiv.com/download/v1/crl" {
shouldTryProxy = true
}
// Fetch or create new cache entry from map
var thisCacheEntry = stivs.GetCrlCache().CopyEntry(crlDistPoint)
if thisCacheEntry != nil {
if thisCacheEntry.Status == stivs.Initializing {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Already Refreshing " + crlDistPoint}
// We just got this CRL Dist Point. No need to force a refresh since we are currently working on it
return
}
} else {
thisCacheEntry = &stivs.CrlCacheEntry{}
thisCacheEntry.URI = crlDistPoint
thisCacheEntry.Status = stivs.Initializing
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
}
// Handle edge case of InvalidDestPoint
if thisCacheEntry.Status == stivs.InvalidDestPoint {
// Only willing to retry dest point that keeps erroring out once every 24 hours
if thisCacheEntry.LastAttempt.Before(thisCacheEntry.LastAttempt.Add(24 * time.Hour)) {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Refusing to retry invalid dest point " + crlDistPoint + " for now"}
return
} else {
// Reset the last attempt to Now() and immediately store, so we don't accept and work multiple requests for this CRL
thisCacheEntry.LastAttempt = time.Now()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
}
}
thisCacheEntry.LastAttempt = time.Now()
crlBytes, err := getCrlFromUrl(crlDistPoint)
if err != nil {
if shouldTryProxy {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Trying proxy for CRL " + crlDistPoint}
martiniCrlProxy := CrlProxyProd
if !GlobalConfig.isAcmeProdMode() {
martiniCrlProxy = CrlProxyDev
}
crlBytes, err = getCrlFromUrl(martiniCrlProxy + crlDistPoint)
}
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not load CRL - " + err.Error()}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
} else {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: HTTP GET Successful"}
}
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Parsing HTTP response..."}
var crlDERBytes []byte
getCrlDERFromPEMBytes(crlBytes, &crlDERBytes)
revocationList, err := x509.ParseRevocationList(crlDERBytes)
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not parse CRL bytes - " + err.Error()}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Parse Successful"}
// AIA chasing and chain validation
// Look through the extensions for the AIA
// Also look to ensure the URL we used to fetch the CRL is the same URL found in the Issuing Distribution Point
var aiaUrl string
for _, extension := range revocationList.Extensions {
if extension.Id.Equal(oids["crlIssuingDistPoint"]) {
issuingDistPointUrl, err := getCrlIssuingDistPointUrlFromAsn1Extension(extension)
if err != nil || !IsUrl(issuingDistPointUrl) {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not get Issuing Dist Point info"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
if issuingDistPointUrl != crlDistPoint {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: CRL Dist Point URL does not match Issuing Dist Point URL"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
}
if extension.Id.Equal(oids["authorityInformationAccess"]) {
aiaUrl, err = getAIAFromAsn1Extension(extension)
if err != nil || !IsUrl(aiaUrl) {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not get AIA info"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
}
}
doAIAChase := true
if !GlobalConfig.isStrictCrlHandling() {
parsedUrl, err := url.Parse(aiaUrl)
if err != nil || parsedUrl.Host == "" {
doAIAChase = false
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Skipping AIA Chase for CRL " + crlDistPoint}
}
}
if doAIAChase {
if aiaUrl == "" {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: AIA not present"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
certBytes, err := getCertsFromUrl(aiaUrl)
if err != nil || len(certBytes) == 0 {
if shouldTryProxy {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Trying proxy for " + aiaUrl}
martiniX5UProxy := X5UProxyProd
if !GlobalConfig.isAcmeProdMode() {
martiniX5UProxy = X5UProxyDev
}
certBytes, err = getCertsFromUrl(martiniX5UProxy + aiaUrl)
}
if err != nil || len(certBytes) == 0 {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not fetch certs from AIA"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
}
var buf []byte
getCertsFromBytes(certBytes, &buf)
certs, err := x509.ParseCertificates(buf)
if err != nil || len(certs) == 0 {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Could not parse certs from AIA"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
// Check CRL Signature against signing cert. Spec says first cert in AIA chain is the signer
err = revocationList.CheckSignatureFrom(certs[0])
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: CRL signature does not match signer"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
// Build trust chain
intermediatePool := x509.NewCertPool()
for _, thisCert := range certs {
if thisCert.Subject.ToRDNSequence().String() != thisCert.Issuer.ToRDNSequence().String() {
intermediatePool.AddCert(thisCert)
}
}
intermediatePool.AddCert(stiPaRootCert)
// Verify CRL signer is authentic
optsCrlSigner := x509.VerifyOptions{
Roots: intermediatePool,
}
_, err = certs[0].Verify(optsCrlSigner)
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: CRL signer does not descent from STI-PA root"}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
// We need to compare the Subject and Issuer ourselves
subjectRdnSeq := certs[0].Subject.ToRDNSequence()
var subjectName pkix.Name
subjectName.FillFromRDNSequence(&subjectRdnSeq)
issuerRdnSeq := revocationList.Issuer.ToRDNSequence()
var issuerName pkix.Name
issuerName.FillFromRDNSequence(&issuerRdnSeq)
if issuerName.String() != subjectName.String() {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Issuer/Subject Mismatch. Aborting..."}
nextAttempt := thisCacheEntry.IncrementError()
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MsgStr = nextAttempt
responseChan <- statusMsg
return
}
}
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: CRL passed all validation measures"}
thisCacheEntry.Number = revocationList.Number
thisCacheEntry.ErrorCount = 0
thisCacheEntry.Status = stivs.Valid
thisCacheEntry.PreviouslyValid = true
thisCacheEntry.NextUpdate = revocationList.NextUpdate
// Build set of revocation data: Issuer DN & Serial Number
tmpList := make([]*stivs.RevokedCertLight, 0)
for _, revokedCert := range revocationList.RevokedCertificates {
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: About to process revocation with serial number " + revokedCert.SerialNumber.String()}
for index := range revokedCert.Extensions {
if revokedCert.Extensions[index].Id.String() == CertIssuerOid {
var seq asn1.RawValue
_, err := asn1.Unmarshal(revokedCert.Extensions[index].Value, &seq)
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Failed to unmarshal asn1 data from revocation - " + err.Error()}
continue
}
rest := seq.Bytes
for len(rest) > 0 {
var v asn1.RawValue
rest, err = asn1.Unmarshal(rest, &v)
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Failed to unmarshal OID Extension 2.5.29.29 from revocation - " + err.Error()}
continue
}
var myRdnSeq pkix.RDNSequence
_, err = asn1.Unmarshal(v.Bytes, &myRdnSeq)
if err != nil {
logger.LogChan <- &logger.LogMessage{Severity: logger.ERROR, MsgStr: "STI-PA CRL: Failed to unmarshal RDNSequence from revocation - " + err.Error()}
continue
}
var certEntryIssuer pkix.Name
certEntryIssuer.FillFromRDNSequence(&myRdnSeq)
thisRevokedCert := stivs.RevokedCertLight{
SerialNumber: revokedCert.SerialNumber,
Issuer: certEntryIssuer,
}
tmpList = append(tmpList, &thisRevokedCert)
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Finished processing revocation with serial number " + revokedCert.SerialNumber.String()}
}
}
}
}
// Replace revoked certs with new list
thisCacheEntry.Revocations = tmpList
totalRevocations := strconv.Itoa(len(revocationList.RevokedCertificates))
processedRevocations := strconv.Itoa(len(thisCacheEntry.Revocations))
logger.LogChan <- &logger.LogMessage{Severity: logger.INFO, MsgStr: "STI-PA CRL: Successfully processed " + processedRevocations + " of " + totalRevocations + " total revocations"}
durationUntilNextRefresh := "12h" // default refresh
if revocationList.NextUpdate.After(time.Now()) {
if revocationList.NextUpdate.Before(time.Now().Add(12 * time.Hour)) {
// Unless sooner
durationUntilNextRefresh = (time.Until(revocationList.NextUpdate.Add(5 * time.Minute))).String()
}
}
stivs.GetCrlCache().AddUpdateEntry(crlDistPoint, thisCacheEntry)
statusMsg.MessageType = Success
statusMsg.MsgStr = thisCacheEntry.URI + ";" + durationUntilNextRefresh
responseChan <- statusMsg
}
func getCrlDERFromPEMBytes(PEMBytes []byte, buf *[]byte) {
block, _ := pem.Decode(PEMBytes)
if block == nil {
return
}
if block.Type == "X509 CRL" {
*buf = append(*buf, block.Bytes...)
}
}
func scheduleCrlJob(duration time.Duration, uri string, crlWorkChan chan string) {
time.AfterFunc(duration, func() {
crlWorkChan <- uri
})
}