1
0
mirror of https://github.com/go-acme/lego.git synced 2025-01-20 13:15:32 +02:00

Add support for ZeroSSL account registration

This commit extends lego library and cli tool to support issuing
certificates from ZeroSSL without having to manually create an account.

Without this commit ZeroSSL can be used but users need to manually
create ZeroSSL account and start `lego` in EAB (External Account
Binding) mode.

From the `lego` cli tool perspective this commit:

Detects if `lego` ir running with ZeroSSL ACME directory `--server
https://acme.zerossl.com/v2/DV90` and uses ZeroSSL API to issue keys for
EAB. There is no need to provide `--eab`, `--kid`, `--hmac` values
anymore.

From the library perspective this commit:

Creates new method `RegisterWithZeroSSL()` in the `registration`
package which takes care of creating ZeroSSL account with a given email.
Internally it re-uses `RegisterWithExternalAccountBinding()` method
after KID and HMAC are retrieved from ZeroSSL registration endpoint.
This commit is contained in:
Julius Kriukas 2021-10-02 14:07:16 +03:00
parent 2f464d47d6
commit 8164e09cb6
4 changed files with 61 additions and 1 deletions

View File

@ -157,6 +157,10 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er
log.Fatal("You did not accept the TOS. Unable to proceed.")
}
if ctx.String("server") == lego.ZeroSSLDirectory {
return client.Registration.RegisterWithZeroSSL(registration.RegisterOptions{TermsOfServiceAgreed: true})
}
if ctx.Bool("eab") {
kid := ctx.String("kid")
hmacEncoded := ctx.String("hmac")

View File

@ -52,7 +52,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
log.Fatalf("Could not create client: %v", err)
}
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") {
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") && config.CADirURL != lego.ZeroSSLDirectory {
log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
}

View File

@ -38,6 +38,9 @@ const (
// LEDirectoryStaging URL to the Let's Encrypt staging.
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
// ZeroSSLDirectory URL to the ZeroSSL production.
ZeroSSLDirectory = "https://acme.zerossl.com/v2/DV90"
)
type Config struct {

View File

@ -1,8 +1,13 @@
package registration
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
@ -69,6 +74,54 @@ func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
return &Resource{URI: account.Location, Body: account.Account}, nil
}
func createZeroSSLAccount(email string) (string, string, error) {
newAccountURL := "https://api.zerossl.com/acme/eab-credentials-email"
data := struct {
Success bool `json:"success"`
KID string `json:"eab_kid"`
HMAC string `json:"eab_hmac_key"`
}{}
resp, err := http.PostForm(newAccountURL, url.Values{"email": {email}})
if err != nil {
return "", "", fmt.Errorf("sending request: %w", err)
}
defer resp.Body.Close()
// ZeroSSL might return errors as plain-text messages instead of JSON,
// so we buffer the response to be able to return it as error.
var rawResp bytes.Buffer
r := io.TeeReader(io.LimitReader(resp.Body, 10*1024), &rawResp) // Limit response to 10KB
if err := json.NewDecoder(r).Decode(&data); err != nil {
// It is likely not a JSON but a plain-text error message
_, _ = io.ReadAll(r) // read the rest of the body
return "", "", fmt.Errorf("parsing response: %w. Original response:\n%s", err, rawResp.String())
}
if !data.Success {
return "", "", errors.New("received success=false")
}
return data.KID, data.HMAC, nil
}
// RegisterWithZeroSSL Register the current account to the ZeroSSL server.
func (r *Registrar) RegisterWithZeroSSL(options RegisterOptions) (*Resource, error) {
if r.user.GetEmail() == "" {
return nil, errors.New("acme: cannot register ZeroSSL account without email address")
}
kid, hmac, err := createZeroSSLAccount(r.user.GetEmail())
if err != nil {
return nil, fmt.Errorf("acme: error registering new ZeroSSL account: %w", err)
}
return r.RegisterWithExternalAccountBinding(RegisterEABOptions{
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
Kid: kid,
HmacEncoded: hmac,
})
}
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
accMsg := acme.Account{