// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package autocert

import (
	"bytes"
	"context"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"fmt"
	"io"
	"math/big"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"golang.org/x/crypto/acme"
	"golang.org/x/crypto/acme/autocert/internal/acmetest"
)

var (
	exampleDomain     = "example.org"
	exampleCertKey    = certKey{domain: exampleDomain}
	exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
)

type memCache struct {
	t       *testing.T
	mu      sync.Mutex
	keyData map[string][]byte
}

func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	v, ok := m.keyData[key]
	if !ok {
		return nil, ErrCacheMiss
	}
	return v, nil
}

// filenameSafe returns whether all characters in s are printable ASCII
// and safe to use in a filename on most filesystems.
func filenameSafe(s string) bool {
	for _, c := range s {
		if c < 0x20 || c > 0x7E {
			return false
		}
		switch c {
		case '\\', '/', ':', '*', '?', '"', '<', '>', '|':
			return false
		}
	}
	return true
}

func (m *memCache) Put(ctx context.Context, key string, data []byte) error {
	if !filenameSafe(key) {
		m.t.Errorf("invalid characters in cache key %q", key)
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	m.keyData[key] = data
	return nil
}

func (m *memCache) Delete(ctx context.Context, key string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	delete(m.keyData, key)
	return nil
}

func newMemCache(t *testing.T) *memCache {
	return &memCache{
		t:       t,
		keyData: make(map[string][]byte),
	}
}

func (m *memCache) numCerts() int {
	m.mu.Lock()
	defer m.mu.Unlock()

	res := 0
	for key := range m.keyData {
		if strings.HasSuffix(key, "+token") ||
			strings.HasSuffix(key, "+key") ||
			strings.HasSuffix(key, "+http-01") {
			continue
		}
		res++
	}
	return res
}

func dummyCert(pub interface{}, san ...string) ([]byte, error) {
	return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
}

func dateDummyCert(pub interface{}, start, end time.Time, san ...string) ([]byte, error) {
	// use EC key to run faster on 386
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}
	t := &x509.Certificate{
		SerialNumber:          randomSerial(),
		NotBefore:             start,
		NotAfter:              end,
		BasicConstraintsValid: true,
		KeyUsage:              x509.KeyUsageKeyEncipherment,
		DNSNames:              san,
	}
	if pub == nil {
		pub = &key.PublicKey
	}
	return x509.CreateCertificate(rand.Reader, t, t, pub, key)
}

func randomSerial() *big.Int {
	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 32))
	if err != nil {
		panic(err)
	}
	return serial
}

type algorithmSupport int

const (
	algRSA algorithmSupport = iota
	algECDSA
)

func clientHelloInfo(sni string, alg algorithmSupport) *tls.ClientHelloInfo {
	hello := &tls.ClientHelloInfo{
		ServerName:   sni,
		CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
	}
	if alg == algECDSA {
		hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
	}
	return hello
}

func testManager(t *testing.T) *Manager {
	man := &Manager{
		Prompt: AcceptTOS,
		Cache:  newMemCache(t),
	}
	t.Cleanup(man.stopRenew)
	return man
}

func TestGetCertificate(t *testing.T) {
	tests := []struct {
		name        string
		hello       *tls.ClientHelloInfo
		domain      string
		expectError string
		prepare     func(t *testing.T, man *Manager, s *acmetest.CAServer)
		verify      func(t *testing.T, man *Manager, leaf *x509.Certificate)
		disableALPN bool
		disableHTTP bool
	}{
		{
			name:        "ALPN",
			hello:       clientHelloInfo("example.org", algECDSA),
			domain:      "example.org",
			disableHTTP: true,
		},
		{
			name:        "HTTP",
			hello:       clientHelloInfo("example.org", algECDSA),
			domain:      "example.org",
			disableALPN: true,
		},
		{
			name:   "nilPrompt",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				man.Prompt = nil
			},
			expectError: "Manager.Prompt not set",
		},
		{
			name:   "trailingDot",
			hello:  clientHelloInfo("example.org.", algECDSA),
			domain: "example.org",
		},
		{
			name:   "unicodeIDN",
			hello:  clientHelloInfo("éé.com", algECDSA),
			domain: "xn--9caa.com",
		},
		{
			name:   "unicodeIDN/mixedCase",
			hello:  clientHelloInfo("éÉ.com", algECDSA),
			domain: "xn--9caa.com",
		},
		{
			name:   "upperCase",
			hello:  clientHelloInfo("EXAMPLE.ORG", algECDSA),
			domain: "example.org",
		},
		{
			name:   "goodCache",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make a valid cert and cache it.
				c := s.Start().LeafCert(exampleDomain, "ECDSA",
					// Use a time before the Let's Encrypt revocation cutoff to also test
					// that non-Let's Encrypt certificates are not renewed.
					time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
				)
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
			// Break the server to check that the cache is used.
			disableALPN: true, disableHTTP: true,
		},
		{
			name:   "expiredCache",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make an expired cert and cache it.
				c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now().Add(-10*time.Minute), time.Now().Add(-5*time.Minute))
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
		},
		{
			name:   "forceRSA",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				man.ForceRSA = true
			},
			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
					t.Errorf("leaf.PublicKey is %T; want *ecdsa.PublicKey", leaf.PublicKey)
				}
			},
		},
		{
			name:   "goodLetsEncrypt",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make a valid certificate issued after the TLS-ALPN-01
				// revocation window and cache it.
				s.IssuerName(pkix.Name{Country: []string{"US"},
					Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
				c := s.Start().LeafCert(exampleDomain, "ECDSA",
					time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC),
					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
				)
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
			// Break the server to check that the cache is used.
			disableALPN: true, disableHTTP: true,
		},
		{
			name:   "revokedLetsEncrypt",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make a certificate issued during the TLS-ALPN-01
				// revocation window and cache it.
				s.IssuerName(pkix.Name{Country: []string{"US"},
					Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
				c := s.Start().LeafCert(exampleDomain, "ECDSA",
					time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
				)
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
				if leaf.NotBefore.Before(time.Now().Add(-10 * time.Minute)) {
					t.Error("certificate was not reissued")
				}
			},
		},
		{
			// TestGetCertificate/tokenCache tests the fallback of token
			// certificate fetches to cache when Manager.certTokens misses.
			name:   "tokenCacheALPN",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make a separate manager with a shared cache, simulating
				// separate nodes that serve requests for the same domain.
				man2 := testManager(t)
				man2.Cache = man.Cache
				// Redirect the verification request to man2, although the
				// client request will hit man, testing that they can complete a
				// verification by communicating through the cache.
				s.ResolveGetCertificate("example.org", man2.GetCertificate)
			},
			// Drop the default verification paths.
			disableALPN: true,
		},
		{
			name:   "tokenCacheHTTP",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				man2 := testManager(t)
				man2.Cache = man.Cache
				s.ResolveHandler("example.org", man2.HTTPHandler(nil))
			},
			disableHTTP: true,
		},
		{
			name:   "ecdsa",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
					t.Error("an ECDSA client was served a non-ECDSA certificate")
				}
			},
		},
		{
			name:   "rsa",
			hello:  clientHelloInfo("example.org", algRSA),
			domain: "example.org",
			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
				if _, ok := leaf.PublicKey.(*rsa.PublicKey); !ok {
					t.Error("an RSA client was served a non-RSA certificate")
				}
			},
		},
		{
			name:   "wrongCacheKeyType",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				// Make an RSA cert and cache it without suffix.
				c := s.Start().LeafCert(exampleDomain, "RSA", time.Now(), time.Now().Add(90*24*time.Hour))
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
				// The RSA cached cert should be silently ignored and replaced.
				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
					t.Error("an ECDSA client was served a non-ECDSA certificate")
				}
				if numCerts := man.Cache.(*memCache).numCerts(); numCerts != 1 {
					t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
				}
			},
		},
		{
			name:   "almostExpiredCache",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				man.RenewBefore = 24 * time.Hour
				// Cache an almost expired cert.
				c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now(), time.Now().Add(10*time.Minute))
				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
					t.Fatalf("man.cachePut: %v", err)
				}
			},
		},
		{
			name:   "provideExternalAuth",
			hello:  clientHelloInfo("example.org", algECDSA),
			domain: "example.org",
			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
				s.ExternalAccountRequired()

				man.ExternalAccountBinding = &acme.ExternalAccountBinding{
					KID: "test-key",
					Key: make([]byte, 32),
				}
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			man := testManager(t)
			s := acmetest.NewCAServer(t)
			if !tt.disableALPN {
				s.ResolveGetCertificate(tt.domain, man.GetCertificate)
			}
			if !tt.disableHTTP {
				s.ResolveHandler(tt.domain, man.HTTPHandler(nil))
			}

			if tt.prepare != nil {
				tt.prepare(t, man, s)
			}

			s.Start()

			man.Client = &acme.Client{DirectoryURL: s.URL()}

			var tlscert *tls.Certificate
			var err error
			done := make(chan struct{})
			go func() {
				tlscert, err = man.GetCertificate(tt.hello)
				close(done)
			}()
			select {
			case <-time.After(time.Minute):
				t.Fatal("man.GetCertificate took too long to return")
			case <-done:
			}
			if tt.expectError != "" {
				if err == nil {
					t.Fatal("expected error, got certificate")
				}
				if !strings.Contains(err.Error(), tt.expectError) {
					t.Errorf("got %q, expected %q", err, tt.expectError)
				}
				return
			}
			if err != nil {
				t.Fatalf("man.GetCertificate: %v", err)
			}

			leaf, err := x509.ParseCertificate(tlscert.Certificate[0])
			if err != nil {
				t.Fatal(err)
			}
			opts := x509.VerifyOptions{
				DNSName:       tt.domain,
				Intermediates: x509.NewCertPool(),
				Roots:         s.Roots(),
			}
			for _, cert := range tlscert.Certificate[1:] {
				c, err := x509.ParseCertificate(cert)
				if err != nil {
					t.Fatal(err)
				}
				opts.Intermediates.AddCert(c)
			}
			if _, err := leaf.Verify(opts); err != nil {
				t.Error(err)
			}

			if san := leaf.DNSNames[0]; san != tt.domain {
				t.Errorf("got SAN %q, expected %q", san, tt.domain)
			}

			if tt.verify != nil {
				tt.verify(t, man, leaf)
			}
		})
	}
}

func TestGetCertificate_failedAttempt(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusBadRequest)
	}))
	defer ts.Close()

	d := createCertRetryAfter
	f := testDidRemoveState
	defer func() {
		createCertRetryAfter = d
		testDidRemoveState = f
	}()
	createCertRetryAfter = 0
	done := make(chan struct{})
	testDidRemoveState = func(ck certKey) {
		if ck != exampleCertKey {
			t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey)
		}
		close(done)
	}

	man := &Manager{
		Prompt: AcceptTOS,
		Client: &acme.Client{
			DirectoryURL: ts.URL,
		},
	}
	defer man.stopRenew()
	hello := clientHelloInfo(exampleDomain, algECDSA)
	if _, err := man.GetCertificate(hello); err == nil {
		t.Error("GetCertificate: err is nil")
	}
	select {
	case <-time.After(5 * time.Second):
		t.Errorf("took too long to remove the %q state", exampleCertKey)
	case <-done:
		man.stateMu.Lock()
		defer man.stateMu.Unlock()
		if v, exist := man.state[exampleCertKey]; exist {
			t.Errorf("state exists for %v: %+v", exampleCertKey, v)
		}
	}
}

func TestRevokeFailedAuthz(t *testing.T) {
	ca := acmetest.NewCAServer(t)
	// Make the authz unfulfillable on the client side, so it will be left
	// pending at the end of the verification attempt.
	ca.ChallengeTypes("fake-01", "fake-02")
	ca.Start()

	m := testManager(t)
	m.Client = &acme.Client{DirectoryURL: ca.URL()}

	_, err := m.GetCertificate(clientHelloInfo("example.org", algECDSA))
	if err == nil {
		t.Fatal("expected GetCertificate to fail")
	}

	start := time.Now()
	for time.Since(start) < 3*time.Second {
		authz, err := m.Client.GetAuthorization(context.Background(), ca.URL()+"/authz/0")
		if err != nil {
			t.Fatal(err)
		}
		if authz.Status == acme.StatusDeactivated {
			return
		}
		time.Sleep(50 * time.Millisecond)
	}
	t.Error("revocations took too long")

}

func TestHTTPHandlerDefaultFallback(t *testing.T) {
	tt := []struct {
		method, url  string
		wantCode     int
		wantLocation string
	}{
		{"GET", "http://example.org", 302, "https://example.org/"},
		{"GET", "http://example.org/foo", 302, "https://example.org/foo"},
		{"GET", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
		{"GET", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
		{"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
		{"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
		{"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
		{"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
		{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"},
		{"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"},
		{"HEAD", "http://example.org", 302, "https://example.org/"},
		{"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
		{"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
		{"HEAD", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
		{"HEAD", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
		{"POST", "http://example.org", 400, ""},
		{"PUT", "http://example.org", 400, ""},
		{"GET", "http://example.org/.well-known/acme-challenge/x", 404, ""},
	}
	var m Manager
	h := m.HTTPHandler(nil)
	for i, test := range tt {
		r := httptest.NewRequest(test.method, test.url, nil)
		w := httptest.NewRecorder()
		h.ServeHTTP(w, r)
		if w.Code != test.wantCode {
			t.Errorf("%d: w.Code = %d; want %d", i, w.Code, test.wantCode)
			t.Errorf("%d: body: %s", i, w.Body.Bytes())
		}
		if v := w.Header().Get("Location"); v != test.wantLocation {
			t.Errorf("%d: Location = %q; want %q", i, v, test.wantLocation)
		}
	}
}

func TestAccountKeyCache(t *testing.T) {
	m := Manager{Cache: newMemCache(t)}
	ctx := context.Background()
	k1, err := m.accountKey(ctx)
	if err != nil {
		t.Fatal(err)
	}
	k2, err := m.accountKey(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(k1, k2) {
		t.Errorf("account keys don't match: k1 = %#v; k2 = %#v", k1, k2)
	}
}

func TestCache(t *testing.T) {
	ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
	if err != nil {
		t.Fatal(err)
	}
	ecdsaCert := &tls.Certificate{
		Certificate: [][]byte{cert},
		PrivateKey:  ecdsaKey,
	}

	rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
	if err != nil {
		t.Fatal(err)
	}
	cert, err = dummyCert(rsaKey.Public(), exampleDomain)
	if err != nil {
		t.Fatal(err)
	}
	rsaCert := &tls.Certificate{
		Certificate: [][]byte{cert},
		PrivateKey:  rsaKey,
	}

	man := &Manager{Cache: newMemCache(t)}
	defer man.stopRenew()
	ctx := context.Background()

	if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil {
		t.Fatalf("man.cachePut: %v", err)
	}
	if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil {
		t.Fatalf("man.cachePut: %v", err)
	}

	res, err := man.cacheGet(ctx, exampleCertKey)
	if err != nil {
		t.Fatalf("man.cacheGet: %v", err)
	}
	if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) {
		t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert)
	}

	res, err = man.cacheGet(ctx, exampleCertKeyRSA)
	if err != nil {
		t.Fatalf("man.cacheGet: %v", err)
	}
	if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
		t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
	}
}

func TestHostWhitelist(t *testing.T) {
	policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "éÉ.com")
	tt := []struct {
		host  string
		allow bool
	}{
		{"example.com", true},
		{"example.org", true},
		{"xn--9caa.com", true}, // éé.com
		{"one.example.com", false},
		{"two.example.org", false},
		{"three.example.net", false},
		{"dummy", false},
	}
	for i, test := range tt {
		err := policy(nil, test.host)
		if err != nil && test.allow {
			t.Errorf("%d: policy(%q): %v; want nil", i, test.host, err)
		}
		if err == nil && !test.allow {
			t.Errorf("%d: policy(%q): nil; want an error", i, test.host)
		}
	}
}

func TestValidCert(t *testing.T) {
	key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	key3, err := rsa.GenerateKey(rand.Reader, 512)
	if err != nil {
		t.Fatal(err)
	}
	cert1, err := dummyCert(key1.Public(), "example.org")
	if err != nil {
		t.Fatal(err)
	}
	cert2, err := dummyCert(key2.Public(), "example.org")
	if err != nil {
		t.Fatal(err)
	}
	cert3, err := dummyCert(key3.Public(), "example.org")
	if err != nil {
		t.Fatal(err)
	}
	now := time.Now()
	early, err := dateDummyCert(key1.Public(), now.Add(time.Hour), now.Add(2*time.Hour), "example.org")
	if err != nil {
		t.Fatal(err)
	}
	expired, err := dateDummyCert(key1.Public(), now.Add(-2*time.Hour), now.Add(-time.Hour), "example.org")
	if err != nil {
		t.Fatal(err)
	}

	tt := []struct {
		ck   certKey
		key  crypto.Signer
		cert [][]byte
		ok   bool
	}{
		{certKey{domain: "example.org"}, key1, [][]byte{cert1}, true},
		{certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true},
		{certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true},
		{certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false},
		{certKey{domain: "example.org"}, key1, [][]byte{{1}}, false},
		{certKey{domain: "example.org"}, key1, [][]byte{cert2}, false},
		{certKey{domain: "example.org"}, key2, [][]byte{cert1}, false},
		{certKey{domain: "example.org"}, key1, [][]byte{cert3}, false},
		{certKey{domain: "example.org"}, key3, [][]byte{cert1}, false},
		{certKey{domain: "example.net"}, key1, [][]byte{cert1}, false},
		{certKey{domain: "example.org"}, key1, [][]byte{early}, false},
		{certKey{domain: "example.org"}, key1, [][]byte{expired}, false},
		{certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false},
		{certKey{domain: "example.org"}, key3, [][]byte{cert3}, false},
	}
	for i, test := range tt {
		leaf, err := validCert(test.ck, test.cert, test.key, now)
		if err != nil && test.ok {
			t.Errorf("%d: err = %v", i, err)
		}
		if err == nil && !test.ok {
			t.Errorf("%d: err is nil", i)
		}
		if err == nil && test.ok && leaf == nil {
			t.Errorf("%d: leaf is nil", i)
		}
	}
}

type cacheGetFunc func(ctx context.Context, key string) ([]byte, error)

func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) {
	return f(ctx, key)
}

func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error {
	return fmt.Errorf("unsupported Put of %q = %q", key, data)
}

func (f cacheGetFunc) Delete(ctx context.Context, key string) error {
	return fmt.Errorf("unsupported Delete of %q", key)
}

func TestManagerGetCertificateBogusSNI(t *testing.T) {
	m := Manager{
		Prompt: AcceptTOS,
		Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) {
			return nil, fmt.Errorf("cache.Get of %s", key)
		}),
	}
	tests := []struct {
		name    string
		wantErr string
	}{
		{"foo.com", "cache.Get of foo.com"},
		{"foo.com.", "cache.Get of foo.com"},
		{`a\b.com`, "acme/autocert: server name contains invalid character"},
		{`a/b.com`, "acme/autocert: server name contains invalid character"},
		{"", "acme/autocert: missing server name"},
		{"foo", "acme/autocert: server name component count invalid"},
		{".foo", "acme/autocert: server name component count invalid"},
		{"foo.", "acme/autocert: server name component count invalid"},
		{"fo.o", "cache.Get of fo.o"},
	}
	for _, tt := range tests {
		_, err := m.GetCertificate(clientHelloInfo(tt.name, algECDSA))
		got := fmt.Sprint(err)
		if got != tt.wantErr {
			t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
		}
	}
}

func TestCertRequest(t *testing.T) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	// An extension from RFC7633. Any will do.
	ext := pkix.Extension{
		Id:    asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1},
		Value: []byte("dummy"),
	}
	b, err := certRequest(key, "example.org", []pkix.Extension{ext})
	if err != nil {
		t.Fatalf("certRequest: %v", err)
	}
	r, err := x509.ParseCertificateRequest(b)
	if err != nil {
		t.Fatalf("ParseCertificateRequest: %v", err)
	}
	var found bool
	for _, v := range r.Extensions {
		if v.Id.Equal(ext.Id) {
			found = true
			break
		}
	}
	if !found {
		t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
	}
}

func TestSupportsECDSA(t *testing.T) {
	tests := []struct {
		CipherSuites     []uint16
		SignatureSchemes []tls.SignatureScheme
		SupportedCurves  []tls.CurveID
		ecdsaOk          bool
	}{
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		}, nil, nil, false},
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		}, nil, nil, true},

		// SignatureSchemes limits, not extends, CipherSuites
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		}, []tls.SignatureScheme{
			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
		}, nil, false},
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		}, []tls.SignatureScheme{
			tls.PKCS1WithSHA256,
		}, nil, false},
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		}, []tls.SignatureScheme{
			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
		}, nil, true},

		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		}, []tls.SignatureScheme{
			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
		}, []tls.CurveID{
			tls.CurveP521,
		}, false},
		{[]uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		}, []tls.SignatureScheme{
			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
		}, []tls.CurveID{
			tls.CurveP256,
			tls.CurveP521,
		}, true},
	}
	for i, tt := range tests {
		result := supportsECDSA(&tls.ClientHelloInfo{
			CipherSuites:     tt.CipherSuites,
			SignatureSchemes: tt.SignatureSchemes,
			SupportedCurves:  tt.SupportedCurves,
		})
		if result != tt.ecdsaOk {
			t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk)
		}
	}
}

func TestEndToEndALPN(t *testing.T) {
	const domain = "example.org"

	// ACME CA server
	ca := acmetest.NewCAServer(t).Start()

	// User HTTPS server.
	m := &Manager{
		Prompt: AcceptTOS,
		Client: &acme.Client{DirectoryURL: ca.URL()},
	}
	us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("OK"))
	}))
	us.TLS = &tls.Config{
		NextProtos: []string{"http/1.1", acme.ALPNProto},
		GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
			cert, err := m.GetCertificate(hello)
			if err != nil {
				t.Errorf("m.GetCertificate: %v", err)
			}
			return cert, err
		},
	}
	us.StartTLS()
	defer us.Close()
	// In TLS-ALPN challenge verification, CA connects to the domain:443 in question.
	// Because the domain won't resolve in tests, we need to tell the CA
	// where to dial to instead.
	ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://"))

	// A client visiting user's HTTPS server.
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:    ca.Roots(),
			ServerName: domain,
		},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Get(us.URL)
	if err != nil {
		t.Fatal(err)
	}
	defer res.Body.Close()
	b, err := io.ReadAll(res.Body)
	if err != nil {
		t.Fatal(err)
	}
	if v := string(b); v != "OK" {
		t.Errorf("user server response: %q; want 'OK'", v)
	}
}

func TestEndToEndHTTP(t *testing.T) {
	const domain = "example.org"

	// ACME CA server.
	ca := acmetest.NewCAServer(t).ChallengeTypes("http-01").Start()

	// User HTTP server for the ACME challenge.
	m := testManager(t)
	m.Client = &acme.Client{DirectoryURL: ca.URL()}
	s := httptest.NewServer(m.HTTPHandler(nil))
	defer s.Close()

	// User HTTPS server.
	ss := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("OK"))
	}))
	ss.TLS = &tls.Config{
		NextProtos: []string{"http/1.1", acme.ALPNProto},
		GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
			cert, err := m.GetCertificate(hello)
			if err != nil {
				t.Errorf("m.GetCertificate: %v", err)
			}
			return cert, err
		},
	}
	ss.StartTLS()
	defer ss.Close()

	// Redirect the CA requests to the HTTP server.
	ca.Resolve(domain, strings.TrimPrefix(s.URL, "http://"))

	// A client visiting user's HTTPS server.
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:    ca.Roots(),
			ServerName: domain,
		},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Get(ss.URL)
	if err != nil {
		t.Fatal(err)
	}
	defer res.Body.Close()
	b, err := io.ReadAll(res.Body)
	if err != nil {
		t.Fatal(err)
	}
	if v := string(b); v != "OK" {
		t.Errorf("user server response: %q; want 'OK'", v)
	}
}
