diff --git a/BUILD.bazel b/BUILD.bazel index 394c39f25..d5c2b42bc 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -71,7 +71,6 @@ filegroup( "//test/e2e:all-srcs", "//test/unit/gen:all-srcs", "//test/unit/listers:all-srcs", - "//third_party:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/go.mod b/go.mod index 68f5c332d..0654c7fb8 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.3.0 github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 // indirect - golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 + golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect diff --git a/go.sum b/go.sum index cca6d5f75..01c89170f 100644 --- a/go.sum +++ b/go.sum @@ -510,6 +510,8 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/hack/build/repos.bzl b/hack/build/repos.bzl index 3bbca0e53..3d429ead2 100644 --- a/hack/build/repos.bzl +++ b/hack/build/repos.bzl @@ -1927,8 +1927,8 @@ def go_repositories(): build_file_generation = "on", build_file_proto_mode = "disable", importpath = "golang.org/x/crypto", - sum = "h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=", - version = "v0.0.0-20190611184440-5c40567a22f8", + sum = "h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=", + version = "v0.0.0-20191202143827-86a70503ff7e", ) go_repository( name = "org_golang_x_exp", diff --git a/pkg/acme/BUILD.bazel b/pkg/acme/BUILD.bazel index 7d5c6f829..fde9bb692 100644 --- a/pkg/acme/BUILD.bazel +++ b/pkg/acme/BUILD.bazel @@ -19,9 +19,9 @@ go_library( "//pkg/util:go_default_library", "//pkg/util/errors:go_default_library", "//pkg/util/pki:go_default_library", - "//third_party/crypto/acme:go_default_library", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_client_go//listers/core/v1:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/acme/cache.go b/pkg/acme/cache.go index ce59a3adc..4cc6596e6 100644 --- a/pkg/acme/cache.go +++ b/pkg/acme/cache.go @@ -26,12 +26,13 @@ import ( "sync" "time" + acmecl "golang.org/x/crypto/acme" + acme "github.com/jetstack/cert-manager/pkg/acme/client" acmemw "github.com/jetstack/cert-manager/pkg/acme/client/middleware" cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2" cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" "github.com/jetstack/cert-manager/pkg/util" - acmecl "github.com/jetstack/cert-manager/third_party/crypto/acme" ) // This file implements a basic cache for ACME clients that can be used to @@ -123,7 +124,7 @@ func buildHTTPClient(skipTLSVerify bool) *http.Client { }) } -var timeout = time.Duration(5 * time.Second) +var timeout = 5 * time.Second func dialTimeout(ctx context.Context, network, addr string) (net.Conn, error) { d := net.Dialer{Timeout: timeout} diff --git a/pkg/acme/client/BUILD.bazel b/pkg/acme/client/BUILD.bazel index 76ca337dc..71b71387a 100644 --- a/pkg/acme/client/BUILD.bazel +++ b/pkg/acme/client/BUILD.bazel @@ -11,7 +11,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/metrics:go_default_library", - "//third_party/crypto/acme:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/acme/client/fake.go b/pkg/acme/client/fake.go index 7dd91b9a2..80346f579 100644 --- a/pkg/acme/client/fake.go +++ b/pkg/acme/client/fake.go @@ -20,7 +20,7 @@ import ( "context" "fmt" - "github.com/jetstack/cert-manager/third_party/crypto/acme" + "golang.org/x/crypto/acme" ) // TODO: expand this out one day to be backed by the pebble wfe package @@ -28,28 +28,30 @@ import ( // FakeACME is a convenience structure to create a stub ACME implementation type FakeACME struct { - FakeCreateOrder func(ctx context.Context, order *acme.Order) (*acme.Order, error) + FakeAuthorizeOrder func(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) FakeGetOrder func(ctx context.Context, url string) (*acme.Order, error) - FakeGetCertificate func(ctx context.Context, url string) ([][]byte, error) + FakeFetchCert func(ctx context.Context, url string, bundle bool) ([][]byte, error) FakeWaitOrder func(ctx context.Context, url string) (*acme.Order, error) - FakeFinalizeOrder func(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) - FakeAcceptChallenge func(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) + FakeCreateOrderCert func(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) + FakeAccept func(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) FakeGetChallenge func(ctx context.Context, url string) (*acme.Challenge, error) FakeGetAuthorization func(ctx context.Context, url string) (*acme.Authorization, error) FakeWaitAuthorization func(ctx context.Context, url string) (*acme.Authorization, error) - FakeCreateAccount func(ctx context.Context, a *acme.Account) (*acme.Account, error) - FakeGetAccount func(ctx context.Context) (*acme.Account, error) + FakeRegister func(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) + FakeGetReg func(ctx context.Context, url string) (*acme.Account, error) FakeHTTP01ChallengeResponse func(token string) (string, error) FakeDNS01ChallengeRecord func(token string) (string, error) FakeDiscover func(ctx context.Context) (acme.Directory, error) - FakeUpdateAccount func(ctx context.Context, a *acme.Account) (*acme.Account, error) + FakeUpdateReg func(ctx context.Context, a *acme.Account) (*acme.Account, error) } -func (f *FakeACME) CreateOrder(ctx context.Context, order *acme.Order) (*acme.Order, error) { - if f.FakeCreateOrder != nil { - return f.FakeCreateOrder(ctx, order) +var _ Interface = &FakeACME{} + +func (f *FakeACME) AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) { + if f.FakeAuthorizeOrder != nil { + return f.FakeAuthorizeOrder(ctx, id, opt...) } - return nil, fmt.Errorf("CreateOrder not implemented") + return nil, fmt.Errorf("AuthorizeOrder not implemented") } func (f *FakeACME) GetOrder(ctx context.Context, url string) (*acme.Order, error) { @@ -59,11 +61,11 @@ func (f *FakeACME) GetOrder(ctx context.Context, url string) (*acme.Order, error return nil, fmt.Errorf("GetOrder not implemented") } -func (f *FakeACME) GetCertificate(ctx context.Context, url string) ([][]byte, error) { - if f.FakeGetCertificate != nil { - return f.FakeGetCertificate(ctx, url) +func (f *FakeACME) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) { + if f.FakeFetchCert != nil { + return f.FakeFetchCert(ctx, url, bundle) } - return nil, fmt.Errorf("GetCertificate not implemented") + return nil, fmt.Errorf("FetchCert not implemented") } func (f *FakeACME) WaitOrder(ctx context.Context, url string) (*acme.Order, error) { @@ -73,18 +75,18 @@ func (f *FakeACME) WaitOrder(ctx context.Context, url string) (*acme.Order, erro return nil, fmt.Errorf("WaitOrder not implemented") } -func (f *FakeACME) FinalizeOrder(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) { - if f.FakeFinalizeOrder != nil { - return f.FakeFinalizeOrder(ctx, finalizeURL, csr) +func (f *FakeACME) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { + if f.FakeCreateOrderCert != nil { + return f.FakeCreateOrderCert(ctx, finalizeURL, csr, bundle) } - return nil, fmt.Errorf("FinalizeOrder not implemented") + return nil, "", fmt.Errorf("CreateOrderCert not implemented") } -func (f *FakeACME) AcceptChallenge(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) { - if f.FakeAcceptChallenge != nil { - return f.FakeAcceptChallenge(ctx, chal) +func (f *FakeACME) Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) { + if f.FakeAccept != nil { + return f.FakeAccept(ctx, chal) } - return nil, fmt.Errorf("AcceptChallenge not implemented") + return nil, fmt.Errorf("Accept not implemented") } func (f *FakeACME) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) { @@ -108,18 +110,18 @@ func (f *FakeACME) WaitAuthorization(ctx context.Context, url string) (*acme.Aut return nil, fmt.Errorf("WaitAuthorization not implemented") } -func (f *FakeACME) CreateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) { - if f.FakeCreateAccount != nil { - return f.FakeCreateAccount(ctx, a) +func (f *FakeACME) Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) { + if f.FakeRegister != nil { + return f.FakeRegister(ctx, a, prompt) } - return nil, fmt.Errorf("CreateAccount not implemented") + return nil, fmt.Errorf("Register not implemented") } -func (f *FakeACME) GetAccount(ctx context.Context) (*acme.Account, error) { - if f.FakeGetAccount != nil { - return f.FakeGetAccount(ctx) +func (f *FakeACME) GetReg(ctx context.Context, url string) (*acme.Account, error) { + if f.FakeGetReg != nil { + return f.FakeGetReg(ctx, url) } - return nil, fmt.Errorf("GetAccount not implemented") + return nil, fmt.Errorf("GetReg not implemented") } func (f *FakeACME) HTTP01ChallengeResponse(token string) (string, error) { @@ -145,9 +147,9 @@ func (f *FakeACME) Discover(ctx context.Context) (acme.Directory, error) { return acme.Directory{}, nil } -func (f *FakeACME) UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) { - if f.FakeUpdateAccount != nil { - return f.FakeUpdateAccount(ctx, a) +func (f *FakeACME) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) { + if f.FakeUpdateReg != nil { + return f.FakeUpdateReg(ctx, a) } - return nil, fmt.Errorf("UpdateAccount not implemented") + return nil, fmt.Errorf("UpdateReg not implemented") } diff --git a/pkg/acme/client/interfaces.go b/pkg/acme/client/interfaces.go index ee8179744..ef67c02a6 100644 --- a/pkg/acme/client/interfaces.go +++ b/pkg/acme/client/interfaces.go @@ -19,25 +19,25 @@ package client import ( "context" - "github.com/jetstack/cert-manager/third_party/crypto/acme" + "golang.org/x/crypto/acme" ) type Interface interface { - CreateOrder(ctx context.Context, order *acme.Order) (*acme.Order, error) + AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) GetOrder(ctx context.Context, url string) (*acme.Order, error) - GetCertificate(ctx context.Context, url string) ([][]byte, error) + FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) WaitOrder(ctx context.Context, url string) (*acme.Order, error) - FinalizeOrder(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) - AcceptChallenge(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) + CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) + Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) GetAuthorization(ctx context.Context, url string) (*acme.Authorization, error) WaitAuthorization(ctx context.Context, url string) (*acme.Authorization, error) - CreateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) - GetAccount(ctx context.Context) (*acme.Account, error) + Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) + GetReg(ctx context.Context, url string) (*acme.Account, error) HTTP01ChallengeResponse(token string) (string, error) DNS01ChallengeRecord(token string) (string, error) Discover(ctx context.Context) (acme.Directory, error) - UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) + UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) } var _ Interface = &acme.Client{} diff --git a/pkg/acme/client/middleware/BUILD.bazel b/pkg/acme/client/middleware/BUILD.bazel index 5c2e67baa..9bfa5b69d 100644 --- a/pkg/acme/client/middleware/BUILD.bazel +++ b/pkg/acme/client/middleware/BUILD.bazel @@ -7,8 +7,8 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/acme/client:go_default_library", - "//third_party/crypto/acme:go_default_library", "@io_k8s_klog//:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/acme/client/middleware/logger.go b/pkg/acme/client/middleware/logger.go index 12e747060..94e935377 100644 --- a/pkg/acme/client/middleware/logger.go +++ b/pkg/acme/client/middleware/logger.go @@ -19,10 +19,10 @@ package middleware import ( "context" + "golang.org/x/crypto/acme" "k8s.io/klog" "github.com/jetstack/cert-manager/pkg/acme/client" - "github.com/jetstack/cert-manager/third_party/crypto/acme" ) func NewLogger(baseCl client.Interface) client.Interface { @@ -34,9 +34,11 @@ type Logger struct { baseCl client.Interface } -func (l *Logger) CreateOrder(ctx context.Context, order *acme.Order) (*acme.Order, error) { +var _ client.Interface = &Logger{} + +func (l *Logger) AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) { klog.Infof("Calling CreateOrder") - return l.baseCl.CreateOrder(ctx, order) + return l.baseCl.AuthorizeOrder(ctx, id, opt...) } func (l *Logger) GetOrder(ctx context.Context, url string) (*acme.Order, error) { @@ -44,9 +46,9 @@ func (l *Logger) GetOrder(ctx context.Context, url string) (*acme.Order, error) return l.baseCl.GetOrder(ctx, url) } -func (l *Logger) GetCertificate(ctx context.Context, url string) ([][]byte, error) { +func (l *Logger) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) { klog.Infof("Calling GetCertificate") - return l.baseCl.GetCertificate(ctx, url) + return l.baseCl.FetchCert(ctx, url, bundle) } func (l *Logger) WaitOrder(ctx context.Context, url string) (*acme.Order, error) { @@ -54,14 +56,14 @@ func (l *Logger) WaitOrder(ctx context.Context, url string) (*acme.Order, error) return l.baseCl.WaitOrder(ctx, url) } -func (l *Logger) FinalizeOrder(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) { +func (l *Logger) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { klog.Infof("Calling FinalizeOrder") - return l.baseCl.FinalizeOrder(ctx, finalizeURL, csr) + return l.baseCl.CreateOrderCert(ctx, finalizeURL, csr, bundle) } -func (l *Logger) AcceptChallenge(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) { +func (l *Logger) Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) { klog.Infof("Calling AcceptChallenge") - return l.baseCl.AcceptChallenge(ctx, chal) + return l.baseCl.Accept(ctx, chal) } func (l *Logger) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) { @@ -79,14 +81,14 @@ func (l *Logger) WaitAuthorization(ctx context.Context, url string) (*acme.Autho return l.baseCl.WaitAuthorization(ctx, url) } -func (l *Logger) CreateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) { +func (l *Logger) Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) { klog.Infof("Calling CreateAccount") - return l.baseCl.CreateAccount(ctx, a) + return l.baseCl.Register(ctx, a, prompt) } -func (l *Logger) GetAccount(ctx context.Context) (*acme.Account, error) { +func (l *Logger) GetReg(ctx context.Context, url string) (*acme.Account, error) { klog.Infof("Calling GetAccount") - return l.baseCl.GetAccount(ctx) + return l.baseCl.GetReg(ctx, url) } func (l *Logger) HTTP01ChallengeResponse(token string) (string, error) { @@ -104,7 +106,7 @@ func (l *Logger) Discover(ctx context.Context) (acme.Directory, error) { return l.baseCl.Discover(ctx) } -func (l *Logger) UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) { +func (l *Logger) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) { klog.Infof("Calling UpdateAccount") - return l.baseCl.UpdateAccount(ctx, a) + return l.baseCl.UpdateReg(ctx, a) } diff --git a/pkg/controller/acmechallenges/BUILD.bazel b/pkg/controller/acmechallenges/BUILD.bazel index e4776741b..aa4d936aa 100644 --- a/pkg/controller/acmechallenges/BUILD.bazel +++ b/pkg/controller/acmechallenges/BUILD.bazel @@ -27,7 +27,6 @@ go_library( "//pkg/logs:go_default_library", "//pkg/metrics:go_default_library", "//pkg/util/feature:go_default_library", - "//third_party/crypto/acme:go_default_library", "@com_github_go_logr_logr//:go_default_library", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/api/errors:go_default_library", @@ -36,6 +35,7 @@ go_library( "@io_k8s_client_go//tools/cache:go_default_library", "@io_k8s_client_go//tools/record:go_default_library", "@io_k8s_client_go//util/workqueue:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) @@ -52,9 +52,9 @@ go_test( "//pkg/controller/test:go_default_library", "//pkg/issuer:go_default_library", "//test/unit/gen:go_default_library", - "//third_party/crypto/acme:go_default_library", "@io_k8s_apimachinery//pkg/runtime:go_default_library", "@io_k8s_client_go//testing:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/controller/acmechallenges/sync.go b/pkg/controller/acmechallenges/sync.go index 703e807ee..387d15f85 100644 --- a/pkg/controller/acmechallenges/sync.go +++ b/pkg/controller/acmechallenges/sync.go @@ -22,6 +22,7 @@ import ( "reflect" "time" + acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -35,7 +36,6 @@ import ( logf "github.com/jetstack/cert-manager/pkg/logs" "github.com/jetstack/cert-manager/pkg/metrics" utilfeature "github.com/jetstack/cert-manager/pkg/util/feature" - acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme" ) const ( @@ -217,7 +217,7 @@ func handleError(ch *cmacme.Challenge, err error) error { if acmeErr, ok = err.(*acmeapi.Error); !ok { return err } - switch acmeErr.Type { + switch acmeErr.ProblemType { // This response type is returned when an authorization has expired or the // request is in some way malformed. // In this case, we should mark the challenge as expired so that the order @@ -315,7 +315,11 @@ func (c *controller) syncChallengeStatus(ctx context.Context, cl acmecl.Interfac // this info ch.Status.Reason = "" if acmeChallenge.Error != nil { - ch.Status.Reason = acmeChallenge.Error.Detail + if acmeErr, ok := acmeChallenge.Error.(*acmeapi.Error); ok { + ch.Status.Reason = acmeErr.Detail + } else { + ch.Status.Reason = acmeChallenge.Error.Error() + } } ch.Status.State = cmState @@ -334,10 +338,10 @@ func (c *controller) acceptChallenge(ctx context.Context, cl acmecl.Interface, c // We manually construct an ACME challenge here from our own internal type // to save additional round trips to the ACME server. acmeChal := &acmeapi.Challenge{ - URL: ch.Spec.URL, + URI: ch.Spec.URL, Token: ch.Spec.Token, } - acmeChal, err := cl.AcceptChallenge(ctx, acmeChal) + acmeChal, err := cl.Accept(ctx, acmeChal) if acmeChal != nil { ch.Status.State = cmacme.State(acmeChal.Status) } @@ -362,28 +366,20 @@ func (c *controller) acceptChallenge(ctx context.Context, cl acmecl.Interface, c } func (c *controller) handleAuthorizationError(ch *cmacme.Challenge, err error) error { - authErr, ok := err.(acmeapi.AuthorizationError) + authErr, ok := err.(*acmeapi.AuthorizationError) if !ok { return handleError(ch, err) } - var acmeCh *acmeapi.Challenge - for _, n := range authErr.Authorization.Challenges { - if n.URL == ch.Spec.URL { - acmeCh = n - break - } - } - - // eventErr is a reference to the error that will be logged as an Event - ch.Status.State = cmacme.State(authErr.Authorization.Status) - if acmeCh != nil && acmeCh.Error != nil { - ch.Status.Reason = fmt.Sprintf("Error accepting challenge: %v", acmeCh.Error.Error()) - err = acmeCh.Error - } else { - ch.Status.Reason = fmt.Sprintf("Error accepting authorization: %v", authErr) - } - c.recorder.Eventf(ch, corev1.EventTypeWarning, "Failed", "Accepting challenge authorization failed: %v", err) + // TODO: the AuthorizationError above could technically contain the final + // state of the authorization in its raw JSON form. This isn't currently + // exposed by the ACME client implementation, so for now we fix this to + // 'invalid' if the returned type here is an AuthorizationError, which + // should be safe as the client library only returns an AuthorizationError + // if the returned state is 'invalid' + ch.Status.State = cmacme.Invalid + ch.Status.Reason = fmt.Sprintf("Error accepting authorization: %v", authErr) + c.recorder.Eventf(ch, corev1.EventTypeWarning, "Failed", "Accepting challenge authorization failed: %v", authErr) // return nil here, as accepting the challenge did not error, the challenge // simply failed diff --git a/pkg/controller/acmechallenges/sync_test.go b/pkg/controller/acmechallenges/sync_test.go index 2df71a7b5..76901be29 100644 --- a/pkg/controller/acmechallenges/sync_test.go +++ b/pkg/controller/acmechallenges/sync_test.go @@ -21,6 +21,7 @@ import ( "fmt" "testing" + acmeapi "golang.org/x/crypto/acme" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" @@ -32,7 +33,6 @@ import ( testpkg "github.com/jetstack/cert-manager/pkg/controller/test" "github.com/jetstack/cert-manager/pkg/issuer" "github.com/jetstack/cert-manager/test/unit/gen" - acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme" ) // Present the challenge value with the given solver. @@ -199,7 +199,7 @@ func TestSyncHappyPath(t *testing.T) { }, }, acmeClient: &acmecl.FakeACME{ - FakeAcceptChallenge: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { + FakeAccept: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { // return something other than valid here so we can verify that // the challenge.status.state is set to the *authorizations* // status and not the challenges @@ -244,27 +244,26 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengeState(cmacme.Invalid), gen.SetChallengeType("http-01"), gen.SetChallengePresented(true), - gen.SetChallengeReason("Error accepting authorization: acme: authorization for identifier example.com is invalid"), + gen.SetChallengeReason("Error accepting authorization: acme: authorization error for example.com: an error happened"), ))), }, ExpectedEvents: []string{ - "Warning Failed Accepting challenge authorization failed: acme: authorization for identifier example.com is invalid", + "Warning Failed Accepting challenge authorization failed: acme: authorization error for example.com: an error happened", }, }, acmeClient: &acmecl.FakeACME{ - FakeAcceptChallenge: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { + FakeAccept: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { // return something other than invalid here so we can verify that // the challenge.status.state is set to the *authorizations* // status and not the challenges return &acmeapi.Challenge{Status: acmeapi.StatusPending}, nil }, FakeWaitAuthorization: func(context.Context, string) (*acmeapi.Authorization, error) { - return nil, acmeapi.AuthorizationError{ - Authorization: &acmeapi.Authorization{ - Status: acmeapi.StatusInvalid, - Identifier: acmeapi.AuthzID{ - Value: "example.com", - }, + return nil, &acmeapi.AuthorizationError{ + URI: "http://testerroruri", + Identifier: "example.com", + Errors: []error{ + fmt.Errorf("an error happened"), }, } }, @@ -304,36 +303,29 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengeState(cmacme.Invalid), gen.SetChallengeType("http-01"), gen.SetChallengePresented(true), - gen.SetChallengeReason("Error accepting challenge: acme: fakeerror: this is a very detailed error"), + gen.SetChallengeReason("Error accepting authorization: acme: authorization error for example.com: 400 fakeerror: this is a very detailed error"), ))), }, ExpectedEvents: []string{ - "Warning Failed Accepting challenge authorization failed: acme: fakeerror: this is a very detailed error", + "Warning Failed Accepting challenge authorization failed: acme: authorization error for example.com: 400 fakeerror: this is a very detailed error", }, }, acmeClient: &acmecl.FakeACME{ - FakeAcceptChallenge: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { + FakeAccept: func(context.Context, *acmeapi.Challenge) (*acmeapi.Challenge, error) { // return something other than invalid here so we can verify that // the challenge.status.state is set to the *authorizations* // status and not the challenges return &acmeapi.Challenge{Status: acmeapi.StatusPending}, nil }, FakeWaitAuthorization: func(context.Context, string) (*acmeapi.Authorization, error) { - return nil, acmeapi.AuthorizationError{ - Authorization: &acmeapi.Authorization{ - Status: acmeapi.StatusInvalid, - Identifier: acmeapi.AuthzID{ - Value: "example.com", - }, - Challenges: []*acmeapi.Challenge{ - { - URL: "testurl", - Error: &acmeapi.Error{ - StatusCode: 400, - Type: "fakeerror", - Detail: "this is a very detailed error", - }, - }, + return nil, &acmeapi.AuthorizationError{ + URI: "http://testerroruri", + Identifier: "example.com", + Errors: []error{ + &acmeapi.Error{ + StatusCode: 400, + ProblemType: "fakeerror", + Detail: "this is a very detailed error", }, }, } diff --git a/pkg/controller/acmeorders/BUILD.bazel b/pkg/controller/acmeorders/BUILD.bazel index 26ad80fab..1ccdcdc0f 100644 --- a/pkg/controller/acmeorders/BUILD.bazel +++ b/pkg/controller/acmeorders/BUILD.bazel @@ -23,7 +23,6 @@ go_library( "//pkg/issuer:go_default_library", "//pkg/logs:go_default_library", "//pkg/metrics:go_default_library", - "//third_party/crypto/acme:go_default_library", "@com_github_go_logr_logr//:go_default_library", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/api/errors:go_default_library", @@ -37,6 +36,7 @@ go_library( "@io_k8s_client_go//tools/record:go_default_library", "@io_k8s_client_go//util/workqueue:go_default_library", "@io_k8s_utils//clock:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) @@ -72,12 +72,12 @@ go_test( "//pkg/apis/meta/v1:go_default_library", "//pkg/controller/test:go_default_library", "//test/unit/gen:go_default_library", - "//third_party/crypto/acme:go_default_library", "@com_github_kr_pretty//:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_apimachinery//pkg/runtime:go_default_library", "@io_k8s_client_go//testing:go_default_library", "@io_k8s_utils//clock/testing:go_default_library", "@io_k8s_utils//pointer:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/controller/acmeorders/sync.go b/pkg/controller/acmeorders/sync.go index 257abd453..03515a8bf 100644 --- a/pkg/controller/acmeorders/sync.go +++ b/pkg/controller/acmeorders/sync.go @@ -23,6 +23,7 @@ import ( "fmt" "reflect" + acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,7 +36,6 @@ import ( cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2" logf "github.com/jetstack/cert-manager/pkg/logs" "github.com/jetstack/cert-manager/pkg/metrics" - acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme" ) func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { @@ -183,7 +183,6 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cmacme.Order) error { log := logf.FromContext(ctx) - dbg := log.V(logf.DebugLevel) if o.Status.URL != "" { return fmt.Errorf("refusing to recreate a new order for Order %q. Please create a new Order resource to initiate a new order", o.Name) @@ -195,11 +194,9 @@ func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cm identifierSet.Insert(o.Spec.CommonName) } log.Info("build set of domains for Order", "domains", identifierSet.List()) - + authzIDs := acmeapi.DomainIDs(identifierSet.List()...) // create a new order with the acme server - orderTemplate := acmeapi.NewOrder(identifierSet.List()...) - dbg.Info("constructed order template", "template", orderTemplate) - acmeOrder, err := cl.CreateOrder(ctx, orderTemplate) + acmeOrder, err := cl.AuthorizeOrder(ctx, authzIDs) if acmeErr, ok := err.(*acmeapi.Error); ok { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(err, "failed to create Order resource due to bad request, marking Order as failed") @@ -213,7 +210,7 @@ func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cm } log.Info("submitted Order to ACME server") - o.Status.URL = acmeOrder.URL + o.Status.URL = acmeOrder.URI o.Status.FinalizeURL = acmeOrder.FinalizeURL o.Status.Authorizations = constructAuthorizations(acmeOrder) c.setOrderState(&o.Status, acmeOrder.Status) @@ -234,12 +231,12 @@ func (c *controller) updateOrderStatus(ctx context.Context, cl acmecl.Interface, } log.V(logf.DebugLevel).Info("Retrieved ACME order from server", "raw_data", acmeOrder) - o.Status.URL = acmeOrder.URL + o.Status.URL = acmeOrder.URI o.Status.FinalizeURL = acmeOrder.FinalizeURL c.setOrderState(&o.Status, acmeOrder.Status) // only set the authorizations field if the lengths mismatch. // state can be rebuilt in this case on the next call to ProcessItem. - if len(o.Status.Authorizations) != len(acmeOrder.Authorizations) { + if len(o.Status.Authorizations) != len(acmeOrder.AuthzURLs) { o.Status.Authorizations = constructAuthorizations(acmeOrder) } @@ -264,8 +261,8 @@ func (c *controller) setOrderState(o *cmacme.OrderStatus, s string) { // named on the Order to fetch additional metadata, instead, use // populateAuthorization on each authorization in turn. func constructAuthorizations(o *acmeapi.Order) []cmacme.ACMEAuthorization { - authzs := make([]cmacme.ACMEAuthorization, len(o.Authorizations)) - for i, url := range o.Authorizations { + authzs := make([]cmacme.ACMEAuthorization, len(o.AuthzURLs)) + for i, url := range o.AuthzURLs { authzs[i].URL = url } return authzs @@ -305,7 +302,7 @@ func (c *controller) fetchMetadataForAuthorizations(ctx context.Context, o *cmac authz.Wildcard = &acmeAuthz.Wildcard authz.Challenges = make([]cmacme.ACMEChallenge, len(acmeAuthz.Challenges)) for i, acmech := range acmeAuthz.Challenges { - authz.Challenges[i].URL = acmech.URL + authz.Challenges[i].URL = acmech.URI authz.Challenges[i].Token = acmech.Token authz.Challenges[i].Type = cmacme.ACMEChallengeType(acmech.Type) } @@ -437,7 +434,7 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * derBytes = block.Bytes } - certSlice, err := cl.FinalizeOrder(ctx, o.Status.FinalizeURL, derBytes) + certSlice, _, err := cl.CreateOrderCert(ctx, o.Status.FinalizeURL, derBytes, true) // if an ACME error is returned and it's a 4xx error, mark this Order as // failed and do not retry it until after applying the global backoff. if acmeErr, ok := err.(*acmeapi.Error); ok { @@ -521,7 +518,7 @@ func (c *controller) fetchCertificateData(ctx context.Context, cl acmecl.Interfa return nil } - certs, err := cl.GetCertificate(ctx, acmeOrder.CertificateURL) + certs, err := cl.FetchCert(ctx, acmeOrder.CertURL, true) if acmeErr, ok := err.(*acmeapi.Error); ok { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(err, "failed to retrieve issued certificate from ACME server") diff --git a/pkg/controller/acmeorders/sync_test.go b/pkg/controller/acmeorders/sync_test.go index 4bb7d91f7..2aa181e9a 100644 --- a/pkg/controller/acmeorders/sync_test.go +++ b/pkg/controller/acmeorders/sync_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + acmeapi "golang.org/x/crypto/acme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" @@ -33,7 +34,6 @@ import ( cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" testpkg "github.com/jetstack/cert-manager/pkg/controller/test" "github.com/jetstack/cert-manager/test/unit/gen" - acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme" ) func TestSyncHappyPath(t *testing.T) { @@ -109,7 +109,7 @@ dGVzdA== testAuthorizationChallengeInvalid.Status.State = cmacme.Invalid testACMEAuthorizationPending := &acmeapi.Authorization{ - URL: "http://authzurl", + URI: "http://authzurl", Status: acmeapi.StatusPending, Identifier: acmeapi.AuthzID{ Value: "test.com", @@ -123,16 +123,16 @@ dGVzdA== } testACMEOrderPending := &acmeapi.Order{ - URL: testOrderPending.Status.URL, + URI: testOrderPending.Status.URL, Identifiers: []acmeapi.AuthzID{ { Type: "dns", Value: "test.com", }, }, - FinalizeURL: testOrderPending.Status.FinalizeURL, - Authorizations: []string{"http://authzurl"}, - Status: acmeapi.StatusPending, + FinalizeURL: testOrderPending.Status.FinalizeURL, + AuthzURLs: []string{"http://authzurl"}, + Status: acmeapi.StatusPending, } // shallow copy testACMEOrderValid := &acmeapi.Order{} @@ -169,7 +169,7 @@ dGVzdA== }, }, acmeClient: &acmecl.FakeACME{ - FakeCreateOrder: func(ctx context.Context, o *acmeapi.Order) (*acmeapi.Order, error) { + FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) { return testACMEOrderPending, nil }, FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) { @@ -250,9 +250,9 @@ dGVzdA== FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) { return testACMEOrderValid, nil }, - FakeFinalizeOrder: func(_ context.Context, url string, csr []byte) ([][]byte, error) { + FakeCreateOrderCert: func(_ context.Context, url string, csr []byte, bundle bool) ([][]byte, string, error) { testData := []byte("test") - return [][]byte{testData}, nil + return [][]byte{testData}, "http://testurl", nil }, FakeHTTP01ChallengeResponse: func(s string) (string, error) { // TODO: assert s = "token" diff --git a/pkg/issuer/acme/BUILD.bazel b/pkg/issuer/acme/BUILD.bazel index da3094165..732b07c45 100644 --- a/pkg/issuer/acme/BUILD.bazel +++ b/pkg/issuer/acme/BUILD.bazel @@ -20,12 +20,12 @@ go_library( "//pkg/logs:go_default_library", "//pkg/util/errors:go_default_library", "//pkg/util/pki:go_default_library", - "//third_party/crypto/acme:go_default_library", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/api/errors:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_client_go//listers/core/v1:go_default_library", "@io_k8s_utils//clock:go_default_library", + "@org_golang_x_crypto//acme:go_default_library", ], ) diff --git a/pkg/issuer/acme/setup.go b/pkg/issuer/acme/setup.go index 46cdef04c..790f050b4 100644 --- a/pkg/issuer/acme/setup.go +++ b/pkg/issuer/acme/setup.go @@ -23,6 +23,7 @@ import ( "net/url" "strings" + acmeapi "golang.org/x/crypto/acme" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,7 +36,6 @@ import ( logf "github.com/jetstack/cert-manager/pkg/logs" "github.com/jetstack/cert-manager/pkg/util/errors" "github.com/jetstack/cert-manager/pkg/util/pki" - acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme" ) const ( @@ -228,7 +228,7 @@ func (a *Acme) Setup(ctx context.Context) error { log.Info("verified existing registration with ACME server") apiutil.SetIssuerCondition(a.issuer, v1alpha2.IssuerConditionReady, cmmeta.ConditionTrue, successAccountRegistered, messageAccountRegistered) - a.issuer.GetStatus().ACMEStatus().URI = account.URL + a.issuer.GetStatus().ACMEStatus().URI = account.URI a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail = registeredEmail return nil @@ -253,7 +253,7 @@ func ensureEmailUpToDate(ctx context.Context, cl client.Interface, acc *acmeapi. acc.Contact = emailurl var err error - acc, err = cl.UpdateAccount(ctx, acc) + acc, err = cl.UpdateReg(ctx, acc) if err != nil { return nil, "", err } @@ -271,7 +271,7 @@ func ensureEmailUpToDate(ctx context.Context, cl client.Interface, acc *acmeapi. // due to a not found error it will register a new account with the given key. func (a *Acme) registerAccount(ctx context.Context, cl client.Interface) (*acmeapi.Account, error) { // check if the account already exists - acc, err := cl.GetAccount(ctx) + acc, err := cl.GetReg(ctx, "") if err == nil { return acc, nil } @@ -289,11 +289,10 @@ func (a *Acme) registerAccount(ctx context.Context, cl client.Interface) (*acmea } acc = &acmeapi.Account{ - Contact: emailurl, - TermsAgreed: true, + Contact: emailurl, } - acc, err = cl.CreateAccount(ctx, acc) + acc, err = cl.Register(ctx, acc, acmeapi.AcceptTOS) if err != nil { return nil, err } diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel deleted file mode 100644 index f9b7ac288..000000000 --- a/third_party/BUILD.bazel +++ /dev/null @@ -1,18 +0,0 @@ -licenses(["notice"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//third_party/crypto/acme:all-srcs", - ], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/third_party/crypto/acme/BUILD.bazel b/third_party/crypto/acme/BUILD.bazel deleted file mode 100644 index 531721cc1..000000000 --- a/third_party/crypto/acme/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -licenses(["notice"]) - -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "acme.go", - "jws.go", - "types.go", - ], - importpath = "github.com/jetstack/cert-manager/third_party/crypto/acme", - visibility = ["//visibility:public"], -) - -go_test( - name = "go_default_test", - srcs = [ - "acme_test.go", - "jws_test.go", - "types_test.go", - ], - embed = [":go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/third_party/crypto/acme/OWNERS b/third_party/crypto/acme/OWNERS deleted file mode 100644 index 998846c74..000000000 --- a/third_party/crypto/acme/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -labels: -- area/acme diff --git a/third_party/crypto/acme/acme.go b/third_party/crypto/acme/acme.go deleted file mode 100644 index 34d51cf1d..000000000 --- a/third_party/crypto/acme/acme.go +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package acme provides an implementation of the -// Automatic Certificate Management Environment (ACME) spec. -// See https://tools.ietf.org/html/draft-ietf-acme-acme-09 for details. -// -// Most common scenarios will want to use autocert subdirectory instead, -// which provides automatic access to certificates from Let's Encrypt -// and any other ACME-based CA. -// -// This package is a work in progress and makes no API stability promises. -package acme - -import ( - "bytes" - "context" - "crypto" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "io" - "io/ioutil" - "math/big" - "net/http" - "net/url" - "strconv" - "sync" - "time" -) - -// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA. -const LetsEncryptURL = "https://acme-v02.api.letsencrypt.org/directory" - -const ( - // max length of a certificate chain - maxChainLen = 5 - // max size of a certificate chain response, in bytes - maxChainSize = (1 << 20) * maxChainLen - - // Max number of collected nonces kept in memory. - // Expect usual peak of 1 or 2. - maxNonces = 100 - - // User-Agent, bump the version each time a change is made to the - // handling of API requests. - userAgent = "go-acme/2" -) - -// Client is an ACME client. -// The only required field is Key. An example of creating a client with a new key -// is as follows: -// -// key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// if err != nil { -// log.Fatal(err) -// } -// client := &Client{Key: key} -// -type Client struct { - // Key is the account key used to register with a CA and sign requests. - // Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey. - Key crypto.Signer - - // HTTPClient optionally specifies an HTTP client to use - // instead of http.DefaultClient. - HTTPClient *http.Client - - // DirectoryURL points to the CA directory endpoint. - // If empty, LetsEncryptURL is used. - // Mutating this value after a successful call of Client's Discover method - // will have no effect. - DirectoryURL string - - // UserAgent is an optional string that identifies this client and - // version to the ACME server. It should be set to something like - // "myclient/1.2.3". - UserAgent string - - noncesMu sync.Mutex - nonces map[string]struct{} // nonces collected from previous responses - - urlMu sync.Mutex // urlMu guards writes to dir and accountURL - dir *Directory // cached result of Client's Discover method - accountURL string -} - -// Discover performs ACME server discovery using c.DirectoryURL. -// -// It caches successful result. So, subsequent calls will not result in -// a network round-trip. This also means mutating c.DirectoryURL after successful call -// of this method will have no effect. -func (c *Client) Discover(ctx context.Context) (Directory, error) { - c.urlMu.Lock() - defer c.urlMu.Unlock() - if c.dir != nil { - return *c.dir, nil - } - - dirURL := c.DirectoryURL - if dirURL == "" { - dirURL = LetsEncryptURL - } - res, err := c.get(ctx, dirURL) - if err != nil { - return Directory{}, err - } - defer res.Body.Close() - c.addNonce(res.Header) - if res.StatusCode != http.StatusOK { - return Directory{}, responseError(res) - } - - var v struct { - NewNonce string - NewAccount string - NewOrder string - NewAuthz string - RevokeCert string - KeyChange string - Meta struct { - TermsOfService string - Website string - CAAIdentities []string - ExternalAccountRequired bool - } - } - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return Directory{}, err - } - c.dir = &Directory{ - NewNonceURL: v.NewNonce, - NewAccountURL: v.NewAccount, - NewOrderURL: v.NewOrder, - NewAuthzURL: v.NewAuthz, - RevokeCertURL: v.RevokeCert, - KeyChangeURL: v.KeyChange, - Terms: v.Meta.TermsOfService, - Website: v.Meta.Website, - CAA: v.Meta.CAAIdentities, - ExternalAccountRequired: v.Meta.ExternalAccountRequired, - } - return *c.dir, nil -} - -// CreateOrder creates a new certificate order. The input order argument is not -// modified and can be built using NewOrder. -func (c *Client) CreateOrder(ctx context.Context, order *Order) (*Order, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - req := struct { - Identifiers []wireAuthzID `json:"identifiers"` - NotBefore string `json:"notBefore,omitempty"` - NotAfter string `json:"notAfter,omitempty"` - }{ - Identifiers: make([]wireAuthzID, len(order.Identifiers)), - } - for i, id := range order.Identifiers { - req.Identifiers[i] = wireAuthzID{ - Type: id.Type, - Value: id.Value, - } - } - if !order.NotBefore.IsZero() { - req.NotBefore = order.NotBefore.Format(time.RFC3339) - } - if !order.NotAfter.IsZero() { - req.NotAfter = order.NotAfter.Format(time.RFC3339) - } - - res, err := c.postWithJWSAccount(ctx, c.dir.NewOrderURL, req) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusCreated { - return nil, responseError(res) - } - var v wireOrder - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, err - } - l, err := resolveLocation(c.dir.NewOrderURL, res.Header) - if err != nil { - return nil, err - } - o := v.order(l, "") - - if o.Status == StatusInvalid { - return nil, OrderInvalidError{o} - } - return o, nil -} - -// FinalizeOrder finalizes an order using the Certificate Signing Request csr -// encoded in DER format. If the order has not been fully authorized, -// an OrderPendingError will be returned. -// -// After requesting finalization, FinalizOrder polls the order using WaitOrder -// until it is finalized and then fetches the associated certificate and returns -// it. -// -// Callers are encouraged to parse the returned certificate chain to ensure it -// is valid and has the expected attributes. -func (c *Client) FinalizeOrder(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - req := struct { - CSR string `json:"csr"` - }{ - CSR: base64.RawURLEncoding.EncodeToString(csr), - } - - res, err := c.postWithJWSAccount(ctx, finalizeURL, req) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, responseError(res) - } - var v wireOrder - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, err - } - l, err := resolveLocation(finalizeURL, res.Header) - if err != nil { - return nil, err - } - o := v.order(l, res.Header.Get("Retry-After")) - if o.Status == StatusProcessing || o.Status == StatusPending { - o, err = c.WaitOrder(ctx, o.URL) - if err != nil { - return nil, err - } - } - if o.Status != StatusValid { - return nil, fmt.Errorf("acme: unexpected order status %q", o.Status) - } - - return c.GetCertificate(ctx, o.CertificateURL) -} - -// GetOrder retrieves an order identified by url. -// -// If a caller needs to poll an order until its status is final, -// see the WaitOrder method. -func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - res, err := c.postWithJWSAccount(ctx, url, nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - err = responseError(res) - return nil, err - } - var v wireOrder - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, err - } - return v.order(url, res.Header.Get("Retry-After")), nil -} - -// WaitOrder waits for an order to transition from StatusProcessing to a final -// state (StatusValid/StatusInvalid), it retries the request until the order is -// final, ctx is cancelled by the caller, or an error response is received. -// -// It returns a non-nil Order only if its Status is StatusValid. In all other -// cases WaitOrder returns an error. If the Status is StatusInvalid, the -// returned error will be of type OrderInvalidError. If the status is -// StatusPending, the returned error will be of type OrderPendingError. -func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) { - sleep := timeSleeper(ctx) - for { - o, err := c.GetOrder(ctx, url) - if e, ok := err.(*Error); ok && e.StatusCode >= 500 && e.StatusCode <= 599 { - // retriable 5xx error - if err := sleep(retryAfter(e.Header.Get("Retry-After"))); err != nil { - return nil, err - } - continue - } - if err != nil { - return nil, err - } - switch o.Status { - case StatusValid: - return o, nil - case StatusInvalid: - return nil, OrderInvalidError{o} - case StatusPending: - return nil, OrderPendingError{o} - case StatusProcessing: // continue retry loop - default: - return nil, fmt.Errorf("acme: unexpected order status %q", o.Status) - } - if err := sleep(o.RetryAfter); err != nil { - return nil, err - } - } -} - -// RevokeCert revokes a previously issued certificate cert, provided in DER -// format. -// -// If key is nil, the account must have been used to issue the certificate or -// have valid authorizations for all of the identifiers in the certificate. If -// key is provided, it must be the certificate's private key. -func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { - if _, err := c.Discover(ctx); err != nil { - return err - } - - body := &struct { - Cert string `json:"certificate"` - Reason int `json:"reason"` - }{ - Cert: base64.RawURLEncoding.EncodeToString(cert), - Reason: int(reason), - } - var res *http.Response - var err error - if key == nil { - res, err = c.postWithJWSAccount(ctx, c.dir.RevokeCertURL, body) - } else { - res, err = c.postWithJWSKey(ctx, key, c.dir.RevokeCertURL, body) - } - if err != nil { - return err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return responseError(res) - } - return nil -} - -// CreateAccount creates a new account. It returns the account details from the -// server and does not modify the account argument that it is called with. -func (c *Client) CreateAccount(ctx context.Context, a *Account) (*Account, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - return c.doAccount(ctx, c.dir.NewAccountURL, false, a) -} - -// GetAccount retrieves the account that the client is configured with. -func (c *Client) GetAccount(ctx context.Context) (*Account, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - return c.doAccount(ctx, c.dir.NewAccountURL, true, nil) -} - -// UpdateAccount updates an existing account. It returns an updated account -// copy. The provided account is not modified. -func (c *Client) UpdateAccount(ctx context.Context, a *Account) (*Account, error) { - return c.doAccount(ctx, a.URL, false, a) -} - -// GetAuthorization retrieves an authorization identified by the given URL. -// -// If a caller needs to poll an authorization until its status is final, -// see the WaitAuthorization method. -func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - res, err := c.postWithJWSAccount(ctx, url, nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, responseError(res) - } - var v wireAuthz - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, fmt.Errorf("acme: invalid response: %v", err) - } - return v.authorization(url), nil -} - -// DeactivateAuthorization relinquishes an existing authorization identified by -// the given URL. -// -// If successful, the caller will be required to obtain a new authorization -// before a new certificate for the domain associated with the authorization is -// issued. -// -// It does not revoke existing certificates. -func (c *Client) DeactivateAuthorization(ctx context.Context, url string) error { - if _, err := c.Discover(ctx); err != nil { - return err - } - - res, err := c.postWithJWSAccount(ctx, url, json.RawMessage(`{"status":"deactivated"}`)) - if err != nil { - return err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return responseError(res) - } - return nil -} - -// WaitAuthorization polls an authorization at the given URL -// until it is in one of the final states, StatusValid or StatusInvalid, -// the ACME CA responded with a 4xx error code, or the context is done. -// -// It returns a non-nil Authorization only if its Status is StatusValid. -// In all other cases WaitAuthorization returns an error. -// If the Status is StatusInvalid, StatusDeactivated, or StatusRevoked the -// returned error will be of type AuthorizationError. -func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - sleep := sleeper(ctx) - for { - res, err := c.postWithJWSAccount(ctx, url, nil) - if err != nil { - return nil, err - } - if res.StatusCode >= 400 && res.StatusCode <= 499 { - // Non-retriable error. For instance, Let's Encrypt may return 404 Not Found - // when requesting an expired authorization. - defer res.Body.Close() - return nil, responseError(res) - } - - retry := res.Header.Get("Retry-After") - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { - res.Body.Close() - if err := sleep(retry); err != nil { - return nil, err - } - continue - } - var raw wireAuthz - err = json.NewDecoder(res.Body).Decode(&raw) - res.Body.Close() - if err != nil { - return nil, err - } - switch raw.Status { - case StatusValid: - return raw.authorization(url), nil - case StatusInvalid, StatusDeactivated, StatusRevoked: - return nil, AuthorizationError{raw.authorization(url)} - case StatusPending, StatusProcessing: // fall through to sleep - default: - return nil, fmt.Errorf("acme: unknown authorization status %q", raw.Status) - } - if err := sleep(retry); err != nil { - return nil, err - } - } -} - -// GetChallenge retrieves the current status of a challenge. -// -// A client typically polls a challenge status using this method. -func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - res, err := c.postWithJWSAccount(ctx, url, nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, responseError(res) - } - v := wireChallenge{URL: url} - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, fmt.Errorf("acme: invalid response: %v", err) - } - return v.challenge(), nil -} - -// AcceptChallenge informs the server that the client accepts one of its -// authorization challenges previously obtained with -// CreateOrder/GetAuthorization. -// -// The server will then perform the validation asynchronously. -func (c *Client) AcceptChallenge(ctx context.Context, chal *Challenge) (*Challenge, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - res, err := c.postWithJWSAccount(ctx, chal.URL, json.RawMessage(`{}`)) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, responseError(res) - } - - var v wireChallenge - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, fmt.Errorf("acme: invalid response: %v", err) - } - return v.challenge(), nil -} - -// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response. -// A TXT record containing the returned value must be provisioned under -// "_acme-challenge" name of the domain being validated. -// -// The token argument is a Challenge.Token value. -func (c *Client) DNS01ChallengeRecord(token string) (string, error) { - ka, err := keyAuth(c.Key.Public(), token) - if err != nil { - return "", err - } - b := sha256.Sum256([]byte(ka)) - return base64.RawURLEncoding.EncodeToString(b[:]), nil -} - -// HTTP01ChallengeResponse returns the response for an http-01 challenge. -// Servers should respond with the value to HTTP requests at the URL path -// provided by HTTP01ChallengePath to validate the challenge and prove control -// over a domain name. -// -// The token argument is a Challenge.Token value. -func (c *Client) HTTP01ChallengeResponse(token string) (string, error) { - return keyAuth(c.Key.Public(), token) -} - -// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge -// should be provided by the servers. -// The response value can be obtained with HTTP01ChallengeResponse. -// -// The token argument is a Challenge.Token value. -func (c *Client) HTTP01ChallengePath(token string) string { - return "/.well-known/acme-challenge/" + token -} - -// doAccount creates, updates, and reads accounts. -// -// A non-nil acct argument indicates whether the intention is to mutate data of -// the Account. Only the Contact field can be updated. -func (c *Client) doAccount(ctx context.Context, url string, getExistingWithKey bool, acct *Account) (*Account, error) { - req := struct { - Contact []string `json:"contact,omitempty"` - TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"` - GetExisting bool `json:"onlyReturnExisting,omitempty"` - }{ - GetExisting: getExistingWithKey, - } - var accountURL string - if url != c.dir.NewAccountURL { - accountURL = url - } - if acct != nil { - req.Contact = acct.Contact - req.TermsAgreed = acct.TermsAgreed - } - res, err := c.retryPostJWS(ctx, c.Key, accountURL, url, req) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, responseError(res) - } - - if getExistingWithKey { - l, err := resolveLocation(url, res.Header) - if err != nil { - return nil, err - } - return c.doAccount(ctx, l, false, nil) - } - - var v struct { - Status string - Contact []string - Orders string - } - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, fmt.Errorf("acme: invalid response: %v", err) - } - l, err := resolveLocation(url, res.Header) - if err != nil { - return nil, err - } - a := &Account{ - URL: l, - Status: v.Status, - Contact: v.Contact, - OrdersURL: v.Orders, - } - if a.URL == "" { - a.URL = url - } - c.urlMu.Lock() - defer c.urlMu.Unlock() - c.accountURL = a.URL - return a, nil -} - -// cacheAccount ensures that the account URL is cached and returns it. -func (c *Client) cacheAccountURL(ctx context.Context) (string, error) { - c.urlMu.Lock() - defer c.urlMu.Unlock() - if c.accountURL != "" { - return c.accountURL, nil - } - res, err := c.postWithJWSKey(ctx, c.Key, c.dir.NewAccountURL, json.RawMessage(`{"onlyReturnExisting":true}`)) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", responseError(res) - } - l, err := resolveLocation(c.dir.NewAccountURL, res.Header) - if err != nil { - return "", err - } - c.accountURL = l - return c.accountURL, nil -} - -func (c *Client) postWithJWSKey(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) { - return c.retryPostJWS(ctx, key, "", url, body) -} - -func (c *Client) postWithJWSAccount(ctx context.Context, url string, body interface{}) (*http.Response, error) { - accountURL, err := c.cacheAccountURL(ctx) - if err != nil { - return nil, err - } - return c.retryPostJWS(ctx, c.Key, accountURL, url, body) -} - -// retryPostJWS will retry calls to postJWS if there is a badNonce error, -// clearing the stored nonces after each error. -// If the response was 4XX-5XX, then responseError is called on the body, -// the body is closed, and the error returned. -func (c *Client) retryPostJWS(ctx context.Context, key crypto.Signer, accountURL, url string, body interface{}) (*http.Response, error) { - sleep := sleeper(ctx) - for { - res, err := c.postJWS(ctx, key, accountURL, url, body) - if err != nil { - return nil, err - } - // handle errors 4XX-5XX with responseError - if res.StatusCode >= 400 && res.StatusCode <= 599 { - err := responseError(res) - res.Body.Close() - if ae, ok := err.(*Error); ok && ae.Type == "urn:ietf:params:acme:error:badNonce" { - // clear any nonces that we might've stored that might now be - // considered bad - c.clearNonces() - retry := res.Header.Get("Retry-After") - if err := sleep(retry); err != nil { - return nil, err - } - continue - } - return nil, err - } - return res, nil - } -} - -// postJWS signs the body with the given key and POSTs it to the provided url. -// The body argument must be JSON-serializable. -// The accountURL should be empty for account creation and certificate revocation. -func (c *Client) postJWS(ctx context.Context, key crypto.Signer, accountURL, url string, body interface{}) (*http.Response, error) { - nonce, err := c.popNonce(ctx) - if err != nil { - return nil, err - } - b, err := jwsEncodeJSON(body, key, accountURL, url, nonce) - if err != nil { - return nil, err - } - res, err := c.post(ctx, url, "application/jose+json", bytes.NewReader(b)) - if err != nil { - return nil, err - } - c.addNonce(res.Header) - return res, nil -} - -// popNonce returns a nonce value previously stored with c.addNonce -// or fetches a fresh one. -func (c *Client) popNonce(ctx context.Context) (string, error) { - c.noncesMu.Lock() - defer c.noncesMu.Unlock() - if len(c.nonces) == 0 { - return c.fetchNonce(ctx) - } - var nonce string - for nonce = range c.nonces { - delete(c.nonces, nonce) - break - } - return nonce, nil -} - -// clearNonces clears any stored nonces -func (c *Client) clearNonces() { - c.noncesMu.Lock() - defer c.noncesMu.Unlock() - c.nonces = make(map[string]struct{}) -} - -// addNonce stores a nonce value found in h (if any) for future use. -func (c *Client) addNonce(h http.Header) { - v := nonceFromHeader(h) - if v == "" { - return - } - c.noncesMu.Lock() - defer c.noncesMu.Unlock() - if len(c.nonces) >= maxNonces { - return - } - if c.nonces == nil { - c.nonces = make(map[string]struct{}) - } - c.nonces[v] = struct{}{} -} - -func (c *Client) httpClient() *http.Client { - if c.HTTPClient != nil { - return c.HTTPClient - } - return http.DefaultClient -} - -func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) { - req, err := c.newRequest("GET", urlStr, nil) - if err != nil { - return nil, err - } - return c.do(ctx, req) -} - -func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error) { - req, err := c.newRequest("HEAD", urlStr, nil) - if err != nil { - return nil, err - } - return c.do(ctx, req) -} - -func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.Reader) (*http.Response, error) { - req, err := c.newRequest("POST", urlStr, body) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", contentType) - return c.do(ctx, req) -} - -func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) { - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - ua := userAgent - if c.UserAgent != "" { - ua += " " + c.UserAgent - } - req.Header.Set("User-Agent", ua) - return req, nil -} - -func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) { - res, err := c.httpClient().Do(req.WithContext(ctx)) - if err != nil { - select { - case <-ctx.Done(): - // Prefer the unadorned context error. - // (The acme package had tests assuming this, previously from ctxhttp's - // behavior, predating net/http supporting contexts natively) - // TODO(bradfitz): reconsider this in the future. But for now this - // requires no test updates. - return nil, ctx.Err() - default: - return nil, err - } - } - return res, nil -} - -func (c *Client) fetchNonce(ctx context.Context) (string, error) { - resp, err := c.head(ctx, c.dir.NewNonceURL) - if err != nil { - return "", err - } - defer resp.Body.Close() - nonce := nonceFromHeader(resp.Header) - if nonce == "" { - if resp.StatusCode > 299 { - return "", responseError(resp) - } - return "", errors.New("acme: nonce not found") - } - return nonce, nil -} - -func nonceFromHeader(h http.Header) string { - return h.Get("Replay-Nonce") -} - -func (c *Client) GetCertificate(ctx context.Context, url string) ([][]byte, error) { - if _, err := c.Discover(ctx); err != nil { - return nil, err - } - - res, err := c.postWithJWSAccount(ctx, url, nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - data, err := ioutil.ReadAll(io.LimitReader(res.Body, maxChainSize+1)) - if err != nil { - return nil, fmt.Errorf("acme: error getting certificate: %v", err) - } - if len(data) > maxChainSize { - return nil, errors.New("acme: certificate chain is too big") - } - var chain [][]byte - for { - var p *pem.Block - p, data = pem.Decode(data) - if p == nil { - if len(chain) == 0 { - return nil, errors.New("acme: invalid PEM certificate chain") - } - break - } - if len(chain) == maxChainLen { - return nil, errors.New("acme: certificate chain is too long") - } - if p.Type != "CERTIFICATE" { - return nil, fmt.Errorf("acme: invalid PEM block type %q", p.Type) - } - chain = append(chain, p.Bytes) - } - return chain, nil -} - -// responseError creates an error of Error type from resp. -func responseError(resp *http.Response) error { - // don't care if ReadAll returns an error: - // json.Unmarshal will fail in that case anyway - b, _ := ioutil.ReadAll(resp.Body) - e := &wireError{Status: resp.StatusCode} - if err := json.Unmarshal(b, e); err != nil { - // this is not a regular error response: - // populate detail with anything we received, - // e.Status will already contain HTTP response code value - e.Detail = string(b) - if e.Detail == "" { - e.Detail = resp.Status - } - } - return e.error(resp.Header) -} - -// sleeper returns a function that accepts the Retry-After HTTP header value -// and an increment that's used with backoff to increasingly sleep on -// consecutive calls until the context is done. If the Retry-After header -// cannot be parsed, then backoff is used with a maximum sleep time of 10 -// seconds. -func sleeper(ctx context.Context) func(ra string) error { - sleep := timeSleeper(ctx) - return func(ra string) error { - return sleep(retryAfter(ra)) - } -} - -func timeSleeper(ctx context.Context) func(time.Time) error { - var count int - return func(t time.Time) error { - d := backoff(count, 10*time.Second) - count++ - if !t.IsZero() { - d = t.Sub(timeNow()) - } - wakeup := time.NewTimer(d) - defer wakeup.Stop() - select { - case <-ctx.Done(): - return ctx.Err() - case <-wakeup.C: - return nil - } - } -} - -// retryAfter parses a Retry-After HTTP header value, -// trying to convert v into an int (seconds) or use http.ParseTime otherwise. -func retryAfter(v string) time.Time { - if i, err := strconv.Atoi(v); err == nil { - return timeNow().Add(time.Duration(i) * time.Second) - } - t, err := http.ParseTime(v) - if err != nil { - return time.Time{} - } - return t -} - -// backoff computes a duration after which an n+1 retry iteration should occur -// using truncated exponential backoff algorithm. -// -// The n argument is always bounded between 0 and 30. -// The max argument defines upper bound for the returned value. -func backoff(n int, max time.Duration) time.Duration { - if n < 0 { - n = 0 - } - if n > 30 { - n = 30 - } - var d time.Duration - if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil { - d = time.Duration(x.Int64()) * time.Millisecond - } - d += time.Duration(1< max { - return max - } - return d -} - -// keyAuth generates a key authorization string for a given token. -func keyAuth(pub crypto.PublicKey, token string) (string, error) { - th, err := JWKThumbprint(pub) - if err != nil { - return "", err - } - return fmt.Sprintf("%s.%s", token, th), nil -} - -func resolveLocation(base string, h http.Header) (string, error) { - u, err := url.Parse(base) - if err != nil { - return "", err - } - u, err = u.Parse(h.Get("Location")) - if err != nil { - return "", fmt.Errorf("acme: error parsing Location: %s", err) - } - return u.String(), nil -} - -// timeNow is useful for testing for fixed current time. -var timeNow = time.Now diff --git a/third_party/crypto/acme/acme_test.go b/third_party/crypto/acme/acme_test.go deleted file mode 100644 index 0877b1d11..000000000 --- a/third_party/crypto/acme/acme_test.go +++ /dev/null @@ -1,1159 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package acme - -import ( - "context" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/base64" - "encoding/json" - "encoding/pem" - "fmt" - "io/ioutil" - "math/big" - "net/http" - "net/http/httptest" - "reflect" - "strings" - "testing" - "time" -) - -// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided -// interface. -func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) { - // Decode request - var req struct{ Payload string } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - t.Fatal(err) - } - payload, err := base64.RawURLEncoding.DecodeString(req.Payload) - if err != nil { - t.Fatal(err) - } - err = json.Unmarshal(payload, v) - if err != nil { - t.Fatal(err) - } -} - -type jwsHead struct { - Alg string - Nonce string - JWK map[string]string `json:"jwk"` -} - -func decodeJWSHead(r *http.Request) (*jwsHead, error) { - var req struct{ Protected string } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, err - } - b, err := base64.RawURLEncoding.DecodeString(req.Protected) - if err != nil { - return nil, err - } - var head jwsHead - if err := json.Unmarshal(b, &head); err != nil { - return nil, err - } - return &head, nil -} - -func TestDiscover(t *testing.T) { - const ( - keyChange = "https://example.com/acme/key-change" - newAccount = "https://example.com/acme/new-account" - newNonce = "https://example.com/acme/new-nonce" - newOrder = "https://example.com/acme/new-order" - revokeCert = "https://example.com/acme/revoke-cert" - terms = "https://example.com/acme/terms" - ) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{ - "keyChange": %q, - "newAccount": %q, - "newNonce": %q, - "newOrder": %q, - "revokeCert": %q, - "meta": { - "termsOfService": %q - } - }`, keyChange, newAccount, newNonce, newOrder, revokeCert, terms) - })) - defer ts.Close() - c := Client{DirectoryURL: ts.URL} - dir, err := c.Discover(context.Background()) - if err != nil { - t.Fatal(err) - } - if dir.KeyChangeURL != keyChange { - t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keyChange) - } - if dir.NewAccountURL != newAccount { - t.Errorf("dir.NewAccountURL = %q; want %q", dir.NewAccountURL, newAccount) - } - if dir.NewNonceURL != newNonce { - t.Errorf("dir.NewNonceURL = %q; want %q", dir.NewNonceURL, newNonce) - } - if dir.RevokeCertURL != revokeCert { - t.Errorf("dir.RevokeCertURL = %q; want %q", dir.RevokeCertURL, revokeCert) - } - if dir.Terms != terms { - t.Errorf("dir.Terms = %q; want %q", dir.Terms, terms) - } -} - -func TestCreateAccount(t *testing.T) { - contacts := []string{"mailto:admin@example.com"} - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - var j struct { - Contact []string - TermsOfServiceAgreed bool - } - decodeJWSRequest(t, &j, r) - - if !reflect.DeepEqual(j.Contact, contacts) { - t.Errorf("j.Contact = %v; want %v", j.Contact, contacts) - } - if !j.TermsOfServiceAgreed { - t.Error("j.TermsOfServiceAgreed = false; want true") - } - - w.Header().Set("Location", "https://example.com/acme/account/1") - w.WriteHeader(http.StatusCreated) - b, _ := json.Marshal(contacts) - fmt.Fprintf(w, `{"status":"valid","orders":"https://example.com/acme/orders","contact":%s}`, b) - })) - defer ts.Close() - - c := Client{Key: testKeyEC, dir: &Directory{NewAccountURL: ts.URL, NewNonceURL: ts.URL}} - a := &Account{Contact: contacts, TermsAgreed: true} - var err error - if a, err = c.CreateAccount(context.Background(), a); err != nil { - t.Fatal(err) - } - if a.URL != "https://example.com/acme/account/1" { - t.Errorf("a.URL = %q; want https://example.com/acme/account/1", a.URL) - } - if a.OrdersURL != "https://example.com/acme/orders" { - t.Errorf("a.OrdersURL = %q; want https://example.com/acme/orders", a.OrdersURL) - } - if a.Status != StatusValid { - t.Errorf("a.Status = %q; want valid", a.Status) - } - if !reflect.DeepEqual(a.Contact, contacts) { - t.Errorf("a.Contact = %v; want %v", a.Contact, contacts) - } -} - -func TestUpdateAccount(t *testing.T) { - contacts := []string{"mailto:admin@example.com"} - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - var j struct { - Contact []string - } - decodeJWSRequest(t, &j, r) - - if !reflect.DeepEqual(j.Contact, contacts) { - t.Errorf("j.Contact = %v; want %v", j.Contact, contacts) - } - b, _ := json.Marshal(contacts) - fmt.Fprintf(w, `{"status":"valid","orders":"https://example.com/acme/orders","contact":%s}`, b) - })) - defer ts.Close() - - c := Client{Key: testKeyEC, dir: &Directory{NewNonceURL: ts.URL}} - a := &Account{URL: ts.URL, Contact: contacts} - var err error - if a, err = c.UpdateAccount(context.Background(), a); err != nil { - t.Fatal(err) - } - if a.OrdersURL != "https://example.com/acme/orders" { - t.Errorf("a.OrdersURL = %q; want https://example.com/acme/orders", a.OrdersURL) - } - if a.Status != StatusValid { - t.Errorf("a.Status = %q; want valid", a.Status) - } - if !reflect.DeepEqual(a.Contact, contacts) { - t.Errorf("a.Contact = %v; want %v", a.Contact, contacts) - } - if a.URL != ts.URL { - t.Errorf("a.URL = %q; want %q", a.URL, ts.URL) - } -} - -func TestGetAccount(t *testing.T) { - contacts := []string{"mailto:admin@example.com"} - - var ts *httptest.Server - ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - var req struct { - Existing bool `json:"onlyReturnExisting"` - } - decodeJWSRequest(t, &req, r) - if req.Existing { - w.Header().Set("Location", ts.URL) - w.WriteHeader(http.StatusOK) - return - } - b, _ := json.Marshal(contacts) - fmt.Fprintf(w, `{"status":"valid","orders":"https://example.com/acme/orders","contact":%s}`, b) - })) - defer ts.Close() - - c := Client{Key: testKeyEC, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL}} - a, err := c.GetAccount(context.Background()) - if err != nil { - t.Fatal(err) - } - if a.OrdersURL != "https://example.com/acme/orders" { - t.Errorf("a.OrdersURL = %q; want https://example.com/acme/orders", a.OrdersURL) - } - if a.Status != StatusValid { - t.Errorf("a.Status = %q; want valid", a.Status) - } - if !reflect.DeepEqual(a.Contact, contacts) { - t.Errorf("a.Contact = %v; want %v", a.Contact, contacts) - } - if a.URL != ts.URL { - t.Errorf("a.URL = %q; want %q", a.URL, ts.URL) - } -} - -func TestCreateOrder(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - var j struct { - Identifiers []struct { - Type string - Value string - } - } - decodeJWSRequest(t, &j, r) - - // Test request - if len(j.Identifiers) != 1 { - t.Errorf("len(j.Identifiers) = %d; want 1", len(j.Identifiers)) - } - if j.Identifiers[0].Type != "dns" { - t.Errorf("j.Identifier.Type = %q; want dns", j.Identifiers[0].Type) - } - if j.Identifiers[0].Value != "example.com" { - t.Errorf("j.Identifier.Value = %q; want example.com", j.Identifiers[0].Value) - } - - w.Header().Set("Location", "https://example.com/acme/order/1") - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ - "identifiers": [{"type":"dns","value":"example.com"}], - "status":"pending", - "authorizations":["https://example.com/acme/order/1/1"], - "finalize":"https://example.com/acme/order/1/finalize" - }`) - })) - defer ts.Close() - - cl := Client{Key: testKeyEC, accountURL: "https://example.com/acme/account", dir: &Directory{NewOrderURL: ts.URL, NewNonceURL: ts.URL}} - o, err := cl.CreateOrder(context.Background(), NewOrder("example.com")) - if err != nil { - t.Fatal(err) - } - - if o.URL != "https://example.com/acme/order/1" { - t.Errorf("URL = %q; want https://example.com/acme/order/1", o.URL) - } - if o.Status != "pending" { - t.Errorf("Status = %q; want pending", o.Status) - } - if o.FinalizeURL != "https://example.com/acme/order/1/finalize" { - t.Errorf("FinalizeURL = %q; want https://example.com/acme/order/1/finalize", o.FinalizeURL) - } - - if n := len(o.Identifiers); n != 1 { - t.Fatalf("len(o.Identifiers) = %d; want 1", n) - } - if o.Identifiers[0].Type != "dns" { - t.Errorf("Identifiers[0].Type = %q; want dns", o.Identifiers[0].Type) - } - if o.Identifiers[0].Value != "example.com" { - t.Errorf("Identifiers[0].Value = %q; want example.com", o.Identifiers[0].Value) - } - - if n := len(o.Authorizations); n != 1 { - t.Fatalf("len(o.Authorizations) = %d; want 1", n) - } - if o.Authorizations[0] != "https://example.com/acme/order/1/1" { - t.Errorf("o.Authorizations[0] = %q; https://example.com/acme/order/1/1", o.Authorizations[0]) - } -} - -func TestGetAuthorization(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want GET", r.Method) - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ - "identifier": {"type":"dns","value":"example.com"}, - "status":"pending", - "challenges":[ - { - "type":"http-01", - "status":"pending", - "url":"https://example.com/acme/challenge/publickey/id1", - "token":"token1" - }, - { - "type":"tls-sni-02", - "status":"pending", - "url":"https://example.com/acme/challenge/publickey/id2", - "token":"token2" - } - ] - }`) - })) - defer ts.Close() - - cl := &Client{Key: testKeyEC, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - auth, err := cl.GetAuthorization(context.Background(), ts.URL) - if err != nil { - t.Fatal(err) - } - - if auth.Status != "pending" { - t.Errorf("Status = %q; want pending", auth.Status) - } - if auth.Identifier.Type != "dns" { - t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type) - } - if auth.Identifier.Value != "example.com" { - t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value) - } - - if n := len(auth.Challenges); n != 2 { - t.Fatalf("len(set.Challenges) = %d; want 2", n) - } - - c := auth.Challenges[0] - if c.Type != "http-01" { - t.Errorf("c.Type = %q; want http-01", c.Type) - } - if c.URL != "https://example.com/acme/challenge/publickey/id1" { - t.Errorf("c.URI = %q; want https://example.com/acme/challenge/publickey/id1", c.URL) - } - if c.Token != "token1" { - t.Errorf("c.Token = %q; want token1", c.Token) - } - - c = auth.Challenges[1] - if c.Type != "tls-sni-02" { - t.Errorf("c.Type = %q; want tls-sni-02", c.Type) - } - if c.URL != "https://example.com/acme/challenge/publickey/id2" { - t.Errorf("c.URI = %q; want https://example.com/acme/challenge/publickey/id2", c.URL) - } - if c.Token != "token2" { - t.Errorf("c.Token = %q; want token2", c.Token) - } -} - -func TestWaitAuthorization(t *testing.T) { - var count int - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - count++ - w.Header().Set("Retry-After", "0") - if count > 1 { - fmt.Fprintf(w, `{"status":"valid"}`) - return - } - fmt.Fprintf(w, `{"status":"pending"}`) - })) - defer ts.Close() - - type res struct { - authz *Authorization - err error - } - done := make(chan res) - defer close(done) - go func() { - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - a, err := client.WaitAuthorization(context.Background(), ts.URL) - done <- res{a, err} - }() - - select { - case <-time.After(5 * time.Second): - t.Fatal("WaitAuthz took too long to return") - case res := <-done: - if res.err != nil { - t.Fatalf("res.err = %v", res.err) - } - if res.authz == nil { - t.Fatal("res.authz is nil") - } - } -} - -func TestWaitAuthorizationInvalid(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "nonce") - return - } - fmt.Fprintf(w, `{"status":"invalid"}`) - })) - defer ts.Close() - - res := make(chan error) - defer close(res) - go func() { - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - _, err := client.WaitAuthorization(context.Background(), ts.URL) - res <- err - }() - - select { - case <-time.After(3 * time.Second): - t.Fatal("WaitAuthz took too long to return") - case err := <-res: - if err == nil { - t.Error("err is nil") - } - if _, ok := err.(AuthorizationError); !ok { - t.Errorf("err is %T; want *AuthorizationError: %v", err, err) - } - } -} - -func TestWaitAuthorizationClientError(t *testing.T) { - const code = http.StatusBadRequest - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(code) - })) - defer ts.Close() - - ch := make(chan error, 1) - go func() { - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - _, err := client.WaitAuthorization(context.Background(), ts.URL) - ch <- err - }() - - select { - case <-time.After(3 * time.Second): - t.Fatal("WaitAuthz took too long to return") - case err := <-ch: - res, ok := err.(*Error) - if !ok { - t.Fatalf("err is %v (%T); want a non-nil *Error", err, err) - } - if res.StatusCode != code { - t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code) - } - } -} - -func TestWaitAuthorizationCancel(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Retry-After", "60") - fmt.Fprintf(w, `{"status":"pending"}`) - })) - defer ts.Close() - - res := make(chan error) - defer close(res) - go func() { - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - _, err := client.WaitAuthorization(ctx, ts.URL) - res <- err - }() - - select { - case <-time.After(time.Second): - t.Fatal("WaitAuthz took too long to return") - case err := <-res: - if err == nil { - t.Error("err is nil") - } - } -} - -func TestDeactivateAuthorization(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "nonce") - return - } - switch r.URL.Path { - case "/1": - var req struct { - Status string - } - decodeJWSRequest(t, &req, r) - if req.Status != "deactivated" { - t.Errorf("req.Status = %q; want deactivated", req.Status) - } - case "/2": - w.WriteHeader(http.StatusInternalServerError) - case "/account": - w.Header().Set("Location", "https://example.com/acme/account/0") - w.Write([]byte("{}")) - } - })) - defer ts.Close() - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - ctx := context.Background() - if err := client.DeactivateAuthorization(ctx, ts.URL+"/1"); err != nil { - t.Errorf("err = %v", err) - } - if client.DeactivateAuthorization(ctx, ts.URL+"/2") == nil { - t.Error("nil error") - } -} - -func TestGetChallenge(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want GET", r.Method) - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ - "type":"http-01", - "status":"pending", - "url":"https://example.com/acme/challenge/publickey/id1", - "validated": "2014-12-01T12:05:00Z", - "error": { - "type": "urn:ietf:params:acme:error:malformed", - "detail": "rejected", - "subproblems": [ - { - "type": "urn:ietf:params:acme:error:unknown", - "detail": "invalid", - "identifier": { - "type": "dns", - "value": "_example.com" - } - } - ] - }, - "token":"token1"}`) - })) - defer ts.Close() - - cl := &Client{Key: testKeyEC, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - chall, err := cl.GetChallenge(context.Background(), ts.URL) - if err != nil { - t.Fatal(err) - } - - if chall.Status != "pending" { - t.Errorf("Status = %q; want pending", chall.Status) - } - if chall.Type != "http-01" { - t.Errorf("c.Type = %q; want http-01", chall.Type) - } - if chall.URL != "https://example.com/acme/challenge/publickey/id1" { - t.Errorf("c.URI = %q; want https://example.com/acme/challenge/publickey/id1", chall.URL) - } - if chall.Token != "token1" { - t.Errorf("c.Token = %q; want token1", chall.Token) - } - vt, _ := time.Parse(time.RFC3339, "2014-12-01T12:05:00Z") - if !chall.Validated.Equal(vt) { - t.Errorf("c.Validated = %v; want %v", chall.Validated, vt) - } - e := chall.Error - if e.Type != "urn:ietf:params:acme:error:malformed" { - t.Fatalf("e.Type = %q; want urn:ietf:params:acme:error:malformed", e.Type) - } - if e.Detail != "rejected" { - t.Fatalf("e.Detail = %q; want rejected", e.Detail) - } - if l := len(e.Subproblems); l != 1 { - t.Fatalf("len(e.Subproblems) = %d; want 1", l) - } - p := e.Subproblems[0] - if p.Type != "urn:ietf:params:acme:error:unknown" { - t.Fatalf("p.Type = %q; want urn:ietf:params:acme:error:unknown", p.Type) - } - if p.Detail != "invalid" { - t.Fatalf("p.Detail = %q; want rejected", p.Detail) - } - if p.Identifier.Type != "dns" { - t.Fatalf("p.Identifier.Type = %q; want dns", p.Identifier.Type) - } - if p.Identifier.Value != "_example.com" { - t.Fatalf("p.Identifier.Type = %q; want _example.com", p.Identifier.Value) - } -} - -func TestAcceptChallenge(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - keyAuth := "token1." + testKeyECThumbprint - // Respond to request - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{ - "type":"http-01", - "status":"pending", - "url":"https://example.com/acme/challenge/publickey/id1", - "token":"token1", - "keyAuthorization":%q - }`, keyAuth) - })) - defer ts.Close() - - cl := Client{Key: testKeyEC, accountURL: "https://example.com/acme/account", dir: &Directory{NewNonceURL: ts.URL}} - c, err := cl.AcceptChallenge(context.Background(), &Challenge{ - URL: ts.URL, - Token: "token1", - }) - if err != nil { - t.Fatal(err) - } - - if c.Type != "http-01" { - t.Errorf("c.Type = %q; want http-01", c.Type) - } - if c.URL != "https://example.com/acme/challenge/publickey/id1" { - t.Errorf("c.URL = %q; want https://example.com/acme/challenge/publickey/id1", c.URL) - } - if c.Token != "token1" { - t.Errorf("c.Token = %q; want token1", c.Token) - } -} - -func TestFinalizeOrder(t *testing.T) { - notBefore := time.Now() - notAfter := notBefore.AddDate(0, 2, 0) - timeNow = func() time.Time { return notBefore } - var sampleCert []byte - - var ts *httptest.Server - var orderGets int - ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - if r.URL.Path == "/cert" && r.Method == "POST" { - pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: sampleCert}) - return - } - if r.URL.Path == "/order" { - status := "processing" - if orderGets > 0 { - status = "valid" - } - fmt.Fprintf(w, `{ - "identifiers": [{"type":"dns","value":"example.com"}], - "status":%q, - "authorizations":["https://example.com/acme/order/1/1"], - "finalize":"https://example.com/acme/order/1/finalize", - "certificate":%q - }`, status, ts.URL+"/cert") - orderGets++ - return - } - if r.Method != "POST" { - t.Errorf("r.Method = %q; want POST", r.Method) - } - - var j struct { - CSR string `json:"csr"` - } - decodeJWSRequest(t, &j, r) - - template := x509.Certificate{ - SerialNumber: big.NewInt(int64(1)), - Subject: pkix.Name{ - Organization: []string{"goacme"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - var err error - sampleCert, err = x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC) - if err != nil { - t.Fatalf("Error creating certificate: %v", err) - } - - w.Header().Set("Location", "/order") - fmt.Fprintf(w, `{ - "identifiers": [{"type":"dns","value":"example.com"}], - "status":"processing", - "authorizations":["https://example.com/acme/order/1/1"], - "finalize":"https://example.com/acme/order/1/finalize" - }`) - })) - defer ts.Close() - - csr := x509.CertificateRequest{ - Version: 0, - Subject: pkix.Name{ - CommonName: "example.com", - Organization: []string{"goacme"}, - }, - } - csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC) - if err != nil { - t.Fatal(err) - } - - c := Client{Key: testKeyEC, accountURL: "https://example.com/acme/account", dir: &Directory{NewNonceURL: ts.URL}} - cert, err := c.FinalizeOrder(context.Background(), ts.URL, csrb) - if err != nil { - t.Fatal(err) - } - if cert == nil { - t.Errorf("cert is nil") - } -} - -func TestWaitOrderInvalid(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "nonce") - return - } - const order = `{"status":%q}` - if r.URL.Path == "/invalid" { - fmt.Fprintf(w, order, "invalid") - } - if r.URL.Path == "/pending" { - fmt.Fprintf(w, order, "pending") - } - })) - defer ts.Close() - - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - _, err := client.WaitOrder(context.Background(), ts.URL+"/pending") - if e, ok := err.(OrderPendingError); ok { - if e.Order == nil { - t.Error("order is nil") - } - if e.Order.Status != "pending" { - t.Errorf("status = %q; want pending", e.Order.Status) - } - } else if err != nil { - t.Error(err) - } - - _, err = client.WaitOrder(context.Background(), ts.URL+"/invalid") - if e, ok := err.(OrderInvalidError); ok { - if e.Order == nil { - t.Error("order is nil") - } - if e.Order.Status != "invalid" { - t.Errorf("status = %q; want invalid", e.Order.Status) - } - } else if err != nil { - t.Error(err) - } -} - -func TestGetOrder(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "test-nonce") - return - } - fmt.Fprintf(w, `{ - "identifiers": [{"type":"dns","value":"example.com"}], - "status":"valid", - "authorizations":["https://example.com/acme/order/1/1"], - "finalize":"https://example.com/acme/order/1/finalize", - "certificate":"https://example.com/acme/cert" - }`) - })) - defer ts.Close() - - client := &Client{Key: testKey, dir: &Directory{NewNonceURL: ts.URL, NewAccountURL: ts.URL + "/account"}} - o, err := client.GetOrder(context.Background(), ts.URL) - if err != nil { - t.Fatal(err) - } - if o.URL != ts.URL { - t.Errorf("URL = %q; want %s", o.URL, ts.URL) - } - if o.Status != "valid" { - t.Errorf("Status = %q; want valid", o.Status) - } - if l := len(o.Authorizations); l != 1 { - t.Errorf("len(Authorizations) = %d; want 1", l) - } - if v := o.Authorizations[0]; v != "https://example.com/acme/order/1/1" { - t.Errorf("Authorizations[0] = %q; want https://example.com/acme/order/1/1", v) - } - if l := len(o.Identifiers); l != 1 { - t.Errorf("len(Identifiers) = %d; want 1", l) - } - if v := o.Identifiers[0].Type; v != "dns" { - t.Errorf("Identifiers[0].Type = %q; want dns", v) - } - if v := o.Identifiers[0].Value; v != "example.com" { - t.Errorf("Identifiers[0].Value = %q; want example.com", v) - } - if o.FinalizeURL != "https://example.com/acme/order/1/finalize" { - t.Errorf("FinalizeURL = %q; want https://example.com/acme/order/1/finalize", o.FinalizeURL) - } - if o.CertificateURL != "https://example.com/acme/cert" { - t.Errorf("FinalizeURL = %q; want https://example.com/acme/cert", o.CertificateURL) - } -} - -func TestRevokeCert(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "HEAD" { - w.Header().Set("Replay-Nonce", "nonce") - return - } - - var req struct { - Certificate string - Reason int - } - decodeJWSRequest(t, &req, r) - if req.Reason != 1 { - t.Errorf("req.Reason = %d; want 1", req.Reason) - } - // echo -n cert | base64 | tr -d '=' | tr '/+' '_-' - cert := "Y2VydA" - if req.Certificate != cert { - t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert) - } - })) - defer ts.Close() - client := &Client{Key: testKeyEC, accountURL: "https://example.com/acme/account", dir: &Directory{RevokeCertURL: ts.URL, NewNonceURL: ts.URL}} - ctx := context.Background() - if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil { - t.Fatal(err) - } -} - -func TestNonce_add(t *testing.T) { - var c Client - c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) - c.addNonce(http.Header{"Replay-Nonce": {}}) - c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) - - nonces := map[string]struct{}{"nonce": {}} - if !reflect.DeepEqual(c.nonces, nonces) { - t.Errorf("c.nonces = %q; want %q", c.nonces, nonces) - } -} - -func TestNonce_addMax(t *testing.T) { - c := &Client{nonces: make(map[string]struct{})} - for i := 0; i < maxNonces; i++ { - c.nonces[fmt.Sprintf("%d", i)] = struct{}{} - } - c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) - if n := len(c.nonces); n != maxNonces { - t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces) - } -} - -func TestNonce_fetch(t *testing.T) { - tests := []struct { - code int - nonce string - }{ - {http.StatusOK, "nonce1"}, - {http.StatusBadRequest, "nonce2"}, - {http.StatusOK, ""}, - } - var i int - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "HEAD" { - t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method) - } - w.Header().Set("Replay-Nonce", tests[i].nonce) - w.WriteHeader(tests[i].code) - })) - defer ts.Close() - for ; i < len(tests); i++ { - test := tests[i] - c := &Client{dir: &Directory{NewNonceURL: ts.URL}} - n, err := c.fetchNonce(context.Background()) - if n != test.nonce { - t.Errorf("%d: n=%q; want %q", i, n, test.nonce) - } - switch { - case err == nil && test.nonce == "": - t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err) - case err != nil && test.nonce != "": - t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce) - } - } -} - -func TestNonce_fetchError(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusTooManyRequests) - })) - defer ts.Close() - c := &Client{dir: &Directory{NewNonceURL: ts.URL}} - _, err := c.fetchNonce(context.Background()) - e, ok := err.(*Error) - if !ok { - t.Fatalf("err is %T; want *Error", err) - } - if e.StatusCode != http.StatusTooManyRequests { - t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests) - } -} - -func TestNonce_postJWS(t *testing.T) { - var count int - seen := make(map[string]bool) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - count++ - w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count)) - if r.Method == "HEAD" { - // We expect the client do a HEAD request - // but only to fetch the first nonce. - return - } - // Make client.CreateOrder happy; we're not testing its result. - defer func() { - w.Header().Set("Location", "https://example.com/acme/order/1") - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"status":"valid"}`)) - }() - - head, err := decodeJWSHead(r) - if err != nil { - t.Errorf("decodeJWSHead: %v", err) - return - } - if head.Nonce == "" { - t.Error("head.Nonce is empty") - return - } - if seen[head.Nonce] { - t.Errorf("nonce is already used: %q", head.Nonce) - } - seen[head.Nonce] = true - })) - defer ts.Close() - - client := Client{Key: testKey, accountURL: "https://example.com/acme/account", dir: &Directory{NewOrderURL: ts.URL, NewNonceURL: ts.URL}} - if _, err := client.CreateOrder(context.Background(), NewOrder("example.com")); err != nil { - t.Errorf("client.CreateOrder 1: %v", err) - } - // The second call should not generate another extra HEAD request. - if _, err := client.CreateOrder(context.Background(), NewOrder("example.com")); err != nil { - t.Errorf("client.CreateOrder 2: %v", err) - } - - if count != 3 { - t.Errorf("total requests count: %d; want 3", count) - } - if n := len(client.nonces); n != 1 { - t.Errorf("len(client.nonces) = %d; want 1", n) - } - for k := range seen { - if _, exist := client.nonces[k]; exist { - t.Errorf("used nonce %q in client.nonces", k) - } - } -} - -func TestRetryPostJWS(t *testing.T) { - var count int - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - count++ - w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count)) - if r.Method == "HEAD" { - // We expect the client to do 2 head requests to fetch - // nonces, one to start and another after getting badNonce - return - } - - head, err := decodeJWSHead(r) - if err != nil { - t.Errorf("decodeJWSHead: %v", err) - } else if head.Nonce == "" { - t.Error("head.Nonce is empty") - } else if head.Nonce == "nonce1" { - // return a badNonce error to force the call to retry - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`)) - return - } - // Make client.CreateOrder happy; we're not testing its result. - w.Header().Set("Location", "https://example.com/acme/order/1") - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"status":"valid"}`)) - })) - defer ts.Close() - - client := Client{Key: testKey, accountURL: "https://example.com/acme/account", dir: &Directory{NewOrderURL: ts.URL, NewNonceURL: ts.URL}} - // This call will fail with badNonce, causing a retry - if _, err := client.CreateOrder(context.Background(), NewOrder("example.com")); err != nil { - t.Errorf("client.CreateOrder 1: %v", err) - } - if count != 4 { - t.Errorf("total requests count: %d; want 4", count) - } -} - -func TestErrorResponse(t *testing.T) { - s := `{ - "status": 400, - "type": "urn:acme:error:xxx", - "detail": "text" - }` - res := &http.Response{ - StatusCode: 400, - Status: "400 Bad Request", - Body: ioutil.NopCloser(strings.NewReader(s)), - Header: http.Header{"X-Foo": {"bar"}}, - } - err := responseError(res) - v, ok := err.(*Error) - if !ok { - t.Fatalf("err = %+v (%T); want *Error type", err, err) - } - if v.StatusCode != 400 { - t.Errorf("v.StatusCode = %v; want 400", v.StatusCode) - } - if v.Type != "urn:acme:error:xxx" { - t.Errorf("v.Type = %q; want urn:acme:error:xxx", v.Type) - } - if v.Detail != "text" { - t.Errorf("v.Detail = %q; want text", v.Detail) - } - if !reflect.DeepEqual(v.Header, res.Header) { - t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header) - } -} - -func TestHTTP01Challenge(t *testing.T) { - const ( - token = "xxx" - // thumbprint is precomputed for testKeyEC in jws_test.go - value = token + "." + testKeyECThumbprint - urlpath = "/.well-known/acme-challenge/" + token - ) - client := &Client{Key: testKeyEC} - val, err := client.HTTP01ChallengeResponse(token) - if err != nil { - t.Fatal(err) - } - if val != value { - t.Errorf("val = %q; want %q", val, value) - } - if path := client.HTTP01ChallengePath(token); path != urlpath { - t.Errorf("path = %q; want %q", path, urlpath) - } -} - -func TestDNS01ChallengeRecord(t *testing.T) { - // echo -n xxx. | \ - // openssl dgst -binary -sha256 | \ - // base64 | tr -d '=' | tr '/+' '_-' - const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo" - - client := &Client{Key: testKeyEC} - val, err := client.DNS01ChallengeRecord("xxx") - if err != nil { - t.Fatal(err) - } - if val != value { - t.Errorf("val = %q; want %q", val, value) - } -} - -func TestBackoff(t *testing.T) { - tt := []struct{ min, max time.Duration }{ - {time.Second, 2 * time.Second}, - {2 * time.Second, 3 * time.Second}, - {4 * time.Second, 5 * time.Second}, - {8 * time.Second, 9 * time.Second}, - } - for i, test := range tt { - d := backoff(i, time.Minute) - if d < test.min || test.max < d { - t.Errorf("%d: d = %v; want between %v and %v", i, d, test.min, test.max) - } - } - - min, max := time.Second, 2*time.Second - if d := backoff(-1, time.Minute); d < min || max < d { - t.Errorf("d = %v; want between %v and %v", d, min, max) - } - - bound := 10 * time.Second - if d := backoff(100, bound); d != bound { - t.Errorf("d = %v; want %v", d, bound) - } -} diff --git a/third_party/crypto/acme/integration_test.go b/third_party/crypto/acme/integration_test.go deleted file mode 100644 index 12ca92a5b..000000000 --- a/third_party/crypto/acme/integration_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build integration_test - -package acme_test - -import ( - "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "net" - "net/http" - "os" - "reflect" - "testing" - - "golang.org/x/crypto/acme" -) - -// This test works with Pebble and Let's Encrypt staging. -// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test -tags integration_test -// For Let's Encrypt you'll need a publicly accessible HTTP server like `ngrok http 8080` and then -// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test -tags integration_test -func TestIntegration(t *testing.T) { - dir := os.Getenv("ACME_DIRECTORY_URL") - testAccountGet := os.Getenv("TEST_ACCOUNT_GET") != "" - testRevoke := os.Getenv("TEST_REVOKE") != "" - testHost := os.Getenv("TEST_HOST") - if testHost == "" { - testHost = "localhost:5002" - } - testIdentifier, listenPort, _ := net.SplitHostPort(testHost) - if dir == "" { - t.Fatal("ACME_DIRECTORY_URL is required") - } - - key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - c := &acme.Client{ - Key: key, - DirectoryURL: dir, - HTTPClient: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - }, - } - - a := &acme.Account{ - Contact: []string{"mailto:user@example.com"}, - TermsAgreed: true, - } - na, err := c.CreateAccount(context.Background(), a) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(a.Contact, na.Contact) { - t.Errorf("na.Contact = %q; want %q", na.Contact, a.Contact) - } - if na.URL == "" { - t.Fatal("empty na.URL") - } - - // this endpoint is not supported by pebble, so put it behind a flag - if testAccountGet { - na, err = c.GetAccount(context.Background()) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a.Contact, na.Contact) { - t.Errorf("na.Contact = %q; want %q", na.Contact, a.Contact) - } - } - - order, err := c.CreateOrder(context.Background(), acme.NewOrder(testIdentifier)) - if err != nil { - t.Fatal(err) - } - auth, err := c.GetAuthorization(context.Background(), order.Authorizations[0]) - if err != nil { - t.Fatal(err) - } - - var challenge *acme.Challenge - for _, ch := range auth.Challenges { - if ch.Type == "http-01" { - challenge = ch - break - } - } - if challenge == nil { - t.Fatal("missing http-01 challenge") - } - - l, err := net.Listen("tcp", ":"+listenPort) - if err != nil { - t.Errorf("error listening for challenge: %s", err) - } - defer l.Close() - go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.URL.Path != c.HTTP01ChallengePath(challenge.Token) { - w.WriteHeader(404) - return - } - res, _ := c.HTTP01ChallengeResponse(challenge.Token) - w.Write([]byte(res)) - })) - - _, err = c.AcceptChallenge(context.Background(), challenge) - if err != nil { - t.Fatal(err) - } - - _, err = c.WaitAuthorization(context.Background(), order.Authorizations[0]) - if err != nil { - t.Fatal(err) - } - - certKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - csr, _ := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: []string{testIdentifier}}, certKey) - der, err := c.FinalizeOrder(context.Background(), order.FinalizeURL, csr) - if err != nil { - t.Fatal(err) - } - - cert, err := x509.ParseCertificate(der[0]) - if err != nil { - t.Fatal(err) - } - if cert.DNSNames[0] != testIdentifier { - t.Errorf("unexpected DNSNames %v", cert.DNSNames) - } - - if testRevoke { - if err := c.RevokeCert(context.Background(), certKey, der[0], acme.CRLReasonUnspecified); err != nil { - t.Fatal(err) - } - } -} diff --git a/third_party/crypto/acme/jws.go b/third_party/crypto/acme/jws.go deleted file mode 100644 index 1b514cccb..000000000 --- a/third_party/crypto/acme/jws.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package acme - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - _ "crypto/sha512" // need for EC keys - "encoding/base64" - "encoding/json" - "fmt" - "math/big" -) - -// jwsEncodeJSON signs claimset using provided key and a nonce. -// The result is serialized in JSON format. -// See https://tools.ietf.org/html/rfc7515#section-7. -func jwsEncodeJSON(claimset interface{}, key crypto.Signer, accountURL, url, nonce string) ([]byte, error) { - alg, sha := jwsHasher(key) - if alg == "" || !sha.Available() { - return nil, ErrUnsupportedKey - } - var phead string - if accountURL == "" { - jwk, err := jwkEncode(key.Public()) - if err != nil { - return nil, err - } - phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url) - } else { - phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, accountURL, nonce, url) - } - phead = base64.RawURLEncoding.EncodeToString([]byte(phead)) - payload := "" - if claimset != nil { - cs, err := json.Marshal(claimset) - if err != nil { - return nil, err - } - payload = base64.RawURLEncoding.EncodeToString(cs) - } - hash := sha.New() - hash.Write([]byte(phead + "." + payload)) - sig, err := jwsSign(key, sha, hash.Sum(nil)) - if err != nil { - return nil, err - } - - enc := struct { - Protected string `json:"protected"` - Payload string `json:"payload"` - Sig string `json:"signature"` - }{ - Protected: phead, - Payload: payload, - Sig: base64.RawURLEncoding.EncodeToString(sig), - } - return json.Marshal(&enc) -} - -// jwkEncode encodes public part of an RSA or ECDSA key into a JWK. -// The result is also suitable for creating a JWK thumbprint. -// https://tools.ietf.org/html/rfc7517 -func jwkEncode(pub crypto.PublicKey) (string, error) { - switch pub := pub.(type) { - case *rsa.PublicKey: - // https://tools.ietf.org/html/rfc7518#section-6.3.1 - n := pub.N - e := big.NewInt(int64(pub.E)) - // Field order is important. - // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. - return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, - base64.RawURLEncoding.EncodeToString(e.Bytes()), - base64.RawURLEncoding.EncodeToString(n.Bytes()), - ), nil - case *ecdsa.PublicKey: - // https://tools.ietf.org/html/rfc7518#section-6.2.1 - p := pub.Curve.Params() - n := p.BitSize / 8 - if p.BitSize%8 != 0 { - n++ - } - x := pub.X.Bytes() - if n > len(x) { - x = append(make([]byte, n-len(x)), x...) - } - y := pub.Y.Bytes() - if n > len(y) { - y = append(make([]byte, n-len(y)), y...) - } - // Field order is important. - // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. - return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, - p.Name, - base64.RawURLEncoding.EncodeToString(x), - base64.RawURLEncoding.EncodeToString(y), - ), nil - } - return "", ErrUnsupportedKey -} - -// jwsSign signs the digest using the given key. -// It returns ErrUnsupportedKey if the key type is unknown. -// The hash is used only for RSA keys. -func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) { - switch key := key.(type) { - case *rsa.PrivateKey: - return key.Sign(rand.Reader, digest, hash) - case *ecdsa.PrivateKey: - r, s, err := ecdsa.Sign(rand.Reader, key, digest) - if err != nil { - return nil, err - } - rb, sb := r.Bytes(), s.Bytes() - size := key.Params().BitSize / 8 - if size%8 > 0 { - size++ - } - sig := make([]byte, size*2) - copy(sig[size-len(rb):], rb) - copy(sig[size*2-len(sb):], sb) - return sig, nil - } - return nil, ErrUnsupportedKey -} - -// jwsHasher indicates suitable JWS algorithm name and a hash function -// to use for signing a digest with the provided key. -// It returns ("", 0) if the key is not supported. -func jwsHasher(key crypto.Signer) (string, crypto.Hash) { - switch key := key.(type) { - case *rsa.PrivateKey: - return "RS256", crypto.SHA256 - case *ecdsa.PrivateKey: - switch key.Params().Name { - case "P-256": - return "ES256", crypto.SHA256 - case "P-384": - return "ES384", crypto.SHA384 - case "P-521": - return "ES512", crypto.SHA512 - } - } - return "", 0 -} - -// JWKThumbprint creates a JWK thumbprint out of pub -// as specified in https://tools.ietf.org/html/rfc7638. -func JWKThumbprint(pub crypto.PublicKey) (string, error) { - jwk, err := jwkEncode(pub) - if err != nil { - return "", err - } - b := sha256.Sum256([]byte(jwk)) - return base64.RawURLEncoding.EncodeToString(b[:]), nil -} diff --git a/third_party/crypto/acme/jws_test.go b/third_party/crypto/acme/jws_test.go deleted file mode 100644 index 3d0b08482..000000000 --- a/third_party/crypto/acme/jws_test.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package acme - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/json" - "encoding/pem" - "fmt" - "math/big" - "testing" -) - -const ( - testKeyPEM = ` ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq -WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30 -Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq -EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf -oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy -KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV -9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H -r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm -ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP -G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS -zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6 -9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s -8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc -7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL -qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ -Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU -RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o -JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd -4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt -jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q -YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73 -c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G -N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7 -EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO -9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx ------END RSA PRIVATE KEY----- -` - - // This thumbprint is for the testKey defined above. - testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ" - - // openssl ecparam -name secp256k1 -genkey -noout - testKeyECPEM = ` ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49 -AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5 -QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ== ------END EC PRIVATE KEY----- -` - // openssl ecparam -name secp384r1 -genkey -noout - testKeyEC384PEM = ` ------BEGIN EC PRIVATE KEY----- -MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD -Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj -JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke -WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg= ------END EC PRIVATE KEY----- -` - // openssl ecparam -name secp521r1 -genkey -noout - testKeyEC512PEM = ` ------BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z -KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx -7JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD -FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd -GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ== ------END EC PRIVATE KEY----- -` - // 1. openssl ec -in key.pem -noout -text - // 2. remove first byte, 04 (the header); the rest is X and Y - // 3. convert each with: echo | xxd -r -p | base64 -w 100 | tr -d '=' | tr '/+' '_-' - testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ" - testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk" - testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt" - testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo" - testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY" - testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax" - - // echo -n '{"crv":"P-256","kty":"EC","x":"","y":""}' | \ - // openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-' - testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU" -) - -var ( - testKey *rsa.PrivateKey - testKeyEC *ecdsa.PrivateKey - testKeyEC384 *ecdsa.PrivateKey - testKeyEC512 *ecdsa.PrivateKey -) - -func init() { - testKey = parseRSA(testKeyPEM, "testKeyPEM") - testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM") - testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM") - testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM") -} - -func decodePEM(s, name string) []byte { - d, _ := pem.Decode([]byte(s)) - if d == nil { - panic("no block found in " + name) - } - return d.Bytes -} - -func parseRSA(s, name string) *rsa.PrivateKey { - b := decodePEM(s, name) - k, err := x509.ParsePKCS1PrivateKey(b) - if err != nil { - panic(fmt.Sprintf("%s: %v", name, err)) - } - return k -} - -func parseEC(s, name string) *ecdsa.PrivateKey { - b := decodePEM(s, name) - k, err := x509.ParseECPrivateKey(b) - if err != nil { - panic(fmt.Sprintf("%s: %v", name, err)) - } - return k -} - -func TestJWSEncodeJSON(t *testing.T) { - claims := struct{ Msg string }{"Hello JWS"} - // JWS signed with testKey and "nonce" as the nonce value - // JSON-serialized JWS fields are split for easier testing - const ( - // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"url":"https://example.com","nonce":"nonce"} - protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + - "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + - "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + - "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + - "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + - "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + - "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + - "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + - "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + - "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + - "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6Imh0dHBzOi8vZXhh" + - "bXBsZS5jb20ifQ" - // {"Msg":"Hello JWS"} - payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ" - signature = "EtedUusG_N7NkuHRs9Ios6V0_VZdjYPut8vqRMDHvZQ3kZO0d5-9" + - "BivWINleGajAW29So64s4WYsITx2Y0g3obSw70Xsr8XVsVox2Wsx" + - "RJgd6KBNk1SGzqUW7-yEaS0fs0ax5SHPwpS9ek9WPCZ0MphfUH3d" + - "qK40x6dYbgY9mInfzf7L11QeRrQdJfGuef_74SJGTp6D4B5UrR2w" + - "m-AoSsRXY5A99U7J8YE9LFTg7pUQRSQWqqGZu-U9VDiB8bBvViVH" + - "1abbI5xHaSagDb1avfdIXqYv_QVeMXF67Nis8f963FOdX0zwjpob" + - "mpi-rsSmLBEtUkLERBIU_8JRdkXMcw" - ) - - b, err := jwsEncodeJSON(claims, testKey, "", "https://example.com", "nonce") - if err != nil { - t.Fatal(err) - } - var jws struct{ Protected, Payload, Signature string } - if err := json.Unmarshal(b, &jws); err != nil { - t.Fatal(err) - } - if jws.Protected != protected { - t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) - } - if jws.Payload != payload { - t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) - } - if jws.Signature != signature { - t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature) - } -} - -func TestJWSEncodeJSONEC(t *testing.T) { - tt := []struct { - key *ecdsa.PrivateKey - x, y string - alg, crv string - }{ - {testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"}, - {testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"}, - {testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"}, - } - for i, test := range tt { - claims := struct{ Msg string }{"Hello JWS"} - b, err := jwsEncodeJSON(claims, test.key, "", "https://example.com", "nonce") - if err != nil { - t.Errorf("%d: %v", i, err) - continue - } - var jws struct{ Protected, Payload, Signature string } - if err := json.Unmarshal(b, &jws); err != nil { - t.Errorf("%d: %v", i, err) - continue - } - - b, err = base64.RawURLEncoding.DecodeString(jws.Protected) - if err != nil { - t.Errorf("%d: jws.Protected: %v", i, err) - } - var head struct { - Alg string - Nonce string - JWK struct { - Crv string - Kty string - X string - Y string - } `json:"jwk"` - } - if err := json.Unmarshal(b, &head); err != nil { - t.Errorf("%d: jws.Protected: %v", i, err) - } - if head.Alg != test.alg { - t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg) - } - if head.Nonce != "nonce" { - t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce) - } - if head.JWK.Crv != test.crv { - t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv) - } - if head.JWK.Kty != "EC" { - t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty) - } - if head.JWK.X != test.x { - t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x) - } - if head.JWK.Y != test.y { - t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y) - } - } -} - -func TestJWKThumbprintRSA(t *testing.T) { - // Key example from RFC 7638 - const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" + - "VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" + - "4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" + - "W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" + - "1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" + - "aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" - const base64E = "AQAB" - const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" - - b, err := base64.RawURLEncoding.DecodeString(base64N) - if err != nil { - t.Fatalf("Error parsing example key N: %v", err) - } - n := new(big.Int).SetBytes(b) - - b, err = base64.RawURLEncoding.DecodeString(base64E) - if err != nil { - t.Fatalf("Error parsing example key E: %v", err) - } - e := new(big.Int).SetBytes(b) - - pub := &rsa.PublicKey{N: n, E: int(e.Uint64())} - th, err := JWKThumbprint(pub) - if err != nil { - t.Error(err) - } - if th != expected { - t.Errorf("thumbprint = %q; want %q", th, expected) - } -} - -func TestJWKThumbprintEC(t *testing.T) { - // Key example from RFC 7520 - // expected was computed with - // echo -n '{"crv":"P-521","kty":"EC","x":"","y":""}' | \ - // openssl dgst -binary -sha256 | \ - // base64 | \ - // tr -d '=' | tr '/+' '_-' - const ( - base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" + - "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt" - base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" + - "QkAgDPrwQrJmbnX9cwlGfP-HqHZR1" - expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M" - ) - - b, err := base64.RawURLEncoding.DecodeString(base64X) - if err != nil { - t.Fatalf("Error parsing example key X: %v", err) - } - x := new(big.Int).SetBytes(b) - - b, err = base64.RawURLEncoding.DecodeString(base64Y) - if err != nil { - t.Fatalf("Error parsing example key Y: %v", err) - } - y := new(big.Int).SetBytes(b) - - pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y} - th, err := JWKThumbprint(pub) - if err != nil { - t.Error(err) - } - if th != expected { - t.Errorf("thumbprint = %q; want %q", th, expected) - } -} - -func TestJWKThumbprintErrUnsupportedKey(t *testing.T) { - _, err := JWKThumbprint(struct{}{}) - if err != ErrUnsupportedKey { - t.Errorf("err = %q; want %q", err, ErrUnsupportedKey) - } -} diff --git a/third_party/crypto/acme/types.go b/third_party/crypto/acme/types.go deleted file mode 100644 index dc7a6b829..000000000 --- a/third_party/crypto/acme/types.go +++ /dev/null @@ -1,410 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package acme - -import ( - "errors" - "fmt" - "net/http" - "time" -) - -// ACME server response statuses used to describe Account, Authorization, and Challenge states. -const ( - StatusUnknown = "unknown" - StatusPending = "pending" - StatusProcessing = "processing" - StatusValid = "valid" - StatusInvalid = "invalid" - StatusRevoked = "revoked" - StatusDeactivated = "deactivated" - StatusReady = "ready" -) - -// CRLReasonCode identifies the reason for a certificate revocation. -type CRLReasonCode int - -// CRL reason codes as defined in RFC 5280. -const ( - CRLReasonUnspecified CRLReasonCode = 0 - CRLReasonKeyCompromise CRLReasonCode = 1 - CRLReasonCACompromise CRLReasonCode = 2 - CRLReasonAffiliationChanged CRLReasonCode = 3 - CRLReasonSuperseded CRLReasonCode = 4 - CRLReasonCessationOfOperation CRLReasonCode = 5 - CRLReasonCertificateHold CRLReasonCode = 6 - CRLReasonRemoveFromCRL CRLReasonCode = 8 - CRLReasonPrivilegeWithdrawn CRLReasonCode = 9 - CRLReasonAACompromise CRLReasonCode = 10 -) - -// ErrUnsupportedKey is returned when an unsupported key type is encountered. -var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported") - -// Error is an ACME error as defined in RFC 7807, Problem Details for HTTP APIs. -type Error struct { - // StatusCode is The HTTP status code generated by the origin server. - StatusCode int - - // Type is a URI that identifies the problem type, typically in - // a "urn:ietf:params:acme:error:xxx" form. - Type string - - // Detail is a human-readable explanation specific to this occurrence of the problem. - Detail string - - // Subproblems is an optional list of additional error information, usually - // indicating problems with specific identifiers during authorization. - Subproblems []Subproblem - - // Header is the original server error response headers. - // It may be nil. - Header http.Header -} - -func (e *Error) Error() string { - return fmt.Sprintf("acme: %s: %s", e.Type, e.Detail) -} - -// An Subproblem is additional error detail that is included in an Error, -// usually indicating a problem with a specific identifier during authorization. -type Subproblem struct { - // Type is a URI that identifies the subproblem type, typically in - // "urn:ietf:params:acme:error:xxx" form. - Type string - - // Detail is a human-readable explanation specific to this occurrence of the - // subproblem. - Detail string - - // Identifier optionally indicates the identifier that this subproblem is about. - Identifier *AuthzID -} - -// OrderInvalidError is returned when an order is marked as invalid. -type OrderInvalidError struct { - // Order is the order that is invalid. - Order *Order -} - -func (e OrderInvalidError) Error() string { - if e.Order == nil || e.Order.Error == nil { - return "acme: order is invalid" - } - return fmt.Sprintf("acme: invalid order (%s): %s", e.Order.Error.Type, e.Order.Error.Detail) -} - -// OrderPendingError is returned when an order is still pending after an -// attempted finalization. -type OrderPendingError struct { - // Order is the order that is pending. - Order *Order -} - -func (e OrderPendingError) Error() string { - return "acme: order is pending due to incomplete authorization" -} - -// AuthorizationError is returned when an authorization is marked as invalid. -type AuthorizationError struct { - // Authorization is the authorization that is invalid. - Authorization *Authorization -} - -func (e AuthorizationError) Error() string { - if e.Authorization == nil { - return "acme: authorization is invalid" - } - return fmt.Sprintf("acme: authorization for identifier %s is %s", e.Authorization.Identifier.Value, e.Authorization.Status) -} - -// RateLimit reports whether err represents a rate limit error and -// any Retry-After duration returned by the server. -// -// See the following for more details on rate limiting: -// https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-6.5 -func RateLimit(err error) (time.Time, bool) { - e, ok := err.(*Error) - if !ok || e.Type != "urn:ietf:params:acme:error:rateLimited" { - return time.Time{}, false - } - if e.Header == nil { - return time.Time{}, true - } - return retryAfter(e.Header.Get("Retry-After")), true -} - -// Account is a user account. It is associated with a private key. -type Account struct { - // URL uniquely identifies the account. - URL string - - // Status is the status of the account. Valid values are StatusValid, - // StatusDeactivated, and StatusRevoked. - Status string - - // Contact is a list of URLs that the server can use to contact the client - // for issues related to this account. - Contact []string - - // TermsAgreed indicates agreement with the terms of service. It is not - // modifiable after account creation. - TermsAgreed bool - - // OrdersURL is the URL used to fetch a list of orders submitted by this - // account. - OrdersURL string -} - -// Directory is ACME server discovery data. -type Directory struct { - // NewNonceURL is used to retrieve new nonces. - NewNonceURL string - - // NewAccountURL is used to create new accounts. - NewAccountURL string - - // NewOrderURL is used to create new orders. - NewOrderURL string - - // NewAuthzURL is used to create new authorizations. - NewAuthzURL string - - // RevokeCertURL is used to revoke a certificate. - RevokeCertURL string - - // KeyChangeURL is used to change the account key. - KeyChangeURL string - - // Terms is a URL identifying the current terms of service. - Terms string - - // Website is an HTTP or HTTPS URL locating a website - // providing more information about the ACME server. - Website string - - // CAA consists of lowercase hostname elements, which the ACME server - // recognises as referring to itself for the purposes of CAA record validation - // as defined in RFC6844. - CAA []string - - // ExternalAccountRequired, if true, indicates that the CA requires that all - // new account requests include an ExternalAccountBinding field associating - // the new account with an external account. - ExternalAccountRequired bool -} - -// NewOrder creates a new order with the domains provided, suitable for creating -// a TLS certificate order with CreateOrder. -func NewOrder(domains ...string) *Order { - o := &Order{Identifiers: make([]AuthzID, len(domains))} - for i, d := range domains { - o.Identifiers[i] = AuthzID{ - Type: "dns", - Value: d, - } - } - return o -} - -// An Order represents a request for a certificate and is used to track the -// progress through to issuance. -type Order struct { - // URL uniquely identifies the order. - URL string - - // Status is the status of the order. It will be one of StatusPending, - // StatusReady, StatusProcessing, StatusValid, and StatusInvalid. - Status string - - // Expires is the teimstamp after which the server will consider the order invalid. - Expires time.Time - - // Identifiers is a list of identifiers that the order pertains to. - Identifiers []AuthzID - - // NotBefore is an optional requested value of the notBefore field in the certificate. - NotBefore time.Time - - // NotAfter is an optional requested value of the notAfter field in the certificate. - NotAfter time.Time - - // Error is the error that occurred while processing the order, if any. - Error *Error - - // Authorizations is a list of URLs for authorizations that the client needs - // to complete before the requested certificate can be issued. For - // valid/invalid orders, these are the authorizations that were completed. - Authorizations []string - - // FinalizeURL is the URL that is used to finalize the Order. - FinalizeURL string - - // CertificateURL is the URL for the certificate that has been issued in - // response to this order. - CertificateURL string - - // RetryAfter is the timestamp, if any, to wait for before fetching this - // order again. - RetryAfter time.Time -} - -// A Challenge is a CA challenge for an identifier. -type Challenge struct { - // Type is the challenge type, e.g. "http-01" or "dns-01". - Type string - - // URL is the URL where a challenge response can be posted. - URL string - - // Token is a random value that uniquely identifies the challenge. - Token string - - // Validated is the time at which the server validated this challenge. - Validated time.Time - - // Status identifies the status of this challenge. Valid values are - // StatusPending, StatusValid, and StatusInvalid. - Status string - - // Error indicates the errors that occurred while the server was validating - // this challenge. - Error *Error -} - -// Authorization encodes an authorization response. -type Authorization struct { - // URL uniquely identifies the authorization. - URL string - - // Status is the status of the authorization. Valid values are - // StatusPending, StatusProcessing, StatusValid, StatusInvalid, and - // StatusRevoked. - Status string - - // Identifier is the identifier that the account is authorized to represent. - Identifier AuthzID - - // Wildcard is true if the authorization is for the base domain of a wildcard identifier. - Wildcard bool - - // Expires is the timestamp after which the server will consider this authorization invalid. - Expires time.Time - - // Challenges is the list of challenges that the client can fulfill in order - // to prove posession of the identifier. For valid/invalid authorizations, - // this is the list of challenges that were used. - Challenges []*Challenge -} - -// AuthzID is an identifier that an account is authorized to represent. -type AuthzID struct { - Type string // The type of identifier, e.g. "dns". - Value string // The identifier itself, e.g. "example.org". -} - -type wireAuthzID struct { - Type string `json:"type"` - Value string `json:"value"` -} - -// wireAuthz is ACME JSON representation of Authorization objects. -type wireAuthz struct { - Status string - Challenges []wireChallenge - Expires time.Time - Identifier struct { - Type string - Value string - } - Wildcard bool -} - -func (z *wireAuthz) authorization(url string) *Authorization { - a := &Authorization{ - URL: url, - Status: z.Status, - Expires: z.Expires, - Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value}, - Wildcard: z.Wildcard, - Challenges: make([]*Challenge, len(z.Challenges)), - } - for i, v := range z.Challenges { - a.Challenges[i] = v.challenge() - } - return a -} - -// wireChallenge is ACME JSON challenge representation. -type wireChallenge struct { - URL string - Type string - Token string - Status string - Validated time.Time - Error *Error -} - -func (c *wireChallenge) challenge() *Challenge { - v := &Challenge{ - URL: c.URL, - Type: c.Type, - Token: c.Token, - Status: c.Status, - Validated: c.Validated, - Error: c.Error, - } - if v.Status == "" { - v.Status = StatusUnknown - } - return v -} - -// wireError is a subset of fields of the Problem Details object -// as described in https://tools.ietf.org/html/rfc7807#section-3.1. -type wireError struct { - Status int - Type string - Detail string - Subproblems []Subproblem -} - -func (e *wireError) error(h http.Header) *Error { - return &Error{ - StatusCode: e.Status, - Type: e.Type, - Detail: e.Detail, - Subproblems: e.Subproblems, - Header: h, - } -} - -type wireOrder struct { - Status string - Expires time.Time - Identifiers []AuthzID - NotBefore time.Time - NotAfter time.Time - Error *Error - Authorizations []string - Finalize string - Certificate string -} - -func (o *wireOrder) order(url string, retryHeader string) *Order { - return &Order{ - URL: url, - Status: o.Status, - Expires: o.Expires, - Identifiers: o.Identifiers, - NotBefore: o.NotBefore, - NotAfter: o.NotAfter, - Error: o.Error, - Authorizations: o.Authorizations, - FinalizeURL: o.Finalize, - CertificateURL: o.Certificate, - RetryAfter: retryAfter(retryHeader), - } -} diff --git a/third_party/crypto/acme/types_test.go b/third_party/crypto/acme/types_test.go deleted file mode 100644 index c0564f4ca..000000000 --- a/third_party/crypto/acme/types_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package acme - -import ( - "errors" - "net/http" - "testing" - "time" -) - -func TestRateLimit(t *testing.T) { - now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC) - f := timeNow - defer func() { timeNow = f }() - timeNow = func() time.Time { return now } - - h120, hTime := http.Header{}, http.Header{} - h120.Set("Retry-After", "120") - hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017") - - err1 := &Error{ - Type: "urn:ietf:params:acme:error:nolimit", - Header: h120, - } - err2 := &Error{ - Type: "urn:ietf:params:acme:error:rateLimited", - Header: h120, - } - err3 := &Error{ - Type: "urn:ietf:params:acme:error:rateLimited", - Header: nil, - } - err4 := &Error{ - Type: "urn:ietf:params:acme:error:rateLimited", - Header: hTime, - } - - tt := []struct { - err error - res time.Time - ok bool - }{ - {}, - {err: errors.New("dummy")}, - {err: err1}, - {err: err2, res: now.Add(2 * time.Minute), ok: true}, - {err: err3, ok: true}, - {err: err4, res: now.Add(time.Hour), ok: true}, - } - for i, test := range tt { - res, ok := RateLimit(test.err) - if ok != test.ok { - t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok) - continue - } - if !res.Equal(test.res) { - t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res) - } - } -}