From 5352b09029215197cc109b46f0560d05ffab29db Mon Sep 17 00:00:00 2001 From: Ben Burkert Date: Tue, 5 Oct 2021 22:21:35 -0400 Subject: [PATCH] acme/autocert: support External Account Binding (EAB) tokens Support External Account Binding (EAB) tokens to the Manager as defined in RFC 8555, Section 7.3.4. If the ExternalAccountBinding field is set on Manager, pass it into the acme Account during registration. Fixes golang/go#48809 Change-Id: I64c38b05ab577acbde9f526638cc8104d15ff055 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/354189 Reviewed-by: Brad Fitzpatrick Trust: Brad Fitzpatrick Reviewed-by: Filippo Valsorda Run-TryBot: Filippo Valsorda Auto-Submit: Filippo Valsorda TryBot-Result: Gopher Robot --- acme/autocert/autocert.go | 7 +++++- acme/autocert/autocert_test.go | 13 ++++++++++ acme/autocert/internal/acmetest/ca.go | 34 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go index 1858184..0061c28 100644 --- a/acme/autocert/autocert.go +++ b/acme/autocert/autocert.go @@ -170,6 +170,11 @@ type Manager struct { // in the template's ExtraExtensions field as is. ExtraExtensions []pkix.Extension + // ExternalAccountBinding optionally represents an arbitrary binding to an + // account of the CA to which the ACME server is tied. + // See RFC 8555, Section 7.3.4 for more details. + ExternalAccountBinding *acme.ExternalAccountBinding + clientMu sync.Mutex client *acme.Client // initialized by acmeClient method @@ -996,7 +1001,7 @@ func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { if m.Email != "" { contact = []string{"mailto:" + m.Email} } - a := &acme.Account{Contact: contact} + a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding} _, err := client.Register(ctx, a, m.Prompt) if err == nil || isAccountAlreadyExist(err) { m.client = client diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go index 4ae408f..ab7504a 100644 --- a/acme/autocert/autocert_test.go +++ b/acme/autocert/autocert_test.go @@ -394,6 +394,19 @@ func TestGetCertificate(t *testing.T) { } }, }, + { + name: "provideExternalAuth", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + s.ExternalAccountRequired() + + man.ExternalAccountBinding = &acme.ExternalAccountBinding{ + KID: "test-key", + Key: make([]byte, 32), + } + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/acme/autocert/internal/acmetest/ca.go b/acme/autocert/internal/acmetest/ca.go index 8c4c642..fa33987 100644 --- a/acme/autocert/internal/acmetest/ca.go +++ b/acme/autocert/internal/acmetest/ca.go @@ -49,6 +49,7 @@ type CAServer struct { challengeTypes []string url string roots *x509.CertPool + eabRequired bool mu sync.Mutex certCount int // number of issued certs @@ -152,6 +153,15 @@ func (ca *CAServer) Roots() *x509.CertPool { return ca.roots } +// ExternalAccountRequired makes an EAB JWS required for account registration. +func (ca *CAServer) ExternalAccountRequired() *CAServer { + if ca.url != "" { + panic("ExternalAccountRequired must be called before Start") + } + ca.eabRequired = true + return ca +} + // Start starts serving requests. The server address becomes available in the // URL field. func (ca *CAServer) Start() *CAServer { @@ -224,6 +234,12 @@ type discovery struct { NewAccount string `json:"newAccount"` NewOrder string `json:"newOrder"` NewAuthz string `json:"newAuthz"` + + Meta discoveryMeta `json:"meta,omitempty"` +} + +type discoveryMeta struct { + ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"` } type challenge struct { @@ -264,6 +280,9 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) { NewNonce: ca.serverURL("/new-nonce"), NewAccount: ca.serverURL("/new-account"), NewOrder: ca.serverURL("/new-order"), + Meta: discoveryMeta{ + ExternalAccountRequired: ca.eabRequired, + }, } if err := json.NewEncoder(w).Encode(resp); err != nil { panic(fmt.Sprintf("discovery response: %v", err)) @@ -283,6 +302,21 @@ func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) { return } ca.acctRegistered = true + + var req struct { + ExternalAccountBinding json.RawMessage + } + + if err := decodePayload(&req, r.Body); err != nil { + ca.httpErrorf(w, http.StatusBadRequest, err.Error()) + return + } + + if ca.eabRequired && len(req.ExternalAccountBinding) == 0 { + ca.httpErrorf(w, http.StatusBadRequest, "registration failed: no JWS for EAB") + return + } + // TODO: Check the user account key against a ca.accountKeys? w.Header().Set("Location", ca.serverURL("/accounts/1")) w.WriteHeader(http.StatusCreated)