Skip to content

Commit

Permalink
acme/autocert: support External Account Binding (EAB) tokens
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
benburkert authored and maisem committed Nov 15, 2022
1 parent ff67fc4 commit c6e9c48
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 1 deletion.
7 changes: 6 additions & 1 deletion acme/autocert/autocert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions acme/autocert/autocert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
34 changes: 34 additions & 0 deletions acme/autocert/internal/acmetest/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down

0 comments on commit c6e9c48

Please sign in to comment.