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.
8package acmetest
9
10import (
11	"crypto"
12	"crypto/ecdsa"
13	"crypto/elliptic"
14	"crypto/rand"
15	"crypto/tls"
16	"crypto/x509"
17	"crypto/x509/pkix"
18	"encoding/base64"
19	"encoding/json"
20	"encoding/pem"
21	"fmt"
22	"io"
23	"log"
24	"math/big"
25	"net/http"
26	"net/http/httptest"
27	"path"
28	"sort"
29	"strconv"
30	"strings"
31	"sync"
32	"time"
33
34	"golang.org/x/crypto/acme"
35)
36
37// CAServer is a simple test server which implements ACME spec bits needed for testing.
38type CAServer struct {
39	URL   string         // server URL after it has been started
40	Roots *x509.CertPool // CA root certificates; initialized in NewCAServer
41
42	rootKey      crypto.Signer
43	rootCert     []byte // DER encoding
44	rootTemplate *x509.Certificate
45
46	server           *httptest.Server
47	challengeTypes   []string // supported challenge types
48	domainsWhitelist []string // only these domains are valid for issuing, unless empty
49
50	mu             sync.Mutex
51	certCount      int                       // number of issued certs
52	domainAddr     map[string]string         // domain name to addr:port resolution
53	authorizations map[string]*authorization // keyed by domain name
54	orders         []*order                  // index is used as order ID
55	errors         []error                   // encountered client errors
56}
57
58// NewCAServer creates a new ACME test server and starts serving requests.
59// The returned CAServer issues certs signed with the CA roots
60// available in the Roots field.
61//
62// The challengeTypes argument defines the supported ACME challenge types
63// sent to a client in a response for a domain authorization.
64// If domainsWhitelist is non-empty, the certs will be issued only for the specified
65// list of domains. Otherwise, any domain name is allowed.
66func NewCAServer(challengeTypes []string, domainsWhitelist []string) *CAServer {
67	var whitelist []string
68	for _, name := range domainsWhitelist {
69		whitelist = append(whitelist, name)
70	}
71	sort.Strings(whitelist)
72	ca := &CAServer{
73		challengeTypes:   challengeTypes,
74		domainsWhitelist: whitelist,
75		domainAddr:       make(map[string]string),
76		authorizations:   make(map[string]*authorization),
77	}
78
79	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
80	if err != nil {
81		panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err))
82	}
83	tmpl := &x509.Certificate{
84		SerialNumber: big.NewInt(1),
85		Subject: pkix.Name{
86			Organization: []string{"Test Acme Co"},
87			CommonName:   "Root CA",
88		},
89		NotBefore:             time.Now(),
90		NotAfter:              time.Now().Add(365 * 24 * time.Hour),
91		KeyUsage:              x509.KeyUsageCertSign,
92		BasicConstraintsValid: true,
93		IsCA:                  true,
94	}
95	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
96	if err != nil {
97		panic(fmt.Sprintf("x509.CreateCertificate: %v", err))
98	}
99	cert, err := x509.ParseCertificate(der)
100	if err != nil {
101		panic(fmt.Sprintf("x509.ParseCertificate: %v", err))
102	}
103	ca.Roots = x509.NewCertPool()
104	ca.Roots.AddCert(cert)
105	ca.rootKey = key
106	ca.rootCert = der
107	ca.rootTemplate = tmpl
108
109	ca.server = httptest.NewServer(http.HandlerFunc(ca.handle))
110	ca.URL = ca.server.URL
111	return ca
112}
113
114// Close shuts down the server and blocks until all outstanding
115// requests on this server have completed.
116func (ca *CAServer) Close() {
117	ca.server.Close()
118}
119
120func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
121	return ca.server.URL + fmt.Sprintf(format, arg...)
122}
123
124func (ca *CAServer) addr(domain string) (string, error) {
125	ca.mu.Lock()
126	defer ca.mu.Unlock()
127	addr, ok := ca.domainAddr[domain]
128	if !ok {
129		return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
130	}
131	return addr, nil
132}
133
134func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
135	s := fmt.Sprintf(format, a...)
136	log.Println(s)
137	http.Error(w, s, code)
138}
139
140// Resolve adds a domain to address resolution for the ca to dial to
141// when validating challenges for the domain authorization.
142func (ca *CAServer) Resolve(domain, addr string) {
143	ca.mu.Lock()
144	defer ca.mu.Unlock()
145	ca.domainAddr[domain] = addr
146}
147
148type discovery struct {
149	NewNonce string `json:"newNonce"`
150	NewReg   string `json:"newAccount"`
151	NewOrder string `json:"newOrder"`
152	NewAuthz string `json:"newAuthz"`
153}
154
155type challenge struct {
156	URI   string `json:"uri"`
157	Type  string `json:"type"`
158	Token string `json:"token"`
159}
160
161type authorization struct {
162	Status     string      `json:"status"`
163	Challenges []challenge `json:"challenges"`
164
165	domain string
166}
167
168type order struct {
169	Status      string   `json:"status"`
170	AuthzURLs   []string `json:"authorizations"`
171	FinalizeURL string   `json:"finalize"`    // CSR submit URL
172	CertURL     string   `json:"certificate"` // already issued cert
173
174	leaf []byte // issued cert in DER format
175}
176
177func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
178	log.Printf("%s %s", r.Method, r.URL)
179	w.Header().Set("Replay-Nonce", "nonce")
180	// TODO: Verify nonce header for all POST requests.
181
182	switch {
183	default:
184		ca.httpErrorf(w, http.StatusBadRequest, "unrecognized r.URL.Path: %s", r.URL.Path)
185
186	// Discovery request.
187	case r.URL.Path == "/":
188		resp := &discovery{
189			NewNonce: ca.serverURL("/new-nonce"),
190			NewReg:   ca.serverURL("/new-reg"),
191			NewOrder: ca.serverURL("/new-order"),
192			NewAuthz: ca.serverURL("/new-authz"),
193		}
194		if err := json.NewEncoder(w).Encode(resp); err != nil {
195			panic(fmt.Sprintf("discovery response: %v", err))
196		}
197
198	// Nonce requests.
199	case r.URL.Path == "/new-nonce":
200		// Nonce values are always set. Nothing else to do.
201		return
202
203	// Client key registration request.
204	case r.URL.Path == "/new-reg":
205		// TODO: Check the user account key against a ca.accountKeys?
206		w.Header().Set("Location", ca.serverURL("/accounts/1"))
207		w.WriteHeader(http.StatusCreated)
208		w.Write([]byte("{}"))
209
210	// New order request.
211	case r.URL.Path == "/new-order":
212		var req struct {
213			Identifiers []struct{ Value string }
214		}
215		if err := decodePayload(&req, r.Body); err != nil {
216			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
217			return
218		}
219		ca.mu.Lock()
220		defer ca.mu.Unlock()
221		o := &order{Status: acme.StatusPending}
222		for _, id := range req.Identifiers {
223			z := ca.authz(id.Value)
224			o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%s", z.domain))
225		}
226		orderID := len(ca.orders)
227		ca.orders = append(ca.orders, o)
228		w.Header().Set("Location", ca.serverURL("/orders/%d", orderID))
229		w.WriteHeader(http.StatusCreated)
230		if err := json.NewEncoder(w).Encode(o); err != nil {
231			panic(err)
232		}
233
234	// Existing order status requests.
235	case strings.HasPrefix(r.URL.Path, "/orders/"):
236		ca.mu.Lock()
237		defer ca.mu.Unlock()
238		o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/orders/"))
239		if err != nil {
240			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
241			return
242		}
243		if err := json.NewEncoder(w).Encode(o); err != nil {
244			panic(err)
245		}
246
247	// Identifier authorization request.
248	case r.URL.Path == "/new-authz":
249		var req struct {
250			Identifier struct{ Value string }
251		}
252		if err := decodePayload(&req, r.Body); err != nil {
253			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
254			return
255		}
256		ca.mu.Lock()
257		defer ca.mu.Unlock()
258		z := ca.authz(req.Identifier.Value)
259		w.Header().Set("Location", ca.serverURL("/authz/%s", z.domain))
260		w.WriteHeader(http.StatusCreated)
261		if err := json.NewEncoder(w).Encode(z); err != nil {
262			panic(fmt.Sprintf("new authz response: %v", err))
263		}
264
265	// Accept tls-alpn-01 challenge type requests.
266	case strings.HasPrefix(r.URL.Path, "/challenge/tls-alpn-01/"):
267		domain := strings.TrimPrefix(r.URL.Path, "/challenge/tls-alpn-01/")
268		ca.mu.Lock()
269		_, exist := ca.authorizations[domain]
270		ca.mu.Unlock()
271		if !exist {
272			ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: no authz for %q", domain)
273			return
274		}
275		go ca.validateChallenge("tls-alpn-01", domain)
276		w.Write([]byte("{}"))
277
278	// Get authorization status requests.
279	case strings.HasPrefix(r.URL.Path, "/authz/"):
280		domain := strings.TrimPrefix(r.URL.Path, "/authz/")
281		ca.mu.Lock()
282		defer ca.mu.Unlock()
283		authz, ok := ca.authorizations[domain]
284		if !ok {
285			ca.httpErrorf(w, http.StatusNotFound, "no authz for %q", domain)
286			return
287		}
288		if err := json.NewEncoder(w).Encode(authz); err != nil {
289			panic(fmt.Sprintf("get authz for %q response: %v", domain, err))
290		}
291
292	// Certificate issuance request.
293	case strings.HasPrefix(r.URL.Path, "/new-cert/"):
294		ca.mu.Lock()
295		defer ca.mu.Unlock()
296		orderID := strings.TrimPrefix(r.URL.Path, "/new-cert/")
297		o, err := ca.storedOrder(orderID)
298		if err != nil {
299			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
300			return
301		}
302		if o.Status != acme.StatusReady {
303			ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
304			return
305		}
306		// Validate CSR request.
307		var req struct {
308			CSR string `json:"csr"`
309		}
310		decodePayload(&req, r.Body)
311		b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
312		csr, err := x509.ParseCertificateRequest(b)
313		if err != nil {
314			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
315			return
316		}
317		names := unique(append(csr.DNSNames, csr.Subject.CommonName))
318		if err := ca.matchWhitelist(names); err != nil {
319			ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
320			return
321		}
322		if err := ca.authorized(names); err != nil {
323			ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
324			return
325		}
326		// Issue the certificate.
327		der, err := ca.leafCert(csr)
328		if err != nil {
329			ca.httpErrorf(w, http.StatusBadRequest, "new-cert response: ca.leafCert: %v", err)
330			return
331		}
332		o.leaf = der
333		o.CertURL = ca.serverURL("/issued-cert/%s", orderID)
334		o.Status = acme.StatusValid
335		if err := json.NewEncoder(w).Encode(o); err != nil {
336			panic(err)
337		}
338
339	// Already issued cert download requests.
340	case strings.HasPrefix(r.URL.Path, "/issued-cert/"):
341		ca.mu.Lock()
342		defer ca.mu.Unlock()
343		o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/issued-cert/"))
344		if err != nil {
345			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
346			return
347		}
348		if o.Status != acme.StatusValid {
349			ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
350			return
351		}
352		w.Header().Set("Content-Type", "application/pem-certificate-chain")
353		pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: o.leaf})
354		pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: ca.rootCert})
355	}
356}
357
358// matchWhitelist reports whether all dnsNames are whitelisted.
359// The whitelist is provided in NewCAServer.
360func (ca *CAServer) matchWhitelist(dnsNames []string) error {
361	if len(ca.domainsWhitelist) == 0 {
362		return nil
363	}
364	var nomatch []string
365	for _, name := range dnsNames {
366		i := sort.SearchStrings(ca.domainsWhitelist, name)
367		if i == len(ca.domainsWhitelist) || ca.domainsWhitelist[i] != name {
368			nomatch = append(nomatch, name)
369		}
370	}
371	if len(nomatch) > 0 {
372		return fmt.Errorf("matchWhitelist: some domains don't match: %q", nomatch)
373	}
374	return nil
375}
376
377// storedOrder retrieves a previously created order at index i.
378// It requires ca.mu to be locked.
379func (ca *CAServer) storedOrder(i string) (*order, error) {
380	idx, err := strconv.Atoi(i)
381	if err != nil {
382		return nil, fmt.Errorf("storedOrder: %v", err)
383	}
384	if idx < 0 {
385		return nil, fmt.Errorf("storedOrder: invalid order index %d", idx)
386	}
387	if idx > len(ca.orders)-1 {
388		return nil, fmt.Errorf("storedOrder: no such order %d", idx)
389	}
390	return ca.orders[idx], nil
391}
392
393// authz returns an existing authorization for the identifier or creates a new one.
394// It requires ca.mu to be locked.
395func (ca *CAServer) authz(identifier string) *authorization {
396	authz, ok := ca.authorizations[identifier]
397	if !ok {
398		authz = &authorization{
399			domain: identifier,
400			Status: acme.StatusPending,
401		}
402		for _, typ := range ca.challengeTypes {
403			authz.Challenges = append(authz.Challenges, challenge{
404				Type:  typ,
405				URI:   ca.serverURL("/challenge/%s/%s", typ, authz.domain),
406				Token: challengeToken(authz.domain, typ),
407			})
408		}
409		ca.authorizations[authz.domain] = authz
410	}
411	return authz
412}
413
414// authorized reports whether all authorizations for dnsNames have been satisfied.
415// It requires ca.mu to be locked.
416func (ca *CAServer) authorized(dnsNames []string) error {
417	var noauthz []string
418	for _, name := range dnsNames {
419		authz, ok := ca.authorizations[name]
420		if !ok || authz.Status != acme.StatusValid {
421			noauthz = append(noauthz, name)
422		}
423	}
424	if len(noauthz) > 0 {
425		return fmt.Errorf("CAServer: no authz for %q", noauthz)
426	}
427	return nil
428}
429
430// leafCert issues a new certificate.
431// It requires ca.mu to be locked.
432func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) {
433	ca.certCount++ // next leaf cert serial number
434	leaf := &x509.Certificate{
435		SerialNumber:          big.NewInt(int64(ca.certCount)),
436		Subject:               pkix.Name{Organization: []string{"Test Acme Co"}},
437		NotBefore:             time.Now(),
438		NotAfter:              time.Now().Add(90 * 24 * time.Hour),
439		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
440		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
441		DNSNames:              csr.DNSNames,
442		BasicConstraintsValid: true,
443	}
444	if len(csr.DNSNames) == 0 {
445		leaf.DNSNames = []string{csr.Subject.CommonName}
446	}
447	return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
448}
449
450// TODO: Only tls-alpn-01 is currently supported: implement http-01 and dns-01.
451func (ca *CAServer) validateChallenge(typ, identifier string) {
452	var err error
453	switch typ {
454	case "tls-alpn-01":
455		err = ca.verifyALPNChallenge(identifier)
456	default:
457		panic(fmt.Sprintf("validation of %q is not implemented", typ))
458	}
459	ca.mu.Lock()
460	defer ca.mu.Unlock()
461	authz := ca.authorizations[identifier]
462	if err != nil {
463		authz.Status = "invalid"
464	} else {
465		authz.Status = "valid"
466	}
467	log.Printf("validated %q for %q; authz status is now: %s", typ, identifier, authz.Status)
468	// Update all pending orders.
469	// An order becomes "ready" if all authorizations are "valid".
470	// An order becomes "invalid" if any authorization is "invalid".
471	// Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6
472OrdersLoop:
473	for i, o := range ca.orders {
474		if o.Status != acme.StatusPending {
475			continue
476		}
477		var countValid int
478		for _, zurl := range o.AuthzURLs {
479			z, ok := ca.authorizations[path.Base(zurl)]
480			if !ok {
481				log.Printf("no authz %q for order %d", zurl, i)
482				continue OrdersLoop
483			}
484			if z.Status == acme.StatusInvalid {
485				o.Status = acme.StatusInvalid
486				log.Printf("order %d is now invalid", i)
487				continue OrdersLoop
488			}
489			if z.Status == acme.StatusValid {
490				countValid++
491			}
492		}
493		if countValid == len(o.AuthzURLs) {
494			o.Status = acme.StatusReady
495			o.FinalizeURL = ca.serverURL("/new-cert/%d", i)
496			log.Printf("order %d is now ready", i)
497		}
498	}
499}
500
501func (ca *CAServer) verifyALPNChallenge(domain string) error {
502	const acmeALPNProto = "acme-tls/1"
503
504	addr, err := ca.addr(domain)
505	if err != nil {
506		return err
507	}
508	conn, err := tls.Dial("tcp", addr, &tls.Config{
509		ServerName:         domain,
510		InsecureSkipVerify: true,
511		NextProtos:         []string{acmeALPNProto},
512	})
513	if err != nil {
514		return err
515	}
516	if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
517		return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
518	}
519	if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
520		return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
521	}
522	// TODO: verify conn.ConnectionState().PeerCertificates[0]
523	return nil
524}
525
526func decodePayload(v interface{}, r io.Reader) error {
527	var req struct{ Payload string }
528	if err := json.NewDecoder(r).Decode(&req); err != nil {
529		return err
530	}
531	payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
532	if err != nil {
533		return err
534	}
535	return json.Unmarshal(payload, v)
536}
537
538func challengeToken(domain, challType string) string {
539	return fmt.Sprintf("token-%s-%s", domain, challType)
540}
541
542func unique(a []string) []string {
543	seen := make(map[string]bool)
544	var res []string
545	for _, s := range a {
546		if s != "" && !seen[s] {
547			seen[s] = true
548			res = append(res, s)
549		}
550	}
551	return res
552}
553