main
go 187 lines 4.96 KB
Raw
1 package acme
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 "crypto/x509"
9 "crypto/x509/pkix"
10 "encoding/pem"
11 "math/big"
12 "os"
13 "path/filepath"
14 "strings"
15 "testing"
16 "time"
17 )
18
19 func TestEnsureCertificateGeneratesLocalDevelopmentMaterial(t *testing.T) {
20 t.Parallel()
21
22 keyDir := t.TempDir()
23 manager, err := NewManager(Config{
24 BaseDomain: "localhost",
25 KeyDir: keyDir,
26 })
27 if err != nil {
28 t.Fatalf("NewManager() error = %v", err)
29 }
30
31 certPEM, keyPEM, err := manager.EnsureTLSMaterial(context.Background())
32 if err != nil {
33 t.Fatalf("EnsureTLSMaterial() error = %v", err)
34 }
35 if len(certPEM) == 0 || len(keyPEM) == 0 {
36 t.Fatalf("EnsureTLSMaterial() returned empty PEM material")
37 }
38
39 certFile, _, err := manager.TLSFiles()
40 if err != nil {
41 t.Fatalf("TLSFiles() error = %v", err)
42 }
43 covered, err := certCoversDomains(certFile, []string{"localhost"})
44 if err != nil {
45 t.Fatalf("certCoversDomains() error = %v", err)
46 }
47 if !covered {
48 t.Fatal("certCoversDomains() = false, want true")
49 }
50 }
51
52 func TestEnsureTLSMaterialUsesManualCertificateWithoutDNSProvider(t *testing.T) {
53 t.Parallel()
54
55 keyDir := t.TempDir()
56 if err := writeManualRelayCertificate(t, keyDir, "portal.example.com"); err != nil {
57 t.Fatalf("writeManualRelayCertificate() error = %v", err)
58 }
59
60 manager, err := NewManager(Config{
61 BaseDomain: "portal.example.com",
62 KeyDir: keyDir,
63 })
64 if err != nil {
65 t.Fatalf("NewManager() error = %v", err)
66 }
67
68 certPEM, keyPEM, err := manager.EnsureTLSMaterial(context.Background())
69 if err != nil {
70 t.Fatalf("EnsureTLSMaterial() error = %v", err)
71 }
72 if len(certPEM) == 0 || len(keyPEM) == 0 {
73 t.Fatalf("EnsureTLSMaterial() returned empty PEM material")
74 }
75 }
76
77 func TestEnsureTLSMaterialRequiresManualCertificateWhenProviderUnset(t *testing.T) {
78 t.Parallel()
79
80 manager, err := NewManager(Config{
81 BaseDomain: "portal.example.com",
82 KeyDir: t.TempDir(),
83 })
84 if err != nil {
85 t.Fatalf("NewManager() error = %v", err)
86 }
87
88 _, _, err = manager.EnsureTLSMaterial(context.Background())
89 if err == nil {
90 t.Fatal("EnsureTLSMaterial() error = nil, want missing manual certificate error")
91 }
92 if got := err.Error(); got == "" || !containsAll(got, "manual certificate mode requires", "fullchain.pem", "privatekey.pem") {
93 t.Fatalf("EnsureTLSMaterial() error = %q, want manual certificate guidance", got)
94 }
95 }
96
97 func TestNewManagerRejectsENSGaslessWithoutDNSProvider(t *testing.T) {
98 t.Parallel()
99
100 _, err := NewManager(Config{
101 BaseDomain: "portal.example.com",
102 KeyDir: t.TempDir(),
103 ENSGaslessEnabled: true,
104 ENSGaslessAddress: "0x1234567890123456789012345678901234567890",
105 })
106 if err == nil {
107 t.Fatal("NewManager() error = nil, want ENS gasless provider error")
108 }
109 if got := err.Error(); got != "ens gasless automation requires ACME_DNS_PROVIDER" {
110 t.Fatalf("NewManager() error = %q, want ENS gasless provider guidance", got)
111 }
112 }
113
114 func TestEnsureTLSMaterialUsesManualCertificateWithDNSProvider(t *testing.T) {
115 t.Parallel()
116
117 keyDir := t.TempDir()
118 if err := writeManualRelayCertificate(t, keyDir, "portal.example.com"); err != nil {
119 t.Fatalf("writeManualRelayCertificate() error = %v", err)
120 }
121
122 manager, err := NewManager(Config{
123 BaseDomain: "portal.example.com",
124 KeyDir: keyDir,
125 DNSProvider: TypeRoute53,
126 })
127 if err != nil {
128 t.Fatalf("NewManager() error = %v", err)
129 }
130
131 certPEM, keyPEM, err := manager.EnsureTLSMaterial(context.Background())
132 if err != nil {
133 t.Fatalf("EnsureTLSMaterial() error = %v", err)
134 }
135 if len(certPEM) == 0 || len(keyPEM) == 0 {
136 t.Fatalf("EnsureTLSMaterial() returned empty PEM material")
137 }
138 }
139
140 func writeManualRelayCertificate(t *testing.T, keyDir, baseDomain string) error {
141 t.Helper()
142
143 privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
144 if err != nil {
145 return err
146 }
147
148 now := time.Now().UTC()
149 template := &x509.Certificate{
150 SerialNumber: big.NewInt(now.UnixNano()),
151 Subject: pkix.Name{
152 CommonName: baseDomain,
153 },
154 NotBefore: now.Add(-time.Hour),
155 NotAfter: now.Add(90 * 24 * time.Hour),
156 DNSNames: []string{baseDomain, "*." + baseDomain},
157 BasicConstraintsValid: true,
158 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
159 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
160 }
161
162 der, err := x509.CreateCertificate(rand.Reader, template, template, privateKey.Public(), privateKey)
163 if err != nil {
164 return err
165 }
166 keyDER, err := x509.MarshalECPrivateKey(privateKey)
167 if err != nil {
168 return err
169 }
170
171 certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
172 keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
173
174 if err := os.WriteFile(filepath.Join(keyDir, fullChainFileName), certPEM, 0o644); err != nil {
175 return err
176 }
177 return os.WriteFile(filepath.Join(keyDir, keyFileName), keyPEM, 0o600)
178 }
179
180 func containsAll(text string, parts ...string) bool {
181 for _, part := range parts {
182 if !strings.Contains(text, part) {
183 return false
184 }
185 }
186 return true
187 }