Most important change in this CL is that Client is now able to correctly format and sign requests in KID form with a valid "kid" value. According to the RFC, most requests must include KID field in the protected head of JWS requests. The KID value is the account identity provided by the CA during registration. The KID value is also the Account URL. Hence, the CL is tied to account management. Updates golang/go#21081 Change-Id: I13f51e1fc52db7596eb933b47fa2014beb93c1ab Reviewed-on: https://go-review.googlesource.com/c/crypto/+/191602 Run-TryBot: Alex Vaghin <ddos@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package acme
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// DeactivateReg permanently disables an existing account associated with c.Key.
|
|
// A deactivated account can no longer request certificate issuance or access
|
|
// resources related to the account, such as orders or authorizations.
|
|
//
|
|
// It works only with RFC8555 compliant CAs.
|
|
func (c *Client) DeactivateReg(ctx context.Context) error {
|
|
url := string(c.accountKID(ctx))
|
|
if url == "" {
|
|
return ErrNoAccount
|
|
}
|
|
req := json.RawMessage(`{"status": "deactivated"}`)
|
|
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// registerRFC is quivalent to c.Register but for RFC-compliant CAs.
|
|
// It expects c.Discover to have already been called.
|
|
// TODO: Implement externalAccountBinding.
|
|
func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
|
|
c.cacheMu.Lock() // guard c.kid access
|
|
defer c.cacheMu.Unlock()
|
|
|
|
req := struct {
|
|
TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
|
Contact []string `json:"contact,omitempty"`
|
|
}{
|
|
Contact: acct.Contact,
|
|
}
|
|
if c.dir.Terms != "" {
|
|
req.TermsAgreed = prompt(c.dir.Terms)
|
|
}
|
|
res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(
|
|
http.StatusOK, // account with this key already registered
|
|
http.StatusCreated, // new account created
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
a, err := responseAccount(res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Cache Account URL even if we return an error to the caller.
|
|
// It is by all means a valid and usable "kid" value for future requests.
|
|
c.kid = keyID(a.URI)
|
|
if res.StatusCode == http.StatusOK {
|
|
return nil, ErrAccountAlreadyExists
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// updateGegRFC is equivalent to c.UpdateReg but for RFC-compliant CAs.
|
|
// It expects c.Discover to have already been called.
|
|
func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
|
|
url := string(c.accountKID(ctx))
|
|
if url == "" {
|
|
return nil, ErrNoAccount
|
|
}
|
|
req := struct {
|
|
Contact []string `json:"contact,omitempty"`
|
|
}{
|
|
Contact: a.Contact,
|
|
}
|
|
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
return responseAccount(res)
|
|
}
|
|
|
|
// getGegRFC is equivalent to c.GetReg but for RFC-compliant CAs.
|
|
// It expects c.Discover to have already been called.
|
|
func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
|
|
req := json.RawMessage(`{"onlyReturnExisting": true}`)
|
|
res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(http.StatusOK))
|
|
if e, ok := err.(*Error); ok && e.ProblemType == "urn:ietf:params:acme:error:accountDoesNotExist" {
|
|
return nil, ErrNoAccount
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
return responseAccount(res)
|
|
}
|
|
|
|
func responseAccount(res *http.Response) (*Account, error) {
|
|
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)
|
|
}
|
|
return &Account{
|
|
URI: res.Header.Get("Location"),
|
|
Status: v.Status,
|
|
Contact: v.Contact,
|
|
OrdersURL: v.Orders,
|
|
}, nil
|
|
}
|