forked from caddyserver/certmagic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
certificates.go
353 lines (315 loc) · 11.3 KB
/
certificates.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
// Copyright 2015 Matthew Holt
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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 certmagic
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"strings"
"time"
"golang.org/x/crypto/ocsp"
)
// Certificate is a tls.Certificate with associated metadata tacked on.
// Even if the metadata can be obtained by parsing the certificate,
// we are more efficient by extracting the metadata onto this struct.
type Certificate struct {
tls.Certificate
// Names is the list of names this certificate is written for.
// The first is the CommonName (if any), the rest are SAN.
Names []string
// NotAfter is when the certificate expires.
NotAfter time.Time
// OCSP contains the certificate's parsed OCSP response.
OCSP *ocsp.Response
// The hex-encoded hash of this cert's chain's bytes.
Hash string
// configs is the list of configs that use or refer to
// The first one is assumed to be the config that is
// "in charge" of this certificate (i.e. determines
// whether it is managed, how it is managed, etc).
// This field will be populated by cacheCertificate.
// Only meddle with it if you know what you're doing!
configs []*Config
// whether this certificate is under our management
managed bool
}
// NeedsRenewal returns true if the certificate is
// expiring soon or has expired.
func (c Certificate) NeedsRenewal() bool {
if c.NotAfter.IsZero() {
return false
}
timeLeft := c.NotAfter.UTC().Sub(time.Now().UTC())
renewDurationBefore := DefaultRenewDurationBefore
if len(c.configs) > 0 && c.configs[0].RenewDurationBefore > 0 {
renewDurationBefore = c.configs[0].RenewDurationBefore
}
return timeLeft < renewDurationBefore
}
// CacheManagedCertificate loads the certificate for domain into the
// cache, from the TLS storage for managed certificates. It returns a
// copy of the Certificate that was put into the cache.
//
// This is a lower-level method; normally you'll call Manage() instead.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
certRes, err := cfg.loadCertResource(domain)
if err != nil {
return Certificate{}, err
}
cert, err := cfg.makeCertificateWithOCSP(certRes.Certificate, certRes.PrivateKey)
if err != nil {
return cert, err
}
cert.managed = true
if cfg.OnEvent != nil {
cfg.OnEvent("cached_managed_cert", cert.Names)
}
return cfg.cacheCertificate(cert), nil
}
// CacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
// and keyFile, which must be in PEM format. It stores the certificate in
// the in-memory cache.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
cert, err := cfg.makeCertificateFromDiskWithOCSP(certFile, keyFile)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
return nil
}
// CacheUnmanagedTLSCertificate adds tlsCert to the certificate cache.
// It staples OCSP if possible.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
var cert Certificate
err := fillCertFromLeaf(&cert, tlsCert)
if err != nil {
return err
}
err = cfg.certCache.stapleOCSP(&cert, nil)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
cfg.cacheCertificate(cert)
return nil
}
// CacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
// of the certificate and key, then caches it in memory.
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
return nil
}
// makeCertificateFromDiskWithOCSP makes a Certificate by loading the
// certificate and key files. It fills out all the fields in
// the certificate except for the Managed and OnDemand flags.
// (It is up to the caller to set those.) It staples OCSP.
func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Certificate, error) {
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return Certificate{}, err
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return Certificate{}, err
}
return cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
}
// makeCertificate turns a certificate PEM bundle and a key PEM block into
// a Certificate with necessary metadata from parsing its bytes filled into
// its struct fields for convenience (except for the OnDemand and Managed
// flags; it is up to the caller to set those properties!). This function
// does NOT staple OCSP.
func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
var cert Certificate
// Convert to a tls.Certificate
tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
// Extract necessary metadata
err = fillCertFromLeaf(&cert, tlsCert)
if err != nil {
return cert, err
}
return cert, nil
}
// makeCertificateWithOCSP is the same as makeCertificate except that it also
// staples OCSP to the certificate.
func (cfg *Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
cert, err := cfg.makeCertificate(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
err = cfg.certCache.stapleOCSP(&cert, certPEMBlock)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
return cert, nil
}
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
if len(tlsCert.Certificate) == 0 {
return fmt.Errorf("certificate is empty")
}
cert.Certificate = tlsCert
// the leaf cert should be the one for the site; it has what we need
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
if err != nil {
return err
}
if leaf.Subject.CommonName != "" { // TODO: CommonName is deprecated
cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
}
for _, name := range leaf.DNSNames {
if name != leaf.Subject.CommonName { // TODO: CommonName is deprecated
cert.Names = append(cert.Names, strings.ToLower(name))
}
}
for _, ip := range leaf.IPAddresses {
if ipStr := ip.String(); ipStr != leaf.Subject.CommonName { // TODO: CommonName is deprecated
cert.Names = append(cert.Names, strings.ToLower(ipStr))
}
}
for _, email := range leaf.EmailAddresses {
if email != leaf.Subject.CommonName { // TODO: CommonName is deprecated
cert.Names = append(cert.Names, strings.ToLower(email))
}
}
if len(cert.Names) == 0 {
return fmt.Errorf("certificate has no names")
}
// save the hash of this certificate (chain) and
// expiration date, for necessity and efficiency
cert.Hash = hashCertificateChain(cert.Certificate.Certificate)
cert.NotAfter = leaf.NotAfter
return nil
}
// managedCertInStorageExpiresSoon returns true if cert (being a
// managed certificate) is expiring within RenewDurationBefore.
// It returns false if there was an error checking the expiration
// of the certificate as found in storage, or if the certificate
// in storage is NOT expiring soon. A certificate that is expiring
// soon in our cache but is not expiring soon in storage probably
// means that another instance renewed the certificate in the
// meantime, and it would be a good idea to simply load the cert
// into our cache rather than repeating the renewal process again.
func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
if len(cert.configs) == 0 {
return false, fmt.Errorf("no configs for certificate")
}
cfg := cert.configs[0]
certRes, err := cfg.loadCertResource(cert.Names[0])
if err != nil {
return false, err
}
tlsCert, err := tls.X509KeyPair(certRes.Certificate, certRes.PrivateKey)
if err != nil {
return false, err
}
leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
if err != nil {
return false, err
}
timeLeft := leaf.NotAfter.Sub(time.Now().UTC())
return timeLeft < cfg.RenewDurationBefore, nil
}
// cacheCertificate adds cert to the in-memory cache. If a certificate
// with the same hash is already cached, it is NOT overwritten; instead,
// cfg is added to the existing certificate's list of configs if not
// already in the list. Then all the names on cert are used to add
// entries to cfg.certificates (the config's name lookup map).
// Then the certificate is stored/updated in the cache. It returns
// a copy of the certificate that ends up being stored in the cache.
//
// It is VERY important, even for some test cases, that the Hash field
// of the cert be set properly.
//
// This function is safe for concurrent use.
func (cfg *Config) cacheCertificate(cert Certificate) Certificate {
cfg.certCache.mu.Lock()
defer cfg.certCache.mu.Unlock()
// if this certificate already exists in the cache,
// use it instead of overwriting it -- very important!
if existingCert, ok := cfg.certCache.cache[cert.Hash]; ok {
cert = existingCert
}
// attach this config to the certificate so we know which
// configs are referencing/using the certificate, but don't
// duplicate entries
var found bool
for _, c := range cert.configs {
if c == cfg {
found = true
break
}
}
if !found {
cert.configs = append(cert.configs, cfg)
}
// key the certificate by all its names for this config only,
// this is how we find the certificate during handshakes
// (yes, if certs overlap in the names they serve, one will
// overwrite another here, but that's just how it goes)
for _, name := range cert.Names {
cfg.certificates[name] = cert.Hash
}
// store the certificate
cfg.certCache.cache[cert.Hash] = cert
return cert
}
// HostQualifies returns true if the hostname alone
// appears eligible for automagic TLS. For example:
// localhost, empty hostname, and IP addresses are
// not eligible because we cannot obtain certificates
// for those names. Wildcard names are allowed, as long
// as they conform to CABF requirements (only one wildcard
// label, and it must be the left-most label).
func HostQualifies(hostname string) bool {
return hostname != "localhost" && // localhost is ineligible
// hostname must not be empty
strings.TrimSpace(hostname) != "" &&
// only one wildcard label allowed, and it must be left-most
(!strings.Contains(hostname, "*") ||
(strings.Count(hostname, "*") == 1 &&
strings.HasPrefix(hostname, "*."))) &&
// must not start or end with a dot
!strings.HasPrefix(hostname, ".") &&
!strings.HasSuffix(hostname, ".") &&
// cannot be an IP address, see
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
net.ParseIP(hostname) == nil
}