Skip to content

Add CacheCustomProviders property to CryptoProviderFactory#3489

Merged
iNinja merged 15 commits into
dev8xfrom
iinglese/cache-custom-providers
Jun 1, 2026
Merged

Add CacheCustomProviders property to CryptoProviderFactory#3489
iNinja merged 15 commits into
dev8xfrom
iinglese/cache-custom-providers

Conversation

@iNinja
Copy link
Copy Markdown
Contributor

@iNinja iNinja commented May 14, 2026

Summary

Adds an opt-in property to enable caching of SignatureProvider instances returned by ICryptoProvider.Create(). Currently, providers created by a CustomCryptoProvider bypass the CryptoProviderCache entirely, meaning a new provider is created on every signature validation. This can be expensive when provider construction involves key materialisation or native resource allocation.

New API

public bool CacheCustomProviders { get; set; } // default: false

When set to true (and CacheSignatureProviders is also true), the SignatureProvider returned by ICryptoProvider.Create() is routed through the existing CryptoProviderCache. Subsequent calls with the same key and algorithm return the cached provider, avoiding repeated construction.

Behaviour

  • Default (false): No change from current behaviour. Custom providers are created and released on every call.
  • true: After Create() returns a SignatureProvider, the cache is checked for an existing equivalent (handling concurrent creation races). If not cached, the provider is added via TryAdd. On cache hit, the cached provider is returned and the duplicate is released.

The cache key is {KeyType}-{InternalId}-{Algorithm}-{ProviderType}, consistent with built-in providers.

Race condition handling

When two threads concurrently create providers for the same key/algorithm:

  1. Both call Create() and get new provider instances
  2. One wins the TryAdd to cache
  3. The other finds the cached provider via TryGetSignatureProvider, increments its ref count, releases the duplicate (Release() + ICryptoProvider.Release()), and returns the cached one

Copy constructor

The property is copied in CryptoProviderFactory(CryptoProviderFactory other) to ensure it propagates correctly when the factory is cloned.

No breaking changes

Existing behaviour is unchanged when CacheCustomProviders is false (the default). The CustomCryptoProvider early-return path in CreateSignatureProvider only reaches the cache logic when the new property is explicitly enabled.

When enabled, SignatureProvider instances returned by
ICryptoProvider.Create() are routed through the library's existing
CryptoProviderCache. This avoids repeated provider creation and key
materialisation on every signature validation when using a custom
crypto provider.

The property defaults to false to preserve existing behaviour.
When true, the cache is checked after Create() returns (using the
provider's actual type as the cache key), and the provider is added
to the cache if not already present. On subsequent calls, the cached
provider is returned directly.

Also copies the new property in the CryptoProviderFactory copy
constructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MZOLN
Copy link
Copy Markdown
Contributor

MZOLN commented May 15, 2026

Could we add some tests? And also some tests for the race conditions?

Comment thread src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs
iNinja and others added 6 commits May 18, 2026 18:32
…m-providers

# Conflicts:
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net10.0/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net462/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net472/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net6.0/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net8.0/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/net9.0/PublicAPI.Unshipped.txt
#	src/Microsoft.IdentityModel.Tokens/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Merge origin/dev8x into cache-custom-providers branch. Resolve
PublicAPI conflicts by keeping both sides and deduplicating.
Add InternalAPI entries for new dev8x telemetry symbols.

Add three tests for CacheCustomProviders:
- WhenEnabled: verifies cached provider is reused (currently failing
  due to EventBasedLRUCache async TryAdd timing)
- WhenDisabled: verifies Create called each time
- WhenCacheSignatureProvidersDisabled: verifies master switch overrides

Restructure caching to check cache before Create using a remembered
provider type from previous calls, avoiding wasteful creation on
cache hits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The EventBasedLRUCache processes TryAdd asynchronously. The test
now retries TryGetSignatureProvider with a polling pattern to
account for the async event queue. Still investigating why the
cache lookup never finds the provider even after waiting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ForVerifying

The SymmetricSignatureProvider(key, algorithm) constructor defaults
to willCreateSignatures=true, which adds the provider to the signing
cache. But CreateForVerifying looks in the verifying cache. Passing
false aligns the provider with the verifying cache.

Also removed debug assertions (no longer needed) and the async
sleep workaround.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verifies that 20 concurrent threads calling CreateForVerifying all
receive the same cached SignatureProvider instance, and that Create
is not called after the cache is warm.

Uses a Barrier to synchronize thread start for maximum contention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@cpp11nullptr
Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Comment thread src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs
Copy link
Copy Markdown
Contributor

Copilot AI commented May 29, 2026

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflicts and merged latest dev8x into this branch in commit f9299d07.

Copilot AI requested a review from cpp11nullptr May 29, 2026 17:30
Comment thread src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs
Comment thread src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs
Comment thread test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs Outdated
Comment thread test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs Outdated
@iNinja iNinja merged commit d000553 into dev8x Jun 1, 2026
2 checks passed
This was referenced Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants