Skip to content

Add GetTLSMaterial impl in RuntimeCryptoProvider#3031

Open
JeethJJ wants to merge 1 commit into
thunder-id:mainfrom
JeethJJ:thunder-km-8
Open

Add GetTLSMaterial impl in RuntimeCryptoProvider#3031
JeethJJ wants to merge 1 commit into
thunder-id:mainfrom
JeethJJ:thunder-km-8

Conversation

@JeethJJ
Copy link
Copy Markdown
Contributor

@JeethJJ JeethJJ commented May 27, 2026

Purpose

Implements GetTLSMaterial in the default key manager.

The implementation wires through the existing LoadTLSConfig utility and exposes the result via the RuntimeCryptoProvider interface so that main.go no longer calls PKI internals directly.

Approach

  • Extended TLSMaterial with a MinVersion uint16 field so callers receive a ready-to-use TLS config without needing to know the minimum version policy.
  • Dropped the unused *KeyRef parameter from GetTLSMaterial — TLS cert identity is not key-ref-based; it comes from server config paths.
  • Added GetTLSConfig() (*tls.Config, error) to PKIServiceInterface, implemented by delegating to LoadTLSConfig with the server's cfg.TLS paths.
  • runtimeCryptoService.GetTLSMaterial now calls pkiService.GetTLSConfig() and maps the result to TLSMaterial.
  • loadCertConfig in main.go updated to accept a RuntimeCryptoProvider instead of calling PKI internals directly; registerServices now returns the provider alongside JWTServiceInterface.
  • Regenerated mocks (PKIServiceInterfaceMock, RuntimeCryptoProviderMock) to reflect interface changes.

Related Issues

Summary by CodeRabbit

  • Improvements
    • TLS/HTTPS certificate configuration now integrates with runtime crypto provider instead of disk-based loading for enhanced security and flexibility
    • Simplified certificate retrieval API by streamlining parameters
    • Enhanced flexibility with support for configurable minimum TLS version requirements
    • Improved separation of concerns in crypto provider architecture

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd718bc4-c318-4981-a332-2c402417291e

📥 Commits

Reviewing files that changed from the base of the PR and between 0e9369d and 5823066.

⛔ Files ignored due to path filters (2)
  • backend/tests/mocks/crypto/cryptomock/RuntimeCryptoProvider_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/crypto/pki/pkimock/PKIServiceInterface_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (8)
  • backend/.mockery.public.yml
  • backend/cmd/server/main.go
  • backend/cmd/server/servicemanager.go
  • backend/internal/system/kmprovider/common/interface.go
  • backend/internal/system/kmprovider/defaultkm/pki/service.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider_test.go
  • backend/internal/system/kmprovider/init.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • backend/.mockery.public.yml
  • backend/cmd/server/servicemanager.go
  • backend/internal/system/kmprovider/init.go
  • backend/internal/system/kmprovider/defaultkm/pki/service.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider_test.go
  • backend/cmd/server/main.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go
  • backend/internal/system/kmprovider/common/interface.go

📝 Walkthrough

Walkthrough

This PR migrates TLS material loading from file-path construction in main.go to the RuntimeCryptoProvider.GetTLSMaterial interface. The GetTLSMaterial signature removes an unused KeyRef parameter, TLSMaterial gains a MinVersion field, and the PKI service exposes GetTLSConfig() to load certificates from disk.

Changes

TLS Material Loading Migration

Layer / File(s) Summary
TLS material interface contracts
backend/internal/system/kmprovider/common/interface.go
RuntimeCryptoProvider.GetTLSMaterial drops the *KeyRef parameter; TLSMaterial struct adds MinVersion uint16 field to carry minimum TLS version from cert config.
PKI service TLS configuration
backend/internal/system/kmprovider/defaultkm/pki/service.go
PKIServiceInterface gains GetTLSConfig() method. Implementation constructs cert/key file paths from serverRuntime.ServerHome and config, then delegates to LoadTLSConfig.
RuntimeCryptoProvider TLS material implementation
backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go
GetTLSMaterial validates pkiService is initialized, calls pkiService.GetTLSConfig(), extracts the first certificate and MinVersion, and returns a populated TLSMaterial struct.
Server startup integration
backend/cmd/server/main.go, backend/cmd/server/servicemanager.go
registerServices returns both jwtService and runtimeCryptoSvc. Startup loadCertConfig now accepts RuntimeCryptoProvider, calls GetTLSMaterial(), and builds tls.Config from the returned material instead of loading files from disk.
Tests, exports, and configuration
backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider_test.go, backend/internal/system/kmprovider/init.go, backend/.mockery.public.yml
Three unit tests cover nil PKI service, GetTLSConfig error propagation, and successful material construction with MinVersion. Type alias RuntimeCryptoProvider exported from kmprovider package. Mockery config package path updated from pkiservice to pki.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • thunder-id/thunderid#2552: Introduces initial runtimeCryptoService.GetTLSMaterial and PKI service integration; this PR refines the interface by removing KeyRef and adding MinVersion to TLSMaterial.
  • thunder-id/thunderid#2681: Wires runtimeCryptoSvc into JOSE/JWT initialization via servicemanager.go and jose init signatures; this PR completes the wiring by integrating it into server TLS startup.
  • thunder-id/thunderid#2657: Refactors public-key discovery to use RuntimeCryptoProvider; both PRs migrate away from direct pkiService dependencies to the runtime crypto abstraction.

Suggested reviewers

  • senthalan
  • ThaminduDilshan
  • hwupathum
  • thiva-k
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing GetTLSMaterial in RuntimeCryptoProvider, which is the primary objective of the PR.
Description check ✅ Passed The PR description covers Purpose, Approach, and Related Issues sections. It clearly explains the implementation, design decisions, and rationale for interface changes.
Linked Issues check ✅ Passed All code requirements from issue #2350 are met: GetTLSMaterial is implemented, TLSMaterial now includes MinVersion, TLS config construction uses RuntimeCryptoProvider instead of direct file paths.
Out of Scope Changes check ✅ Passed All changes are within scope of #2350: interface refactoring, TLSMaterial enrichment, PKI service updates, main.go refactoring, and test additions directly support the migration objective.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
backend/internal/oauth/oauth2/authz/service.go (1)

428-437: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Differentiate context failures from invalid assertion failures.

On Line 579, all VerifyJWT errors are collapsed into "invalid assertion signature", and on Line 428 that becomes an invalid_request response. With context-aware verification, context.Canceled/context.DeadlineExceeded should be treated as server-side failures, not client assertion errors.

Suggested fix
@@
-		if err := as.verifyAssertion(ctx, assertion); err != nil {
+		if err := as.verifyAssertion(ctx, assertion); err != nil {
+			if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+				authErr = &AuthorizationError{
+					Code:    oauth2const.ErrorServerError,
+					Message: "Failed to process authorization request",
+				}
+				return err
+			}
 			as.logger.Debug("Assertion verification failed", log.Error(err))
@@
 func (as *authorizeService) verifyAssertion(ctx context.Context, assertion string) error {
 	if err := as.jwtService.VerifyJWT(ctx, assertion, "", ""); err != nil {
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+			return err
+		}
 		as.logger.Debug("Invalid assertion signature", log.String("error", err.Error.DefaultValue))
 		return errors.New("invalid assertion signature")
 	}
 	return nil
 }

Also applies to: 578-582

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/authz/service.go` around lines 428 - 437, The
verification path currently treats all VerifyJWT errors as assertion failures
and turns them into an invalid_request AuthorizationError in as.verifyAssertion
(seen where as.verifyAssertion is called and the AuthorizationError is
constructed); change this so context errors are recognized and handled as
server-side failures: in VerifyJWT (and/or as.verifyAssertion) check
errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) and
return or propagate a distinct server error (so the caller does not map it to
oauth2const.ErrorInvalidRequest) — ensure the caller that builds the
AuthorizationError (the block that logs "Assertion verification failed" and sets
Code to ErrorInvalidRequest) treats context.Canceled/DeadlineExceeded as an
internal/server error path (or bubbles up the context error) rather than marking
the client assertion invalid.
backend/internal/oauth/oauth2/introspect/service.go (1)

60-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid treating context timeout/cancel as an inactive token result.

With Line 82 now using request context, context.Canceled and context.DeadlineExceeded can occur. The current bool-only path (Line 60) maps these to active=false, which masks transient/server-side failures as token invalidation.

Suggested fix
@@
-	if !s.validateToken(ctx, logger, token) {
+	valid, validationErr := s.validateToken(ctx, logger, token)
+	if validationErr != nil {
+		return nil, validationErr
+	}
+	if !valid {
 		return &IntrospectResponse{
 			Active: false,
 		}, nil
 	}
@@
-func (s *tokenIntrospectionService) validateToken(ctx context.Context, logger *log.Logger, token string) bool {
+func (s *tokenIntrospectionService) validateToken(ctx context.Context, logger *log.Logger, token string) (bool, error) {
 	if err := s.jwtService.VerifyJWT(ctx, token, "", ""); err != nil {
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+			return false, err
+		}
 		logger.Debug("Failed to verify refresh token", log.String("error", err.Error.DefaultValue))
-		return false
+		return false, nil
 	}
-	return true
+	return true, nil
 }

Also applies to: 81-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/introspect/service.go` around lines 60 - 64,
The current call to s.validateToken(ctx, logger, token) treats any failure
(including context.Canceled or context.DeadlineExceeded) as an inactive token;
change the flow so context cancellations/timeouts are propagated as errors
instead of mapping to Active=false: update s.validateToken to return (bool,
error) or return a distinct error for context issues, then in the caller (the
introspect path that builds IntrospectResponse) check the error and if ctx.Err()
or an error equal to context.Canceled/context.DeadlineExceeded is present,
return that error (or a 5xx/internal error) rather than returning
&IntrospectResponse{Active:false}, otherwise use the boolean to set Active.
Ensure you reference s.validateToken and the IntrospectResponse Active branch
when making the change.
backend/internal/system/security/jwt_authenticator_test.go (1)

288-288: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hardcoded brand string literal found

Line 288 contains "ThunderID" in a raw string literal. Is this hardcoded brand name intentional? If not, please replace it with a named constant (or runtime/config-sourced value where appropriate) instead of embedding the brand directly.

As per coding guidelines: **/*.go: Scan for hardcoded occurrences of the string literals Thunder or ThunderID in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/system/security/jwt_authenticator_test.go` at line 288, The
test contains a hardcoded brand string "ThunderID" (seen in the test case name
for the JWT authenticator tests); replace this literal with a named constant
(e.g., ThunderIDBrand) defined near the top of the test file or in a test
constants block and use that constant in the test case `name` and any other
occurrences in `jwt_authenticator_test.go` to avoid embedding the brand
directly; ensure the constant is used consistently (instead of the raw string)
so future scans for `Thunder`/`ThunderID` detect the centralized symbol.
backend/internal/oauth/oauth2/tokenservice/validator_test.go (1)

529-535: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix AssertNotCalled argument list for VerifyJWTSignature
VerifyJWTSignature is mocked as VerifyJWTSignature(ctx, jwtToken), but the untrusted-issuer assertion uses AssertNotCalled(..., "VerifyJWTSignature", tokenInvalid) (missing the ctx arg). Change it to suite.mockJWTService.AssertNotCalled(suite.T(), "VerifyJWTSignature", mock.Anything, tokenInvalid) (or assert the method is never called at all).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/oauth/oauth2/tokenservice/validator_test.go` around lines
529 - 535, The test currently asserts the JWT verifier wasn't called using
suite.mockJWTService.AssertNotCalled(suite.T(), "VerifyJWTSignature",
tokenInvalid) but VerifyJWTSignature is defined as VerifyJWTSignature(ctx,
jwtToken); update the assertion in the ValidateSubjectToken test to include the
context placeholder so it matches the mocked signature (e.g., use mock.Anything
for the ctx), i.e. call suite.mockJWTService.AssertNotCalled(suite.T(),
"VerifyJWTSignature", mock.Anything, tokenInvalid) or alternatively assert the
method was never called at all for mockJWTService to ensure
VerifyJWTSignature(ctx, jwtToken) was not invoked for tokenInvalid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/cmd/server/main.go`:
- Around line 167-170: The loadCertConfig function currently calls
runtimeSvc.GetTLSMaterial with context.Background(), which can block
indefinitely; change it to use a bounded context (e.g., context.WithTimeout)
when calling runtimeSvc.GetTLSMaterial inside loadCertConfig, create the timeout
context with a reasonable startup deadline, defer the cancel function, use that
context in the GetTLSMaterial call, and ensure error handling detects and
surfaces context deadline/cancellation errors appropriately so startup won't
hang.

In `@backend/internal/system/jose/jwe/service.go`:
- Around line 167-170: The current handling of Decrypt errors treats all
provider errors as ErrorJWEDecryptionFailed; change the error branch after
calling js.cryptoProvider.Decrypt(ctx, &js.keyRef, params, encryptedKey) to
detect context cancellation/timeouts and propagate them instead of converting to
ErrorJWEDecryptionFailed: if errors.Is(err, context.Canceled) || errors.Is(err,
context.DeadlineExceeded) (or simply if ctx.Err() != nil) return nil, ctx.Err()
(or return the original err) so request aborts are surfaced, otherwise log and
return &ErrorJWEDecryptionFailed; reference the call to
js.cryptoProvider.Decrypt, the js.logger.Error line, and the
ErrorJWEDecryptionFailed symbol when making the change.

In `@backend/internal/system/jose/jwt/service.go`:
- Around line 231-233: The code currently maps every error from
VerifyJWTSignature to ErrorInvalidTokenSignature; instead, change the error
handling in the caller (the block around VerifyJWTSignature in service.go) to
preserve operational/context errors and only convert true signature-failure
errors to ErrorInvalidTokenSignature: call js.VerifyJWTSignature(ctx, jwtToken),
and if it returns an error, check for context.Canceled/context.DeadlineExceeded
(errors.Is) and any provider/lookup errors (the specific sentinel or typed
errors returned by VerifyJWTSignature) and return those errors as-is (or
wrapped) while only returning &ErrorInvalidTokenSignature when the error
indicates a real signature/validation failure; reference VerifyJWTSignature and
ErrorInvalidTokenSignature to locate and update the conditional.

In `@backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go`:
- Around line 200-207: The code returns kmprovider.TLSMaterial using
tlsCfg.Certificates[0] without nil/length checks; modify the code around the
call to s.pkiService.GetTLSConfig() (the tlsCfg variable) to validate that
tlsCfg != nil and that len(tlsCfg.Certificates) > 0 before indexing, and if
either check fails return a clear error (e.g., "no TLS config" or "no
certificates available") instead of risking a panic when constructing
kmprovider.TLSMaterial.

---

Outside diff comments:
In `@backend/internal/oauth/oauth2/authz/service.go`:
- Around line 428-437: The verification path currently treats all VerifyJWT
errors as assertion failures and turns them into an invalid_request
AuthorizationError in as.verifyAssertion (seen where as.verifyAssertion is
called and the AuthorizationError is constructed); change this so context errors
are recognized and handled as server-side failures: in VerifyJWT (and/or
as.verifyAssertion) check errors.Is(err, context.Canceled) || errors.Is(err,
context.DeadlineExceeded) and return or propagate a distinct server error (so
the caller does not map it to oauth2const.ErrorInvalidRequest) — ensure the
caller that builds the AuthorizationError (the block that logs "Assertion
verification failed" and sets Code to ErrorInvalidRequest) treats
context.Canceled/DeadlineExceeded as an internal/server error path (or bubbles
up the context error) rather than marking the client assertion invalid.

In `@backend/internal/oauth/oauth2/introspect/service.go`:
- Around line 60-64: The current call to s.validateToken(ctx, logger, token)
treats any failure (including context.Canceled or context.DeadlineExceeded) as
an inactive token; change the flow so context cancellations/timeouts are
propagated as errors instead of mapping to Active=false: update s.validateToken
to return (bool, error) or return a distinct error for context issues, then in
the caller (the introspect path that builds IntrospectResponse) check the error
and if ctx.Err() or an error equal to context.Canceled/context.DeadlineExceeded
is present, return that error (or a 5xx/internal error) rather than returning
&IntrospectResponse{Active:false}, otherwise use the boolean to set Active.
Ensure you reference s.validateToken and the IntrospectResponse Active branch
when making the change.

In `@backend/internal/oauth/oauth2/tokenservice/validator_test.go`:
- Around line 529-535: The test currently asserts the JWT verifier wasn't called
using suite.mockJWTService.AssertNotCalled(suite.T(), "VerifyJWTSignature",
tokenInvalid) but VerifyJWTSignature is defined as VerifyJWTSignature(ctx,
jwtToken); update the assertion in the ValidateSubjectToken test to include the
context placeholder so it matches the mocked signature (e.g., use mock.Anything
for the ctx), i.e. call suite.mockJWTService.AssertNotCalled(suite.T(),
"VerifyJWTSignature", mock.Anything, tokenInvalid) or alternatively assert the
method was never called at all for mockJWTService to ensure
VerifyJWTSignature(ctx, jwtToken) was not invoked for tokenInvalid.

In `@backend/internal/system/security/jwt_authenticator_test.go`:
- Line 288: The test contains a hardcoded brand string "ThunderID" (seen in the
test case name for the JWT authenticator tests); replace this literal with a
named constant (e.g., ThunderIDBrand) defined near the top of the test file or
in a test constants block and use that constant in the test case `name` and any
other occurrences in `jwt_authenticator_test.go` to avoid embedding the brand
directly; ensure the constant is used consistently (instead of the raw string)
so future scans for `Thunder`/`ThunderID` detect the centralized symbol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d1ac276e-a409-4bdb-ae51-fe83430c8a5f

📥 Commits

Reviewing files that changed from the base of the PR and between 24d4d00 and dbf74ef.

⛔ Files ignored due to path filters (6)
  • backend/tests/mocks/crypto/cryptomock/RuntimeCryptoProvider_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/crypto/pki/pkimock/PKIServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/jose/jwemock/JWEServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/jose/jwtmock/JWTServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/oauth/oauth2/tokenservicemock/TokenBuilderInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/oauth/oauth2/tokenservicemock/TokenValidatorInterface_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (49)
  • backend/.mockery.public.yml
  • backend/cmd/server/main.go
  • backend/cmd/server/servicemanager.go
  • backend/internal/authn/consent/service.go
  • backend/internal/authn/consent/service_test.go
  • backend/internal/authn/magiclink/service.go
  • backend/internal/authn/magiclink/service_test.go
  • backend/internal/authn/service.go
  • backend/internal/authn/service_test.go
  • backend/internal/notification/otp_service.go
  • backend/internal/notification/otp_service_test.go
  • backend/internal/oauth/jwks/JWKSServiceInterface_mock_test.go
  • backend/internal/oauth/jwks/handler.go
  • backend/internal/oauth/jwks/handler_test.go
  • backend/internal/oauth/jwks/service.go
  • backend/internal/oauth/jwks/service_test.go
  • backend/internal/oauth/oauth2/authz/service.go
  • backend/internal/oauth/oauth2/authz/service_test.go
  • backend/internal/oauth/oauth2/dpop/context.go
  • backend/internal/oauth/oauth2/granthandlers/authorization_code.go
  • backend/internal/oauth/oauth2/granthandlers/authorization_code_test.go
  • backend/internal/oauth/oauth2/granthandlers/client_credentials.go
  • backend/internal/oauth/oauth2/granthandlers/client_credentials_test.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token.go
  • backend/internal/oauth/oauth2/granthandlers/refresh_token_test.go
  • backend/internal/oauth/oauth2/granthandlers/token_exchange.go
  • backend/internal/oauth/oauth2/granthandlers/token_exchange_test.go
  • backend/internal/oauth/oauth2/introspect/service.go
  • backend/internal/oauth/oauth2/introspect/service_test.go
  • backend/internal/oauth/oauth2/tokenservice/builder.go
  • backend/internal/oauth/oauth2/tokenservice/builder_test.go
  • backend/internal/oauth/oauth2/tokenservice/model.go
  • backend/internal/oauth/oauth2/tokenservice/validator.go
  • backend/internal/oauth/oauth2/tokenservice/validator_test.go
  • backend/internal/oauth/oauth2/userinfo/service.go
  • backend/internal/oauth/oauth2/userinfo/service_test.go
  • backend/internal/system/jose/jwe/service.go
  • backend/internal/system/jose/jwe/service_test.go
  • backend/internal/system/jose/jwt/service.go
  • backend/internal/system/jose/jwt/service_test.go
  • backend/internal/system/kmprovider/common/interface.go
  • backend/internal/system/kmprovider/defaultkm/pki/service.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go
  • backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider_test.go
  • backend/internal/system/kmprovider/init.go
  • backend/internal/system/mcp/auth/token_verifier.go
  • backend/internal/system/mcp/auth/token_verifier_test.go
  • backend/internal/system/security/jwt_authenticator.go
  • backend/internal/system/security/jwt_authenticator_test.go
💤 Files with no reviewable changes (2)
  • backend/internal/oauth/oauth2/tokenservice/model.go
  • backend/internal/oauth/oauth2/dpop/context.go

Comment on lines +167 to 170
// loadCertConfig loads the TLS material via the runtime crypto provider.
func loadCertConfig(logger *log.Logger, runtimeSvc kmprovider.RuntimeCryptoProvider) *tls.Config {
mat, err := runtimeSvc.GetTLSMaterial(context.Background())
if err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a bounded context for TLS material loading at startup.

Line 169 uses context.Background(), so TLS material fetch can block startup indefinitely. Prefer a timeout-bound context.

Proposed fix
 const shutdownTimeout = 5 * time.Second
+const tlsMaterialLoadTimeout = 10 * time.Second
@@
 func loadCertConfig(logger *log.Logger, runtimeSvc kmprovider.RuntimeCryptoProvider) *tls.Config {
-	mat, err := runtimeSvc.GetTLSMaterial(context.Background())
+	ctx, cancel := context.WithTimeout(context.Background(), tlsMaterialLoadTimeout)
+	defer cancel()
+	mat, err := runtimeSvc.GetTLSMaterial(ctx)
 	if err != nil {
 		logger.Fatal("Failed to load TLS material", log.Error(err))
 	}
📝 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
// loadCertConfig loads the TLS material via the runtime crypto provider.
func loadCertConfig(logger *log.Logger, runtimeSvc kmprovider.RuntimeCryptoProvider) *tls.Config {
mat, err := runtimeSvc.GetTLSMaterial(context.Background())
if err != nil {
// loadCertConfig loads the TLS material via the runtime crypto provider.
func loadCertConfig(logger *log.Logger, runtimeSvc kmprovider.RuntimeCryptoProvider) *tls.Config {
ctx, cancel := context.WithTimeout(context.Background(), tlsMaterialLoadTimeout)
defer cancel()
mat, err := runtimeSvc.GetTLSMaterial(ctx)
if err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/cmd/server/main.go` around lines 167 - 170, The loadCertConfig
function currently calls runtimeSvc.GetTLSMaterial with context.Background(),
which can block indefinitely; change it to use a bounded context (e.g.,
context.WithTimeout) when calling runtimeSvc.GetTLSMaterial inside
loadCertConfig, create the timeout context with a reasonable startup deadline,
defer the cancel function, use that context in the GetTLSMaterial call, and
ensure error handling detects and surfaces context deadline/cancellation errors
appropriately so startup won't hang.

Comment on lines +167 to 170
cek, err := js.cryptoProvider.Decrypt(ctx, &js.keyRef, params, encryptedKey)
if err != nil {
js.logger.Error("Failed to decrypt CEK: " + err.Error())
return nil, &ErrorJWEDecryptionFailed
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't report canceled decrypts as bad JWE payloads.

Passing ctx into cryptoProvider.Decrypt is correct, but this now makes context.Canceled / deadline failures reachable here. Returning ErrorJWEDecryptionFailed for every provider error hides request aborts and makes callers treat them like malformed tokens.

Suggested fix
 import (
 	"context"
 	"crypto"
 	"encoding/base64"
 	"encoding/json"
+	"errors"
 	"fmt"
@@
 	cek, err := js.cryptoProvider.Decrypt(ctx, &js.keyRef, params, encryptedKey)
 	if err != nil {
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+			return nil, &serviceerror.InternalServerError
+		}
 		js.logger.Error("Failed to decrypt CEK: " + err.Error())
 		return nil, &ErrorJWEDecryptionFailed
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/system/jose/jwe/service.go` around lines 167 - 170, The
current handling of Decrypt errors treats all provider errors as
ErrorJWEDecryptionFailed; change the error branch after calling
js.cryptoProvider.Decrypt(ctx, &js.keyRef, params, encryptedKey) to detect
context cancellation/timeouts and propagate them instead of converting to
ErrorJWEDecryptionFailed: if errors.Is(err, context.Canceled) || errors.Is(err,
context.DeadlineExceeded) (or simply if ctx.Err() != nil) return nil, ctx.Err()
(or return the original err) so request aborts are surfaced, otherwise log and
return &ErrorJWEDecryptionFailed; reference the call to
js.cryptoProvider.Decrypt, the js.logger.Error line, and the
ErrorJWEDecryptionFailed symbol when making the change.

Comment on lines 231 to 233
// First verify signature using the configured server key and algorithm
if err := js.VerifyJWTSignature(jwtToken); err != nil {
if err := js.VerifyJWTSignature(ctx, jwtToken); err != nil {
return &ErrorInvalidTokenSignature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve non-signature failures from VerifyJWTSignature.

Now that VerifyJWTSignature uses the caller's ctx, provider lookup failures and canceled/deadline-exceeded requests are reachable here. Collapsing every error to ErrorInvalidTokenSignature turns those operational failures into bad-token responses for access/refresh-token validation.

Suggested fix
 	// First verify signature using the configured server key and algorithm
 	if err := js.VerifyJWTSignature(ctx, jwtToken); err != nil {
+		if err == &serviceerror.InternalServerError {
+			return err
+		}
 		return &ErrorInvalidTokenSignature
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/system/jose/jwt/service.go` around lines 231 - 233, The code
currently maps every error from VerifyJWTSignature to
ErrorInvalidTokenSignature; instead, change the error handling in the caller
(the block around VerifyJWTSignature in service.go) to preserve
operational/context errors and only convert true signature-failure errors to
ErrorInvalidTokenSignature: call js.VerifyJWTSignature(ctx, jwtToken), and if it
returns an error, check for context.Canceled/context.DeadlineExceeded
(errors.Is) and any provider/lookup errors (the specific sentinel or typed
errors returned by VerifyJWTSignature) and return those errors as-is (or
wrapped) while only returning &ErrorInvalidTokenSignature when the error
indicates a real signature/validation failure; reference VerifyJWTSignature and
ErrorInvalidTokenSignature to locate and update the conditional.

Comment on lines +200 to +207
tlsCfg, err := s.pkiService.GetTLSConfig()
if err != nil {
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
return &kmprovider.TLSMaterial{
Certificate: tlsCfg.Certificates[0],
MinVersion: tlsCfg.MinVersion,
}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard tlsCfg and certificate slice before indexing.

Line 205 dereferences tlsCfg.Certificates[0] without validating tlsCfg != nil and len(Certificates) > 0, which can panic.

Proposed fix
 	tlsCfg, err := s.pkiService.GetTLSConfig()
 	if err != nil {
 		return nil, fmt.Errorf("failed to load TLS config: %w", err)
 	}
+	if tlsCfg == nil {
+		return nil, errors.New("received nil TLS config")
+	}
+	if len(tlsCfg.Certificates) == 0 {
+		return nil, errors.New("TLS config has no certificates")
+	}
 	return &kmprovider.TLSMaterial{
 		Certificate: tlsCfg.Certificates[0],
 		MinVersion:  tlsCfg.MinVersion,
 	}, 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
tlsCfg, err := s.pkiService.GetTLSConfig()
if err != nil {
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
return &kmprovider.TLSMaterial{
Certificate: tlsCfg.Certificates[0],
MinVersion: tlsCfg.MinVersion,
}, nil
tlsCfg, err := s.pkiService.GetTLSConfig()
if err != nil {
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
if tlsCfg == nil {
return nil, errors.New("received nil TLS config")
}
if len(tlsCfg.Certificates) == 0 {
return nil, errors.New("TLS config has no certificates")
}
return &kmprovider.TLSMaterial{
Certificate: tlsCfg.Certificates[0],
MinVersion: tlsCfg.MinVersion,
}, nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/system/kmprovider/defaultkm/runtime_crypto_provider.go`
around lines 200 - 207, The code returns kmprovider.TLSMaterial using
tlsCfg.Certificates[0] without nil/length checks; modify the code around the
call to s.pkiService.GetTLSConfig() (the tlsCfg variable) to validate that
tlsCfg != nil and that len(tlsCfg.Certificates) > 0 before indexing, and if
either check fails return a clear error (e.g., "no TLS config" or "no
certificates available") instead of risking a panic when constructing
kmprovider.TLSMaterial.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/cmd/server/main.go 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@JeethJJ JeethJJ force-pushed the thunder-km-8 branch 3 times, most recently from b46bf09 to 0e9369d Compare May 27, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate TLS material loading to RuntimeCryptoProvider.GetTLSMaterial

1 participant