prober.go 14 KB

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