package cmd import ( "context" "crypto/x509" "encoding/json" "encoding/pem" "fmt" "io" "net/http" "os" "strings" "time" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/registration" "github.com/hashicorp/go-retryablehttp" "github.com/urfave/cli/v2" ) const filePerm os.FileMode = 0o600 // setupClient creates a new client with challenge settings. func setupClient(ctx *cli.Context, account *Account, keyType certcrypto.KeyType) *lego.Client { client := newClient(ctx, account, keyType) setupChallenges(ctx, client) return client } func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, certcrypto.KeyType) { keyType := getKeyType(ctx) privateKey := accountsStorage.GetPrivateKey(keyType) var account *Account if accountsStorage.ExistsAccountFilePath() { account = accountsStorage.LoadAccount(privateKey) } else { account = &Account{Email: accountsStorage.GetEmail(), key: privateKey} } return account, keyType } func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client { config := lego.NewConfig(acc) config.CADirURL = ctx.String(flgServer) config.Certificate = lego.CertificateConfig{ KeyType: keyType, Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second, OverallRequestLimit: ctx.Int(flgOverallRequestLimit), DisableCommonName: ctx.Bool(flgDisableCommonName), } config.UserAgent = getUserAgent(ctx) if ctx.IsSet(flgHTTPTimeout) { config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second } if ctx.Bool(flgTLSSkipVerify) { defaultTransport, ok := config.HTTPClient.Transport.(*http.Transport) if ok { // This is always true because the default client used by the CLI defined the transport. tr := defaultTransport.Clone() tr.TLSClientConfig.InsecureSkipVerify = true config.HTTPClient.Transport = tr } } retryClient := retryablehttp.NewClient() retryClient.RetryMax = 5 retryClient.HTTPClient = config.HTTPClient retryClient.CheckRetry = checkRetry retryClient.Logger = nil if _, v := os.LookupEnv("LEGO_DEBUG_ACME_HTTP_CLIENT"); v { retryClient.Logger = log.Logger } config.HTTPClient = retryClient.StandardClient() client, err := lego.NewClient(config) if err != nil { log.Fatalf("Could not create client: %v", err) } if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) { log.Fatalf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC) } return client } // getKeyType the type from which private keys should be generated. func getKeyType(ctx *cli.Context) certcrypto.KeyType { keyType := ctx.String(flgKeyType) switch strings.ToUpper(keyType) { case "RSA2048": return certcrypto.RSA2048 case "RSA3072": return certcrypto.RSA3072 case "RSA4096": return certcrypto.RSA4096 case "RSA8192": return certcrypto.RSA8192 case "EC256": return certcrypto.EC256 case "EC384": return certcrypto.EC384 } log.Fatalf("Unsupported KeyType: %s", keyType) return "" } func getUserAgent(ctx *cli.Context) string { return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version)) } func createNonExistingFolder(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { return os.MkdirAll(path, 0o700) } else if err != nil { return err } return nil } func readCSRFile(filename string) (*x509.CertificateRequest, error) { bytes, err := os.ReadFile(filename) if err != nil { return nil, err } raw := bytes // see if we can find a PEM-encoded CSR var p *pem.Block rest := bytes for { // decode a PEM block p, rest = pem.Decode(rest) // did we fail? if p == nil { break } // did we get a CSR? if p.Type == "CERTIFICATE REQUEST" || p.Type == "NEW CERTIFICATE REQUEST" { raw = p.Bytes } } // no PEM-encoded CSR // assume we were given a DER-encoded ASN.1 CSR // (if this assumption is wrong, parsing these bytes will fail) return x509.ParseCertificateRequest(raw) } func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { rt, err := retryablehttp.ErrorPropagatedRetryPolicy(ctx, resp, err) if err != nil { return rt, err } if resp == nil { return rt, nil } if resp.StatusCode/100 == 2 { return rt, nil } all, err := io.ReadAll(resp.Body) if err == nil { var errorDetails *acme.ProblemDetails err = json.Unmarshal(all, &errorDetails) if err != nil { return rt, fmt.Errorf("%s %s: %s", resp.Request.Method, resp.Request.URL.Redacted(), string(all)) } switch errorDetails.Type { case acme.BadNonceErr: return false, &acme.NonceError{ ProblemDetails: errorDetails, } case acme.AlreadyReplacedErr: if errorDetails.HTTPStatus == http.StatusConflict { return false, &acme.AlreadyReplacedError{ ProblemDetails: errorDetails, } } default: log.Warnf("retry: %v", errorDetails) return rt, errorDetails } } return rt, nil }