diff options
author | Mike Crute <mike@crute.us> | 2023-08-01 18:26:05 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2023-08-01 18:26:09 -0700 |
commit | b3b8bd0d937b6143cda877127dae4cb1385b866e (patch) | |
tree | f6d09b191a74fcdd5b6ffc98bc3bef18eb98e2b9 /crypto | |
parent | b29262b0a5384246b04764488869d1fbc81e2d1a (diff) | |
download | golib-b3b8bd0d937b6143cda877127dae4cb1385b866e.tar.bz2 golib-b3b8bd0d937b6143cda877127dae4cb1385b866e.tar.xz golib-b3b8bd0d937b6143cda877127dae4cb1385b866e.zip |
crypto: fork x/crypto autocertv0.6.0
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/acme/autocert/ABOUT_FORK.txt | 6 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/autocert.go | 1286 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/autocert_test.go | 1058 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/cache.go | 135 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/cache_test.go | 66 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/internal/acmetest/ca.go | 817 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/listener.go | 155 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/renewal.go | 156 | ||||
-rw-r--r-- | crypto/acme/autocert/fork/renewal_test.go | 269 | ||||
-rw-r--r-- | crypto/ocsp/client.go | 125 |
10 files changed, 4073 insertions, 0 deletions
diff --git a/crypto/acme/autocert/ABOUT_FORK.txt b/crypto/acme/autocert/ABOUT_FORK.txt new file mode 100644 index 0000000..e8e41f0 --- /dev/null +++ b/crypto/acme/autocert/ABOUT_FORK.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | This folder is a fork of x/crypto/acme/autocert that contains the following | ||
2 | changes. This is being carried here because stdlib is very slow to picking up | ||
3 | the changes. | ||
4 | |||
5 | - DNS-01 challenges | ||
6 | - OCSP stapling | ||
diff --git a/crypto/acme/autocert/fork/autocert.go b/crypto/acme/autocert/fork/autocert.go new file mode 100644 index 0000000..d57c49a --- /dev/null +++ b/crypto/acme/autocert/fork/autocert.go | |||
@@ -0,0 +1,1286 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | // Package autocert provides automatic access to certificates from Let's Encrypt | ||
6 | // and any other ACME-based CA. | ||
7 | // | ||
8 | // This package is a work in progress and makes no API stability promises. | ||
9 | package fork | ||
10 | |||
11 | import ( | ||
12 | "bytes" | ||
13 | "context" | ||
14 | "crypto" | ||
15 | "crypto/ecdsa" | ||
16 | "crypto/elliptic" | ||
17 | "crypto/rand" | ||
18 | "crypto/rsa" | ||
19 | "crypto/tls" | ||
20 | "crypto/x509" | ||
21 | "crypto/x509/pkix" | ||
22 | "encoding/pem" | ||
23 | "errors" | ||
24 | "fmt" | ||
25 | "io" | ||
26 | mathrand "math/rand" | ||
27 | "net" | ||
28 | "net/http" | ||
29 | "path" | ||
30 | "strings" | ||
31 | "sync" | ||
32 | "time" | ||
33 | |||
34 | "code.crute.us/mcrute/golib/crypto/ocsp" | ||
35 | |||
36 | "golang.org/x/crypto/acme" | ||
37 | "golang.org/x/net/idna" | ||
38 | ) | ||
39 | |||
40 | // DefaultACMEDirectory is the default ACME Directory URL used when the Manager's Client is nil. | ||
41 | const DefaultACMEDirectory = "https://acme-v02.api.letsencrypt.org/directory" | ||
42 | |||
43 | // createCertRetryAfter is how much time to wait before removing a failed state | ||
44 | // entry due to an unsuccessful createCert call. | ||
45 | // This is a variable instead of a const for testing. | ||
46 | // TODO: Consider making it configurable or an exp backoff? | ||
47 | var createCertRetryAfter = time.Minute | ||
48 | |||
49 | // pseudoRand is safe for concurrent use. | ||
50 | var pseudoRand *lockedMathRand | ||
51 | |||
52 | var errPreRFC = errors.New("autocert: ACME server doesn't support RFC 8555") | ||
53 | |||
54 | func init() { | ||
55 | src := mathrand.NewSource(time.Now().UnixNano()) | ||
56 | pseudoRand = &lockedMathRand{rnd: mathrand.New(src)} | ||
57 | } | ||
58 | |||
59 | // AcceptTOS is a Manager.Prompt function that always returns true to | ||
60 | // indicate acceptance of the CA's Terms of Service during account | ||
61 | // registration. | ||
62 | func AcceptTOS(tosURL string) bool { return true } | ||
63 | |||
64 | // HostPolicy specifies which host names the Manager is allowed to respond to. | ||
65 | // It returns a non-nil error if the host should be rejected. | ||
66 | // The returned error is accessible via tls.Conn.Handshake and its callers. | ||
67 | // See Manager's HostPolicy field and GetCertificate method docs for more details. | ||
68 | type HostPolicy func(ctx context.Context, host string) error | ||
69 | |||
70 | // HostWhitelist returns a policy where only the specified host names are allowed. | ||
71 | // Only exact matches are currently supported. Subdomains, regexp or wildcard | ||
72 | // will not match. | ||
73 | // | ||
74 | // Note that all hosts will be converted to Punycode via idna.Lookup.ToASCII so that | ||
75 | // Manager.GetCertificate can handle the Unicode IDN and mixedcase hosts correctly. | ||
76 | // Invalid hosts will be silently ignored. | ||
77 | func HostWhitelist(hosts ...string) HostPolicy { | ||
78 | whitelist := make(map[string]bool, len(hosts)) | ||
79 | for _, h := range hosts { | ||
80 | if h, err := idna.Lookup.ToASCII(h); err == nil { | ||
81 | whitelist[h] = true | ||
82 | } | ||
83 | } | ||
84 | return func(_ context.Context, host string) error { | ||
85 | if !whitelist[host] { | ||
86 | return fmt.Errorf("acme/autocert: host %q not configured in HostWhitelist", host) | ||
87 | } | ||
88 | return nil | ||
89 | } | ||
90 | } | ||
91 | |||
92 | // defaultHostPolicy is used when Manager.HostPolicy is not set. | ||
93 | func defaultHostPolicy(context.Context, string) error { | ||
94 | return nil | ||
95 | } | ||
96 | |||
97 | // DNSManager is used by the Manager for handling dns-01 challenges with | ||
98 | // external DNS services. | ||
99 | type DNSManager interface { | ||
100 | // Fulfill receives a record in the format returned by | ||
101 | // acme.DNS01ChallengeRecord and places it into an external DNS service. | ||
102 | // | ||
103 | // The ACME challenge will be validated by the CA soon after this function | ||
104 | // returns. Therefore, this should ensure that DNS has propagated such that | ||
105 | // the ACME server can validate the record and block until it can verify | ||
106 | // the propagation. | ||
107 | Fulfill(ctx context.Context, domain string, record string) error | ||
108 | |||
109 | // Cleanup receives a record in the format returned by | ||
110 | // acme.DNS01ChallengeRecord and should remove it from the external DNS | ||
111 | // service. | ||
112 | Cleanup(ctx context.Context, domain string, record string) | ||
113 | } | ||
114 | |||
115 | // Manager is a stateful certificate manager built on top of acme.Client. | ||
116 | // It obtains and refreshes certificates automatically using "tls-alpn-01", | ||
117 | // "http-01", or "dns-01" challenge types, as well as providing them to a | ||
118 | // TLS server via tls.Config. | ||
119 | // | ||
120 | // You must specify a cache implementation, such as DirCache, | ||
121 | // to reuse obtained certificates across program restarts. | ||
122 | // Otherwise your server is very likely to exceed the certificate | ||
123 | // issuer's request rate limits. | ||
124 | type Manager struct { | ||
125 | // Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS). | ||
126 | // The registration may require the caller to agree to the CA's TOS. | ||
127 | // If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report | ||
128 | // whether the caller agrees to the terms. | ||
129 | // | ||
130 | // To always accept the terms, the callers can use AcceptTOS. | ||
131 | Prompt func(tosURL string) bool | ||
132 | |||
133 | // Cache optionally stores and retrieves previously-obtained certificates | ||
134 | // and other state. If nil, certs will only be cached for the lifetime of | ||
135 | // the Manager. Multiple Managers can share the same Cache. | ||
136 | // | ||
137 | // Using a persistent Cache, such as DirCache, is strongly recommended. | ||
138 | Cache Cache | ||
139 | |||
140 | // HostPolicy controls which domains the Manager will attempt | ||
141 | // to retrieve new certificates for. It does not affect cached certs. | ||
142 | // | ||
143 | // If non-nil, HostPolicy is called before requesting a new cert. | ||
144 | // If nil, all hosts are currently allowed. This is not recommended, | ||
145 | // as it opens a potential attack where clients connect to a server | ||
146 | // by IP address and pretend to be asking for an incorrect host name. | ||
147 | // Manager will attempt to obtain a certificate for that host, incorrectly, | ||
148 | // eventually reaching the CA's rate limit for certificate requests | ||
149 | // and making it impossible to obtain actual certificates. | ||
150 | // | ||
151 | // See GetCertificate for more details. | ||
152 | HostPolicy HostPolicy | ||
153 | |||
154 | // RenewBefore optionally specifies how early certificates should | ||
155 | // be renewed before they expire. | ||
156 | // | ||
157 | // If zero, they're renewed 30 days before expiration. | ||
158 | RenewBefore time.Duration | ||
159 | |||
160 | // Client is used to perform low-level operations, such as account registration | ||
161 | // and requesting new certificates. | ||
162 | // | ||
163 | // If Client is nil, a zero-value acme.Client is used with DefaultACMEDirectory | ||
164 | // as the directory endpoint. | ||
165 | // If the Client.Key is nil, a new ECDSA P-256 key is generated and, | ||
166 | // if Cache is not nil, stored in cache. | ||
167 | // | ||
168 | // Mutating the field after the first call of GetCertificate method will have no effect. | ||
169 | Client *acme.Client | ||
170 | |||
171 | // Email optionally specifies a contact email address. | ||
172 | // This is used by CAs, such as Let's Encrypt, to notify about problems | ||
173 | // with issued certificates. | ||
174 | // | ||
175 | // If the Client's account key is already registered, Email is not used. | ||
176 | Email string | ||
177 | |||
178 | // ForceRSA used to make the Manager generate RSA certificates. It is now ignored. | ||
179 | // | ||
180 | // Deprecated: the Manager will request the correct type of certificate based | ||
181 | // on what each client supports. | ||
182 | ForceRSA bool | ||
183 | |||
184 | // StapleOCSP makes the Manager request an OCSP response from the CA | ||
185 | // and will staple it to the certificate returned by GetCertificate. | ||
186 | // This is currently best-effort and if the CA fails to return a valid | ||
187 | // OCSP response the certificate will be returned without the staple. | ||
188 | StapleOCSP bool | ||
189 | |||
190 | // ExtraExtensions are used when generating a new CSR (Certificate Request), | ||
191 | // thus allowing customization of the resulting certificate. | ||
192 | // For instance, TLS Feature Extension (RFC 7633) can be used | ||
193 | // to prevent an OCSP downgrade attack. | ||
194 | // | ||
195 | // The field value is passed to crypto/x509.CreateCertificateRequest | ||
196 | // in the template's ExtraExtensions field as is. | ||
197 | ExtraExtensions []pkix.Extension | ||
198 | |||
199 | // ExternalAccountBinding optionally represents an arbitrary binding to an | ||
200 | // account of the CA to which the ACME server is tied. | ||
201 | // See RFC 8555, Section 7.3.4 for more details. | ||
202 | ExternalAccountBinding *acme.ExternalAccountBinding | ||
203 | |||
204 | // DNSManager is used to respond to dns-01 challenges returned from the CA. | ||
205 | // If this field is nil then DNS challenges will not be requested from the | ||
206 | // CA. | ||
207 | DNSManager DNSManager | ||
208 | |||
209 | clientMu sync.Mutex | ||
210 | client *acme.Client // initialized by acmeClient method | ||
211 | ocspClient *ocsp.Client // initialized by getOcspClient method | ||
212 | |||
213 | stateMu sync.Mutex | ||
214 | state map[certKey]*certState | ||
215 | |||
216 | // renewal tracks the set of domains currently running renewal timers. | ||
217 | renewalMu sync.Mutex | ||
218 | renewal map[certKey]*domainRenewal | ||
219 | |||
220 | // challengeMu guards tryHTTP01, certTokens and httpTokens. | ||
221 | challengeMu sync.RWMutex | ||
222 | // tryHTTP01 indicates whether the Manager should try "http-01" challenge type | ||
223 | // during the authorization flow. | ||
224 | tryHTTP01 bool | ||
225 | // httpTokens contains response body values for http-01 challenges | ||
226 | // and is keyed by the URL path at which a challenge response is expected | ||
227 | // to be provisioned. | ||
228 | // The entries are stored for the duration of the authorization flow. | ||
229 | httpTokens map[string][]byte | ||
230 | // certTokens contains temporary certificates for tls-alpn-01 challenges | ||
231 | // and is keyed by the domain name which matches the ClientHello server name. | ||
232 | // The entries are stored for the duration of the authorization flow. | ||
233 | certTokens map[string]*tls.Certificate | ||
234 | |||
235 | // nowFunc, if not nil, returns the current time. This may be set for | ||
236 | // testing purposes. | ||
237 | nowFunc func() time.Time | ||
238 | } | ||
239 | |||
240 | // certKey is the key by which certificates are tracked in state, renewal and cache. | ||
241 | type certKey struct { | ||
242 | domain string // without trailing dot | ||
243 | isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA) | ||
244 | isToken bool // tls-based challenge token cert; key type is undefined regardless of isRSA | ||
245 | } | ||
246 | |||
247 | func (c certKey) String() string { | ||
248 | if c.isToken { | ||
249 | return c.domain + "+token" | ||
250 | } | ||
251 | if c.isRSA { | ||
252 | return c.domain + "+rsa" | ||
253 | } | ||
254 | return c.domain | ||
255 | } | ||
256 | |||
257 | // TLSConfig creates a new TLS config suitable for net/http.Server servers, | ||
258 | // supporting HTTP/2 and the tls-alpn-01 ACME challenge type. | ||
259 | func (m *Manager) TLSConfig() *tls.Config { | ||
260 | return &tls.Config{ | ||
261 | GetCertificate: m.GetCertificate, | ||
262 | NextProtos: []string{ | ||
263 | "h2", "http/1.1", // enable HTTP/2 | ||
264 | acme.ALPNProto, // enable tls-alpn ACME challenges | ||
265 | }, | ||
266 | } | ||
267 | } | ||
268 | |||
269 | // GetCertificate implements the tls.Config.GetCertificate hook. | ||
270 | // It provides a TLS certificate for hello.ServerName host, including answering | ||
271 | // tls-alpn-01 challenges. | ||
272 | // All other fields of hello are ignored. | ||
273 | // | ||
274 | // If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting | ||
275 | // a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation. | ||
276 | // The error is propagated back to the caller of GetCertificate and is user-visible. | ||
277 | // This does not affect cached certs. See HostPolicy field description for more details. | ||
278 | // | ||
279 | // If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will | ||
280 | // also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01. | ||
281 | // If DNSManager is specified, no additional configuration is required and GetCertificate | ||
282 | // can be used directly for dns-01 challenges. | ||
283 | func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
284 | if m.Prompt == nil { | ||
285 | return nil, errors.New("acme/autocert: Manager.Prompt not set") | ||
286 | } | ||
287 | |||
288 | name := hello.ServerName | ||
289 | if name == "" { | ||
290 | return nil, errors.New("acme/autocert: missing server name") | ||
291 | } | ||
292 | if !strings.Contains(strings.Trim(name, "."), ".") { | ||
293 | return nil, errors.New("acme/autocert: server name component count invalid") | ||
294 | } | ||
295 | |||
296 | // Note that this conversion is necessary because some server names in the handshakes | ||
297 | // started by some clients (such as cURL) are not converted to Punycode, which will | ||
298 | // prevent us from obtaining certificates for them. In addition, we should also treat | ||
299 | // example.com and EXAMPLE.COM as equivalent and return the same certificate for them. | ||
300 | // Fortunately, this conversion also helped us deal with this kind of mixedcase problems. | ||
301 | // | ||
302 | // Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use | ||
303 | // idna.Punycode.ToASCII (or just idna.ToASCII) here. | ||
304 | name, err := idna.Lookup.ToASCII(name) | ||
305 | if err != nil { | ||
306 | return nil, errors.New("acme/autocert: server name contains invalid character") | ||
307 | } | ||
308 | |||
309 | // In the worst-case scenario, the timeout needs to account for caching, host policy, | ||
310 | // domain ownership verification and certificate issuance. | ||
311 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) | ||
312 | defer cancel() | ||
313 | |||
314 | // Check whether this is a token cert requested for TLS-ALPN challenge. | ||
315 | if wantsTokenCert(hello) { | ||
316 | m.challengeMu.RLock() | ||
317 | defer m.challengeMu.RUnlock() | ||
318 | if cert := m.certTokens[name]; cert != nil { | ||
319 | return cert, nil | ||
320 | } | ||
321 | if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil { | ||
322 | return cert, nil | ||
323 | } | ||
324 | // TODO: cache error results? | ||
325 | return nil, fmt.Errorf("acme/autocert: no token cert for %q", name) | ||
326 | } | ||
327 | |||
328 | // regular domain | ||
329 | ck := certKey{ | ||
330 | domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114 | ||
331 | isRSA: !supportsECDSA(hello), | ||
332 | } | ||
333 | cert, err := m.cert(ctx, ck) | ||
334 | if err == nil { | ||
335 | return cert, nil | ||
336 | } | ||
337 | if err != ErrCacheMiss { | ||
338 | return nil, err | ||
339 | } | ||
340 | |||
341 | // first-time | ||
342 | if err := m.hostPolicy()(ctx, name); err != nil { | ||
343 | return nil, err | ||
344 | } | ||
345 | cert, err = m.createCert(ctx, ck) | ||
346 | if err != nil { | ||
347 | return nil, err | ||
348 | } | ||
349 | m.cachePut(ctx, ck, cert) | ||
350 | return cert, nil | ||
351 | } | ||
352 | |||
353 | // wantsTokenCert reports whether a TLS request with SNI is made by a CA server | ||
354 | // for a challenge verification. | ||
355 | func wantsTokenCert(hello *tls.ClientHelloInfo) bool { | ||
356 | // tls-alpn-01 | ||
357 | if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto { | ||
358 | return true | ||
359 | } | ||
360 | return false | ||
361 | } | ||
362 | |||
363 | func supportsECDSA(hello *tls.ClientHelloInfo) bool { | ||
364 | // The "signature_algorithms" extension, if present, limits the key exchange | ||
365 | // algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1. | ||
366 | if hello.SignatureSchemes != nil { | ||
367 | ecdsaOK := false | ||
368 | schemeLoop: | ||
369 | for _, scheme := range hello.SignatureSchemes { | ||
370 | const tlsECDSAWithSHA1 tls.SignatureScheme = 0x0203 // constant added in Go 1.10 | ||
371 | switch scheme { | ||
372 | case tlsECDSAWithSHA1, tls.ECDSAWithP256AndSHA256, | ||
373 | tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512: | ||
374 | ecdsaOK = true | ||
375 | break schemeLoop | ||
376 | } | ||
377 | } | ||
378 | if !ecdsaOK { | ||
379 | return false | ||
380 | } | ||
381 | } | ||
382 | if hello.SupportedCurves != nil { | ||
383 | ecdsaOK := false | ||
384 | for _, curve := range hello.SupportedCurves { | ||
385 | if curve == tls.CurveP256 { | ||
386 | ecdsaOK = true | ||
387 | break | ||
388 | } | ||
389 | } | ||
390 | if !ecdsaOK { | ||
391 | return false | ||
392 | } | ||
393 | } | ||
394 | for _, suite := range hello.CipherSuites { | ||
395 | switch suite { | ||
396 | case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, | ||
397 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | ||
398 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, | ||
399 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, | ||
400 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
401 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | ||
402 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: | ||
403 | return true | ||
404 | } | ||
405 | } | ||
406 | return false | ||
407 | } | ||
408 | |||
409 | // HTTPHandler configures the Manager to provision ACME "http-01" challenge responses. | ||
410 | // It returns an http.Handler that responds to the challenges and must be | ||
411 | // running on port 80. If it receives a request that is not an ACME challenge, | ||
412 | // it delegates the request to the optional fallback handler. | ||
413 | // | ||
414 | // If fallback is nil, the returned handler redirects all GET and HEAD requests | ||
415 | // to the default TLS port 443 with 302 Found status code, preserving the original | ||
416 | // request path and query. It responds with 400 Bad Request to all other HTTP methods. | ||
417 | // The fallback is not protected by the optional HostPolicy. | ||
418 | // | ||
419 | // Because the fallback handler is run with unencrypted port 80 requests, | ||
420 | // the fallback should not serve TLS-only requests. | ||
421 | // | ||
422 | // If HTTPHandler is never called, the Manager will use the "tls-alpn-01" | ||
423 | // challenge for domain verification and/or "dns-01" if DNSManager is specified. | ||
424 | func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler { | ||
425 | m.challengeMu.Lock() | ||
426 | defer m.challengeMu.Unlock() | ||
427 | m.tryHTTP01 = true | ||
428 | |||
429 | if fallback == nil { | ||
430 | fallback = http.HandlerFunc(handleHTTPRedirect) | ||
431 | } | ||
432 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
433 | if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") { | ||
434 | fallback.ServeHTTP(w, r) | ||
435 | return | ||
436 | } | ||
437 | // A reasonable context timeout for cache and host policy only, | ||
438 | // because we don't wait for a new certificate issuance here. | ||
439 | ctx, cancel := context.WithTimeout(r.Context(), time.Minute) | ||
440 | defer cancel() | ||
441 | if err := m.hostPolicy()(ctx, r.Host); err != nil { | ||
442 | http.Error(w, err.Error(), http.StatusForbidden) | ||
443 | return | ||
444 | } | ||
445 | data, err := m.httpToken(ctx, r.URL.Path) | ||
446 | if err != nil { | ||
447 | http.Error(w, err.Error(), http.StatusNotFound) | ||
448 | return | ||
449 | } | ||
450 | w.Write(data) | ||
451 | }) | ||
452 | } | ||
453 | |||
454 | func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { | ||
455 | if r.Method != "GET" && r.Method != "HEAD" { | ||
456 | http.Error(w, "Use HTTPS", http.StatusBadRequest) | ||
457 | return | ||
458 | } | ||
459 | target := "https://" + stripPort(r.Host) + r.URL.RequestURI() | ||
460 | http.Redirect(w, r, target, http.StatusFound) | ||
461 | } | ||
462 | |||
463 | func stripPort(hostport string) string { | ||
464 | host, _, err := net.SplitHostPort(hostport) | ||
465 | if err != nil { | ||
466 | return hostport | ||
467 | } | ||
468 | return net.JoinHostPort(host, "443") | ||
469 | } | ||
470 | |||
471 | // cert returns an existing certificate either from m.state or cache. | ||
472 | // If a certificate is found in cache but not in m.state, the latter will be filled | ||
473 | // with the cached value. | ||
474 | func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) { | ||
475 | m.stateMu.Lock() | ||
476 | if s, ok := m.state[ck]; ok { | ||
477 | m.stateMu.Unlock() | ||
478 | s.RLock() | ||
479 | defer s.RUnlock() | ||
480 | m.stapleOcsp(ctx, s) | ||
481 | return s.tlscert() | ||
482 | } | ||
483 | defer m.stateMu.Unlock() | ||
484 | cert, err := m.cacheGet(ctx, ck) | ||
485 | if err != nil { | ||
486 | return nil, err | ||
487 | } | ||
488 | signer, ok := cert.PrivateKey.(crypto.Signer) | ||
489 | if !ok { | ||
490 | return nil, errors.New("acme/autocert: private key cannot sign") | ||
491 | } | ||
492 | if m.state == nil { | ||
493 | m.state = make(map[certKey]*certState) | ||
494 | } | ||
495 | s := &certState{ | ||
496 | key: signer, | ||
497 | cert: cert.Certificate, | ||
498 | leaf: cert.Leaf, | ||
499 | } | ||
500 | m.state[ck] = s | ||
501 | m.stapleOcsp(ctx, s) // Should happen before startRenew | ||
502 | m.startRenew(ck, s) | ||
503 | return cert, nil | ||
504 | } | ||
505 | |||
506 | // cacheGet always returns a valid certificate, or an error otherwise. | ||
507 | // If a cached certificate exists but is not valid, ErrCacheMiss is returned. | ||
508 | func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) { | ||
509 | if m.Cache == nil { | ||
510 | return nil, ErrCacheMiss | ||
511 | } | ||
512 | data, err := m.Cache.Get(ctx, ck.String()) | ||
513 | if err != nil { | ||
514 | return nil, err | ||
515 | } | ||
516 | |||
517 | // private | ||
518 | priv, pub := pem.Decode(data) | ||
519 | if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { | ||
520 | return nil, ErrCacheMiss | ||
521 | } | ||
522 | privKey, err := parsePrivateKey(priv.Bytes) | ||
523 | if err != nil { | ||
524 | return nil, err | ||
525 | } | ||
526 | |||
527 | // public | ||
528 | var pubDER [][]byte | ||
529 | for len(pub) > 0 { | ||
530 | var b *pem.Block | ||
531 | b, pub = pem.Decode(pub) | ||
532 | if b == nil { | ||
533 | break | ||
534 | } | ||
535 | pubDER = append(pubDER, b.Bytes) | ||
536 | } | ||
537 | if len(pub) > 0 { | ||
538 | // Leftover content not consumed by pem.Decode. Corrupt. Ignore. | ||
539 | return nil, ErrCacheMiss | ||
540 | } | ||
541 | |||
542 | // verify and create TLS cert | ||
543 | leaf, err := validCert(ck, pubDER, privKey, m.now()) | ||
544 | if err != nil { | ||
545 | return nil, ErrCacheMiss | ||
546 | } | ||
547 | tlscert := &tls.Certificate{ | ||
548 | Certificate: pubDER, | ||
549 | PrivateKey: privKey, | ||
550 | Leaf: leaf, | ||
551 | } | ||
552 | return tlscert, nil | ||
553 | } | ||
554 | |||
555 | func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error { | ||
556 | if m.Cache == nil { | ||
557 | return nil | ||
558 | } | ||
559 | |||
560 | // contains PEM-encoded data | ||
561 | var buf bytes.Buffer | ||
562 | |||
563 | // private | ||
564 | switch key := tlscert.PrivateKey.(type) { | ||
565 | case *ecdsa.PrivateKey: | ||
566 | if err := encodeECDSAKey(&buf, key); err != nil { | ||
567 | return err | ||
568 | } | ||
569 | case *rsa.PrivateKey: | ||
570 | b := x509.MarshalPKCS1PrivateKey(key) | ||
571 | pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b} | ||
572 | if err := pem.Encode(&buf, pb); err != nil { | ||
573 | return err | ||
574 | } | ||
575 | default: | ||
576 | return errors.New("acme/autocert: unknown private key type") | ||
577 | } | ||
578 | |||
579 | // public | ||
580 | for _, b := range tlscert.Certificate { | ||
581 | pb := &pem.Block{Type: "CERTIFICATE", Bytes: b} | ||
582 | if err := pem.Encode(&buf, pb); err != nil { | ||
583 | return err | ||
584 | } | ||
585 | } | ||
586 | |||
587 | return m.Cache.Put(ctx, ck.String(), buf.Bytes()) | ||
588 | } | ||
589 | |||
590 | func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error { | ||
591 | b, err := x509.MarshalECPrivateKey(key) | ||
592 | if err != nil { | ||
593 | return err | ||
594 | } | ||
595 | pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} | ||
596 | return pem.Encode(w, pb) | ||
597 | } | ||
598 | |||
599 | // createCert starts the domain ownership verification and returns a certificate | ||
600 | // for that domain upon success. | ||
601 | // | ||
602 | // If the domain is already being verified, it waits for the existing verification to complete. | ||
603 | // Either way, createCert blocks for the duration of the whole process. | ||
604 | func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) { | ||
605 | // TODO: maybe rewrite this whole piece using sync.Once | ||
606 | state, err := m.certState(ck) | ||
607 | if err != nil { | ||
608 | return nil, err | ||
609 | } | ||
610 | // state may exist if another goroutine is already working on it | ||
611 | // in which case just wait for it to finish | ||
612 | if !state.locked { | ||
613 | state.RLock() | ||
614 | defer state.RUnlock() | ||
615 | m.stapleOcsp(ctx, state) | ||
616 | return state.tlscert() | ||
617 | } | ||
618 | |||
619 | // We are the first; state is locked. | ||
620 | // Unblock the readers when domain ownership is verified | ||
621 | // and we got the cert or the process failed. | ||
622 | defer state.Unlock() | ||
623 | state.locked = false | ||
624 | |||
625 | der, leaf, err := m.authorizedCert(ctx, state.key, ck) | ||
626 | if err != nil { | ||
627 | // Remove the failed state after some time, | ||
628 | // making the manager call createCert again on the following TLS hello. | ||
629 | didRemove := testDidRemoveState // The lifetime of this timer is untracked, so copy mutable local state to avoid races. | ||
630 | time.AfterFunc(createCertRetryAfter, func() { | ||
631 | defer didRemove(ck) | ||
632 | m.stateMu.Lock() | ||
633 | defer m.stateMu.Unlock() | ||
634 | // Verify the state hasn't changed and it's still invalid | ||
635 | // before deleting. | ||
636 | s, ok := m.state[ck] | ||
637 | if !ok { | ||
638 | return | ||
639 | } | ||
640 | if _, err := validCert(ck, s.cert, s.key, m.now()); err == nil { | ||
641 | return | ||
642 | } | ||
643 | delete(m.state, ck) | ||
644 | }) | ||
645 | return nil, err | ||
646 | } | ||
647 | state.cert = der | ||
648 | state.leaf = leaf | ||
649 | m.stapleOcsp(ctx, state) // Should happen before startRenew | ||
650 | m.startRenew(ck, state) | ||
651 | return state.tlscert() | ||
652 | } | ||
653 | |||
654 | // certState returns a new or existing certState. | ||
655 | // If a new certState is returned, state.exist is false and the state is locked. | ||
656 | // The returned error is non-nil only in the case where a new state could not be created. | ||
657 | func (m *Manager) certState(ck certKey) (*certState, error) { | ||
658 | m.stateMu.Lock() | ||
659 | defer m.stateMu.Unlock() | ||
660 | if m.state == nil { | ||
661 | m.state = make(map[certKey]*certState) | ||
662 | } | ||
663 | // existing state | ||
664 | if state, ok := m.state[ck]; ok { | ||
665 | return state, nil | ||
666 | } | ||
667 | |||
668 | // new locked state | ||
669 | var ( | ||
670 | err error | ||
671 | key crypto.Signer | ||
672 | ) | ||
673 | if ck.isRSA { | ||
674 | key, err = rsa.GenerateKey(rand.Reader, 2048) | ||
675 | } else { | ||
676 | key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
677 | } | ||
678 | if err != nil { | ||
679 | return nil, err | ||
680 | } | ||
681 | |||
682 | state := &certState{ | ||
683 | key: key, | ||
684 | locked: true, | ||
685 | } | ||
686 | state.Lock() // will be unlocked by m.certState caller | ||
687 | m.state[ck] = state | ||
688 | return state, nil | ||
689 | } | ||
690 | |||
691 | // stapleOcsp fetches an OCSP response and stores it on the certState for later stapling | ||
692 | func (m *Manager) stapleOcsp(ctx context.Context, s *certState) error { | ||
693 | // Only staple if that's the requested behavior | ||
694 | if !m.StapleOCSP { | ||
695 | return nil | ||
696 | } | ||
697 | |||
698 | // Don't staple if there's an existing fresh staple | ||
699 | if s.ocsp != nil && s.nextOcsp.Add(-time.Hour).Before(time.Now()) { | ||
700 | return nil | ||
701 | } | ||
702 | |||
703 | tc, err := s.tlscert() | ||
704 | if err != nil { | ||
705 | return err | ||
706 | } | ||
707 | |||
708 | raw, res, err := m.getOcspClient().Fetch(ctx, tc) | ||
709 | if err != nil { | ||
710 | return err | ||
711 | } | ||
712 | |||
713 | s.ocsp = raw | ||
714 | s.nextOcsp = res.NextUpdate | ||
715 | |||
716 | return nil | ||
717 | } | ||
718 | |||
719 | func (m *Manager) getOcspClient() *ocsp.Client { | ||
720 | m.clientMu.Lock() | ||
721 | defer m.clientMu.Unlock() | ||
722 | if m.ocspClient == nil { | ||
723 | m.ocspClient = &ocsp.Client{} | ||
724 | } | ||
725 | return m.ocspClient | ||
726 | } | ||
727 | |||
728 | // authorizedCert starts the domain ownership verification process and requests a new cert upon success. | ||
729 | // The key argument is the certificate private key. | ||
730 | func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) { | ||
731 | csr, err := certRequest(key, ck.domain, m.ExtraExtensions) | ||
732 | if err != nil { | ||
733 | return nil, nil, err | ||
734 | } | ||
735 | |||
736 | client, err := m.acmeClient(ctx) | ||
737 | if err != nil { | ||
738 | return nil, nil, err | ||
739 | } | ||
740 | dir, err := client.Discover(ctx) | ||
741 | if err != nil { | ||
742 | return nil, nil, err | ||
743 | } | ||
744 | if dir.OrderURL == "" { | ||
745 | return nil, nil, errPreRFC | ||
746 | } | ||
747 | |||
748 | o, err := m.verifyRFC(ctx, client, ck.domain) | ||
749 | if err != nil { | ||
750 | return nil, nil, err | ||
751 | } | ||
752 | chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true) | ||
753 | if err != nil { | ||
754 | return nil, nil, err | ||
755 | } | ||
756 | |||
757 | leaf, err = validCert(ck, chain, key, m.now()) | ||
758 | if err != nil { | ||
759 | return nil, nil, err | ||
760 | } | ||
761 | return chain, leaf, nil | ||
762 | } | ||
763 | |||
764 | // verifyRFC runs the identifier (domain) order-based authorization flow for RFC compliant CAs | ||
765 | // using each applicable ACME challenge type. | ||
766 | func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) { | ||
767 | // Try each supported challenge type starting with a new order each time. | ||
768 | // The nextTyp index of the next challenge type to try is shared across | ||
769 | // all order authorizations: if we've tried a challenge type once and it didn't work, | ||
770 | // it will most likely not work on another order's authorization either. | ||
771 | challengeTypes := m.supportedChallengeTypes() | ||
772 | nextTyp := 0 // challengeTypes index | ||
773 | AuthorizeOrderLoop: | ||
774 | for { | ||
775 | o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain)) | ||
776 | if err != nil { | ||
777 | return nil, err | ||
778 | } | ||
779 | // Remove all hanging authorizations to reduce rate limit quotas | ||
780 | // after we're done. | ||
781 | defer func(urls []string) { | ||
782 | go m.deactivatePendingAuthz(urls) | ||
783 | }(o.AuthzURLs) | ||
784 | |||
785 | // Check if there's actually anything we need to do. | ||
786 | switch o.Status { | ||
787 | case acme.StatusReady: | ||
788 | // Already authorized. | ||
789 | return o, nil | ||
790 | case acme.StatusPending: | ||
791 | // Continue normal Order-based flow. | ||
792 | default: | ||
793 | return nil, fmt.Errorf("acme/autocert: invalid new order status %q; order URL: %q", o.Status, o.URI) | ||
794 | } | ||
795 | |||
796 | // Satisfy all pending authorizations. | ||
797 | for _, zurl := range o.AuthzURLs { | ||
798 | z, err := client.GetAuthorization(ctx, zurl) | ||
799 | if err != nil { | ||
800 | return nil, err | ||
801 | } | ||
802 | if z.Status != acme.StatusPending { | ||
803 | // We are interested only in pending authorizations. | ||
804 | continue | ||
805 | } | ||
806 | // Pick the next preferred challenge. | ||
807 | var chal *acme.Challenge | ||
808 | for chal == nil && nextTyp < len(challengeTypes) { | ||
809 | chal = pickChallenge(challengeTypes[nextTyp], z.Challenges) | ||
810 | nextTyp++ | ||
811 | } | ||
812 | if chal == nil { | ||
813 | return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found", z.URI, domain) | ||
814 | } | ||
815 | // Respond to the challenge and wait for validation result. | ||
816 | cleanup, err := m.fulfill(ctx, client, chal, domain) | ||
817 | if err != nil { | ||
818 | continue AuthorizeOrderLoop | ||
819 | } | ||
820 | defer cleanup() | ||
821 | if _, err := client.Accept(ctx, chal); err != nil { | ||
822 | continue AuthorizeOrderLoop | ||
823 | } | ||
824 | if _, err := client.WaitAuthorization(ctx, z.URI); err != nil { | ||
825 | continue AuthorizeOrderLoop | ||
826 | } | ||
827 | } | ||
828 | |||
829 | // All authorizations are satisfied. | ||
830 | // Wait for the CA to update the order status. | ||
831 | o, err = client.WaitOrder(ctx, o.URI) | ||
832 | if err != nil { | ||
833 | continue AuthorizeOrderLoop | ||
834 | } | ||
835 | return o, nil | ||
836 | } | ||
837 | } | ||
838 | |||
839 | func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge { | ||
840 | for _, c := range chal { | ||
841 | if c.Type == typ { | ||
842 | return c | ||
843 | } | ||
844 | } | ||
845 | return nil | ||
846 | } | ||
847 | |||
848 | func (m *Manager) supportedChallengeTypes() []string { | ||
849 | m.challengeMu.RLock() | ||
850 | defer m.challengeMu.RUnlock() | ||
851 | typ := []string{"tls-alpn-01"} | ||
852 | if m.tryHTTP01 { | ||
853 | typ = append(typ, "http-01") | ||
854 | } | ||
855 | if m.DNSManager != nil { | ||
856 | typ = append(typ, "dns-01") | ||
857 | } | ||
858 | return typ | ||
859 | } | ||
860 | |||
861 | // deactivatePendingAuthz relinquishes all authorizations identified by the elements | ||
862 | // of the provided uri slice which are in "pending" state. | ||
863 | // It ignores revocation errors. | ||
864 | // | ||
865 | // deactivatePendingAuthz takes no context argument and instead runs with its own | ||
866 | // "detached" context because deactivations are done in a goroutine separate from | ||
867 | // that of the main issuance or renewal flow. | ||
868 | func (m *Manager) deactivatePendingAuthz(uri []string) { | ||
869 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) | ||
870 | defer cancel() | ||
871 | client, err := m.acmeClient(ctx) | ||
872 | if err != nil { | ||
873 | return | ||
874 | } | ||
875 | for _, u := range uri { | ||
876 | z, err := client.GetAuthorization(ctx, u) | ||
877 | if err == nil && z.Status == acme.StatusPending { | ||
878 | client.RevokeAuthorization(ctx, u) | ||
879 | } | ||
880 | } | ||
881 | } | ||
882 | |||
883 | // fulfill provisions a response to the challenge chal. | ||
884 | // The cleanup is non-nil only if provisioning succeeded. | ||
885 | func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge, domain string) (cleanup func(), err error) { | ||
886 | switch chal.Type { | ||
887 | case "tls-alpn-01": | ||
888 | cert, err := client.TLSALPN01ChallengeCert(chal.Token, domain) | ||
889 | if err != nil { | ||
890 | return nil, err | ||
891 | } | ||
892 | m.putCertToken(ctx, domain, &cert) | ||
893 | return func() { go m.deleteCertToken(domain) }, nil | ||
894 | case "http-01": | ||
895 | resp, err := client.HTTP01ChallengeResponse(chal.Token) | ||
896 | if err != nil { | ||
897 | return nil, err | ||
898 | } | ||
899 | p := client.HTTP01ChallengePath(chal.Token) | ||
900 | m.putHTTPToken(ctx, p, resp) | ||
901 | return func() { go m.deleteHTTPToken(p) }, nil | ||
902 | case "dns-01": | ||
903 | rec, err := client.DNS01ChallengeRecord(chal.Token) | ||
904 | if err != nil { | ||
905 | return nil, err | ||
906 | } | ||
907 | err = m.DNSManager.Fulfill(ctx, domain, rec) | ||
908 | if err != nil { | ||
909 | return nil, err | ||
910 | } | ||
911 | return func() { m.DNSManager.Cleanup(ctx, domain, rec) }, err | ||
912 | } | ||
913 | return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) | ||
914 | } | ||
915 | |||
916 | // putCertToken stores the token certificate with the specified name | ||
917 | // in both m.certTokens map and m.Cache. | ||
918 | func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) { | ||
919 | m.challengeMu.Lock() | ||
920 | defer m.challengeMu.Unlock() | ||
921 | if m.certTokens == nil { | ||
922 | m.certTokens = make(map[string]*tls.Certificate) | ||
923 | } | ||
924 | m.certTokens[name] = cert | ||
925 | m.cachePut(ctx, certKey{domain: name, isToken: true}, cert) | ||
926 | } | ||
927 | |||
928 | // deleteCertToken removes the token certificate with the specified name | ||
929 | // from both m.certTokens map and m.Cache. | ||
930 | func (m *Manager) deleteCertToken(name string) { | ||
931 | m.challengeMu.Lock() | ||
932 | defer m.challengeMu.Unlock() | ||
933 | delete(m.certTokens, name) | ||
934 | if m.Cache != nil { | ||
935 | ck := certKey{domain: name, isToken: true} | ||
936 | m.Cache.Delete(context.Background(), ck.String()) | ||
937 | } | ||
938 | } | ||
939 | |||
940 | // httpToken retrieves an existing http-01 token value from an in-memory map | ||
941 | // or the optional cache. | ||
942 | func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) { | ||
943 | m.challengeMu.RLock() | ||
944 | defer m.challengeMu.RUnlock() | ||
945 | if v, ok := m.httpTokens[tokenPath]; ok { | ||
946 | return v, nil | ||
947 | } | ||
948 | if m.Cache == nil { | ||
949 | return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath) | ||
950 | } | ||
951 | return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath)) | ||
952 | } | ||
953 | |||
954 | // putHTTPToken stores an http-01 token value using tokenPath as key | ||
955 | // in both in-memory map and the optional Cache. | ||
956 | // | ||
957 | // It ignores any error returned from Cache.Put. | ||
958 | func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) { | ||
959 | m.challengeMu.Lock() | ||
960 | defer m.challengeMu.Unlock() | ||
961 | if m.httpTokens == nil { | ||
962 | m.httpTokens = make(map[string][]byte) | ||
963 | } | ||
964 | b := []byte(val) | ||
965 | m.httpTokens[tokenPath] = b | ||
966 | if m.Cache != nil { | ||
967 | m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b) | ||
968 | } | ||
969 | } | ||
970 | |||
971 | // deleteHTTPToken removes an http-01 token value from both in-memory map | ||
972 | // and the optional Cache, ignoring any error returned from the latter. | ||
973 | // | ||
974 | // If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout. | ||
975 | func (m *Manager) deleteHTTPToken(tokenPath string) { | ||
976 | m.challengeMu.Lock() | ||
977 | defer m.challengeMu.Unlock() | ||
978 | delete(m.httpTokens, tokenPath) | ||
979 | if m.Cache != nil { | ||
980 | m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath)) | ||
981 | } | ||
982 | } | ||
983 | |||
984 | // httpTokenCacheKey returns a key at which an http-01 token value may be stored | ||
985 | // in the Manager's optional Cache. | ||
986 | func httpTokenCacheKey(tokenPath string) string { | ||
987 | return path.Base(tokenPath) + "+http-01" | ||
988 | } | ||
989 | |||
990 | // startRenew starts a cert renewal and OCSP staple timer loop, one per domain. | ||
991 | // | ||
992 | // The loop is scheduled in two cases: | ||
993 | // - a cert was fetched from cache for the first time (wasn't in m.state) | ||
994 | // - a new cert was created by m.createCert | ||
995 | func (m *Manager) startRenew(ck certKey, s *certState) { | ||
996 | m.renewalMu.Lock() | ||
997 | defer m.renewalMu.Unlock() | ||
998 | if m.renewal[ck] != nil { | ||
999 | // another goroutine is already on it | ||
1000 | return | ||
1001 | } | ||
1002 | if m.renewal == nil { | ||
1003 | m.renewal = make(map[certKey]*domainRenewal) | ||
1004 | } | ||
1005 | dr := &domainRenewal{m: m, ck: ck, key: s.key} | ||
1006 | m.renewal[ck] = dr | ||
1007 | dr.start(s.leaf.NotAfter) | ||
1008 | } | ||
1009 | |||
1010 | // stopRenew stops all currently running cert renewal timers. | ||
1011 | // The timers are not restarted during the lifetime of the Manager. | ||
1012 | func (m *Manager) stopRenew() { | ||
1013 | m.renewalMu.Lock() | ||
1014 | defer m.renewalMu.Unlock() | ||
1015 | for name, dr := range m.renewal { | ||
1016 | delete(m.renewal, name) | ||
1017 | dr.stop() | ||
1018 | } | ||
1019 | } | ||
1020 | |||
1021 | func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) { | ||
1022 | const keyName = "acme_account+key" | ||
1023 | |||
1024 | // Previous versions of autocert stored the value under a different key. | ||
1025 | const legacyKeyName = "acme_account.key" | ||
1026 | |||
1027 | genKey := func() (*ecdsa.PrivateKey, error) { | ||
1028 | return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
1029 | } | ||
1030 | |||
1031 | if m.Cache == nil { | ||
1032 | return genKey() | ||
1033 | } | ||
1034 | |||
1035 | data, err := m.Cache.Get(ctx, keyName) | ||
1036 | if err == ErrCacheMiss { | ||
1037 | data, err = m.Cache.Get(ctx, legacyKeyName) | ||
1038 | } | ||
1039 | if err == ErrCacheMiss { | ||
1040 | key, err := genKey() | ||
1041 | if err != nil { | ||
1042 | return nil, err | ||
1043 | } | ||
1044 | var buf bytes.Buffer | ||
1045 | if err := encodeECDSAKey(&buf, key); err != nil { | ||
1046 | return nil, err | ||
1047 | } | ||
1048 | if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil { | ||
1049 | return nil, err | ||
1050 | } | ||
1051 | return key, nil | ||
1052 | } | ||
1053 | if err != nil { | ||
1054 | return nil, err | ||
1055 | } | ||
1056 | |||
1057 | priv, _ := pem.Decode(data) | ||
1058 | if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { | ||
1059 | return nil, errors.New("acme/autocert: invalid account key found in cache") | ||
1060 | } | ||
1061 | return parsePrivateKey(priv.Bytes) | ||
1062 | } | ||
1063 | |||
1064 | func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { | ||
1065 | m.clientMu.Lock() | ||
1066 | defer m.clientMu.Unlock() | ||
1067 | if m.client != nil { | ||
1068 | return m.client, nil | ||
1069 | } | ||
1070 | |||
1071 | client := m.Client | ||
1072 | if client == nil { | ||
1073 | client = &acme.Client{DirectoryURL: DefaultACMEDirectory} | ||
1074 | } | ||
1075 | if client.Key == nil { | ||
1076 | var err error | ||
1077 | client.Key, err = m.accountKey(ctx) | ||
1078 | if err != nil { | ||
1079 | return nil, err | ||
1080 | } | ||
1081 | } | ||
1082 | if client.UserAgent == "" { | ||
1083 | client.UserAgent = "autocert" | ||
1084 | } | ||
1085 | var contact []string | ||
1086 | if m.Email != "" { | ||
1087 | contact = []string{"mailto:" + m.Email} | ||
1088 | } | ||
1089 | a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding} | ||
1090 | _, err := client.Register(ctx, a, m.Prompt) | ||
1091 | if err == nil || isAccountAlreadyExist(err) { | ||
1092 | m.client = client | ||
1093 | err = nil | ||
1094 | } | ||
1095 | return m.client, err | ||
1096 | } | ||
1097 | |||
1098 | // isAccountAlreadyExist reports whether the err, as returned from acme.Client.Register, | ||
1099 | // indicates the account has already been registered. | ||
1100 | func isAccountAlreadyExist(err error) bool { | ||
1101 | if err == acme.ErrAccountAlreadyExists { | ||
1102 | return true | ||
1103 | } | ||
1104 | ae, ok := err.(*acme.Error) | ||
1105 | return ok && ae.StatusCode == http.StatusConflict | ||
1106 | } | ||
1107 | |||
1108 | func (m *Manager) hostPolicy() HostPolicy { | ||
1109 | if m.HostPolicy != nil { | ||
1110 | return m.HostPolicy | ||
1111 | } | ||
1112 | return defaultHostPolicy | ||
1113 | } | ||
1114 | |||
1115 | func (m *Manager) renewBefore() time.Duration { | ||
1116 | if m.RenewBefore > renewJitter { | ||
1117 | return m.RenewBefore | ||
1118 | } | ||
1119 | return 720 * time.Hour // 30 days | ||
1120 | } | ||
1121 | |||
1122 | func (m *Manager) now() time.Time { | ||
1123 | if m.nowFunc != nil { | ||
1124 | return m.nowFunc() | ||
1125 | } | ||
1126 | return time.Now() | ||
1127 | } | ||
1128 | |||
1129 | // certState is ready when its mutex is unlocked for reading. | ||
1130 | type certState struct { | ||
1131 | sync.RWMutex | ||
1132 | locked bool // locked for read/write | ||
1133 | key crypto.Signer // private key for cert | ||
1134 | cert [][]byte // DER encoding | ||
1135 | leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil | ||
1136 | nextOcsp time.Time // time ocsp response expires, must renew before this | ||
1137 | ocsp []byte // raw ocsp response from CA for stapling to cert | ||
1138 | } | ||
1139 | |||
1140 | // tlscert creates a tls.Certificate from s.key and s.cert. | ||
1141 | // Callers should wrap it in s.RLock() and s.RUnlock(). | ||
1142 | func (s *certState) tlscert() (*tls.Certificate, error) { | ||
1143 | if s.key == nil { | ||
1144 | return nil, errors.New("acme/autocert: missing signer") | ||
1145 | } | ||
1146 | if len(s.cert) == 0 { | ||
1147 | return nil, errors.New("acme/autocert: missing certificate") | ||
1148 | } | ||
1149 | return &tls.Certificate{ | ||
1150 | PrivateKey: s.key, | ||
1151 | Certificate: s.cert, | ||
1152 | Leaf: s.leaf, | ||
1153 | OCSPStaple: s.ocsp, | ||
1154 | }, nil | ||
1155 | } | ||
1156 | |||
1157 | // certRequest generates a CSR for the given common name. | ||
1158 | func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) { | ||
1159 | req := &x509.CertificateRequest{ | ||
1160 | Subject: pkix.Name{CommonName: name}, | ||
1161 | DNSNames: []string{name}, | ||
1162 | ExtraExtensions: ext, | ||
1163 | } | ||
1164 | return x509.CreateCertificateRequest(rand.Reader, req, key) | ||
1165 | } | ||
1166 | |||
1167 | // Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates | ||
1168 | // PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. | ||
1169 | // OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. | ||
1170 | // | ||
1171 | // Inspired by parsePrivateKey in crypto/tls/tls.go. | ||
1172 | func parsePrivateKey(der []byte) (crypto.Signer, error) { | ||
1173 | if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { | ||
1174 | return key, nil | ||
1175 | } | ||
1176 | if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { | ||
1177 | switch key := key.(type) { | ||
1178 | case *rsa.PrivateKey: | ||
1179 | return key, nil | ||
1180 | case *ecdsa.PrivateKey: | ||
1181 | return key, nil | ||
1182 | default: | ||
1183 | return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping") | ||
1184 | } | ||
1185 | } | ||
1186 | if key, err := x509.ParseECPrivateKey(der); err == nil { | ||
1187 | return key, nil | ||
1188 | } | ||
1189 | |||
1190 | return nil, errors.New("acme/autocert: failed to parse private key") | ||
1191 | } | ||
1192 | |||
1193 | // validCert parses a cert chain provided as der argument and verifies the leaf and der[0] | ||
1194 | // correspond to the private key, the domain and key type match, and expiration dates | ||
1195 | // are valid. It doesn't do any revocation checking. | ||
1196 | // | ||
1197 | // The returned value is the verified leaf cert. | ||
1198 | func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) { | ||
1199 | // parse public part(s) | ||
1200 | var n int | ||
1201 | for _, b := range der { | ||
1202 | n += len(b) | ||
1203 | } | ||
1204 | pub := make([]byte, n) | ||
1205 | n = 0 | ||
1206 | for _, b := range der { | ||
1207 | n += copy(pub[n:], b) | ||
1208 | } | ||
1209 | x509Cert, err := x509.ParseCertificates(pub) | ||
1210 | if err != nil || len(x509Cert) == 0 { | ||
1211 | return nil, errors.New("acme/autocert: no public key found") | ||
1212 | } | ||
1213 | // verify the leaf is not expired and matches the domain name | ||
1214 | leaf = x509Cert[0] | ||
1215 | if now.Before(leaf.NotBefore) { | ||
1216 | return nil, errors.New("acme/autocert: certificate is not valid yet") | ||
1217 | } | ||
1218 | if now.After(leaf.NotAfter) { | ||
1219 | return nil, errors.New("acme/autocert: expired certificate") | ||
1220 | } | ||
1221 | if err := leaf.VerifyHostname(ck.domain); err != nil { | ||
1222 | return nil, err | ||
1223 | } | ||
1224 | // renew certificates revoked by Let's Encrypt in January 2022 | ||
1225 | if isRevokedLetsEncrypt(leaf) { | ||
1226 | return nil, errors.New("acme/autocert: certificate was probably revoked by Let's Encrypt") | ||
1227 | } | ||
1228 | // ensure the leaf corresponds to the private key and matches the certKey type | ||
1229 | switch pub := leaf.PublicKey.(type) { | ||
1230 | case *rsa.PublicKey: | ||
1231 | prv, ok := key.(*rsa.PrivateKey) | ||
1232 | if !ok { | ||
1233 | return nil, errors.New("acme/autocert: private key type does not match public key type") | ||
1234 | } | ||
1235 | if pub.N.Cmp(prv.N) != 0 { | ||
1236 | return nil, errors.New("acme/autocert: private key does not match public key") | ||
1237 | } | ||
1238 | if !ck.isRSA && !ck.isToken { | ||
1239 | return nil, errors.New("acme/autocert: key type does not match expected value") | ||
1240 | } | ||
1241 | case *ecdsa.PublicKey: | ||
1242 | prv, ok := key.(*ecdsa.PrivateKey) | ||
1243 | if !ok { | ||
1244 | return nil, errors.New("acme/autocert: private key type does not match public key type") | ||
1245 | } | ||
1246 | if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 { | ||
1247 | return nil, errors.New("acme/autocert: private key does not match public key") | ||
1248 | } | ||
1249 | if ck.isRSA && !ck.isToken { | ||
1250 | return nil, errors.New("acme/autocert: key type does not match expected value") | ||
1251 | } | ||
1252 | default: | ||
1253 | return nil, errors.New("acme/autocert: unknown public key algorithm") | ||
1254 | } | ||
1255 | return leaf, nil | ||
1256 | } | ||
1257 | |||
1258 | // https://community.letsencrypt.org/t/2022-01-25-issue-with-tls-alpn-01-validation-method/170450 | ||
1259 | var letsEncryptFixDeployTime = time.Date(2022, time.January, 26, 00, 48, 0, 0, time.UTC) | ||
1260 | |||
1261 | // isRevokedLetsEncrypt returns whether the certificate is likely to be part of | ||
1262 | // a batch of certificates revoked by Let's Encrypt in January 2022. This check | ||
1263 | // can be safely removed from May 2022. | ||
1264 | func isRevokedLetsEncrypt(cert *x509.Certificate) bool { | ||
1265 | O := cert.Issuer.Organization | ||
1266 | return len(O) == 1 && O[0] == "Let's Encrypt" && | ||
1267 | cert.NotBefore.Before(letsEncryptFixDeployTime) | ||
1268 | } | ||
1269 | |||
1270 | type lockedMathRand struct { | ||
1271 | sync.Mutex | ||
1272 | rnd *mathrand.Rand | ||
1273 | } | ||
1274 | |||
1275 | func (r *lockedMathRand) int63n(max int64) int64 { | ||
1276 | r.Lock() | ||
1277 | n := r.rnd.Int63n(max) | ||
1278 | r.Unlock() | ||
1279 | return n | ||
1280 | } | ||
1281 | |||
1282 | // For easier testing. | ||
1283 | var ( | ||
1284 | // Called when a state is removed. | ||
1285 | testDidRemoveState = func(certKey) {} | ||
1286 | ) | ||
diff --git a/crypto/acme/autocert/fork/autocert_test.go b/crypto/acme/autocert/fork/autocert_test.go new file mode 100644 index 0000000..ef466b5 --- /dev/null +++ b/crypto/acme/autocert/fork/autocert_test.go | |||
@@ -0,0 +1,1058 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "bytes" | ||
9 | "context" | ||
10 | "crypto" | ||
11 | "crypto/ecdsa" | ||
12 | "crypto/elliptic" | ||
13 | "crypto/rand" | ||
14 | "crypto/rsa" | ||
15 | "crypto/tls" | ||
16 | "crypto/x509" | ||
17 | "crypto/x509/pkix" | ||
18 | "encoding/asn1" | ||
19 | "fmt" | ||
20 | "io" | ||
21 | "math/big" | ||
22 | "net/http" | ||
23 | "net/http/httptest" | ||
24 | "reflect" | ||
25 | "strings" | ||
26 | "sync" | ||
27 | "testing" | ||
28 | "time" | ||
29 | |||
30 | "code.crute.us/mcrute/golib/crypto/acme/autocert/fork/internal/acmetest" | ||
31 | "golang.org/x/crypto/acme" | ||
32 | ) | ||
33 | |||
34 | var ( | ||
35 | exampleDomain = "example.org" | ||
36 | exampleCertKey = certKey{domain: exampleDomain} | ||
37 | exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true} | ||
38 | ) | ||
39 | |||
40 | type memCache struct { | ||
41 | t *testing.T | ||
42 | mu sync.Mutex | ||
43 | keyData map[string][]byte | ||
44 | } | ||
45 | |||
46 | func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) { | ||
47 | m.mu.Lock() | ||
48 | defer m.mu.Unlock() | ||
49 | |||
50 | v, ok := m.keyData[key] | ||
51 | if !ok { | ||
52 | return nil, ErrCacheMiss | ||
53 | } | ||
54 | return v, nil | ||
55 | } | ||
56 | |||
57 | // filenameSafe returns whether all characters in s are printable ASCII | ||
58 | // and safe to use in a filename on most filesystems. | ||
59 | func filenameSafe(s string) bool { | ||
60 | for _, c := range s { | ||
61 | if c < 0x20 || c > 0x7E { | ||
62 | return false | ||
63 | } | ||
64 | switch c { | ||
65 | case '\\', '/', ':', '*', '?', '"', '<', '>', '|': | ||
66 | return false | ||
67 | } | ||
68 | } | ||
69 | return true | ||
70 | } | ||
71 | |||
72 | func (m *memCache) Put(ctx context.Context, key string, data []byte) error { | ||
73 | if !filenameSafe(key) { | ||
74 | m.t.Errorf("invalid characters in cache key %q", key) | ||
75 | } | ||
76 | |||
77 | m.mu.Lock() | ||
78 | defer m.mu.Unlock() | ||
79 | |||
80 | m.keyData[key] = data | ||
81 | return nil | ||
82 | } | ||
83 | |||
84 | func (m *memCache) Delete(ctx context.Context, key string) error { | ||
85 | m.mu.Lock() | ||
86 | defer m.mu.Unlock() | ||
87 | |||
88 | delete(m.keyData, key) | ||
89 | return nil | ||
90 | } | ||
91 | |||
92 | func newMemCache(t *testing.T) *memCache { | ||
93 | return &memCache{ | ||
94 | t: t, | ||
95 | keyData: make(map[string][]byte), | ||
96 | } | ||
97 | } | ||
98 | |||
99 | func (m *memCache) numCerts() int { | ||
100 | m.mu.Lock() | ||
101 | defer m.mu.Unlock() | ||
102 | |||
103 | res := 0 | ||
104 | for key := range m.keyData { | ||
105 | if strings.HasSuffix(key, "+token") || | ||
106 | strings.HasSuffix(key, "+key") || | ||
107 | strings.HasSuffix(key, "+http-01") { | ||
108 | continue | ||
109 | } | ||
110 | res++ | ||
111 | } | ||
112 | return res | ||
113 | } | ||
114 | |||
115 | func dummyCert(pub interface{}, san ...string) ([]byte, error) { | ||
116 | return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...) | ||
117 | } | ||
118 | |||
119 | func dateDummyCert(pub interface{}, start, end time.Time, san ...string) ([]byte, error) { | ||
120 | // use EC key to run faster on 386 | ||
121 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
122 | if err != nil { | ||
123 | return nil, err | ||
124 | } | ||
125 | t := &x509.Certificate{ | ||
126 | SerialNumber: randomSerial(), | ||
127 | NotBefore: start, | ||
128 | NotAfter: end, | ||
129 | BasicConstraintsValid: true, | ||
130 | KeyUsage: x509.KeyUsageKeyEncipherment, | ||
131 | DNSNames: san, | ||
132 | } | ||
133 | if pub == nil { | ||
134 | pub = &key.PublicKey | ||
135 | } | ||
136 | return x509.CreateCertificate(rand.Reader, t, t, pub, key) | ||
137 | } | ||
138 | |||
139 | func randomSerial() *big.Int { | ||
140 | serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 32)) | ||
141 | if err != nil { | ||
142 | panic(err) | ||
143 | } | ||
144 | return serial | ||
145 | } | ||
146 | |||
147 | type algorithmSupport int | ||
148 | |||
149 | const ( | ||
150 | algRSA algorithmSupport = iota | ||
151 | algECDSA | ||
152 | ) | ||
153 | |||
154 | func clientHelloInfo(sni string, alg algorithmSupport) *tls.ClientHelloInfo { | ||
155 | hello := &tls.ClientHelloInfo{ | ||
156 | ServerName: sni, | ||
157 | CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}, | ||
158 | } | ||
159 | if alg == algECDSA { | ||
160 | hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305) | ||
161 | } | ||
162 | return hello | ||
163 | } | ||
164 | |||
165 | func testManager(t *testing.T) *Manager { | ||
166 | man := &Manager{ | ||
167 | Prompt: AcceptTOS, | ||
168 | Cache: newMemCache(t), | ||
169 | } | ||
170 | t.Cleanup(man.stopRenew) | ||
171 | return man | ||
172 | } | ||
173 | |||
174 | func TestGetCertificate(t *testing.T) { | ||
175 | tests := []struct { | ||
176 | name string | ||
177 | hello *tls.ClientHelloInfo | ||
178 | domain string | ||
179 | expectError string | ||
180 | prepare func(t *testing.T, man *Manager, s *acmetest.CAServer) | ||
181 | verify func(t *testing.T, man *Manager, leaf *x509.Certificate) | ||
182 | disableALPN bool | ||
183 | disableHTTP bool | ||
184 | }{ | ||
185 | { | ||
186 | name: "ALPN", | ||
187 | hello: clientHelloInfo("example.org", algECDSA), | ||
188 | domain: "example.org", | ||
189 | disableHTTP: true, | ||
190 | }, | ||
191 | { | ||
192 | name: "HTTP", | ||
193 | hello: clientHelloInfo("example.org", algECDSA), | ||
194 | domain: "example.org", | ||
195 | disableALPN: true, | ||
196 | }, | ||
197 | { | ||
198 | name: "nilPrompt", | ||
199 | hello: clientHelloInfo("example.org", algECDSA), | ||
200 | domain: "example.org", | ||
201 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
202 | man.Prompt = nil | ||
203 | }, | ||
204 | expectError: "Manager.Prompt not set", | ||
205 | }, | ||
206 | { | ||
207 | name: "trailingDot", | ||
208 | hello: clientHelloInfo("example.org.", algECDSA), | ||
209 | domain: "example.org", | ||
210 | }, | ||
211 | { | ||
212 | name: "unicodeIDN", | ||
213 | hello: clientHelloInfo("éé.com", algECDSA), | ||
214 | domain: "xn--9caa.com", | ||
215 | }, | ||
216 | { | ||
217 | name: "unicodeIDN/mixedCase", | ||
218 | hello: clientHelloInfo("éÉ.com", algECDSA), | ||
219 | domain: "xn--9caa.com", | ||
220 | }, | ||
221 | { | ||
222 | name: "upperCase", | ||
223 | hello: clientHelloInfo("EXAMPLE.ORG", algECDSA), | ||
224 | domain: "example.org", | ||
225 | }, | ||
226 | { | ||
227 | name: "goodCache", | ||
228 | hello: clientHelloInfo("example.org", algECDSA), | ||
229 | domain: "example.org", | ||
230 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
231 | // Make a valid cert and cache it. | ||
232 | c := s.Start().LeafCert(exampleDomain, "ECDSA", | ||
233 | // Use a time before the Let's Encrypt revocation cutoff to also test | ||
234 | // that non-Let's Encrypt certificates are not renewed. | ||
235 | time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), | ||
236 | time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), | ||
237 | ) | ||
238 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
239 | t.Fatalf("man.cachePut: %v", err) | ||
240 | } | ||
241 | }, | ||
242 | // Break the server to check that the cache is used. | ||
243 | disableALPN: true, disableHTTP: true, | ||
244 | }, | ||
245 | { | ||
246 | name: "expiredCache", | ||
247 | hello: clientHelloInfo("example.org", algECDSA), | ||
248 | domain: "example.org", | ||
249 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
250 | // Make an expired cert and cache it. | ||
251 | c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now().Add(-10*time.Minute), time.Now().Add(-5*time.Minute)) | ||
252 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
253 | t.Fatalf("man.cachePut: %v", err) | ||
254 | } | ||
255 | }, | ||
256 | }, | ||
257 | { | ||
258 | name: "forceRSA", | ||
259 | hello: clientHelloInfo("example.org", algECDSA), | ||
260 | domain: "example.org", | ||
261 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
262 | man.ForceRSA = true | ||
263 | }, | ||
264 | verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { | ||
265 | if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { | ||
266 | t.Errorf("leaf.PublicKey is %T; want *ecdsa.PublicKey", leaf.PublicKey) | ||
267 | } | ||
268 | }, | ||
269 | }, | ||
270 | { | ||
271 | name: "goodLetsEncrypt", | ||
272 | hello: clientHelloInfo("example.org", algECDSA), | ||
273 | domain: "example.org", | ||
274 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
275 | // Make a valid certificate issued after the TLS-ALPN-01 | ||
276 | // revocation window and cache it. | ||
277 | s.IssuerName(pkix.Name{Country: []string{"US"}, | ||
278 | Organization: []string{"Let's Encrypt"}, CommonName: "R3"}) | ||
279 | c := s.Start().LeafCert(exampleDomain, "ECDSA", | ||
280 | time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC), | ||
281 | time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), | ||
282 | ) | ||
283 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
284 | t.Fatalf("man.cachePut: %v", err) | ||
285 | } | ||
286 | }, | ||
287 | // Break the server to check that the cache is used. | ||
288 | disableALPN: true, disableHTTP: true, | ||
289 | }, | ||
290 | { | ||
291 | name: "revokedLetsEncrypt", | ||
292 | hello: clientHelloInfo("example.org", algECDSA), | ||
293 | domain: "example.org", | ||
294 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
295 | // Make a certificate issued during the TLS-ALPN-01 | ||
296 | // revocation window and cache it. | ||
297 | s.IssuerName(pkix.Name{Country: []string{"US"}, | ||
298 | Organization: []string{"Let's Encrypt"}, CommonName: "R3"}) | ||
299 | c := s.Start().LeafCert(exampleDomain, "ECDSA", | ||
300 | time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), | ||
301 | time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), | ||
302 | ) | ||
303 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
304 | t.Fatalf("man.cachePut: %v", err) | ||
305 | } | ||
306 | }, | ||
307 | verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { | ||
308 | if leaf.NotBefore.Before(time.Now().Add(-10 * time.Minute)) { | ||
309 | t.Error("certificate was not reissued") | ||
310 | } | ||
311 | }, | ||
312 | }, | ||
313 | { | ||
314 | // TestGetCertificate/tokenCache tests the fallback of token | ||
315 | // certificate fetches to cache when Manager.certTokens misses. | ||
316 | name: "tokenCacheALPN", | ||
317 | hello: clientHelloInfo("example.org", algECDSA), | ||
318 | domain: "example.org", | ||
319 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
320 | // Make a separate manager with a shared cache, simulating | ||
321 | // separate nodes that serve requests for the same domain. | ||
322 | man2 := testManager(t) | ||
323 | man2.Cache = man.Cache | ||
324 | // Redirect the verification request to man2, although the | ||
325 | // client request will hit man, testing that they can complete a | ||
326 | // verification by communicating through the cache. | ||
327 | s.ResolveGetCertificate("example.org", man2.GetCertificate) | ||
328 | }, | ||
329 | // Drop the default verification paths. | ||
330 | disableALPN: true, | ||
331 | }, | ||
332 | { | ||
333 | name: "tokenCacheHTTP", | ||
334 | hello: clientHelloInfo("example.org", algECDSA), | ||
335 | domain: "example.org", | ||
336 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
337 | man2 := testManager(t) | ||
338 | man2.Cache = man.Cache | ||
339 | s.ResolveHandler("example.org", man2.HTTPHandler(nil)) | ||
340 | }, | ||
341 | disableHTTP: true, | ||
342 | }, | ||
343 | { | ||
344 | name: "ecdsa", | ||
345 | hello: clientHelloInfo("example.org", algECDSA), | ||
346 | domain: "example.org", | ||
347 | verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { | ||
348 | if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { | ||
349 | t.Error("an ECDSA client was served a non-ECDSA certificate") | ||
350 | } | ||
351 | }, | ||
352 | }, | ||
353 | { | ||
354 | name: "rsa", | ||
355 | hello: clientHelloInfo("example.org", algRSA), | ||
356 | domain: "example.org", | ||
357 | verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { | ||
358 | if _, ok := leaf.PublicKey.(*rsa.PublicKey); !ok { | ||
359 | t.Error("an RSA client was served a non-RSA certificate") | ||
360 | } | ||
361 | }, | ||
362 | }, | ||
363 | { | ||
364 | name: "wrongCacheKeyType", | ||
365 | hello: clientHelloInfo("example.org", algECDSA), | ||
366 | domain: "example.org", | ||
367 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
368 | // Make an RSA cert and cache it without suffix. | ||
369 | c := s.Start().LeafCert(exampleDomain, "RSA", time.Now(), time.Now().Add(90*24*time.Hour)) | ||
370 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
371 | t.Fatalf("man.cachePut: %v", err) | ||
372 | } | ||
373 | }, | ||
374 | verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { | ||
375 | // The RSA cached cert should be silently ignored and replaced. | ||
376 | if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { | ||
377 | t.Error("an ECDSA client was served a non-ECDSA certificate") | ||
378 | } | ||
379 | if numCerts := man.Cache.(*memCache).numCerts(); numCerts != 1 { | ||
380 | t.Errorf("found %d certificates in cache; want %d", numCerts, 1) | ||
381 | } | ||
382 | }, | ||
383 | }, | ||
384 | { | ||
385 | name: "almostExpiredCache", | ||
386 | hello: clientHelloInfo("example.org", algECDSA), | ||
387 | domain: "example.org", | ||
388 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
389 | man.RenewBefore = 24 * time.Hour | ||
390 | // Cache an almost expired cert. | ||
391 | c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now(), time.Now().Add(10*time.Minute)) | ||
392 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
393 | t.Fatalf("man.cachePut: %v", err) | ||
394 | } | ||
395 | }, | ||
396 | }, | ||
397 | { | ||
398 | name: "provideExternalAuth", | ||
399 | hello: clientHelloInfo("example.org", algECDSA), | ||
400 | domain: "example.org", | ||
401 | prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { | ||
402 | s.ExternalAccountRequired() | ||
403 | |||
404 | man.ExternalAccountBinding = &acme.ExternalAccountBinding{ | ||
405 | KID: "test-key", | ||
406 | Key: make([]byte, 32), | ||
407 | } | ||
408 | }, | ||
409 | }, | ||
410 | } | ||
411 | for _, tt := range tests { | ||
412 | t.Run(tt.name, func(t *testing.T) { | ||
413 | man := testManager(t) | ||
414 | s := acmetest.NewCAServer(t) | ||
415 | if !tt.disableALPN { | ||
416 | s.ResolveGetCertificate(tt.domain, man.GetCertificate) | ||
417 | } | ||
418 | if !tt.disableHTTP { | ||
419 | s.ResolveHandler(tt.domain, man.HTTPHandler(nil)) | ||
420 | } | ||
421 | |||
422 | if tt.prepare != nil { | ||
423 | tt.prepare(t, man, s) | ||
424 | } | ||
425 | |||
426 | s.Start() | ||
427 | |||
428 | man.Client = &acme.Client{DirectoryURL: s.URL()} | ||
429 | |||
430 | tlscert, err := man.GetCertificate(tt.hello) | ||
431 | if tt.expectError != "" { | ||
432 | if err == nil { | ||
433 | t.Fatal("expected error, got certificate") | ||
434 | } | ||
435 | if !strings.Contains(err.Error(), tt.expectError) { | ||
436 | t.Errorf("got %q, expected %q", err, tt.expectError) | ||
437 | } | ||
438 | return | ||
439 | } | ||
440 | if err != nil { | ||
441 | t.Fatalf("man.GetCertificate: %v", err) | ||
442 | } | ||
443 | |||
444 | leaf, err := x509.ParseCertificate(tlscert.Certificate[0]) | ||
445 | if err != nil { | ||
446 | t.Fatal(err) | ||
447 | } | ||
448 | opts := x509.VerifyOptions{ | ||
449 | DNSName: tt.domain, | ||
450 | Intermediates: x509.NewCertPool(), | ||
451 | Roots: s.Roots(), | ||
452 | } | ||
453 | for _, cert := range tlscert.Certificate[1:] { | ||
454 | c, err := x509.ParseCertificate(cert) | ||
455 | if err != nil { | ||
456 | t.Fatal(err) | ||
457 | } | ||
458 | opts.Intermediates.AddCert(c) | ||
459 | } | ||
460 | if _, err := leaf.Verify(opts); err != nil { | ||
461 | t.Error(err) | ||
462 | } | ||
463 | |||
464 | if san := leaf.DNSNames[0]; san != tt.domain { | ||
465 | t.Errorf("got SAN %q, expected %q", san, tt.domain) | ||
466 | } | ||
467 | |||
468 | if tt.verify != nil { | ||
469 | tt.verify(t, man, leaf) | ||
470 | } | ||
471 | }) | ||
472 | } | ||
473 | } | ||
474 | |||
475 | func TestGetCertificate_failedAttempt(t *testing.T) { | ||
476 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
477 | w.WriteHeader(http.StatusBadRequest) | ||
478 | })) | ||
479 | defer ts.Close() | ||
480 | |||
481 | d := createCertRetryAfter | ||
482 | f := testDidRemoveState | ||
483 | defer func() { | ||
484 | createCertRetryAfter = d | ||
485 | testDidRemoveState = f | ||
486 | }() | ||
487 | createCertRetryAfter = 0 | ||
488 | done := make(chan struct{}) | ||
489 | testDidRemoveState = func(ck certKey) { | ||
490 | if ck != exampleCertKey { | ||
491 | t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey) | ||
492 | } | ||
493 | close(done) | ||
494 | } | ||
495 | |||
496 | man := &Manager{ | ||
497 | Prompt: AcceptTOS, | ||
498 | Client: &acme.Client{ | ||
499 | DirectoryURL: ts.URL, | ||
500 | }, | ||
501 | } | ||
502 | defer man.stopRenew() | ||
503 | hello := clientHelloInfo(exampleDomain, algECDSA) | ||
504 | if _, err := man.GetCertificate(hello); err == nil { | ||
505 | t.Error("GetCertificate: err is nil") | ||
506 | } | ||
507 | |||
508 | <-done | ||
509 | man.stateMu.Lock() | ||
510 | defer man.stateMu.Unlock() | ||
511 | if v, exist := man.state[exampleCertKey]; exist { | ||
512 | t.Errorf("state exists for %v: %+v", exampleCertKey, v) | ||
513 | } | ||
514 | } | ||
515 | |||
516 | func TestRevokeFailedAuthz(t *testing.T) { | ||
517 | ca := acmetest.NewCAServer(t) | ||
518 | // Make the authz unfulfillable on the client side, so it will be left | ||
519 | // pending at the end of the verification attempt. | ||
520 | ca.ChallengeTypes("fake-01", "fake-02") | ||
521 | ca.Start() | ||
522 | |||
523 | m := testManager(t) | ||
524 | m.Client = &acme.Client{DirectoryURL: ca.URL()} | ||
525 | |||
526 | _, err := m.GetCertificate(clientHelloInfo("example.org", algECDSA)) | ||
527 | if err == nil { | ||
528 | t.Fatal("expected GetCertificate to fail") | ||
529 | } | ||
530 | |||
531 | logTicker := time.NewTicker(3 * time.Second) | ||
532 | defer logTicker.Stop() | ||
533 | for { | ||
534 | authz, err := m.Client.GetAuthorization(context.Background(), ca.URL()+"/authz/0") | ||
535 | if err != nil { | ||
536 | t.Fatal(err) | ||
537 | } | ||
538 | if authz.Status == acme.StatusDeactivated { | ||
539 | return | ||
540 | } | ||
541 | |||
542 | select { | ||
543 | case <-logTicker.C: | ||
544 | t.Logf("still waiting on revocations") | ||
545 | default: | ||
546 | } | ||
547 | time.Sleep(50 * time.Millisecond) | ||
548 | } | ||
549 | } | ||
550 | |||
551 | func TestHTTPHandlerDefaultFallback(t *testing.T) { | ||
552 | tt := []struct { | ||
553 | method, url string | ||
554 | wantCode int | ||
555 | wantLocation string | ||
556 | }{ | ||
557 | {"GET", "http://example.org", 302, "https://example.org/"}, | ||
558 | {"GET", "http://example.org/foo", 302, "https://example.org/foo"}, | ||
559 | {"GET", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"}, | ||
560 | {"GET", "http://example.org/?a=b", 302, "https://example.org/?a=b"}, | ||
561 | {"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"}, | ||
562 | {"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"}, | ||
563 | {"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"}, | ||
564 | {"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"}, | ||
565 | {"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"}, | ||
566 | {"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"}, | ||
567 | {"HEAD", "http://example.org", 302, "https://example.org/"}, | ||
568 | {"HEAD", "http://example.org/foo", 302, "https://example.org/foo"}, | ||
569 | {"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"}, | ||
570 | {"HEAD", "http://example.org/?a=b", 302, "https://example.org/?a=b"}, | ||
571 | {"HEAD", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"}, | ||
572 | {"POST", "http://example.org", 400, ""}, | ||
573 | {"PUT", "http://example.org", 400, ""}, | ||
574 | {"GET", "http://example.org/.well-known/acme-challenge/x", 404, ""}, | ||
575 | } | ||
576 | var m Manager | ||
577 | h := m.HTTPHandler(nil) | ||
578 | for i, test := range tt { | ||
579 | r := httptest.NewRequest(test.method, test.url, nil) | ||
580 | w := httptest.NewRecorder() | ||
581 | h.ServeHTTP(w, r) | ||
582 | if w.Code != test.wantCode { | ||
583 | t.Errorf("%d: w.Code = %d; want %d", i, w.Code, test.wantCode) | ||
584 | t.Errorf("%d: body: %s", i, w.Body.Bytes()) | ||
585 | } | ||
586 | if v := w.Header().Get("Location"); v != test.wantLocation { | ||
587 | t.Errorf("%d: Location = %q; want %q", i, v, test.wantLocation) | ||
588 | } | ||
589 | } | ||
590 | } | ||
591 | |||
592 | func TestAccountKeyCache(t *testing.T) { | ||
593 | m := Manager{Cache: newMemCache(t)} | ||
594 | ctx := context.Background() | ||
595 | k1, err := m.accountKey(ctx) | ||
596 | if err != nil { | ||
597 | t.Fatal(err) | ||
598 | } | ||
599 | k2, err := m.accountKey(ctx) | ||
600 | if err != nil { | ||
601 | t.Fatal(err) | ||
602 | } | ||
603 | if !reflect.DeepEqual(k1, k2) { | ||
604 | t.Errorf("account keys don't match: k1 = %#v; k2 = %#v", k1, k2) | ||
605 | } | ||
606 | } | ||
607 | |||
608 | func TestCache(t *testing.T) { | ||
609 | ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
610 | if err != nil { | ||
611 | t.Fatal(err) | ||
612 | } | ||
613 | cert, err := dummyCert(ecdsaKey.Public(), exampleDomain) | ||
614 | if err != nil { | ||
615 | t.Fatal(err) | ||
616 | } | ||
617 | ecdsaCert := &tls.Certificate{ | ||
618 | Certificate: [][]byte{cert}, | ||
619 | PrivateKey: ecdsaKey, | ||
620 | } | ||
621 | |||
622 | rsaKey, err := rsa.GenerateKey(rand.Reader, 512) | ||
623 | if err != nil { | ||
624 | t.Fatal(err) | ||
625 | } | ||
626 | cert, err = dummyCert(rsaKey.Public(), exampleDomain) | ||
627 | if err != nil { | ||
628 | t.Fatal(err) | ||
629 | } | ||
630 | rsaCert := &tls.Certificate{ | ||
631 | Certificate: [][]byte{cert}, | ||
632 | PrivateKey: rsaKey, | ||
633 | } | ||
634 | |||
635 | man := &Manager{Cache: newMemCache(t)} | ||
636 | defer man.stopRenew() | ||
637 | ctx := context.Background() | ||
638 | |||
639 | if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil { | ||
640 | t.Fatalf("man.cachePut: %v", err) | ||
641 | } | ||
642 | if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil { | ||
643 | t.Fatalf("man.cachePut: %v", err) | ||
644 | } | ||
645 | |||
646 | res, err := man.cacheGet(ctx, exampleCertKey) | ||
647 | if err != nil { | ||
648 | t.Fatalf("man.cacheGet: %v", err) | ||
649 | } | ||
650 | if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) { | ||
651 | t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert) | ||
652 | } | ||
653 | |||
654 | res, err = man.cacheGet(ctx, exampleCertKeyRSA) | ||
655 | if err != nil { | ||
656 | t.Fatalf("man.cacheGet: %v", err) | ||
657 | } | ||
658 | if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) { | ||
659 | t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert) | ||
660 | } | ||
661 | } | ||
662 | |||
663 | func TestHostWhitelist(t *testing.T) { | ||
664 | policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "éÉ.com") | ||
665 | tt := []struct { | ||
666 | host string | ||
667 | allow bool | ||
668 | }{ | ||
669 | {"example.com", true}, | ||
670 | {"example.org", true}, | ||
671 | {"xn--9caa.com", true}, // éé.com | ||
672 | {"one.example.com", false}, | ||
673 | {"two.example.org", false}, | ||
674 | {"three.example.net", false}, | ||
675 | {"dummy", false}, | ||
676 | } | ||
677 | for i, test := range tt { | ||
678 | err := policy(nil, test.host) | ||
679 | if err != nil && test.allow { | ||
680 | t.Errorf("%d: policy(%q): %v; want nil", i, test.host, err) | ||
681 | } | ||
682 | if err == nil && !test.allow { | ||
683 | t.Errorf("%d: policy(%q): nil; want an error", i, test.host) | ||
684 | } | ||
685 | } | ||
686 | } | ||
687 | |||
688 | func TestValidCert(t *testing.T) { | ||
689 | key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
690 | if err != nil { | ||
691 | t.Fatal(err) | ||
692 | } | ||
693 | key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
694 | if err != nil { | ||
695 | t.Fatal(err) | ||
696 | } | ||
697 | key3, err := rsa.GenerateKey(rand.Reader, 512) | ||
698 | if err != nil { | ||
699 | t.Fatal(err) | ||
700 | } | ||
701 | cert1, err := dummyCert(key1.Public(), "example.org") | ||
702 | if err != nil { | ||
703 | t.Fatal(err) | ||
704 | } | ||
705 | cert2, err := dummyCert(key2.Public(), "example.org") | ||
706 | if err != nil { | ||
707 | t.Fatal(err) | ||
708 | } | ||
709 | cert3, err := dummyCert(key3.Public(), "example.org") | ||
710 | if err != nil { | ||
711 | t.Fatal(err) | ||
712 | } | ||
713 | now := time.Now() | ||
714 | early, err := dateDummyCert(key1.Public(), now.Add(time.Hour), now.Add(2*time.Hour), "example.org") | ||
715 | if err != nil { | ||
716 | t.Fatal(err) | ||
717 | } | ||
718 | expired, err := dateDummyCert(key1.Public(), now.Add(-2*time.Hour), now.Add(-time.Hour), "example.org") | ||
719 | if err != nil { | ||
720 | t.Fatal(err) | ||
721 | } | ||
722 | |||
723 | tt := []struct { | ||
724 | ck certKey | ||
725 | key crypto.Signer | ||
726 | cert [][]byte | ||
727 | ok bool | ||
728 | }{ | ||
729 | {certKey{domain: "example.org"}, key1, [][]byte{cert1}, true}, | ||
730 | {certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true}, | ||
731 | {certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true}, | ||
732 | {certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false}, | ||
733 | {certKey{domain: "example.org"}, key1, [][]byte{{1}}, false}, | ||
734 | {certKey{domain: "example.org"}, key1, [][]byte{cert2}, false}, | ||
735 | {certKey{domain: "example.org"}, key2, [][]byte{cert1}, false}, | ||
736 | {certKey{domain: "example.org"}, key1, [][]byte{cert3}, false}, | ||
737 | {certKey{domain: "example.org"}, key3, [][]byte{cert1}, false}, | ||
738 | {certKey{domain: "example.net"}, key1, [][]byte{cert1}, false}, | ||
739 | {certKey{domain: "example.org"}, key1, [][]byte{early}, false}, | ||
740 | {certKey{domain: "example.org"}, key1, [][]byte{expired}, false}, | ||
741 | {certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false}, | ||
742 | {certKey{domain: "example.org"}, key3, [][]byte{cert3}, false}, | ||
743 | } | ||
744 | for i, test := range tt { | ||
745 | leaf, err := validCert(test.ck, test.cert, test.key, now) | ||
746 | if err != nil && test.ok { | ||
747 | t.Errorf("%d: err = %v", i, err) | ||
748 | } | ||
749 | if err == nil && !test.ok { | ||
750 | t.Errorf("%d: err is nil", i) | ||
751 | } | ||
752 | if err == nil && test.ok && leaf == nil { | ||
753 | t.Errorf("%d: leaf is nil", i) | ||
754 | } | ||
755 | } | ||
756 | } | ||
757 | |||
758 | type cacheGetFunc func(ctx context.Context, key string) ([]byte, error) | ||
759 | |||
760 | func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) { | ||
761 | return f(ctx, key) | ||
762 | } | ||
763 | |||
764 | func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error { | ||
765 | return fmt.Errorf("unsupported Put of %q = %q", key, data) | ||
766 | } | ||
767 | |||
768 | func (f cacheGetFunc) Delete(ctx context.Context, key string) error { | ||
769 | return fmt.Errorf("unsupported Delete of %q", key) | ||
770 | } | ||
771 | |||
772 | func TestManagerGetCertificateBogusSNI(t *testing.T) { | ||
773 | m := Manager{ | ||
774 | Prompt: AcceptTOS, | ||
775 | Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) { | ||
776 | return nil, fmt.Errorf("cache.Get of %s", key) | ||
777 | }), | ||
778 | } | ||
779 | tests := []struct { | ||
780 | name string | ||
781 | wantErr string | ||
782 | }{ | ||
783 | {"foo.com", "cache.Get of foo.com"}, | ||
784 | {"foo.com.", "cache.Get of foo.com"}, | ||
785 | {`a\b.com`, "acme/autocert: server name contains invalid character"}, | ||
786 | {`a/b.com`, "acme/autocert: server name contains invalid character"}, | ||
787 | {"", "acme/autocert: missing server name"}, | ||
788 | {"foo", "acme/autocert: server name component count invalid"}, | ||
789 | {".foo", "acme/autocert: server name component count invalid"}, | ||
790 | {"foo.", "acme/autocert: server name component count invalid"}, | ||
791 | {"fo.o", "cache.Get of fo.o"}, | ||
792 | } | ||
793 | for _, tt := range tests { | ||
794 | _, err := m.GetCertificate(clientHelloInfo(tt.name, algECDSA)) | ||
795 | got := fmt.Sprint(err) | ||
796 | if got != tt.wantErr { | ||
797 | t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr) | ||
798 | } | ||
799 | } | ||
800 | } | ||
801 | |||
802 | func TestCertRequest(t *testing.T) { | ||
803 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
804 | if err != nil { | ||
805 | t.Fatal(err) | ||
806 | } | ||
807 | // An extension from RFC7633. Any will do. | ||
808 | ext := pkix.Extension{ | ||
809 | Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1}, | ||
810 | Value: []byte("dummy"), | ||
811 | } | ||
812 | b, err := certRequest(key, "example.org", []pkix.Extension{ext}) | ||
813 | if err != nil { | ||
814 | t.Fatalf("certRequest: %v", err) | ||
815 | } | ||
816 | r, err := x509.ParseCertificateRequest(b) | ||
817 | if err != nil { | ||
818 | t.Fatalf("ParseCertificateRequest: %v", err) | ||
819 | } | ||
820 | var found bool | ||
821 | for _, v := range r.Extensions { | ||
822 | if v.Id.Equal(ext.Id) { | ||
823 | found = true | ||
824 | break | ||
825 | } | ||
826 | } | ||
827 | if !found { | ||
828 | t.Errorf("want %v in Extensions: %v", ext, r.Extensions) | ||
829 | } | ||
830 | } | ||
831 | |||
832 | func TestSupportsECDSA(t *testing.T) { | ||
833 | tests := []struct { | ||
834 | CipherSuites []uint16 | ||
835 | SignatureSchemes []tls.SignatureScheme | ||
836 | SupportedCurves []tls.CurveID | ||
837 | ecdsaOk bool | ||
838 | }{ | ||
839 | {[]uint16{ | ||
840 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | ||
841 | }, nil, nil, false}, | ||
842 | {[]uint16{ | ||
843 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
844 | }, nil, nil, true}, | ||
845 | |||
846 | // SignatureSchemes limits, not extends, CipherSuites | ||
847 | {[]uint16{ | ||
848 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | ||
849 | }, []tls.SignatureScheme{ | ||
850 | tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, | ||
851 | }, nil, false}, | ||
852 | {[]uint16{ | ||
853 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
854 | }, []tls.SignatureScheme{ | ||
855 | tls.PKCS1WithSHA256, | ||
856 | }, nil, false}, | ||
857 | {[]uint16{ | ||
858 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
859 | }, []tls.SignatureScheme{ | ||
860 | tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, | ||
861 | }, nil, true}, | ||
862 | |||
863 | {[]uint16{ | ||
864 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
865 | }, []tls.SignatureScheme{ | ||
866 | tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, | ||
867 | }, []tls.CurveID{ | ||
868 | tls.CurveP521, | ||
869 | }, false}, | ||
870 | {[]uint16{ | ||
871 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||
872 | }, []tls.SignatureScheme{ | ||
873 | tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, | ||
874 | }, []tls.CurveID{ | ||
875 | tls.CurveP256, | ||
876 | tls.CurveP521, | ||
877 | }, true}, | ||
878 | } | ||
879 | for i, tt := range tests { | ||
880 | result := supportsECDSA(&tls.ClientHelloInfo{ | ||
881 | CipherSuites: tt.CipherSuites, | ||
882 | SignatureSchemes: tt.SignatureSchemes, | ||
883 | SupportedCurves: tt.SupportedCurves, | ||
884 | }) | ||
885 | if result != tt.ecdsaOk { | ||
886 | t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk) | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | |||
891 | func TestEndToEndALPN(t *testing.T) { | ||
892 | const domain = "example.org" | ||
893 | |||
894 | // ACME CA server | ||
895 | ca := acmetest.NewCAServer(t).Start() | ||
896 | |||
897 | // User HTTPS server. | ||
898 | m := &Manager{ | ||
899 | Prompt: AcceptTOS, | ||
900 | Client: &acme.Client{DirectoryURL: ca.URL()}, | ||
901 | } | ||
902 | us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
903 | w.Write([]byte("OK")) | ||
904 | })) | ||
905 | us.TLS = &tls.Config{ | ||
906 | NextProtos: []string{"http/1.1", acme.ALPNProto}, | ||
907 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
908 | cert, err := m.GetCertificate(hello) | ||
909 | if err != nil { | ||
910 | t.Errorf("m.GetCertificate: %v", err) | ||
911 | } | ||
912 | return cert, err | ||
913 | }, | ||
914 | } | ||
915 | us.StartTLS() | ||
916 | defer us.Close() | ||
917 | // In TLS-ALPN challenge verification, CA connects to the domain:443 in question. | ||
918 | // Because the domain won't resolve in tests, we need to tell the CA | ||
919 | // where to dial to instead. | ||
920 | ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://")) | ||
921 | |||
922 | // A client visiting user's HTTPS server. | ||
923 | tr := &http.Transport{ | ||
924 | TLSClientConfig: &tls.Config{ | ||
925 | RootCAs: ca.Roots(), | ||
926 | ServerName: domain, | ||
927 | }, | ||
928 | } | ||
929 | client := &http.Client{Transport: tr} | ||
930 | res, err := client.Get(us.URL) | ||
931 | if err != nil { | ||
932 | t.Fatal(err) | ||
933 | } | ||
934 | defer res.Body.Close() | ||
935 | b, err := io.ReadAll(res.Body) | ||
936 | if err != nil { | ||
937 | t.Fatal(err) | ||
938 | } | ||
939 | if v := string(b); v != "OK" { | ||
940 | t.Errorf("user server response: %q; want 'OK'", v) | ||
941 | } | ||
942 | } | ||
943 | |||
944 | func TestEndToEndHTTP(t *testing.T) { | ||
945 | const domain = "example.org" | ||
946 | |||
947 | // ACME CA server. | ||
948 | ca := acmetest.NewCAServer(t).ChallengeTypes("http-01").Start() | ||
949 | |||
950 | // User HTTP server for the ACME challenge. | ||
951 | m := testManager(t) | ||
952 | m.Client = &acme.Client{DirectoryURL: ca.URL()} | ||
953 | s := httptest.NewServer(m.HTTPHandler(nil)) | ||
954 | defer s.Close() | ||
955 | |||
956 | // User HTTPS server. | ||
957 | ss := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
958 | w.Write([]byte("OK")) | ||
959 | })) | ||
960 | ss.TLS = &tls.Config{ | ||
961 | NextProtos: []string{"http/1.1", acme.ALPNProto}, | ||
962 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
963 | cert, err := m.GetCertificate(hello) | ||
964 | if err != nil { | ||
965 | t.Errorf("m.GetCertificate: %v", err) | ||
966 | } | ||
967 | return cert, err | ||
968 | }, | ||
969 | } | ||
970 | ss.StartTLS() | ||
971 | defer ss.Close() | ||
972 | |||
973 | // Redirect the CA requests to the HTTP server. | ||
974 | ca.Resolve(domain, strings.TrimPrefix(s.URL, "http://")) | ||
975 | |||
976 | // A client visiting user's HTTPS server. | ||
977 | tr := &http.Transport{ | ||
978 | TLSClientConfig: &tls.Config{ | ||
979 | RootCAs: ca.Roots(), | ||
980 | ServerName: domain, | ||
981 | }, | ||
982 | } | ||
983 | client := &http.Client{Transport: tr} | ||
984 | res, err := client.Get(ss.URL) | ||
985 | if err != nil { | ||
986 | t.Fatal(err) | ||
987 | } | ||
988 | defer res.Body.Close() | ||
989 | b, err := io.ReadAll(res.Body) | ||
990 | if err != nil { | ||
991 | t.Fatal(err) | ||
992 | } | ||
993 | if v := string(b); v != "OK" { | ||
994 | t.Errorf("user server response: %q; want 'OK'", v) | ||
995 | } | ||
996 | } | ||
997 | |||
998 | type dnsManager struct { | ||
999 | CA *acmetest.CAServer | ||
1000 | } | ||
1001 | |||
1002 | func (m *dnsManager) Fulfill(ctx context.Context, domain string, record string) error { | ||
1003 | m.CA.PutDNSResponse(domain, record) | ||
1004 | return nil | ||
1005 | } | ||
1006 | |||
1007 | func (m *dnsManager) Cleanup(ctx context.Context, domain string, record string) { | ||
1008 | } | ||
1009 | |||
1010 | func TestEndToEndDNS(t *testing.T) { | ||
1011 | const domain = "example.org" | ||
1012 | |||
1013 | // ACME CA server | ||
1014 | ca := acmetest.NewCAServer(t).ChallengeTypes("dns-01") | ||
1015 | ca.Start() | ||
1016 | |||
1017 | // User HTTPS server. | ||
1018 | m := &Manager{ | ||
1019 | Prompt: AcceptTOS, | ||
1020 | Client: &acme.Client{DirectoryURL: ca.URL()}, | ||
1021 | DNSManager: &dnsManager{ca}, | ||
1022 | } | ||
1023 | us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
1024 | w.Write([]byte("OK")) | ||
1025 | })) | ||
1026 | us.TLS = &tls.Config{ | ||
1027 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
1028 | cert, err := m.GetCertificate(hello) | ||
1029 | if err != nil { | ||
1030 | t.Errorf("m.GetCertificate: %v", err) | ||
1031 | } | ||
1032 | return cert, err | ||
1033 | }, | ||
1034 | } | ||
1035 | us.StartTLS() | ||
1036 | defer us.Close() | ||
1037 | |||
1038 | // A client visiting user's HTTPS server. | ||
1039 | tr := &http.Transport{ | ||
1040 | TLSClientConfig: &tls.Config{ | ||
1041 | RootCAs: ca.Roots(), | ||
1042 | ServerName: domain, | ||
1043 | }, | ||
1044 | } | ||
1045 | client := &http.Client{Transport: tr} | ||
1046 | res, err := client.Get(us.URL) | ||
1047 | if err != nil { | ||
1048 | t.Fatal(err) | ||
1049 | } | ||
1050 | defer res.Body.Close() | ||
1051 | b, err := io.ReadAll(res.Body) | ||
1052 | if err != nil { | ||
1053 | t.Fatal(err) | ||
1054 | } | ||
1055 | if v := string(b); v != "OK" { | ||
1056 | t.Errorf("user server response: %q; want 'OK'", v) | ||
1057 | } | ||
1058 | } | ||
diff --git a/crypto/acme/autocert/fork/cache.go b/crypto/acme/autocert/fork/cache.go new file mode 100644 index 0000000..934c389 --- /dev/null +++ b/crypto/acme/autocert/fork/cache.go | |||
@@ -0,0 +1,135 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "context" | ||
9 | "errors" | ||
10 | "os" | ||
11 | "path/filepath" | ||
12 | ) | ||
13 | |||
14 | // ErrCacheMiss is returned when a certificate is not found in cache. | ||
15 | var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss") | ||
16 | |||
17 | // Cache is used by Manager to store and retrieve previously obtained certificates | ||
18 | // and other account data as opaque blobs. | ||
19 | // | ||
20 | // Cache implementations should not rely on the key naming pattern. Keys can | ||
21 | // include any printable ASCII characters, except the following: \/:*?"<>| | ||
22 | type Cache interface { | ||
23 | // Get returns a certificate data for the specified key. | ||
24 | // If there's no such key, Get returns ErrCacheMiss. | ||
25 | Get(ctx context.Context, key string) ([]byte, error) | ||
26 | |||
27 | // Put stores the data in the cache under the specified key. | ||
28 | // Underlying implementations may use any data storage format, | ||
29 | // as long as the reverse operation, Get, results in the original data. | ||
30 | Put(ctx context.Context, key string, data []byte) error | ||
31 | |||
32 | // Delete removes a certificate data from the cache under the specified key. | ||
33 | // If there's no such key in the cache, Delete returns nil. | ||
34 | Delete(ctx context.Context, key string) error | ||
35 | } | ||
36 | |||
37 | // DirCache implements Cache using a directory on the local filesystem. | ||
38 | // If the directory does not exist, it will be created with 0700 permissions. | ||
39 | type DirCache string | ||
40 | |||
41 | // Get reads a certificate data from the specified file name. | ||
42 | func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) { | ||
43 | name = filepath.Join(string(d), filepath.Clean("/"+name)) | ||
44 | var ( | ||
45 | data []byte | ||
46 | err error | ||
47 | done = make(chan struct{}) | ||
48 | ) | ||
49 | go func() { | ||
50 | data, err = os.ReadFile(name) | ||
51 | close(done) | ||
52 | }() | ||
53 | select { | ||
54 | case <-ctx.Done(): | ||
55 | return nil, ctx.Err() | ||
56 | case <-done: | ||
57 | } | ||
58 | if os.IsNotExist(err) { | ||
59 | return nil, ErrCacheMiss | ||
60 | } | ||
61 | return data, err | ||
62 | } | ||
63 | |||
64 | // Put writes the certificate data to the specified file name. | ||
65 | // The file will be created with 0600 permissions. | ||
66 | func (d DirCache) Put(ctx context.Context, name string, data []byte) error { | ||
67 | if err := os.MkdirAll(string(d), 0700); err != nil { | ||
68 | return err | ||
69 | } | ||
70 | |||
71 | done := make(chan struct{}) | ||
72 | var err error | ||
73 | go func() { | ||
74 | defer close(done) | ||
75 | var tmp string | ||
76 | if tmp, err = d.writeTempFile(name, data); err != nil { | ||
77 | return | ||
78 | } | ||
79 | defer os.Remove(tmp) | ||
80 | select { | ||
81 | case <-ctx.Done(): | ||
82 | // Don't overwrite the file if the context was canceled. | ||
83 | default: | ||
84 | newName := filepath.Join(string(d), filepath.Clean("/"+name)) | ||
85 | err = os.Rename(tmp, newName) | ||
86 | } | ||
87 | }() | ||
88 | select { | ||
89 | case <-ctx.Done(): | ||
90 | return ctx.Err() | ||
91 | case <-done: | ||
92 | } | ||
93 | return err | ||
94 | } | ||
95 | |||
96 | // Delete removes the specified file name. | ||
97 | func (d DirCache) Delete(ctx context.Context, name string) error { | ||
98 | name = filepath.Join(string(d), filepath.Clean("/"+name)) | ||
99 | var ( | ||
100 | err error | ||
101 | done = make(chan struct{}) | ||
102 | ) | ||
103 | go func() { | ||
104 | err = os.Remove(name) | ||
105 | close(done) | ||
106 | }() | ||
107 | select { | ||
108 | case <-ctx.Done(): | ||
109 | return ctx.Err() | ||
110 | case <-done: | ||
111 | } | ||
112 | if err != nil && !os.IsNotExist(err) { | ||
113 | return err | ||
114 | } | ||
115 | return nil | ||
116 | } | ||
117 | |||
118 | // writeTempFile writes b to a temporary file, closes the file and returns its path. | ||
119 | func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) { | ||
120 | // TempFile uses 0600 permissions | ||
121 | f, err := os.CreateTemp(string(d), prefix) | ||
122 | if err != nil { | ||
123 | return "", err | ||
124 | } | ||
125 | defer func() { | ||
126 | if reterr != nil { | ||
127 | os.Remove(f.Name()) | ||
128 | } | ||
129 | }() | ||
130 | if _, err := f.Write(b); err != nil { | ||
131 | f.Close() | ||
132 | return "", err | ||
133 | } | ||
134 | return f.Name(), f.Close() | ||
135 | } | ||
diff --git a/crypto/acme/autocert/fork/cache_test.go b/crypto/acme/autocert/fork/cache_test.go new file mode 100644 index 0000000..0d690a6 --- /dev/null +++ b/crypto/acme/autocert/fork/cache_test.go | |||
@@ -0,0 +1,66 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "context" | ||
9 | "os" | ||
10 | "path/filepath" | ||
11 | "reflect" | ||
12 | "testing" | ||
13 | ) | ||
14 | |||
15 | // make sure DirCache satisfies Cache interface | ||
16 | var _ Cache = DirCache("/") | ||
17 | |||
18 | func TestDirCache(t *testing.T) { | ||
19 | dir, err := os.MkdirTemp("", "autocert") | ||
20 | if err != nil { | ||
21 | t.Fatal(err) | ||
22 | } | ||
23 | defer os.RemoveAll(dir) | ||
24 | dir = filepath.Join(dir, "certs") // a nonexistent dir | ||
25 | cache := DirCache(dir) | ||
26 | ctx := context.Background() | ||
27 | |||
28 | // test cache miss | ||
29 | if _, err := cache.Get(ctx, "nonexistent"); err != ErrCacheMiss { | ||
30 | t.Errorf("get: %v; want ErrCacheMiss", err) | ||
31 | } | ||
32 | |||
33 | // test put/get | ||
34 | b1 := []byte{1} | ||
35 | if err := cache.Put(ctx, "dummy", b1); err != nil { | ||
36 | t.Fatalf("put: %v", err) | ||
37 | } | ||
38 | b2, err := cache.Get(ctx, "dummy") | ||
39 | if err != nil { | ||
40 | t.Fatalf("get: %v", err) | ||
41 | } | ||
42 | if !reflect.DeepEqual(b1, b2) { | ||
43 | t.Errorf("b1 = %v; want %v", b1, b2) | ||
44 | } | ||
45 | name := filepath.Join(dir, "dummy") | ||
46 | if _, err := os.Stat(name); err != nil { | ||
47 | t.Error(err) | ||
48 | } | ||
49 | |||
50 | // test put deletes temp file | ||
51 | tmp, err := filepath.Glob(name + "?*") | ||
52 | if err != nil { | ||
53 | t.Error(err) | ||
54 | } | ||
55 | if tmp != nil { | ||
56 | t.Errorf("temp file exists: %s", tmp) | ||
57 | } | ||
58 | |||
59 | // test delete | ||
60 | if err := cache.Delete(ctx, "dummy"); err != nil { | ||
61 | t.Fatalf("delete: %v", err) | ||
62 | } | ||
63 | if _, err := cache.Get(ctx, "dummy"); err != ErrCacheMiss { | ||
64 | t.Errorf("get: %v; want ErrCacheMiss", err) | ||
65 | } | ||
66 | } | ||
diff --git a/crypto/acme/autocert/fork/internal/acmetest/ca.go b/crypto/acme/autocert/fork/internal/acmetest/ca.go new file mode 100644 index 0000000..8fc4273 --- /dev/null +++ b/crypto/acme/autocert/fork/internal/acmetest/ca.go | |||
@@ -0,0 +1,817 @@ | |||
1 | // Copyright 2018 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | // Package acmetest provides types for testing acme and autocert packages. | ||
6 | // | ||
7 | // TODO: Consider moving this to x/crypto/acme/internal/acmetest for acme tests as well. | ||
8 | package acmetest | ||
9 | |||
10 | import ( | ||
11 | "context" | ||
12 | "crypto" | ||
13 | "crypto/ecdsa" | ||
14 | "crypto/elliptic" | ||
15 | "crypto/rand" | ||
16 | "crypto/rsa" | ||
17 | "crypto/tls" | ||
18 | "crypto/x509" | ||
19 | "crypto/x509/pkix" | ||
20 | "encoding/asn1" | ||
21 | "encoding/base64" | ||
22 | "encoding/json" | ||
23 | "encoding/pem" | ||
24 | "fmt" | ||
25 | "io" | ||
26 | "math/big" | ||
27 | "net" | ||
28 | "net/http" | ||
29 | "net/http/httptest" | ||
30 | "path" | ||
31 | "strconv" | ||
32 | "strings" | ||
33 | "sync" | ||
34 | "testing" | ||
35 | "time" | ||
36 | |||
37 | "golang.org/x/crypto/acme" | ||
38 | ) | ||
39 | |||
40 | // CAServer is a simple test server which implements ACME spec bits needed for testing. | ||
41 | type CAServer struct { | ||
42 | rootKey crypto.Signer | ||
43 | rootCert []byte // DER encoding | ||
44 | rootTemplate *x509.Certificate | ||
45 | |||
46 | t *testing.T | ||
47 | server *httptest.Server | ||
48 | issuer pkix.Name | ||
49 | challengeTypes []string | ||
50 | url string | ||
51 | roots *x509.CertPool | ||
52 | eabRequired bool | ||
53 | |||
54 | mu sync.Mutex | ||
55 | certCount int // number of issued certs | ||
56 | acctRegistered bool // set once an account has been registered | ||
57 | domainAddr map[string]string // domain name to addr:port resolution | ||
58 | dnsResponses map[string]string // responses to dns challenges | ||
59 | domainGetCert map[string]getCertificateFunc // domain name to GetCertificate function | ||
60 | domainHandler map[string]http.Handler // domain name to Handle function | ||
61 | validAuthz map[string]*authorization // valid authz, keyed by domain name | ||
62 | authorizations []*authorization // all authz, index is used as ID | ||
63 | orders []*order // index is used as order ID | ||
64 | errors []error // encountered client errors | ||
65 | } | ||
66 | |||
67 | type getCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) | ||
68 | |||
69 | // NewCAServer creates a new ACME test server. The returned CAServer issues | ||
70 | // certs signed with the CA roots available in the Roots field. | ||
71 | func NewCAServer(t *testing.T) *CAServer { | ||
72 | ca := &CAServer{t: t, | ||
73 | challengeTypes: []string{"fake-01", "tls-alpn-01", "http-01", "dns-01"}, | ||
74 | domainAddr: make(map[string]string), | ||
75 | domainGetCert: make(map[string]getCertificateFunc), | ||
76 | domainHandler: make(map[string]http.Handler), | ||
77 | validAuthz: make(map[string]*authorization), | ||
78 | dnsResponses: make(map[string]string), | ||
79 | } | ||
80 | |||
81 | ca.server = httptest.NewUnstartedServer(http.HandlerFunc(ca.handle)) | ||
82 | |||
83 | r, err := rand.Int(rand.Reader, big.NewInt(1000000)) | ||
84 | if err != nil { | ||
85 | panic(fmt.Sprintf("rand.Int: %v", err)) | ||
86 | } | ||
87 | ca.issuer = pkix.Name{ | ||
88 | Organization: []string{"Test Acme Co"}, | ||
89 | CommonName: "Root CA " + r.String(), | ||
90 | } | ||
91 | |||
92 | return ca | ||
93 | } | ||
94 | |||
95 | func (ca *CAServer) generateRoot() { | ||
96 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
97 | if err != nil { | ||
98 | panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err)) | ||
99 | } | ||
100 | tmpl := &x509.Certificate{ | ||
101 | SerialNumber: big.NewInt(1), | ||
102 | Subject: ca.issuer, | ||
103 | NotBefore: time.Now(), | ||
104 | NotAfter: time.Now().Add(365 * 24 * time.Hour), | ||
105 | KeyUsage: x509.KeyUsageCertSign, | ||
106 | BasicConstraintsValid: true, | ||
107 | IsCA: true, | ||
108 | } | ||
109 | der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) | ||
110 | if err != nil { | ||
111 | panic(fmt.Sprintf("x509.CreateCertificate: %v", err)) | ||
112 | } | ||
113 | cert, err := x509.ParseCertificate(der) | ||
114 | if err != nil { | ||
115 | panic(fmt.Sprintf("x509.ParseCertificate: %v", err)) | ||
116 | } | ||
117 | ca.roots = x509.NewCertPool() | ||
118 | ca.roots.AddCert(cert) | ||
119 | ca.rootKey = key | ||
120 | ca.rootCert = der | ||
121 | ca.rootTemplate = tmpl | ||
122 | } | ||
123 | |||
124 | func (ca *CAServer) PutDNSResponse(domain, record string) { | ||
125 | ca.mu.Lock() | ||
126 | defer ca.mu.Unlock() | ||
127 | ca.dnsResponses[domain] = record | ||
128 | } | ||
129 | |||
130 | // IssuerName sets the name of the issuing CA. | ||
131 | func (ca *CAServer) IssuerName(name pkix.Name) *CAServer { | ||
132 | if ca.url != "" { | ||
133 | panic("IssuerName must be called before Start") | ||
134 | } | ||
135 | ca.issuer = name | ||
136 | return ca | ||
137 | } | ||
138 | |||
139 | // ChallengeTypes sets the supported challenge types. | ||
140 | func (ca *CAServer) ChallengeTypes(types ...string) *CAServer { | ||
141 | if ca.url != "" { | ||
142 | panic("ChallengeTypes must be called before Start") | ||
143 | } | ||
144 | ca.challengeTypes = types | ||
145 | return ca | ||
146 | } | ||
147 | |||
148 | // URL returns the server address, after Start has been called. | ||
149 | func (ca *CAServer) URL() string { | ||
150 | if ca.url == "" { | ||
151 | panic("URL called before Start") | ||
152 | } | ||
153 | return ca.url | ||
154 | } | ||
155 | |||
156 | // Roots returns a pool cointaining the CA root. | ||
157 | func (ca *CAServer) Roots() *x509.CertPool { | ||
158 | if ca.url == "" { | ||
159 | panic("Roots called before Start") | ||
160 | } | ||
161 | return ca.roots | ||
162 | } | ||
163 | |||
164 | // ExternalAccountRequired makes an EAB JWS required for account registration. | ||
165 | func (ca *CAServer) ExternalAccountRequired() *CAServer { | ||
166 | if ca.url != "" { | ||
167 | panic("ExternalAccountRequired must be called before Start") | ||
168 | } | ||
169 | ca.eabRequired = true | ||
170 | return ca | ||
171 | } | ||
172 | |||
173 | // Start starts serving requests. The server address becomes available in the | ||
174 | // URL field. | ||
175 | func (ca *CAServer) Start() *CAServer { | ||
176 | if ca.url == "" { | ||
177 | ca.generateRoot() | ||
178 | ca.server.Start() | ||
179 | ca.t.Cleanup(ca.server.Close) | ||
180 | ca.url = ca.server.URL | ||
181 | } | ||
182 | return ca | ||
183 | } | ||
184 | |||
185 | func (ca *CAServer) serverURL(format string, arg ...interface{}) string { | ||
186 | return ca.server.URL + fmt.Sprintf(format, arg...) | ||
187 | } | ||
188 | |||
189 | func (ca *CAServer) addr(domain string) (string, bool) { | ||
190 | ca.mu.Lock() | ||
191 | defer ca.mu.Unlock() | ||
192 | addr, ok := ca.domainAddr[domain] | ||
193 | return addr, ok | ||
194 | } | ||
195 | |||
196 | func (ca *CAServer) getCert(domain string) (getCertificateFunc, bool) { | ||
197 | ca.mu.Lock() | ||
198 | defer ca.mu.Unlock() | ||
199 | f, ok := ca.domainGetCert[domain] | ||
200 | return f, ok | ||
201 | } | ||
202 | |||
203 | func (ca *CAServer) getHandler(domain string) (http.Handler, bool) { | ||
204 | ca.mu.Lock() | ||
205 | defer ca.mu.Unlock() | ||
206 | h, ok := ca.domainHandler[domain] | ||
207 | return h, ok | ||
208 | } | ||
209 | |||
210 | func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) { | ||
211 | s := fmt.Sprintf(format, a...) | ||
212 | ca.t.Errorf(format, a...) | ||
213 | http.Error(w, s, code) | ||
214 | } | ||
215 | |||
216 | // Resolve adds a domain to address resolution for the ca to dial to | ||
217 | // when validating challenges for the domain authorization. | ||
218 | func (ca *CAServer) Resolve(domain, addr string) { | ||
219 | ca.mu.Lock() | ||
220 | defer ca.mu.Unlock() | ||
221 | ca.domainAddr[domain] = addr | ||
222 | } | ||
223 | |||
224 | // ResolveGetCertificate redirects TLS connections for domain to f when | ||
225 | // validating challenges for the domain authorization. | ||
226 | func (ca *CAServer) ResolveGetCertificate(domain string, f getCertificateFunc) { | ||
227 | ca.mu.Lock() | ||
228 | defer ca.mu.Unlock() | ||
229 | ca.domainGetCert[domain] = f | ||
230 | } | ||
231 | |||
232 | // ResolveHandler redirects HTTP requests for domain to f when | ||
233 | // validating challenges for the domain authorization. | ||
234 | func (ca *CAServer) ResolveHandler(domain string, h http.Handler) { | ||
235 | ca.mu.Lock() | ||
236 | defer ca.mu.Unlock() | ||
237 | ca.domainHandler[domain] = h | ||
238 | } | ||
239 | |||
240 | type discovery struct { | ||
241 | NewNonce string `json:"newNonce"` | ||
242 | NewAccount string `json:"newAccount"` | ||
243 | NewOrder string `json:"newOrder"` | ||
244 | NewAuthz string `json:"newAuthz"` | ||
245 | |||
246 | Meta discoveryMeta `json:"meta,omitempty"` | ||
247 | } | ||
248 | |||
249 | type discoveryMeta struct { | ||
250 | ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"` | ||
251 | } | ||
252 | |||
253 | type challenge struct { | ||
254 | URI string `json:"uri"` | ||
255 | Type string `json:"type"` | ||
256 | Token string `json:"token"` | ||
257 | } | ||
258 | |||
259 | type authorization struct { | ||
260 | Status string `json:"status"` | ||
261 | Challenges []challenge `json:"challenges"` | ||
262 | |||
263 | domain string | ||
264 | id int | ||
265 | } | ||
266 | |||
267 | type order struct { | ||
268 | Status string `json:"status"` | ||
269 | AuthzURLs []string `json:"authorizations"` | ||
270 | FinalizeURL string `json:"finalize"` // CSR submit URL | ||
271 | CertURL string `json:"certificate"` // already issued cert | ||
272 | |||
273 | leaf []byte // issued cert in DER format | ||
274 | } | ||
275 | |||
276 | func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) { | ||
277 | ca.t.Logf("%s %s", r.Method, r.URL) | ||
278 | w.Header().Set("Replay-Nonce", "nonce") | ||
279 | // TODO: Verify nonce header for all POST requests. | ||
280 | |||
281 | switch { | ||
282 | default: | ||
283 | ca.httpErrorf(w, http.StatusBadRequest, "unrecognized r.URL.Path: %s", r.URL.Path) | ||
284 | |||
285 | // Discovery request. | ||
286 | case r.URL.Path == "/": | ||
287 | resp := &discovery{ | ||
288 | NewNonce: ca.serverURL("/new-nonce"), | ||
289 | NewAccount: ca.serverURL("/new-account"), | ||
290 | NewOrder: ca.serverURL("/new-order"), | ||
291 | Meta: discoveryMeta{ | ||
292 | ExternalAccountRequired: ca.eabRequired, | ||
293 | }, | ||
294 | } | ||
295 | if err := json.NewEncoder(w).Encode(resp); err != nil { | ||
296 | panic(fmt.Sprintf("discovery response: %v", err)) | ||
297 | } | ||
298 | |||
299 | // Nonce requests. | ||
300 | case r.URL.Path == "/new-nonce": | ||
301 | // Nonce values are always set. Nothing else to do. | ||
302 | return | ||
303 | |||
304 | // Client key registration request. | ||
305 | case r.URL.Path == "/new-account": | ||
306 | ca.mu.Lock() | ||
307 | defer ca.mu.Unlock() | ||
308 | if ca.acctRegistered { | ||
309 | ca.httpErrorf(w, http.StatusServiceUnavailable, "multiple accounts are not implemented") | ||
310 | return | ||
311 | } | ||
312 | ca.acctRegistered = true | ||
313 | |||
314 | var req struct { | ||
315 | ExternalAccountBinding json.RawMessage | ||
316 | } | ||
317 | |||
318 | if err := decodePayload(&req, r.Body); err != nil { | ||
319 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
320 | return | ||
321 | } | ||
322 | |||
323 | if ca.eabRequired && len(req.ExternalAccountBinding) == 0 { | ||
324 | ca.httpErrorf(w, http.StatusBadRequest, "registration failed: no JWS for EAB") | ||
325 | return | ||
326 | } | ||
327 | |||
328 | // TODO: Check the user account key against a ca.accountKeys? | ||
329 | w.Header().Set("Location", ca.serverURL("/accounts/1")) | ||
330 | w.WriteHeader(http.StatusCreated) | ||
331 | w.Write([]byte("{}")) | ||
332 | |||
333 | // New order request. | ||
334 | case r.URL.Path == "/new-order": | ||
335 | var req struct { | ||
336 | Identifiers []struct{ Value string } | ||
337 | } | ||
338 | if err := decodePayload(&req, r.Body); err != nil { | ||
339 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
340 | return | ||
341 | } | ||
342 | ca.mu.Lock() | ||
343 | defer ca.mu.Unlock() | ||
344 | o := &order{Status: acme.StatusPending} | ||
345 | for _, id := range req.Identifiers { | ||
346 | z := ca.authz(id.Value) | ||
347 | o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%d", z.id)) | ||
348 | } | ||
349 | orderID := len(ca.orders) | ||
350 | ca.orders = append(ca.orders, o) | ||
351 | w.Header().Set("Location", ca.serverURL("/orders/%d", orderID)) | ||
352 | w.WriteHeader(http.StatusCreated) | ||
353 | if err := json.NewEncoder(w).Encode(o); err != nil { | ||
354 | panic(err) | ||
355 | } | ||
356 | |||
357 | // Existing order status requests. | ||
358 | case strings.HasPrefix(r.URL.Path, "/orders/"): | ||
359 | ca.mu.Lock() | ||
360 | defer ca.mu.Unlock() | ||
361 | o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/orders/")) | ||
362 | if err != nil { | ||
363 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
364 | return | ||
365 | } | ||
366 | if err := json.NewEncoder(w).Encode(o); err != nil { | ||
367 | panic(err) | ||
368 | } | ||
369 | |||
370 | // Accept challenge requests. | ||
371 | case strings.HasPrefix(r.URL.Path, "/challenge/"): | ||
372 | parts := strings.Split(r.URL.Path, "/") | ||
373 | typ, id := parts[len(parts)-2], parts[len(parts)-1] | ||
374 | ca.mu.Lock() | ||
375 | supported := false | ||
376 | for _, suppTyp := range ca.challengeTypes { | ||
377 | if suppTyp == typ { | ||
378 | supported = true | ||
379 | } | ||
380 | } | ||
381 | a, err := ca.storedAuthz(id) | ||
382 | ca.mu.Unlock() | ||
383 | if !supported { | ||
384 | ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ) | ||
385 | return | ||
386 | } | ||
387 | if err != nil { | ||
388 | ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err) | ||
389 | return | ||
390 | } | ||
391 | ca.validateChallenge(a, typ) | ||
392 | w.Write([]byte("{}")) | ||
393 | |||
394 | // Get authorization status requests. | ||
395 | case strings.HasPrefix(r.URL.Path, "/authz/"): | ||
396 | var req struct{ Status string } | ||
397 | decodePayload(&req, r.Body) | ||
398 | deactivate := req.Status == "deactivated" | ||
399 | ca.mu.Lock() | ||
400 | defer ca.mu.Unlock() | ||
401 | authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/")) | ||
402 | if err != nil { | ||
403 | ca.httpErrorf(w, http.StatusNotFound, "%v", err) | ||
404 | return | ||
405 | } | ||
406 | if deactivate { | ||
407 | // Note we don't invalidate authorized orders as we should. | ||
408 | authz.Status = "deactivated" | ||
409 | ca.t.Logf("authz %d is now %s", authz.id, authz.Status) | ||
410 | ca.updatePendingOrders() | ||
411 | } | ||
412 | if err := json.NewEncoder(w).Encode(authz); err != nil { | ||
413 | panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err)) | ||
414 | } | ||
415 | |||
416 | // Certificate issuance request. | ||
417 | case strings.HasPrefix(r.URL.Path, "/new-cert/"): | ||
418 | ca.mu.Lock() | ||
419 | defer ca.mu.Unlock() | ||
420 | orderID := strings.TrimPrefix(r.URL.Path, "/new-cert/") | ||
421 | o, err := ca.storedOrder(orderID) | ||
422 | if err != nil { | ||
423 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
424 | return | ||
425 | } | ||
426 | if o.Status != acme.StatusReady { | ||
427 | ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status) | ||
428 | return | ||
429 | } | ||
430 | // Validate CSR request. | ||
431 | var req struct { | ||
432 | CSR string `json:"csr"` | ||
433 | } | ||
434 | decodePayload(&req, r.Body) | ||
435 | b, _ := base64.RawURLEncoding.DecodeString(req.CSR) | ||
436 | csr, err := x509.ParseCertificateRequest(b) | ||
437 | if err != nil { | ||
438 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
439 | return | ||
440 | } | ||
441 | // Issue the certificate. | ||
442 | der, err := ca.leafCert(csr) | ||
443 | if err != nil { | ||
444 | ca.httpErrorf(w, http.StatusBadRequest, "new-cert response: ca.leafCert: %v", err) | ||
445 | return | ||
446 | } | ||
447 | o.leaf = der | ||
448 | o.CertURL = ca.serverURL("/issued-cert/%s", orderID) | ||
449 | o.Status = acme.StatusValid | ||
450 | if err := json.NewEncoder(w).Encode(o); err != nil { | ||
451 | panic(err) | ||
452 | } | ||
453 | |||
454 | // Already issued cert download requests. | ||
455 | case strings.HasPrefix(r.URL.Path, "/issued-cert/"): | ||
456 | ca.mu.Lock() | ||
457 | defer ca.mu.Unlock() | ||
458 | o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/issued-cert/")) | ||
459 | if err != nil { | ||
460 | ca.httpErrorf(w, http.StatusBadRequest, err.Error()) | ||
461 | return | ||
462 | } | ||
463 | if o.Status != acme.StatusValid { | ||
464 | ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status) | ||
465 | return | ||
466 | } | ||
467 | w.Header().Set("Content-Type", "application/pem-certificate-chain") | ||
468 | pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: o.leaf}) | ||
469 | pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: ca.rootCert}) | ||
470 | } | ||
471 | } | ||
472 | |||
473 | // storedOrder retrieves a previously created order at index i. | ||
474 | // It requires ca.mu to be locked. | ||
475 | func (ca *CAServer) storedOrder(i string) (*order, error) { | ||
476 | idx, err := strconv.Atoi(i) | ||
477 | if err != nil { | ||
478 | return nil, fmt.Errorf("storedOrder: %v", err) | ||
479 | } | ||
480 | if idx < 0 { | ||
481 | return nil, fmt.Errorf("storedOrder: invalid order index %d", idx) | ||
482 | } | ||
483 | if idx > len(ca.orders)-1 { | ||
484 | return nil, fmt.Errorf("storedOrder: no such order %d", idx) | ||
485 | } | ||
486 | |||
487 | ca.updatePendingOrders() | ||
488 | return ca.orders[idx], nil | ||
489 | } | ||
490 | |||
491 | // storedAuthz retrieves a previously created authz at index i. | ||
492 | // It requires ca.mu to be locked. | ||
493 | func (ca *CAServer) storedAuthz(i string) (*authorization, error) { | ||
494 | idx, err := strconv.Atoi(i) | ||
495 | if err != nil { | ||
496 | return nil, fmt.Errorf("storedAuthz: %v", err) | ||
497 | } | ||
498 | if idx < 0 { | ||
499 | return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx) | ||
500 | } | ||
501 | if idx > len(ca.authorizations)-1 { | ||
502 | return nil, fmt.Errorf("storedAuthz: no such authz %d", idx) | ||
503 | } | ||
504 | return ca.authorizations[idx], nil | ||
505 | } | ||
506 | |||
507 | // authz returns an existing valid authorization for the identifier or creates a | ||
508 | // new one. It requires ca.mu to be locked. | ||
509 | func (ca *CAServer) authz(identifier string) *authorization { | ||
510 | authz, ok := ca.validAuthz[identifier] | ||
511 | if !ok { | ||
512 | authzId := len(ca.authorizations) | ||
513 | authz = &authorization{ | ||
514 | id: authzId, | ||
515 | domain: identifier, | ||
516 | Status: acme.StatusPending, | ||
517 | } | ||
518 | for _, typ := range ca.challengeTypes { | ||
519 | authz.Challenges = append(authz.Challenges, challenge{ | ||
520 | Type: typ, | ||
521 | URI: ca.serverURL("/challenge/%s/%d", typ, authzId), | ||
522 | Token: challengeToken(authz.domain, typ, authzId), | ||
523 | }) | ||
524 | } | ||
525 | ca.authorizations = append(ca.authorizations, authz) | ||
526 | } | ||
527 | return authz | ||
528 | } | ||
529 | |||
530 | // leafCert issues a new certificate. | ||
531 | // It requires ca.mu to be locked. | ||
532 | func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) { | ||
533 | ca.certCount++ // next leaf cert serial number | ||
534 | leaf := &x509.Certificate{ | ||
535 | SerialNumber: big.NewInt(int64(ca.certCount)), | ||
536 | Subject: pkix.Name{Organization: []string{"Test Acme Co"}}, | ||
537 | NotBefore: time.Now(), | ||
538 | NotAfter: time.Now().Add(90 * 24 * time.Hour), | ||
539 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, | ||
540 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
541 | DNSNames: csr.DNSNames, | ||
542 | BasicConstraintsValid: true, | ||
543 | } | ||
544 | if len(csr.DNSNames) == 0 { | ||
545 | leaf.DNSNames = []string{csr.Subject.CommonName} | ||
546 | } | ||
547 | return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey) | ||
548 | } | ||
549 | |||
550 | // LeafCert issues a leaf certificate. | ||
551 | func (ca *CAServer) LeafCert(name, keyType string, notBefore, notAfter time.Time) *tls.Certificate { | ||
552 | if ca.url == "" { | ||
553 | panic("LeafCert called before Start") | ||
554 | } | ||
555 | |||
556 | ca.mu.Lock() | ||
557 | defer ca.mu.Unlock() | ||
558 | var pk crypto.Signer | ||
559 | switch keyType { | ||
560 | case "RSA": | ||
561 | var err error | ||
562 | pk, err = rsa.GenerateKey(rand.Reader, 1024) | ||
563 | if err != nil { | ||
564 | ca.t.Fatal(err) | ||
565 | } | ||
566 | case "ECDSA": | ||
567 | var err error | ||
568 | pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
569 | if err != nil { | ||
570 | ca.t.Fatal(err) | ||
571 | } | ||
572 | default: | ||
573 | panic("LeafCert: unknown key type") | ||
574 | } | ||
575 | ca.certCount++ // next leaf cert serial number | ||
576 | leaf := &x509.Certificate{ | ||
577 | SerialNumber: big.NewInt(int64(ca.certCount)), | ||
578 | Subject: pkix.Name{Organization: []string{"Test Acme Co"}}, | ||
579 | NotBefore: notBefore, | ||
580 | NotAfter: notAfter, | ||
581 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, | ||
582 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||
583 | DNSNames: []string{name}, | ||
584 | BasicConstraintsValid: true, | ||
585 | } | ||
586 | der, err := x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, pk.Public(), ca.rootKey) | ||
587 | if err != nil { | ||
588 | ca.t.Fatal(err) | ||
589 | } | ||
590 | return &tls.Certificate{ | ||
591 | Certificate: [][]byte{der}, | ||
592 | PrivateKey: pk, | ||
593 | } | ||
594 | } | ||
595 | |||
596 | func (ca *CAServer) validateChallenge(authz *authorization, typ string) { | ||
597 | var err error | ||
598 | switch typ { | ||
599 | case "tls-alpn-01": | ||
600 | err = ca.verifyALPNChallenge(authz) | ||
601 | case "http-01": | ||
602 | err = ca.verifyHTTPChallenge(authz) | ||
603 | case "dns-01": | ||
604 | err = ca.verifyDNSChallenge(authz) | ||
605 | default: | ||
606 | panic(fmt.Sprintf("validation of %q is not implemented", typ)) | ||
607 | } | ||
608 | ca.mu.Lock() | ||
609 | defer ca.mu.Unlock() | ||
610 | if err != nil { | ||
611 | authz.Status = "invalid" | ||
612 | } else { | ||
613 | authz.Status = "valid" | ||
614 | ca.validAuthz[authz.domain] = authz | ||
615 | } | ||
616 | ca.t.Logf("validated %q for %q, err: %v", typ, authz.domain, err) | ||
617 | ca.t.Logf("authz %d is now %s", authz.id, authz.Status) | ||
618 | |||
619 | ca.updatePendingOrders() | ||
620 | } | ||
621 | |||
622 | func (ca *CAServer) updatePendingOrders() { | ||
623 | // Update all pending orders. | ||
624 | // An order becomes "ready" if all authorizations are "valid". | ||
625 | // An order becomes "invalid" if any authorization is "invalid". | ||
626 | // Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6 | ||
627 | for i, o := range ca.orders { | ||
628 | if o.Status != acme.StatusPending { | ||
629 | continue | ||
630 | } | ||
631 | |||
632 | countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i) | ||
633 | if countInvalid > 0 { | ||
634 | o.Status = acme.StatusInvalid | ||
635 | ca.t.Logf("order %d is now invalid", i) | ||
636 | continue | ||
637 | } | ||
638 | if countValid == len(o.AuthzURLs) { | ||
639 | o.Status = acme.StatusReady | ||
640 | o.FinalizeURL = ca.serverURL("/new-cert/%d", i) | ||
641 | ca.t.Logf("order %d is now ready", i) | ||
642 | } | ||
643 | } | ||
644 | } | ||
645 | |||
646 | func (ca *CAServer) validateAuthzURLs(urls []string, orderNum int) (countValid, countInvalid int) { | ||
647 | for _, zurl := range urls { | ||
648 | z, err := ca.storedAuthz(path.Base(zurl)) | ||
649 | if err != nil { | ||
650 | ca.t.Logf("no authz %q for order %d", zurl, orderNum) | ||
651 | continue | ||
652 | } | ||
653 | if z.Status == acme.StatusInvalid { | ||
654 | countInvalid++ | ||
655 | } | ||
656 | if z.Status == acme.StatusValid { | ||
657 | countValid++ | ||
658 | } | ||
659 | } | ||
660 | return countValid, countInvalid | ||
661 | } | ||
662 | |||
663 | func (ca *CAServer) verifyALPNChallenge(a *authorization) error { | ||
664 | const acmeALPNProto = "acme-tls/1" | ||
665 | |||
666 | addr, haveAddr := ca.addr(a.domain) | ||
667 | getCert, haveGetCert := ca.getCert(a.domain) | ||
668 | if !haveAddr && !haveGetCert { | ||
669 | return fmt.Errorf("no resolution information for %q", a.domain) | ||
670 | } | ||
671 | if haveAddr && haveGetCert { | ||
672 | return fmt.Errorf("overlapping resolution information for %q", a.domain) | ||
673 | } | ||
674 | |||
675 | var crt *x509.Certificate | ||
676 | switch { | ||
677 | case haveAddr: | ||
678 | conn, err := tls.Dial("tcp", addr, &tls.Config{ | ||
679 | ServerName: a.domain, | ||
680 | InsecureSkipVerify: true, | ||
681 | NextProtos: []string{acmeALPNProto}, | ||
682 | MinVersion: tls.VersionTLS12, | ||
683 | }) | ||
684 | if err != nil { | ||
685 | return err | ||
686 | } | ||
687 | if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto { | ||
688 | return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto) | ||
689 | } | ||
690 | if n := len(conn.ConnectionState().PeerCertificates); n != 1 { | ||
691 | return fmt.Errorf("len(PeerCertificates) = %d; want 1", n) | ||
692 | } | ||
693 | crt = conn.ConnectionState().PeerCertificates[0] | ||
694 | case haveGetCert: | ||
695 | hello := &tls.ClientHelloInfo{ | ||
696 | ServerName: a.domain, | ||
697 | // TODO: support selecting ECDSA. | ||
698 | CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}, | ||
699 | SupportedProtos: []string{acme.ALPNProto}, | ||
700 | SupportedVersions: []uint16{tls.VersionTLS12}, | ||
701 | } | ||
702 | c, err := getCert(hello) | ||
703 | if err != nil { | ||
704 | return err | ||
705 | } | ||
706 | crt, err = x509.ParseCertificate(c.Certificate[0]) | ||
707 | if err != nil { | ||
708 | return err | ||
709 | } | ||
710 | } | ||
711 | |||
712 | if err := crt.VerifyHostname(a.domain); err != nil { | ||
713 | return fmt.Errorf("verifyALPNChallenge: VerifyHostname: %v", err) | ||
714 | } | ||
715 | // See RFC 8737, Section 6.1. | ||
716 | oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} | ||
717 | for _, x := range crt.Extensions { | ||
718 | if x.Id.Equal(oid) { | ||
719 | // TODO: check the token. | ||
720 | return nil | ||
721 | } | ||
722 | } | ||
723 | return fmt.Errorf("verifyTokenCert: no id-pe-acmeIdentifier extension found") | ||
724 | } | ||
725 | |||
726 | func (ca *CAServer) verifyDNSChallenge(a *authorization) error { | ||
727 | ca.mu.Lock() | ||
728 | defer ca.mu.Unlock() | ||
729 | |||
730 | if _, ok := ca.dnsResponses[a.domain]; !ok { | ||
731 | return fmt.Errorf("verifyDNSChallenge: no DNS response registered for domain") | ||
732 | } | ||
733 | |||
734 | return nil | ||
735 | } | ||
736 | |||
737 | func (ca *CAServer) verifyHTTPChallenge(a *authorization) error { | ||
738 | addr, haveAddr := ca.addr(a.domain) | ||
739 | handler, haveHandler := ca.getHandler(a.domain) | ||
740 | if !haveAddr && !haveHandler { | ||
741 | return fmt.Errorf("no resolution information for %q", a.domain) | ||
742 | } | ||
743 | if haveAddr && haveHandler { | ||
744 | return fmt.Errorf("overlapping resolution information for %q", a.domain) | ||
745 | } | ||
746 | |||
747 | token := challengeToken(a.domain, "http-01", a.id) | ||
748 | path := "/.well-known/acme-challenge/" + token | ||
749 | |||
750 | var body string | ||
751 | switch { | ||
752 | case haveAddr: | ||
753 | t := &http.Transport{ | ||
754 | DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) { | ||
755 | return (&net.Dialer{}).DialContext(ctx, network, addr) | ||
756 | }, | ||
757 | } | ||
758 | req, err := http.NewRequest("GET", "http://"+a.domain+path, nil) | ||
759 | if err != nil { | ||
760 | return err | ||
761 | } | ||
762 | res, err := t.RoundTrip(req) | ||
763 | if err != nil { | ||
764 | return err | ||
765 | } | ||
766 | if res.StatusCode != http.StatusOK { | ||
767 | return fmt.Errorf("http token: w.Code = %d; want %d", res.StatusCode, http.StatusOK) | ||
768 | } | ||
769 | b, err := io.ReadAll(res.Body) | ||
770 | if err != nil { | ||
771 | return err | ||
772 | } | ||
773 | body = string(b) | ||
774 | case haveHandler: | ||
775 | r := httptest.NewRequest("GET", path, nil) | ||
776 | r.Host = a.domain | ||
777 | w := httptest.NewRecorder() | ||
778 | handler.ServeHTTP(w, r) | ||
779 | if w.Code != http.StatusOK { | ||
780 | return fmt.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK) | ||
781 | } | ||
782 | body = w.Body.String() | ||
783 | } | ||
784 | |||
785 | if !strings.HasPrefix(body, token) { | ||
786 | return fmt.Errorf("http token value = %q; want 'token-http-01.' prefix", body) | ||
787 | } | ||
788 | return nil | ||
789 | } | ||
790 | |||
791 | func decodePayload(v interface{}, r io.Reader) error { | ||
792 | var req struct{ Payload string } | ||
793 | if err := json.NewDecoder(r).Decode(&req); err != nil { | ||
794 | return err | ||
795 | } | ||
796 | payload, err := base64.RawURLEncoding.DecodeString(req.Payload) | ||
797 | if err != nil { | ||
798 | return err | ||
799 | } | ||
800 | return json.Unmarshal(payload, v) | ||
801 | } | ||
802 | |||
803 | func challengeToken(domain, challType string, authzID int) string { | ||
804 | return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID) | ||
805 | } | ||
806 | |||
807 | func unique(a []string) []string { | ||
808 | seen := make(map[string]bool) | ||
809 | var res []string | ||
810 | for _, s := range a { | ||
811 | if s != "" && !seen[s] { | ||
812 | seen[s] = true | ||
813 | res = append(res, s) | ||
814 | } | ||
815 | } | ||
816 | return res | ||
817 | } | ||
diff --git a/crypto/acme/autocert/fork/listener.go b/crypto/acme/autocert/fork/listener.go new file mode 100644 index 0000000..21fd6dd --- /dev/null +++ b/crypto/acme/autocert/fork/listener.go | |||
@@ -0,0 +1,155 @@ | |||
1 | // Copyright 2017 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "crypto/tls" | ||
9 | "log" | ||
10 | "net" | ||
11 | "os" | ||
12 | "path/filepath" | ||
13 | "runtime" | ||
14 | "time" | ||
15 | ) | ||
16 | |||
17 | // NewListener returns a net.Listener that listens on the standard TLS | ||
18 | // port (443) on all interfaces and returns *tls.Conn connections with | ||
19 | // LetsEncrypt certificates for the provided domain or domains. | ||
20 | // | ||
21 | // It enables one-line HTTPS servers: | ||
22 | // | ||
23 | // log.Fatal(http.Serve(autocert.NewListener("example.com"), handler)) | ||
24 | // | ||
25 | // NewListener is a convenience function for a common configuration. | ||
26 | // More complex or custom configurations can use the autocert.Manager | ||
27 | // type instead. | ||
28 | // | ||
29 | // Use of this function implies acceptance of the LetsEncrypt Terms of | ||
30 | // Service. If domains is not empty, the provided domains are passed | ||
31 | // to HostWhitelist. If domains is empty, the listener will do | ||
32 | // LetsEncrypt challenges for any requested domain, which is not | ||
33 | // recommended. | ||
34 | // | ||
35 | // Certificates are cached in a "golang-autocert" directory under an | ||
36 | // operating system-specific cache or temp directory. This may not | ||
37 | // be suitable for servers spanning multiple machines. | ||
38 | // | ||
39 | // The returned listener uses a *tls.Config that enables HTTP/2, and | ||
40 | // should only be used with servers that support HTTP/2. | ||
41 | // | ||
42 | // The returned Listener also enables TCP keep-alives on the accepted | ||
43 | // connections. The returned *tls.Conn are returned before their TLS | ||
44 | // handshake has completed. | ||
45 | func NewListener(domains ...string) net.Listener { | ||
46 | m := &Manager{ | ||
47 | Prompt: AcceptTOS, | ||
48 | } | ||
49 | if len(domains) > 0 { | ||
50 | m.HostPolicy = HostWhitelist(domains...) | ||
51 | } | ||
52 | dir := cacheDir() | ||
53 | if err := os.MkdirAll(dir, 0700); err != nil { | ||
54 | log.Printf("warning: autocert.NewListener not using a cache: %v", err) | ||
55 | } else { | ||
56 | m.Cache = DirCache(dir) | ||
57 | } | ||
58 | return m.Listener() | ||
59 | } | ||
60 | |||
61 | // Listener listens on the standard TLS port (443) on all interfaces | ||
62 | // and returns a net.Listener returning *tls.Conn connections. | ||
63 | // | ||
64 | // The returned listener uses a *tls.Config that enables HTTP/2, and | ||
65 | // should only be used with servers that support HTTP/2. | ||
66 | // | ||
67 | // The returned Listener also enables TCP keep-alives on the accepted | ||
68 | // connections. The returned *tls.Conn are returned before their TLS | ||
69 | // handshake has completed. | ||
70 | // | ||
71 | // Unlike NewListener, it is the caller's responsibility to initialize | ||
72 | // the Manager m's Prompt, Cache, HostPolicy, and other desired options. | ||
73 | func (m *Manager) Listener() net.Listener { | ||
74 | ln := &listener{ | ||
75 | conf: m.TLSConfig(), | ||
76 | } | ||
77 | ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443") | ||
78 | return ln | ||
79 | } | ||
80 | |||
81 | type listener struct { | ||
82 | conf *tls.Config | ||
83 | |||
84 | tcpListener net.Listener | ||
85 | tcpListenErr error | ||
86 | } | ||
87 | |||
88 | func (ln *listener) Accept() (net.Conn, error) { | ||
89 | if ln.tcpListenErr != nil { | ||
90 | return nil, ln.tcpListenErr | ||
91 | } | ||
92 | conn, err := ln.tcpListener.Accept() | ||
93 | if err != nil { | ||
94 | return nil, err | ||
95 | } | ||
96 | tcpConn := conn.(*net.TCPConn) | ||
97 | |||
98 | // Because Listener is a convenience function, help out with | ||
99 | // this too. This is not possible for the caller to set once | ||
100 | // we return a *tcp.Conn wrapping an inaccessible net.Conn. | ||
101 | // If callers don't want this, they can do things the manual | ||
102 | // way and tweak as needed. But this is what net/http does | ||
103 | // itself, so copy that. If net/http changes, we can change | ||
104 | // here too. | ||
105 | tcpConn.SetKeepAlive(true) | ||
106 | tcpConn.SetKeepAlivePeriod(3 * time.Minute) | ||
107 | |||
108 | return tls.Server(tcpConn, ln.conf), nil | ||
109 | } | ||
110 | |||
111 | func (ln *listener) Addr() net.Addr { | ||
112 | if ln.tcpListener != nil { | ||
113 | return ln.tcpListener.Addr() | ||
114 | } | ||
115 | // net.Listen failed. Return something non-nil in case callers | ||
116 | // call Addr before Accept: | ||
117 | return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443} | ||
118 | } | ||
119 | |||
120 | func (ln *listener) Close() error { | ||
121 | if ln.tcpListenErr != nil { | ||
122 | return ln.tcpListenErr | ||
123 | } | ||
124 | return ln.tcpListener.Close() | ||
125 | } | ||
126 | |||
127 | func homeDir() string { | ||
128 | if runtime.GOOS == "windows" { | ||
129 | return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") | ||
130 | } | ||
131 | if h := os.Getenv("HOME"); h != "" { | ||
132 | return h | ||
133 | } | ||
134 | return "/" | ||
135 | } | ||
136 | |||
137 | func cacheDir() string { | ||
138 | const base = "golang-autocert" | ||
139 | switch runtime.GOOS { | ||
140 | case "darwin": | ||
141 | return filepath.Join(homeDir(), "Library", "Caches", base) | ||
142 | case "windows": | ||
143 | for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} { | ||
144 | if v := os.Getenv(ev); v != "" { | ||
145 | return filepath.Join(v, base) | ||
146 | } | ||
147 | } | ||
148 | // Worst case: | ||
149 | return filepath.Join(homeDir(), base) | ||
150 | } | ||
151 | if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" { | ||
152 | return filepath.Join(xdg, base) | ||
153 | } | ||
154 | return filepath.Join(homeDir(), ".cache", base) | ||
155 | } | ||
diff --git a/crypto/acme/autocert/fork/renewal.go b/crypto/acme/autocert/fork/renewal.go new file mode 100644 index 0000000..789e20a --- /dev/null +++ b/crypto/acme/autocert/fork/renewal.go | |||
@@ -0,0 +1,156 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "context" | ||
9 | "crypto" | ||
10 | "sync" | ||
11 | "time" | ||
12 | ) | ||
13 | |||
14 | // renewJitter is the maximum deviation from Manager.RenewBefore. | ||
15 | const renewJitter = time.Hour | ||
16 | |||
17 | // domainRenewal tracks the state used by the periodic timers | ||
18 | // renewing a single domain's cert. | ||
19 | type domainRenewal struct { | ||
20 | m *Manager | ||
21 | ck certKey | ||
22 | key crypto.Signer | ||
23 | |||
24 | timerMu sync.Mutex | ||
25 | timer *time.Timer | ||
26 | timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running | ||
27 | } | ||
28 | |||
29 | // start starts a cert renewal timer at the time | ||
30 | // defined by the certificate expiration time exp. | ||
31 | // | ||
32 | // If the timer is already started, calling start is a noop. | ||
33 | func (dr *domainRenewal) start(exp time.Time) { | ||
34 | dr.timerMu.Lock() | ||
35 | defer dr.timerMu.Unlock() | ||
36 | if dr.timer != nil { | ||
37 | return | ||
38 | } | ||
39 | dr.timer = time.AfterFunc(dr.next(exp), dr.renew) | ||
40 | } | ||
41 | |||
42 | // stop stops the cert renewal timer and waits for any in-flight calls to renew | ||
43 | // to complete. If the timer is already stopped, calling stop is a noop. | ||
44 | func (dr *domainRenewal) stop() { | ||
45 | dr.timerMu.Lock() | ||
46 | defer dr.timerMu.Unlock() | ||
47 | for { | ||
48 | if dr.timer == nil { | ||
49 | return | ||
50 | } | ||
51 | if dr.timer.Stop() { | ||
52 | dr.timer = nil | ||
53 | return | ||
54 | } else { | ||
55 | // dr.timer fired, and we acquired dr.timerMu before the renew callback did. | ||
56 | // (We know this because otherwise the renew callback would have reset dr.timer!) | ||
57 | timerClose := make(chan struct{}) | ||
58 | dr.timerClose = timerClose | ||
59 | dr.timerMu.Unlock() | ||
60 | <-timerClose | ||
61 | dr.timerMu.Lock() | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | // renew is called periodically by a timer. | ||
67 | // The first renew call is kicked off by dr.start. | ||
68 | func (dr *domainRenewal) renew() { | ||
69 | dr.timerMu.Lock() | ||
70 | defer dr.timerMu.Unlock() | ||
71 | if dr.timerClose != nil { | ||
72 | close(dr.timerClose) | ||
73 | dr.timer, dr.timerClose = nil, nil | ||
74 | return | ||
75 | } | ||
76 | |||
77 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | ||
78 | defer cancel() | ||
79 | // TODO: rotate dr.key at some point? | ||
80 | next, err := dr.do(ctx) | ||
81 | if err != nil { | ||
82 | next = renewJitter / 2 | ||
83 | next += time.Duration(pseudoRand.int63n(int64(next))) | ||
84 | } | ||
85 | testDidRenewLoop(next, err) | ||
86 | dr.timer = time.AfterFunc(next, dr.renew) | ||
87 | } | ||
88 | |||
89 | // updateState locks and replaces the relevant Manager.state item with the given | ||
90 | // state. It additionally updates dr.key with the given state's key. | ||
91 | func (dr *domainRenewal) updateState(state *certState) { | ||
92 | dr.m.stateMu.Lock() | ||
93 | defer dr.m.stateMu.Unlock() | ||
94 | dr.key = state.key | ||
95 | dr.m.state[dr.ck] = state | ||
96 | } | ||
97 | |||
98 | // do is similar to Manager.createCert but it doesn't lock a Manager.state item. | ||
99 | // Instead, it requests a new certificate independently and, upon success, | ||
100 | // replaces dr.m.state item with a new one and updates cache for the given domain. | ||
101 | // | ||
102 | // It may lock and update the Manager.state if the expiration date of the currently | ||
103 | // cached cert is far enough in the future. | ||
104 | // | ||
105 | // The returned value is a time interval after which the renewal should occur again. | ||
106 | func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) { | ||
107 | // a race is likely unavoidable in a distributed environment | ||
108 | // but we try nonetheless | ||
109 | if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil { | ||
110 | next := dr.next(tlscert.Leaf.NotAfter) | ||
111 | if next > dr.m.renewBefore()+renewJitter { | ||
112 | signer, ok := tlscert.PrivateKey.(crypto.Signer) | ||
113 | if ok { | ||
114 | state := &certState{ | ||
115 | key: signer, | ||
116 | cert: tlscert.Certificate, | ||
117 | leaf: tlscert.Leaf, | ||
118 | } | ||
119 | dr.updateState(state) | ||
120 | return next, nil | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck) | ||
126 | if err != nil { | ||
127 | return 0, err | ||
128 | } | ||
129 | state := &certState{ | ||
130 | key: dr.key, | ||
131 | cert: der, | ||
132 | leaf: leaf, | ||
133 | } | ||
134 | tlscert, err := state.tlscert() | ||
135 | if err != nil { | ||
136 | return 0, err | ||
137 | } | ||
138 | if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil { | ||
139 | return 0, err | ||
140 | } | ||
141 | dr.updateState(state) | ||
142 | return dr.next(leaf.NotAfter), nil | ||
143 | } | ||
144 | |||
145 | func (dr *domainRenewal) next(expiry time.Time) time.Duration { | ||
146 | d := expiry.Sub(dr.m.now()) - dr.m.renewBefore() | ||
147 | // add a bit of randomness to renew deadline | ||
148 | n := pseudoRand.int63n(int64(renewJitter)) | ||
149 | d -= time.Duration(n) | ||
150 | if d < 0 { | ||
151 | return 0 | ||
152 | } | ||
153 | return d | ||
154 | } | ||
155 | |||
156 | var testDidRenewLoop = func(next time.Duration, err error) {} | ||
diff --git a/crypto/acme/autocert/fork/renewal_test.go b/crypto/acme/autocert/fork/renewal_test.go new file mode 100644 index 0000000..25689ba --- /dev/null +++ b/crypto/acme/autocert/fork/renewal_test.go | |||
@@ -0,0 +1,269 @@ | |||
1 | // Copyright 2016 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package fork | ||
6 | |||
7 | import ( | ||
8 | "context" | ||
9 | "crypto" | ||
10 | "crypto/ecdsa" | ||
11 | "testing" | ||
12 | "time" | ||
13 | |||
14 | "code.crute.us/mcrute/golib/crypto/acme/autocert/fork/internal/acmetest" | ||
15 | "golang.org/x/crypto/acme" | ||
16 | ) | ||
17 | |||
18 | func TestRenewalNext(t *testing.T) { | ||
19 | now := time.Now() | ||
20 | man := &Manager{ | ||
21 | RenewBefore: 7 * 24 * time.Hour, | ||
22 | nowFunc: func() time.Time { return now }, | ||
23 | } | ||
24 | defer man.stopRenew() | ||
25 | tt := []struct { | ||
26 | expiry time.Time | ||
27 | min, max time.Duration | ||
28 | }{ | ||
29 | {now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour}, | ||
30 | {now.Add(time.Hour), 0, 1}, | ||
31 | {now, 0, 1}, | ||
32 | {now.Add(-time.Hour), 0, 1}, | ||
33 | } | ||
34 | |||
35 | dr := &domainRenewal{m: man} | ||
36 | for i, test := range tt { | ||
37 | next := dr.next(test.expiry) | ||
38 | if next < test.min || test.max < next { | ||
39 | t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max) | ||
40 | } | ||
41 | } | ||
42 | } | ||
43 | |||
44 | func TestRenewFromCache(t *testing.T) { | ||
45 | man := testManager(t) | ||
46 | man.RenewBefore = 24 * time.Hour | ||
47 | |||
48 | ca := acmetest.NewCAServer(t).Start() | ||
49 | ca.ResolveGetCertificate(exampleDomain, man.GetCertificate) | ||
50 | |||
51 | man.Client = &acme.Client{ | ||
52 | DirectoryURL: ca.URL(), | ||
53 | } | ||
54 | |||
55 | // cache an almost expired cert | ||
56 | now := time.Now() | ||
57 | c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute)) | ||
58 | if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { | ||
59 | t.Fatal(err) | ||
60 | } | ||
61 | |||
62 | // verify the renewal happened | ||
63 | defer func() { | ||
64 | // Stop the timers that read and execute testDidRenewLoop before restoring it. | ||
65 | // Otherwise the timer callback may race with the deferred write. | ||
66 | man.stopRenew() | ||
67 | testDidRenewLoop = func(next time.Duration, err error) {} | ||
68 | }() | ||
69 | renewed := make(chan bool, 1) | ||
70 | testDidRenewLoop = func(next time.Duration, err error) { | ||
71 | defer func() { | ||
72 | select { | ||
73 | case renewed <- true: | ||
74 | default: | ||
75 | // The renewal timer uses a random backoff. If the first renewal fails for | ||
76 | // some reason, we could end up with multiple calls here before the test | ||
77 | // stops the timer. | ||
78 | } | ||
79 | }() | ||
80 | |||
81 | if err != nil { | ||
82 | t.Errorf("testDidRenewLoop: %v", err) | ||
83 | } | ||
84 | // Next should be about 90 days: | ||
85 | // CaServer creates 90days expiry + account for man.RenewBefore. | ||
86 | // Previous expiration was within 1 min. | ||
87 | future := 88 * 24 * time.Hour | ||
88 | if next < future { | ||
89 | t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future) | ||
90 | } | ||
91 | |||
92 | // ensure the new cert is cached | ||
93 | after := time.Now().Add(future) | ||
94 | tlscert, err := man.cacheGet(context.Background(), exampleCertKey) | ||
95 | if err != nil { | ||
96 | t.Errorf("man.cacheGet: %v", err) | ||
97 | return | ||
98 | } | ||
99 | if !tlscert.Leaf.NotAfter.After(after) { | ||
100 | t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after) | ||
101 | } | ||
102 | |||
103 | // verify the old cert is also replaced in memory | ||
104 | man.stateMu.Lock() | ||
105 | defer man.stateMu.Unlock() | ||
106 | s := man.state[exampleCertKey] | ||
107 | if s == nil { | ||
108 | t.Errorf("m.state[%q] is nil", exampleCertKey) | ||
109 | return | ||
110 | } | ||
111 | tlscert, err = s.tlscert() | ||
112 | if err != nil { | ||
113 | t.Errorf("s.tlscert: %v", err) | ||
114 | return | ||
115 | } | ||
116 | if !tlscert.Leaf.NotAfter.After(after) { | ||
117 | t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after) | ||
118 | } | ||
119 | } | ||
120 | |||
121 | // trigger renew | ||
122 | hello := clientHelloInfo(exampleDomain, algECDSA) | ||
123 | if _, err := man.GetCertificate(hello); err != nil { | ||
124 | t.Fatal(err) | ||
125 | } | ||
126 | <-renewed | ||
127 | } | ||
128 | |||
129 | func TestRenewFromCacheAlreadyRenewed(t *testing.T) { | ||
130 | ca := acmetest.NewCAServer(t).Start() | ||
131 | man := testManager(t) | ||
132 | man.RenewBefore = 24 * time.Hour | ||
133 | man.Client = &acme.Client{ | ||
134 | DirectoryURL: "invalid", | ||
135 | } | ||
136 | |||
137 | // cache a recently renewed cert with a different private key | ||
138 | now := time.Now() | ||
139 | newCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Hour*24*90)) | ||
140 | if err := man.cachePut(context.Background(), exampleCertKey, newCert); err != nil { | ||
141 | t.Fatal(err) | ||
142 | } | ||
143 | newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now) | ||
144 | if err != nil { | ||
145 | t.Fatal(err) | ||
146 | } | ||
147 | |||
148 | // set internal state to an almost expired cert | ||
149 | oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute)) | ||
150 | if err != nil { | ||
151 | t.Fatal(err) | ||
152 | } | ||
153 | oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now) | ||
154 | if err != nil { | ||
155 | t.Fatal(err) | ||
156 | } | ||
157 | man.stateMu.Lock() | ||
158 | if man.state == nil { | ||
159 | man.state = make(map[certKey]*certState) | ||
160 | } | ||
161 | s := &certState{ | ||
162 | key: oldCert.PrivateKey.(crypto.Signer), | ||
163 | cert: oldCert.Certificate, | ||
164 | leaf: oldLeaf, | ||
165 | } | ||
166 | man.state[exampleCertKey] = s | ||
167 | man.stateMu.Unlock() | ||
168 | |||
169 | // verify the renewal accepted the newer cached cert | ||
170 | defer func() { | ||
171 | // Stop the timers that read and execute testDidRenewLoop before restoring it. | ||
172 | // Otherwise the timer callback may race with the deferred write. | ||
173 | man.stopRenew() | ||
174 | testDidRenewLoop = func(next time.Duration, err error) {} | ||
175 | }() | ||
176 | renewed := make(chan bool, 1) | ||
177 | testDidRenewLoop = func(next time.Duration, err error) { | ||
178 | defer func() { | ||
179 | select { | ||
180 | case renewed <- true: | ||
181 | default: | ||
182 | // The renewal timer uses a random backoff. If the first renewal fails for | ||
183 | // some reason, we could end up with multiple calls here before the test | ||
184 | // stops the timer. | ||
185 | } | ||
186 | }() | ||
187 | |||
188 | if err != nil { | ||
189 | t.Errorf("testDidRenewLoop: %v", err) | ||
190 | } | ||
191 | // Next should be about 90 days | ||
192 | // Previous expiration was within 1 min. | ||
193 | future := 88 * 24 * time.Hour | ||
194 | if next < future { | ||
195 | t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future) | ||
196 | } | ||
197 | |||
198 | // ensure the cached cert was not modified | ||
199 | tlscert, err := man.cacheGet(context.Background(), exampleCertKey) | ||
200 | if err != nil { | ||
201 | t.Errorf("man.cacheGet: %v", err) | ||
202 | return | ||
203 | } | ||
204 | if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) { | ||
205 | t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) | ||
206 | } | ||
207 | |||
208 | // verify the old cert is also replaced in memory | ||
209 | man.stateMu.Lock() | ||
210 | defer man.stateMu.Unlock() | ||
211 | s := man.state[exampleCertKey] | ||
212 | if s == nil { | ||
213 | t.Errorf("m.state[%q] is nil", exampleCertKey) | ||
214 | return | ||
215 | } | ||
216 | stateKey := s.key.Public().(*ecdsa.PublicKey) | ||
217 | if !stateKey.Equal(newLeaf.PublicKey) { | ||
218 | t.Error("state key was not updated from cache") | ||
219 | return | ||
220 | } | ||
221 | tlscert, err = s.tlscert() | ||
222 | if err != nil { | ||
223 | t.Errorf("s.tlscert: %v", err) | ||
224 | return | ||
225 | } | ||
226 | if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) { | ||
227 | t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // assert the expiring cert is returned from state | ||
232 | hello := clientHelloInfo(exampleDomain, algECDSA) | ||
233 | tlscert, err := man.GetCertificate(hello) | ||
234 | if err != nil { | ||
235 | t.Fatal(err) | ||
236 | } | ||
237 | if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) { | ||
238 | t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter) | ||
239 | } | ||
240 | |||
241 | // trigger renew | ||
242 | man.startRenew(exampleCertKey, s) | ||
243 | <-renewed | ||
244 | func() { | ||
245 | man.renewalMu.Lock() | ||
246 | defer man.renewalMu.Unlock() | ||
247 | |||
248 | // verify the private key is replaced in the renewal state | ||
249 | r := man.renewal[exampleCertKey] | ||
250 | if r == nil { | ||
251 | t.Errorf("m.renewal[%q] is nil", exampleCertKey) | ||
252 | return | ||
253 | } | ||
254 | renewalKey := r.key.Public().(*ecdsa.PublicKey) | ||
255 | if !renewalKey.Equal(newLeaf.PublicKey) { | ||
256 | t.Error("renewal private key was not updated from cache") | ||
257 | } | ||
258 | }() | ||
259 | |||
260 | // assert the new cert is returned from state after renew | ||
261 | hello = clientHelloInfo(exampleDomain, algECDSA) | ||
262 | tlscert, err = man.GetCertificate(hello) | ||
263 | if err != nil { | ||
264 | t.Fatal(err) | ||
265 | } | ||
266 | if !newLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) { | ||
267 | t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) | ||
268 | } | ||
269 | } | ||
diff --git a/crypto/ocsp/client.go b/crypto/ocsp/client.go new file mode 100644 index 0000000..4f5ff97 --- /dev/null +++ b/crypto/ocsp/client.go | |||
@@ -0,0 +1,125 @@ | |||
1 | package ocsp | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "context" | ||
6 | "crypto/tls" | ||
7 | "crypto/x509" | ||
8 | "fmt" | ||
9 | "io" | ||
10 | "net/http" | ||
11 | |||
12 | "golang.org/x/crypto/ocsp" | ||
13 | ) | ||
14 | |||
15 | const ( | ||
16 | ocspBodySizeLimit = 1024 * 1024 | ||
17 | ocspMimeType = "application/ocsp-request" | ||
18 | ) | ||
19 | |||
20 | type Client struct { | ||
21 | // HTTPClient optionally specifies an HTTP client to use | ||
22 | // instead of http.DefaultClient. | ||
23 | HTTPClient *http.Client | ||
24 | } | ||
25 | |||
26 | func (c *Client) httpClient() *http.Client { | ||
27 | if c.HTTPClient != nil { | ||
28 | return c.HTTPClient | ||
29 | } | ||
30 | return http.DefaultClient | ||
31 | } | ||
32 | |||
33 | func (c *Client) Fetch(ctx context.Context, chain *tls.Certificate) ([]byte, *ocsp.Response, error) { | ||
34 | var certs []*x509.Certificate | ||
35 | for _, c := range chain.Certificate { | ||
36 | cert, err := x509.ParseCertificate(c) | ||
37 | if err != nil { | ||
38 | return nil, nil, fmt.Errorf("ocsp/client: error parsing certificate chain: %w", err) | ||
39 | } | ||
40 | certs = append(certs, cert) | ||
41 | } | ||
42 | |||
43 | if len(certs) == 0 { | ||
44 | return nil, nil, fmt.Errorf("ocsp/client: no certificates found in bundle") | ||
45 | } | ||
46 | |||
47 | // We expect the certificate slice to be ordered downwards the chain. | ||
48 | // SRV CRT -> CA. We need to pull the leaf and issuer certs out of it, | ||
49 | // which should always be the first two certificates. If there's no | ||
50 | // OCSP server listed in the leaf cert, there's nothing to do. And if | ||
51 | // we have only one certificate so far, we need to get the issuer cert. | ||
52 | leaf := certs[0] | ||
53 | if len(leaf.OCSPServer) == 0 { | ||
54 | return nil, nil, fmt.Errorf("ocsp/client: no ocsp server specified in certificate") | ||
55 | } | ||
56 | |||
57 | if len(certs) == 1 { | ||
58 | if len(leaf.IssuingCertificateURL) == 0 { | ||
59 | return nil, nil, fmt.Errorf("ocsp/client: no URL to issuing certificate") | ||
60 | } | ||
61 | |||
62 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, leaf.IssuingCertificateURL[0], nil) | ||
63 | if err != nil { | ||
64 | return nil, nil, fmt.Errorf("ocsp/client: building certificate request: %w", err) | ||
65 | } | ||
66 | |||
67 | resp, err := c.httpClient().Do(req) | ||
68 | if err != nil { | ||
69 | return nil, nil, fmt.Errorf("ocsp/client: getting issuer certificate: %w", err) | ||
70 | } | ||
71 | defer resp.Body.Close() | ||
72 | |||
73 | issuerBytes, err := io.ReadAll(io.LimitReader(resp.Body, ocspBodySizeLimit)) | ||
74 | if err != nil { | ||
75 | return nil, nil, fmt.Errorf("ocsp/client: reading issuer certificate: %w", err) | ||
76 | } | ||
77 | |||
78 | issuer, err := x509.ParseCertificate(issuerBytes) | ||
79 | if err != nil { | ||
80 | return nil, nil, fmt.Errorf("ocsp/client: parsing issuer certificate: %w", err) | ||
81 | } | ||
82 | |||
83 | certs = append(certs, issuer) | ||
84 | } | ||
85 | |||
86 | issuer := certs[1] | ||
87 | |||
88 | req, err := ocsp.CreateRequest(leaf, issuer, nil) | ||
89 | if err != nil { | ||
90 | return nil, nil, fmt.Errorf("ocsp/client: creating ocsp request: %w", err) | ||
91 | } | ||
92 | |||
93 | // httpRes, err := http.Post(, ocspMimeType, ) | ||
94 | httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, leaf.OCSPServer[0], bytes.NewReader(req)) | ||
95 | if err != nil { | ||
96 | return nil, nil, fmt.Errorf("ocsp/client: building ocsp http request: %w", err) | ||
97 | } | ||
98 | |||
99 | httpRes, err := c.httpClient().Do(httpReq) | ||
100 | if err != nil { | ||
101 | return nil, nil, fmt.Errorf("ocsp/client: making ocsp request: %w", err) | ||
102 | } | ||
103 | defer httpRes.Body.Close() | ||
104 | |||
105 | rawRes, err := io.ReadAll(io.LimitReader(httpRes.Body, ocspBodySizeLimit)) | ||
106 | if err != nil { | ||
107 | return nil, nil, fmt.Errorf("ocsp/client: reading ocsp response: %w", err) | ||
108 | } | ||
109 | |||
110 | res, err := ocsp.ParseResponse(rawRes, issuer) | ||
111 | if err != nil { | ||
112 | return nil, nil, fmt.Errorf("ocsp/client: parsing ocsp response: %w", err) | ||
113 | } | ||
114 | |||
115 | if res.Status != ocsp.Good { | ||
116 | return nil, nil, fmt.Errorf("ocsp/client: invalid: ocsp response was not of Good status") | ||
117 | } | ||
118 | |||
119 | // This is invalid, the response expires after the certificate | ||
120 | if res.NextUpdate.After(leaf.NotAfter) { | ||
121 | return nil, nil, fmt.Errorf("ocsp/client: invalid: ocsp response valid after certificate expiration") | ||
122 | } | ||
123 | |||
124 | return rawRes, res, nil | ||
125 | } | ||