CVE-2025-54799
Description
Let's Encrypt client and ACME library written in Go (Lego). In versions 4.25.1 and below, the github.com/go-acme/lego/v4/acme/api package (thus the lego library and the lego cli as well) don't enforce HTTPS when talking to CAs as an ACME client. Unlike the http-01 challenge which solves an ACME challenge over unencrypted HTTP, the ACME protocol requires HTTPS when a client communicates with the CA to performs ACME functions. However, the library fails to enforce HTTPS both in the original discover URL (configured by the library user) and in the subsequent addresses returned by the CAs in the directory and order objects. If users input HTTP URLs or CAs misconfigure endpoints, protocol operations occur over HTTP instead of HTTPS. This compromises privacy by exposing request/response details like account and request identifiers to network attackers. This was fixed in version 4.25.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/go-acme/legoGo | <= 4.25.1 | — |
github.com/go-acme/lego/v3Go | <= 4.25.1 | — |
github.com/go-acme/lego/v4Go | < 4.25.2 | 4.25.2 |
Affected products
1Patches
28737b36c8595238454b5f74ffix: enforce HTTPS to the ACME server (#2608)
16 files changed · +161 −107
acme/api/certificate_test.go+6 −7 modified@@ -3,7 +3,6 @@ package api import ( "crypto/rand" "crypto/rsa" - "net/http" "testing" "github.com/go-acme/lego/v4/platform/tester" @@ -74,14 +73,14 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) @@ -91,14 +90,14 @@ func TestCertificateService_Get_issuerRelUp(t *testing.T) { } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true)
acme/api/internal/nonces/nonce_manager_test.go+18 −16 modified@@ -8,35 +8,37 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - time.Sleep(250 * time.Millisecond) - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - t.Cleanup(server.Close) + manager, _ := servermock.NewBuilder( + func(server *httptest.Server) (*Manager, error) { + doer := sender.NewDoer(server.Client(), "lego-test") + + return NewManager(doer, server.URL), nil + }). + Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(250 * time.Millisecond) + + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + + servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) + })). + BuildHTTPS(t) - doer := sender.NewDoer(http.DefaultClient, "lego-test") - j := NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } ch <- true }() go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) }
acme/api/internal/secure/jws_test.go+18 −16 modified@@ -9,35 +9,37 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/nonces" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - time.Sleep(250 * time.Millisecond) - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - t.Cleanup(server.Close) + manager, _ := servermock.NewBuilder( + func(server *httptest.Server) (*nonces.Manager, error) { + doer := sender.NewDoer(server.Client(), "lego-test") + + return nonces.NewManager(doer, server.URL), nil + }). + Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(250 * time.Millisecond) + + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + + servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) + })). + BuildHTTPS(t) - doer := sender.NewDoer(http.DefaultClient, "lego-test") - j := nonces.NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } ch <- true }() go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) }
acme/api/internal/sender/sender.go+27 −0 modified@@ -27,6 +27,8 @@ type Doer struct { // NewDoer Creates a new Doer. func NewDoer(client *http.Client, userAgent string) *Doer { + client.Transport = newHTTPSOnly(client) + return &Doer{ httpClient: client, userAgent: userAgent, @@ -150,3 +152,28 @@ func checkError(req *http.Request, resp *http.Response) error { } return nil } + +type httpsOnly struct { + rt http.RoundTripper +} + +func newHTTPSOnly(client *http.Client) *httpsOnly { + if client.Transport == nil { + return &httpsOnly{rt: http.DefaultTransport} + } + + return &httpsOnly{rt: client.Transport} +} + +// RoundTrip ensure HTTPS is used. +// Each ACME function is accomplished by the client sending a sequence of HTTPS requests to the server [RFC2818], +// carrying JSON messages [RFC8259]. +// Use of HTTPS is REQUIRED. +// https://datatracker.ietf.org/doc/html/rfc8555#section-6.1 +func (r *httpsOnly) RoundTrip(req *http.Request) (*http.Response, error) { + if req.URL.Scheme != "https" { + return nil, fmt.Errorf("HTTPS is required: %s", req.URL) + } + + return r.rt.RoundTrip(req) +}
acme/api/internal/sender/sender_test.go+12 −2 modified@@ -12,13 +12,13 @@ import ( func TestDo_UserAgentOnAllHTTPMethod(t *testing.T) { var ua, method string - server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + server := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ua = r.Header.Get("User-Agent") method = r.Method })) t.Cleanup(server.Close) - doer := NewDoer(http.DefaultClient, "") + doer := NewDoer(server.Client(), "") testCases := []struct { method string @@ -65,3 +65,13 @@ func TestDo_CustomUserAgent(t *testing.T) { } assert.Len(t, strings.Split(ua, " "), 5) } + +func TestDo_failWithHTTP(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) + t.Cleanup(server.Close) + + sender := NewDoer(server.Client(), "test") + + _, err := sender.Post(server.URL, strings.NewReader("data"), "text/plain", nil) + require.ErrorContains(t, err, "HTTPS is required: http://") +}
acme/api/order_test.go+3 −3 modified@@ -22,7 +22,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /newOrder", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { body, err := readSignedBody(req, privateKey) @@ -52,9 +52,9 @@ func TestOrderService_NewWithOptions(t *testing.T) { Replaces: order.Replaces, }).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct {
certificate/certificates_test.go+16 −16 modified@@ -175,14 +175,14 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -209,14 +209,14 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -243,14 +243,14 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -277,21 +277,21 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Add("Link", - fmt.Sprintf(`<http://%s/certificate/1>;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) + fmt.Sprintf(`<https://%s/certificate/1>;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) servermock.RawStringResponse(certResponseMock).ServeHTTP(rw, req) })). Route("/certificate/1", servermock.RawStringResponse(certResponseMock2)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -321,14 +321,14 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
certificate/renewal_test.go+19 −16 modified@@ -43,7 +43,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, servermock.RawStringResponse(`{ "suggestedWindow": { @@ -55,12 +55,12 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { }`). WithHeader("Content-Type", "application/json"). WithHeader("Retry-After", "21600")). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -82,24 +82,23 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { require.NoError(t, err, "Could not generate test key") testCases := []struct { - desc string - httpClient *http.Client - request RenewalInfoRequest - handler http.HandlerFunc + desc string + timeout time.Duration + request RenewalInfoRequest + handler http.HandlerFunc }{ { - desc: "API timeout", - httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms. - request: RenewalInfoRequest{leaf}, + desc: "API timeout", + timeout: 500 * time.Millisecond, // HTTP client that times out after 500ms. + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that takes 2ms to respond. time.Sleep(2 * time.Millisecond) }, }, { - desc: "API error", - httpClient: http.DefaultClient, - request: RenewalInfoRequest{leaf}, + desc: "API error", + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that responds with error instead of renewal info. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) @@ -111,11 +110,15 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, test.handler). - Build(t) + BuildHTTPS(t) - core, err := api.New(test.httpClient, "lego-test", apiURL+"/dir", "", key) + if test.timeout != 0 { + client.Timeout = test.timeout + } + + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
challenge/dns01/dns_challenge_test.go+6 −7 modified@@ -4,7 +4,6 @@ import ( "crypto/rand" "crypto/rsa" "errors" - "net/http" "testing" "time" @@ -32,12 +31,12 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -114,12 +113,12 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -201,12 +200,12 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct {
challenge/http01/http_challenge_test.go+8 −8 modified@@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("", "23457") @@ -100,7 +100,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -123,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - apiURL := tester.MockACMEServer().Build(t) + apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -169,7 +169,7 @@ func TestChallengeUnix(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(httpsClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -188,12 +188,12 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) validate := func(_ *api.Core, _ string, _ acme.Challenge) error { return nil } @@ -371,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { @@ -414,7 +414,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer)
challenge/resolver/solver_manager_test.go+4 −4 modified@@ -37,7 +37,7 @@ func TestValidate(t *testing.T) { privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /chlg", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if err := validateNoBody(privateKey, req); err != nil { @@ -46,7 +46,7 @@ func TestValidate(t *testing.T) { } rw.Header().Set("Link", - fmt.Sprintf(`<http://%s/my-authz>; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) + fmt.Sprintf(`<https://%s/my-authz>; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) st := statuses[0] statuses = statuses[1:] @@ -74,9 +74,9 @@ func TestValidate(t *testing.T) { servermock.JSONEncode(authorization).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct {
challenge/tlsalpn01/tls_alpn_challenge_test.go+6 −7 modified@@ -8,7 +8,6 @@ import ( "crypto/tls" "encoding/asn1" "net" - "net/http" "testing" "github.com/go-acme/lego/v4/acme" @@ -21,7 +20,7 @@ import ( ) func TestChallenge(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) domain := "localhost" port := "24457" @@ -69,7 +68,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -93,12 +92,12 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -123,7 +122,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) domain := "127.0.0.1" port := "24457" @@ -170,7 +169,7 @@ func TestChallengeIPaddress(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(
lego/client_test.go+2 −1 modified@@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -26,6 +26,7 @@ func TestNewClient(t *testing.T) { config := NewConfig(user) config.CADirURL = apiURL + "/dir" + config.HTTPClient = httpsClient client, err := NewClient(config) require.NoError(t, err, "Could not create client")
platform/tester/api.go+1 −1 modified@@ -17,7 +17,7 @@ func MockACMEServer() *servermock.Builder[string] { return server.URL, nil }). Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - serverURL := fmt.Sprintf("http://%s", req.Context().Value(http.LocalAddrContextKey)) + serverURL := fmt.Sprintf("https://%s", req.Context().Value(http.LocalAddrContextKey)) servermock.JSONEncode(acme.Directory{ NewNonceURL: serverURL + "/nonce",
platform/tester/servermock/builder.go+12 −0 modified@@ -70,3 +70,15 @@ func (b *Builder[T]) Build(t *testing.T) T { return client } + +func (b *Builder[T]) BuildHTTPS(t *testing.T) (T, *http.Client) { + t.Helper() + + server := httptest.NewTLSServer(b.mux) + t.Cleanup(server.Close) + + client, err := b.clientBuilder(server) + require.NoError(t, err) + + return client, server.Client() +}
registration/registar_test.go+3 −3 modified@@ -16,15 +16,15 @@ import ( ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("/account", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Location", fmt.Sprintf("http://%s/account", req.Context().Value(http.LocalAddrContextKey))) servermock.JSONEncode(acme.Account{Status: "valid"}).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -35,7 +35,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { privatekey: key, } - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) registrar := NewRegistrar(core, user)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.