Skip to content

🔥 feat: Externalize TLS configuration from app.Listen()#4019

Closed
simonnix wants to merge 1 commit intogofiber:mainfrom
simonnix:tls-providers
Closed

🔥 feat: Externalize TLS configuration from app.Listen()#4019
simonnix wants to merge 1 commit intogofiber:mainfrom
simonnix:tls-providers

Conversation

@simonnix
Copy link

Summary

  • Externalize *tls.Config object creation logic from app.Listen() using two interfaces and easy to use implementations.

  • Add a similar interface and easy to use implementation for configuring *tls.Config object for client.

Problem

Currently TLS configuration is strictly done from the parameters CertFile and CertKeyFile (v2/v3) or from AutoCertManager (v3) in ListenConfig inside app.Listen()

Mutual TLS is also strictly configured using CertClientFile (with CertFile and CertKeyFile only).

Any other use cases, like using configuration files (containing the certificate and key) or manually connecting to a configuration manager (like Hashicorp Vault) and not relying on files present on host, are complicated. You have to use App.Listener() which does not support preforking.

Having a certificate's key in a file means it can be included in system backups, and otherwise, easily retrieved.

Solution

This feature provides a way to externalize the TLS configuration from app.Listen(), with two interfaces references in ListenConfig() :

  1. ServerTLSProvider which defines a function that potentially returns a *tls.Config object or an error.
  2. ServerTLSCustomizer which defines a function to add functionnality to an existing *tls.Config object, like MutualTLS.

Implementations for the first are provided in the fiber package to provide similar current functionalities :

  • ServerCertificateProvider : Loading certificate and key from file, but now also from string. It can use a single parameter (certficate and key can be in the same file) or use the current CertFile and CertKeyFile parameters (marked as deprecated).
  • ACMECertificateProvider : Loading certificate from an autocert.Manager.

Implementations for the second are provided in the new fiber/addon/verifypeer package which provides 3 implementations for :

  • Mutual TLS.
  • Mutual TLS with CRL check (from file, string or in a URL provided)
  • mutual TLS with OCSP Stapling (using a URL provided).

Additionally this feature adds a similar interface ClientTLSProvider and easy to use implementation to configure a *tls.Config object for client use in the client package. Its usage has been added to the "Mutual TLS with*" implementations and also could be used in Storage providers instead of a *tls.Config. It supports :

  • Loading a certificate/key from a file or string
  • Loading Root certificates
  • Loading system Root certificates
  • Setting TLS minimum version

A SetTLSProvider function has been added to Client to use this interface instead of the previous function SetCertificates(), SetRootCertificate() and SetRootCertificateFromString.

Finally new certificates have been added, from an OpenSSL generated PKI :

  • Root CA Certificate
    • Expires in 30 years
    • An index.txt file (signed certificate status list)
    • Intermediate CA Certificate
      • signed by Root CA (expires in 20 years)
      • An index.txt file (signed certificate status list)
      • Certificate Revocation List (v2, in PEM format)
      • Test certificates (expired in 10 years) :
        • server ( includes a SNI for 'localhost' )
        • client
        • revoked
        • expired

Each test certificates have the corresponding files :

  • cert.pem (certificate)
  • key.pem (certificate's key, not encrypted)
  • fullchain.pem (certificate, key and Intermediate CA certificate)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds pluggable TLS provider/customizer APIs for server and client, implements ACME/server certificate and client certificate providers, and introduces VerifyPeer addon features (MutualTLS, CRL, OCSP) with tests, an OCSP responder test harness, PKI testdata, and documentation updates.

Changes

Cohort / File(s) Summary
Server TLS providers & tests
tls_provider.go, tls_provider_test.go
Add ServerTLSProvider and ServerTLSCustomizer interfaces; implement ACMECertificateProvider and ServerCertificateProvider; validate TLS versions and add provider tests.
Listen integration
listen.go, listen_test.go
Replace file-based CertFile/CertKeyFile/CertClientFile/AutoCertManager/TLSMinVersion with TLSProvider and TLSCustomizer on ListenConfig; Listen now obtains *tls.Config from provider and applies optional customizer; tests adjusted.
Client TLS provider & client changes
client/tls_provider.go, client/tls_provider_test.go, client/client.go, client/client_test.go, docs/client/rest.md
Introduce ClientTLSProvider and ClientCertificateProvider; remove legacy SetCertificates/SetRootCertificate* APIs; add SetTLSProvider; update client TLS flow and tests.
VerifyPeer: Mutual TLS
addon/verifypeer/cert.go, addon/verifypeer/cert_test.go, docs/addon/verifypeer.md
Add MutualTLSCustomizer to require and verify client certificates (supports PEM or file); include tests and docs.
VerifyPeer: CRL support
addon/verifypeer/crl.go, addon/verifypeer/crl_test.go
Add MutualTLSWithCRLCustomizer to load CRL from PEM/file/URL, validate CRL signature, and reject revoked certs; tests for file/content/HTTP sources.
VerifyPeer: OCSP & responder tests
addon/verifypeer/ocsp.go, addon/verifypeer/ocsp_test.go, addon/verifypeer/ocsp_responder_test.go
Add MutualTLSWithOCSPCustomizer for live OCSP checks (http/https, optional override); add OCSP responder test harness and tests for good/revoked cases.
VerifyPeer integration & listen tests
addon/verifypeer/listen_test.go
Integration tests covering mutual TLS startup/shutdown, prefork scenarios, and legacy paths.
PKI testdata
.github/testdata/pki/index.txt, .github/testdata/pki/intermediate/index.txt
Add index entries (valid, revoked, expired) used by OCSP/CRL tests.
Docs & whats_new
docs/api/fiber.md, docs/whats_new.md, docs/addon/verifypeer.md
Document new Server/Client TLS provider/customizer interfaces, provider usage examples, and migration guidance.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant TLS_Provider as TLS Provider
  participant TLS_Customizer as TLS Customizer
  participant OCSP_CRL as OCSP/CRL Endpoint

  Client->>Server: Start TLS handshake (may present client cert)
  Server->>TLS_Provider: ProvideServerTLS()
  TLS_Provider-->>Server: *tls.Config
  Server->>TLS_Customizer: CustomizeServerTLS(config)
  TLS_Customizer-->>Server: installs VerifyPeerCertificate callback
  Server->>Client: Continue TLS handshake
  TLS_Customizer->>OCSP_CRL: Fetch/verify OCSP or CRL (http/https/file)
  OCSP_CRL-->>TLS_Customizer: OCSP/CRL response
  TLS_Customizer-->>Server: validation result (accept/reject)
  Server-->>Client: handshake success or failure
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 I hop with a cert and a tune,
Providers hum beneath the moon.
OCSP and CRL I nibble through,
Clients trusted, servers true.
Securely I bound and soon.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is comprehensive and well-structured, covering the problem statement, solution overview, and detailed implementation for both server-side and client-side TLS configuration. However, the description template requires specific checklist items (Benchmarks, Documentation Update, Changelog/What's New, Migration Guide, etc.) to be explicitly addressed, and most are not checked or filled in. Complete the required checklist items from the description template: explicitly check or address Benchmarks, Documentation Update, Changelog/What's New, Migration Guide, API Alignment, API Longevity, and Examples sections with concrete details or confirmations.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: externalizing TLS configuration from app.Listen() via a provider-based approach, which is the main focus of this comprehensive feature PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ReneWerner87 ReneWerner87 added this to v3 Jan 19, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jan 19, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @simonnix, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the TLS configuration mechanisms within the Fiber framework, providing a more modular and extensible approach for both server and client-side secure communication. By introducing interface-based providers and customizers, it moves away from rigid, hardcoded TLS settings, enabling developers to dynamically manage certificates, implement advanced client verification methods like CRL and OCSP, and integrate with various certificate sources more seamlessly. This change enhances the security and flexibility of TLS handling across the application.

Highlights

  • Externalized TLS Configuration: The core TLS configuration logic for ListenConfig has been externalized into two new interfaces: ServerTLSProvider for creating *tls.Config objects and ServerTLSCustomizer for modifying existing *tls.Config objects. This allows for greater flexibility and customizability.
  • New Server TLS Providers: Introduced ServerCertificateProvider for loading server certificates and keys from files or strings, and ACMECertificateProvider for integrating with autocert.Manager for automatic certificate management. Old CertFile, CertKeyFile, CertClientFile, and AutoCertManager fields in ListenConfig are now deprecated.
  • Advanced Client Certificate Verification: A new package fiber/addon/verifypeer has been added, providing implementations of ServerTLSCustomizer for advanced client certificate verification: MutualTLSCustomizer for basic mTLS, MutualTLSWithCRLCustomizer for CRL-based verification, and MutualTLSWithOCSPCustomizer for OCSP stapling.
  • Client-Side TLS Configuration: A new ClientTLSProvider interface and its implementation ClientCertificateProvider have been introduced for configuring TLS settings for the Fiber client. This replaces the previous SetCertificates(), SetRootCertificate(), and SetRootCertificateFromString() methods, offering a unified and extensible approach.
  • Comprehensive PKI Test Data: A complete set of OpenSSL-generated PKI test certificates, including Root CA, Intermediate CA, client, server, revoked, and expired certificates, has been added to .github/testdata/pki to support thorough testing of the new TLS features.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@simonnix simonnix changed the title 🔥 feat: Externalize TLS configuration from ListenConfig 🔥 feat: Externalize TLS configuration from app.Listen() Jan 19, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant and valuable refactoring of the TLS configuration handling in Fiber. By externalizing the TLS configuration logic from app.Listen() into ServerTLSProvider and ServerTLSCustomizer interfaces, you've made the setup more modular, flexible, and easier to extend. The addition of a similar ClientTLSProvider for the client package is also a great improvement for consistency.

I've identified a few areas for improvement, including a critical issue that could lead to a panic, some redundant code, and a potential error that is being ignored. My review comments include specific suggestions to address these points. Overall, this is a well-thought-out feature that enhances Fiber's capabilities.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert.go`:
- Around line 43-66: ClientCertificate() on MutualTLSCustomizer can return (nil,
nil) which leads to a panic when CustomizeServerTLS calls
cfg.ClientCAs.AddCert(certificate); change the behavior to never return (nil,
nil): in MutualTLSCustomizer.ClientCertificate ensure that if no valid PEM block
of type "CERTIFICATE" is found you return a non-nil error (e.g., "tls: no client
certificate provided" or similar) instead of (nil, nil), and keep the existing
error paths for parse/read failures so callers like CustomizeServerTLS can
safely assume a non-nil *x509.Certificate on a nil error.

In `@addon/verifypeer/listen_test.go`:
- Around line 19-150: Add t.Parallel() at the start of Test_Listen_MutualTLS,
Test_Listen_MutualTLS_Deprecated, Test_Listen_MutualTLS_Prefork, and
Test_Listen_MutualTLS_Prefork_Deprecated; protect the global mutation of
testPreforkMaster by using a mutex (e.g., a new package-level sync.Mutex) around
assignments and reads and ensure each test sets testPreforkMaster under the lock
and schedules a reset via t.Cleanup() to restore previous value; finally fix the
path typo in Test_Listen_MutualTLS_Prefork by changing
"../../.github/testdate/template.html" to
"../../.github/testdata/template.html".

In `@addon/verifypeer/ocsp_test.go`:
- Around line 18-63: The test Test_OCSP_Server has a data race on the shared
variable network and uses flaky sleeps; fix it by adding t.Parallel(), replacing
the shared network string with two dedicated address channels (e.g., ocspAddrCh
and appAddrCh), have the stapler.Listen goroutine send its resolved addr into
ocspAddrCh and the app.Listen goroutine send into appAddrCh (use the
ListenerAddrFunc closures), then receive the addresses synchronously in the main
test to extract ocspPort and appPort (removing time.Sleep calls); ensure you
still assert errors from stapler.Listen and app.Listen and update references of
network to the values read from the channels.

In `@addon/verifypeer/ocsp.go`:
- Around line 82-90: The code dereferences m.TLSProvider for HTTPS OCSP
endpoints which can panic; in the OCSP client setup (where ocspURL is parsed and
you call m.TLSProvider.ProvideClientTLS and cc.SetTLSConfig) add a nil-check for
m.TLSProvider and return a clear error (e.g. "TLS provider required for https
OCSP endpoint") before calling ProvideClientTLS, or otherwise provide a fallback
TLS config, ensuring you only call ProvideClientTLS and cc.SetTLSConfig when
m.TLSProvider != nil.
- Around line 65-68: The current logic silently ignores validation errors for an
explicit OCSPServer override (m.OCSPServer) by only setting ocspServer when
validateOSCPServer returns nil; change this to fail fast: call
m.validateOSCPServer(m.OCSPServer) and if it returns an error, propagate/return
that error instead of continuing, otherwise assign ocspServer = m.OCSPServer;
update the code paths that call validateOSCPServer/ocspServer so they handle the
returned error appropriately.

In `@client/tls_provider.go`:
- Around line 42-46: The JSON tag for IncludeSystemCertificates is inverted
("ignore_system_certificates") and should match the field semantics; update the
struct tag on IncludeSystemCertificates to "include_system_certificates" to
align behavior, or if you must preserve backward compatibility implement a
custom UnmarshalJSON for the containing struct to accept either
"include_system_certificates" or the legacy "ignore_system_certificates" key and
map it to IncludeSystemCertificates. Ensure you update any related
marshaling/unmarshaling logic or docs to reflect the chosen key.

In `@docs/client/rest.md`:
- Around line 391-395: The docs example uses a non-existent field name; update
the ClientCertificateProvider struct literal to use the correct field name
RootCertificates instead of SetRootCertificates so the example compiles—locate
the example where ClientCertificateProvider is constructed in
docs/client/rest.md and replace SetRootCertificates with RootCertificates.

In `@tls_provider_test.go`:
- Around line 14-16: The test sets the package-level flag testPreforkMaster
(from prefork.go) to true but never restores it, causing state leakage across
tests; update each prefork-related test (e.g., the tests that set
testPreforkMaster in tls_provider_test.go) to capture the previous value before
changing it and restore it with a defer (save old := testPreforkMaster;
testPreforkMaster = true; defer func(){ testPreforkMaster = old }()), ensuring
the flag is reset after the test completes to avoid races when tests run in
parallel.
🟡 Minor comments (18)
addon/verifypeer/crl.go-114-114 (1)

114-114: Typo in error message: "lhe" should be "the".

📝 Suggested fix
-				return errors.New("tls: lhe certificate was revoked")
+				return errors.New("tls: the certificate was revoked")
addon/verifypeer/crl.go-52-52 (1)

52-52: Fix doc comment: incorrect interface name.

📝 Suggested fix
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer]
+// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]
addon/verifypeer/cert.go-27-28 (1)

27-28: Typo in doc comment.

"certiticate" should be "certificate".

📝 Suggested fix
-// It will add the certiticate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert
+// It will add the certificate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert
addon/verifypeer/crl.go-19-19 (1)

19-19: Fix doc comment: incorrect interface name.

Same as in cert.go, the comment references [fiber.ServerCertificateCustomizer] but should be fiber.ServerTLSCustomizer.

📝 Suggested fix
-// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerCertificateCustomizer].
+// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerTLSCustomizer].
addon/verifypeer/cert.go-14-14 (1)

14-14: Fix doc comment: incorrect interface name.

The comment references [fiber.ServerCertificateCustomizer] but the actual interface is fiber.ServerTLSCustomizer.

📝 Suggested fix
-// MutualTLSCustomizer is a struct implementing [fiber.ServerCertificateCustomizer].
+// MutualTLSCustomizer is a struct implementing [fiber.ServerTLSCustomizer].
docs/addon/verifypeer.md-7-12 (1)

7-12: Fix typos and interface naming in VerifyPeer docs.

There are several spelling issues and outdated interface names (“ServerCertificateCustomizer”, “OCSP Stapler”).

📝 Suggested doc edits
-The VerifyPeer addon package aim is to provides aadditional `fiber.ServerTLSCustomizer` interface implementationsto add means
-to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection.
+The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` implementations to configure a
+`*tls.Config`'s `VerifyPeerCertificate` field with extra validation for client mTLS connections.
@@
-`MutualTLSCustomizer` is a struct implementing `fiber.ServerCertificateCustomizer`, providing MutualTLS configuration to a `tls.Config` object.
+`MutualTLSCustomizer` is a struct implementing `fiber.ServerTLSCustomizer`, providing MutualTLS configuration to a `tls.Config` object.
@@
-See [OpenSSL documenation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for more information.
+See [OpenSSL documentation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for more information.
@@
-## MutualTLS with OCSP Stapler
+## MutualTLS with OCSP Stapling

Also applies to: 47-49, 75-79

addon/verifypeer/listen_test.go-86-92 (1)

86-92: Fix typo in testdata path.

testdate looks like a typo; it likely should be testdata.

✅ Suggested fix
-			Certificate: "../../.github/testdate/template.html",
+			Certificate: "../../.github/testdata/template.html",
client/tls_provider_test.go-50-52 (1)

50-52: Avoid deferring Close before ensuring os.Open succeeded.

If os.Open fails, file is nil and the deferred Close will panic.

✅ Suggested fix
 	file, err := os.Open("../.github/testdata/pki/cacert.pem")
-	defer func() { require.NoError(t, file.Close()) }()
 	require.NoError(t, err)
+	defer func() { require.NoError(t, file.Close()) }()
addon/verifypeer/ocsp.go-19-40 (1)

19-40: Align OCSP customizer docs with actual behavior.

The comments reference ServerCertificateCustomizer and a 1-second default, but the code implements ServerTLSCustomizer and sets 10 seconds.

📝 Suggested doc fix
-// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerCertificateCustomizer].
+// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerTLSCustomizer].
@@
-	// Default: 1 * time.Seconds
+	// Default: 10 * time.Second
 	Timeout time.Duration `json:"timeout"`
docs/client/rest.md-376-383 (1)

376-383: Fix typo and hard tabs in the SetTLSProvider section.

There’s a typo (“ths”) and markdownlint flags hard tabs in the interface snippet.

📝 Suggested fix
- Sets ths TLS configuration for the client, using the `ClientTLSProvider` interface.
+ Sets the TLS configuration for the client, using the `ClientTLSProvider` interface.
@@
 type ClientTLSProvider interface {
-	ProvideClientTLS() (*tls.Config, error)
+    ProvideClientTLS() (*tls.Config, error)
 }

As per coding guidelines, run make markdown after updating this file.

docs/whats_new.md-190-213 (1)

190-213: Tidy grammar and remove hard tabs in the TLS provider section.

The section has a grammar slip and markdownlint reports hard tabs in the code blocks.

📝 Suggested fix
-We provider now two interfaces and implementations to let you configure TLS in `ListenConfig`:
+We provide two interfaces and implementations to let you configure TLS in `ListenConfig`:
@@
 type ServerTLSProvider interface {
-	    ProvideServerTLS() (*tls.Config, error)
+    ProvideServerTLS() (*tls.Config, error)
 }
@@
 type ServerTLSCustomizer interface {
-	    CustomizeServerTLS(config *tls.Config) error
+    CustomizeServerTLS(config *tls.Config) error
 }
@@
-It can be a string to file or its content directly :
+It can be a file path or its content directly:

As per coding guidelines, run make markdown after updating this file.

tls_provider_test.go-13-17 (1)

13-17: Add t.Parallel() at the start of each test function.

These tests lack the required parallel execution directive. According to the repository's coding guidelines, all test functions should call t.Parallel() at the beginning to maximize concurrency.

Suggested change (apply to each test)
func Test_ServerTLSProvider_Version(t *testing.T) {
+	t.Parallel()
	testPreforkMaster = true

Applies to: Test_ServerTLSProvider_Version (line 13), Test_ServerTLSProvider (line 57), Test_ServerTLSProvider_Deprecated (line 80), Test_ServerTLSProvider_Prefork (line 105), Test_ServerTLSProvider_Prefork_Deprecated (line 134).

addon/verifypeer/ocsp_responder_test.go-137-138 (1)

137-138: Incorrect format specifier usage in logging calls.

The log.Errorf and log.Info calls use incorrect format specifiers:

  1. %w is for fmt.Errorf error wrapping, not for logging—use %v instead
  2. log.Info and log.Warn don't use printf-style formatting; use log.Infof/log.Warnf for formatted output
-	log.Errorf("%w", err)
+	log.Errorf("issuer verification failed: %v", err)
 	return nil, err
 }
 
 // get the index entry, if it exists
 ent, err := o.getIndexEntry(req.SerialNumber)
 if err != nil {
-	log.Errorf("%w", err)
+	log.Errorf("index entry lookup failed: %v", err)
 	status = ocsp.Unknown
 } else {
-	log.Info("Found entry %+v", ent)
+	log.Infof("Found entry %+v", ent)

Similar fixes needed for log.Warn on line 150 and log.Info on lines 154, 270.

Also applies to: 144-147

docs/api/fiber.md-120-120 (1)

120-120: Fix typo: "rewiewing" → "reviewing"

-| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference>                 | `func(tlsConfig *tls.Config)` | Allows rewiewing and customizing `tls.Config` returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is returned from `TLSProvider`                                                                                                                       | `nil`              |
+| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference>                 | `func(tlsConfig *tls.Config)` | Allows reviewing and customizing `tls.Config` returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is returned from `TLSProvider`                                                                                                                       | `nil`              |
tls_provider.go-12-21 (1)

12-21: Fix typo in interface documentation: "Implentations" → "Implementations"

-	// NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred.
+	// NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.
docs/api/fiber.md-173-173 (1)

173-173: Fix duplicate word: "also be also" → "also be"

-`Certificate` can also be also a string containing what that file should contain.
+`Certificate` can also be a string containing what that file should contain.
listen.go-157-159 (1)

157-159: Logic inconsistent with documented behavior for TLSConfigFunc.

The comment on lines 57-59 states that TLSConfigFunc receives a potentially nil tlsConfig so the user can "react to this." However, the current condition cfg.TLSProvider != nil prevents TLSConfigFunc from being called when no provider is set.

Consider whether TLSConfigFunc should be invoked unconditionally (or only when set), allowing users to handle cases where no TLS config exists:

-	if cfg.TLSProvider != nil && cfg.TLSConfigFunc != nil {
+	if cfg.TLSConfigFunc != nil {
 		cfg.TLSConfigFunc(tlsConfig)
 	}

Alternatively, if the current behavior is intentional, update the documentation to clarify that TLSConfigFunc is only called when TLSProvider is set.

addon/verifypeer/ocsp_responder_test.go-288-292 (1)

288-292: Potential nil pointer dereference if PEM decode fails.

If the file doesn't contain valid PEM data, pem.Decode returns nil for block, causing a panic on line 289:

 	block, _ := pem.Decode(ct)
+	if block == nil {
+		return nil, errors.New("failed to decode PEM block from certificate file")
+	}
 	cert, err := x509.ParseCertificate(block.Bytes)

The same issue exists in parseKeyFile at lines 302-303.

🧹 Nitpick comments (10)
addon/verifypeer/cert_test.go (2)

64-66: Missing t.Parallel() call.

Per coding guidelines, tests should invoke t.Parallel() at the start to maximize concurrency.

📝 Suggested fix
 func Test_Cert_File(t *testing.T) {
+	t.Parallel()
 	testCert(t, "../../.github/testdata/pki/intermediate/cacert.pem")
 }

68-72: Missing t.Parallel() call.

Per coding guidelines, tests should invoke t.Parallel() at the start to maximize concurrency.

📝 Suggested fix
 func Test_Cert_Content(t *testing.T) {
+	t.Parallel()
 	cert, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/cacert.pem"))
 	require.NoError(t, err)
 	testCert(t, string(cert))
 }
addon/verifypeer/crl.go (2)

109-119: Consider adding a defensive bounds check.

While verifiedChains should be non-empty when VerifyPeerCertificate is called after chain verification with RequireAndVerifyClientCert, a defensive check improves robustness.

♻️ Optional defensive check
 	if clientCRL != nil {
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("tls: no verified certificate chain")
+			}
 			cert := verifiedChains[0][0]
 			for _, revokedCertificate := range clientCRL.RevokedCertificateEntries {
 				if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 {
-					return errors.New("tls: lhe certificate was revoked")
+					return errors.New("tls: the certificate was revoked")
 				}
 			}
 			return nil
 		}
 	}

88-97: Silent CRL fetch failures from distribution points.

When RevocationList is empty, CRL fetch failures from CRLDistributionPoints are silently ignored. This might mask configuration issues. Consider logging a warning when all distribution point fetches fail.

addon/verifypeer/crl_test.go (3)

66-68: Missing t.Parallel() call.

Per coding guidelines, tests should invoke t.Parallel() at the start to maximize concurrency.

📝 Suggested fix
 func Test_CRL_File(t *testing.T) {
+	t.Parallel()
 	testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem")
 }

70-75: Missing t.Parallel() call.

📝 Suggested fix
 func Test_CRL_Content(t *testing.T) {
+	t.Parallel()
 	crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem"))
 	require.NoError(t, err)
 
 	testCRL(t, string(crlFile))
 }

77-105: Missing t.Parallel() call.

📝 Suggested fix
 func Test_CRL_Server(t *testing.T) {
+	t.Parallel()
 	var network string
tls_provider.go (2)

42-44: Consider avoiding mutation of receiver fields.

ProvideServerTLS() mutates p.TLSMinVersion when defaulting. This side effect could surprise callers who inspect the provider after calling the method. Consider using a local variable instead:

+	minVersion := p.TLSMinVersion
+	if minVersion == 0 {
+		minVersion = tls.VersionTLS12
+	}
-	if p.TLSMinVersion == 0 {
-		p.TLSMinVersion = tls.VersionTLS12
-	}
-
-	if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 {
+	if minVersion != tls.VersionTLS12 && minVersion != tls.VersionTLS13 {
 		return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13")
 	}

The same pattern applies to ServerCertificateProvider.ProvideServerTLS() at lines 99-101.


136-141: Redundant MinVersion assignment.

Line 137 sets tlsConfig.MinVersion again, but setCert() (lines 108-112) already sets it. Remove the redundant assignment:

 	if tlsConfig != nil {
-		tlsConfig.MinVersion = p.TLSMinVersion
 		tlsHandler := &TLSHandler{}
 		tlsConfig.GetCertificate = tlsHandler.GetClientInfo
 		return tlsConfig, nil
 	}
addon/verifypeer/ocsp_responder_test.go (1)

132-133: Unnecessary error wrapping without context.

fmt.Errorf("%w", err) adds no additional context. Either return the error directly or add a descriptive message:

-	return nil, fmt.Errorf("%w", err)
+	return nil, err

Or with context:

-	return nil, fmt.Errorf("%w", err)
+	return nil, fmt.Errorf("failed to parse OCSP request: %w", err)

This pattern appears throughout the file (lines 187, 196, 207, 210, 223, 239, 279, 286, 291, 300, 305).

@gaby
Copy link
Member

gaby commented Jan 19, 2026

In my opinion this is "too much of a change" for something no one has requested on the issues tab. It just adds complexity to the TLS configuration, and makes to too complicated compared to using native golang.

@gaby
Copy link
Member

gaby commented Jan 19, 2026

In my opinion this is "too much of a change" for something no one has requested on the issues tab. It just adds complexity to the TLS configuration, and makes to too complicated compared to using native golang.

We should have discussed implementation details in an issue before proceeding with a more complex approach for something that should have been straightforward.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 53-63: CustomizeServerTLS is fetching clientCACert via
mtls.ClientCertificate() but never configures the server to require client
certs, so VerifyPeerCertificate won't run; update the CustomizeServerTLS method
on MutualTLSWithCRLCustomizer to set config.ClientAuth =
tls.RequireAndVerifyClientCert (or appropriate auth mode) and config.ClientCAs =
x509.NewCertPool() then add clientCACert into that pool (use the returned
clientCACert), ensuring VerifyPeerCertificate remains assigned on config so CRL
checks execute; reference the CustomizeServerTLS method,
MutualTLSWithCRLCustomizer type, mtls.ClientCertificate(), and
VerifyPeerCertificate when making the change.

In `@addon/verifypeer/ocsp.go`:
- Around line 46-60: CustomizeServerTLS in MutualTLSWithOCSPCustomizer retrieves
a client CA cert but never wires mutual TLS into the provided tls.Config; after
calling mtls.ClientCertificate() grab the returned clientCACert and (1) ensure
config.ClientAuth is set to tls.RequireAndVerifyClientCert (or
tls.VerifyClientCertIfGiven if desired) and (2) add the client CA to
config.ClientCAs by creating or appending to a x509.CertPool and calling
AppendCertsFromPEM/ AddCert as appropriate; update the method to perform these
steps (referencing CustomizeServerTLS, MutualTLSWithOCSPCustomizer,
MutualTLSCustomizer, and ClientCertificate) so client certificates are actually
requested and verified.
♻️ Duplicate comments (2)
addon/verifypeer/ocsp.go (2)

64-67: Fail fast on invalid explicit OCSPServer configuration.

When m.OCSPServer is explicitly provided but invalid, the validation error is silently ignored, which could mask misconfiguration and weaken security. Return the error instead.

🐛 Proposed fix
 	if m.OCSPServer != "" {
-		if err := m.validateOSCPServer(m.OCSPServer); err == nil {
-			ocspServer = m.OCSPServer
+		if err := m.validateOCSPServer(m.OCSPServer); err != nil {
+			return err
 		}
+		ocspServer = m.OCSPServer
 	} else {

83-89: Guard against nil TLSProvider for HTTPS OCSP endpoints.

When ocspURL.Scheme == "https" and m.TLSProvider is nil, line 84 will panic with a nil pointer dereference. Add a nil check before calling ProvideClientTLS.

🐛 Proposed fix
 		if ocspURL.Scheme == "https" {
+			if m.TLSProvider == nil {
+				return errors.New("mtls: TLSProvider is required for https OCSP server")
+			}
 			tlsConfig, err := m.TLSProvider.ProvideClientTLS()
 			if err != nil {
 				return err
 			}
 			cc.SetTLSConfig(tlsConfig)
 		}
🧹 Nitpick comments (10)
tls_provider.go (3)

17-17: Typo in documentation: "Implentations" → "Implementations".

-	// NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred.
+	// NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.

42-44: Avoid mutating the receiver's state.

Directly assigning p.TLSMinVersion = tls.VersionTLS12 modifies the receiver, which could cause unexpected behavior if the provider is reused or inspected after calling ProvideServerTLS(). Consider using a local variable instead.

♻️ Proposed fix
 func (p *ACMECertificateProvider) ProvideServerTLS() (*tls.Config, error) {
 	var tlsConfig *tls.Config
+	minVersion := p.TLSMinVersion

-	if p.TLSMinVersion == 0 {
-		p.TLSMinVersion = tls.VersionTLS12
+	if minVersion == 0 {
+		minVersion = tls.VersionTLS12
 	}

-	if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 {
+	if minVersion != tls.VersionTLS12 && minVersion != tls.VersionTLS13 {
 		return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13")
 	}
 	if p.AutoCertManager != nil {
 		tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
-			MinVersion:     p.TLSMinVersion,
+			MinVersion:     minVersion,
 			GetCertificate: p.AutoCertManager.GetCertificate,
 			NextProtos:     []string{"http/1.1", "acme-tls/1"},
 		}
 	}

99-101: Same mutation concern as ACMECertificateProvider.

This method also mutates p.TLSMinVersion directly. Apply the same local-variable pattern for consistency.

addon/verifypeer/ocsp.go (4)

19-19: Incorrect interface name in documentation.

The comment references ServerCertificateCustomizer but the implemented interface is ServerTLSCustomizer.

-// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerCertificateCustomizer].
+// MutualTLSWithOCSPCustomizer is a struct implementing [fiber.ServerTLSCustomizer].

39-40: Documentation mismatch: default is 10 seconds, not 1.

The comment says 1 * time.Seconds but line 54 sets the default to 10 * time.Second.

-	// Default: 1 * time.Seconds
+	// Default: 10 * time.Second
 	Timeout time.Duration `json:"timeout"`

91-92: Add defensive bounds check on verifiedChains.

Accessing verifiedChains[0][0] without checking if the slice is non-empty could panic in edge cases. While the TLS library typically ensures at least one chain exists, a defensive check improves robustness.

♻️ Proposed fix
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("mtls: no verified certificate chain")
+			}
 			cert := verifiedChains[0][0]

132-132: Typo in method name: "OSCP" → "OCSP".

The method name validateOSCPServer has a transposition error. Should be validateOCSPServer to match the standard OCSP (Online Certificate Status Protocol) acronym.

addon/verifypeer/crl.go (3)

19-19: Incorrect interface name in documentation.

Same issue as in ocsp.go - the comment references ServerCertificateCustomizer but the implemented interface is ServerTLSCustomizer.

-// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerCertificateCustomizer].
+// MutualTLSWithCRLCustomizer is a struct implementing [fiber.ServerTLSCustomizer].

46-47: Minor typo: "time.Seconds" → "time.Second".

-	// Default: 10 * time.Seconds
+	// Default: 10 * time.Second
 	Timeout time.Duration `json:"timeout"`

109-110: Add defensive bounds check on verifiedChains.

Same concern as in ocsp.go - add a check before accessing verifiedChains[0][0].

♻️ Proposed fix
 	if clientCRL != nil {
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("tls: no verified certificate chain")
+			}
 			cert := verifiedChains[0][0]

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 106-112: After parsing crlBytes into crl via
x509.ParseRevocationList, validate the CRL signature before assigning to
clientCRL: call crl.CheckSignatureFrom(...) using the client CA certificate
object used for client cert verification (e.g., clientCACert or issuerCert) and
return an error if the signature check fails; only set clientCRL = crl when
CheckSignatureFrom succeeds to prevent using tampered revocation lists.

In `@client/tls_provider.go`:
- Around line 63-112: Change initTLS to return an error and have setCertificate
return an error too; inside initTLS, when p.IncludeSystemCertificates is true
propagate and return the error from x509.SystemCertPool() instead of falling
back to x509.NewCertPool(); update all callers (the conditional call guarded by
p.IncludeSystemCertificates, setCertificate, and appendRootAndReturn) to handle
and return the error up the stack; ensure tlsConfig remains initialized on
success and keep existing error wrapping for tls.X509KeyPair /
tls.LoadX509KeyPair but propagate those errors from the p.Certificate handling
so caller sees failures.

In `@docs/client/rest.md`:
- Around line 376-383: The docs for SetTLSProvider contain a typo ("ths" ->
"the") and use hard tabs that violate markdownlint; update the
heading/description to "Sets the TLS configuration for the client, using the
`ClientTLSProvider` interface." and replace any hard tabs with spaces in this
section (including the code block for the ClientTLSProvider interface and the
ProvideClientTLS() signature), then run `make markdown` to reformat and validate
the documentation.

@simonnix
Copy link
Author

Hi @gaby ,

We should have discussed implementation details in an issue before proceeding with a more complex approach for something that should have been straightforward.

I tried to discuss this on Discord and you replied to me I did not need to have anything else than files to setup TLS.

I also did post a preliminary implementation I posted on Discord, without replies : https://github.com/simonnix/fbrtls .

In my opinion this is "too much of a change" for something no one has requested on the issues tab. It just adds complexity to the TLS configuration, and makes to too complicated compared to using native golang.

I am sorry I told you I never posted a PR before on Fiber. I should have opened an issue maybe, but I preferred tried to make it.

The change in usage is minimal.

I think it is easier like that than having app.Listen() handles setting up a *tls.Config object all by itself.

And it lets the developer configure the *tls.Config his way.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 46-47: The comment above the Timeout field is incorrect: change
the comment string from "Default: 10 * time.Seconds" to "Default: 10 *
time.Second" to reference the correct Go time constant used by the Timeout field
(Timeout time.Duration) in this file.
- Around line 162-170: The code calls cc.Get(distURL.String()) in the CRL fetch
path and then blindly passes resp.Body to m.decodePemCrl without validating the
HTTP response; update the logic in the function that uses cc.Get and
m.decodePemCrl (the CRL fetch routine using distURL) to check resp.StatusCode
(e.g., ensure 200 <= status < 300) before attempting to parse the body, close
resp.Body on all paths, and return a clear formatted error containing the URL
and the HTTP status (and optionally status text) when the response is not
successful so callers see the HTTP failure instead of a PEM parsing error.

In `@client/tls_provider.go`:
- Around line 13-22: Fix the typos in the ClientTLSProvider interface doc
comments: change the mention of "ProviderClientTLSConfig" to the actual method
name "ProvideClientTLS" and correct "Implentations" to "Implementations" in the
note describing return behavior; ensure the doc text matches the
ProvideClientTLS() method signature and reads clearly.
- Around line 34-45: Update the field comments for RootCertificates and
IncludeSystemCertificates in tls_provider.go to fix typos: change
"authentificate" to "authenticate" in the RootCertificates comment and remove
the extra space in "allows to  inherit" so it reads "allows to inherit" in the
IncludeSystemCertificates comment; keep the rest of the documentation text and
tags unchanged.
🧹 Nitpick comments (2)
addon/verifypeer/crl.go (2)

117-127: Add defensive bounds check in VerifyPeerCertificate callback.

The callback directly accesses verifiedChains[0][0] without verifying that the slices are non-empty. While with RequireAndVerifyClientCert the chains should be populated, adding a defensive check prevents potential panics from unexpected edge cases.

Proposed fix
 	if clientCRL != nil {
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("tls: no verified certificate chains")
+			}
 			cert := verifiedChains[0][0]
 			for _, revokedCertificate := range clientCRL.RevokedCertificateEntries {
 				if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 {
 					return errors.New("tls: the certificate was revoked")
 				}
 			}
 			return nil
 		}
 	}

93-102: Silent fallback when CRL distribution point fetches fail.

When RevocationList is not explicitly configured and fetching from all CRLDistributionPoints fails, the connection proceeds without any revocation checking. This fail-open behavior could allow revoked certificates to authenticate successfully.

Consider either:

  1. Documenting this behavior explicitly in the struct/method comments
  2. Adding a FailOnCRLUnavailable bool field to optionally enforce fail-closed behavior

Is this fail-open behavior intentional? If users expect revocation checking when a CA has distribution points configured, silent failures could be a security gap.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@addon/verifypeer/crl.go`:
- Around line 19-22: The doc comments for MutualTLSWithCRLCustomizer incorrectly
reference fiber.ServerCertificateCustomizer; update the comments to reference
the correct interface name fiber.ServerTLSCustomizer wherever it appears
(including the comment block above the MutualTLSWithCRLCustomizer type and the
secondary comment near its methods) so documentation matches the actual
interface.
♻️ Duplicate comments (4)
client/tls_provider.go (2)

13-21: Fix typos in ClientTLSProvider docs.

Line 15 and Line 18 still refer to ProviderClientTLSConfig and “Implentations”. These should match the method name and correct spelling.

📝 Proposed fix
-	// ProviderClientTLSConfig provides possibly a *tls.Config object to be used by a client or
+	// ProvideClientTLS provides possibly a *tls.Config object to be used by a client or
@@
-	// NOTE: Implentations may return (nil, nil) if no tls.Config can be provided and no error occurred.
+	// NOTE: Implementations may return (nil, nil) if no tls.Config can be provided and no error occurred.

34-43: Correct wording in field comments.

Line 37 uses “authentificate” and Line 42 has a double space. Please fix the wording.

📝 Proposed fix
-	// permitting to authentificate a remote server.
+	// permitting to authenticate a remote server.
@@
-	// IncludeSystemCertificates allows to  inherit CA certificates from the system.
+	// IncludeSystemCertificates allows to inherit CA certificates from the system.
addon/verifypeer/crl.go (2)

46-47: Typo in default timeout comment.

Use time.Second, not time.Seconds.

📝 Proposed fix
-	// Default: 10 * time.Seconds
+	// Default: 10 * time.Second

162-170: Check HTTP status before parsing the CRL response.

Non-2xx responses should surface as HTTP errors, not PEM parse failures.

🐛 Proposed fix
 	resp, err := cc.Get(distURL.String())
 	if err != nil {
 		return []byte{}, fmt.Errorf("tls: unable to fetch certificate revocation list from url=%q: %w", distURL.String(), err)
 	}
 
+	if resp.StatusCode() < 200 || resp.StatusCode() >= 300 {
+		return []byte{}, fmt.Errorf("tls: failed to fetch CRL from url=%q: HTTP status %d", distURL.String(), resp.StatusCode())
+	}
+
 	if bytes, ok := m.decodePemCrl(resp.Body()); ok {
 		return bytes, nil
 	}
🧹 Nitpick comments (1)
addon/verifypeer/crl.go (1)

117-126: Preserve any existing VerifyPeerCertificate hook.

Setting config.VerifyPeerCertificate here overwrites any prior customizer (e.g., OCSP). Consider chaining the previous hook so multiple customizers compose instead of clobbering.

♻️ Proposed refactor
 	if clientCRL != nil {
-		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+		prevVerify := config.VerifyPeerCertificate
+		config.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if prevVerify != nil {
+				if err := prevVerify(rawCerts, verifiedChains); err != nil {
+					return err
+				}
+			}
 			cert := verifiedChains[0][0]
 			for _, revokedCertificate := range clientCRL.RevokedCertificateEntries {
 				if revokedCertificate.SerialNumber.Cmp(cert.SerialNumber) == 0 {
 					return errors.New("tls: the certificate was revoked")
 				}
 			}
 			return nil
 		}
 	}

@simonnix simonnix force-pushed the tls-providers branch 3 times, most recently from 0664e7b to f1ecc57 Compare January 19, 2026 20:12
@simonnix
Copy link
Author

Hi, every issues from AI bots have been resolved.

Do you want me to squash my commits before your reviews ?

Let me know.

@gaby
Copy link
Member

gaby commented Jan 19, 2026

@simonnix You can squash them, but this solution is too convoluted and complex for something that was just a matter of adding few new fields in the config.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert_test.go`:
- Around line 64-72: Both Test_Cert_File and Test_Cert_Content are missing
t.Parallel(); add t.Parallel() as the first statement in each test function
(Test_Cert_File and Test_Cert_Content) so they run concurrently; ensure the call
appears before any setup or calls to testCert to follow Go testing concurrency
guidelines.

In `@addon/verifypeer/listen_test.go`:
- Around line 36-39: The test uses assert.NoError inside background goroutines
calling app.Shutdown(), which can miss failures; change each goroutine (the ones
invoking app.Shutdown() at the places using time.Sleep) to send the returned
error on a channel (or assign to a synced variable) and then receive that error
in the main test goroutine and call assert.NoError on it; update the instances
around app.Shutdown() (and their anonymous goroutines) so the shutdown result is
forwarded and asserted synchronously in the test instead of asserting inside the
goroutine.

In `@addon/verifypeer/ocsp.go`:
- Around line 53-55: The function currently applies the same default for
m.Timeout twice; remove the duplicate check/assignment so only the initial
defaulting (the first "if m.Timeout == 0 { m.Timeout = 10 * time.Second }"
block) remains and delete the later identical block that reassigns m.Timeout to
10 * time.Second to avoid redundant logic.

In `@client/tls_provider_test.go`:
- Around line 47-59: In Test_ClientTLSProvider_RootCertificatesFromString move
the defer that closes file so it runs only after confirming os.Open succeeded:
call require.NoError(t, err) (checking the error returned by os.Open) before
deferring file.Close(), so the defer uses a non-nil file; ensure the existing
require.NoError calls for ReadAll remain unchanged and reference the local
variable file used by the defer.

In `@docs/addon/verifypeer.md`:
- Line 48: Correct the typo in the markdown sentence "See [OpenSSL
documenation](https://docs.openssl.org/3.5/man1/openssl-ca/#crl-options) for
more information." by changing "documenation" to "documentation" so the link
text reads "OpenSSL documentation"; update the text/anchor where that exact
string appears (the link text "OpenSSL documenation") to preserve the same URL
but fix the spelling.
- Around line 7-8: Fix typos in the VerifyPeer addon introduction: change
"aadditional" to "additional" and "implementationsto" to "implementations to" in
the paragraph that describes the VerifyPeer package and its implementation of
fiber.ServerTLSCustomizer to configure a *tls.Config's VerifyPeerCertificate for
client mTLS validation; after editing the docs/addon/verifypeer.md run `make
markdown`.
- Line 12: The docs incorrectly reference the interface as
fiber.ServerCertificateCustomizer; update all occurrences to the correct
interface name fiber.ServerTLSCustomizer in the verifypeer.md documentation
where MutualTLSCustomizer is described, and ensure any mentions alongside
MutualTLSCustomizer and tls_provider.go reflect the corrected symbol name.

In `@docs/api/fiber.md`:
- Line 120: Typo in the docs: change the word "rewiewing" to "reviewing" in the
table row describing TLSConfigFunc; update the sentence for the <Reference
id="tlsconfigfunc">TLSConfigFunc</Reference> entry that references `TLSProvider`
and `tlsConfig` so it reads "Allows reviewing and customizing `tls.Config`
returned from `TLSProvider` as you want. `tlsConfig` may be `nil` if none is
returned from `TLSProvider`."
- Line 173: Fix the grammar in the sentence referencing `Certificate`: remove
the redundant word "also" so the phrase reads "Certificate can also be a string
containing what that file should contain." Locate the line containing the
backticked `Certificate` in docs/api/fiber.md and update the sentence
accordingly.

In `@docs/whats_new.md`:
- Line 192: Fix the typo in the sentence "We provider now two interfaces and
implementations to let you configure TLS in `ListenConfig`" by replacing
"provider" with "provide" so it reads "We provide now two interfaces and
implementations to let you configure TLS in `ListenConfig`." Ensure the updated
text preserves the backticks around `ListenConfig`.

In `@listen_test.go`:
- Around line 225-228: Replace the deprecated CertFile/CertKeyFile fields on
ServerCertificateProvider with the single Certificate field pointing to the
fullchain.pem file (which includes cert, key, and intermediates) and add
t.Parallel() as the first statement in the test function; specifically, update
the ServerCertificateProvider initializer to use Certificate:
"./.github/testdata/pki/intermediate/server/fullchain.pem" instead of
CertFile/CertKeyFile and insert t.Parallel() at the top of the test (the test
that configures ServerCertificateProvider).

In `@tls_provider.go`:
- Around line 14-20: Fix the documentation typo in the comment for
ProvideServerTLS: change "Implentations" to "Implementations" in the comment
block above the ProvideServerTLS method (the comment that mentions returning
"(nil, nil)" and examples about certificate paths) so the doc reads correctly.
🧹 Nitpick comments (8)
client/tls_provider.go (1)

56-107: LGTM with one minor suggestion.

The TLS configuration logic is well-structured with proper error propagation. The approach of using the same file for both cert and key parameters in X509KeyPair/LoadX509KeyPair is correct since the Certificate field is documented to contain both certificate and key in PEM format.

📝 Minor: Consider aligning error message with field name

The error messages reference "CertificateChain" but the field is named "Certificate":

-			return nil, fmt.Errorf("clienttls: cannot load TLS key pair from CertificateChain: %w", err)
+			return nil, fmt.Errorf("clienttls: cannot load TLS key pair from Certificate: %w", err)
-		return nil, fmt.Errorf("clienttls: cannot load TLS key pair from CertificateChain: %w", err)
+		return nil, fmt.Errorf("clienttls: cannot load TLS key pair from Certificate: %w", err)
addon/verifypeer/ocsp.go (1)

105-111: Consider adding a defensive bounds check on verifiedChains.

While Go's TLS stack guarantees verifiedChains is non-empty when RequireAndVerifyClientCert is set, a defensive check would make the code more robust against unexpected edge cases.

♻️ Proposed defensive check
 	config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+		if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+			return errors.New("mtls: no verified certificate chain")
+		}
 		cert := verifiedChains[0][0]
 		opts := &ocsp.RequestOptions{Hash: crypto.SHA256}
addon/verifypeer/ocsp_responder_test.go (1)

146-158: Use structured logging format for consistency.

The log.Info and log.Warn calls at lines 147, 150, and 270 use printf-style formatting (e.g., log.Info("Found entry %+v", ent)), but Fiber's logger typically expects structured arguments. Consider using log.Infof or log.Infow for format strings.

♻️ Proposed fix
-		log.Info("Found entry %+v", ent)
+		log.Infof("Found entry %+v", ent)
 		switch ent.Status {
 		case StatusRevoked:
-			log.Warn("This certificate is revoked")
+			log.Warnf("This certificate is revoked")
addon/verifypeer/cert_test.go (1)

29-47: Consider using a channel for server readiness instead of sleep.

The time.Sleep(500 * time.Millisecond) at line 45 is fragile—under heavy load, the server may not be ready in time. Consider using a channel signaled from ListenerAddrFunc to ensure the server is actually listening before proceeding with client requests.

Additionally, the assert.NoError in the goroutine (line 30) may not properly fail the test if the assertion fails.

♻️ Proposed improvement
 func testCert(t *testing.T, cert string) {
 	t.Helper()
-	var network string
+	ready := make(chan string, 1)
+	listenErr := make(chan error, 1)

 	// App
 	app := fiber.New()
 	app.Use(logger.New())
 	app.Get("/", func(c fiber.Ctx) error {
 		return c.SendString("Hello")
 	})
 	go func() {
-		assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
+		listenErr <- app.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
-				network = addr.String()
+				ready <- addr.String()
 			},
 			// ...
-		}))
+		})
 	}()

-	time.Sleep(500 * time.Millisecond)
-	appPort, found := strings.CutPrefix(network, "0.0.0.0:")
+	network := <-ready
+	appPort, found := strings.CutPrefix(network, "0.0.0.0:")
 	require.True(t, found)
addon/verifypeer/crl_test.go (4)

66-68: Missing t.Parallel() call.

Per coding guidelines, Go tests should invoke t.Parallel() at the start of each test to maximize concurrency.

Proposed fix
 func Test_CRL_File(t *testing.T) {
+	t.Parallel()
 	testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem")
 }

70-75: Missing t.Parallel() call.

Per coding guidelines, Go tests should invoke t.Parallel() at the start of each test.

Proposed fix
 func Test_CRL_Content(t *testing.T) {
+	t.Parallel()
 	crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem"))
 	require.NoError(t, err)

 	testCRL(t, string(crlFile))
 }

77-105: Missing t.Parallel() call.

Per coding guidelines, Go tests should invoke t.Parallel() at the start of each test.

Proposed fix
 func Test_CRL_Server(t *testing.T) {
+	t.Parallel()
 	var network string

47-49: Consider replacing time.Sleep with a synchronization primitive.

Using time.Sleep for server startup synchronization can cause flaky tests. Consider using a channel or sync.WaitGroup to signal when the server is ready.

Example using a channel
+	ready := make(chan struct{})
 	go func() {
 		assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
 				network = addr.String()
+				close(ready)
 			},
 			TLSProvider: &fiber.ServerCertificateProvider{
 				Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem",
 			},
 			TLSCustomizer: &verifypeer.MutualTLSWithCRLCustomizer{
 				Certificate:    "../../.github/testdata/pki/intermediate/cacert.pem",
 				RevocationList: crl,
 			},
 		}))
 	}()

-	time.Sleep(500 * time.Millisecond)
+	<-ready

@simonnix
Copy link
Author

@gaby We can talk about it here.

Should ListenConfig only offer a *tls.Config field ?

What would be great for you, maintainer, and for us developers too ?

How should we proceed ? Let me know.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In `@addon/verifypeer/cert_test.go`:
- Around line 29-43: The test spawns app.Listen in a goroutine and uses
assert.NoError(t, app.Listen(...)) there, which can miss failures because
assertions in goroutines don't fail the test reliably; modify the goroutine to
send the error returned by app.Listen to a channel (e.g., listenErrCh), replace
assert.NoError inside the goroutine with sending the error, and after triggering
shutdown (or after the test logic completes) receive from that channel and call
assert.NoError(t, listenErr) to propagate any Listen errors; apply this change
around the app.Listen call that configures ListenerAddrFunc, TLSProvider, and
TLSCustomizer (verifypeer.MutualTLSCustomizer) so Listen failures are observed
by the test.

In `@addon/verifypeer/crl.go`:
- Around line 162-173: The HTTP response from cc.Get in crl.go (resp) is never
closed, causing connection leaks; after the successful cc.Get(distURL.String())
and err check in the function that calls m.decodePemCrl, immediately ensure the
response body is closed in all return paths (e.g., add a defer resp.Body.Close()
right after obtaining resp) so that the subsequent StatusCode check, the call to
m.decodePemCrl(resp.Body), and any error returns still release the underlying
connection.

In `@addon/verifypeer/ocsp_responder_test.go`:
- Around line 136-144: The log.Errorf calls in ocsp_responder_test.go use the
fmt wrapping verb "%w" which is invalid for logging and produces garbage output;
update the logging in the error branches of verifyIssuer and getIndexEntry (the
calls currently using log.Errorf("%w", err)) to use a proper formatting verb
like "%v" or "%s" and include a short contextual message (e.g., "verifyIssuer
error: %v" and "getIndexEntry error: %v") so the actual error text is logged.

In `@addon/verifypeer/ocsp.go`:
- Around line 19-46: Update the struct and method comments to reference the
actual interface name fiber.ServerTLSCustomizer instead of
ServerCertificateCustomizer, and correct the Timeout default comment to match
the real value (use "1 * time.Second" or whatever the implementation uses) for
the Timeout field on MutualTLSWithOCSPCustomizer; ensure the top comment
mentions MutualTLSWithOCSPCustomizer implements fiber.ServerTLSCustomizer and
the CustomizeServerTLS method doc also references fiber.ServerTLSCustomizer, and
update the Timeout field's "Default" text to the exact time.Duration literal
used in code.

In `@client/client_test.go`:
- Around line 1891-1898: The test Test_Client_R currently asserts
reflect.TypeFor[Request]().Name() which only checks the Request type's name
rather than the runtime type of req; update the assertion to validate req's
concrete type returned by client.R()—for example use require.IsType(t,
&Request{}, req) or require.Equal(t, reflect.TypeOf(&Request{}).Elem(),
reflect.TypeOf(req).Elem()) so that req (from New() and R()) is actually a
Request instance; modify the assertions around New(), R(), and req accordingly.

In `@client/tls_provider.go`:
- Around line 95-104: The error messages in the TLS loading branch incorrectly
refer to "CertificateChain"; update the fmt.Errorf calls in the code paths that
call tls.X509KeyPair and tls.LoadX509KeyPair to mention p.Certificate (e.g.,
"clienttls: cannot load TLS key pair from Certificate") so the message correctly
references the Certificate field; locate the branches around p.Certificate,
tls.X509KeyPair, tls.LoadX509KeyPair and setCertificate and change the error
text accordingly.

In `@docs/addon/verifypeer.md`:
- Around line 75-83: Update the docs to use the standard term "OCSP Stapling"
instead of "OCSP Stapler": change the heading "MutualTLS with OCSP Stapler" and
any occurrences of "OCSP Stapler" in the description to "MutualTLS with OCSP
Stapling" / "OCSP Stapling", keeping references to the struct
MutualTLSWithOCSPCustomizer and fiber.ServerTLSCustomizer intact; then run make
markdown to regenerate the documentation outputs per guidelines.

In `@docs/api/fiber.md`:
- Around line 156-158: The TLS interface code blocks use hard tabs; replace tabs
with spaces in the ServerTLSProvider snippet (the type ServerTLSProvider
interface { ProvideServerTLS() (*tls.Config, error) }) and any other TLS
interface snippets (e.g., the other occurrence) so the code fences contain
spaces only, then run make markdown to reformat/update generated docs.

In `@tls_provider.go`:
- Line 38: The doc comment above ProvideServerTLS incorrectly references
[ServerTLSConfigProvider]; update the comment to reference the correct interface
name [ServerTLSProvider] (or remove the bracketed link if not needed) so the
comment matches the actual interface; locate the ProvideServerTLS function and
change its comment to mention ServerTLSProvider to keep docs and code
consistent.
- Line 95: Update the doc comment above ProvideServerTLS to reference the
correct interface name (replace the incorrect [ServerTLSConfigProvider] with the
actual interface implemented, e.g. [ServerTLSProvider]) so the comment
accurately documents that ProvideServerTLS implements the proper interface.
♻️ Duplicate comments (3)
docs/addon/verifypeer.md (1)

7-8: Fix grammar in the VerifyPeer introduction.

The sentence is ungrammatical and has a spacing error around *tls.Config's.

✏️ Suggested edit
-The VerifyPeer addon package aim is to provides additional `fiber.ServerTLSCustomizer` interface implementations to add means
-to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection.
+The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` interface implementations to configure
+a `*tls.Config`'s `VerifyPeerCertificate` field with additional validation for client mTLS connections.

Please run make markdown after updating the docs. As per coding guidelines.

addon/verifypeer/crl.go (1)

52-53: Fix interface name in CustomizeServerTLS docstring.

✏️ Suggested edit
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer]
+// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]
addon/verifypeer/listen_test.go (1)

70-73: Assertions in goroutines may not fail the test properly.

Using assert.NoError inside a goroutine won't reliably fail the test because the goroutine may outlive the test function. The first test (Test_Listen_MutualTLS) correctly uses a channel pattern. Apply the same pattern here for consistency.

♻️ Suggested fix
+	shutdownErr := make(chan error, 1)
 	go func() {
 		time.Sleep(1000 * time.Millisecond)
-		assert.NoError(t, app.Shutdown())
+		shutdownErr <- app.Shutdown()
 	}()
 
 	require.NoError(t, app.Listen(":0", fiber.ListenConfig{
 		// ...
 	}))
+	require.NoError(t, <-shutdownErr)

The same issue exists in Test_Listen_MutualTLS_Prefork (lines 109-112) and Test_Listen_MutualTLS_Prefork_Deprecated (lines 149-152).

🧹 Nitpick comments (10)
client/client.go (1)

247-257: Avoid holding the client lock during provider I/O and skip nil configs.

ProvideClientTLS may perform I/O; holding c.mu can block unrelated setters. Also, the interface allows (nil, nil)—currently that clears any existing TLS config. If that’s not intended, guard config != nil and apply under the lock only after the provider call.

♻️ Suggested refactor
 func (c *Client) SetTLSProvider(provider ClientTLSProvider) *Client {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	if provider != nil {
-		if config, err := provider.ProvideClientTLS(); err != nil {
-			c.logger.Panicf("client: unable to set tlsConfig: %v", err)
-		} else {
-			c.applyTLSConfig(config)
-		}
-	}
+	if provider == nil {
+		return c
+	}
+	config, err := provider.ProvideClientTLS()
+	if err != nil {
+		c.logger.Panicf("client: unable to set tlsConfig: %v", err)
+		return c
+	}
+	if config == nil {
+		return c
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.applyTLSConfig(config)
 	return c
 }
client/tls_provider.go (1)

56-76: Avoid mutating provider state inside ProvideClientTLS.

Mutating p.TLSMinVersion can introduce data races if the provider is reused across goroutines. Prefer a local default.

♻️ Suggested refactor
-	if p.TLSMinVersion == 0 {
-		p.TLSMinVersion = tls.VersionTLS12
-	}
+	minVersion := p.TLSMinVersion
+	if minVersion == 0 {
+		minVersion = tls.VersionTLS12
+	}
@@
-			tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
-				MinVersion: p.TLSMinVersion,
+			tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
+				MinVersion: minVersion,
 			}
docs/client/rest.md (1)

376-406: Minor grammar fix needed.

Line 399 is missing a comma for proper sentence structure.

-If the server can be authenticated using one of the system root certificates, you can also load them :
+If the server can be authenticated using one of the system root certificates, you can also load them:

Also, as per coding guidelines, please run make markdown to lint all Markdown files after updating.

tls_provider.go (1)

42-44: Consider avoiding mutation of receiver fields.

Both ACMECertificateProvider.ProvideServerTLS() and ServerCertificateProvider.ProvideServerTLS() mutate p.TLSMinVersion when it's zero. This side effect could cause unexpected behavior if the provider instance is reused or inspected after calling ProvideServerTLS().

Consider using a local variable instead:

♻️ Suggested refactor for ACMECertificateProvider
 func (p *ACMECertificateProvider) ProvideServerTLS() (*tls.Config, error) {
 	var tlsConfig *tls.Config
+	tlsMinVersion := p.TLSMinVersion
 
-	if p.TLSMinVersion == 0 {
-		p.TLSMinVersion = tls.VersionTLS12
+	if tlsMinVersion == 0 {
+		tlsMinVersion = tls.VersionTLS12
 	}
 
-	if p.TLSMinVersion != tls.VersionTLS12 && p.TLSMinVersion != tls.VersionTLS13 {
+	if tlsMinVersion != tls.VersionTLS12 && tlsMinVersion != tls.VersionTLS13 {
 		return nil, errors.New("tls: unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13")
 	}
 	if p.AutoCertManager != nil {
 		tlsConfig = &tls.Config{
-			MinVersion:     p.TLSMinVersion,
+			MinVersion:     tlsMinVersion,
 			GetCertificate: p.AutoCertManager.GetCertificate,
 			NextProtos:     []string{"http/1.1", "acme-tls/1"},
 		}
 	}

Apply a similar pattern to ServerCertificateProvider.

Also applies to: 99-101

addon/verifypeer/listen_test.go (1)

16-19: Using go:linkname to access internal test variables is fragile.

While go:linkname works, it couples this test to the internal implementation of the fiber package. If testPreforkMaster is renamed or removed, these tests will fail at link time with a cryptic error. Consider whether there's a cleaner way to test prefork behavior, such as exposing a test helper function.

addon/verifypeer/cert_test.go (1)

45-47: Race condition: time.Sleep is unreliable for server startup synchronization.

The test uses time.Sleep(500ms) to wait for the server to start, but ListenerAddrFunc is already being used to capture the address. Consider using a channel to signal when the listener is ready:

♻️ Suggested fix
+	ready := make(chan struct{})
 	go func() {
 		assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
 				network = addr.String()
+				close(ready)
 			},
 			// ...
 		}))
 	}()
 
-	time.Sleep(500 * time.Millisecond)
+	<-ready
 	appPort, found := strings.CutPrefix(network, "0.0.0.0:")
listen.go (1)

142-159: Consider warning or erroring when TLSCustomizer is set but TLSProvider returns nil.

When TLSProvider.ProvideServerTLS() returns (nil, nil) (which is valid per the interface contract), the TLSCustomizer is silently skipped because of the if tlsConfig != nil check. This could lead to confusing behavior where users set a customizer expecting it to run, but it never does.

Consider logging a warning or returning an error when TLSCustomizer is set but tlsConfig is nil:

♻️ Suggested enhancement
 	if tlsConfig != nil {
 		if cfg.TLSCustomizer != nil {
 			if err := cfg.TLSCustomizer.CustomizeServerTLS(tlsConfig); err != nil {
 				return err
 			}
 		}
+	} else if cfg.TLSCustomizer != nil {
+		log.Warn("TLSCustomizer is set but TLSProvider returned nil config; customizer will not be applied")
 	}
listen_test.go (1)

187-208: Consider adding t.Parallel() for test concurrency.

This test binds to port :0 and uses an in-memory shutdown pattern, making it safe for parallel execution. Per coding guidelines, tests should invoke t.Parallel() at the start.

Suggested fix
 func Test_App_Listener_TLS_Listener(t *testing.T) {
+	t.Parallel()
 	// Create tls certificate
 	cer, err := tls.LoadX509KeyPair("./.github/testdata/pki/intermediate/server/cert.pem", "./.github/testdata/pki/intermediate/server/key.pem")
docs/whats_new.md (1)

192-192: Minor grammar improvement for readability.

Consider reordering for natural English phrasing.

Suggested fix
-We provide now two interfaces and implementations to let you configure TLS in `ListenConfig`:
+We now provide two interfaces and implementations to let you configure TLS in `ListenConfig`:
addon/verifypeer/ocsp_responder_test.go (1)

131-133: Redundant error wrapping with fmt.Errorf("%w", err).

Using fmt.Errorf("%w", err) without additional context is redundant—it creates a new error that wraps the original without adding information. Either add meaningful context or return the error directly.

Example fix for one occurrence
 	req, err := ocsp.ParseRequest(rawreq)
 	if err != nil {
-		return nil, fmt.Errorf("%w", err)
+		return nil, fmt.Errorf("parse OCSP request: %w", err)
 	}

Or simply:

 	req, err := ocsp.ParseRequest(rawreq)
 	if err != nil {
-		return nil, fmt.Errorf("%w", err)
+		return nil, err
 	}

Also applies to: 186-188, 195-197, 206-208, 209-211, 222-224, 238-240, 285-287, 290-292, 299-301, 304-306

Comment on lines +29 to +43
go func() {
assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
DisableStartupMessage: true,
ListenerNetwork: fiber.NetworkTCP4,
ListenerAddrFunc: func(addr net.Addr) {
network = addr.String()
},
TLSProvider: &fiber.ServerCertificateProvider{
Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem",
},
TLSCustomizer: &verifypeer.MutualTLSCustomizer{
Certificate: cert,
},
}))
}()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assertions in goroutines won't fail the test properly.

assert.NoError(t, app.Listen(...)) inside a goroutine won't cause the test to fail if Listen returns an error, because the goroutine may complete after the test function returns.

Consider using a channel to capture and propagate errors:

♻️ Suggested fix
+	listenErr := make(chan error, 1)
 	go func() {
-		assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
+		listenErr <- app.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
 				network = addr.String()
 			},
 			TLSProvider: &fiber.ServerCertificateProvider{
 				Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem",
 			},
 			TLSCustomizer: &verifypeer.MutualTLSCustomizer{
 				Certificate: cert,
 			},
-		}))
+		})
 	}()

Then check listenErr after shutdown.

🤖 Prompt for AI Agents
In `@addon/verifypeer/cert_test.go` around lines 29 - 43, The test spawns
app.Listen in a goroutine and uses assert.NoError(t, app.Listen(...)) there,
which can miss failures because assertions in goroutines don't fail the test
reliably; modify the goroutine to send the error returned by app.Listen to a
channel (e.g., listenErrCh), replace assert.NoError inside the goroutine with
sending the error, and after triggering shutdown (or after the test logic
completes) receive from that channel and call assert.NoError(t, listenErr) to
propagate any Listen errors; apply this change around the app.Listen call that
configures ListenerAddrFunc, TLSProvider, and TLSCustomizer
(verifypeer.MutualTLSCustomizer) so Listen failures are observed by the test.

Comment on lines 1891 to 1898
func Test_Client_R(t *testing.T) {
t.Parallel()

client := New()
req := client.R()

require.Equal(t, "Request", reflect.TypeOf(req).Elem().Name())
require.Equal(t, "Request", reflect.TypeFor[Request]().Name())
require.Equal(t, client, req.Client())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The assertion no longer validates the actual request object's type.

The change from reflect.TypeOf(req).Elem().Name() to reflect.TypeFor[Request]().Name() alters the test's behavior. The original code verified that req is of type Request, while the new code only checks that the type Request has name "Request" (a tautology that's always true).

If the intent is to verify req's type, consider:

-	require.Equal(t, "Request", reflect.TypeFor[Request]().Name())
+	require.Equal(t, "Request", reflect.TypeOf(req).Elem().Name())

Or if using Go 1.22+ idioms, you could use a type assertion:

require.IsType(t, &Request{}, req)
🤖 Prompt for AI Agents
In `@client/client_test.go` around lines 1891 - 1898, The test Test_Client_R
currently asserts reflect.TypeFor[Request]().Name() which only checks the
Request type's name rather than the runtime type of req; update the assertion to
validate req's concrete type returned by client.R()—for example use
require.IsType(t, &Request{}, req) or require.Equal(t,
reflect.TypeOf(&Request{}).Elem(), reflect.TypeOf(req).Elem()) so that req (from
New() and R()) is actually a Request instance; modify the assertions around
New(), R(), and req accordingly.

- Externalize `*tls.Config` object creation logic from `app.Listen()`
  using two interfaces and easy to use implementations.

- Add a similar interface and easy to use implementation for
  configuring `*tls.Config` object for `client`.

Currently TLS configuration is strictly done from the parameters
`CertFile` and `CertKeyFile` (v2/v3) or from `AutoCertManager` (v3)
in `ListenConfig` inside `app.Listen()`

Mutual TLS is also strictly configured using `CertClientFile` (with
`CertFile` and `CertKeyFile` only).

Any other use cases, like using configuration files (containing the
certificate and key) or manually connecting to a configuration manager
(like Hashicorp Vault) and not relying on files present on host, are
complicated. You have to use `App.Listener()` which does not support
preforking.

Having a certificate's key in a file means it can be included in system
backups, and otherwise, easily retrieved.

This feature provides a way to externalize the TLS configuration
from `app.Listen()`, with two interfaces references in `ListenConfig()` :

1. `ServerTLSProvider` which defines a function that potentially returns a
   `*tls.Config` object or an error.
2. `ServerTLSCustomizer` which defines a function to add functionnality
   to an existing `*tls.Config` object, like MutualTLS.

Implementations for the first are provided in the `fiber` package to
provide similar current functionalities :

- `ServerCertificateProvider` : Loading certificate and key from file,
  but now also from string. It can use a single parameter (certficate
  and key can be in the same file) or use the current `CertFile` and
  `CertKeyFile` parameters (marked as deprecated).
- `ACMECertificateProvider` : Loading certificate from an
  `autocert.Manager`.

Implementations for the second are provided in the new
`fiber/addon/verifypeer` package which provides 3 implementations for :

- Mutual TLS.
- Mutual TLS with CRL check (from file, string or in a URL provided)
- mutual TLS with OCSP Stapling (using a URL provided).

Additionally this feature adds a similar interface `ClientTLSProvider`
and easy to use implementation to configure a `*tls.Config` object
for client use in the `client` package. Its usage has been added to the
"Mutual TLS with*" implementations and also could be used in `Storage`
providers instead of a `*tls.Config`. It supports :

- Loading a certificate/key from a file or string
- Loading Root certificates
- Loading system Root certificates
- Setting TLS minimum version

A `SetTLSProvider` function has been added to `Client` to use this
interface instead of the previous function `SetCertificates()`,
`SetRootCertificate()` and `SetRootCertificateFromString`.

Finally new certificates have been added, from an OpenSSL generated PKI :

- Root CA Certificate
  - Expires in 30 years
  - An `index.txt` file (signed certificate status list)
  - Intermediate CA Certificate
    - signed by Root CA (expires in 20 years)
    - An `index.txt` file (signed certificate status list)
    - Certificate Revocation List (v2, in PEM format)
    - Test certificates (expired in 10 years) :
      - server ( includes a SNI for 'localhost' )
      - client
      - revoked
      - expired

Each test certificates have the corresponding files :

- `cert.pem` (certificate)
- `key.pem` (certificate's key, not encrypted)
- `fullchain.pem` (certificate, key and Intermediate CA certificate)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@addon/verifypeer/ocsp_responder_test.go`:
- Around line 283-305: Both parseCertFile and parseKeyFile assume pem.Decode
returned a non-nil block and will panic when block is nil; update these
functions (parseCertFile and parseKeyFile) to check the returned block from
pem.Decode and return a descriptive error (e.g., “invalid PEM block” or include
filename) when block == nil before accessing block.Bytes, then proceed to parse
only when block is valid.

In `@client/client.go`:
- Around line 247-259: SetTLSProvider currently panics on provider errors which
is inconsistent with other setters; change its signature to return (*Client,
error) so runtime TLS provider failures can be handled instead of terminating
the app. In the SetTLSProvider method call provider.ProvideClientTLS(), if err
!= nil return (c, err) (do not logger.Panicf), otherwise call
c.applyTLSConfig(config) as before and return (c, nil); update any callers to
handle the returned error. Use the function/method names SetTLSProvider,
ClientTLSProvider.

In `@docs/addon/verifypeer.md`:
- Around line 97-99: The OCSP example uses the wrong field name: update the
TLSProvider block that constructs a fiber.ServerCertificateProvider to use the
correct field name Certificate (not CertificateChain) to match the
ServerCertificateProvider struct in tls_provider.go; locate the TLSProvider
initializer and replace CertificateChain with Certificate so the example
compiles against the fiber.ServerCertificateProvider type.

In `@listen.go`:
- Around line 157-159: The implementation only invokes cfg.TLSConfigFunc when
cfg.TLSProvider is non-nil, which contradicts the doc comment that tlsConfig may
be nil and TLSConfigFunc can be called independently; modify the code to call
cfg.TLSConfigFunc(tlsConfig) whenever cfg.TLSConfigFunc != nil (regardless of
cfg.TLSProvider) so callers of TLSConfigFunc receive the expected tlsConfig
(possibly nil), and keep or update the doc comment to state that TLSConfigFunc
may be called with a nil tlsConfig.

In `@tls_provider_test.go`:
- Around line 13-16: Add t.Parallel() as the first statement in every test
function in tls_provider_test.go so they opt into parallel execution per project
convention; specifically insert t.Parallel() at the start of
Test_ServerTLSProvider_Version and likewise at the very beginning of the other
Test_* functions in this file (ensure it appears before any setup like modifying
testPreforkMaster or cleanup calls).
♻️ Duplicate comments (3)
docs/api/fiber.md (1)

218-222: Replace hard tab with spaces in interface code block.

Markdownlint flags a hard tab at line 220. As noted in past reviews, please run make markdown after updating the docs.

Proposed fix
 ```go title="Signature"
 type ServerTLSCustomizer interface {
-	  CustomizeServerTLS(config *tls.Config) error
+    CustomizeServerTLS(config *tls.Config) error
 }
</details>

</blockquote></details>
<details>
<summary>docs/addon/verifypeer.md (1)</summary><blockquote>

`7-8`: **Fix grammar in the introduction.**

The sentence has grammatical issues: "aim is to provides" should be "aims to provide".


<details>
<summary>Proposed fix</summary>

```diff
-The VerifyPeer addon package aim is to provides additional `fiber.ServerTLSCustomizer` interface implementations to add means
-to configure a `*tls.Config` 's `VerifyPeerCertificate` field with additional validation for client mTLS connection.
+The VerifyPeer addon package aims to provide additional `fiber.ServerTLSCustomizer` interface implementations
+to configure a `*tls.Config`'s `VerifyPeerCertificate` field with additional validation for client mTLS connections.

As per coding guidelines, please run make markdown after updating the docs.

tls_provider_test.go (1)

108-109: Restore testPreforkMaster after prefork tests.

testPreforkMaster is a package-level flag; setting it without restoring leaks state across tests and can race once tests run in parallel. Capture the prior value and restore it via t.Cleanup().

🔧 Suggested fix (apply to each prefork test)
func Test_ServerTLSProvider_Prefork(t *testing.T) {
-	testPreforkMaster = true
+	prevPreforkMaster := testPreforkMaster
+	testPreforkMaster = true
+	t.Cleanup(func() { testPreforkMaster = prevPreforkMaster })

Also applies to: 137-138

🧹 Nitpick comments (14)
listen_test.go (1)

187-208: Consider adding t.Parallel() to this test.

Per coding guidelines, tests should invoke t.Parallel() at the start. This test doesn't have shared state that would prevent parallel execution.

Suggested change
 func Test_App_Listener_TLS_Listener(t *testing.T) {
+	t.Parallel()
 	// Create tls certificate
 	cer, err := tls.LoadX509KeyPair("./.github/testdata/pki/intermediate/server/cert.pem", "./.github/testdata/pki/intermediate/server/key.pem")
addon/verifypeer/cert.go (2)

25-28: Fix typos in doc comments.

Minor typos in the documentation: "parse" should be "parses" and "certiticate" should be "certificate".

Suggested fix
 // CustomizeServerTLS implements [fiber.ServerTLSCustomizer].
 //
-// It parse the ClientCertificate, either a file path or its content, a PEM encoded CA certificate.
-// It will add the certiticate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert
+// It parses the ClientCertificate, either a file path or its content, a PEM encoded CA certificate.
+// It will add the certificate to config.ClientCAs and set config.ClientAuth to tls.RequireAndVerifyClientCert

44-68: ClientCertificate() can return (nil, nil) silently.

When Certificate is empty, or the PEM block type is not "CERTIFICATE", this method returns (nil, nil). While CustomizeServerTLS handles this gracefully (returns early at line 33-34), callers using ClientCertificate() directly should be aware of this behavior.

Consider returning an error for the case when the PEM block exists but has an unexpected type, as this likely indicates misconfiguration rather than an intentional empty value.

Suggested improvement
 	if block != nil && block.Type == "CERTIFICATE" {
 		cert, err := x509.ParseCertificate(block.Bytes)
 		if err != nil {
 			return nil, fmt.Errorf("tls: cannot load client CA certificate: %w", err)
 		}
 		certificate = cert
+	} else if block != nil {
+		return nil, fmt.Errorf("tls: unexpected PEM block type %q, expected CERTIFICATE", block.Type)
 	}
addon/verifypeer/ocsp.go (2)

101-106: Guard against empty verifiedChains to prevent potential panic.

While VerifyPeerCertificate is typically called with verified chains when ClientAuth is RequireAndVerifyClientCert, accessing verifiedChains[0][0] without bounds checking could panic in edge cases.

Defensive check
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("mtls: no verified certificate chain")
+			}
 			cert := verifiedChains[0][0]
 			opts := &ocsp.RequestOptions{Hash: crypto.SHA256}

142-148: Typo in function name: validateOSCPServer should be validateOCSPServer.

The function name has a typo - "OSCP" should be "OCSP" (Online Certificate Status Protocol).

Fix typo
-func (*MutualTLSWithOCSPCustomizer) validateOSCPServer(ocspServer string) error {
+func (*MutualTLSWithOCSPCustomizer) validateOCSPServer(ocspServer string) error {

Also update the call sites at lines 71 and 77.

addon/verifypeer/crl.go (3)

52-53: Fix interface name in doc comment.

The doc comment still references fiber.ServerCertificateCustomizer, but the interface is fiber.ServerTLSCustomizer.

Fix
-// CustomizeServerTLS implements [fiber.ServerCertificateCustomizer]
+// CustomizeServerTLS implements [fiber.ServerTLSCustomizer]

117-127: Add bounds check for verifiedChains to prevent potential panic.

Same issue as in ocsp.go - accessing verifiedChains[0][0] without checking bounds could panic.

Defensive check
 	if clientCRL != nil {
 		config.VerifyPeerCertificate = func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
+			if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
+				return errors.New("tls: no verified certificate chain")
+			}
 			cert := verifiedChains[0][0]
 			for _, revokedCertificate := range clientCRL.RevokedCertificateEntries {

93-101: Silent failure when fetching CRL from distribution points may mask issues.

When RevocationList is empty, the code attempts to fetch CRL from CRLDistributionPoints but silently ignores all failures. This could leave the server without CRL protection without any indication. Consider logging a warning when all distribution points fail.

Add logging for visibility
+	var fetchErrors []error
 	} else {
 		for _, dist := range clientCACert.CRLDistributionPoints {
 			if distURL, err := m.validateFetchURL(dist); err == nil {
 				if crl, err := m.fetchCRL(distURL); err == nil {
 					crlBytes = crl
 					break
+				} else {
+					fetchErrors = append(fetchErrors, err)
 				}
 			}
 		}
+		if len(crlBytes) == 0 && len(fetchErrors) > 0 {
+			// Consider logging: CRL fetch failed from all distribution points
+		}
 	}
addon/verifypeer/crl_test.go (3)

66-68: Add t.Parallel() to each test function.

Per coding guidelines, all Go tests should invoke t.Parallel() at the start to maximize concurrency.

Proposed fix
 func Test_CRL_File(t *testing.T) {
+	t.Parallel()
 	testCRL(t, "../../.github/testdata/pki/intermediate/crl.pem")
 }

 func Test_CRL_Content(t *testing.T) {
+	t.Parallel()
 	crlFile, err := os.ReadFile(filepath.Clean("../../.github/testdata/pki/intermediate/crl.pem"))
 	require.NoError(t, err)

 	testCRL(t, string(crlFile))
 }

 func Test_CRL_Server(t *testing.T) {
+	t.Parallel()
 	var network string

Also applies to: 70-75, 77-105


30-45: Avoid assert inside goroutines; use t.Errorf or channel-based error propagation.

Per retrieved learnings, the linter does not allow require or assert assertions from within goroutines. Consider capturing errors via a channel or using t.Errorf.

Proposed fix
+	errCh := make(chan error, 1)
 	go func() {
-		assert.NoError(t, app.Listen(":0", fiber.ListenConfig{
+		errCh <- app.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
 				network = addr.String()
 			},
 			TLSProvider: &fiber.ServerCertificateProvider{
 				Certificate: "../../.github/testdata/pki/intermediate/server/fullchain.pem",
 			},
 			TLSCustomizer: &verifypeer.MutualTLSWithCRLCustomizer{
 				Certificate:    "../../.github/testdata/pki/intermediate/cacert.pem",
 				RevocationList: crl,
 			},
-		}))
+		})
 	}()

47-49: Replace time.Sleep with a synchronization primitive for server readiness.

Using time.Sleep for waiting on server startup is flaky. Consider using a channel signaled from ListenerAddrFunc to wait until the listener is ready.

Example approach
ready := make(chan struct{})
ListenerAddrFunc: func(addr net.Addr) {
    network = addr.String()
    close(ready)
},
// ...
<-ready // wait for listener
client/tls_provider.go (1)

56-59: Consider avoiding mutation of the receiver struct.

Setting p.TLSMinVersion directly modifies the original ClientCertificateProvider instance. If the same provider is reused with different expectations, this could cause confusion. Consider using a local variable instead.

Proposed fix
 func (p *ClientCertificateProvider) ProvideClientTLS() (*tls.Config, error) {
-	if p.TLSMinVersion == 0 {
-		p.TLSMinVersion = tls.VersionTLS12
-	}
+	minVersion := p.TLSMinVersion
+	if minVersion == 0 {
+		minVersion = tls.VersionTLS12
+	}

 	var tlsConfig *tls.Config

 	initTLS := func() error {
 		if tlsConfig == nil {
 			tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
-				MinVersion: p.TLSMinVersion,
+				MinVersion: minVersion,
 			}
docs/whats_new.md (1)

195-206: Replace hard tabs with spaces in code blocks.

Markdownlint flags hard tabs at lines 197 and 204. Please run make markdown to fix formatting. As per coding guidelines.

Proposed fix
   ```go title="Signature"
   type ServerTLSProvider interface {
-	    ProvideServerTLS() (*tls.Config, error)
+      ProvideServerTLS() (*tls.Config, error)
   }
  • ServerTLSCustomizer : Add functionality to the *tls.Config object, like MutualTLS.
    type ServerTLSCustomizer interface {
  •   CustomizeServerTLS(config *tls.Config) error
    
  •  CustomizeServerTLS(config *tls.Config) error
    
    }
</details>

</blockquote></details>
<details>
<summary>addon/verifypeer/ocsp_test.go (1)</summary><blockquote>

`25-33`: **Propagate `Listen` errors to avoid hangs.**

If `Listen` fails before `ListenerAddrFunc` runs, the main goroutine blocks on the address channel forever. Consider wiring an error channel (or `select` with timeout) so failures surface deterministically and the test doesn’t hang.  


<details>
<summary>♻️ Sketch of a safer pattern</summary>

```diff
-	staplerAddrCh := make(chan string, 1)
+	staplerAddrCh := make(chan string, 1)
+	staplerErrCh := make(chan error, 1)
...
 	go func() {
-		assert.NoError(t, stapler.Listen(":0", fiber.ListenConfig{
+		staplerErrCh <- stapler.Listen(":0", fiber.ListenConfig{
 			DisableStartupMessage: true,
 			ListenerNetwork:       fiber.NetworkTCP4,
 			ListenerAddrFunc: func(addr net.Addr) {
 				staplerAddrCh <- addr.String()
 			},
-		}))
+		})
 	}()
-	ocspAddr := <-staplerAddrCh
+	select {
+	case ocspAddr := <-staplerAddrCh:
+		// use ocspAddr
+	case err := <-staplerErrCh:
+		require.NoError(t, err)
+	}

Also applies to: 45-60

Comment on lines +283 to +305
func parseCertFile(filename string) (*x509.Certificate, error) {
ct, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(ct)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return cert, nil
}

// parses a PEM encoded PKCS8 private key (RSA only)
func parseKeyFile(filename string) (any, error) {
kt, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(kt)
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against invalid PEM blocks to avoid panics.

pem.Decode can return nil on malformed input; accessing block.Bytes will panic. Return a clear error instead so tests fail predictably.

✅ Proposed fix
func parseCertFile(filename string) (*x509.Certificate, error) {
 	ct, err := os.ReadFile(filepath.Clean(filename))
 	if err != nil {
 		return nil, fmt.Errorf("%w", err)
 	}
 	block, _ := pem.Decode(ct)
+	if block == nil {
+		return nil, errors.New("tls: invalid PEM certificate")
+	}
 	cert, err := x509.ParseCertificate(block.Bytes)
 	if err != nil {
 		return nil, fmt.Errorf("%w", err)
 	}
 	return cert, nil
}

func parseKeyFile(filename string) (any, error) {
 	kt, err := os.ReadFile(filepath.Clean(filename))
 	if err != nil {
 		return nil, fmt.Errorf("%w", err)
 	}
 	block, _ := pem.Decode(kt)
+	if block == nil {
+		return nil, errors.New("tls: invalid PEM key")
+	}
 	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
 	if err != nil {
 		return nil, fmt.Errorf("%w", err)
 	}
 	return key, nil
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func parseCertFile(filename string) (*x509.Certificate, error) {
ct, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(ct)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return cert, nil
}
// parses a PEM encoded PKCS8 private key (RSA only)
func parseKeyFile(filename string) (any, error) {
kt, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(kt)
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
func parseCertFile(filename string) (*x509.Certificate, error) {
ct, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(ct)
if block == nil {
return nil, errors.New("tls: invalid PEM certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return cert, nil
}
// parses a PEM encoded PKCS8 private key (RSA only)
func parseKeyFile(filename string) (any, error) {
kt, err := os.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, fmt.Errorf("%w", err)
}
block, _ := pem.Decode(kt)
if block == nil {
return nil, errors.New("tls: invalid PEM key")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return key, nil
}
🤖 Prompt for AI Agents
In `@addon/verifypeer/ocsp_responder_test.go` around lines 283 - 305, Both
parseCertFile and parseKeyFile assume pem.Decode returned a non-nil block and
will panic when block is nil; update these functions (parseCertFile and
parseKeyFile) to check the returned block from pem.Decode and return a
descriptive error (e.g., “invalid PEM block” or include filename) when block ==
nil before accessing block.Bytes, then proceed to parse only when block is
valid.

Comment on lines +247 to 259
// SetTLSProvider lets you configure TLS through a [ClientTLSProvider]
func (c *Client) SetTLSProvider(provider ClientTLSProvider) *Client {
c.mu.Lock()
defer c.mu.Unlock()
if provider != nil {
if config, err := provider.ProvideClientTLS(); err != nil {
c.logger.Panicf("client: unable to set tlsConfig: %v", err)
} else {
c.applyTLSConfig(config)
}
}()

pem, err := io.ReadAll(file)
if err != nil {
c.logger.Panicf("client: %v", err)
}

config := c.TLSConfig()
if config.RootCAs == nil {
config.RootCAs = x509.NewCertPool()
}

if !config.RootCAs.AppendCertsFromPEM(pem) {
c.logger.Panicf("client: %v", ErrFailedToAppendCert)
}

return c
}

// SetRootCertificateFromString adds one or more root certificates from a string to the client's TLS configuration.
func (c *Client) SetRootCertificateFromString(pem string) *Client {
config := c.TLSConfig()

if config.RootCAs == nil {
config.RootCAs = x509.NewCertPool()
}

if !config.RootCAs.AppendCertsFromPEM([]byte(pem)) {
c.logger.Panicf("client: %v", ErrFailedToAppendCert)
}

return c
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's see the panic patterns in client.go
rg -n "Panicf|panic\(" client/client.go -B 2 -A 2

Repository: gofiber/fiber

Length of output: 816


🏁 Script executed:

# Find the applyTLSConfig implementation to understand nil handling
rg -n "func.*applyTLSConfig" client/client.go -A 15

Repository: gofiber/fiber

Length of output: 544


🏁 Script executed:

# Check the ClientTLSProvider interface definition
rg -n "type ClientTLSProvider" -A 5

Repository: gofiber/fiber

Length of output: 872


🏁 Script executed:

# Look at NewWithClient to compare error handling patterns
rg -n "func NewWithClient" -A 30

Repository: gofiber/fiber

Length of output: 2918


🏁 Script executed:

# Check if other setter methods in client.go return errors or panic
rg -n "func \(c \*Client\) Set" client/client.go -A 10 | head -50

Repository: gofiber/fiber

Length of output: 1632


🏁 Script executed:

# Look for how TLS is typically set up in examples or tests
fd -e md client/ | head -10 | xargs grep -l "SetTLSProvider\|TLS" 2>/dev/null | head -3

Repository: gofiber/fiber

Length of output: 390


🏁 Script executed:

# Check error handling patterns in other setter methods
rg -n "return.*error|Panicf|panic" client/client.go | grep -E "Set[A-Z]|func" | head -20

Repository: gofiber/fiber

Length of output: 39


The nil config handling is safe; clarify if aggressive panic is intentional.

The applyTLSConfig(nil) call is safe—nil is a valid *tls.Config value, and the interface explicitly documents that implementations may return (nil, nil) if no configuration is needed. This concern can be removed.

However, SetTLSProvider is unique among setter methods in client.go (e.g., SetJSONMarshal, SetXMLMarshal, SetCBORMarshal)—all other setters simply assign and return without error handling. Panicking via logger.Panicf on provider errors is aggressive compared to this pattern.

If TLS provider failures during initialization are intended to be treated as startup failures (consistent with Fiber's design philosophy), this is acceptable and the approach aligns with how constructors handle validation errors. However, if SetTLSProvider is also used for runtime TLS reconfiguration, consider returning an error instead to avoid unexpected application termination.

🤖 Prompt for AI Agents
In `@client/client.go` around lines 247 - 259, SetTLSProvider currently panics on
provider errors which is inconsistent with other setters; change its signature
to return (*Client, error) so runtime TLS provider failures can be handled
instead of terminating the app. In the SetTLSProvider method call
provider.ProvideClientTLS(), if err != nil return (c, err) (do not
logger.Panicf), otherwise call c.applyTLSConfig(config) as before and return (c,
nil); update any callers to handle the returned error. Use the function/method
names SetTLSProvider, ClientTLSProvider.

Comment on lines +97 to +99
TLSProvider: &fiber.ServerCertificateProvider{
CertificateChain: "./certificate.pem",
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect field name in OCSP example.

The example uses CertificateChain but the ServerCertificateProvider struct uses Certificate as the field name (per tls_provider.go).

Proposed fix
             TLSProvider: &fiber.ServerCertificateProvider{
-                CertificateChain: "./certificate.pem",
+                Certificate: "./certificate.pem",
             },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TLSProvider: &fiber.ServerCertificateProvider{
CertificateChain: "./certificate.pem",
},
TLSProvider: &fiber.ServerCertificateProvider{
Certificate: "./certificate.pem",
},
🤖 Prompt for AI Agents
In `@docs/addon/verifypeer.md` around lines 97 - 99, The OCSP example uses the
wrong field name: update the TLSProvider block that constructs a
fiber.ServerCertificateProvider to use the correct field name Certificate (not
CertificateChain) to match the ServerCertificateProvider struct in
tls_provider.go; locate the TLSProvider initializer and replace CertificateChain
with Certificate so the example compiles against the
fiber.ServerCertificateProvider type.

Comment on lines +157 to 159
if cfg.TLSProvider != nil && cfg.TLSConfigFunc != nil {
cfg.TLSConfigFunc(tlsConfig)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistency between TLSConfigFunc documentation and implementation.

The doc comment (lines 57-59) states that tlsConfig may be nil, implying TLSConfigFunc could be called with a nil config. However, the implementation only calls TLSConfigFunc when TLSProvider != nil. This means if a user sets only TLSConfigFunc (without TLSProvider), it will never be invoked.

Consider either:

  1. Updating the docs to clarify TLSConfigFunc is only called when TLSProvider is set, or
  2. Changing the condition to always call TLSConfigFunc when it's set (and document that tlsConfig may be nil).
🤖 Prompt for AI Agents
In `@listen.go` around lines 157 - 159, The implementation only invokes
cfg.TLSConfigFunc when cfg.TLSProvider is non-nil, which contradicts the doc
comment that tlsConfig may be nil and TLSConfigFunc can be called independently;
modify the code to call cfg.TLSConfigFunc(tlsConfig) whenever cfg.TLSConfigFunc
!= nil (regardless of cfg.TLSProvider) so callers of TLSConfigFunc receive the
expected tlsConfig (possibly nil), and keep or update the doc comment to state
that TLSConfigFunc may be called with a nil tlsConfig.

Comment on lines +13 to +16
func Test_ServerTLSProvider_Version(t *testing.T) {
prevPreforkMaster := testPreforkMaster
testPreforkMaster = true
t.Cleanup(func() { testPreforkMaster = prevPreforkMaster })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add t.Parallel() at the start of each test.

These new tests should opt into parallel execution per project convention. Please add t.Parallel() as the first line of each Test_* function in this file.
As per coding guidelines, please apply this to all tests here.

♻️ Example fix (apply similarly to all tests)
func Test_ServerTLSProvider_Version(t *testing.T) {
+	t.Parallel()
 	prevPreforkMaster := testPreforkMaster
 	testPreforkMaster = true
 	t.Cleanup(func() { testPreforkMaster = prevPreforkMaster })

Also applies to: 59-60, 82-83, 107-109, 136-138

🤖 Prompt for AI Agents
In `@tls_provider_test.go` around lines 13 - 16, Add t.Parallel() as the first
statement in every test function in tls_provider_test.go so they opt into
parallel execution per project convention; specifically insert t.Parallel() at
the start of Test_ServerTLSProvider_Version and likewise at the very beginning
of the other Test_* functions in this file (ensure it appears before any setup
like modifying testPreforkMaster or cleanup calls).

@ReneWerner87
Copy link
Member

ReneWerner87 commented Jan 20, 2026

@gaby @simonnix

Okay, I agree, that looks a bit overdeveloped.

What problem are we trying to solve?
You can already do all that with app.Listener, right?

If prefork is the problem, then we should solve it.
If the user has a listener via reuseport.Listen, you could also allow the prefork concept in the app.Listener method and use the listener, its just a small adjustment.

fiber/listen.go

Line 286 in 5ca0c1e

log.Warn("Prefork isn't supported for custom listeners.")

if ln, err = reuseport.Listen(cfg.ListenerNetwork, addr); err != nil {

Another option would be to extend app.ListerConfig with a tlsConfig.

fiber/listen.go

Lines 44 to 127 in 5ca0c1e

type ListenConfig struct {
// GracefulContext is a field to shutdown Fiber by given context gracefully.
//
// Default: nil
GracefulContext context.Context `json:"graceful_context"` //nolint:containedctx // It's needed to set context inside Listen.
// TLSConfigFunc allows customizing tls.Config as you want.
//
// Default: nil
TLSConfigFunc func(tlsConfig *tls.Config) `json:"tls_config_func"`
// ListenerFunc allows accessing and customizing net.Listener.
//
// Default: nil
ListenerAddrFunc func(addr net.Addr) `json:"listener_addr_func"`
// BeforeServeFunc allows customizing and accessing fiber app before serving the app.
//
// Default: nil
BeforeServeFunc func(app *App) error `json:"before_serve_func"`
// AutoCertManager manages TLS certificates automatically using the ACME protocol,
// Enables integration with Let's Encrypt or other ACME-compatible providers.
//
// Default: nil
AutoCertManager *autocert.Manager `json:"auto_cert_manager"`
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "unix" (Unix Domain Sockets)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen.
//
// Default: NetworkTCP4
ListenerNetwork string `json:"listener_network"`
// CertFile is a path of certificate file.
// If you want to use TLS, you have to enter this field.
//
// Default : ""
CertFile string `json:"cert_file"`
// KeyFile is a path of certificate's private key.
// If you want to use TLS, you have to enter this field.
//
// Default : ""
CertKeyFile string `json:"cert_key_file"`
// CertClientFile is a path of client certificate.
// If you want to use mTLS, you have to enter this field.
//
// Default : ""
CertClientFile string `json:"cert_client_file"`
// When the graceful shutdown begins, use this field to set the timeout
// duration. If the timeout is reached, OnPostShutdown will be called with the error.
// Set to 0 to disable the timeout and wait indefinitely.
//
// Default: 10 * time.Second
ShutdownTimeout time.Duration `json:"shutdown_timeout"`
// FileMode to set for Unix Domain Socket (ListenerNetwork must be "unix")
//
// Default: 0770
UnixSocketFileMode os.FileMode `json:"unix_socket_file_mode"`
// TLSMinVersion allows to set TLS minimum version.
//
// Default: tls.VersionTLS12
// WARNING: TLS1.0 and TLS1.1 versions are not supported.
TLSMinVersion uint16 `json:"tls_min_version"`
// When set to true, it will not print out the «Fiber» ASCII art and listening address.
//
// Default: false
DisableStartupMessage bool `json:"disable_startup_message"`
// When set to true, this will spawn multiple Go processes listening on the same port.
//
// Default: false
EnablePrefork bool `json:"enable_prefork"`
// If set to true, will print all routes with their method, path and handler.
//
// Default: false
EnablePrintRoutes bool `json:"enable_print_routes"`
}

Currently, this is created based on the cert files -> but you could also make it so that tlsConfig is checked first, and if it is not available, it is created via cert files.

fiber/listen.go

Lines 170 to 211 in 5ca0c1e

var tlsConfig *tls.Config
if cfg.CertFile != "" && cfg.CertKeyFile != "" {
cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.CertKeyFile)
if err != nil {
return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %w", cfg.CertFile, cfg.CertKeyFile, err)
}
tlsHandler := &TLSHandler{}
tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
MinVersion: cfg.TLSMinVersion,
Certificates: []tls.Certificate{
cert,
},
GetCertificate: tlsHandler.GetClientInfo,
}
if cfg.CertClientFile != "" {
clientCACert, err := os.ReadFile(filepath.Clean(cfg.CertClientFile))
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(clientCACert)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = clientCertPool
}
// Attach the tlsHandler to the config
app.SetTLSHandler(tlsHandler)
} else if cfg.AutoCertManager != nil {
tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
MinVersion: cfg.TLSMinVersion,
GetCertificate: cfg.AutoCertManager.GetCertificate,
NextProtos: []string{"http/1.1", "acme-tls/1"},
}
}
if tlsConfig != nil && cfg.TLSConfigFunc != nil {
cfg.TLSConfigFunc(tlsConfig)
}

Depending on what the problem is that needs to be solved,
we could use simpler options here.

A small side note on how prefork works:
The master process starts new processes with the same parameters as the master process was started with and adds a marker
so that you know that it is a child process.
The same creation of the listener config or the listener now runs within the child processes, and this is then used.

fiber/prefork.go

Lines 100 to 118 in 5ca0c1e

for range maxProcs {
cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec // It's fine to launch the same process again
if testPreforkMaster {
// When test prefork master,
// just start the child process with a dummy cmd,
// which will exit soon
cmd = dummyCmd()
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// add fiber prefork child flag into child proc env
cmd.Env = append(os.Environ(),
fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal),
)
if err = cmd.Start(); err != nil {
return fmt.Errorf("failed to start a child prefork process, error: %w", err)
}

@ReneWerner87
Copy link
Member

solved by #4042

@github-project-automation github-project-automation bot moved this to Done in v3 Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants