Skip to content

Commit f17568a

Browse files
committed
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.
1 parent 2f464d4 commit f17568a

File tree

4 files changed

+61
-1
lines changed

4 files changed

+61
-1
lines changed

cmd/cmd_run.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er
157157
log.Fatal("You did not accept the TOS. Unable to proceed.")
158158
}
159159

160+
if ctx.String("server") == lego.ZeroSSLDirectory {
161+
return client.Registration.RegisterWithZeroSSL(registration.RegisterOptions{TermsOfServiceAgreed: true})
162+
}
163+
160164
if ctx.Bool("eab") {
161165
kid := ctx.String("kid")
162166
hmacEncoded := ctx.String("hmac")

cmd/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
5252
log.Fatalf("Could not create client: %v", err)
5353
}
5454

55-
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") {
55+
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") && config.CADirURL != lego.ZeroSSLDirectory {
5656
log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
5757
}
5858

lego/client_config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const (
3838

3939
// LEDirectoryStaging URL to the Let's Encrypt staging.
4040
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
41+
42+
// ZeroSSLDirectory URL to the ZeroSSL production.
43+
ZeroSSLDirectory = "https://acme.zerossl.com/v2/DV90"
4144
)
4245

4346
type Config struct {

registration/registar.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package registration
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"errors"
7+
"fmt"
8+
"io"
59
"net/http"
10+
"net/url"
611

712
"github.com/go-acme/lego/v4/acme"
813
"github.com/go-acme/lego/v4/acme/api"
@@ -69,6 +74,54 @@ func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
6974
return &Resource{URI: account.Location, Body: account.Account}, nil
7075
}
7176

77+
func createZeroSSLAccount(email string) (string, string, error) {
78+
newAccountURL := "https://api.zerossl.com/acme/eab-credentials-email"
79+
data := struct {
80+
Success bool `json:"success"`
81+
KID string `json:"eab_kid"`
82+
HMAC string `json:"eab_hmac_key"`
83+
}{}
84+
85+
resp, err := http.PostForm(newAccountURL, url.Values{"email": {email}})
86+
if err != nil {
87+
return "", "", fmt.Errorf("sending request: %w", err)
88+
}
89+
defer resp.Body.Close()
90+
91+
// ZeroSSL might return errors as plain-text messages instead of JSON,
92+
// so we buffer the response to be able to return it as error.
93+
var rawResp bytes.Buffer
94+
r := io.TeeReader(io.LimitReader(resp.Body, 10*1024), &rawResp) // Limit response to 10KB
95+
if err := json.NewDecoder(r).Decode(&data); err != nil {
96+
// It is likely not a JSON but a plain-text error message
97+
_, _ = io.ReadAll(r) // read the rest of the body
98+
return "", "", fmt.Errorf("parsing response: %w. Original response:\n%s", err, rawResp.String())
99+
}
100+
101+
if !data.Success {
102+
return "", "", fmt.Errorf("received success=false")
103+
}
104+
return data.KID, data.HMAC, nil
105+
}
106+
107+
// RegisterWithZeroSSL Register the current account to the ZeroSSL server.
108+
func (r *Registrar) RegisterWithZeroSSL(options RegisterOptions) (*Resource, error) {
109+
if r.user.GetEmail() == "" {
110+
return nil, errors.New("acme: cannot register ZeroSSL account without email address")
111+
}
112+
113+
kid, hmac, err := createZeroSSLAccount(r.user.GetEmail())
114+
if err != nil {
115+
return nil, fmt.Errorf("acme: error registering new ZeroSSL account: %w", err)
116+
}
117+
118+
return r.RegisterWithExternalAccountBinding(RegisterEABOptions{
119+
TermsOfServiceAgreed: options.TermsOfServiceAgreed,
120+
Kid: kid,
121+
HmacEncoded: hmac,
122+
})
123+
}
124+
72125
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
73126
func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOptions) (*Resource, error) {
74127
accMsg := acme.Account{

0 commit comments

Comments
 (0)