1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package main
27
28 import (
29 "context"
30 "crypto"
31 "crypto/ecdsa"
32 "crypto/elliptic"
33 "crypto/rand"
34 "crypto/tls"
35 "crypto/x509"
36 "encoding/pem"
37 "errors"
38 "flag"
39 "fmt"
40 "log"
41 "net"
42 "net/http"
43 "os"
44 "os/exec"
45 "strings"
46 "time"
47
48 "golang.org/x/crypto/acme"
49 )
50
51 var (
52
53
54
55
56
57 directory = flag.String("d", "", "ACME directory URL.")
58 reginfo = flag.String("r", "", "ACME account registration info.")
59 flow = flag.String("f", "", `Flow to run: "order" or "preauthz" (RFC8555).`)
60 chaltyp = flag.String("t", "", "Challenge type: tls-alpn-01, http-01 or dns-01.")
61 addr = flag.String("a", "", "Local server address for tls-alpn-01 and http-01.")
62 dnsscript = flag.String("s", "", "Script to run for provisioning dns-01 challenges.")
63 domain = flag.String("domain", "", "Space separate domain identifiers.")
64 ipaddr = flag.String("ip", "", "Space separate IP address identifiers.")
65 )
66
67 func main() {
68 flag.Usage = func() {
69 fmt.Fprintln(flag.CommandLine.Output(), `
70 The prober program runs against an actual ACME CA implementation.
71 It spins up an HTTP server to fulfill authorization challenges
72 or execute a DNS script to provision a response to dns-01 challenge.
73
74 For http-01 and tls-alpn-01 challenge types this requires the ACME CA
75 to be able to reach the HTTP server.
76
77 A usage example:
78
79 go run prober.go \
80 -d https://acme-staging-v02.api.letsencrypt.org/directory \
81 -f order \
82 -t http-01 \
83 -a :8080 \
84 -domain some.example.org
85
86 The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080
87 in order for the test to be able to fulfill http-01 challenge.
88 To test tls-alpn-01 challenge, 443 port would need to be tunneled
89 to 0.0.0.0:8080.
90 When running with dns-01 challenge type, use -s argument instead of -a.
91 `)
92 flag.PrintDefaults()
93 }
94 flag.Parse()
95
96 identifiers := acme.DomainIDs(strings.Fields(*domain)...)
97 identifiers = append(identifiers, acme.IPIDs(strings.Fields(*ipaddr)...)...)
98 if len(identifiers) == 0 {
99 log.Fatal("at least one domain or IP addr identifier is required")
100 }
101
102
103 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
104 defer cancel()
105
106
107 akey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
108 if err != nil {
109 log.Fatal(err)
110 }
111 cl := &acme.Client{Key: akey, DirectoryURL: *directory}
112 a := &acme.Account{Contact: strings.Fields(*reginfo)}
113 if _, err := cl.Register(ctx, a, acme.AcceptTOS); err != nil {
114 log.Fatalf("Register: %v", err)
115 }
116
117
118 p := &prober{
119 client: cl,
120 chalType: *chaltyp,
121 localAddr: *addr,
122 dnsScript: *dnsscript,
123 }
124 switch *flow {
125 case "order":
126 p.runOrder(ctx, identifiers)
127 case "preauthz":
128 p.runPreauthz(ctx, identifiers)
129 default:
130 log.Fatalf("unknown flow: %q", *flow)
131 }
132 if len(p.errors) > 0 {
133 os.Exit(1)
134 }
135 }
136
137 type prober struct {
138 client *acme.Client
139 chalType string
140 localAddr string
141 dnsScript string
142
143 errors []error
144 }
145
146 func (p *prober) errorf(format string, a ...interface{}) {
147 err := fmt.Errorf(format, a...)
148 log.Print(err)
149 p.errors = append(p.errors, err)
150 }
151
152 func (p *prober) runOrder(ctx context.Context, identifiers []acme.AuthzID) {
153
154
155
156
157 o, err := p.client.AuthorizeOrder(ctx, identifiers)
158 if err != nil {
159 log.Fatalf("AuthorizeOrder: %v", err)
160 }
161
162 var zurls []string
163 for _, u := range o.AuthzURLs {
164 z, err := p.client.GetAuthorization(ctx, u)
165 if err != nil {
166 log.Fatalf("GetAuthorization(%q): %v", u, err)
167 }
168 log.Printf("%+v", z)
169 if z.Status != acme.StatusPending {
170 log.Printf("authz status is %q; skipping", z.Status)
171 continue
172 }
173 if err := p.fulfill(ctx, z); err != nil {
174 log.Fatalf("fulfill(%s): %v", z.URI, err)
175 }
176 zurls = append(zurls, z.URI)
177 log.Printf("authorized for %+v", z.Identifier)
178 }
179
180 log.Print("all challenges are done")
181 if _, err := p.client.WaitOrder(ctx, o.URI); err != nil {
182 log.Fatalf("WaitOrder(%q): %v", o.URI, err)
183 }
184 csr, certkey := newCSR(identifiers)
185 der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
186 if err != nil {
187 log.Fatalf("CreateOrderCert: %v", err)
188 }
189 log.Printf("cert URL: %s", curl)
190 if err := checkCert(der, identifiers); err != nil {
191 p.errorf("invalid cert: %v", err)
192 }
193
194
195 for _, v := range zurls {
196 if err := p.client.RevokeAuthorization(ctx, v); err != nil {
197 p.errorf("RevokAuthorization(%q): %v", v, err)
198 continue
199 }
200 }
201
202 if err := p.client.DeactivateReg(ctx); err != nil {
203 p.errorf("DeactivateReg: %v", err)
204 }
205
206 if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
207 p.errorf("RevokeCert: %v", err)
208 }
209 }
210
211 func (p *prober) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) {
212 dir, err := p.client.Discover(ctx)
213 if err != nil {
214 log.Fatalf("Discover: %v", err)
215 }
216 if dir.AuthzURL == "" {
217 log.Fatal("CA does not support pre-authorization")
218 }
219
220 var zurls []string
221 for _, id := range identifiers {
222 z, err := authorize(ctx, p.client, id)
223 if err != nil {
224 log.Fatalf("AuthorizeID(%+v): %v", z, err)
225 }
226 if z.Status == acme.StatusValid {
227 log.Printf("authz %s is valid; skipping", z.URI)
228 continue
229 }
230 if err := p.fulfill(ctx, z); err != nil {
231 log.Fatalf("fulfill(%s): %v", z.URI, err)
232 }
233 zurls = append(zurls, z.URI)
234 log.Printf("authorized for %+v", id)
235 }
236
237
238
239 log.Print("all challenges are done")
240 o, err := p.client.AuthorizeOrder(ctx, identifiers)
241 if err != nil {
242 log.Fatalf("AuthorizeOrder: %v", err)
243 }
244 waitCtx, cancel := context.WithTimeout(ctx, time.Minute)
245 defer cancel()
246 if _, err := p.client.WaitOrder(waitCtx, o.URI); err != nil {
247 log.Fatalf("WaitOrder(%q): %v", o.URI, err)
248 }
249 csr, certkey := newCSR(identifiers)
250 der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
251 if err != nil {
252 log.Fatalf("CreateOrderCert: %v", err)
253 }
254 log.Printf("cert URL: %s", curl)
255 if err := checkCert(der, identifiers); err != nil {
256 p.errorf("invalid cert: %v", err)
257 }
258
259
260 for _, v := range zurls {
261 if err := p.client.RevokeAuthorization(ctx, v); err != nil {
262 p.errorf("RevokeAuthorization(%q): %v", v, err)
263 continue
264 }
265 }
266
267 if err := p.client.DeactivateReg(ctx); err != nil {
268 p.errorf("DeactivateReg: %v", err)
269 }
270
271 if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
272 p.errorf("RevokeCert: %v", err)
273 }
274 }
275
276 func (p *prober) fulfill(ctx context.Context, z *acme.Authorization) error {
277 var chal *acme.Challenge
278 for i, c := range z.Challenges {
279 log.Printf("challenge %d: %+v", i, c)
280 if c.Type == p.chalType {
281 log.Printf("picked %s for authz %s", c.URI, z.URI)
282 chal = c
283 }
284 }
285 if chal == nil {
286 return fmt.Errorf("challenge type %q wasn't offered for authz %s", p.chalType, z.URI)
287 }
288
289 switch chal.Type {
290 case "tls-alpn-01":
291 return p.runTLSALPN01(ctx, z, chal)
292 case "http-01":
293 return p.runHTTP01(ctx, z, chal)
294 case "dns-01":
295 return p.runDNS01(ctx, z, chal)
296 default:
297 return fmt.Errorf("unknown challenge type %q", chal.Type)
298 }
299 }
300
301 func (p *prober) runTLSALPN01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
302 tokenCert, err := p.client.TLSALPN01ChallengeCert(chal.Token, z.Identifier.Value)
303 if err != nil {
304 return fmt.Errorf("TLSALPN01ChallengeCert: %v", err)
305 }
306 s := &http.Server{
307 Addr: p.localAddr,
308 TLSConfig: &tls.Config{
309 NextProtos: []string{acme.ALPNProto},
310 GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
311 log.Printf("hello: %+v", hello)
312 return &tokenCert, nil
313 },
314 },
315 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
316 log.Printf("%s %s", r.Method, r.URL)
317 w.WriteHeader(http.StatusNotFound)
318 }),
319 }
320 go s.ListenAndServeTLS("", "")
321 defer s.Close()
322
323 if _, err := p.client.Accept(ctx, chal); err != nil {
324 return fmt.Errorf("Accept(%q): %v", chal.URI, err)
325 }
326 _, zerr := p.client.WaitAuthorization(ctx, z.URI)
327 return zerr
328 }
329
330 func (p *prober) runHTTP01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
331 body, err := p.client.HTTP01ChallengeResponse(chal.Token)
332 if err != nil {
333 return fmt.Errorf("HTTP01ChallengeResponse: %v", err)
334 }
335 s := &http.Server{
336 Addr: p.localAddr,
337 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
338 log.Printf("%s %s", r.Method, r.URL)
339 if r.URL.Path != p.client.HTTP01ChallengePath(chal.Token) {
340 w.WriteHeader(http.StatusNotFound)
341 return
342 }
343 w.Write([]byte(body))
344 }),
345 }
346 go s.ListenAndServe()
347 defer s.Close()
348
349 if _, err := p.client.Accept(ctx, chal); err != nil {
350 return fmt.Errorf("Accept(%q): %v", chal.URI, err)
351 }
352 _, zerr := p.client.WaitAuthorization(ctx, z.URI)
353 return zerr
354 }
355
356 func (p *prober) runDNS01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
357 token, err := p.client.DNS01ChallengeRecord(chal.Token)
358 if err != nil {
359 return fmt.Errorf("DNS01ChallengeRecord: %v", err)
360 }
361
362 name := fmt.Sprintf("_acme-challenge.%s", z.Identifier.Value)
363 cmd := exec.CommandContext(ctx, p.dnsScript, name, token)
364 cmd.Stdin = os.Stdin
365 cmd.Stdout = os.Stdout
366 cmd.Stderr = os.Stderr
367 if err := cmd.Run(); err != nil {
368 return fmt.Errorf("%s: %v", p.dnsScript, err)
369 }
370
371 if _, err := p.client.Accept(ctx, chal); err != nil {
372 return fmt.Errorf("Accept(%q): %v", chal.URI, err)
373 }
374 _, zerr := p.client.WaitAuthorization(ctx, z.URI)
375 return zerr
376 }
377
378 func authorize(ctx context.Context, client *acme.Client, id acme.AuthzID) (*acme.Authorization, error) {
379 if id.Type == "ip" {
380 return client.AuthorizeIP(ctx, id.Value)
381 }
382 return client.Authorize(ctx, id.Value)
383 }
384
385 func checkCert(derChain [][]byte, id []acme.AuthzID) error {
386 if len(derChain) == 0 {
387 return errors.New("cert chain is zero bytes")
388 }
389 for i, b := range derChain {
390 crt, err := x509.ParseCertificate(b)
391 if err != nil {
392 return fmt.Errorf("%d: ParseCertificate: %v", i, err)
393 }
394 log.Printf("%d: serial: 0x%s", i, crt.SerialNumber)
395 log.Printf("%d: subject: %s", i, crt.Subject)
396 log.Printf("%d: issuer: %s", i, crt.Issuer)
397 log.Printf("%d: expires in %.1f day(s)", i, time.Until(crt.NotAfter).Hours()/24)
398 if i > 0 {
399 continue
400 }
401 p := &pem.Block{Type: "CERTIFICATE", Bytes: b}
402 log.Printf("%d: leaf:\n%s", i, pem.EncodeToMemory(p))
403 for _, v := range id {
404 if err := crt.VerifyHostname(v.Value); err != nil {
405 return err
406 }
407 }
408 }
409 return nil
410 }
411
412 func newCSR(identifiers []acme.AuthzID) ([]byte, crypto.Signer) {
413 var csr x509.CertificateRequest
414 for _, id := range identifiers {
415 switch id.Type {
416 case "dns":
417 csr.DNSNames = append(csr.DNSNames, id.Value)
418 case "ip":
419 csr.IPAddresses = append(csr.IPAddresses, net.ParseIP(id.Value))
420 default:
421 panic(fmt.Sprintf("newCSR: unknown identifier type %q", id.Type))
422 }
423 }
424 k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
425 if err != nil {
426 panic(fmt.Sprintf("newCSR: ecdsa.GenerateKey for a cert: %v", err))
427 }
428 b, err := x509.CreateCertificateRequest(rand.Reader, &csr, k)
429 if err != nil {
430 panic(fmt.Sprintf("newCSR: x509.CreateCertificateRequest: %v", err))
431 }
432 return b, k
433 }
434
View as plain text