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 <bradfitz@golang.org>
Trust: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
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 @@
// 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 @@
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 @@
}
},
},
+ {
+ 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 @@
challengeTypes []string
url string
roots *x509.CertPool
+ eabRequired bool
mu sync.Mutex
certCount int // number of issued certs
@@ -152,6 +153,15 @@
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 @@
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 @@
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 @@
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)