forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 1
/
crl_util.go
195 lines (165 loc) · 6.03 KB
/
crl_util.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
package pki
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"time"
"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/logical"
)
type revocationInfo struct {
CertificateBytes []byte `json:"certificate_bytes"`
RevocationTime int64 `json:"revocation_time"`
}
// Revokes a cert, and tries to be smart about error recovery
func revokeCert(b *backend, req *logical.Request, serial string) (*logical.Response, error) {
alreadyRevoked := false
var revInfo revocationInfo
certEntry, err := fetchCertBySerial(req, "revoked/", serial)
// Don't check error because it's expected that it may fail here;
// just check for existence
if certEntry != nil {
// Verify that it is also deleted from certs/
// in case of partial failure from an earlier run.
certEntry, _ = fetchCertBySerial(req, "certs/", serial)
if certEntry == nil {
// Everything seems sane, so don't rebuild the CRL
return nil, nil
}
// Still exists in certs/; set the revocation info, below it will
// be removed from certs/ and the CRL rotated
alreadyRevoked = true
revEntry, err := req.Storage.Get("revoked/" + serial)
if revEntry == nil || err != nil {
return nil, fmt.Errorf("Error getting existing revocation info")
}
err = revEntry.DecodeJSON(&revInfo)
if err != nil {
return nil, fmt.Errorf("Error decoding existing revocation info")
}
}
if !alreadyRevoked {
certEntry, err = fetchCertBySerial(req, "certs/", serial)
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
cert, err := x509.ParseCertificate(certEntry.Value)
if err != nil {
return nil, fmt.Errorf("Error parsing certificate")
}
if cert == nil {
return nil, fmt.Errorf("Got a nil certificate")
}
if cert.NotAfter.Before(time.Now()) {
return nil, nil
}
revInfo.CertificateBytes = certEntry.Value
revInfo.RevocationTime = time.Now().Unix()
certEntry, err = logical.StorageEntryJSON("revoked/"+serial, revInfo)
if err != nil {
return nil, fmt.Errorf("Error creating revocation entry")
}
err = req.Storage.Put(certEntry)
if err != nil {
return nil, fmt.Errorf("Error saving revoked certificate to new location")
}
}
crlErr := buildCRL(b, req)
switch crlErr.(type) {
case certutil.UserError:
return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil
case certutil.InternalError:
return nil, fmt.Errorf("Error encountered during CRL building: %s", crlErr)
}
err = req.Storage.Delete("certs/" + serial)
if err != nil {
return nil, fmt.Errorf("Error deleting cert from valid-certs location")
}
return &logical.Response{
Data: map[string]interface{}{
"revocation_time": revInfo.RevocationTime,
},
}, nil
}
// Builds a CRL by going through the list of revoked certificates and building
// a new CRL with the stored revocation times and serial numbers.
//
// If a certificate has already expired, it will be removed entirely rather than
// become part of the new CRL.
func buildCRL(b *backend, req *logical.Request) error {
revokedSerials, err := req.Storage.List("revoked/")
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error fetching list of revoked certs: %s", err)}
}
revokedCerts := []pkix.RevokedCertificate{}
var revInfo revocationInfo
for _, serial := range revokedSerials {
revokedEntry, err := req.Storage.Get("revoked/" + serial)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Unable to fetch revoked cert with serial %s: %s", serial, err)}
}
if revokedEntry == nil {
return certutil.InternalError{Err: fmt.Sprintf("Revoked certificate entry for serial %s is nil", serial)}
}
if revokedEntry.Value == nil || len(revokedEntry.Value) == 0 {
// TODO: In this case, remove it and continue? How likely is this to
// happen? Alternately, could skip it entirely, or could implement a
// delete function so that there is a way to remove these
return certutil.InternalError{Err: fmt.Sprintf("Found revoked serial but actual certificate is empty")}
}
err = revokedEntry.DecodeJSON(&revInfo)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error decoding revocation entry for serial %s: %s", serial, err)}
}
revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Unable to parse stored revoked certificate with serial %s: %s", serial, err)}
}
if revokedCert.NotAfter.Before(time.Now()) {
err = req.Storage.Delete(serial)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Unable to delete revoked, expired certificate with serial %s: %s", serial, err)}
}
continue
}
revokedCerts = append(revokedCerts, pkix.RevokedCertificate{
SerialNumber: revokedCert.SerialNumber,
RevocationTime: time.Unix(revInfo.RevocationTime, 0),
})
}
signingBundle, caErr := fetchCAInfo(req)
switch caErr.(type) {
case certutil.UserError:
return certutil.UserError{Err: fmt.Sprintf("Could not fetch the CA certificate: %s", caErr)}
case certutil.InternalError:
return certutil.InternalError{Err: fmt.Sprintf("Error fetching CA certificate: %s", caErr)}
}
crlLifetime := b.crlLifetime
crlInfo, err := b.CRL(req.Storage)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error fetching CRL config information: %s", err)}
}
if crlInfo != nil {
crlDur, err := time.ParseDuration(crlInfo.Expiry)
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error parsing CRL duration of %s", crlInfo.Expiry)}
}
crlLifetime = crlDur
}
crlBytes, err := signingBundle.Certificate.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime))
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error creating new CRL: %s", err)}
}
err = req.Storage.Put(&logical.StorageEntry{
Key: "crl",
Value: crlBytes,
})
if err != nil {
return certutil.InternalError{Err: fmt.Sprintf("Error storing CRL: %s", err)}
}
return nil
}