diff --git a/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs b/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs index 7df368ae9a..13d05ef371 100644 --- a/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs +++ b/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs @@ -16,6 +16,7 @@ public class CryptoProviderFactory { private static CryptoProviderFactory _default; private static readonly ConcurrentDictionary _typeToAlgorithmMap = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _customProviderTypeCache = new ConcurrentDictionary(); private static int _defaultSignatureProviderObjectPoolCacheSize = Environment.ProcessorCount * 4; private static string _typeofAsymmetricSignatureProvider = typeof(AsymmetricSignatureProvider).ToString(); private static string _typeofSymmetricSignatureProvider = typeof(SymmetricSignatureProvider).ToString(); @@ -92,6 +93,7 @@ public CryptoProviderFactory(CryptoProviderFactory other) CryptoProviderCache = new InMemoryCryptoProviderCache() { CryptoProviderFactory = this }; CustomCryptoProvider = other.CustomCryptoProvider; CacheSignatureProviders = other.CacheSignatureProviders; + CacheCustomProviders = other.CacheCustomProviders; SignatureProviderObjectPoolCacheSize = other.SignatureProviderObjectPoolCacheSize; } @@ -115,6 +117,18 @@ public CryptoProviderFactory(CryptoProviderFactory other) [DefaultValue(true)] public bool CacheSignatureProviders { get; set; } = DefaultCacheSignatureProviders; + /// + /// Gets or sets a bool controlling if instances created by + /// should be cached. Default is . + /// + /// + /// When , signature providers returned by + /// are cached using the same used for built-in providers. This avoids + /// repeated provider creation and key materialisation on every signature validation. + /// + [DefaultValue(false)] + public bool CacheCustomProviders { get; set; } + /// /// Gets or sets the maximum size of the object pool used by the SignatureProvider that are used for crypto objects. /// @@ -598,6 +612,23 @@ private SignatureProvider CreateSignatureProvider( SignatureProvider signatureProvider; if (CustomCryptoProvider != null && CustomCryptoProvider.IsSupportedAlgorithm(algorithm, key, willCreateSignatures)) { + if (CacheCustomProviders && CacheSignatureProviders && cacheProvider) + { + // Try cache lookup first using the remembered provider type from a previous Create call. + string cacheTypeKey = CustomCryptoProvider.GetType().ToString() + "-" + algorithm; + if (_customProviderTypeCache.TryGetValue(cacheTypeKey, out string providerType) + && CryptoProviderCache.TryGetSignatureProvider( + key, + algorithm, + providerType, + willCreateSignatures, + out SignatureProvider cachedProvider)) + { + cachedProvider.AddRef(); + return cachedProvider; + } + } + signatureProvider = CustomCryptoProvider.Create(algorithm, key, willCreateSignatures) as SignatureProvider; if (signatureProvider == null) throw LogHelper.LogExceptionMessage( @@ -608,6 +639,17 @@ private SignatureProvider CreateSignatureProvider( LogHelper.MarkAsNonPII(key.KeyId), LogHelper.MarkAsNonPII(typeof(SignatureProvider))))); + if (CacheCustomProviders && CacheSignatureProviders && cacheProvider) + { + // Remember the provider type for future cache lookups. + string cacheTypeKey = CustomCryptoProvider.GetType().ToString() + "-" + algorithm; + string providerType = signatureProvider.GetType().ToString(); + _customProviderTypeCache.TryAdd(cacheTypeKey, providerType); + + if (ShouldCacheSignatureProvider(signatureProvider)) + signatureProvider.IsCached = CryptoProviderCache.TryAdd(signatureProvider); + } + return signatureProvider; } @@ -858,9 +900,14 @@ public virtual void ReleaseSignatureProvider(SignatureProvider signatureProvider signatureProvider.Release(); if (CustomCryptoProvider != null && CustomCryptoProvider.IsSupportedAlgorithm(signatureProvider.Algorithm)) - CustomCryptoProvider.Release(signatureProvider); + { + if (!signatureProvider.IsCached) + CustomCryptoProvider.Release(signatureProvider); + } else if (signatureProvider.CryptoProviderCache == null && signatureProvider.RefCount == 0 && !signatureProvider.IsCached) + { signatureProvider.Dispose(); + } } } } diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index ad9824b5e2..b1feb68aca 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,3 +1,112 @@ +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.AlgorithmTag = "Algorithm" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.ConfigurationSourceTag = "ConfigurationSource" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.ErrorTag = "Error" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.FailureValue = "Failure" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.IssuerTag = "Issuer" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.KeyAlgorithmTag = "KeyAlgorithm" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationSourceHandler = "Handler" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationSourceRetriever = "Retriever" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.Protocols.ConfigurationSourceUnknown = "Unknown" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.AlgorithmNotSupported = "AlgorithmNotSupported" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.None = "None" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.Other = "Other" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.SignatureProviderCreationFailed = "SignatureProviderCreationFailed" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.SignatureVerificationFailed = "SignatureVerificationFailed" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors.SigningKeyNotFound = "SigningKeyNotFound" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.StatusTag = "Status" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryConstants.SuccessValue = "Success" -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.SignatureValidationCounterDescription = "Counter capturing signature validation operations with algorithm and key size details." -> string +const Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.SignatureValidationCounterName = "IdentityModelSignatureValidation" -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10211 = "IDX10211: Issuer validation failed. Unable to validate issuer. The 'issuer' parameter is null or whitespace." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10212 = "IDX10212: Issuer validation failed. Issuer: '{0}'. Did not match any of: validationParameters.ValidIssuers: '{1}' or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer: '{2}'.\nFor more details, see https://aka.ms/IdentityModel/issuer-validation." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10503 = "IDX10503: Signature validation failed. The token's kid is: '{0}', but did not match any keys in TokenValidationParameters or Configuration. Keys tried: '{1}'.\nNumber of keys in TokenValidationParameters: '{2}'. \nNumber of keys in Configuration: '{3}'.\nExceptions caught:\n '{4}'.\ntoken: '{5}'. See https://aka.ms/IDX10503 for details." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10506 = "IDX10506: Signature validation failed. The user defined 'Delegate' specified on TokenValidationParameters did not return a '{0}', but returned a '{1}' when validating token: '{2}'.\nIf you are using ASP.NET Core 8 or later, see https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/8.0/securitytoken-events for more details." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10517 = "IDX10517: Signature validation failed. The token's kid is missing. Keys tried: '{0}'.\nNumber of keys in TokenValidationParameters: '{1}'. \nNumber of keys in Configuration: '{2}'.\nExceptions caught:\n '{3}'.\ntoken: '{4}'. See https://aka.ms/IDX10503 for details." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10521 = "IDX10521: Signature validation failed.An exception was thrown when trying to validate the signature. Key tried: '{0}'. Exception: '{1}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10522 = "IDX10522: Signature validation failed. The token's kid: '{0}' did not match any keys in ValidationParameters or Configuration.\nAll keys in ValidationParameters and Configuration were tried. \nNumber of keys in ValidationParameters: '{1}'. \nNumber of keys in Configuration: '{2}'.\ntoken: '{3}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10523 = "IDX10523: Signature validation failed. The token's kid is empty.\nAll keys in ValidationParameters and Configuration were tried. \nNumber of keys in ValidationParameters: '{0}'. \nNumber of keys in Configuration: '{1}'.\ntoken: '{2}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10524 = "IDX10524: Signature validation failed. The token's kid: '{0}' did not match any keys in ValidationParameters or Configuration.\nAll keys in ValidationParameters and Configuration were tried. No non-null keys were found. \nNumber of keys in ValidationParameters: '{1}'. \nNumber of keys in Configuration: '{2}'.\ntoken: '{3}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10525 = "IDX10525: Signature validation failed. The token's kid is missing.\nAll keys in ValidationParameters and Configuration were tried. No non-null keys were found. \nNumber of keys in ValidationParameters: '{0}'. \nNumber of keys in Configuration: '{1}'.\ntoken: '{2}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10526 = "IDX10526: Signature validation failed. The token's kid is missing.\nValidationParameters.TryAllIssuerSigningKeys is set to false. All keys will not be tried. \nNumber of keys in ValidationParameters: '{0}'. \nNumber of keys in Configuration: '{1}'.\ntoken: '{2}'." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10527 = "IDX10527: Signature validation failed. The token's kid is: '{0}', but did not match any keys in ValidationParameters or Configuration and TryAllIssuerSigningKeys is false.\nNumber of keys in ValidationParameters: '{1}'. \nNumber of keys in Configuration: '{2}'.\ntoken: '{3}'." -> string +Microsoft.IdentityModel.Telemetry.ITelemetryClient.IncrementSignatureValidationCounter(string errorType, string issuer, string algorithm, Microsoft.IdentityModel.Tokens.SecurityKey key) -> void +Microsoft.IdentityModel.Telemetry.NullTelemetryClient +Microsoft.IdentityModel.Telemetry.NullTelemetryClient.NullTelemetryClient() -> void +Microsoft.IdentityModel.Telemetry.TelemetryClient.IncrementSignatureValidationCounter(string errorType, string issuer, string algorithm, Microsoft.IdentityModel.Tokens.SecurityKey key) -> void +Microsoft.IdentityModel.Telemetry.TelemetryConstants.SignatureValidationErrors +Microsoft.IdentityModel.Tokens.Cnf.Jkt.set -> void +Microsoft.IdentityModel.Tokens.Experimental.ValidatedSignatureKey.ValidatedSignatureKey(System.DateTime? validFrom, System.DateTime? validTo, System.DateTime? validationTime) -> void +Microsoft.IdentityModel.Tokens.Experimental.ValidatedToken.ValidatedAlgorithm.set -> void +Microsoft.IdentityModel.Tokens.Experimental.ValidatedToken.ValidatedSignatureKey.set -> void +Microsoft.IdentityModel.Tokens.Experimental.ValidationFailureType.Name.set -> void +Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters.DecryptionKeyResolver.get -> Microsoft.IdentityModel.Tokens.Experimental.DecryptionKeyResolverDelegate +Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters.DecryptionKeyResolver.set -> void +Microsoft.IdentityModel.Tokens.JsonWebKey.RepresentAsAsymmetricPublicJwkForDpop() -> System.Text.Json.Nodes.JsonObject +Microsoft.IdentityModel.Tokens.SecurityTokenDecryptionFailedException.SecurityTokenDecryptionFailedException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenDecryptionFailedException.SecurityTokenDecryptionFailedException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenException.SecurityTokenException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenException.SecurityTokenException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenException.ValidationError.get -> Microsoft.IdentityModel.Tokens.Experimental.ValidationError +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAlgorithmException.SecurityTokenInvalidAlgorithmException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAlgorithmException.SecurityTokenInvalidAlgorithmException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException.SecurityTokenInvalidAudienceException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException.SecurityTokenInvalidAudienceException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException.SecurityTokenInvalidIssuerException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException.SecurityTokenInvalidIssuerException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidLifetimeException.SecurityTokenInvalidLifetimeException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidLifetimeException.SecurityTokenInvalidLifetimeException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message, System.Exception innerException, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.ValidationError.get -> Microsoft.IdentityModel.Tokens.Experimental.ValidationError +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException.SecurityTokenInvalidSignatureException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException.SecurityTokenInvalidSignatureException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSigningKeyException.SecurityTokenInvalidSigningKeyException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSigningKeyException.SecurityTokenInvalidSigningKeyException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidTypeException.SecurityTokenInvalidTypeException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidTypeException.SecurityTokenInvalidTypeException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenKeyWrapException.SecurityTokenKeyWrapException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenKeyWrapException.SecurityTokenKeyWrapException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenReplayDetectedException.SecurityTokenReplayDetectedException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenReplayDetectedException.SecurityTokenReplayDetectedException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException.SecurityTokenSignatureKeyNotFoundException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException.SecurityTokenSignatureKeyNotFoundException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenValidationException.SecurityTokenValidationException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenValidationException.SecurityTokenValidationException(string message, Microsoft.IdentityModel.Tokens.Experimental.ValidationError validationError) -> void +Microsoft.IdentityModel.Tokens.TokenValidationResult.TokenValidationResult(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.TokenHandler tokenHandler, Microsoft.IdentityModel.Tokens.TokenValidationParameters tokenValidationParameters, string issuer) -> void +static Microsoft.IdentityModel.Telemetry.CryptoTelemetry.GetKeyAlgorithmId(Microsoft.IdentityModel.Tokens.SecurityKey key) -> string +static Microsoft.IdentityModel.Telemetry.CryptoTelemetry.GetTrackedIssuerOrOther(string issuer) -> string +static Microsoft.IdentityModel.Telemetry.CryptoTelemetry.RecordSignatureValidationTelemetry.get -> bool +static Microsoft.IdentityModel.Telemetry.CryptoTelemetry.RecordSignatureValidationTelemetry.set -> void +static Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.IncrementSignatureValidationCounter(in System.Diagnostics.TagList tagList) -> void +static Microsoft.IdentityModel.Tokens.ConfirmationClaimTypesUtf8Bytes.Jkt.get -> System.ReadOnlySpan +static Microsoft.IdentityModel.Tokens.TokenUtilities.GetAllSigningKeys(Microsoft.IdentityModel.Tokens.BaseConfiguration configuration, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> System.Collections.Generic.IEnumerable +static Microsoft.IdentityModel.Tokens.TokenUtilities.IsRecoverableConfigurationAndExceptionType(string kid, Microsoft.IdentityModel.Tokens.BaseConfiguration currentConfiguration, Microsoft.IdentityModel.Tokens.BaseConfiguration lkgConfiguration, Microsoft.IdentityModel.Tokens.Experimental.ValidationFailureType failureType) -> bool +static Microsoft.IdentityModel.Tokens.TokenUtilities.IsRecoverableFailureType(Microsoft.IdentityModel.Tokens.Experimental.ValidationFailureType failureType, bool configContainsDecryptionKeys) -> bool +static Microsoft.IdentityModel.Tokens.Validators.ValidateAlgorithmInternal(string algorithm, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateAudienceInternal(System.Collections.Generic.IList audiences, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateIssuerInternalAsync(string issuer, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +static Microsoft.IdentityModel.Tokens.Validators.ValidateLifetimeInternal(System.DateTime? notBefore, System.DateTime? expires, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateSignatureKey(Microsoft.IdentityModel.Tokens.SecurityKey securityKey, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateSignatureKeyInternal(Microsoft.IdentityModel.Tokens.SecurityKey securityKey, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateTokenReplayInternal(System.DateTime? expires, string securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static Microsoft.IdentityModel.Tokens.Validators.ValidateTokenTypeInternal(string type, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.Experimental.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.Experimental.ValidationResult +static readonly Microsoft.IdentityModel.Telemetry.NullTelemetryClient.Instance -> Microsoft.IdentityModel.Telemetry.NullTelemetryClient +static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.SignatureValidationCounter -> System.Diagnostics.Metrics.Counter +virtual Microsoft.IdentityModel.Tokens.CreateECDsaDelegate.Invoke(Microsoft.IdentityModel.Tokens.JsonWebKey jsonWebKey, bool usePrivateKey) -> System.Security.Cryptography.ECDsa +virtual Microsoft.IdentityModel.Tokens.DecryptDelegate.Invoke(byte[] bytes) -> byte[] +virtual Microsoft.IdentityModel.Tokens.DecryptionDelegate.Invoke(byte[] cipherText, byte[] authenticatedData, byte[] iv, byte[] authenticationTag) -> byte[] +virtual Microsoft.IdentityModel.Tokens.EncryptDelegate.Invoke(byte[] bytes) -> byte[] +virtual Microsoft.IdentityModel.Tokens.EncryptionDelegate.Invoke(byte[] plaintText, byte[] authenticatedData, byte[] iv) -> Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionResult +virtual Microsoft.IdentityModel.Tokens.EventBasedLRUCache.ItemCompacted.Invoke(TValue Value) -> void +virtual Microsoft.IdentityModel.Tokens.EventBasedLRUCache.ItemExpired.Invoke(TValue Value) -> void +virtual Microsoft.IdentityModel.Tokens.EventBasedLRUCache.ItemRemoved.Invoke(TValue Value) -> void +virtual Microsoft.IdentityModel.Tokens.EventBasedLRUCache.ShouldRemove.Invoke(TValue Value) -> bool +virtual Microsoft.IdentityModel.Tokens.IssuerValidatorAsync.Invoke(string issuer, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.TokenValidationParameters validationParameters) -> System.Threading.Tasks.ValueTask +virtual Microsoft.IdentityModel.Tokens.SignDelegate.Invoke(byte[] bytes) -> byte[] +virtual Microsoft.IdentityModel.Tokens.SignUsingOffsetDelegate.Invoke(byte[] bytes, int offset, int count) -> byte[] +virtual Microsoft.IdentityModel.Tokens.SignUsingSpanDelegate.Invoke(System.ReadOnlySpan bytes, System.Span signature, out int bytesWritten) -> bool +virtual Microsoft.IdentityModel.Tokens.VerifyDelegate.Invoke(byte[] bytes, byte[] signature) -> bool +virtual Microsoft.IdentityModel.Tokens.VerifyUsingOffsetDelegate.Invoke(byte[] bytes, int offset, int count, byte[] signature) -> bool ~Microsoft.IdentityModel.Tokens.MlDsaSecurityKey.MlDsaSecurityKey(Microsoft.IdentityModel.Tokens.JsonWebKey webKey, bool usePrivateKey) -> void static Microsoft.IdentityModel.Tokens.MlDsaAdapter.CreateMlDsa(Microsoft.IdentityModel.Tokens.JsonWebKey jsonWebKey, bool usePrivateKey) -> System.Security.Cryptography.MLDsa diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net10.0/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net462/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net472/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net6.0/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net8.0/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net9.0/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2..feae2969cf 100644 --- a/src/Microsoft.IdentityModel.Tokens/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.get -> bool +Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CacheCustomProviders.set -> void \ No newline at end of file diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs index 51d36785b2..1b90b20a36 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/CryptoProviderFactoryTests.cs @@ -1271,6 +1271,340 @@ private static IList CreateVerifyingProviders(CryptoProviderF private static bool GetSignatureProviderIsDisposedByReflect(SignatureProvider signatureProvider) => (bool)signatureProvider.GetType().GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(signatureProvider); + + [Fact] + public void CacheCustomProviders_WhenEnabled_CachesProviderFromCustomCryptoProvider() + { + // Arrange + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "test-cache-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + createCount++; + return new SymmetricSignatureProvider(signingKey, algorithm, false); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = true + }; + + // Act — first call creates and should cache + var provider1 = factory.CreateForVerifying(signingKey, algorithm); + + // Debug: verify the cache state directly + string providerType = provider1.GetType().ToString(); + string internalId = signingKey.InternalId; + + Assert.True(internalId.Length > 0, + $"Key InternalId should not be empty, got: '{internalId}'"); + Assert.True(provider1.IsCached, + $"Provider should be marked as cached. IsCached={provider1.IsCached}"); + + // Try to retrieve directly from the cache to isolate the issue + bool tryGetResult = factory.CryptoProviderCache.TryGetSignatureProvider( + signingKey, algorithm, providerType, false, out var fromCache); + + // Debug: manually compute the cache keys to compare + string addKey = $"{provider1.Key.GetType()}-{provider1.Key.InternalId}-{provider1.Algorithm}-{provider1.GetType()}"; + string getKey = $"{signingKey.GetType()}-{signingKey.InternalId}-{algorithm}-{providerType}"; + + Assert.True(tryGetResult, + $"TryGetSignatureProvider failed. " + + $"addKey='{addKey}', " + + $"getKey='{getKey}', " + + $"keysMatch={addKey == getKey}, " + + $"createCount={createCount}"); + + Assert.Same(provider1, fromCache); + } + + [Fact] + public void CacheCustomProviders_WhenDisabled_CreatesNewProviderEachTime() + { + // Arrange + var signingKey = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + createCount++; + return new SymmetricSignatureProvider(signingKey, algorithm); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = false, // default + CacheSignatureProviders = true + }; + + // Act + var provider1 = factory.CreateForVerifying(signingKey, algorithm); + var provider2 = factory.CreateForVerifying(signingKey, algorithm); + + // Assert — Create called each time, providers are not the same + Assert.Equal(2, createCount); + Assert.NotSame(provider1, provider2); + } + + [Fact] + public void CacheCustomProviders_WhenCacheSignatureProvidersDisabled_DoesNotCache() + { + // Arrange — CacheCustomProviders = true but CacheSignatureProviders = false + // The master switch overrides the sub-switch. + var signingKey = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + createCount++; + return new SymmetricSignatureProvider(signingKey, algorithm); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = false // master switch off + }; + + // Act + var provider1 = factory.CreateForVerifying(signingKey, algorithm); + var provider2 = factory.CreateForVerifying(signingKey, algorithm); + + // Assert — Create called each time despite CacheCustomProviders = true + Assert.Equal(2, createCount); + } + + /// + /// A custom crypto provider that counts how many times Create and Release are called. + /// + private class CountingCryptoProvider : ICryptoProvider + { + private readonly string _algorithm; + private readonly Func _factory; + + public CountingCryptoProvider(string algorithm, Func factory) + { + _algorithm = algorithm; + _factory = factory; + } + + public int ReleaseCount; + + public bool IsSupportedAlgorithm(string algorithm, params object[] args) => + algorithm == _algorithm; + + public object Create(string algorithm, params object[] args) => + _factory(); + + public void Release(object cryptoInstance) + { + Interlocked.Increment(ref ReleaseCount); + } + } + + [Fact] + public void CacheCustomProviders_ConcurrentAccess_AllThreadsGetSameProvider() + { + // Arrange + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "concurrent-test-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + Interlocked.Increment(ref createCount); + return new SymmetricSignatureProvider(signingKey, algorithm, false); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = true + }; + + // Warm the cache with an initial call so the provider type is known + // and the provider is in the cache. + var warmup = factory.CreateForVerifying(signingKey, algorithm); + Assert.True(warmup.IsCached, "Warmup provider should be cached."); + int warmupCreateCount = createCount; + + // Act — launch many concurrent calls + int threadCount = 20; + var providers = new SignatureProvider[threadCount]; + var barrier = new System.Threading.Barrier(threadCount); + + Parallel.For(0, threadCount, i => + { + barrier.SignalAndWait(); // synchronize start + providers[i] = factory.CreateForVerifying(signingKey, algorithm); + }); + + // Assert — all threads should get the same cached instance + for (int i = 0; i < threadCount; i++) + { + Assert.Same(warmup, providers[i]); + } + + // Create should not have been called again after warmup (all cache hits) + Assert.Equal(warmupCreateCount, createCount); + } + + [Fact] + public void ReleaseSignatureProvider_CachedCustomProvider_DoesNotCallCustomRelease() + { + // Arrange — create a cached custom provider, then release it. + // CustomCryptoProvider.Release should NOT be called because the provider is still in the cache. + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "release-cached-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + Interlocked.Increment(ref createCount); + return new SymmetricSignatureProvider(signingKey, algorithm, false); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = true + }; + + // Act + var provider = factory.CreateForVerifying(signingKey, algorithm); + Assert.True(provider.IsCached, "Provider should be cached."); + + factory.ReleaseSignatureProvider(provider); + + // Assert — Release should NOT have been called on the custom crypto provider + Assert.Equal(0, customCrypto.ReleaseCount); + } + + [Fact] + public void ReleaseSignatureProvider_NonCachedCustomProvider_CallsCustomRelease() + { + // Arrange — create a non-cached custom provider, then release it. + // CustomCryptoProvider.Release SHOULD be called because the provider is not in the cache. + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "release-noncached-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + Interlocked.Increment(ref createCount); + return new SymmetricSignatureProvider(signingKey, algorithm, false); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = false, // not caching + CacheSignatureProviders = true + }; + + // Act + var provider = factory.CreateForVerifying(signingKey, algorithm); + Assert.False(provider.IsCached, "Provider should NOT be cached."); + + factory.ReleaseSignatureProvider(provider); + + // Assert — Release SHOULD have been called on the custom crypto provider + Assert.Equal(1, customCrypto.ReleaseCount); + } + + [Fact] + public void CacheCustomProviders_CreateForSigning_CachesAndReturnsFromCache() + { + // Arrange — verify the CreateForSigning path also uses the cache + // (all other tests use CreateForVerifying). Also verifies that calling + // the factory a second time returns the same cached instance (suggestion 2). + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "signing-cache-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + Interlocked.Increment(ref createCount); + return new SymmetricSignatureProvider(signingKey, algorithm, true); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = true + }; + + // Act — first call creates and caches; second call should hit the cache + var provider1 = factory.CreateForSigning(signingKey, algorithm); + var provider2 = factory.CreateForSigning(signingKey, algorithm); + + // Assert — same instance returned, Create called only once + Assert.True(provider1.IsCached, "Provider should be cached."); + Assert.Same(provider1, provider2); + Assert.Equal(1, createCount); + } + + [Fact] + public void CacheCustomProviders_WhenCacheProviderParamFalse_DoesNotCache() + { + // Arrange — CacheCustomProviders = true but the overload is called + // with cacheProvider = false, so the provider should NOT be cached. + var signingKey = new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricKeyBytes_256) + { + KeyId = "nocache-param-kid" + }; + var algorithm = SecurityAlgorithms.HmacSha256; + + int createCount = 0; + var customCrypto = new CountingCryptoProvider(algorithm, () => + { + Interlocked.Increment(ref createCount); + return new SymmetricSignatureProvider(signingKey, algorithm, false); + }); + + var factory = new CryptoProviderFactory(CryptoProviderCacheTests.CreateCacheForTesting()) + { + CustomCryptoProvider = customCrypto, + CacheCustomProviders = true, + CacheSignatureProviders = true + }; + + // Act — pass cacheProvider: false explicitly + var provider1 = factory.CreateForVerifying(signingKey, algorithm, cacheProvider: false); + var provider2 = factory.CreateForVerifying(signingKey, algorithm, cacheProvider: false); + + // Assert — provider is NOT cached, Create called each time + Assert.False(provider1.IsCached, "Provider should NOT be cached when cacheProvider=false."); + Assert.NotSame(provider1, provider2); + Assert.Equal(2, createCount); + } } } #pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant