Switch to using upstream golang.org/x/crypto

Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
James Munnelly
2019-12-03 16:49:31 +00:00
parent ba354e4078
commit 641fe0da7c
29 changed files with 145 additions and 3450 deletions
-1
View File
@@ -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"],
+1 -1
View File
@@ -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
+2
View File
@@ -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=
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -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",
],
)
+3 -2
View File
@@ -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}
+1 -1
View File
@@ -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",
],
)
+38 -36
View File
@@ -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")
}
+8 -8
View File
@@ -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{}
+1 -1
View File
@@ -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",
],
)
+17 -15
View File
@@ -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)
}
+2 -2
View File
@@ -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",
],
)
+19 -23
View File
@@ -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
+21 -29
View File
@@ -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",
},
},
}
+2 -2
View File
@@ -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",
],
)
+11 -14
View File
@@ -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")
+9 -9
View File
@@ -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"
+1 -1
View File
@@ -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",
],
)
+6 -7
View File
@@ -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
}
-18
View File
@@ -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"],
)
-38
View File
@@ -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"],
)
-2
View File
@@ -1,2 +0,0 @@
labels:
- area/acme
-978
View File
@@ -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<<uint(n)) * time.Second
if d > 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
File diff suppressed because it is too large Load Diff
-146
View File
@@ -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)
}
}
}
-161
View File
@@ -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
}
-320
View File
@@ -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 <val> | 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":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
// 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":"<base64X>","y":"<base64Y>"}' | \
// 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)
}
}
-410
View File
@@ -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),
}
}
-63
View File
@@ -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)
}
}
}