...

Source file src/golang.org/x/crypto/acme/internal/acmeprobe/prober.go

Documentation: golang.org/x/crypto/acme/internal/acmeprobe

     1  // Copyright 2019 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  // The acmeprober program runs against an actual ACME CA implementation.
     6  // It spins up an HTTP server to fulfill authorization challenges
     7  // or execute a DNS script to provision a response to dns-01 challenge.
     8  //
     9  // For http-01 and tls-alpn-01 challenge types this requires the ACME CA
    10  // to be able to reach the HTTP server.
    11  //
    12  // A usage example:
    13  //
    14  //	go run prober.go \
    15  //	  -d https://acme-staging-v02.api.letsencrypt.org/directory \
    16  //	  -f order \
    17  //	  -t http-01 \
    18  //	  -a :8080 \
    19  //	  -domain some.example.org
    20  //
    21  // The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080
    22  // in order for the test to be able to fulfill http-01 challenge.
    23  // To test tls-alpn-01 challenge, 443 port would need to be tunneled
    24  // to 0.0.0.0:8080.
    25  // When running with dns-01 challenge type, use -s argument instead of -a.
    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  	// ACME CA directory URL.
    53  	// Let's Encrypt v2 prod: https://acme-v02.api.letsencrypt.org/directory
    54  	// Let's Encrypt v2 staging: https://acme-staging-v02.api.letsencrypt.org/directory
    55  	// See the following for more CAs implementing ACME protocol:
    56  	// https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment#CAs_&_PKIs_that_offer_ACME_certificates
    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  	// Duration of the whole run.
   103  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
   104  	defer cancel()
   105  
   106  	// Create and register a new account.
   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  	// Run the desired flow test.
   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  	// Create a new order and pick a challenge.
   154  	// Note that Let's Encrypt will reply with 400 error:malformed
   155  	// "NotBefore and NotAfter are not supported" when providing a NotAfter
   156  	// value like WithOrderNotAfter(time.Now().Add(24 * time.Hour)).
   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  	// Deactivate all authorizations we satisfied earlier.
   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  	// Deactivate the account. We don't need it for any further calls.
   202  	if err := p.client.DeactivateReg(ctx); err != nil {
   203  		p.errorf("DeactivateReg: %v", err)
   204  	}
   205  	// Try revoking the issued cert using its private key.
   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  	// We should be all set now.
   238  	// Expect all authorizations to be satisfied.
   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  	// Deactivate all authorizations we satisfied earlier.
   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  	// Deactivate the account. We don't need it for any further calls.
   267  	if err := p.client.DeactivateReg(ctx); err != nil {
   268  		p.errorf("DeactivateReg: %v", err)
   269  	}
   270  	// Try revoking the issued cert using its private key.
   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 { // not a leaf cert
   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