Add External Account Binding support. (#516)
* Second draft of External Account Binding support with xenolf's proposed changes included. * Require --eab if the ACME directory says it requires External Account Binding. * Inner EAB JWS should not contain any nonce. Ref: https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.7.3.5
This commit is contained in:
parent
8a990209a9
commit
5115a955b2
5 changed files with 128 additions and 7 deletions
|
@ -149,6 +149,11 @@ func (c *Client) GetToSURL() string {
|
|||
return c.directory.Meta.TermsOfService
|
||||
}
|
||||
|
||||
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
|
||||
func (c *Client) GetExternalAccountRequired() bool {
|
||||
return c.directory.Meta.ExternalAccountRequired
|
||||
}
|
||||
|
||||
// Register the current account to the ACME server.
|
||||
func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
|
@ -183,6 +188,55 @@ func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
|
|||
return reg, nil
|
||||
}
|
||||
|
||||
// Register the current account to the ACME server.
|
||||
func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
return nil, errors.New("acme: cannot register a nil client or user")
|
||||
}
|
||||
logf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail())
|
||||
|
||||
accMsg := accountMessage{}
|
||||
if c.user.GetEmail() != "" {
|
||||
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
||||
} else {
|
||||
accMsg.Contact = []string{}
|
||||
}
|
||||
accMsg.TermsOfServiceAgreed = tosAgreed
|
||||
|
||||
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error())
|
||||
}
|
||||
|
||||
eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error())
|
||||
}
|
||||
|
||||
eabPayload := eabJWS.FullSerialize()
|
||||
|
||||
accMsg.ExternalAccountBinding = []byte(eabPayload)
|
||||
|
||||
var serverReg accountMessage
|
||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
||||
if err != nil {
|
||||
remoteErr, ok := err.(RemoteError)
|
||||
if ok && remoteErr.StatusCode == 409 {
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
reg := &RegistrationResource{
|
||||
URI: hdr.Get("Location"),
|
||||
Body: serverReg,
|
||||
}
|
||||
c.jws.kid = reg.URI
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
|
||||
// ResolveAccountByKey will attempt to look up an account using the given account key
|
||||
// and return its registration resource.
|
||||
func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
|
||||
|
|
|
@ -87,6 +87,35 @@ func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, e
|
|||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
||||
jwk := jose.JSONWebKey{Key: j.privKey}
|
||||
jwkJSON, err := jwk.Public().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error())
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(
|
||||
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
||||
&jose.SignerOptions{
|
||||
EmbedJWK: false,
|
||||
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||
"kid": kid,
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create External Account Binding jose signer -> %s", err.Error())
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(jwkJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to External Account Binding sign content -> %s", err.Error())
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
if nonce, ok := j.nonces.Pop(); ok {
|
||||
return nonce, nil
|
||||
|
|
|
@ -2,6 +2,7 @@ package acme
|
|||
|
||||
import (
|
||||
"time"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// RegistrationResource represents all important informations about a registration
|
||||
|
@ -26,11 +27,12 @@ type directory struct {
|
|||
}
|
||||
|
||||
type accountMessage struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Orders string `json:"orders,omitempty"`
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Orders string `json:"orders,omitempty"`
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
||||
}
|
||||
|
||||
type orderResource struct {
|
||||
|
|
12
cli.go
12
cli.go
|
@ -128,6 +128,18 @@ func main() {
|
|||
Name: "accept-tos, a",
|
||||
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "eab",
|
||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "kid",
|
||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "hmac",
|
||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key-type, k",
|
||||
Value: "rsa2048",
|
||||
|
|
|
@ -122,6 +122,10 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
|
|||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01})
|
||||
}
|
||||
|
||||
if client.GetExternalAccountRequired() && !c.GlobalIsSet("eab") {
|
||||
logger().Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
|
||||
}
|
||||
|
||||
return conf, acc, client
|
||||
}
|
||||
|
||||
|
@ -241,6 +245,8 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) {
|
|||
}
|
||||
|
||||
func run(c *cli.Context) error {
|
||||
var err error
|
||||
|
||||
conf, acc, client := setup(c)
|
||||
if acc.Registration == nil {
|
||||
accepted := handleTOS(c, client)
|
||||
|
@ -248,7 +254,25 @@ func run(c *cli.Context) error {
|
|||
logger().Fatal("You did not accept the TOS. Unable to proceed.")
|
||||
}
|
||||
|
||||
reg, err := client.Register(accepted)
|
||||
var reg *acme.RegistrationResource
|
||||
|
||||
if c.GlobalBool("eab") {
|
||||
kid := c.GlobalString("kid")
|
||||
hmacEncoded := c.GlobalString("hmac")
|
||||
|
||||
if kid == "" || hmacEncoded == "" {
|
||||
logger().Fatalf("Requires arguments --kid and --hmac.")
|
||||
}
|
||||
|
||||
reg, err = client.RegisterWithExternalAccountBinding(
|
||||
accepted,
|
||||
kid,
|
||||
hmacEncoded,
|
||||
)
|
||||
} else {
|
||||
reg, err = client.Register(accepted)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger().Fatalf("Could not complete registration\n\t%s", err.Error())
|
||||
}
|
||||
|
@ -301,7 +325,7 @@ func run(c *cli.Context) error {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := checkFolder(conf.CertPath()); err != nil {
|
||||
if err = checkFolder(conf.CertPath()); err != nil {
|
||||
logger().Fatalf("Could not check/create path: %s", err.Error())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue