From d33d5abafbfeb08cd754b7aeedb9dacd380bfa85 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Mon, 11 Nov 2019 23:35:13 +0000 Subject: [PATCH] Switch to order-based workflow for RFC 8555 --- acme.go | 99 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/acme.go b/acme.go index 4cb17897..85c0c112 100644 --- a/acme.go +++ b/acme.go @@ -133,7 +133,7 @@ func (a *ACME) acmeClient(ctx context.Context) (*acme.Client, error) { // account is already registered we get a StatusConflict, // which we can ignore. _, err = client.Register(ctx, ac, func(_ string) bool { return true }) - if ae, ok := err.(*acme.Error); err == nil || ok && ae.StatusCode == http.StatusConflict { + if ae, ok := err.(*acme.Error); err == nil || err == acme.ErrAccountAlreadyExists || (ok && ae.StatusCode == http.StatusConflict) { a.client = client err = nil } @@ -149,7 +149,8 @@ func (a *ACME) GetCertificate(ctx context.Context, key crypto.Signer, c *certCon return nil, nil, err } - if err = a.verifyAll(ctx, client, c); err != nil { + o, err := a.verifyAll(ctx, client, c) + if err != nil { return nil, nil, err } @@ -157,7 +158,7 @@ func (a *ACME) GetCertificate(ctx context.Context, key crypto.Signer, c *certCon if err != nil { return nil, nil, err } - der, _, err = client.CreateCert(ctx, csr, 0, true) + der, _, err = client.CreateOrderCert(ctx, o.FinalizeURL, csr, true) if err != nil { return nil, nil, err } @@ -168,56 +169,66 @@ func (a *ACME) GetCertificate(ctx context.Context, key crypto.Signer, c *certCon return der, leaf, nil } -func (a *ACME) verifyAll(ctx context.Context, client *acme.Client, c *certConfig) error { - for _, domain := range c.Names { - if err := a.verify(ctx, client, c, domain); err != nil { - return err - } - } - return nil -} - -func (a *ACME) verify(ctx context.Context, client *acme.Client, c *certConfig, domain string) error { +func (a *ACME) verifyAll(ctx context.Context, client *acme.Client, c *certConfig) (*acme.Order, error) { // Make an authorization request to the ACME server, and // verify that it returns a valid response with challenges. - authz, err := client.Authorize(ctx, domain) + o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(c.Names...)) if err != nil { - return err - } - switch authz.Status { - case acme.StatusValid: - return nil // already authorized - case acme.StatusInvalid: - return fmt.Errorf("invalid authorization %q", authz.URI) + return nil, fmt.Errorf("AuthorizeOrder failed: %v", err) } - // Pick a challenge that matches our preferences and the - // available validators. The validator fulfills the challenge, - // and returns a cleanup function that we're going to call - // before we return. All steps are sequential and idempotent. - chal := a.pickChallenge(authz.Challenges, c) - if chal == nil { - return fmt.Errorf("unable to authorize %q", domain) - } - v, ok := a.validators[chal.Type] - if !ok { - return fmt.Errorf("challenge type '%s' is not available", chal.Type) + switch o.Status { + case acme.StatusReady: + return o, nil // already authorized + case acme.StatusPending: + default: + return nil, fmt.Errorf("invalid new order status %q", o.Status) } - cleanup, err := v.Fulfill(ctx, client, domain, chal) - if err != nil { - return err + + for _, zurl := range o.AuthzURLs { + z, err := client.GetAuthorization(ctx, zurl) + if err != nil { + return nil, fmt.Errorf("GetAuthorization(%s) failed: %v", zurl, err) + } + if z.Status != acme.StatusPending { + continue + } + // Pick a challenge that matches our preferences and the + // available validators. The validator fulfills the challenge, + // and returns a cleanup function that we're going to call + // before we return. All steps are sequential and idempotent. + chal := a.pickChallenge(z.Challenges, c) + if chal == nil { + return nil, fmt.Errorf("unable to authorize %q", c.Names) + } + + v, ok := a.validators[chal.Type] + if !ok { + return nil, fmt.Errorf("challenge type '%s' is not available", chal.Type) + } + + for _, domain := range c.Names { + cleanup, err := v.Fulfill(ctx, client, domain, chal) + if err != nil { + return nil, fmt.Errorf("fulfillment failed: %v", err) + } + defer cleanup() + } + + if _, err := client.Accept(ctx, chal); err != nil { + return nil, fmt.Errorf("challenge accept failed: %v", err) + } + if _, err := client.WaitAuthorization(ctx, z.URI); err != nil { + return nil, fmt.Errorf("WaitAuthorization(%s) failed: %v", z.URI, err) + } } - defer cleanup() - // Tell the ACME server that we've accepted the challenge, and - // then wait, possibly for some time, until there is an - // authorization response (either successful or not) from the - // server. - if _, err = client.Accept(ctx, chal); err != nil { - return err + // Authorizations are satisfied, wait for the CA + // to update the order status. + if _, err = client.WaitOrder(ctx, o.URI); err != nil { + return nil, err } - _, err = client.WaitAuthorization(ctx, authz.URI) - return err + return o, nil } // Pick a challenge with the right type from the Challenge response -- GitLab