diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 439ebd93b2..2f29ac340c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,15 +1,25 @@ name: "CodeQL" on: - push: - branches: [ "dev", "dev6x" ] + push: + paths-ignore: + - 'test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/**' + - 'test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/**' + - '/src/Microsoft.IdentityModel.KeyVaultExtensions/**' + - '/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/**' + branches: [ "dev", "dev6x", "dev7x"] pull_request: + paths-ignore: + - 'test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/**' + - 'test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/**' + - '/src/Microsoft.IdentityModel.KeyVaultExtensions/**' + - '/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/**' types: - opened - synchronize - reopened - ready_for_review - branches: [ "dev", "dev6x" ] + branches: [ "dev", "dev6x", "dev7x"] jobs: analyze: diff --git a/Wilson.sln b/Wilson.sln index 9c671e6168..18274278e0 100644 --- a/Wilson.sln +++ b/Wilson.sln @@ -66,18 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Jso EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{EB14B99B-2255-45BC-BF14-E488DCD4A4BA}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{B961CF69-0DE6-4B9F-9473-9F669365BD62}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.KeyVaultExtensions.Tests", "test\Microsoft.IdentityModel.KeyVaultExtensions.Tests\Microsoft.IdentityModel.KeyVaultExtensions.Tests.csproj", "{987772FA-BA24-4EF4-9B58-3DA78FFD61DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests", "test\Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests\Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests.csproj", "{97315A25-B694-4BD0-8DF5-C339884A6D26}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.KeyVaultExtensions", "src\Microsoft.IdentityModel.KeyVaultExtensions\Microsoft.IdentityModel.KeyVaultExtensions.csproj", "{F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.TestExtensions", "src\Microsoft.IdentityModel.TestExtensions\Microsoft.IdentityModel.TestExtensions.csproj", "{AF787AA8-DE6E-4B74-816E-E8F3203A2FA0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.ManagedKeyVaultSecurityKey", "src\Microsoft.IdentityModel.ManagedKeyVaultSecurityKey\Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.csproj", "{8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Protocols.SignedHttpRequest", "src\Microsoft.IdentityModel.Protocols.SignedHttpRequest\Microsoft.IdentityModel.Protocols.SignedHttpRequest.csproj", "{C768FBB5-DE0D-4970-918C-96B37485E34C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests", "test\Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests\Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests.csproj", "{15944563-F7DA-4150-B5F1-6144EBF2CE23}" @@ -85,6 +75,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4655DBB4-70C6-475D-8971-FE6619B85F70}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + buildPack.bat = buildPack.bat + buildTestPack.bat = buildTestPack.bat EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Validators", "src\Microsoft.IdentityModel.Validators\Microsoft.IdentityModel.Validators.csproj", "{DA585910-0E6C-45A5-AABD-30917130FD63}" @@ -198,26 +190,10 @@ Global {DBF58792-25DF-4B6E-866C-77A0BC5AB81B}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBF58792-25DF-4B6E-866C-77A0BC5AB81B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBF58792-25DF-4B6E-866C-77A0BC5AB81B}.Release|Any CPU.Build.0 = Release|Any CPU - {987772FA-BA24-4EF4-9B58-3DA78FFD61DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {987772FA-BA24-4EF4-9B58-3DA78FFD61DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {987772FA-BA24-4EF4-9B58-3DA78FFD61DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {987772FA-BA24-4EF4-9B58-3DA78FFD61DD}.Release|Any CPU.Build.0 = Release|Any CPU - {97315A25-B694-4BD0-8DF5-C339884A6D26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97315A25-B694-4BD0-8DF5-C339884A6D26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97315A25-B694-4BD0-8DF5-C339884A6D26}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97315A25-B694-4BD0-8DF5-C339884A6D26}.Release|Any CPU.Build.0 = Release|Any CPU - {F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6}.Release|Any CPU.Build.0 = Release|Any CPU {AF787AA8-DE6E-4B74-816E-E8F3203A2FA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AF787AA8-DE6E-4B74-816E-E8F3203A2FA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF787AA8-DE6E-4B74-816E-E8F3203A2FA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF787AA8-DE6E-4B74-816E-E8F3203A2FA0}.Release|Any CPU.Build.0 = Release|Any CPU - {8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F}.Release|Any CPU.Build.0 = Release|Any CPU {C768FBB5-DE0D-4970-918C-96B37485E34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C768FBB5-DE0D-4970-918C-96B37485E34C}.Debug|Any CPU.Build.0 = Debug|Any CPU {C768FBB5-DE0D-4970-918C-96B37485E34C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -287,12 +263,7 @@ Global {E4E6D0ED-12CB-4C01-A4C1-4F60D10E2304} = {BD2706C5-6C57-484D-89C8-A0CF5F8E3D19} {DBF58792-25DF-4B6E-866C-77A0BC5AB81B} = {BD2706C5-6C57-484D-89C8-A0CF5F8E3D19} {EB14B99B-2255-45BC-BF14-E488DCD4A4BA} = {BD2706C5-6C57-484D-89C8-A0CF5F8E3D19} - {B961CF69-0DE6-4B9F-9473-9F669365BD62} = {8905D2E3-4499-4A86-BF3E-F098F228DD59} - {987772FA-BA24-4EF4-9B58-3DA78FFD61DD} = {B961CF69-0DE6-4B9F-9473-9F669365BD62} - {97315A25-B694-4BD0-8DF5-C339884A6D26} = {B961CF69-0DE6-4B9F-9473-9F669365BD62} - {F5636C24-D6D5-4F6A-8A21-7C78FC1FC6C6} = {EB14B99B-2255-45BC-BF14-E488DCD4A4BA} {AF787AA8-DE6E-4B74-816E-E8F3203A2FA0} = {EB14B99B-2255-45BC-BF14-E488DCD4A4BA} - {8DFF1DEA-F01F-4CE4-9471-5D2CEFB7E59F} = {EB14B99B-2255-45BC-BF14-E488DCD4A4BA} {C768FBB5-DE0D-4970-918C-96B37485E34C} = {BD2706C5-6C57-484D-89C8-A0CF5F8E3D19} {15944563-F7DA-4150-B5F1-6144EBF2CE23} = {8905D2E3-4499-4A86-BF3E-F098F228DD59} {DA585910-0E6C-45A5-AABD-30917130FD63} = {BD2706C5-6C57-484D-89C8-A0CF5F8E3D19} diff --git a/build/common.props b/build/common.props index 0fbddb3d63..efd7e90e2f 100644 --- a/build/common.props +++ b/build/common.props @@ -32,7 +32,7 @@ - 7.6.1 + 8.0.0 preview-$([System.DateTime]::Now.AddYears(-2019).Year)$([System.DateTime]::Now.ToString("MMddHHmmss")) @@ -46,8 +46,8 @@ - true - 7.0.0 + false + 8.0.0 diff --git a/build/dependencies.props b/build/dependencies.props index 23e1b91065..8203da572c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -2,8 +2,6 @@ 2.1.1 - 3.0.5 - 1.0.3 4.5.0 1.0.0 2.0.3 diff --git a/buildConfiguration.xml b/buildConfiguration.xml index 06e064dafa..0abda703bf 100644 --- a/buildConfiguration.xml +++ b/buildConfiguration.xml @@ -14,8 +14,6 @@ - - @@ -33,8 +31,6 @@ - - diff --git a/buildPack.bat b/buildPack.bat index 7e782b647f..6a4c89623c 100644 --- a/buildPack.bat +++ b/buildPack.bat @@ -1,2 +1,3 @@ +dotnet clean Product.proj > clean.log dotnet build /r Product.proj dotnet pack --no-restore -o artifacts --no-build Product.proj diff --git a/buildTestPack.bat b/buildTestPack.bat index 40e59fda52..b5dba1b86f 100644 --- a/buildTestPack.bat +++ b/buildTestPack.bat @@ -1,3 +1,4 @@ +dotnet clean Product.proj > clean.log dotnet build /r Product.proj dotnet test --no-restore --no-build Product.proj dotnet pack --no-restore -o artifacts --no-build Product.proj diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs new file mode 100644 index 0000000000..3242311e9a --- /dev/null +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs @@ -0,0 +1,771 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; + +namespace Microsoft.IdentityModel.JsonWebTokens +{ + /// + /// A designed for creating and validating Json Web Tokens. + /// See: https://datatracker.ietf.org/doc/html/rfc7519 and http://www.rfc-editor.org/info/rfc7515. + /// + public partial class JsonWebTokenHandler : TokenHandler + { + /// + /// Returns a value that indicates if this handler can validate a . + /// + /// 'true', indicating this instance can validate a . + public virtual bool CanValidateToken + { + get { return true; } + } + + internal async ValueTask ValidateJWEAsync( + JsonWebToken jwtToken, + TokenValidationParameters validationParameters, + BaseConfiguration configuration) + { + try + { + TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters), validationParameters); + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + + + tokenValidationResult = await ValidateJWSAsync( + tokenValidationResult.SecurityToken as JsonWebToken, + validationParameters, + configuration).ConfigureAwait(false); + + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + + jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken; + jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload; + return new TokenValidationResult + { + SecurityToken = jwtToken, + ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking, + IsValid = true, + TokenType = tokenValidationResult.TokenType + }; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null + }; + } + } + + internal async ValueTask ValidateJWEAsync( + JsonWebToken jwtToken, + TokenValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + try + { + TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters), validationParameters); + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + + tokenValidationResult = await ValidateJWSAsync( + tokenValidationResult.SecurityToken as JsonWebToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); + + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + + jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken; + jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload; + return new TokenValidationResult + { + SecurityToken = jwtToken, + ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking, + IsValid = true, + TokenType = tokenValidationResult.TokenType + }; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null + }; + } + } + + internal async ValueTask ValidateJWSAsync( + JsonWebToken jsonWebToken, + TokenValidationParameters validationParameters, + BaseConfiguration configuration) + { + try + { + TokenValidationResult tokenValidationResult; + if (validationParameters.TransformBeforeSignatureValidation != null) + jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken; + + if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null) + { + var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters); + tokenValidationResult = await ValidateTokenPayloadAsync( + validatedToken, + validationParameters, + configuration).ConfigureAwait(false); + + Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters); + } + else + { + if (validationParameters.ValidateSignatureLast) + { + tokenValidationResult = await ValidateTokenPayloadAsync( + jsonWebToken, + validationParameters, + configuration).ConfigureAwait(false); + + if (tokenValidationResult.IsValid) + tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration); + } + else + { + var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration); + tokenValidationResult = await ValidateTokenPayloadAsync( + validatedToken, + validationParameters, + configuration).ConfigureAwait(false); + } + } + + return tokenValidationResult; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null + }; + } + } + + internal async ValueTask ValidateJWSAsync( + JsonWebToken jsonWebToken, + TokenValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + try + { + BaseConfiguration currentConfiguration = null; + if (validationParameters.ConfigurationManager != null) + { + try + { + currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set + // directly on them, allowing the library to continue with token validation. + if (LogHelper.IsEnabled(EventLogLevel.Warning)) + LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString())); + } + } + + TokenValidationResult tokenValidationResult; + if (validationParameters.TransformBeforeSignatureValidation != null) + jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken; + + if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null) + { + var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters); + tokenValidationResult = await ValidateTokenPayloadAsync( + validatedToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); + + Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters); + } + else + { + if (validationParameters.ValidateSignatureLast) + { + tokenValidationResult = await ValidateTokenPayloadAsync( + jsonWebToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); + + if (tokenValidationResult.IsValid) + tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, currentConfiguration); + } + else + { + var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, currentConfiguration); + tokenValidationResult = await ValidateTokenPayloadAsync( + validatedToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); + } + } + + return tokenValidationResult; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false, + TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null + }; + } + } + + private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) + { + JsonWebToken validatedToken = ValidateSignature(jsonWebToken, validationParameters, configuration); + Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, jsonWebToken, validationParameters, configuration); + return validatedToken; + } + + /// + /// Validates the JWT signature. + /// + private static JsonWebToken ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) + { + bool kidMatched = false; + IEnumerable keys = null; + + if (!jwtToken.IsSigned) + { + if (validationParameters.RequireSignedTokens) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10504, jwtToken))); + else + return jwtToken; + } + + if (validationParameters.IssuerSigningKeyResolverUsingConfiguration != null) + { + keys = validationParameters.IssuerSigningKeyResolverUsingConfiguration(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters, configuration); + } + else if (validationParameters.IssuerSigningKeyResolver != null) + { + keys = validationParameters.IssuerSigningKeyResolver(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters); + } + else + { + var key = JwtTokenUtilities.ResolveTokenSigningKey(jwtToken.Kid, jwtToken.X5t, validationParameters, configuration); + if (key != null) + { + kidMatched = true; + keys = [key]; + } + } + + if (validationParameters.TryAllIssuerSigningKeys && keys.IsNullOrEmpty()) + { + // control gets here if: + // 1. User specified delegate: IssuerSigningKeyResolver returned null + // 2. ResolveIssuerSigningKey returned null + // Try all the keys. This is the degenerate case, not concerned about perf. + keys = TokenUtilities.GetAllSigningKeys(configuration, validationParameters); + } + + // keep track of exceptions thrown, keys that were tried + StringBuilder exceptionStrings = null; + StringBuilder keysAttempted = null; + var kidExists = !string.IsNullOrEmpty(jwtToken.Kid); + + if (keys != null) + { + foreach (var key in keys) + { +#pragma warning disable CA1031 // Do not catch general exception types + try + { + if (ValidateSignature(jwtToken, key, validationParameters)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(TokenLogMessages.IDX10242, jwtToken); + + jwtToken.SigningKey = key; + return jwtToken; + } + } + catch (Exception ex) + { + (exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString()); + } +#pragma warning restore CA1031 // Do not catch general exception types + + if (key != null) + { + (keysAttempted ??= new StringBuilder()).Append(key.ToString()).Append(" , KeyId: ").AppendLine(key.KeyId); + if (kidExists && !kidMatched && key.KeyId != null) + kidMatched = jwtToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + } + } + + // Get information on where keys used during token validation came from for debugging purposes. + var keysInTokenValidationParameters = TokenUtilities.GetAllSigningKeys(validationParameters: validationParameters); + + var keysInConfiguration = TokenUtilities.GetAllSigningKeys(configuration); + var numKeysInTokenValidationParameters = keysInTokenValidationParameters.Count(); + var numKeysInConfiguration = keysInConfiguration.Count(); + + if (kidExists) + { + if (kidMatched) + { + JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path + var isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid)); + var keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration"; + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511, + LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), + LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), + LogHelper.MarkAsNonPII(numKeysInConfiguration), + LogHelper.MarkAsNonPII(keyLocation), + LogHelper.MarkAsNonPII(jwtToken.Kid), + (object)exceptionStrings ?? "", + jwtToken))); + } + + if (!validationParameters.ValidateSignatureLast) + { + InternalValidators.ValidateAfterSignatureFailed( + jwtToken, + jwtToken.ValidFromNullable, + jwtToken.ValidToNullable, + jwtToken.Audiences, + validationParameters, + configuration); + } + } + + if (keysAttempted is not null) + { + if (kidExists) + { + throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10503, + LogHelper.MarkAsNonPII(jwtToken.Kid), + LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), + LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), + LogHelper.MarkAsNonPII(numKeysInConfiguration), + (object)exceptionStrings ?? "", + jwtToken))); + } + else + { + throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10517, + LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), + LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), + LogHelper.MarkAsNonPII(numKeysInConfiguration), + (object)exceptionStrings ?? "", + jwtToken))); + } + } + + throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(TokenLogMessages.IDX10500)); + } + + internal static bool IsSignatureValid(byte[] signatureBytes, int signatureBytesLength, SignatureProvider signatureProvider, byte[] dataToVerify, int dataToVerifyLength) + { + if (signatureProvider is SymmetricSignatureProvider) + { + return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength); + } + else + { + if (signatureBytes.Length == signatureBytesLength) + { + return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength); + } + else + { + byte[] sigBytes = new byte[signatureBytesLength]; + Array.Copy(signatureBytes, 0, sigBytes, 0, signatureBytesLength); + return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, sigBytes, 0, signatureBytesLength); + } + } + } + + internal static bool ValidateSignature(byte[] bytes, int len, string stringWithSignature, int signatureStartIndex, SignatureProvider signatureProvider) + { + return Base64UrlEncoding.Decode( + stringWithSignature, + signatureStartIndex + 1, + stringWithSignature.Length - signatureStartIndex - 1, + signatureProvider, + bytes, + len, + IsSignatureValid); + } + + internal static bool ValidateSignature(JsonWebToken jsonWebToken, SecurityKey key, TokenValidationParameters validationParameters) + { + var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory; + if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX14000, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key); + + return false; + } + + Validators.ValidateAlgorithm(jsonWebToken.Alg, key, jsonWebToken, validationParameters); + var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg); + try + { + if (signatureProvider == null) + throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), LogHelper.MarkAsNonPII(jsonWebToken.Alg)))); + + return EncodingUtils.PerformEncodingDependentOperation( + jsonWebToken.EncodedToken, + 0, + jsonWebToken.Dot2, + Encoding.UTF8, + jsonWebToken.EncodedToken, + jsonWebToken.Dot2, + signatureProvider, + ValidateSignature); + } + finally + { + cryptoProviderFactory.ReleaseSignatureProvider(signatureProvider); + } + } + + private static JsonWebToken ValidateSignatureUsingDelegates(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters) + { + if (validationParameters.SignatureValidatorUsingConfiguration != null) + { + // TODO - get configuration from validationParameters + BaseConfiguration configuration = null; + var validatedToken = validationParameters.SignatureValidatorUsingConfiguration(jsonWebToken.EncodedToken, validationParameters, configuration); + if (validatedToken == null) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); + + if (!(validatedToken is JsonWebToken validatedJsonWebToken)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken))); + + return validatedJsonWebToken; + } + else if (validationParameters.SignatureValidator != null) + { + var validatedToken = validationParameters.SignatureValidator(jsonWebToken.EncodedToken, validationParameters); + if (validatedToken == null) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); + + if (!(validatedToken is JsonWebToken validatedJsonWebToken)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken))); + + return validatedJsonWebToken; + } + + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); + } + + /// + /// Validates a JWS or a JWE. + /// + /// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format. + /// A required for validation. + /// A + [Obsolete("`JsonWebTokens.ValidateToken(string, TokenValidationParameters)` has been deprecated and will be removed in a future release. Use `JsonWebTokens.ValidateTokenAsync(string, TokenValidationParameters)` instead. For more information, see https://aka.ms/IdentityModel/7-breaking-changes", false)] + public virtual TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) + { + return ValidateTokenAsync(token, validationParameters).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Validates a token. + /// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property. + /// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result. + /// + /// The token to be validated. + /// A required for validation. + /// A + /// + /// TokenValidationResult.Exception will be set to one of the following exceptions if the is invalid. + /// if is null or empty. + /// if is null. + /// 'token.Length' is greater than . + /// if is not a valid , + /// if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid , + /// + public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) + { + if (string.IsNullOrEmpty(token)) + return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(token)), IsValid = false }; + + if (validationParameters == null) + return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false }; + + if (token.Length > MaximumTokenSizeInBytes) + return new TokenValidationResult { Exception = LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))), IsValid = false }; + + try + { + TokenValidationResult result = ReadToken(token, validationParameters); + if (result.IsValid) + return await ValidateTokenAsync(result.SecurityToken, validationParameters).ConfigureAwait(false); + + return result; + } + catch (Exception ex) + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false + }; + } + } + + /// + public override async Task ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters) + { + if (token == null) + throw LogHelper.LogArgumentNullException(nameof(token)); + + if (validationParameters == null) + return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false }; + + var jwt = token as JsonWebToken; + if (jwt == null) + return new TokenValidationResult { Exception = LogHelper.LogArgumentException(nameof(token), $"{nameof(token)} must be a {nameof(JsonWebToken)}."), IsValid = false }; + + try + { + return await ValidateTokenAsync(jwt, validationParameters).ConfigureAwait(false); + } + catch (Exception ex) + { + return new TokenValidationResult + { + Exception = ex, + IsValid = false + }; + } + } + + /// + /// Internal method for token validation, responsible for: + /// (1) Obtaining a configuration from the . + /// (2) Revalidating using the Last Known Good Configuration (if present), and obtaining a refreshed configuration (if necessary) and revalidating using it. + /// + /// The JWT token + /// The to be used for validation. + /// + internal async ValueTask ValidateTokenAsync( + JsonWebToken jsonWebToken, + TokenValidationParameters validationParameters) + { + BaseConfiguration currentConfiguration = null; + if (validationParameters.ConfigurationManager != null) + { + try + { + currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set + // directly on them, allowing the library to continue with token validation. + if (LogHelper.IsEnabled(EventLogLevel.Warning)) + LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString())); + } + } + + TokenValidationResult tokenValidationResult = jsonWebToken.IsEncrypted ? + await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false) : + await ValidateJWSAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false); + + if (validationParameters.ConfigurationManager != null) + { + if (tokenValidationResult.IsValid) + { + // Set current configuration as LKG if it exists. + if (currentConfiguration != null) + validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration; + + return tokenValidationResult; + } + else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception)) + { + // If we were still unable to validate, attempt to refresh the configuration and validate using it + // but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on + // retrieval error as this case should have already been hit before. This refresh handles the case + // where a new valid configuration was somehow published during validation time. + if (currentConfiguration != null) + { + validationParameters.ConfigurationManager.RequestRefresh(); + validationParameters.RefreshBeforeValidation = true; + var lastConfig = currentConfiguration; + currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); + + // Only try to re-validate using the newly obtained config if it doesn't reference equal the previously used configuration. + if (lastConfig != currentConfiguration) + { + tokenValidationResult = jsonWebToken.IsEncrypted ? + await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false) : + await ValidateJWSAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false); + + if (tokenValidationResult.IsValid) + { + validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration; + return tokenValidationResult; + } + } + } + + if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration) + { + validationParameters.RefreshBeforeValidation = false; + validationParameters.ValidateWithLKG = true; + var recoverableException = tokenValidationResult.Exception; + + foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations()) + { + if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException)) + { + tokenValidationResult = jsonWebToken.IsEncrypted ? + await ValidateJWEAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false) : + await ValidateJWSAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false); + + if (tokenValidationResult.IsValid) + return tokenValidationResult; + } + } + } + } + } + + return tokenValidationResult; + } + + internal async ValueTask ValidateTokenPayloadAsync( + JsonWebToken jsonWebToken, + TokenValidationParameters validationParameters, + BaseConfiguration configuration) + { + var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null; + var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null; + + Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters); + Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters); + string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration).ConfigureAwait(false); + + Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters); + if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor)) + { + // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string + // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens, + // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature + // and (since issuer validation occurs first) came from a trusted authority. + // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one, + // this code will still work properly. + TokenValidationResult tokenValidationResult = + await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false); + + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + } + + string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters); + return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuer) + { + IsValid = true, + TokenType = tokenType + }; + } + + internal async ValueTask ValidateTokenPayloadAsync( + JsonWebToken jsonWebToken, + TokenValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null; + var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null; + + Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters); + Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters); + + IssuerValidationResult issuerValidationResult = await Validators.ValidateIssuerAsync( + jsonWebToken.Issuer, + jsonWebToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); + + if (!issuerValidationResult.IsValid) + { + return new TokenValidationResult(jsonWebToken, this, validationParameters, issuerValidationResult.Issuer) + { + IsValid = false, + Exception = issuerValidationResult.Exception + }; + } + + Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters); + if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor)) + { + // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string + // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens, + // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature + // and (since issuer validation occurs first) came from a trusted authority. + // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one, + // this code will still work properly. + TokenValidationResult tokenValidationResult = + await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false); + + if (!tokenValidationResult.IsValid) + return tokenValidationResult; + } + + string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters); + return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuerValidationResult.Issuer) + { + IsValid = true, + TokenType = tokenType + }; + } + } +} diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs index cf4ad2e264..43d1999a3c 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs @@ -3,12 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; -using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; @@ -174,15 +170,6 @@ public virtual bool CanReadToken(string token) } } - /// - /// Returns a value that indicates if this handler can validate a . - /// - /// 'true', indicating this instance can validate a . - public virtual bool CanValidateToken - { - get { return true; } - } - private static StringComparison GetStringComparisonRuleIf509(SecurityKey securityKey) => (securityKey is X509SecurityKey) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; @@ -498,90 +485,6 @@ public override SecurityToken ReadToken(string token) return ReadJsonWebToken(token); } - /// - /// Validates a JWS or a JWE. - /// - /// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format. - /// A required for validation. - /// A - [Obsolete("`JsonWebTokens.ValidateToken(string, TokenValidationParameters)` has been deprecated and will be removed in a future release. Use `JsonWebTokens.ValidateTokenAsync(string, TokenValidationParameters)` instead. For more information, see https://aka.ms/IdentityModel/7-breaking-changes", false)] - public virtual TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { - return ValidateTokenAsync(token, validationParameters).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Validates a token. - /// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property. - /// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result. - /// - /// The token to be validated. - /// A required for validation. - /// A - /// - /// TokenValidationResult.Exception will be set to one of the following exceptions if the is invalid. - /// if is null or empty. - /// if is null. - /// 'token.Length' is greater than . - /// if is not a valid , - /// if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid , - /// - public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) - { - if (string.IsNullOrEmpty(token)) - return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(token)), IsValid = false }; - - if (validationParameters == null) - return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false }; - - if (token.Length > MaximumTokenSizeInBytes) - return new TokenValidationResult { Exception = LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))), IsValid = false }; - - try - { - TokenValidationResult result = ReadToken(token, validationParameters); - if (result.IsValid) - return await ValidateTokenAsync(result.SecurityToken, validationParameters).ConfigureAwait(false); - - return result; - } - catch (Exception ex) - { - return new TokenValidationResult - { - Exception = ex, - IsValid = false - }; - } - } - - /// - public override async Task ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters) - { - if (token == null) - throw LogHelper.LogArgumentNullException(nameof(token)); - - if (validationParameters == null) - return new TokenValidationResult { Exception = LogHelper.LogArgumentNullException(nameof(validationParameters)), IsValid = false }; - - var jwt = token as JsonWebToken; - if (jwt == null) - return new TokenValidationResult { Exception = LogHelper.LogArgumentException(nameof(token), $"{nameof(token)} must be a {nameof(JsonWebToken)}."), IsValid = false }; - - try - { - return await ValidateTokenAsync(jwt, validationParameters).ConfigureAwait(false); - } - catch (Exception ex) - { - return new TokenValidationResult - { - Exception = ex, - IsValid = false - }; - } - } - /// /// Converts a string into an instance of . /// @@ -627,453 +530,5 @@ private static TokenValidationResult ReadToken(string token, TokenValidationPara IsValid = true }; } - - /// - /// Private method for token validation, responsible for: - /// (1) Obtaining a configuration from the . - /// (2) Revalidating using the Last Known Good Configuration (if present), and obtaining a refreshed configuration (if necessary) and revalidating using it. - /// - /// The JWT token - /// The to be used for validation. - /// - private async ValueTask ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters) - { - BaseConfiguration currentConfiguration = null; - if (validationParameters.ConfigurationManager != null) - { - try - { - currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - // The exception is not re-thrown as the TokenValidationParameters may have the issuer and signing key set - // directly on them, allowing the library to continue with token validation. - if (LogHelper.IsEnabled(EventLogLevel.Warning)) - LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10261, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString())); - } - } - - TokenValidationResult tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false); - if (validationParameters.ConfigurationManager != null) - { - if (tokenValidationResult.IsValid) - { - // Set current configuration as LKG if it exists. - if (currentConfiguration != null) - validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration; - - return tokenValidationResult; - } - else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception)) - { - // If we were still unable to validate, attempt to refresh the configuration and validate using it - // but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on - // retrieval error as this case should have already been hit before. This refresh handles the case - // where a new valid configuration was somehow published during validation time. - if (currentConfiguration != null) - { - validationParameters.ConfigurationManager.RequestRefresh(); - validationParameters.RefreshBeforeValidation = true; - var lastConfig = currentConfiguration; - currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); - - // Only try to re-validate using the newly obtained config if it doesn't reference equal the previously used configuration. - if (lastConfig != currentConfiguration) - { - tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false); - - if (tokenValidationResult.IsValid) - { - validationParameters.ConfigurationManager.LastKnownGoodConfiguration = currentConfiguration; - return tokenValidationResult; - } - } - } - - if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration) - { - validationParameters.RefreshBeforeValidation = false; - validationParameters.ValidateWithLKG = true; - var recoverableException = tokenValidationResult.Exception; - - foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations()) - { - if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException)) - { - tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false); - - if (tokenValidationResult.IsValid) - return tokenValidationResult; - } - } - } - } - } - - return tokenValidationResult; - } - - private ValueTask ValidateTokenAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - return jsonWebToken.IsEncrypted ? - ValidateJWEAsync(jsonWebToken, validationParameters, configuration) : - ValidateJWSAsync(jsonWebToken, validationParameters, configuration); - } - - private async ValueTask ValidateJWSAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - try - { - TokenValidationResult tokenValidationResult; - if (validationParameters.TransformBeforeSignatureValidation != null) - jsonWebToken = validationParameters.TransformBeforeSignatureValidation(jsonWebToken, validationParameters) as JsonWebToken; - - if (validationParameters.SignatureValidator != null || validationParameters.SignatureValidatorUsingConfiguration != null) - { - var validatedToken = ValidateSignatureUsingDelegates(jsonWebToken, validationParameters, configuration); - tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration).ConfigureAwait(false); - Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, validatedToken, validationParameters, configuration); - } - else - { - if (validationParameters.ValidateSignatureLast) - { - tokenValidationResult = await ValidateTokenPayloadAsync(jsonWebToken, validationParameters, configuration).ConfigureAwait(false); - if (tokenValidationResult.IsValid) - tokenValidationResult.SecurityToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration); - } - else - { - var validatedToken = ValidateSignatureAndIssuerSecurityKey(jsonWebToken, validationParameters, configuration); - tokenValidationResult = await ValidateTokenPayloadAsync(validatedToken, validationParameters, configuration).ConfigureAwait(false); - } - } - - return tokenValidationResult; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - return new TokenValidationResult - { - Exception = ex, - IsValid = false, - TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jsonWebToken : null - }; - } - } - - private async ValueTask ValidateJWEAsync(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - try - { - TokenValidationResult tokenValidationResult = ReadToken(DecryptToken(jwtToken, validationParameters, configuration), validationParameters); - if (!tokenValidationResult.IsValid) - return tokenValidationResult; - - tokenValidationResult = await ValidateJWSAsync(tokenValidationResult.SecurityToken as JsonWebToken, validationParameters, configuration).ConfigureAwait(false); - if (!tokenValidationResult.IsValid) - return tokenValidationResult; - - jwtToken.InnerToken = tokenValidationResult.SecurityToken as JsonWebToken; - jwtToken.Payload = (tokenValidationResult.SecurityToken as JsonWebToken).Payload; - return new TokenValidationResult - { - SecurityToken = jwtToken, - ClaimsIdentityNoLocking = tokenValidationResult.ClaimsIdentityNoLocking, - IsValid = true, - TokenType = tokenValidationResult.TokenType - }; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - return new TokenValidationResult - { - Exception = ex, - IsValid = false, - TokenOnFailedValidation = validationParameters.IncludeTokenOnFailedValidation ? jwtToken : null - }; - } - } - - private static JsonWebToken ValidateSignatureUsingDelegates(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - if (validationParameters.SignatureValidatorUsingConfiguration != null) - { - var validatedToken = validationParameters.SignatureValidatorUsingConfiguration(jsonWebToken.EncodedToken, validationParameters, configuration); - if (validatedToken == null) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); - - if (!(validatedToken is JsonWebToken validatedJsonWebToken)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken))); - - return validatedJsonWebToken; - } - else if (validationParameters.SignatureValidator != null) - { - var validatedToken = validationParameters.SignatureValidator(jsonWebToken.EncodedToken, validationParameters); - if (validatedToken == null) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); - - if (!(validatedToken is JsonWebToken validatedJsonWebToken)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10506, LogHelper.MarkAsNonPII(typeof(JsonWebToken)), LogHelper.MarkAsNonPII(validatedToken.GetType()), jsonWebToken))); - - return validatedJsonWebToken; - } - - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10505, jsonWebToken))); - } - - private static JsonWebToken ValidateSignatureAndIssuerSecurityKey(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - JsonWebToken validatedToken = ValidateSignature(jsonWebToken, validationParameters, configuration); - Validators.ValidateIssuerSecurityKey(validatedToken.SigningKey, jsonWebToken, validationParameters, configuration); - - return validatedToken; - } - - private async ValueTask ValidateTokenPayloadAsync(JsonWebToken jsonWebToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - var expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? (DateTime?)jsonWebToken.ValidTo : null; - var notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? (DateTime?)jsonWebToken.ValidFrom : null; - - Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters); - Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters); - string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration).ConfigureAwait(false); - - Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters); - if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor)) - { - // Infinite recursion should not occur here, as the JsonWebToken passed into this method is (1) constructed from a string - // AND (2) the signature is successfully validated on it. (1) implies that even if there are nested actor tokens, - // they must end at some point since they cannot reference one another. (2) means that the token has a valid signature - // and (since issuer validation occurs first) came from a trusted authority. - // NOTE: More than one nested actor token should not be considered a valid token, but if we somehow encounter one, - // this code will still work properly. - TokenValidationResult tokenValidationResult = - await ValidateTokenAsync(jsonWebToken.Actor, validationParameters.ActorValidationParameters ?? validationParameters).ConfigureAwait(false); - - if (!tokenValidationResult.IsValid) - return tokenValidationResult; - } - - string tokenType = Validators.ValidateTokenType(jsonWebToken.Typ, jsonWebToken, validationParameters); - return new TokenValidationResult(jsonWebToken, this, validationParameters.Clone(), issuer) - { - IsValid = true, - TokenType = tokenType - }; - } - - /// - /// Validates the JWT signature. - /// - private static JsonWebToken ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - bool kidMatched = false; - IEnumerable keys = null; - - if (!jwtToken.IsSigned) - { - if (validationParameters.RequireSignedTokens) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10504, jwtToken))); - else - return jwtToken; - } - - if (validationParameters.IssuerSigningKeyResolverUsingConfiguration != null) - { - keys = validationParameters.IssuerSigningKeyResolverUsingConfiguration(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters, configuration); - } - else if (validationParameters.IssuerSigningKeyResolver != null) - { - keys = validationParameters.IssuerSigningKeyResolver(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters); - } - else - { - var key = JwtTokenUtilities.ResolveTokenSigningKey(jwtToken.Kid, jwtToken.X5t, validationParameters, configuration); - if (key != null) - { - kidMatched = true; - keys = [key]; - } - } - - if (validationParameters.TryAllIssuerSigningKeys && keys.IsNullOrEmpty()) - { - // control gets here if: - // 1. User specified delegate: IssuerSigningKeyResolver returned null - // 2. ResolveIssuerSigningKey returned null - // Try all the keys. This is the degenerate case, not concerned about perf. - keys = TokenUtilities.GetAllSigningKeys(configuration, validationParameters); - } - - // keep track of exceptions thrown, keys that were tried - StringBuilder exceptionStrings = null; - StringBuilder keysAttempted = null; - var kidExists = !string.IsNullOrEmpty(jwtToken.Kid); - - if (keys != null) - { - foreach (var key in keys) - { - try - { - if (ValidateSignature(jwtToken, key, validationParameters)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(TokenLogMessages.IDX10242, jwtToken); - - jwtToken.SigningKey = key; - return jwtToken; - } - } - catch (Exception ex) - { - (exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString()); - } - - if (key != null) - { - (keysAttempted ??= new StringBuilder()).Append(key.ToString()).Append(" , KeyId: ").AppendLine(key.KeyId); - if (kidExists && !kidMatched && key.KeyId != null) - kidMatched = jwtToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); - } - } - } - - // Get information on where keys used during token validation came from for debugging purposes. - var keysInTokenValidationParameters = TokenUtilities.GetAllSigningKeys(validationParameters: validationParameters); - var keysInConfiguration = TokenUtilities.GetAllSigningKeys(configuration); - var numKeysInTokenValidationParameters = keysInTokenValidationParameters.Count(); - var numKeysInConfiguration = keysInConfiguration.Count(); - - if (kidExists) - { - if (kidMatched) - { - JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path - var isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid)); - var keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration"; - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511, - LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), - LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), - LogHelper.MarkAsNonPII(numKeysInConfiguration), - LogHelper.MarkAsNonPII(keyLocation), - LogHelper.MarkAsNonPII(jwtToken.Kid), - (object)exceptionStrings ?? "", - jwtToken))); - } - - if (!validationParameters.ValidateSignatureLast) - { - InternalValidators.ValidateAfterSignatureFailed( - jwtToken, - jwtToken.ValidFromNullable, - jwtToken.ValidToNullable, - jwtToken.Audiences, - validationParameters, - configuration); - } - } - - if (keysAttempted is not null) - { - if (kidExists) - { - throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10503, - LogHelper.MarkAsNonPII(jwtToken.Kid), - LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), - LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), - LogHelper.MarkAsNonPII(numKeysInConfiguration), - (object)exceptionStrings ?? "", - jwtToken))); - } - else - { - throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10517, - LogHelper.MarkAsNonPII((object)keysAttempted ?? ""), - LogHelper.MarkAsNonPII(numKeysInTokenValidationParameters), - LogHelper.MarkAsNonPII(numKeysInConfiguration), - (object)exceptionStrings ?? "", - jwtToken))); - } - } - - throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(TokenLogMessages.IDX10500)); - } - - internal static bool IsSignatureValid(byte[] signatureBytes, int signatureBytesLength, SignatureProvider signatureProvider, byte[] dataToVerify, int dataToVerifyLength) - { - if (signatureProvider is SymmetricSignatureProvider) - { - return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength); - } - else - { - if (signatureBytes.Length == signatureBytesLength) - { - return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, signatureBytes, 0, signatureBytesLength); - } - else - { - byte[] sigBytes = new byte[signatureBytesLength]; - Array.Copy(signatureBytes, 0, sigBytes, 0, signatureBytesLength); - return signatureProvider.Verify(dataToVerify, 0, dataToVerifyLength, sigBytes, 0, signatureBytesLength); - } - } - } - - internal static bool ValidateSignature(byte[] bytes, int len, string stringWithSignature, int signatureStartIndex, SignatureProvider signatureProvider) - { - return Base64UrlEncoding.Decode( - stringWithSignature, - signatureStartIndex + 1, - stringWithSignature.Length - signatureStartIndex - 1, - signatureProvider, - bytes, - len, - IsSignatureValid); - } - - internal static bool ValidateSignature(JsonWebToken jsonWebToken, SecurityKey key, TokenValidationParameters validationParameters) - { - var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory; - if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX14000, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key); - - return false; - } - - Validators.ValidateAlgorithm(jsonWebToken.Alg, key, jsonWebToken, validationParameters); - var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg); - try - { - if (signatureProvider == null) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), LogHelper.MarkAsNonPII(jsonWebToken.Alg)))); - - return EncodingUtils.PerformEncodingDependentOperation( - jsonWebToken.EncodedToken, - 0, - jsonWebToken.Dot2, - Encoding.UTF8, - jsonWebToken.EncodedToken, - jsonWebToken.Dot2, - signatureProvider, - ValidateSignature); - } - finally - { - cryptoProviderFactory.ReleaseSignatureProvider(signatureProvider); - } - } } } diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/InternalsVisibleTo.cs deleted file mode 100644 index 5e625e7a84..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/InternalsVisibleTo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.KeyVaultExtensions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultCryptoProvider.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultCryptoProvider.cs deleted file mode 100644 index 7eecfe8d82..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultCryptoProvider.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Linq; -using Microsoft.Azure.KeyVault.WebKey; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace Microsoft.IdentityModel.KeyVaultExtensions -{ - /// - /// Provides cryptographic operators based on Azure Key Vault. - /// - public class KeyVaultCryptoProvider : ICryptoProvider - { - private readonly CryptoProviderCache _cache; - - /// - /// Initializes a new instance of the class. - /// - public KeyVaultCryptoProvider() - { - _cache = new InMemoryCryptoProviderCache(); - } - - /// - /// Gets the - /// - internal CryptoProviderCache CryptoProviderCache => _cache; - - /// - /// Returns a cryptographic operator that supports the algorithm. - /// - /// the algorithm that defines the cryptographic operator. - /// the arguments required by the cryptographic operator. May be null. - /// if is null or empty. - /// if is null. - /// if does not contain a . - /// call when finished with the object. - public object Create(string algorithm, params object[] args) - { - if (string.IsNullOrEmpty(algorithm)) - throw LogHelper.LogArgumentNullException(nameof(algorithm)); - - if (args == null) - throw LogHelper.LogArgumentNullException(nameof(args)); - - if (args.FirstOrDefault() is KeyVaultSecurityKey key) - { - if (JsonWebKeyEncryptionAlgorithm.AllAlgorithms.Contains(algorithm, StringComparer.Ordinal)) - return new KeyVaultKeyWrapProvider(key, algorithm); - else if (JsonWebKeySignatureAlgorithm.AllAlgorithms.Contains(algorithm, StringComparer.Ordinal)) - { - var willCreateSignatures = (bool)(args.Skip(1).FirstOrDefault() ?? false); - - if (_cache.TryGetSignatureProvider(key, algorithm, typeofProvider: key.GetType().ToString(), willCreateSignatures, out var cachedProvider)) - return cachedProvider; - - var signatureProvider = new KeyVaultSignatureProvider(key, algorithm, willCreateSignatures); - if (CryptoProviderFactory.ShouldCacheSignatureProvider(signatureProvider)) - _cache.TryAdd(signatureProvider); - - return signatureProvider; - } - } - - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10652, LogHelper.MarkAsNonPII(algorithm)))); - } - - /// - /// Called to determine if a cryptographic operation is supported. - /// - /// the algorithm that defines the cryptographic operator. - /// the arguments required by the cryptographic operator. May be null. - /// true if supported - public bool IsSupportedAlgorithm(string algorithm, params object[] args) - { - if (string.IsNullOrEmpty(algorithm)) - throw LogHelper.LogArgumentNullException(nameof(algorithm)); - - if (args == null) - throw LogHelper.LogArgumentNullException(nameof(args)); - - return args.FirstOrDefault() is KeyVaultSecurityKey - && (JsonWebKeyEncryptionAlgorithm.AllAlgorithms.Contains(algorithm, StringComparer.Ordinal) || JsonWebKeySignatureAlgorithm.AllAlgorithms.Contains(algorithm, StringComparer.Ordinal)); - } - - /// - /// Called to release the object returned from - /// - /// the object returned from . - public void Release(object cryptoInstance) - { - if (cryptoInstance is SignatureProvider signatureProvider) - _cache.TryRemove(signatureProvider); - - if (cryptoInstance is IDisposable obj) - obj.Dispose(); - } - } -} diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultKeyWrapProvider.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultKeyWrapProvider.cs deleted file mode 100644 index 63c625fe03..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultKeyWrapProvider.cs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.KeyVault; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace Microsoft.IdentityModel.KeyVaultExtensions -{ - /// - /// Provides wrap and unwrap operations using Azure Key Vault. - /// - public class KeyVaultKeyWrapProvider : KeyWrapProvider - { - private readonly IKeyVaultClient _client; - private readonly KeyVaultSecurityKey _key; - private readonly string _algorithm; - private bool _disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// The that will be used for key wrap operations. - /// The key wrap algorithm to apply. - /// if is null. - /// if is not a . - /// if is null or empty. - public KeyVaultKeyWrapProvider(SecurityKey key, string algorithm) - : this(key, algorithm, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The that will be used for key wrap operations. - /// The key wrap algorithm to apply. - /// A mock used for testing purposes. - internal KeyVaultKeyWrapProvider(SecurityKey key, string algorithm, IKeyVaultClient? client) - { - _algorithm = string.IsNullOrEmpty(algorithm) ? throw LogHelper.LogArgumentNullException(nameof(algorithm)) : algorithm; - if (key == null) - throw LogHelper.LogArgumentNullException(nameof(key)); - - _key = key as KeyVaultSecurityKey ?? throw LogHelper.LogExceptionMessage(new NotSupportedException(key.GetType().ToString())); - _client = client ?? new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(_key.Callback!)); - } - - /// - /// Gets the KeyWrap algorithm that is being used. - /// - public override string Algorithm => _algorithm; - - /// - /// Gets or sets a user context for a . - /// - /// This is null by default. This can be used by runtimes or for extensibility scenarios. - public override string? Context { get; set; } - - /// - /// Gets the that is being used. - /// - public override SecurityKey Key => _key; - - /// - /// Unwrap a key. - /// - /// key to unwrap. - /// if is null. - /// if .Length == 0. - /// Unwrapped key. - public override byte[] UnwrapKey(byte[] keyBytes) - { - return UnwrapKeyAsync(keyBytes, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Wrap a key. - /// - /// the key to be wrapped - /// if is null. - /// if .Length == 0. - /// wrapped key. - public override byte[] WrapKey(byte[] keyBytes) - { - return WrapKeyAsync(keyBytes, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// true, if called from Dispose(), false, if invoked inside a finalizer - protected override void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _disposed = true; - _client.Dispose(); - } - } - } - - /// - /// Unwraps a symmetric key using Azure Key Vault. - /// - /// key to unwrap. - /// Propagates notification that operations should be canceled. - /// if is null. - /// if .Length == 0. - /// Unwrapped key. - private async Task UnwrapKeyAsync(byte[] keyBytes, CancellationToken cancellation) - { - if (keyBytes == null || keyBytes.Length == 0) - throw LogHelper.LogArgumentNullException(nameof(keyBytes)); - - return (await _client.UnwrapKeyAsync(_key.KeyId, Algorithm, keyBytes, cancellation).ConfigureAwait(false)).Result; - } - - /// - /// Wraps a symmetric key using Azure Key Vault. - /// - /// the key to be wrapped - /// Propagates notification that operations should be canceled. - /// if is null. - /// if .Length == 0. - /// wrapped key. - private async Task WrapKeyAsync(byte[] keyBytes, CancellationToken cancellation) - { - if (keyBytes == null || keyBytes.Length == 0) - throw LogHelper.LogArgumentNullException(nameof(keyBytes)); - - return (await _client.WrapKeyAsync(_key.KeyId, Algorithm, keyBytes, cancellation).ConfigureAwait(false)).Result; - } - } -} diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSecurityKey.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSecurityKey.cs deleted file mode 100644 index 9a56ce27e8..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSecurityKey.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.KeyVault; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace Microsoft.IdentityModel.KeyVaultExtensions -{ - /// - /// Provides signing and verifying operations using Azure Key Vault. - /// - public class KeyVaultSecurityKey : SecurityKey - { - private int? _keySize; - private string? _keyId; - - /// - /// The authentication callback delegate which is to be implemented by the client code. - /// - /// Identifier of the authority, a URL. - /// Identifier of the target resource that is the recipient of the requested token, a URL. - /// The scope of the authentication request. - /// An access token for Azure Key Vault. - public delegate Task AuthenticationCallback(string authority, string resource, string scope); - - /// - /// Initializes a new instance of the class. - /// - protected KeyVaultSecurityKey() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// The key identifier that is recognized by KeyVault. - /// The authentication callback that will obtain the access_token for KeyVault. - /// if is null or empty. - /// if is null. - public KeyVaultSecurityKey(string keyIdentifier, AuthenticationCallback callback) - { - Callback = callback ?? throw LogHelper.LogArgumentNullException(nameof(callback)); - KeyId = keyIdentifier; - } - - internal KeyVaultSecurityKey(string keyIdentifier, int keySize) - { - _keyId = keyIdentifier; - _keySize = keySize; - } - - /// - /// The authentication callback delegate that retrieves an access token for the KeyVault. - /// - public AuthenticationCallback? Callback { get; protected set; } - - /// - /// The uniform resource identifier of the security key. - /// - public override string KeyId - { - get => _keyId!; - set - { - if (string.IsNullOrEmpty(value)) - throw LogHelper.LogArgumentNullException(nameof(value)); - else if (StringComparer.Ordinal.Equals(_keyId, value)) - return; - - _keyId = value; - - // Reset the properties so they can be retrieved from Azure KeyVault the next time they are accessed. - _keySize = null; - } - } - - /// - /// The size of the security key. - /// - public override int KeySize - { - get - { - if (!_keySize.HasValue) - Initialize(); - - return _keySize!.Value; - } - } - - /// - /// Retrieve the properties from Azure Key Vault. - /// - private void Initialize() - { - using (var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(Callback!))) - { - var bundle = client.GetKeyAsync(_keyId, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - _keySize = new BitArray(bundle.Key.N).Length; - } - } - } -} diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSignatureProvider.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSignatureProvider.cs deleted file mode 100644 index 38478482fd..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/KeyVaultSignatureProvider.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.KeyVault; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace Microsoft.IdentityModel.KeyVaultExtensions -{ - /// - /// Provides signing and verifying operations using Azure Key Vault. - /// - public class KeyVaultSignatureProvider : SignatureProvider - { - private readonly HashAlgorithm _hash; - private readonly IKeyVaultClient _client; - private readonly KeyVaultSecurityKey _key; - private bool _disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// The that will be used for signature operations. - /// The signature algorithm to apply. - /// Whether this is required to create signatures then set this to true. - /// is null. - /// is null or empty. - public KeyVaultSignatureProvider(SecurityKey key, string algorithm, bool willCreateSignatures) - : this(key, algorithm, willCreateSignatures, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The that will be used for signature operations. - /// The signature algorithm to apply. - /// Whether this is required to create signatures then set this to true. - /// A mock used for testing purposes. - internal KeyVaultSignatureProvider(SecurityKey key, string algorithm, bool willCreateSignatures, IKeyVaultClient? client) - : base(key, algorithm) - { - _key = key as KeyVaultSecurityKey ?? throw LogHelper.LogArgumentNullException(nameof(key)); - _client = client ?? new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(_key.Callback!)); - WillCreateSignatures = willCreateSignatures; - - switch (algorithm) - { - case SecurityAlgorithms.RsaSha256: - _hash = SHA256.Create(); - break; - case SecurityAlgorithms.RsaSha384: - _hash = SHA384.Create(); - break; - case SecurityAlgorithms.RsaSha512: - _hash = SHA512.Create(); - break; - default: - throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10652, LogHelper.MarkAsNonPII(algorithm)), nameof(algorithm))); - } - } - - /// - /// Produces a signature over the 'input' using Azure Key Vault. - /// - /// The bytes to sign. - /// A signature over the input. - /// if is null. - /// if .Length == 0. - /// If Dispose has been called. - public override byte[] Sign(byte[] input) - { - return SignAsync(input, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Verifies that the is over using Azure Key Vault. - /// - /// bytes to verify. - /// signature to compare against. - /// true if the computed signature matches the signature parameter, false otherwise. - /// is null or has length == 0. - /// is null or has length == 0. - /// If Dispose has been called. - public override bool Verify(byte[] input, byte[] signature) - { - return VerifyAsync(input, signature, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - public override bool Verify(byte[] input, int inputOffset, int lengthOffset, byte[] signature, int signatureOffset, int signatureLength) => throw new NotImplementedException(); - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// true, if called from Dispose(), false, if invoked inside a finalizer - protected override void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _disposed = true; - _hash.Dispose(); - _client.Dispose(); - } - } - } - - /// - /// Creates a digital signature using Azure Key Vault. - /// - /// bytes to sign. - /// Propagates notification that operations should be canceled. - /// A signature over the input. - /// is null or has length == 0. - /// If Dispose has been called. - private async Task SignAsync(byte[] input, CancellationToken cancellation) - { - if (input == null || input.Length == 0) - throw LogHelper.LogArgumentNullException(nameof(input)); - - if (_disposed) - throw LogHelper.LogExceptionMessage(new ObjectDisposedException(GetType().ToString())); - - return (await _client.SignAsync(_key.KeyId, Algorithm, _hash.ComputeHash(input), cancellation).ConfigureAwait(false)).Result; - } - - /// - /// Verifies a digital signature using Azure Key Vault. - /// - /// bytes to verify. - /// signature to compare against. - /// Propagates notification that operations should be canceled. - /// true if the computed signature matches the signature parameter, false otherwise. - /// is null or has length == 0. - /// is null or has length == 0. - /// If Dispose has been called. - private async Task VerifyAsync(byte[] input, byte[] signature, CancellationToken cancellation) - { - if (input == null || input.Length == 0) - throw LogHelper.LogArgumentNullException(nameof(input)); - - if (signature == null || signature.Length == 0) - throw LogHelper.LogArgumentNullException(nameof(signature)); - - if (_disposed) - throw LogHelper.LogExceptionMessage(new ObjectDisposedException(GetType().ToString())); - - return await _client.VerifyAsync(_key.KeyId, Algorithm, _hash.ComputeHash(input), signature, cancellation).ConfigureAwait(false); - } - } -} diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/Microsoft.IdentityModel.KeyVaultExtensions.csproj b/src/Microsoft.IdentityModel.KeyVaultExtensions/Microsoft.IdentityModel.KeyVaultExtensions.csproj deleted file mode 100644 index 2825f85730..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/Microsoft.IdentityModel.KeyVaultExtensions.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Microsoft.IdentityModel.KeyVaultExtensions - Includes types that provide support for signing and encrypting tokens with Azure Key Vault. - true - Microsoft.IdentityModel.KeyVaultExtensions - netstandard2.0;net6.0;net8.0 - $(TargetFrameworks);net9.0 - .NET;Windows;Authentication;Identity;Azure;Key;Vault;Extensions - enable - - - - full - true - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/src/Microsoft.IdentityModel.KeyVaultExtensions/Properties/AssemblyInfo.cs b/src/Microsoft.IdentityModel.KeyVaultExtensions/Properties/AssemblyInfo.cs deleted file mode 100644 index 1f06ec3782..0000000000 --- a/src/Microsoft.IdentityModel.KeyVaultExtensions/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyMetadata("Serviceable", "True")] -[assembly: CLSCompliant(true)] -[assembly: ComVisible(false)] diff --git a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/GlobalSuppression.cs b/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/GlobalSuppression.cs deleted file mode 100644 index 04498d0dc3..0000000000 --- a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/GlobalSuppression.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Previously released with this name", Scope ="Type", Target = "Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.ManagedKeyVaultSecurityKey")] diff --git a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/InternalsVisibleTo.cs deleted file mode 100644 index cae70d8a10..0000000000 --- a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/InternalsVisibleTo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Tokens.Extensions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/ManagedKeyVaultSecurityKey.cs b/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/ManagedKeyVaultSecurityKey.cs deleted file mode 100644 index 78ce01f9ee..0000000000 --- a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/ManagedKeyVaultSecurityKey.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Azure.Services.AppAuthentication; -using Microsoft.IdentityModel.Clients.ActiveDirectory; -using Microsoft.IdentityModel.KeyVaultExtensions; -using Microsoft.IdentityModel.Logging; -using System; - -namespace Microsoft.IdentityModel.ManagedKeyVaultSecurityKey -{ - /// - /// Provides signing and verifying operations using Azure Key Vault - /// for resources that are using Managed identities for Azure resources. - /// - public class ManagedKeyVaultSecurityKey : KeyVaultSecurityKey - { - /// - /// Initializes a new instance of the class. - /// - /// The key identifier that is recognized by KeyVault. - /// if is null or empty. - public ManagedKeyVaultSecurityKey(string keyIdentifier) - : base(keyIdentifier, new AuthenticationCallback((new AzureServiceTokenProvider()).KeyVaultTokenCallback)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The key identifier that is recognized by KeyVault. - /// The authentication callback. - /// if is null or empty. - /// if is null. - public ManagedKeyVaultSecurityKey(string keyIdentifier, AuthenticationCallback callback) - : base(keyIdentifier, callback) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The key identifier. - /// Identifier of the client. - /// Secret of the client identity. - /// if is null or empty. - /// if is null or empty. - /// if is null or clientSecret. - public ManagedKeyVaultSecurityKey(string keyIdentifier, string clientId, string clientSecret) - { - if (string.IsNullOrEmpty(keyIdentifier)) - throw LogHelper.LogArgumentNullException(nameof(keyIdentifier)); - - if (string.IsNullOrEmpty(clientId)) - throw LogHelper.LogArgumentNullException(nameof(clientId)); - - if (string.IsNullOrEmpty(clientSecret)) - throw LogHelper.LogArgumentNullException(nameof(clientSecret)); - - KeyId = keyIdentifier; - Callback = new AuthenticationCallback(async (string authority, string resource, string scope) => - (await (new AuthenticationContext(authority, TokenCache.DefaultShared)).AcquireTokenAsync(resource, new ClientCredential(clientId, clientSecret)).ConfigureAwait(false)).AccessToken); - } - } -} diff --git a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.csproj b/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.csproj deleted file mode 100644 index aa342135ca..0000000000 --- a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - Microsoft.IdentityModel.ManagedKeyVaultSecurityKey - Includes types that provide support for signing and encrypting tokens with Azure Key Vault for - Applications that are using Managed identities for Azure resources. - true - Microsoft.IdentityModel.ManagedKeyVaultSecurityKey - netstandard2.0 - .NET;Windows;Authentication;Identity;Azure;Key;Vault;Extensions - enable - - - - full - true - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Properties/AssemblyInfo.cs b/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Properties/AssemblyInfo.cs deleted file mode 100644 index 1f06ec3782..0000000000 --- a/src/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyMetadata("Serviceable", "True")] -[assembly: CLSCompliant(true)] -[assembly: ComVisible(false)] diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs index 6ddc1b613d..36fb436a61 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs @@ -28,6 +28,7 @@ public class OpenIdConnectConfiguration : BaseConfiguration private ICollection _claimsSupported; private ICollection _claimsLocalesSupported; private ICollection _claimTypesSupported; + private ICollection _codeChallengeMethodsSupported; private ICollection _displayValuesSupported; private ICollection _dPoPSigningAlgValuesSupported; private ICollection _grantTypesSupported; @@ -42,6 +43,8 @@ public class OpenIdConnectConfiguration : BaseConfiguration private ICollection _requestObjectSigningAlgValuesSupported; private ICollection _responseModesSupported; private ICollection _responseTypesSupported; + private ICollection _revocationEndpointAuthMethodsSupported; + private ICollection _revocationEndpointAuthSigningAlgValuesSupported; private ICollection _scopesSupported; private ICollection _subjectTypesSupported; private ICollection _tokenEndpointAuthMethodsSupported; @@ -233,6 +236,24 @@ public OpenIdConnectConfiguration(string json) Interlocked.CompareExchange(ref _claimTypesSupported, new Collection(), null) ?? _claimTypesSupported; + /// + /// Gets the collection of 'code_challenge_methods_supported' + /// + [JsonPropertyName(OpenIdProviderMetadataNames.CodeChallengeMethodsSupported)] + public ICollection CodeChallengeMethodsSupported => + _codeChallengeMethodsSupported ?? + Interlocked.CompareExchange(ref _codeChallengeMethodsSupported, new Collection(), null) ?? + _codeChallengeMethodsSupported; + + /// + /// Gets or sets the 'device_authorization_endpoint'. + /// + [JsonPropertyName(OpenIdProviderMetadataNames.DeviceAuthorizationEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif + public string DeviceAuthorizationEndpoint { get; set; } + /// /// Gets the collection of 'display_values_supported' /// @@ -374,7 +395,7 @@ public OpenIdConnectConfiguration(string json) /// Gets or sets the /// [JsonIgnore] - public JsonWebKeySet JsonWebKeySet {get; set;} + public JsonWebKeySet JsonWebKeySet { get; set; } /// /// Boolean value specifying whether the OP can pass a sid (session ID) query parameter to identify the RP session at the OP when the logout_uri is used. Dafault Value is false. @@ -508,6 +529,33 @@ public OpenIdConnectConfiguration(string json) Interlocked.CompareExchange(ref _responseTypesSupported, new Collection(), null) ?? _responseTypesSupported; + /// + /// Gets or sets the 'revocation_endpoint' + /// + [JsonPropertyName(OpenIdProviderMetadataNames.RevocationEndpoint)] +#if NET6_0_OR_GREATER + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif + public string RevocationEndpoint { get; set; } + + /// + /// Gets the collection of 'revocation_endpoint_auth_methods_supported'. + /// + [JsonPropertyName(OpenIdProviderMetadataNames.RevocationEndpointAuthMethodsSupported)] + public ICollection RevocationEndpointAuthMethodsSupported => + _revocationEndpointAuthMethodsSupported ?? + Interlocked.CompareExchange(ref _revocationEndpointAuthMethodsSupported, new Collection(), null) ?? + _revocationEndpointAuthMethodsSupported; + + /// + /// Gets the collection of 'revocation_endpoint_auth_signing_alg_values_supported'. + /// + [JsonPropertyName(OpenIdProviderMetadataNames.RevocationEndpointAuthSigningAlgValuesSupported)] + public ICollection RevocationEndpointAuthSigningAlgValuesSupported => + _revocationEndpointAuthSigningAlgValuesSupported ?? + Interlocked.CompareExchange(ref _revocationEndpointAuthSigningAlgValuesSupported, new Collection(), null) ?? + _revocationEndpointAuthSigningAlgValuesSupported; + /// /// Gets or sets the 'service_documentation' /// @@ -688,6 +736,17 @@ public bool ShouldSerializeClaimTypesSupported() return ClaimTypesSupported.Count > 0; } + /// + /// Gets a bool that determines if the 'code_challenge_methods_supported' (CodeChallengeMethodsSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'code_challenge_methods_supported' (CodeChallengeMethodsSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeCodeChallengeMethodsSupported() + { + return CodeChallengeMethodsSupported.Count > 0; + } + /// /// Gets a bool that determines if the 'display_values_supported' (DisplayValuesSupported) property should be serialized. /// This is used by Json.NET in order to conditionally serialize properties. @@ -842,6 +901,28 @@ public bool ShouldSerializeResponseTypesSupported() return ResponseTypesSupported.Count > 0; } + /// + /// Gets a bool that determines if the 'revocation_endpoint_auth_methods_supported' (RevocationEndpointAuthMethodsSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'revocation_endpoint_auth_methods_supported' (RevocationEndpointAuthMethodsSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRevocationEndpointAuthMethodsSupported() + { + return RevocationEndpointAuthMethodsSupported.Count > 0; + } + + /// + /// Gets a bool that determines if the 'revocation_endpoint_auth_signing_alg_values_supported' (RevocationEndpointAuthSigningAlgValuesSupported) property should be serialized. + /// This is used by Json.NET in order to conditionally serialize properties. + /// + /// true if 'revocation_endpoint_auth_signing_alg_values_supported' (RevocationEndpointAuthSigningAlgValuesSupported) is not empty; otherwise, false. + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRevocationEndpointAuthSigningAlgValuesSupported() + { + return RevocationEndpointAuthSigningAlgValuesSupported.Count > 0; + } + /// /// Gets a bool that determines if the 'SigningKeys' property should be serialized. /// This is used by Json.NET in order to conditionally serialize properties. diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs index 17b354b390..d8d0c9d37b 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfigurationRetriever.cs @@ -10,13 +10,11 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { - /// /// Retrieves a populated given an address. /// public class OpenIdConnectConfigurationRetriever : IConfigurationRetriever { - /// /// Retrieves a populated given an address. /// diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Exceptions/OpenIdConnectProtocolException.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Exceptions/OpenIdConnectProtocolException.cs index 1cc301ed01..91585f9573 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Exceptions/OpenIdConnectProtocolException.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Exceptions/OpenIdConnectProtocolException.cs @@ -6,7 +6,6 @@ namespace Microsoft.IdentityModel.Protocols.OpenIdConnect { - /// /// This exception is thrown when an OpenIdConnect protocol handler encounters a protocol error. /// diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs index fc877effad..9e99a0899d 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Json/OpenIdConnectConfigurationSerializer.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif using System.Collections.Generic; using System.IO; using System.Text; @@ -26,7 +29,13 @@ internal static class OpenIdConnectConfigurationSerializer // If not found, then we assume we should put the value into AdditionalData. // If we didn't do that, we would pay a performance penalty for those cases where there is AdditionalData // but otherwise the JSON properties are all lower case. - public static HashSet OpenIdProviderMetadataNamesUpperCase = new HashSet + public static readonly +#if NET8_0_OR_GREATER + FrozenSet +#else + HashSet +#endif + OpenIdProviderMetadataNamesUpperCase = new HashSet { "ACR_VALUES_SUPPORTED", "AUTHORIZATION_ENDPOINT", @@ -40,7 +49,9 @@ internal static class OpenIdConnectConfigurationSerializer "CLAIMS_PARAMETER_SUPPORTED", "CLAIMS_SUPPORTED", "CLAIM_TYPES_SUPPORTED", + "CODE_CHALLENGE_METHODS_SUPPORTED", ".WELL-KNOWN/OPENID-CONFIGURATION", + "DEVICE_AUTHORIZATION_ENDPOINT", "DISPLAY_VALUES_SUPPORTED", "DPOP_SIGNING_ALG_VALUES_SUPPORTED", "END_SESSION_ENDPOINT", @@ -71,6 +82,9 @@ internal static class OpenIdConnectConfigurationSerializer "REQUIRE_REQUEST_URI_REGISTRATION", "RESPONSE_MODES_SUPPORTED", "RESPONSE_TYPES_SUPPORTED", + "REVOCATION_ENDPOINT", + "REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED", + "REVOCATION_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED", "SERVICE_DOCUMENTATION", "SCOPES_SUPPORTED", "SUBJECT_TYPES_SUPPORTED", @@ -82,7 +96,11 @@ internal static class OpenIdConnectConfigurationSerializer "USERINFO_ENCRYPTION_ALG_VALUES_SUPPORTED", "USERINFO_ENCRYPTION_ENC_VALUES_SUPPORTED", "USERINFO_SIGNING_ALG_VALUES_SUPPORTED", - }; + } +#if NET8_0_OR_GREATER + .ToFrozenSet() +#endif + ; #region Read public static OpenIdConnectConfiguration Read(string json) @@ -97,7 +115,7 @@ public static OpenIdConnectConfiguration Read(string json, OpenIdConnectConfigur { return Read(ref reader, config); } - catch(JsonException ex) + catch (JsonException ex) { if (ex.GetType() == typeof(JsonException)) throw; @@ -131,7 +149,7 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC LogHelper.MarkAsNonPII(reader.CurrentDepth), LogHelper.MarkAsNonPII(reader.BytesConsumed)))); - while(true) + while (true) { #region Check property name using ValueTextEquals // https://datatracker.ietf.org/doc/html/rfc7517#section-4, does not require that we reject JSON with duplicate member names. @@ -174,6 +192,12 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC else if (reader.ValueTextEquals(Utf8Bytes.ClaimTypesSupported)) JsonPrimitives.ReadStrings(ref reader, config.ClaimTypesSupported, MetadataName.ClaimTypesSupported, ClassName, true); + else if (reader.ValueTextEquals(Utf8Bytes.CodeChallengeMethodsSupported)) + JsonPrimitives.ReadStrings(ref reader, config.CodeChallengeMethodsSupported, MetadataName.CodeChallengeMethodsSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.DeviceAuthorizationEndpoint)) + config.DeviceAuthorizationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.DeviceAuthorizationEndpoint, ClassName, true); + else if (reader.ValueTextEquals(Utf8Bytes.DisplayValuesSupported)) JsonPrimitives.ReadStrings(ref reader, config.DisplayValuesSupported, MetadataName.DisplayValuesSupported, ClassName, true); @@ -277,14 +301,20 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC else if (reader.ValueTextEquals(Utf8Bytes.ResponseTypesSupported)) JsonPrimitives.ReadStrings(ref reader, config.ResponseTypesSupported, MetadataName.ResponseTypesSupported, ClassName, true); - else if (reader.ValueTextEquals(Utf8Bytes.ScopesSupported)) - JsonPrimitives.ReadStrings(ref reader, config.ScopesSupported, MetadataName.ScopesSupported, ClassName, true); + else if (reader.ValueTextEquals(Utf8Bytes.RevocationEndpoint)) + config.RevocationEndpoint = JsonPrimitives.ReadString(ref reader, MetadataName.RevocationEndpoint, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RevocationEndpointAuthMethodsSupported)) + JsonPrimitives.ReadStrings(ref reader, config.RevocationEndpointAuthMethodsSupported, MetadataName.RevocationEndpointAuthMethodsSupported, ClassName, true); + + else if (reader.ValueTextEquals(Utf8Bytes.RevocationEndpointAuthSigningAlgValuesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.RevocationEndpointAuthSigningAlgValuesSupported, MetadataName.RevocationEndpointAuthSigningAlgValuesSupported, ClassName, true); else if (reader.ValueTextEquals(Utf8Bytes.ServiceDocumentation)) - config.ServiceDocumentation = JsonPrimitives.ReadString(ref reader, MetadataName.ScopesSupported, ClassName, true); + config.ServiceDocumentation = JsonPrimitives.ReadString(ref reader, MetadataName.ServiceDocumentation, ClassName, true); - else if (reader.ValueTextEquals(Utf8Bytes.SubjectTypesSupported)) - JsonPrimitives.ReadStrings(ref reader, config.SubjectTypesSupported, MetadataName.SubjectTypesSupported, ClassName, true); + else if (reader.ValueTextEquals(Utf8Bytes.ScopesSupported)) + JsonPrimitives.ReadStrings(ref reader, config.ScopesSupported, MetadataName.ScopesSupported, ClassName, true); else if (reader.ValueTextEquals(Utf8Bytes.SubjectTypesSupported)) JsonPrimitives.ReadStrings(ref reader, config.SubjectTypesSupported, MetadataName.SubjectTypesSupported, ClassName, true); @@ -366,6 +396,12 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC else if (propertyName.Equals(MetadataName.ClaimTypesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.ClaimTypesSupported, propertyName, ClassName); + else if (propertyName.Equals(MetadataName.CodeChallengeMethodsSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.CodeChallengeMethodsSupported, propertyName, ClassName); + + else if (propertyName.Equals(MetadataName.DeviceAuthorizationEndpoint, StringComparison.OrdinalIgnoreCase)) + config.DeviceAuthorizationEndpoint = JsonPrimitives.ReadString(ref reader, propertyName, ClassName); + else if (propertyName.Equals(MetadataName.DisplayValuesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.DisplayValuesSupported, propertyName, ClassName); @@ -407,7 +443,7 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC else if (propertyName.Equals(MetadataName.IdTokenEncryptionEncValuesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.IdTokenEncryptionEncValuesSupported, propertyName, ClassName); - else if (propertyName.Equals(MetadataName.IdTokenSigningAlgValuesSupported , StringComparison.OrdinalIgnoreCase)) + else if (propertyName.Equals(MetadataName.IdTokenSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.IdTokenSigningAlgValuesSupported, propertyName, ClassName); else if (propertyName.Equals(MetadataName.IntrospectionEndpoint, StringComparison.OrdinalIgnoreCase)) @@ -470,6 +506,15 @@ public static OpenIdConnectConfiguration Read(ref Utf8JsonReader reader, OpenIdC else if (propertyName.Equals(MetadataName.ResponseTypesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.ResponseTypesSupported, propertyName, ClassName); + else if (propertyName.Equals(MetadataName.RevocationEndpoint, StringComparison.OrdinalIgnoreCase)) + config.RevocationEndpoint = JsonPrimitives.ReadString(ref reader, propertyName, ClassName); + + else if (propertyName.Equals(MetadataName.RevocationEndpointAuthMethodsSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.RevocationEndpointAuthMethodsSupported, propertyName, ClassName); + + else if (propertyName.Equals(MetadataName.RevocationEndpointAuthSigningAlgValuesSupported, StringComparison.OrdinalIgnoreCase)) + JsonPrimitives.ReadStrings(ref reader, config.RevocationEndpointAuthSigningAlgValuesSupported, propertyName, ClassName); + else if (propertyName.Equals(MetadataName.ScopesSupported, StringComparison.OrdinalIgnoreCase)) JsonPrimitives.ReadStrings(ref reader, config.ScopesSupported, propertyName, ClassName); @@ -577,6 +622,12 @@ public static void Write(ref Utf8JsonWriter writer, OpenIdConnectConfiguration c if (config.ClaimTypesSupported.Count > 0) JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ClaimTypesSupported, config.ClaimTypesSupported); + if (config.CodeChallengeMethodsSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.CodeChallengeMethodsSupported, config.CodeChallengeMethodsSupported); + + if (!string.IsNullOrEmpty(config.DeviceAuthorizationEndpoint)) + writer.WriteString(Utf8Bytes.DeviceAuthorizationEndpoint, config.DeviceAuthorizationEndpoint); + if (config.DisplayValuesSupported.Count > 0) JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.DisplayValuesSupported, config.DisplayValuesSupported); @@ -638,7 +689,7 @@ public static void Write(ref Utf8JsonWriter writer, OpenIdConnectConfiguration c writer.WriteString(Utf8Bytes.PushedAuthorizationRequestEndpoint, config.PushedAuthorizationRequestEndpoint); if (!string.IsNullOrEmpty(config.RegistrationEndpoint)) - writer.WriteString(Utf8Bytes.RegistrationEndpoint, config.RegistrationEndpoint); + writer.WriteString(Utf8Bytes.RegistrationEndpoint, config.RegistrationEndpoint); if (config.RequestObjectEncryptionAlgValuesSupported.Count > 0) JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RequestObjectEncryptionAlgValuesSupported, config.RequestObjectEncryptionAlgValuesSupported); @@ -670,6 +721,15 @@ public static void Write(ref Utf8JsonWriter writer, OpenIdConnectConfiguration c if (config.ScopesSupported.Count > 0) JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.ScopesSupported, config.ScopesSupported); + if (!string.IsNullOrEmpty(config.RevocationEndpoint)) + writer.WriteString(Utf8Bytes.RevocationEndpoint, config.RevocationEndpoint); + + if (config.RevocationEndpointAuthMethodsSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RevocationEndpointAuthMethodsSupported, config.RevocationEndpointAuthMethodsSupported); + + if (config.RevocationEndpointAuthSigningAlgValuesSupported.Count > 0) + JsonPrimitives.WriteStrings(ref writer, Utf8Bytes.RevocationEndpointAuthSigningAlgValuesSupported, config.RevocationEndpointAuthSigningAlgValuesSupported); + if (!string.IsNullOrEmpty(config.ServiceDocumentation)) writer.WriteString(Utf8Bytes.ServiceDocumentation, config.ServiceDocumentation); @@ -708,4 +768,3 @@ public static void Write(ref Utf8JsonWriter writer, OpenIdConnectConfiguration c #endregion } } - diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs index 554f81a0f7..62f15637c6 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdProviderMetadataNames.cs @@ -24,6 +24,8 @@ public static class OpenIdProviderMetadataNames public const string ClaimsParameterSupported = "claims_parameter_supported"; public const string ClaimsSupported = "claims_supported"; public const string ClaimTypesSupported = "claim_types_supported"; + public const string CodeChallengeMethodsSupported = "code_challenge_methods_supported"; + public const string DeviceAuthorizationEndpoint = "device_authorization_endpoint"; public const string Discovery = ".well-known/openid-configuration"; public const string DisplayValuesSupported = "display_values_supported"; public const string DPoPSigningAlgValuesSupported = "dpop_signing_alg_values_supported"; @@ -56,6 +58,9 @@ public static class OpenIdProviderMetadataNames public const string RequireRequestUriRegistration = "require_request_uri_registration"; public const string ResponseModesSupported = "response_modes_supported"; public const string ResponseTypesSupported = "response_types_supported"; + public const string RevocationEndpoint = "revocation_endpoint"; + public const string RevocationEndpointAuthMethodsSupported = "revocation_endpoint_auth_methods_supported"; + public const string RevocationEndpointAuthSigningAlgValuesSupported = "revocation_endpoint_auth_signing_alg_values_supported"; public const string ServiceDocumentation = "service_documentation"; public const string ScopesSupported = "scopes_supported"; public const string SubjectTypesSupported = "subject_types_supported"; @@ -89,6 +94,8 @@ internal static class OpenIdProviderMetadataUtf8Bytes public static ReadOnlySpan ClaimsParameterSupported => "claims_parameter_supported"u8; public static ReadOnlySpan ClaimsSupported => "claims_supported"u8; public static ReadOnlySpan ClaimTypesSupported => "claim_types_supported"u8; + public static ReadOnlySpan CodeChallengeMethodsSupported => "code_challenge_methods_supported"u8; + public static ReadOnlySpan DeviceAuthorizationEndpoint => "device_authorization_endpoint"u8; public static ReadOnlySpan Discovery => ".well-known/openid-configuration"u8; public static ReadOnlySpan DisplayValuesSupported => "display_values_supported"u8; public static ReadOnlySpan DPoPSigningAlgValuesSupported => "dpop_signing_alg_values_supported"u8; @@ -121,6 +128,9 @@ internal static class OpenIdProviderMetadataUtf8Bytes public static ReadOnlySpan RequireRequestUriRegistration => "require_request_uri_registration"u8; public static ReadOnlySpan ResponseModesSupported => "response_modes_supported"u8; public static ReadOnlySpan ResponseTypesSupported => "response_types_supported"u8; + public static ReadOnlySpan RevocationEndpoint => "revocation_endpoint"u8; + public static ReadOnlySpan RevocationEndpointAuthMethodsSupported => "revocation_endpoint_auth_methods_supported"u8; + public static ReadOnlySpan RevocationEndpointAuthSigningAlgValuesSupported => "revocation_endpoint_auth_signing_alg_values_supported"u8; public static ReadOnlySpan ServiceDocumentation => "service_documentation"u8; public static ReadOnlySpan ScopesSupported => "scopes_supported"u8; public static ReadOnlySpan SubjectTypesSupported => "subject_types_supported"u8; diff --git a/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs index e75bc6aa1a..349e9b51bb 100644 --- a/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs +++ b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs @@ -199,14 +199,9 @@ private void InitializeUsingRsa(RSA rsa, string algorithm) RSASignaturePadding = RSASignaturePadding.Pkcs1; } - RSAEncryptionPadding = algorithm switch - { - SecurityAlgorithms.RsaOAEP => RSAEncryptionPadding.OaepSHA1, - SecurityAlgorithms.RsaOaepKeyWrap => RSAEncryptionPadding.OaepSHA1, - SecurityAlgorithms.RsaOAEP256 => RSAEncryptionPadding.OaepSHA256, - _ => RSAEncryptionPadding.Pkcs1 - }; - + RSAEncryptionPadding = (algorithm.Equals(SecurityAlgorithms.RsaOAEP) || algorithm.Equals(SecurityAlgorithms.RsaOaepKeyWrap)) + ? RSAEncryptionPadding.OaepSHA1 + : RSAEncryptionPadding.Pkcs1; RSA = rsa; _decryptFunction = DecryptWithRsa; _encryptFunction = EncryptWithRsa; diff --git a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs index f544c99e93..e346c7cf73 100644 --- a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs +++ b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenException.cs @@ -2,18 +2,22 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Runtime.Serialization; +using System.Text; using Microsoft.IdentityModel.Logging; namespace Microsoft.IdentityModel.Tokens { - /// /// Represents a security token exception. /// [Serializable] public class SecurityTokenException : Exception { + [NonSerialized] + private string _stackTrace; + /// /// Initializes a new instance of the class. /// @@ -55,6 +59,49 @@ protected SecurityTokenException(SerializationInfo info, StreamingContext contex { } + /// + /// Gets the stack trace that is captured when the exception is created. + /// + public override string StackTrace + { + get + { + if (_stackTrace == null) + { + if (ExceptionDetail == null) + return base.StackTrace; +#if NET8_0_OR_GREATER + _stackTrace = new StackTrace(ExceptionDetail.StackFrames).ToString(); +#else + StringBuilder sb = new(); + foreach (StackFrame frame in ExceptionDetail.StackFrames) + { + sb.Append(frame.ToString()); + sb.Append(Environment.NewLine); + } + + _stackTrace = sb.ToString(); +#endif + } + + return _stackTrace; + } + } + + /// + /// Gets or sets the source of the exception. + /// + public override string Source + { + get => base.Source; + set => base.Source = value; + } + + internal ExceptionDetail ExceptionDetail + { + get; set; + } + #if NET472 || NETSTANDARD2_0 || NET6_0_OR_GREATER /// /// When overridden in a derived class, sets the System.Runtime.Serialization.SerializationInfo diff --git a/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs index ba34f3462b..dd03b90595 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs +++ b/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.KeyVaultExtensions, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.TestUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CrossVersionTokenValidation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Tokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.JsonWebTokens, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.JsonWebTokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Protocols.OpenIdConnect, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.TestUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.IdentityModel.Tokens.Jwt, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.IdentityModel.Tokens.Jwt.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.S2S, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs index ef28e1cf5c..89defe3734 100644 --- a/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs +++ b/src/Microsoft.IdentityModel.Tokens/Json/JsonWebKeySerializer.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif using System.Collections.Generic; using System.IO; using System.Text; @@ -21,7 +24,13 @@ internal static class JsonWebKeySerializer // If not found, then we assume we should put the value into AdditionalData. // If we didn't do that, we would pay a performance penalty for those cases where there is AdditionalData // but otherwise the JSON properties are all lower case. - public static HashSet JsonWebKeyParameterNamesUpperCase = new HashSet + public static readonly +#if NET8_0_OR_GREATER + FrozenSet +#else + HashSet +# endif + JsonWebKeyParameterNamesUpperCase = new HashSet { "ALG", "CRV", @@ -46,7 +55,11 @@ internal static class JsonWebKeySerializer "X5T#S256", "X5U", "Y" - }; + } +#if NET8_0_OR_GREATER + .ToFrozenSet() +#endif + ; #region Read public static JsonWebKey Read(string json) @@ -399,4 +412,3 @@ public static void Write(ref Utf8JsonWriter writer, JsonWebKey jsonWebKey) #endregion } } - diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index b9fe6c2996..679fbf2b8f 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -35,7 +35,7 @@ internal static class LogMessages public const string IDX10207 = "IDX10207: Unable to validate audience. The 'audiences' parameter is null."; public const string IDX10208 = "IDX10208: Unable to validate audience. validationParameters.ValidAudience is null or whitespace and validationParameters.ValidAudiences is null."; public const string IDX10209 = "IDX10209: Token has length: '{0}' which is larger than the MaximumTokenSizeInBytes: '{1}'."; - public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace"; + public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace."; public const string IDX10214 = "IDX10214: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudience: '{1}' or validationParameters.ValidAudiences: '{2}'."; public const string IDX10222 = "IDX10222: Lifetime validation failed. The token is not yet valid. ValidFrom (UTC): '{0}', Current time (UTC): '{1}'."; public const string IDX10223 = "IDX10223: Lifetime validation failed. The token is expired. ValidTo (UTC): '{0}', Current time (UTC): '{1}'."; @@ -79,6 +79,8 @@ internal static class LogMessages //public const string IDX10263 = "IDX10263: Unable to re-validate with ConfigurationManager.LastKnownGoodConfiguration as it is expired."; public const string IDX10264 = "IDX10264: Reading issuer signing keys from validation parameters and configuration."; public const string IDX10265 = "IDX10265: Reading issuer signing keys from configuration."; + //public const string IDX10266 = "IDX10266: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace, validationParameters.ValidIssuers is null or empty and ConfigurationManager is null."; + // 10500 - SignatureValidation public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature."; diff --git a/src/Microsoft.IdentityModel.Tokens/Properties/AssemblyInfo.cs b/src/Microsoft.IdentityModel.Tokens/Properties/AssemblyInfo.cs index f22a0f4b5e..12d7bcb55f 100644 --- a/src/Microsoft.IdentityModel.Tokens/Properties/AssemblyInfo.cs +++ b/src/Microsoft.IdentityModel.Tokens/Properties/AssemblyInfo.cs @@ -17,7 +17,6 @@ [assembly: InternalsVisibleTo("Microsoft.IdentityModel.Tokens.Saml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.IdentityModel.Xml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.AzureAD.SmartSessionEvaluator, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo("Microsoft.IdentityModel.KeyVaultExtensions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.IdentityModel.Protocols, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.IdentityModel.Protocols.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.IdentityModel.Validators, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.Tokens/SecurityAlgorithms.cs b/src/Microsoft.IdentityModel.Tokens/SecurityAlgorithms.cs index 16bee7667c..3db07ff95e 100644 --- a/src/Microsoft.IdentityModel.Tokens/SecurityAlgorithms.cs +++ b/src/Microsoft.IdentityModel.Tokens/SecurityAlgorithms.cs @@ -31,7 +31,6 @@ public static class SecurityAlgorithms public const string Aes256KW = "A256KW"; public const string RsaPKCS1 = "RSA1_5"; public const string RsaOAEP = "RSA-OAEP"; - public const string RsaOAEP256 = "RSA-OAEP-256"; // See: https://www.w3.org/TR/xmlenc-core1/#sec-Exclusive-Canonicalization public const string ExclusiveC14n = "http://www.w3.org/2001/10/xml-exc-c14n#"; diff --git a/src/Microsoft.IdentityModel.Tokens/SupportedAlgorithms.cs b/src/Microsoft.IdentityModel.Tokens/SupportedAlgorithms.cs index dfd2416db7..086374360a 100644 --- a/src/Microsoft.IdentityModel.Tokens/SupportedAlgorithms.cs +++ b/src/Microsoft.IdentityModel.Tokens/SupportedAlgorithms.cs @@ -40,7 +40,6 @@ internal static class SupportedAlgorithms internal static readonly ICollection RsaEncryptionAlgorithms = new Collection { SecurityAlgorithms.RsaOAEP, - SecurityAlgorithms.RsaOAEP256, SecurityAlgorithms.RsaPKCS1, SecurityAlgorithms.RsaOaepKeyWrap }; diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs index bb68764a6d..2112772699 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs @@ -13,7 +13,7 @@ namespace Microsoft.IdentityModel.Tokens /// /// Contains a set of parameters that are used by a when validating a . /// - public class TokenValidationParameters + public partial class TokenValidationParameters { private string _authenticationType; private TimeSpan _clockSkew = DefaultClockSkew; @@ -38,7 +38,7 @@ public class TokenValidationParameters /// Default for the maximum token size. /// /// 250 KB (kilobytes). - public const Int32 DefaultMaximumTokenSizeInBytes = 1024 * 250; + public const int DefaultMaximumTokenSizeInBytes = 1024 * 250; /// /// Copy constructor for . @@ -66,6 +66,7 @@ protected TokenValidationParameters(TokenValidationParameters other) IssuerSigningKeyValidatorUsingConfiguration = other.IssuerSigningKeyValidatorUsingConfiguration; IssuerValidator = other.IssuerValidator; IssuerValidatorAsync = other.IssuerValidatorAsync; + IssuerValidationDelegateAsync = other.IssuerValidationDelegateAsync; IssuerValidatorUsingConfiguration = other.IssuerValidatorUsingConfiguration; LifetimeValidator = other.LifetimeValidator; LogTokenId = other.LogTokenId; @@ -172,22 +173,6 @@ public string AuthenticationType } } - ///// - ///// Gets or sets the for validating X509Certificate2(s). - ///// - //public X509CertificateValidator CertificateValidator - //{ - // get - // { - // return _certificateValidator; - // } - - // set - // { - // _certificateValidator = value; - // } - //} - /// /// Gets or sets the clock skew to apply when validating a time. /// @@ -368,7 +353,6 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, /// public IssuerValidator IssuerValidator { get; set; } - /// /// Gets or sets a delegate that will be used to validate the issuer of the token. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd b/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd new file mode 100644 index 0000000000..ebefb90859 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/AsyncValidate.cd @@ -0,0 +1,81 @@ + + + + + + ABEAIAABEEAAEAIAAAAAAAABEQAAAEEACABAAAAkIoA= + Validation\TokenValidationResult.cs + + + + + + AAEAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA= + Validation\IssuerValidationResult.cs + + + + + + AAAEAAAAAAAAAAAAAAAAEAAEAAAAAAAAAEAABAAAAAA= + Validation\ExceptionDetail.cs + + + + + + + + + AIAAAAJAAAAAAAAAAAgAIAABAAgAAAAABEBBAAAAAAA= + Validation\ValidationResult.cs + + + + + + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAA= + Validation\LogDetail.cs + + + + + + + + + + + + AAAIAAAAAAAAAAAAAAIAAAQAAABAQAAAAAAAAAAAAAA= + Validation\ValidationFailureType.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAEIAAIAAAAAA= + Validation\MessageDetail.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + CallContext.cs + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs new file mode 100644 index 0000000000..1ab311c9e8 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ExceptionDetail.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains information so that Exceptions can be logged or thrown written as required. + /// + internal class ExceptionDetail + { + /// + /// Creates an instance of + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of exception that occurred. + /// contains information about the stack frame where the exception occurred. + public ExceptionDetail(MessageDetail messageDetail, Type exceptionType, StackFrame stackFrame) + : this(messageDetail, exceptionType, stackFrame, null) + { + } + + /// + /// Creates an instance of + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of exception that occurred. + /// contains information about the stack frame where the exception occurred. + /// is the inner exception that occurred. + public ExceptionDetail(MessageDetail messageDetail, Type exceptionType, StackFrame stackFrame, Exception innerException) + { + ExceptionType = exceptionType; + InnerException = innerException; + MessageDetail = messageDetail; + StackFrames.Add(stackFrame); + } + + /// + /// Creates an instance of an using + /// + /// An instantance of an Exception. + public Exception GetException() + { + if (InnerException != null) + return Activator.CreateInstance(ExceptionType, MessageDetail.Message, InnerException) as Exception; + + return Activator.CreateInstance(ExceptionType, MessageDetail.Message) as Exception; + } + + /// + /// Gets the type of exception that occurred. + /// + public Type ExceptionType { get; } + + /// + /// Gets the inner exception that occurred. + /// + public Exception InnerException { get; } + + /// + /// Gets the message details that are used to generate the exception message. + /// + public MessageDetail MessageDetail { get; } + + /// + /// Gets the stack frames where the exception occurred. + /// + public IList StackFrames { get; } = []; + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd b/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd new file mode 100644 index 0000000000..cd69bb66db --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Exceptions.cd @@ -0,0 +1,35 @@ + + + + + + AIAAAAAAAgAAAgAAAAQAAAAAAAAAAAAAAAAAAAAAAAA= + Exceptions\SecurityTokenException.cs + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Exceptions\SecurityTokenValidationException.cs + + + + + + AAgAAEAAAAAAAAAAAAACAAAgAAAAAAAAAAAAAAAAAAA= + Exceptions\SecurityTokenInvalidIssuerException.cs + + + + + + AAAEAAAAAAAAAAAAAAAAEAAEAAAAAAAAAEAABAAAAAA= + Validation\ExceptionDetail.cs + + + + \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs new file mode 100644 index 0000000000..35c53dc561 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/IssuerValidationResult.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains the result of validating a issuer. + /// The contains a collection of for each step in the token validation. + /// + internal class IssuerValidationResult : ValidationResult + { + private Exception _exception; + + /// + /// Creates an instance of + /// + /// is the issuer that was validated successfully. + public IssuerValidationResult(string issuer) + : base(ValidationFailureType.ValidationSucceeded) + { + Issuer = issuer; + IsValid = true; + } + + /// + /// Creates an instance of + /// + /// is the issuer that was intended to be validated. + /// is the that occurred during validation. + /// is the that occurred during validation. + public IssuerValidationResult(string issuer, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail) + : base(validationFailure, exceptionDetail) + { + Issuer = issuer; + IsValid = false; + } + + /// + /// Gets the that occurred during validation. + /// + public override Exception Exception + { + get + { + if (_exception != null || ExceptionDetail == null) + return _exception; + + HasValidOrExceptionWasRead = true; + _exception = ExceptionDetail.GetException(); + SecurityTokenInvalidIssuerException securityTokenInvalidIssuerException = _exception as SecurityTokenInvalidIssuerException; + if (securityTokenInvalidIssuerException != null) + { + securityTokenInvalidIssuerException.InvalidIssuer = Issuer; + securityTokenInvalidIssuerException.ExceptionDetail = ExceptionDetail; + securityTokenInvalidIssuerException.Source = "Microsoft.IdentityModel.Tokens"; + } + + return _exception; + } + } + + /// + /// Gets the issuer that was validated or intended to be validated. + /// + public string Issuer { get; } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs new file mode 100644 index 0000000000..6b91a67c1e --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/LogDetail.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Abstractions; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains information so that logs can be written when needed. + /// + internal class LogDetail + { + /// + /// Creates an instance of + /// + /// contains information about the exception that is used to generate the exception message. + /// is the level of the event log. + public LogDetail(MessageDetail messageDetail, EventLogLevel eventLogLevel) + { + EventLogLevel = eventLogLevel; + MessageDetail = messageDetail; + } + + /// + /// Gets the level of the event log. + /// + public EventLogLevel EventLogLevel { get; } + + /// + /// Gets the message detail. + /// + public MessageDetail MessageDetail { get; } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs new file mode 100644 index 0000000000..800126a725 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/MessageDetail.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains information about a message that is used to generate a message for logging or exceptions. + /// + internal class MessageDetail + { + private string _message; + + // TODO - remove the need to create NonPII objects, we could use tuples where bool == true => object is PII. + // TODO - does this need to be ReadOnlyMemory? + /// + /// Creates an instance of + /// + /// The message to be formated. + /// The parameters for formatting. + public MessageDetail(string formatString, params object[] parameters) + { + // TODO - paramter validation. + FormatString = formatString; + Parameters = parameters; + } + + /// + /// Gets the formatted message. + /// + public string Message + { + get + { + _message ??= LogHelper.FormatInvariant(FormatString, Parameters); + return _message; + } + } + + private string FormatString { get; } + + private object[] Parameters { get; } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs new file mode 100644 index 0000000000..cab11a6bbc --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationParameters.IssuerValidationDelegate.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// partial class for the IssuerValidation delegate. + /// + public partial class TokenValidationParameters + { + /// + /// Gets or sets a delegate that will be used to validate the issuer of a . + /// + internal IssuerValidationDelegateAsync IssuerValidationDelegateAsync { get; set; } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs similarity index 85% rename from src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs rename to src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs index 6714c71b9e..46c90738e0 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenValidationResult.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/TokenValidationResult.cs @@ -19,10 +19,6 @@ public class TokenValidationResult private readonly TokenValidationParameters _validationParameters; private readonly TokenHandler _tokenHandler; - private Exception _exception; - private bool _hasIsValidOrExceptionBeenRead = false; - private bool _isValid = false; - // Fields lazily initialized in a thread-safe manner. _claimsIdentity is protected by the _claimsIdentitySyncObj // lock, and since null is a valid initialized value, _claimsIdentityInitialized tracks whether or not it's valid. // _claims is constructed by reading the data from the ClaimsIdentity and is synchronized using Interlockeds @@ -37,6 +33,11 @@ public class TokenValidationResult private ClaimsIdentity _claimsIdentity; private Dictionary _claims; private Dictionary _propertyBag; + // TODO - lazy creation of _validationResults + private List _validationResults = []; + + private Exception _exception; + private bool _isValid; /// /// Creates an instance of @@ -60,6 +61,18 @@ internal TokenValidationResult(SecurityToken securityToken, TokenHandler tokenHa SecurityToken = securityToken; } + /// + /// Adds a to the list of . + /// + /// the associated with one of the validation steps. For example . + internal void AddValidationResult(ValidationResult validationResult) + { + if (validationResult is null) + throw LogHelper.LogArgumentNullException(nameof(validationResult)); + + _validationResults.Add(validationResult); + } + /// /// The created from the validated security token. /// @@ -67,7 +80,7 @@ public IDictionary Claims { get { - if (!_hasIsValidOrExceptionBeenRead) + if (!HasValidOrExceptionWasRead) LogHelper.LogWarning(LogMessages.IDX10109); if (_claims is null && ClaimsIdentity is { } ci) @@ -162,7 +175,7 @@ public Exception Exception { get { - _hasIsValidOrExceptionBeenRead = true; + HasValidOrExceptionWasRead = true; return _exception; } set @@ -171,6 +184,8 @@ public Exception Exception } } + internal bool HasValidOrExceptionWasRead { get; set; } + /// /// Gets or sets the issuer that was found in the token. /// @@ -183,7 +198,7 @@ public bool IsValid { get { - _hasIsValidOrExceptionBeenRead = true; + HasValidOrExceptionWasRead = true; return _isValid; } set @@ -228,5 +243,19 @@ public bool IsValid /// (e.g for a JSON Web Token, from the "typ" header). /// public string TokenType { get; set; } + + /// + /// Gets the list of that contains the result of validating the token. + /// + internal IReadOnlyList ValidationResults + { + get + { + if (_validationResults is null) + Interlocked.CompareExchange(ref _validationResults, new List(), null); + + return _validationResults; + } + } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd new file mode 100644 index 0000000000..a6af5bfb82 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationDelegates.cd @@ -0,0 +1,21 @@ + + + + + + YAgEEAQAKMQck0AAi5R6AACRWgBkBQIAAQgYQsaIkxA= + Validation\TokenValidationParameters.IssuerValidationDelegate.cs + + + + + + + + + AAAAAAAAAACAAAAAACAQBAAAAAAAAAAAgAAAAAAAAAA= + Validation\Validators.Issuer.cs + + + + \ No newline at end of file diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs new file mode 100644 index 0000000000..3d872bb63a --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// The type of the failure that occurred when validating a . + /// + internal abstract class ValidationFailureType + { + /// + /// Creates an instance of + /// + protected ValidationFailureType(string name) + { + Name = name; + } + + /// + /// Gets the name of the . + /// + public string Name { get; } + + /// + /// Defines a type that represents a required parameter was null. + /// + public static readonly ValidationFailureType NullArgument = new NullArgumentFailure("NullArgument"); + private class NullArgumentFailure : ValidationFailureType { internal NullArgumentFailure(string name) : base(name) { } } + + /// + /// Defines a type that represents that issuer validation failed. + /// + public static readonly ValidationFailureType IssuerValidationFailed = new IssuerValidationFailure("IssuerValidationFailed"); + private class IssuerValidationFailure : ValidationFailureType { internal IssuerValidationFailure(string name) : base(name) { } } + + /// + /// Defines a type that represents that no evaluation has taken place. + /// + public static readonly ValidationFailureType ValidationNotEvaluated = new NotEvaluated("NotEvaluated"); + private class NotEvaluated : ValidationFailureType { internal NotEvaluated(string name) : base(name) { } } + + /// + /// Defines a type that represents that no evaluation has taken place. + /// + public static readonly ValidationFailureType ValidationSucceeded = new Succeeded("Succeeded"); + private class Succeeded : ValidationFailureType { internal Succeeded(string name) : base(name) { } } + + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs new file mode 100644 index 0000000000..f4d8ce0dc0 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains results of a single step in validating a . + /// A maintains a list of for each step in the token validation. + /// + internal abstract class ValidationResult + { + private bool _isValid = false; + + /// + /// Creates an instance of + /// + protected ValidationResult() + { + ValidationFailureType = ValidationFailureType.ValidationNotEvaluated; + } + + /// + /// Creates an instance of + /// + /// The that occurred during validation. + protected ValidationResult(ValidationFailureType validationFailureType) + { + ValidationFailureType = validationFailureType; + } + + /// + /// Creates an instance of + /// + /// The that occurred during validation. + /// The representing the that occurred during validation. + protected ValidationResult(ValidationFailureType validationFailureType, ExceptionDetail exceptionDetail) + { + ValidationFailureType = validationFailureType; + ExceptionDetail = exceptionDetail; + } + + /// + /// Adds a new stack frame to the exception details. + /// + /// + public void AddStackFrame(StackFrame stackFrame) + { + ExceptionDetail.StackFrames.Add(stackFrame); + } + + /// + /// Gets the that occurred during validation. + /// + public abstract Exception Exception { get; } + + /// + /// Gets the that occurred during validation. + /// + public ExceptionDetail ExceptionDetail { get; } + + /// + /// True if the token was successfully validated, false otherwise. + /// + public bool IsValid + { + get + { + HasValidOrExceptionWasRead = true; + return _isValid; + } + set + { + _isValid = value; + } + } + + // TODO - HasValidOrExceptionWasRead, IsValid, Exception are temporary and will be removed when TokenValidationResult derives from ValidationResult. + /// + /// Gets or sets a boolean recording if IsValid or Exception was called. + /// + protected bool HasValidOrExceptionWasRead { get; set; } + + /// + /// Logs the validation result. + /// +#pragma warning disable CA1822 // Mark members as static + public void Log() +#pragma warning restore CA1822 // Mark members as static + { + // TODO - Do we need this, how will it work? + } + + /// + /// Contains any logs that would have been written. + /// + public IList LogDetails { get; } = new List(); + + /// + /// Gets the indicating why the validation was not satisfied. + /// + public ValidationFailureType ValidationFailureType + { + get; + } = ValidationFailureType.ValidationNotEvaluated; + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs new file mode 100644 index 0000000000..9c41082c53 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Partial class for Audience Validation. + /// + public static partial class Validators + { + /// + /// Determines if the audiences found in a are valid. + /// + /// The audiences found in the . + /// The being validated. + /// required for validation. + /// If 'validationParameters' is null. + /// If 'audiences' is null and is true. + /// If is null or whitespace and is null. + /// If none of the 'audiences' matched either or one of . + /// An EXACT match is required. + public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.AudienceValidator != null) + { + if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidAudienceException( + LogHelper.FormatInvariant( + LogMessages.IDX10231, + LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString()))) + { + InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) + }); + + return; + } + + if (!validationParameters.ValidateAudience) + { + LogHelper.LogWarning(LogMessages.IDX10233); + return; + } + + if (audiences == null) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null }); + + if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); + + if (!audiences.Any()) + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206)) + { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); + + // create enumeration of all valid audiences from validationParameters + IEnumerable validationParametersAudiences; + + if (validationParameters.ValidAudiences == null) + validationParametersAudiences = new[] { validationParameters.ValidAudience }; + else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience)) + validationParametersAudiences = validationParameters.ValidAudiences; + else + validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience }); + + if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences)) + return; + + SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException( + LogHelper.FormatInvariant(LogMessages.IDX10214, + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)), + LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences)))) + { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }; + + if (!validationParameters.LogValidationExceptions) + throw ex; + + throw LogHelper.LogExceptionMessage(ex); + } + + private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences) + { + foreach (string tokenAudience in audiences) + { + if (string.IsNullOrWhiteSpace(tokenAudience)) + continue; + + foreach (string validAudience in validationParametersAudiences) + { + if (string.IsNullOrWhiteSpace(validAudience)) + continue; + + if (AudiencesMatch(validationParameters, tokenAudience, validAudience)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); + + return true; + } + } + } + + return false; + } + + private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience) + { + if (validAudience.Length == tokenAudience.Length) + { + if (string.Equals(validAudience, tokenAudience)) + return true; + } + else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience)) + return true; + + return false; + } + + private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience) + { + int length = -1; + + if (validAudience.Length == tokenAudience.Length + 1 && validAudience.EndsWith("/", StringComparison.InvariantCulture)) + length = validAudience.Length - 1; + else if (tokenAudience.Length == validAudience.Length + 1 && tokenAudience.EndsWith("/", StringComparison.InvariantCulture)) + length = tokenAudience.Length - 1; + + // the length of the audiences is different by more than 1 and neither ends in a "/" + if (length == -1) + return false; + + if (string.CompareOrdinal(validAudience, 0, tokenAudience, 0, length) == 0) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); + + return true; + } + + return false; + } + + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs new file mode 100644 index 0000000000..faca7d71e5 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs @@ -0,0 +1,293 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Definition for delegate that will validate the issuer value in a token. + /// + /// The issuer to validate. + /// The that is being validated. + /// required for validation. + /// + /// + /// A that contains the results of validating the issuer. + /// This delegate is not expected to throw. + internal delegate Task IssuerValidationDelegateAsync( + string issuer, + SecurityToken securityToken, + TokenValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken); + + /// + /// IssuerValidation + /// + public static partial class Validators + { + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If is null or whitespace and is null. + /// If 'issuer' failed to matched either or one of . + /// An EXACT match is required. + public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + return ValidateIssuer(issuer, securityToken, validationParameters, null); + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The required for issuer and signing key validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If ' configuration' is null. + /// If is null or whitespace and is null and is null. + /// If 'issuer' failed to matched either or one of or . + /// An EXACT match is required. + internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) + { + ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration); + return vt.IsCompletedSuccessfully ? + vt.Result : + vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The required for issuer and signing key validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If ' configuration' is null. + /// If is null or whitespace and is null and is null. + /// If 'issuer' failed to matched either or one of or . + /// An EXACT match is required. + internal static async ValueTask ValidateIssuerAsync( + string issuer, + SecurityToken securityToken, + TokenValidationParameters validationParameters, + BaseConfiguration configuration) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.IssuerValidatorAsync != null) + return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false); + + if (validationParameters.IssuerValidatorUsingConfiguration != null) + return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration); + + if (validationParameters.IssuerValidator != null) + return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); + + if (!validationParameters.ValidateIssuer) + { + LogHelper.LogWarning(LogMessages.IDX10235); + return issuer; + } + + if (string.IsNullOrWhiteSpace(issuer)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211) + { InvalidIssuer = issuer }); + + // Throw if all possible places to validate against are null or empty + if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) + && validationParameters.ValidIssuers.IsNullOrEmpty() + && string.IsNullOrWhiteSpace(configuration?.Issuer)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204) + { InvalidIssuer = issuer }); + + if (configuration != null) + { + if (string.Equals(configuration.Issuer, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + } + + if (string.Equals(validationParameters.ValidIssuer, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + + if (validationParameters.ValidIssuers != null) + { + foreach (string str in validationParameters.ValidIssuers) + { + if (string.IsNullOrEmpty(str)) + { + LogHelper.LogInformation(LogMessages.IDX10262); + continue; + } + + if (string.Equals(str, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + } + } + + SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException( + LogHelper.FormatInvariant(LogMessages.IDX10205, + LogHelper.MarkAsNonPII(issuer), + LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)), + LogHelper.MarkAsNonPII(configuration?.Issuer))) + { InvalidIssuer = issuer }; + + if (!validationParameters.LogValidationExceptions) + throw ex; + + throw LogHelper.LogExceptionMessage(ex); + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// + /// + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If is null or whitespace and is null. + /// If 'issuer' failed to matched either or one of . + /// An EXACT match is required. + internal static async Task ValidateIssuerAsync( + string issuer, + SecurityToken securityToken, + TokenValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(issuer)) + { + return new IssuerValidationResult( + issuer, + ValidationFailureType.NullArgument, + new ExceptionDetail( + new MessageDetail( + LogMessages.IDX10211, + null), + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true), + null)); + } + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (securityToken == null) + throw LogHelper.LogArgumentNullException(nameof(securityToken)); + + BaseConfiguration configuration = null; + if (validationParameters.ConfigurationManager != null) + configuration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false); + + // Throw if all possible places to validate against are null or empty + if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) + && validationParameters.ValidIssuers.IsNullOrEmpty() + && string.IsNullOrWhiteSpace(configuration?.Issuer)) + { + return new IssuerValidationResult( + issuer, + ValidationFailureType.IssuerValidationFailed, + new ExceptionDetail( + new MessageDetail( + LogMessages.IDX10211, + null), + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true))); + } + + // TODO - we should distinguish if configuration, TVP.ValidIssuer or TVP.ValidIssuers was used to validate the issuer. + if (configuration != null) + { + if (string.Equals(configuration.Issuer, issuer)) + { + // TODO - how and when to log + // Logs will have to be passed back to Wilson + // so that they can be written to the correct place and in the correct format respecting PII. + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer), callContext); + + return new IssuerValidationResult(issuer); + } + } + + if (string.Equals(validationParameters.ValidIssuer, issuer)) + { + return new IssuerValidationResult(issuer); + } + + if (validationParameters.ValidIssuers != null) + { + foreach (string str in validationParameters.ValidIssuers) + { + if (string.IsNullOrEmpty(str)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10262); + + continue; + } + + if (string.Equals(str, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return new IssuerValidationResult(issuer); + } + } + } + + return new IssuerValidationResult( + issuer, + ValidationFailureType.IssuerValidationFailed, + new ExceptionDetail( + new MessageDetail( + LogMessages.IDX10205, + LogHelper.MarkAsNonPII(issuer), + LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)), + LogHelper.MarkAsNonPII(configuration?.Issuer)), + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true))); + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs new file mode 100644 index 0000000000..77c553c4db --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// IssuerValidation + /// + public static partial class Validators + { + /// + /// Validates the lifetime of a . + /// + /// The 'notBefore' time found in the . + /// The 'expiration' time found in the . + /// The being validated. + /// required for validation. + /// If 'validationParameters' is null. + /// If 'expires.HasValue' is false and is true. + /// If 'notBefore' is > 'expires'. + /// If 'notBefore' is > DateTime.UtcNow. + /// If 'expires' is < DateTime.UtcNow. + /// All time comparisons apply . + public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.LifetimeValidator != null) + { + if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken)) + { NotBefore = notBefore, Expires = expires }); + + return; + } + + if (!validationParameters.ValidateLifetime) + { + LogHelper.LogInformation(LogMessages.IDX10238); + return; + } + + ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters); + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs index 482f2f3d37..6c652f6a3b 100644 --- a/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs +++ b/src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs @@ -3,6 +3,7 @@ using System; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Abstractions; namespace Microsoft.IdentityModel.Tokens { @@ -49,7 +50,8 @@ internal static void ValidateLifetime(DateTime? notBefore, DateTime? expires, Se }); // if it reaches here, that means lifetime of the token is valid - LogHelper.LogInformation(LogMessages.IDX10239); + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10239); } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs index 2c9f9e6ca1..000ba4c617 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validators.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; using Microsoft.IdentityModel.Abstractions; using Microsoft.IdentityModel.Logging; @@ -14,7 +12,7 @@ namespace Microsoft.IdentityModel.Tokens /// /// AudienceValidator /// - public static class Validators + public static partial class Validators { /// /// Validates if a given algorithm for a is valid. @@ -50,283 +48,6 @@ public static void ValidateAlgorithm(string algorithm, SecurityKey securityKey, } } - /// - /// Determines if the audiences found in a are valid. - /// - /// The audiences found in the . - /// The being validated. - /// required for validation. - /// If 'validationParameters' is null. - /// If 'audiences' is null and is true. - /// If is null or whitespace and is null. - /// If none of the 'audiences' matched either or one of . - /// An EXACT match is required. - public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.AudienceValidator != null) - { - if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage( - new SecurityTokenInvalidAudienceException( - LogHelper.FormatInvariant( - LogMessages.IDX10231, - LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString()))) - { - InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) - }); - - return; - } - - if (!validationParameters.ValidateAudience) - { - LogHelper.LogWarning(LogMessages.IDX10233); - return; - } - - if (audiences == null) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null }); - - if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); - - if (!audiences.Any()) - throw LogHelper.LogExceptionMessage( - new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206)) - { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); - - // create enumeration of all valid audiences from validationParameters - IEnumerable validationParametersAudiences; - - if (validationParameters.ValidAudiences == null) - validationParametersAudiences = new[] { validationParameters.ValidAudience }; - else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience)) - validationParametersAudiences = validationParameters.ValidAudiences; - else - validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience }); - - if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences)) - return; - - SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException( - LogHelper.FormatInvariant(LogMessages.IDX10214, - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)), - LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"), - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences)))) - { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }; - - if (!validationParameters.LogValidationExceptions) - throw ex; - - throw LogHelper.LogExceptionMessage(ex); - } - - private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences) - { - foreach (string tokenAudience in audiences) - { - if (string.IsNullOrWhiteSpace(tokenAudience)) - continue; - - foreach (string validAudience in validationParametersAudiences) - { - if (string.IsNullOrWhiteSpace(validAudience)) - continue; - - if (AudiencesMatch(validationParameters, tokenAudience, validAudience)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); - - return true; - } - } - } - - return false; - } - - private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience) - { - if (validAudience.Length == tokenAudience.Length) - { - if (string.Equals(validAudience, tokenAudience)) - return true; - } - else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience)) - return true; - - return false; - } - - private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience) - { - int length = -1; - - if (validAudience.Length == tokenAudience.Length + 1 && validAudience.EndsWith("/", StringComparison.InvariantCulture)) - length = validAudience.Length - 1; - else if (tokenAudience.Length == validAudience.Length + 1 && tokenAudience.EndsWith("/", StringComparison.InvariantCulture)) - length = tokenAudience.Length - 1; - - // the length of the audiences is different by more than 1 and neither ends in a "/" - if (length == -1) - return false; - - if (string.CompareOrdinal(validAudience, 0, tokenAudience, 0, length) == 0) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); - - return true; - } - - return false; - } - - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// required for validation. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If is null or whitespace and is null. - /// If 'issuer' failed to matched either or one of . - /// An EXACT match is required. - public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - return ValidateIssuer(issuer, securityToken, validationParameters, null); - } - - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// required for validation. - /// The required for issuer and signing key validation. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If ' configuration' is null. - /// If is null or whitespace and is null and is null. - /// If 'issuer' failed to matched either or one of or . - /// An EXACT match is required. - internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration); - return vt.IsCompletedSuccessfully ? - vt.Result : - vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// required for validation. - /// The required for issuer and signing key validation. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If ' configuration' is null. - /// If is null or whitespace and is null and is null. - /// If 'issuer' failed to matched either or one of or . - /// An EXACT match is required. - internal static async ValueTask ValidateIssuerAsync( - string issuer, - SecurityToken securityToken, - TokenValidationParameters validationParameters, - BaseConfiguration configuration) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.IssuerValidatorAsync != null) - return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false); - - if (validationParameters.IssuerValidatorUsingConfiguration != null) - return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration); - - if (validationParameters.IssuerValidator != null) - return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); - - if (!validationParameters.ValidateIssuer) - { - LogHelper.LogWarning(LogMessages.IDX10235); - return issuer; - } - - if (string.IsNullOrWhiteSpace(issuer)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211) - { InvalidIssuer = issuer }); - - // Throw if all possible places to validate against are null or empty - if ( string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) - && validationParameters.ValidIssuers.IsNullOrEmpty() - && string.IsNullOrWhiteSpace(configuration?.Issuer)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204) - { InvalidIssuer = issuer }); - - if (configuration != null) - { - if (string.Equals(configuration.Issuer, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - } - - if (string.Equals(validationParameters.ValidIssuer, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - - if (validationParameters.ValidIssuers != null) - { - foreach (string str in validationParameters.ValidIssuers) - { - if (string.IsNullOrEmpty(str)) - { - LogHelper.LogInformation(LogMessages.IDX10262); - continue; - } - - if (string.Equals(str, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - } - } - - SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException( - LogHelper.FormatInvariant(LogMessages.IDX10205, - LogHelper.MarkAsNonPII(issuer), - LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"), - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)), - LogHelper.MarkAsNonPII(configuration?.Issuer))) - { InvalidIssuer = issuer }; - - if (!validationParameters.LogValidationExceptions) - throw ex; - - throw LogHelper.LogExceptionMessage(ex); - } - /// /// Validates the that signed a . /// @@ -422,42 +143,6 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T } } - /// - /// Validates the lifetime of a . - /// - /// The 'notBefore' time found in the . - /// The 'expiration' time found in the . - /// The being validated. - /// required for validation. - /// If 'validationParameters' is null. - /// If 'expires.HasValue' is false and is true. - /// If 'notBefore' is > 'expires'. - /// If 'notBefore' is > DateTime.UtcNow. - /// If 'expires' is < DateTime.UtcNow. - /// All time comparisons apply . - public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.LifetimeValidator != null) - { - if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken)) - { NotBefore = notBefore, Expires = expires }); - - return; - } - - if (!validationParameters.ValidateLifetime) - { - LogHelper.LogInformation(LogMessages.IDX10238); - return; - } - - ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters); - } - /// /// Validates if a token has been replayed. /// diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs index 02f308f804..bd041cac2f 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTests.cs @@ -2744,30 +2744,6 @@ public static TheoryData RoundTripJWEKeyWrapTestCases EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes128CbcHmacSha256) }, new CreateTokenTheoryData - { - TestId = "RsaOAEP256_Aes128CbcHmacSha256", - ValidationParameters = Default.TokenValidationParameters(KeyingMaterial.RsaSecurityKey_2048, Default.SymmetricSigningKey256), - Payload = Default.PayloadString, - SigningCredentials = Default.SymmetricSigningCredentials, - EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaOAEP256, SecurityAlgorithms.Aes128CbcHmacSha256) - }, - new CreateTokenTheoryData - { - TestId = "RsaOAEP256_Aes192CbcHmacSha384", - ValidationParameters = Default.TokenValidationParameters(KeyingMaterial.RsaSecurityKey_2048, Default.SymmetricSigningKey256), - Payload = Default.PayloadString, - SigningCredentials = Default.SymmetricSigningCredentials, - EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaOAEP256, SecurityAlgorithms.Aes192CbcHmacSha384) - }, - new CreateTokenTheoryData - { - TestId = "RsaOAEP256_Aes256CbcHmacSha512", - ValidationParameters = Default.TokenValidationParameters(KeyingMaterial.RsaSecurityKey_2048, Default.SymmetricSigningKey256), - Payload = Default.PayloadString, - SigningCredentials = Default.SymmetricSigningCredentials, - EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaOAEP256, SecurityAlgorithms.Aes256CbcHmacSha512) - }, - new CreateTokenTheoryData { TestId = "RsaOAEP_Aes192CbcHmacSha384", ValidationParameters = Default.TokenValidationParameters(KeyingMaterial.RsaSecurityKey_2048, Default.SymmetricSigningKey256), @@ -3547,7 +3523,7 @@ public static TheoryData ValidateJwsWithConfigTheoryData incorrectSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); theoryData.Add(new JwtTheoryData { - TestId = nameof(Default.AsymmetricJws) + "_" + "TVPInvalid" + "_" + "ConfigSigningKeysInvalid" + "_SignatureValidatorReturnsValidToken", + TestId = nameof(Default.AsymmetricJws) + "_TVPInvalid_ConfigSigningKeysInvalid_SignatureValidatorReturnsValidToken", Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/Microsoft.IdentityModel.JsonWebTokens.Tests.csproj b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/Microsoft.IdentityModel.JsonWebTokens.Tests.csproj index e333ac1f95..523c78d729 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/Microsoft.IdentityModel.JsonWebTokens.Tests.csproj +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/Microsoft.IdentityModel.JsonWebTokens.Tests.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultCryptoProviderTests.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultCryptoProviderTests.cs deleted file mode 100644 index 39e83e852c..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultCryptoProviderTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; -using Microsoft.Azure.KeyVault.WebKey; -using Microsoft.IdentityModel.TestUtils; -using Microsoft.IdentityModel.Tokens; -using Xunit; -using static Microsoft.IdentityModel.KeyVaultExtensions.KeyVaultSecurityKey; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultCryptoProviderTests - { - [Fact] - public void ShouldCacheSignatureProvider() - { - TestUtilities.WriteHeader($"{this}.ShouldCacheSignatureProvider"); - var context = new CompareContext($"{this}.ShouldCacheSignatureProvider"); - var keyVaultKeyWithEmptyKid = new CustomKeyVaultSecurityKey("test", new AuthenticationCallback((string authority, string resource, string scope) => Task.FromResult(string.Empty))); - var keyVaultCryptoProvider = new KeyVaultCryptoProvider(); - var signatureProvider = keyVaultCryptoProvider.Create(JsonWebKeySignatureAlgorithm.RS256, keyVaultKeyWithEmptyKid, true); - if (keyVaultCryptoProvider.CryptoProviderCache.TryGetSignatureProvider(keyVaultKeyWithEmptyKid, SecurityAlgorithms.RsaSha256Signature, typeof(KeyVaultSignatureProvider).ToString(), true, out var _)) - context.Diffs.Add("A SignatureProvider was added to keyVaultCryptoProvider.CryptoProviderCache.CryptoProviderCache, but ShouldCacheSignatureProvider() should return false as the key has an empty key id."); - - CryptoProviderFactory.Default.ReleaseSignatureProvider(signatureProvider as KeyVaultSignatureProvider); - - TestUtilities.AssertFailIfErrors(context); - } - - public class CustomKeyVaultSecurityKey : KeyVaultSecurityKey - { - /// - /// Initializes a new instance of the class. - /// - public CustomKeyVaultSecurityKey(string keyIdentifier, AuthenticationCallback callback) : base(keyIdentifier, callback) - { - } - - internal override string InternalId => ""; - } - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultKeyWrapProviderTests.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultKeyWrapProviderTests.cs deleted file mode 100644 index e87b5fcca2..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultKeyWrapProviderTests.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Microsoft.Azure.KeyVault; -using Microsoft.IdentityModel.TestUtils; -using Microsoft.IdentityModel.Tokens; -using Xunit; - -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultKeyWrapProviderTests - { - private readonly MockKeyVaultClient _client; - private readonly SecurityKey _key; - - public KeyVaultKeyWrapProviderTests() - { - _client = new MockKeyVaultClient(); - _key = new KeyVaultSecurityKey(KeyVaultUtilities.CreateKeyIdentifier(), keySize: default); - } - - [Theory, MemberData(nameof(DisposeProviderTheoryData))] - public void DisposeProviderTest(KeyWrapProviderTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.DisposeProviderTest", theoryData); - - try - { - var provider = new KeyVaultKeyWrapProvider(_key, theoryData.Algorithm, _client); - _key.CryptoProviderFactory.ReleaseKeyWrapProvider(provider); - - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception exception) - { - theoryData.ExpectedException.ProcessException(exception, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData DisposeProviderTheoryData - { - get => new TheoryData - { - new KeyWrapProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaPKCS1, - ExpectedException = ExpectedException.NoExceptionExpected, - First = true, - TestId = nameof(SecurityAlgorithms.RsaPKCS1), - }, - new KeyWrapProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaOAEP, - ExpectedException = ExpectedException.NoExceptionExpected, - TestId = nameof(SecurityAlgorithms.RsaOAEP), - }, - new KeyWrapProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaOAEP256, - ExpectedException = ExpectedException.NoExceptionExpected, - TestId = nameof(SecurityAlgorithms.RsaOAEP256), - }, - }; - } - - [Theory, MemberData(nameof(KeyWrapProviderTheoryData))] - public void WrapUnwrapKeyTest(KeyWrapProviderTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.WrapUnwrapKeyTest", theoryData); - - try - { - var provider = new KeyVaultKeyWrapProvider(_key, theoryData.Algorithm, _client); - if (provider == null) - context.AddDiff("(provider == null)"); - - var keyBytes = Guid.NewGuid().ToByteArray(); - var wrappedKey = provider.WrapKey(keyBytes); - if (wrappedKey == null) - context.AddDiff("(wrappedKey == null)"); - - if (_client.ExpectedKeyWrapLength != wrappedKey.Length) - context.AddDiff($"_client.ExpectedKeyWrapLength != wrappedKey.Length. {_client.ExpectedKeyWrapLength} != {wrappedKey.Length}"); - - if (Utility.AreEqual(keyBytes, wrappedKey)) - context.AddDiff("Utility.AreEqual(keyBytes, wrappedKey)"); - - var unwrappedKey = provider.UnwrapKey(wrappedKey); - if (unwrappedKey == null) - context.AddDiff("(unwrappedKey == null)"); - - IdentityComparer.AreBytesEqual(keyBytes, unwrappedKey, context); - - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception exception) - { - theoryData.ExpectedException.ProcessException(exception, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData KeyWrapProviderTheoryData - { - get => new TheoryData - { - new KeyWrapProviderTheoryData - { - Algorithm = null, - ExpectedException = ExpectedException.ArgumentNullException(), - First = true, - TestId = "NullAlgorithm", - }, - new KeyWrapProviderTheoryData - { - Algorithm = string.Empty, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "EmptyAlgorithm", - }, - new KeyWrapProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaPKCS1, - TestId = nameof(SecurityAlgorithms.RsaPKCS1), - }, - new KeyWrapProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaOAEP, - TestId = nameof(SecurityAlgorithms.RsaOAEP), - }, - }; - } - } -} - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyAuthenticationCallbackTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyAuthenticationCallbackTheoryData.cs deleted file mode 100644 index 39747206c6..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyAuthenticationCallbackTheoryData.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultSecurityKeyAuthenticationCallbackTheoryData : KeyVaultSecurityKeyTheoryData - { - public KeyVaultSecurityKey.AuthenticationCallback Callback { get; set; } = new KeyVaultSecurityKey.AuthenticationCallback((string authority, string resource, string scope) => Task.FromResult(string.Empty)); - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyConfidentialClientTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyConfidentialClientTheoryData.cs deleted file mode 100644 index 75e3f1bca9..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyConfidentialClientTheoryData.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultSecurityKeyConfidentialClientTheoryData : KeyVaultSecurityKeyTheoryData - { - public string ClientId { get; set; } = $"{Guid.NewGuid():D}"; - public string ClientSecret { get; set; } = Guid.NewGuid().ToString(); - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyManagedServiceIdentityTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyManagedServiceIdentityTheoryData.cs deleted file mode 100644 index f5c096fa16..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyManagedServiceIdentityTheoryData.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultSecurityKeyManagedServiceIdentityTheoryData : KeyVaultSecurityKeyTheoryData - { - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTests.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTests.cs deleted file mode 100644 index 9badfe1f52..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Reflection; -using Microsoft.Azure.KeyVault.Models; -using Microsoft.IdentityModel.TestUtils; -using Xunit; - -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultSecurityKeyTests - { - private static ExpectedException ArgumentNullExceptionExpected = new ExpectedException(typeExpected: typeof(TargetInvocationException), substringExpected: "Exception has been thrown by the target of an invocation.", innerTypeExpected: typeof(ArgumentNullException)); - private static ExpectedException KeyVaultErrorExceptionExpected = new ExpectedException(typeExpected: typeof(TargetInvocationException), substringExpected: "Exception has been thrown by the target of an invocation.", innerTypeExpected: typeof(KeyVaultErrorException)); - - [Theory, MemberData(nameof(KeyVaultSecurityKeyAuthenticationCallbackTheoryData))] - public void AuthenticationCallbackConstructorParams(KeyVaultSecurityKeyAuthenticationCallbackTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.AuthenticationCallbackConstructorParams", theoryData); - - try - { - _ = Activator.CreateInstance(theoryData.Type, new object[] { theoryData.KeyIdentifier, theoryData.Callback }); - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - } - - public static TheoryData KeyVaultSecurityKeyAuthenticationCallbackTheoryData - { - get => new TheoryData - { - new KeyVaultSecurityKeyAuthenticationCallbackTheoryData - { - // Callback = default, - ExpectedException = ExpectedException.ArgumentNullException(), - First = true, - KeyIdentifier = null, - TestId = typeof(KeyVaultSecurityKey).FullName, - Type = typeof(KeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyAuthenticationCallbackTheoryData - { - // Callback = default, - ExpectedException = ExpectedException.ArgumentNullException(), - KeyIdentifier = string.Empty, - TestId = typeof(KeyVaultSecurityKey).FullName, - Type = typeof(KeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyAuthenticationCallbackTheoryData - { - Callback = null, - ExpectedException = ExpectedException.ArgumentNullException(), - // KeyIdentifier = default, - TestId = typeof(KeyVaultSecurityKey).FullName, - Type = typeof(KeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyAuthenticationCallbackTheoryData - { - // Callback = default, - ExpectedException = KeyVaultErrorExceptionExpected, - // KeyIdentifier = default, - TestId = typeof(KeyVaultSecurityKey).FullName, - Type = typeof(KeyVaultSecurityKey), - }, - }; - } - } -} - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTheoryData.cs deleted file mode 100644 index 73dd0a20c8..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSecurityKeyTheoryData.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Microsoft.IdentityModel.TestUtils; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public abstract class KeyVaultSecurityKeyTheoryData : TheoryDataBase - { - public string KeyIdentifier { get; set; } = KeyVaultUtilities.CreateKeyIdentifier(); - public Type Type { get; set; } - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSignatureProviderTests.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSignatureProviderTests.cs deleted file mode 100644 index 7ab512625c..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultSignatureProviderTests.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Linq; -using Microsoft.Azure.KeyVault; -using Microsoft.IdentityModel.TestUtils; -using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.KeyVaultExtensions; -using Xunit; - -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyVaultSignatureProviderTests - { - private readonly MockKeyVaultClient _client; - private readonly SecurityKey _key; - - public KeyVaultSignatureProviderTests() - { - _client = new MockKeyVaultClient(); - _key = new KeyVaultSecurityKey(KeyVaultUtilities.CreateKeyIdentifier(), keySize: default); - } - - [Theory, MemberData(nameof(DisposeProviderTheoryData))] - public void DisposeProviderTest(SignatureProviderTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.DisposeProviderTest", theoryData); - - try - { - var provider = new KeyVaultSignatureProvider(_key, theoryData.Algorithm, willCreateSignatures: true, _client); - _key.CryptoProviderFactory.ReleaseSignatureProvider(provider); - - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception exception) - { - theoryData.ExpectedException.ProcessException(exception, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData DisposeProviderTheoryData - { - get => new TheoryData - { - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha256, - ExpectedException = ExpectedException.NoExceptionExpected, - First = true, - TestId = nameof(SecurityAlgorithms.RsaSha256), - }, - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha384, - ExpectedException = ExpectedException.NoExceptionExpected, - TestId = nameof(SecurityAlgorithms.RsaSha384), - }, - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha512, - ExpectedException = ExpectedException.NoExceptionExpected, - TestId = nameof(SecurityAlgorithms.RsaSha512), - }, - }; - } - - [Theory, MemberData(nameof(SignatureProviderTheoryData))] - public void SignatureTest(SignatureProviderTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.SignatureTest", theoryData); - - try - { - var provider = new KeyVaultSignatureProvider(_key, theoryData.Algorithm, willCreateSignatures: true, _client); - if (provider == null) - context.AddDiff("(provider == null)"); - - var input = Guid.NewGuid().ToByteArray(); - var signature = provider.Sign(input); - - if (signature == null) - context.AddDiff("(signature == null)"); - - if (_client.ExpectedSignatureLength != signature.Length) - context.AddDiff($"_client.ExpectedSignatureLength != signature.Length. == {_client.ExpectedSignatureLength}, {signature.Length}."); - - if (!provider.Verify(input, signature)) - context.AddDiff("!provider.Verify(input, signature)"); - - var tamperedInput = new byte[input.Length]; - input.CopyTo(tamperedInput, 0); - if (tamperedInput[0] == byte.MaxValue) - tamperedInput[0]--; - else - tamperedInput[0]++; - - if (provider.Verify(tamperedInput, signature)) - context.AddDiff("provider.Verify(tamperedInput, signature)"); - - foreach (var data in SignatureProviderTheoryData) - { - var newAlgorithm = (data.Single() as SignatureProviderTheoryData)?.Algorithm; - if (string.IsNullOrEmpty(newAlgorithm)) - continue; // Skip invalid input - - // Check that a given Security Key will only validate a signature using the same hash algorithm. - var isValidSignature = new KeyVaultSignatureProvider(_key, newAlgorithm, willCreateSignatures: false, _client).Verify(input, signature); - if (StringComparer.Ordinal.Equals(theoryData.Algorithm, newAlgorithm)) - { - if (!isValidSignature) - context.AddDiff("Signature should have been valid, isValidSignature == false"); - } - else if (isValidSignature) - context.AddDiff("Signature should NOT have been valid, isValidSignature == true"); - } - - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception exception) - { - theoryData.ExpectedException.ProcessException(exception, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData SignatureProviderTheoryData - { - get => new TheoryData - { - new SignatureProviderTheoryData - { - Algorithm = null, - ExpectedException = ExpectedException.ArgumentNullException(), - First = true, - TestId = "NullAlgorithm", - }, - new SignatureProviderTheoryData - { - Algorithm = string.Empty, - ExpectedException = ExpectedException.ArgumentNullException(), - TestId = "EmptyAlgorithm", - }, - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha256, - TestId = nameof(SecurityAlgorithms.RsaSha256), - }, - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha384, - TestId = nameof(SecurityAlgorithms.RsaSha384), - }, - new SignatureProviderTheoryData - { - Algorithm = SecurityAlgorithms.RsaSha512, - TestId = nameof(SecurityAlgorithms.RsaSha512), - }, - }; - } - } -} - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultUtilities.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultUtilities.cs deleted file mode 100644 index 072a868068..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyVaultUtilities.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public static class KeyVaultUtilities - { - public static string CreateKeyIdentifier() => CreateKeyIdentifier("contoso.vault.azure.net", nameof(KeyVaultUtilities), $"{Guid.NewGuid():N}"); - - public static string CreateKeyIdentifier(string vaultBaseUrl, string vaultKeyName, string vaultKeyVersion) - { - return new UriBuilder(Uri.UriSchemeHttps, vaultBaseUrl, -1, $"/keys/{vaultKeyName}/{vaultKeyVersion}").Uri.ToString(); - } - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyWrapProviderTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyWrapProviderTheoryData.cs deleted file mode 100644 index 5e8ab0f194..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/KeyWrapProviderTheoryData.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.IdentityModel.TestUtils; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class KeyWrapProviderTheoryData : TheoryDataBase - { - public string Algorithm { get; set; } - } -} diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Microsoft.IdentityModel.KeyVaultExtensions.Tests.csproj b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Microsoft.IdentityModel.KeyVaultExtensions.Tests.csproj deleted file mode 100644 index 3c797af7a1..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Microsoft.IdentityModel.KeyVaultExtensions.Tests.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - Microsoft.IdentityModel.KeyVaultExtensions.Tests - $(MSBuildThisFileDirectory)..\..\build\35MSSharedLib1024.snk - true - Tests for Microsoft.IdentityModel.KeyVaultExtensions - true - Microsoft.IdentityModel.KeyVaultExtensions.Tests - true - - - - - - - - - - - - diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/MockKeyVaultClient.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/MockKeyVaultClient.cs deleted file mode 100644 index 8fcbc31691..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/MockKeyVaultClient.cs +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.KeyVault; -using Microsoft.Azure.KeyVault.Models; -using Microsoft.IdentityModel.TestUtils; -using Microsoft.IdentityModel.Tokens; -using Microsoft.Rest; -using Microsoft.Rest.Azure; -using Newtonsoft.Json; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - [CLSCompliant(false)] - public class MockKeyVaultClient : IKeyVaultClient - { - private readonly Microsoft.Azure.KeyVault.WebKey.JsonWebKey _key; - private readonly RSACryptoServiceProvider _rsa; - private bool _disposed = false; - - public MockKeyVaultClient() - { - _rsa = new RSACryptoServiceProvider(); - _rsa.ImportParameters(KeyingMaterial.RsaParameters_2048); - _key = new Microsoft.Azure.KeyVault.WebKey.JsonWebKey(_rsa, includePrivateParameters: false); - } - - public int ExpectedKeyWrapLength => 256; - - public int ExpectedSignatureLength => 256; - - public JsonSerializerSettings SerializationSettings => throw new NotImplementedException(); - - public JsonSerializerSettings DeserializationSettings => throw new NotImplementedException(); - - public ServiceClientCredentials Credentials => throw new NotImplementedException(); - - public string ApiVersion => throw new NotImplementedException(); - - public string AcceptLanguage { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public int? LongRunningOperationRetryTimeout { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public bool? GenerateClientRequestId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public Task> BackupCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> BackupKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> BackupSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> BackupStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> CreateCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, CertificatePolicy certificatePolicy = null, CertificateAttributes certificateAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> CreateKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string kty, int? keySize = null, IList keyOps = null, KeyAttributes keyAttributes = null, IDictionary tags = null, string curve = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DecryptWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] value, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteCertificateContactsWithHttpMessagesAsync(string vaultBaseUrl, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteCertificateIssuerWithHttpMessagesAsync(string vaultBaseUrl, string issuerName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteCertificateOperationWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> DeleteStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - /// - /// Calls and - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - _disposed = true; - if (disposing) - { - _rsa.Dispose(); - } - } - } - - public Task> EncryptWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] value, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetCertificateContactsWithHttpMessagesAsync(string vaultBaseUrl, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificateIssuersNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificateIssuersWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetCertificateIssuerWithHttpMessagesAsync(string vaultBaseUrl, string issuerName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetCertificateOperationWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetCertificatePolicyWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificatesNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificatesWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, bool? includePending = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificateVersionsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetCertificateVersionsWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, string certificateVersion, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedCertificatesNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedCertificatesWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, bool? includePending = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetDeletedCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedKeysNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedKeysWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetDeletedKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedSasDefinitionsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedSasDefinitionsWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetDeletedSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedSecretsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedSecretsWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetDeletedSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedStorageAccountsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetDeletedStorageAccountsWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetDeletedStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetKeysNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetKeysWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetKeyVersionsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetKeyVersionsWithHttpMessagesAsync(string vaultBaseUrl, string keyName, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - _key.Kid = GetKeyIdentifier(vaultBaseUrl, keyName, keyVersion); - KeyAttributes attributes = new KeyAttributes(enabled: true); - var response = new AzureOperationResponse - { - Body = new KeyBundle(_key, attributes), - }; - - return Task.FromResult(response); - } - - public Task> GetPendingCertificateSigningRequestWithHttpMessagesAsync(string vault, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSasDefinitionsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSasDefinitionsWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSecretsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSecretsWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSecretVersionsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetSecretVersionsWithHttpMessagesAsync(string vaultBaseUrl, string secretName, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, string secretVersion, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetStorageAccountsNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task>> GetStorageAccountsWithHttpMessagesAsync(string vaultBaseUrl, int? maxresults = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> GetStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> ImportCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, string base64EncodedCertificate, string password = null, CertificatePolicy certificatePolicy = null, CertificateAttributes certificateAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> ImportKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Microsoft.Azure.KeyVault.WebKey.JsonWebKey key, bool? hsm = null, KeyAttributes keyAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> MergeCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, IList x509Certificates, CertificateAttributes certificateAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task PurgeDeletedCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task PurgeDeletedKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task PurgeDeletedSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task PurgeDeletedStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RecoverDeletedCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RecoverDeletedKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RecoverDeletedSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RecoverDeletedSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RecoverDeletedStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RegenerateStorageAccountKeyWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string keyName, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RestoreCertificateWithHttpMessagesAsync(string vaultBaseUrl, byte[] certificateBundleBackup, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RestoreKeyWithHttpMessagesAsync(string vaultBaseUrl, byte[] keyBundleBackup, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RestoreSecretWithHttpMessagesAsync(string vaultBaseUrl, byte[] secretBundleBackup, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> RestoreStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, byte[] storageBundleBackup, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SetCertificateContactsWithHttpMessagesAsync(string vaultBaseUrl, Contacts contacts, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SetCertificateIssuerWithHttpMessagesAsync(string vaultBaseUrl, string issuerName, string provider, IssuerCredentials credentials = null, OrganizationDetails organizationDetails = null, IssuerAttributes attributes = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SetSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, string templateUri, string sasType, string validityPeriod, SasDefinitionAttributes sasDefinitionAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SetSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, string value, IDictionary tags = null, string contentType = null, SecretAttributes secretAttributes = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SetStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string resourceId, string activeKeyName, bool autoRegenerateKey, string regenerationPeriod = null, StorageAccountAttributes storageAccountAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> SignWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] value, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - string digestAlgorithm; - switch (algorithm) - { - case SecurityAlgorithms.RsaSha256: - digestAlgorithm = SecurityAlgorithms.Sha256; - break; - case SecurityAlgorithms.RsaSha384: - digestAlgorithm = SecurityAlgorithms.Sha384; - break; - case SecurityAlgorithms.RsaSha512: - digestAlgorithm = SecurityAlgorithms.Sha512; - break; - default: - throw new NotImplementedException(); - } - - var result = _rsa.SignHash(value, digestAlgorithm); - var response = new AzureOperationResponse - { - Body = new KeyOperationResult(GetKeyIdentifier(vaultBaseUrl, keyName, keyVersion), result), - }; - - return Task.FromResult(response); - } - - public Task> UnwrapKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] value, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - bool fOAEP; - if (StringComparer.OrdinalIgnoreCase.Equals(algorithm, SecurityAlgorithms.RsaOAEP)) - fOAEP = true; - else if (StringComparer.OrdinalIgnoreCase.Equals(algorithm, SecurityAlgorithms.RsaPKCS1)) - fOAEP = false; - else - throw new NotImplementedException($"The mock key vault is not configured to unwrap keys using the {algorithm} security algorithm."); - - var result = _rsa.Decrypt(value, fOAEP); - var response = new AzureOperationResponse - { - Body = new KeyOperationResult(GetKeyIdentifier(vaultBaseUrl, keyName, keyVersion), result), - }; - - return Task.FromResult(response); - } - - public Task> UpdateCertificateIssuerWithHttpMessagesAsync(string vaultBaseUrl, string issuerName, string provider = null, IssuerCredentials credentials = null, OrganizationDetails organizationDetails = null, IssuerAttributes attributes = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateCertificateOperationWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, bool cancellationRequested, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateCertificatePolicyWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, CertificatePolicy certificatePolicy, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateCertificateWithHttpMessagesAsync(string vaultBaseUrl, string certificateName, string certificateVersion, CertificatePolicy certificatePolicy = null, CertificateAttributes certificateAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, IList keyOps = null, KeyAttributes keyAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateSasDefinitionWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string sasDefinitionName, string templateUri = null, string sasType = null, string validityPeriod = null, SasDefinitionAttributes sasDefinitionAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateSecretWithHttpMessagesAsync(string vaultBaseUrl, string secretName, string secretVersion, string contentType = null, SecretAttributes secretAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> UpdateStorageAccountWithHttpMessagesAsync(string vaultBaseUrl, string storageAccountName, string activeKeyName = null, bool? autoRegenerateKey = null, string regenerationPeriod = null, StorageAccountAttributes storageAccountAttributes = null, IDictionary tags = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task> VerifyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] digest, byte[] signature, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - string digestAlgorithm; - switch (algorithm) - { - case SecurityAlgorithms.RsaSha256: - digestAlgorithm = SecurityAlgorithms.Sha256; - break; - case SecurityAlgorithms.RsaSha384: - digestAlgorithm = SecurityAlgorithms.Sha384; - break; - case SecurityAlgorithms.RsaSha512: - digestAlgorithm = SecurityAlgorithms.Sha512; - break; - default: - digestAlgorithm = null; - break; - } - - var result = string.IsNullOrEmpty(digestAlgorithm) ? null : (bool?)_rsa.VerifyHash(digest, digestAlgorithm, signature); - var response = new AzureOperationResponse - { - Body = new KeyVerifyResult(result), - }; - - return Task.FromResult(response); - } - - public Task> WrapKeyWithHttpMessagesAsync(string vaultBaseUrl, string keyName, string keyVersion, string algorithm, byte[] value, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) - { - bool fOAEP; - if (StringComparer.OrdinalIgnoreCase.Equals(algorithm, SecurityAlgorithms.RsaOAEP)) - fOAEP = true; - else if (StringComparer.OrdinalIgnoreCase.Equals(algorithm, SecurityAlgorithms.RsaPKCS1)) - fOAEP = false; - else - throw new NotImplementedException($"The mock key vault is not configured to wrap keys using the {algorithm} security algorithm."); - - var response = new AzureOperationResponse - { - Body = new KeyOperationResult(GetKeyIdentifier(vaultBaseUrl, keyName, keyVersion), _rsa.Encrypt(value, fOAEP)), - }; - - return Task.FromResult(response); - } - - private string GetKeyIdentifier(string vaultBaseUrl, string keyName, string keyVersion) - { - return new Uri(new Uri(vaultBaseUrl), $"/keys/{keyName}/{keyVersion}").ToString(); - } - } -} - diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index b6a9bad9cd..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Runtime.InteropServices; -using Xunit; - -[assembly: CLSCompliant(false)] -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -[assembly: ComVisible(false)] - diff --git a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/SignatureProviderTheoryData.cs b/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/SignatureProviderTheoryData.cs deleted file mode 100644 index 212a8656ed..0000000000 --- a/test/Microsoft.IdentityModel.KeyVaultExtensions.Tests/SignatureProviderTheoryData.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.IdentityModel.TestUtils; - -namespace Microsoft.IdentityModel.KeyVaultExtensions.Tests -{ - public class SignatureProviderTheoryData : TheoryDataBase - { - public string Algorithm { get; set; } - } -} diff --git a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/KeyVaultSecurityKeyTests.cs b/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/KeyVaultSecurityKeyTests.cs deleted file mode 100644 index d7c3d41dc6..0000000000 --- a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/KeyVaultSecurityKeyTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Azure.KeyVault.Models; -using Microsoft.IdentityModel.Clients.ActiveDirectory; -using Microsoft.IdentityModel.KeyVaultExtensions; -using Microsoft.IdentityModel.KeyVaultExtensions.Tests; -using Microsoft.IdentityModel.TestUtils; -using System; -using System.Reflection; -using Xunit; - -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - -namespace Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests -{ - public class KeyVaultSecurityKeyTests - { - private static ExpectedException AdalServiceExceptionExpected = new ExpectedException(typeExpected: typeof(TargetInvocationException), substringExpected: "Exception has been thrown by the target of an invocation.", innerTypeExpected: typeof(AdalServiceException)); - private static ExpectedException ArgumentNullExceptionExpected = new ExpectedException(typeExpected: typeof(TargetInvocationException), substringExpected: "Exception has been thrown by the target of an invocation.", innerTypeExpected: typeof(ArgumentNullException)); - private static ExpectedException KeyVaultErrorExceptionExpected = new ExpectedException(typeExpected: typeof(TargetInvocationException), substringExpected: "Exception has been thrown by the target of an invocation.", innerTypeExpected: typeof(KeyVaultErrorException)); - - [Theory, MemberData(nameof(KeyVaultSecurityKeyConfidentialClientTheoryData))] - public void ConfidentialClientConstructorParams(KeyVaultSecurityKeyConfidentialClientTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.ConfidentialClientConstructorParams", theoryData); - - try - { - _ = Activator.CreateInstance(theoryData.Type, new object[] { theoryData.KeyIdentifier, theoryData.ClientId, theoryData.ClientSecret }); - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData KeyVaultSecurityKeyConfidentialClientTheoryData - { - get => new TheoryData - { - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - // ClientId = default, - // ClientSecret = default, - ExpectedException = ArgumentNullExceptionExpected, - First = true, - KeyIdentifier = null, - TestId = "Test1", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - // ClientId = default, - // ClientSecret = default, - ExpectedException = ArgumentNullExceptionExpected, - KeyIdentifier = string.Empty, - TestId = "Test2", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - ClientId = null, - // ClientSecret = default, - ExpectedException = ArgumentNullExceptionExpected, - // KeyIdentifier = default, - TestId = "Test3", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - ClientId = string.Empty, - /* - ClientSecret = default, - */ - ExpectedException = ArgumentNullExceptionExpected, - // KeyIdentifier = default, - TestId = "Test4", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - // ClientId = default, - ClientSecret = null, - ExpectedException = ArgumentNullExceptionExpected, - // KeyIdentifier = default, - TestId = "Test5", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - // ClientId = default, - ClientSecret = string.Empty, - ExpectedException = ArgumentNullExceptionExpected, - // KeyIdentifier = default, - TestId = "Test6", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyConfidentialClientTheoryData - { - // ClientId = default, - // ClientSecret = default, - // KeyIdentifier = default, - TestId = "Test7", - Type = typeof(ManagedKeyVaultSecurityKey), - } - }; - } - - [Theory, MemberData(nameof(KeyVaultSecurityKeyManagedServiceIdentityTheoryData))] - public void ManagedServiceIdentityConstructorParams(KeyVaultSecurityKeyTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.ManagedServiceIdentityConstructorParams", theoryData); - - try - { - _ = Activator.CreateInstance(theoryData.Type, new object[] { theoryData.KeyIdentifier }); - theoryData.ExpectedException.ProcessNoException(context); - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - } - - public static TheoryData KeyVaultSecurityKeyManagedServiceIdentityTheoryData - { - get => new TheoryData - { - new KeyVaultSecurityKeyManagedServiceIdentityTheoryData - { - ExpectedException = ExpectedException.ArgumentNullException(), - First = true, - KeyIdentifier = null, - TestId = "Test1", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyManagedServiceIdentityTheoryData - { - ExpectedException = ExpectedException.ArgumentNullException(), - KeyIdentifier = string.Empty, - TestId = "Test2", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - new KeyVaultSecurityKeyManagedServiceIdentityTheoryData - { - ExpectedException = KeyVaultErrorExceptionExpected, - // KeyIdentifier = default, - TestId = "Test3", - Type = typeof(ManagedKeyVaultSecurityKey), - }, - }; - } - } -} - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests.csproj b/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests.csproj deleted file mode 100644 index d372f245e9..0000000000 --- a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests - $(MSBuildThisFileDirectory)..\..\build\35MSSharedLib1024.snk - true - Tests for Microsoft.IdentityModel.ManagedKeyVaultSecurityKey - true - Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests - true - - - - - - - - - - - - - diff --git a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index b6a9bad9cd..0000000000 --- a/test/Microsoft.IdentityModel.ManagedKeyVaultSecurityKey.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Runtime.InteropServices; -using Xunit; - -[assembly: CLSCompliant(false)] -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -[assembly: ComVisible(false)] - diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConfigData.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConfigData.cs index 56495e2331..1f60d43365 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConfigData.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConfigData.cs @@ -42,123 +42,128 @@ public static OpenIdConnectConfiguration FullyPopulatedWithKeys public static string HttpsBadUri = "https://_____NoSuchfile____"; #region Configuration Strings - public static string OpenIdConnectMetadataPingString = @"{""authorization_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/as\/authorization.oauth2"", - ""issuer"":""https:\/\/connect-interop.pinglabs.org:9031"", - ""id_token_signing_alg_values_supported"":[""none"",""HS256"",""HS384"",""HS512"",""RS256"",""RS384"",""RS512"",""ES256"",""ES384"",""ES512""], - ""claim_types_supported"":[""normal""], - ""claims_parameter_supported"":false, - ""ping_end_session_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/idp\/startSLO.ping"", - ""ping_revoked_sris_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/pf-ws\/rest\/sessionMgmt\/revokedSris"", - ""request_parameter_supported"":false, - ""request_uri_parameter_supported"":false, - ""response_modes_supported"":[""fragment"",""query"",""form_post""], - ""response_types_supported"":[""code"",""token"",""id_token"",""code token"",""code id_token"",""token id_token"",""code token id_token""], - ""revocation_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/as\/revoke_token.oauth2"", - ""scopes_supported"":[""phone"",""address"",""email"",""openid"",""profile""], - ""subject_types_supported"":[""public""], - ""token_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/as\/token.oauth2"", - ""token_endpoint_auth_methods_supported"":[""client_secret_basic"",""client_secret_post""], - ""userinfo_endpoint"":""https:\/\/connect-interop.pinglabs.org:9031\/idp\/userinfo.openid"", - ""version"":""3.0""}"; + public static string OpenIdConnectMetadataPingString = @"{""authorization_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/as\/authorization.oauth2"", + ""issuer"": ""https:\/\/connect-interop.pinglabs.org:9031"", + ""id_token_signing_alg_values_supported"": [""none"", ""HS256"", ""HS384"", ""HS512"", ""RS256"", ""RS384"", ""RS512"", ""ES256"", ""ES384"", ""ES512""], + ""claim_types_supported"": [""normal""], + ""claims_parameter_supported"": false, + ""ping_end_session_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/idp\/startSLO.ping"", + ""ping_revoked_sris_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/pf-ws\/rest\/sessionMgmt\/revokedSris"", + ""request_parameter_supported"": false, + ""request_uri_parameter_supported"": false, + ""response_modes_supported"": [""fragment"", ""query"", ""form_post""], + ""response_types_supported"": [""code"", ""token"", ""id_token"", ""code token"", ""code id_token"", ""token id_token"", ""code token id_token""], + ""revocation_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/as\/revoke_token.oauth2"", + ""scopes_supported"": [""phone"", ""address"", ""email"", ""openid"", ""profile""], + ""subject_types_supported"": [""public""], + ""token_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/as\/token.oauth2"", + ""token_endpoint_auth_methods_supported"": [""client_secret_basic"", ""client_secret_post""], + ""userinfo_endpoint"": ""https:\/\/connect-interop.pinglabs.org:9031\/idp\/userinfo.openid"", + ""version"": ""3.0""}"; public static string JsonFile = @"OpenIdConnectMetadata.json"; public static string OpenIdConnectMetadataFileEnd2End = @"OpenIdConnectMetadataEnd2End.json"; public static string OpenIdConnectMetadataFileEnd2EndEC = @"OpenIdConnectMetadataEnd2EndEC.json"; public static string JsonWebKeySetBadUriFile = @"OpenIdConnectMetadataJsonWebKeySetBadUri.json"; public static string JsonAllValues = - @"{ ""acr_values_supported"" : [""acr_value1"", ""acr_value2"", ""acr_value3""], - ""authorization_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", - ""authorization_response_iss_parameter_supported"" : false, - ""backchannel_authentication_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/bc-authorize"", - ""backchannel_authentication_request_signing_alg_values_supported"" : [""ES384"", ""ES512""], - ""backchannel_token_delivery_modes_supported"" : [""poll"", ""ping""], - ""backchannel_user_code_parameter_supported"" : false, + @"{ ""acr_values_supported"": [""acr_value1"", ""acr_value2"", ""acr_value3""], + ""authorization_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", + ""authorization_response_iss_parameter_supported"": false, + ""backchannel_authentication_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/bc-authorize"", + ""backchannel_authentication_request_signing_alg_values_supported"": [""ES384"", ""ES512""], + ""backchannel_token_delivery_modes_supported"": [""poll"", ""ping""], + ""backchannel_user_code_parameter_supported"": false, + ""code_challenge_methods_supported"": [""plain"", ""S256""], + ""device_authorization_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/devicecode"", ""frontchannel_logout_session_supported"": ""true"", ""frontchannel_logout_supported"": ""true"", - ""check_session_iframe"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", - ""claims_locales_supported"" : [ ""claim_local1"", ""claim_local2"", ""claim_local3"" ], - ""claims_parameter_supported"" : true, - ""claims_supported"": [ ""sub"", ""iss"", ""aud"", ""exp"", ""iat"", ""auth_time"", ""acr"", ""amr"", ""nonce"", ""email"", ""given_name"", ""family_name"", ""nickname"" ], - ""claim_types_supported"" : [ ""Normal Claims"", ""Aggregated Claims"", ""Distributed Claims"" ], - ""display_values_supported"" : [ ""displayValue1"", ""displayValue2"", ""displayValue3"" ], - ""dpop_signing_alg_values_supported"" : [""ES384"", ""ES512""], - ""end_session_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", - ""grant_types_supported"" : [""authorization_code"",""implicit""], - ""http_logout_supported"" : true, - ""id_token_encryption_alg_values_supported"" : [""RSA1_5"", ""A256KW""], - ""id_token_encryption_enc_values_supported"" : [""A128CBC-HS256"",""A256CBC-HS512""], - ""id_token_signing_alg_values_supported"" : [""RS256""], - ""introspection_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/introspect"", - ""introspection_endpoint_auth_methods_supported"" : [""client_secret_post"",""private_key_jwt""], - ""introspection_endpoint_auth_signing_alg_values_supported"" : [""ES192"", ""ES256""], - ""issuer"" : ""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", - ""jwks_uri"" : ""JsonWebKeySet.json"", - ""logout_session_supported"" : true, - ""microsoft_multi_refresh_token"" : true, - ""op_policy_uri"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/op_policy_uri"", - ""op_tos_uri"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/op_tos_uri"", - ""prompt_values_supported"" : [""none"", ""login"", ""consent""], - ""pushed_authorization_request_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/par"", - ""request_object_encryption_alg_values_supported"" : [""A192KW"", ""A256KW""], - ""request_object_encryption_enc_values_supported"" : [""A192GCM"",""A256GCM""], - ""request_object_signing_alg_values_supported"" : [""PS256"", ""PS512""], - ""request_parameter_supported"" : true, - ""request_uri_parameter_supported"" : true, - ""require_pushed_authorization_requests"" : false, - ""require_request_uri_registration"" : true, - ""response_modes_supported"" : [""query"", ""fragment"",""form_post""], - ""response_types_supported"" : [""code"",""id_token"",""code id_token""], - ""service_documentation"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/service_documentation"", - ""scopes_supported"" : [""openid""], - ""subject_types_supported"" : [""pairwise""], - ""token_endpoint"" : ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", - ""token_endpoint_auth_methods_supported"" : [""client_secret_post"",""private_key_jwt""], - ""token_endpoint_auth_signing_alg_values_supported"" : [""ES192"", ""ES256""], - ""ui_locales_supported"" : [""hak-CN"", ""en-us""], - ""userinfo_endpoint"" : ""https://login.microsoftonline.com/add29489-7269-41f4-8841-b63c95564420/openid/userinfo"", - ""userinfo_encryption_alg_values_supported"" : [""ECDH-ES+A128KW"",""ECDH-ES+A192KW""], - ""userinfo_encryption_enc_values_supported"" : [""A256CBC-HS512"", ""A128CBC-HS256""], - ""userinfo_signing_alg_values_supported"" : [""ES384"", ""ES512""] + ""check_session_iframe"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", + ""claims_locales_supported"": [""claim_local1"", ""claim_local2"", ""claim_local3"" ], + ""claims_parameter_supported"": true, + ""claims_supported"": [""sub"", ""iss"", ""aud"", ""exp"", ""iat"", ""auth_time"", ""acr"", ""amr"", ""nonce"", ""email"", ""given_name"", ""family_name"", ""nickname"" ], + ""claim_types_supported"": [""Normal Claims"", ""Aggregated Claims"", ""Distributed Claims"" ], + ""display_values_supported"": [""displayValue1"", ""displayValue2"", ""displayValue3"" ], + ""dpop_signing_alg_values_supported"": [""ES384"", ""ES512""], + ""end_session_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", + ""grant_types_supported"": [""authorization_code"", ""implicit""], + ""http_logout_supported"": true, + ""id_token_encryption_alg_values_supported"": [""RSA1_5"", ""A256KW""], + ""id_token_encryption_enc_values_supported"": [""A128CBC-HS256"", ""A256CBC-HS512""], + ""id_token_signing_alg_values_supported"": [""RS256""], + ""introspection_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/introspect"", + ""introspection_endpoint_auth_methods_supported"": [""client_secret_post"", ""private_key_jwt""], + ""introspection_endpoint_auth_signing_alg_values_supported"": [""ES192"", ""ES256""], + ""issuer"": ""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", + ""jwks_uri"": ""JsonWebKeySet.json"", + ""logout_session_supported"": true, + ""microsoft_multi_refresh_token"": true, + ""op_policy_uri"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/op_policy_uri"", + ""op_tos_uri"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/op_tos_uri"", + ""prompt_values_supported"": [""none"", ""login"", ""consent""], + ""pushed_authorization_request_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/par"", + ""request_object_encryption_alg_values_supported"": [""A192KW"", ""A256KW""], + ""request_object_encryption_enc_values_supported"": [""A192GCM"", ""A256GCM""], + ""request_object_signing_alg_values_supported"": [""PS256"", ""PS512""], + ""request_parameter_supported"": true, + ""request_uri_parameter_supported"": true, + ""require_pushed_authorization_requests"": false, + ""require_request_uri_registration"": true, + ""response_modes_supported"": [""query"", ""fragment"", ""form_post""], + ""response_types_supported"": [""code"", ""id_token"", ""code id_token""], + ""revocation_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/revocation"", + ""revocation_endpoint_auth_methods_supported"": [""client_secret_post"", ""client_secret_basic""], + ""revocation_endpoint_auth_signing_alg_values_supported"": [""ES192"", ""ES256""], + ""service_documentation"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/service_documentation"", + ""scopes_supported"": [""openid""], + ""subject_types_supported"": [""pairwise""], + ""token_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", + ""token_endpoint_auth_methods_supported"": [""client_secret_post"", ""private_key_jwt""], + ""token_endpoint_auth_signing_alg_values_supported"": [""ES192"", ""ES256""], + ""ui_locales_supported"": [""hak-CN"", ""en-us""], + ""userinfo_endpoint"": ""https://login.microsoftonline.com/add29489-7269-41f4-8841-b63c95564420/openid/userinfo"", + ""userinfo_encryption_alg_values_supported"": [""ECDH-ES+A128KW"", ""ECDH-ES+A192KW""], + ""userinfo_encryption_enc_values_supported"": [""A256CBC-HS512"", ""A128CBC-HS256""], + ""userinfo_signing_alg_values_supported"": [""ES384"", ""ES512""] }"; public static string OpenIdConnectMetadataSingleX509DataString = - @"{ ""authorization_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", - ""check_session_iframe"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", - ""end_session_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", - ""id_token_signing_alg_values_supported"":[""RS256""], - ""issuer"":""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", - ""jwks_uri"":""JsonWebKeySetSingleX509Data.json"", + @"{ ""authorization_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", + ""check_session_iframe"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", + ""end_session_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", + ""id_token_signing_alg_values_supported"": [""RS256""], + ""issuer"": ""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", + ""jwks_uri"": ""JsonWebKeySetSingleX509Data.json"", ""microsoft_multi_refresh_token"":true, - ""response_types_supported"":[""code"",""id_token"",""code id_token""], - ""response_modes_supported"":[""query"",""fragment"",""form_post""], - ""scopes_supported"":[""openid""], - ""subject_types_supported"":[""pairwise""], - ""token_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", - ""token_endpoint_auth_methods_supported"":[""client_secret_post"",""private_key_jwt""] + ""response_types_supported"": [""code"", ""id_token"", ""code id_token""], + ""response_modes_supported"": [""query"", ""fragment"", ""form_post""], + ""scopes_supported"": [""openid""], + ""subject_types_supported"": [""pairwise""], + ""token_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", + ""token_endpoint_auth_methods_supported"": [""client_secret_post"", ""private_key_jwt""] }"; public static string JsonWithSigningKeys = - @"{ ""authorization_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", - ""check_session_iframe"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", - ""end_session_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", - ""id_token_signing_alg_values_supported"":[""RS256""], - ""issuer"":""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", - ""jwks_uri"":""JsonWebKeySetSingleX509Data.json"", + @"{ ""authorization_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/authorize"", + ""check_session_iframe"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession"", + ""end_session_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"", + ""id_token_signing_alg_values_supported"": [""RS256""], + ""issuer"": ""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/"", + ""jwks_uri"": ""JsonWebKeySetSingleX509Data.json"", ""microsoft_multi_refresh_token"":true, - ""response_types_supported"":[""code"",""id_token"",""code id_token""], - ""response_modes_supported"":[""query"",""fragment"",""form_post""], - ""scopes_supported"":[""openid""], - ""subject_types_supported"":[""pairwise""], - ""token_endpoint"":""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", - ""token_endpoint_auth_methods_supported"":[""client_secret_post"",""private_key_jwt""], - ""SigningKeys"":[""key1"",""key2""] + ""response_types_supported"": [""code"", ""id_token"", ""code id_token""], + ""response_modes_supported"": [""query"", ""fragment"", ""form_post""], + ""scopes_supported"": [""openid""], + ""subject_types_supported"": [""pairwise""], + ""token_endpoint"": ""https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"", + ""token_endpoint_auth_methods_supported"": [""client_secret_post"", ""private_key_jwt""], + ""SigningKeys"": [""key1"", ""key2""] }"; - public static string OpenIdConnectMetadataBadX509DataString = @"{""jwks_uri"":""JsonWebKeySetBadX509Data.json""}"; - public static string OpenIdConnectMetadataBadBase64DataString = @"{""jwks_uri"":""JsonWebKeySetBadBase64Data.json""}"; - public static string OpenIdConnectMetadataBadUriKeysString = @"{""jwks_uri"":""___NoSuchFile___""}"; + public static string OpenIdConnectMetadataBadX509DataString = @"{""jwks_uri"": ""JsonWebKeySetBadX509Data.json""}"; + public static string OpenIdConnectMetadataBadBase64DataString = @"{""jwks_uri"": ""JsonWebKeySetBadBase64Data.json""}"; + public static string OpenIdConnectMetadataBadUriKeysString = @"{""jwks_uri"": ""___NoSuchFile___""}"; public static string OpenIdConnectMetadataBadFormatString = @"{""issuer""::""https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/""}"; - public static string OpenIdConnectMetadataPingLabsJWKSString = @"{""jwks_uri"":""PingLabsJWKS.json""}"; + public static string OpenIdConnectMetadataPingLabsJWKSString = @"{""jwks_uri"": ""PingLabsJWKS.json""}"; public static string OpenIdConnectMetatadataBadJson = @"{..."; #endregion @@ -191,14 +196,14 @@ public static OpenIdConnectConfiguration FullyPopulatedWithKeys "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo", "revocation_endpoint": "https://oauth2.googleapis.com/revoke", "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", - "response_types_supported": ["code","id_token","code id_token"], + "response_types_supported": ["code", "id_token", "code id_token"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], - "scopes_supported": ["openid","email","profile"], - "token_endpoint_auth_methods_supported": ["client_secret_post","client_secret_basic"], - "claims_supported": ["aud","email","email_verified","exp","family_name","given_name","iat","iss","locale","name","picture","sub"], - "code_challenge_methods_supported": ["plain","S256"], - "grant_types_supported": ["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:device_code","urn:ietf:params:oauth:grant-type:jwt-bearer"] + "scopes_supported": ["openid", "email", "profile"], + "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"], + "claims_supported": ["aud", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "locale", "name", "picture", "sub"], + "code_challenge_methods_supported": ["plain", "S256"], + "grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer"] } """; public static OpenIdConnectConfiguration AccountsGoogleComConfig @@ -209,12 +214,15 @@ public static OpenIdConnectConfiguration AccountsGoogleComConfig OpenIdConnectConfiguration config = new OpenIdConnectConfiguration { AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth", + DeviceAuthorizationEndpoint = "https://oauth2.googleapis.com/device/code", Issuer = "https://accounts.google.com", JwksUri = "https://www.googleapis.com/oauth2/v3/certs", + RevocationEndpoint = "https://oauth2.googleapis.com/revoke", TokenEndpoint = "https://oauth2.googleapis.com/token", UserInfoEndpoint = "https://openidconnect.googleapis.com/v1/userinfo", }; + AddToCollection(config.CodeChallengeMethodsSupported, "plain", "S256"); AddToCollection(config.ResponseTypesSupported, "code", "id_token", "code id_token"); config.SubjectTypesSupported.Add("public"); config.IdTokenSigningAlgValuesSupported.Add("RS256"); @@ -223,27 +231,22 @@ public static OpenIdConnectConfiguration AccountsGoogleComConfig AddToCollection(config.ClaimsSupported, "aud", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "locale", "name", "picture", "sub"); AddToCollection(config.GrantTypesSupported, "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer"); - // Adjust if Google changes their config or https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2456 is implemented. - config.AdditionalData.Add("device_authorization_endpoint", "https://oauth2.googleapis.com/device/code"); - config.AdditionalData.Add("code_challenge_methods_supported", JsonUtilities.CreateJsonElement(""" ["plain","S256"] """)); - config.AdditionalData.Add("revocation_endpoint", "https://oauth2.googleapis.com/revoke"); - return config; } } #endregion - #region AADCommonV1 2/2/2024 https://login.microsoftonline.com/common/.well-known/openid-configuration + #region AADCommonV1 2/2/2024 https://login.microsoftonline.com/common/.well-known/openid-configuration public static string AADCommonV1Json => """ { "token_endpoint": "https://login.microsoftonline.com/common/oauth2/token", - "token_endpoint_auth_methods_supported": ["client_secret_post","private_key_jwt","client_secret_basic"], + "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], "jwks_uri": "https://login.microsoftonline.com/common/discovery/keys", - "response_modes_supported": ["query","fragment","form_post"], + "response_modes_supported": ["query", "fragment", "form_post"], "subject_types_supported": ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"], - "response_types_supported": ["code","id_token","code id_token","token id_token","token"], + "response_types_supported": ["code", "id_token", "code id_token", "token id_token", "token"], "scopes_supported": ["openid"], "issuer": "https://sts.windows.net/{tenantid}/", "microsoft_multi_refresh_token": true, @@ -252,7 +255,7 @@ public static OpenIdConnectConfiguration AccountsGoogleComConfig "http_logout_supported": true, "frontchannel_logout_supported": true, "end_session_endpoint": "https://login.microsoftonline.com/common/oauth2/logout", - "claims_supported": ["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","amr","nonce","email","given_name","family_name","nickname"], + "claims_supported": ["sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname"], "check_session_iframe": "https://login.microsoftonline.com/common/oauth2/checksession", "userinfo_endpoint": "https://login.microsoftonline.com/common/openid/userinfo", "kerberos_endpoint": "https://login.microsoftonline.com/common/kerberos", @@ -271,6 +274,7 @@ public static OpenIdConnectConfiguration AADCommonV1Config OpenIdConnectConfiguration config = new OpenIdConnectConfiguration { AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/authorize", + DeviceAuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/devicecode", CheckSessionIframe = "https://login.microsoftonline.com/common/oauth2/checksession", HttpLogoutSupported = true, Issuer = "https://sts.windows.net/{tenantid}/", @@ -289,7 +293,6 @@ public static OpenIdConnectConfiguration AADCommonV1Config AddToCollection(config.TokenEndpointAuthMethodsSupported, "client_secret_post", "private_key_jwt", "client_secret_basic"); AddToCollection(config.ClaimsSupported, "sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname"); config.AdditionalData.Add("microsoft_multi_refresh_token", true); - config.AdditionalData.Add("device_authorization_endpoint", "https://login.microsoftonline.com/common/oauth2/devicecode"); config.AdditionalData.Add("kerberos_endpoint", "https://login.microsoftonline.com/common/kerberos"); config.AdditionalData.Add("tenant_region_scope", null); config.AdditionalData.Add("cloud_instance_name", "microsoftonline.com"); @@ -307,13 +310,13 @@ public static OpenIdConnectConfiguration AADCommonV1Config """ { "token_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token", - "token_endpoint_auth_methods_supported": ["client_secret_post","private_key_jwt","client_secret_basic"], + "token_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt", "client_secret_basic"], "jwks_uri": "https://login.microsoftonline.com/common/discovery/v2.0/keys", - "response_modes_supported": ["query","fragment","form_post"], + "response_modes_supported": ["query", "fragment", "form_post"], "subject_types_supported": ["pairwise"], "id_token_signing_alg_values_supported": ["RS256"], - "response_types_supported": ["code","id_token","code id_token","id_token token"], - "scopes_supported": ["openid","profile","email","offline_access"], + "response_types_supported": ["code", "id_token", "code id_token", "id_token token"], + "scopes_supported": ["openid", "profile", "email", "offline_access"], "issuer": "https://login.microsoftonline.com/{tenantid}/v2.0", "request_uri_parameter_supported": false, "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", @@ -322,7 +325,7 @@ public static OpenIdConnectConfiguration AADCommonV1Config "http_logout_supported": true, "frontchannel_logout_supported": true, "end_session_endpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/logout", - "claims_supported": ["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"], + "claims_supported": ["sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "nonce", "preferred_username", "name", "tid", "ver", "at_hash", "c_hash", "email"], "kerberos_endpoint": "https://login.microsoftonline.com/common/kerberos", "tenant_region_scope": null, "cloud_instance_name": "microsoftonline.com", @@ -339,6 +342,7 @@ public static OpenIdConnectConfiguration AADCommonV2Config OpenIdConnectConfiguration config = new OpenIdConnectConfiguration { AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + DeviceAuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode", HttpLogoutSupported = true, Issuer = "https://login.microsoftonline.com/{tenantid}/v2.0", JwksUri = "https://login.microsoftonline.com/common/discovery/v2.0/keys", @@ -355,7 +359,6 @@ public static OpenIdConnectConfiguration AADCommonV2Config AddToCollection(config.ScopesSupported, "openid", "profile", "email", "offline_access"); AddToCollection(config.TokenEndpointAuthMethodsSupported, "client_secret_post", "private_key_jwt", "client_secret_basic"); AddToCollection(config.ClaimsSupported, "sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "nonce", "preferred_username", "name", "tid", "ver", "at_hash", "c_hash", "email"); - config.AdditionalData.Add("device_authorization_endpoint", "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode"); config.AdditionalData.Add("kerberos_endpoint", "https://login.microsoftonline.com/common/kerberos"); config.AdditionalData.Add("tenant_region_scope", null); config.AdditionalData.Add("cloud_instance_name", "microsoftonline.com"); @@ -617,6 +620,8 @@ private static OpenIdConnectConfiguration SetDefaultConfiguration(OpenIdConnectC config.ClaimsParameterSupported = true; AddToCollection(config.ClaimsSupported, "sub", "iss", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname"); AddToCollection(config.ClaimTypesSupported, "Normal Claims", "Aggregated Claims", "Distributed Claims"); + AddToCollection(config.CodeChallengeMethodsSupported, "plain", "S256"); + config.DeviceAuthorizationEndpoint = "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/devicecode"; AddToCollection(config.DisplayValuesSupported, "displayValue1", "displayValue2", "displayValue3"); AddToCollection(config.DPoPSigningAlgValuesSupported, "ES384", "ES512"); config.EndSessionEndpoint = "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout"; @@ -646,8 +651,11 @@ private static OpenIdConnectConfiguration SetDefaultConfiguration(OpenIdConnectC config.RequireRequestUriRegistration = true; AddToCollection(config.ResponseModesSupported, "query", "fragment", "form_post"); AddToCollection(config.ResponseTypesSupported, "code", "id_token", "code id_token"); - config.ScopesSupported.Add("openid"); + config.RevocationEndpoint = "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/revocation"; + AddToCollection(config.RevocationEndpointAuthMethodsSupported, "client_secret_post", "client_secret_basic"); + AddToCollection(config.RevocationEndpointAuthSigningAlgValuesSupported, "ES192", "ES256"); config.ServiceDocumentation = "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/service_documentation"; + config.ScopesSupported.Add("openid"); config.SubjectTypesSupported.Add("pairwise"); config.TokenEndpoint = "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/token"; AddToCollection(config.TokenEndpointAuthMethodsSupported, "client_secret_post", "private_key_jwt"); diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs index 8baedf49a2..9ee0bb7cdb 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs @@ -83,6 +83,7 @@ public void Defaults() Assert.NotNull(configuration.ClaimsLocalesSupported); Assert.False(configuration.ClaimsParameterSupported); Assert.NotNull(configuration.ClaimTypesSupported); + Assert.NotNull(configuration.CodeChallengeMethodsSupported); Assert.NotNull(configuration.DisplayValuesSupported); Assert.NotNull(configuration.DPoPSigningAlgValuesSupported); Assert.NotNull(configuration.GrantTypesSupported); @@ -102,6 +103,8 @@ public void Defaults() Assert.False(configuration.RequireRequestUriRegistration); Assert.NotNull(configuration.ResponseModesSupported); Assert.NotNull(configuration.ResponseTypesSupported); + Assert.NotNull(configuration.RevocationEndpointAuthMethodsSupported); + Assert.NotNull(configuration.RevocationEndpointAuthSigningAlgValuesSupported); Assert.NotNull(configuration.ScopesSupported); Assert.NotNull(configuration.SigningKeys); Assert.NotNull(configuration.SubjectTypesSupported); @@ -138,8 +141,8 @@ public void GetSets() OpenIdConnectConfiguration configuration = new OpenIdConnectConfiguration(); Type type = typeof(OpenIdConnectConfiguration); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 58) - Assert.True(false, "Number of properties has changed from 58 to: " + properties.Length + ", adjust tests"); + if (properties.Length != 63) + Assert.True(false, "Number of properties has changed from 63 to: " + properties.Length + ", adjust tests"); TestUtilities.CallAllPublicInstanceAndStaticPropertyGets(configuration, "OpenIdConnectConfiguration_GetSets"); @@ -154,6 +157,8 @@ public void GetSets() new KeyValuePair>("BackchannelUserCodeParameterSupported", new List{ false, true, true }), new KeyValuePair>("CheckSessionIframe", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), new KeyValuePair>("ClaimsParameterSupported", new List{ false, true, false }), + new KeyValuePair>("CodeChallengeMethodsSupported", new List{ false, true, true }), + new KeyValuePair>("DeviceAuthorizationEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), new KeyValuePair>("EndSessionEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), new KeyValuePair>("HttpLogoutSupported", new List{ false, true, true }), new KeyValuePair>("IntrospectionEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), @@ -169,6 +174,9 @@ public void GetSets() new KeyValuePair>("RequestUriParameterSupported", new List{ false, true, true }), new KeyValuePair>("RequirePushedAuthorizationRequests", new List{ false, true, true }), new KeyValuePair>("RequireRequestUriRegistration", new List{ false, true, true }), + new KeyValuePair>("RevocationEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), + new KeyValuePair>("RevocationEndpointAuthMethodsSupported", new List{ false, true, true }), + new KeyValuePair>("RevocationEndpointAuthSigningAlgValuesSupported", new List{ false, true, true }), new KeyValuePair>("ServiceDocumentation", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), new KeyValuePair>("TokenEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), new KeyValuePair>("UserInfoEndpoint", new List{ (string)null, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }), @@ -286,6 +294,8 @@ public void NonemptyCollectionSerialization() "claims_supported", "claims_locales_supported", "claim_types_supported", + "code_challenge_methods_supported", + "device_authorization_endpoint", "display_values_supported", "dpop_signing_alg_values_supported", "grant_types_supported", @@ -300,6 +310,9 @@ public void NonemptyCollectionSerialization() "request_object_signing_alg_values_supported", "response_modes_supported", "response_types_supported", + "revocation_endpoint", + "revocation_endpoint_auth_methods_supported", + "revocation_endpoint_auth_signing_alg_values_supported", "scopes_supported", "subject_types_supported", "token_endpoint_auth_methods_supported", diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMetadata.json b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMetadata.json index e71ed8aa53..bf10bb1f2c 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMetadata.json +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectMetadata.json @@ -7,22 +7,24 @@ "backchannel_token_delivery_modes_supported": ["poll", "ping"], "backchannel_user_code_parameter_supported": false, "dpop_signing_alg_values_supported": ["ES384", "ES512"], - "frontchannel_logout_session_supported": "true", - "frontchannel_logout_supported": "true", + "frontchannel_logout_session_supported": "true", + "frontchannel_logout_supported": "true", "check_session_iframe": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/checksession", "claims_locales_supported": ["claim_local1", "claim_local2", "claim_local3"], "claims_parameter_supported": true, "claims_supported": ["sub", "iss", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname"], "claim_types_supported": ["Normal Claims", "Aggregated Claims", "Distributed Claims"], + "code_challenge_methods_supported": ["plain", "S256"], + "device_authorization_endpoint": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/devicecode", "display_values_supported": ["displayValue1", "displayValue2", "displayValue3"], "end_session_endpoint": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/logout", - "grant_types_supported": ["authorization_code","implicit"], + "grant_types_supported": ["authorization_code", "implicit"], "http_logout_supported": true, "id_token_encryption_alg_values_supported": ["RSA1_5", "A256KW"], - "id_token_encryption_enc_values_supported": ["A128CBC-HS256","A256CBC-HS512"], + "id_token_encryption_enc_values_supported": ["A128CBC-HS256", "A256CBC-HS512"], "id_token_signing_alg_values_supported": ["RS256"], "introspection_endpoint": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/introspect", - "introspection_endpoint_auth_methods_supported": ["client_secret_post","private_key_jwt"], + "introspection_endpoint_auth_methods_supported": ["client_secret_post", "private_key_jwt"], "introspection_endpoint_auth_signing_alg_values_supported": ["ES192", "ES256"], "issuer": "https://sts.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/", "jwks_uri": "JsonWebKeySet.json", @@ -33,7 +35,7 @@ "prompt_values_supported": ["none", "login", "consent"], "pushed_authorization_request_endpoint": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/par", "request_object_encryption_alg_values_supported": ["A192KW", "A256KW"], - "request_object_encryption_enc_values_supported": ["A192GCM","A256GCM"], + "request_object_encryption_enc_values_supported": ["A192GCM", "A256GCM"], "request_object_signing_alg_values_supported": ["PS256", "PS512"], "request_parameter_supported": true, "request_uri_parameter_supported": true, @@ -41,6 +43,9 @@ "require_request_uri_registration": true, "response_types_supported": ["code", "id_token", "code id_token"], "response_modes_supported": ["query", "fragment", "form_post"], + "revocation_endpoint": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/oauth2/revocation", + "revocation_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"], + "revocation_endpoint_auth_signing_alg_values_supported": ["ES192", "ES256"], "service_documentation": "https://login.windows.net/d062b2b0-9aca-4ff7-b32a-ba47231a4002/service_documentation", "scopes_supported": ["openid"], "subject_types_supported": ["pairwise"], @@ -49,7 +54,7 @@ "token_endpoint_auth_signing_alg_values_supported": ["ES192", "ES256"], "ui_locales_supported": ["hak-CN", "en-us"], "userinfo_endpoint": "https://login.microsoftonline.com/add29489-7269-41f4-8841-b63c95564420/openid/userinfo", - "userinfo_encryption_alg_values_supported": ["ECDH-ES+A128KW","ECDH-ES+A192KW"], + "userinfo_encryption_alg_values_supported": ["ECDH-ES+A128KW", "ECDH-ES+A192KW"], "userinfo_encryption_enc_values_supported": ["A256CBC-HS512", "A128CBC-HS256"], "userinfo_signing_alg_values_supported": ["ES384", "ES512"] -} \ No newline at end of file +} diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectSerializationTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectSerializationTests.cs index 9d28137c02..22454614f5 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectSerializationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectSerializationTests.cs @@ -57,8 +57,8 @@ public static TheoryData DesrializeTheoryData theoryData.Add(new OpenIdConnectTheoryData("AccountsGoogleCom") { - CompareTo = JsonUtilities.SetAdditionalDataKeysToUpperCase(OpenIdConfigData.AccountsGoogleComConfig), - Json = JsonUtilities.SetAdditionalDataKeysToUpperCase(OpenIdConfigData.AccountsGoogleComJson, OpenIdConfigData.AccountsGoogleComConfig) + CompareTo = OpenIdConfigData.AccountsGoogleComConfig, + Json = OpenIdConfigData.AccountsGoogleComJson }); theoryData.Add(new OpenIdConnectTheoryData("FrontChannelFalse") diff --git a/test/Microsoft.IdentityModel.TestUtils/DerivedTypes.cs b/test/Microsoft.IdentityModel.TestUtils/DerivedTypes.cs index f4580dba31..1590981f36 100644 --- a/test/Microsoft.IdentityModel.TestUtils/DerivedTypes.cs +++ b/test/Microsoft.IdentityModel.TestUtils/DerivedTypes.cs @@ -303,7 +303,7 @@ public DerivedSecurityKey(string keyId, int keySize) _keySize = keySize; } - internal override string InternalId { get =>_keyId; } + internal override string InternalId { get => _keyId; } public Exception ThrowOnGetKeyId { get; set; } diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs index 753b97efad..872a9e8cea 100644 --- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs +++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs @@ -19,6 +19,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.Json; +using System.Xml.Linq; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; @@ -60,6 +61,7 @@ public class IdentityComparer { typeof(IEnumerable).ToString(), AreX509DataEnumsEqual }, { typeof(int).ToString(), AreIntsEqual }, { typeof(IssuerSerial).ToString(), CompareAllPublicProperties }, + { typeof(IssuerValidationResult).ToString(), AreIssuerValidationResultsEqual }, { typeof(JArray).ToString(), AreJArraysEqual }, { typeof(JObject).ToString(), AreJObjectsEqual }, { typeof(JsonElement).ToString(), AreJsonElementsEqual }, @@ -542,6 +544,66 @@ public static bool AreEqual(object object1, object object2, CompareContext conte return context.Merge(localContext); } + public static bool AreIssuerValidationResultsEqual(object object1, object object2, CompareContext context) + { + var localContext = new CompareContext(context); + if (!ContinueCheckingEquality(object1, object2, context)) + return context.Merge(localContext); + + return AreIssuerValidationResultsEqual( + object1 as IssuerValidationResult, + object2 as IssuerValidationResult, + "IssuerValidationResult1", + "IssuerValidationResult2", + null, + context); + } + + internal static bool AreIssuerValidationResultsEqual( + IssuerValidationResult issuerValidationResult1, + IssuerValidationResult issuerValidationResult2, + string name1, + string name2, + string stackPrefix, + CompareContext context) + { + var localContext = new CompareContext(context); + if (!ContinueCheckingEquality(issuerValidationResult1, issuerValidationResult2, localContext)) + return context.Merge(localContext); + + if (issuerValidationResult1.Issuer != issuerValidationResult2.Issuer) + localContext.Diffs.Add($"IssuerValidationResult1.Issuer: {issuerValidationResult1.Issuer} != IssuerValidationResult2.Issuer: {issuerValidationResult2.Issuer}"); + + // true => both are not null. + if (ContinueCheckingEquality(issuerValidationResult1.Exception, issuerValidationResult2.Exception, localContext)) + { + AreStringsEqual( + issuerValidationResult1.Exception.Message, + issuerValidationResult2.Exception.Message, + $"({name1})issuerValidationResult1.Exception.Message", + $"({name2})issuerValidationResult1.Exception.Message", + localContext); + + AreStringsEqual( + issuerValidationResult1.Exception.Source, + issuerValidationResult2.Exception.Source, + $"({name1})issuerValidationResult1.Exception.Source", + $"({name2})issuerValidationResult2.Exception.Source", + localContext); + + if (!string.IsNullOrEmpty(stackPrefix)) + AreStringPrefixesEqual( + issuerValidationResult1.Exception.StackTrace.Trim(), + issuerValidationResult2.Exception.StackTrace.Trim(), + $"({name1})issuerValidationResult1.Exception.StackTrace", + $"({name2})issuerValidationResult2.Exception.StackTrace", + stackPrefix.Trim(), + localContext); + } + + return context.Merge(localContext); + } + public static bool AreJArraysEqual(object object1, object object2, CompareContext context) { var localContext = new CompareContext(context); @@ -1089,15 +1151,43 @@ public static bool AreStringsEqual(object object1, object object2, string name1, if (!string.Equals(str1, str2, context.StringComparison)) { - localContext.Diffs.Add($"{name1} != {name2}, StringComparison: '{context.StringComparison}'"); - localContext.Diffs.Add(str1); + localContext.Diffs.Add($"'{name1}' != '{name2}', StringComparison: '{context.StringComparison}'"); + localContext.Diffs.Add($"'{str1}'"); localContext.Diffs.Add($"!="); - localContext.Diffs.Add(str2); + localContext.Diffs.Add($"'{str2}'"); + } + + return context.Merge(localContext); + } + + public static bool AreStringPrefixesEqual( + string string1, + string string2, + string name1, + string name2, + string prefix, + CompareContext context) + { + var localContext = new CompareContext(context); + if (!ContinueCheckingEquality(string1, string2, localContext)) + return context.Merge(localContext); + + if (!string1.StartsWith(prefix, context.StringComparison)) + { + localContext.Diffs.Add($"'{name1}': does not start with prefix: '{prefix}', StringComparison: '{context.StringComparison}'"); + localContext.Diffs.Add($"'{string1}'"); + } + + if (!string2.StartsWith(prefix, context.StringComparison)) + { + localContext.Diffs.Add($"'{name2}': does not start with prefix: '{prefix}', StringComparison: '{context.StringComparison}'"); + localContext.Diffs.Add($"'{string2}'"); } return context.Merge(localContext); } + public static bool AreStringEnumDictionariesEqual(IDictionary> dictionary1, IDictionary> dictionary2, CompareContext context) { var localContext = new CompareContext(context); @@ -1316,6 +1406,26 @@ public static bool CompareAllPublicProperties(object obj1, object obj2, CompareC return context.Merge($"CompareAllPublicProperties: {type}", localContext); } + public static bool IsOnlyOneObjectNull(object object1, object object2, CompareContext context) + { + if (object1 == null && object2 == null) + return false; + + if (object1 == null) + { + context.Diffs.Add(BuildStringDiff(object2.GetType().ToString(), object1, object2)); + return true; + } + + if (object2 == null) + { + context.Diffs.Add(BuildStringDiff(object1.GetType().ToString(), object1, object2)); + return true; + } + + return false; + } + public static bool ContinueCheckingEquality(object obj1, object obj2, CompareContext context) { if (obj1 == null && obj2 == null) diff --git a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs index f0663cc997..1170d40822 100644 --- a/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/ValidationDelegates.cs @@ -87,7 +87,7 @@ public static string IssuerValidatorUsingConfigEcho(string issuer, SecurityToken return issuer; } - public static ValueTask IssuerValidatorAsync(string issuer, SecurityToken token, TokenValidationParameters validationParameters) + public static ValueTask IssuerValidatorInternalAsync(string issuer, SecurityToken token, TokenValidationParameters validationParameters) { return new ValueTask(issuer); } diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs index e78a0391b6..f293582ed3 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/IdentityComparerTests.cs @@ -575,9 +575,9 @@ public void CompareStrings() var string2 = "goodbye"; IdentityComparer.AreEqual(string1, string2, context); - Assert.True(context.Diffs.Count(s => s == "str1 != str2, StringComparison: 'Ordinal'") == 1); - Assert.True(context.Diffs[1] == string1); - Assert.True(context.Diffs[3] == string2); + Assert.True(context.Diffs.Count(s => s == "'str1' != 'str2', StringComparison: 'Ordinal'") == 1); + Assert.True(context.Diffs[1] == $"'{string1}'"); + Assert.True(context.Diffs[3] == $"'{string2}'"); } [Fact] diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs index dfbc058216..8dd4f023f1 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Json/JsonUtilities.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.Json; using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.JsonWebTokens; using Newtonsoft.Json.Linq; namespace Microsoft.IdentityModel.Tokens.Json.Tests @@ -169,6 +170,11 @@ public static void SetAdditionalDataValues(IDictionary dictionar dictionary["true"] = true; } + public static JsonWebToken CreateUnsignedJsonWebToken(string key, object value) + { + return new JsonWebToken(CreateUnsignedToken(key, value)); + } + public static string CreateUnsignedToken(string key, object value) { return EmptyHeader + "." + CreateEncodedJson(key, value) + "."; diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/KeyVaultVerify.cs b/test/Microsoft.IdentityModel.Tokens.Tests/KeyVaultVerify.cs index 96069e5ab5..6d27b0aa9f 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/KeyVaultVerify.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/KeyVaultVerify.cs @@ -28,7 +28,6 @@ public void DecryptValidate(KeyWrapTestParams testParams) CryptoProviderFactory.Default.ReleaseKeyWrapProvider(keyWrapProvider); } else if (testParams.Algorithm.Equals(SecurityAlgorithms.RsaOAEP, StringComparison.OrdinalIgnoreCase) - || testParams.Algorithm.Equals(SecurityAlgorithms.RsaOAEP256, StringComparison.OrdinalIgnoreCase) || testParams.Algorithm.Equals(SecurityAlgorithms.RsaPKCS1, StringComparison.OrdinalIgnoreCase)) { var keyWrapProvider = CryptoProviderFactory.Default.CreateKeyWrapProvider(testParams.Key, testParams.Algorithm); diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj b/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj index 571c3cdde6..1457cc8bd1 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Microsoft.IdentityModel.Tokens.Tests.csproj @@ -13,7 +13,6 @@ - diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/ReferenceTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/ReferenceTests.cs index 7dd6683545..57b25b2d7d 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/ReferenceTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/ReferenceTests.cs @@ -182,9 +182,7 @@ public void KeyWrapReferenceTest(KeyWrapTestParams testParams) Assert.True(Utility.AreEqual(unwrappedKey, testParams.KeyToWrap), "Utility.AreEqual(unwrappedKey, testParams.KeyToWrap)"); } else if (testParams.Algorithm.Equals(SecurityAlgorithms.RsaOAEP, StringComparison.OrdinalIgnoreCase) - || testParams.Algorithm.Equals(SecurityAlgorithms.RsaPKCS1, StringComparison.OrdinalIgnoreCase) - || testParams.Algorithm.Equals(SecurityAlgorithms.RsaOAEP256, StringComparison.OrdinalIgnoreCase) - ) + || testParams.Algorithm.Equals(SecurityAlgorithms.RsaPKCS1, StringComparison.OrdinalIgnoreCase)) { var rsaKeyWrapProvider = CryptoProviderFactory.Default.CreateKeyWrapProvider(testParams.Key, testParams.Algorithm); byte[] unwrappedKey = rsaKeyWrapProvider.UnwrapKey(testParams.EncryptedKey); diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/RsaKeyWrapProviderTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/RsaKeyWrapProviderTests.cs index 6036a5fc9d..d84a37f441 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/RsaKeyWrapProviderTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/RsaKeyWrapProviderTests.cs @@ -103,14 +103,6 @@ public static TheoryData RsaKeyWrapConstructorTheoryData() WrapKey = KeyingMaterial.RsaSecurityKey_1024 }, new KeyWrapTheoryData - { - ExpectedException = ExpectedException.SecurityTokenKeyWrapException("IDX10661:"), - TestId = "KeyTooSmall1024", - WillUnwrap = false, - WrapAlgorithm = SecurityAlgorithms.RsaOAEP256, - WrapKey = KeyingMaterial.RsaSecurityKey_1024 - }, - new KeyWrapTheoryData { ExpectedException = ExpectedException.SecurityTokenKeyWrapException("IDX10661:"), TestId = "KeyDoesNotMatchAlgorithm", @@ -213,38 +205,11 @@ public static TheoryData RsaUnwrapMismatchTheoryData() new KeyWrapTheoryData { ExpectedException = ExpectedException.KeyWrapException("IDX10659:"), - TestId = "AlgorithmAndKeyMismatchRsaPKCS1Bits4096RsaOAEPKey2048", + TestId = "AlgorithmAndKeyMismatchRsaPKCS1Bits4096RsaOAEKey2048", UnwrapAlgorithm = SecurityAlgorithms.RsaOAEP, UnwrapKey = KeyingMaterial.RsaSecurityKey_2048, WrapAlgorithm = SecurityAlgorithms.RsaPKCS1, WrapKey = KeyingMaterial.RsaSecurityKey_4096_Public, - }, - new KeyWrapTheoryData - { - ExpectedException = ExpectedException.KeyWrapException("IDX10659:"), - TestId = "AlgorithmMismatchRsaPKCS1RsaOAEP256", - UnwrapAlgorithm = SecurityAlgorithms.RsaOAEP256, - UnwrapKey = KeyingMaterial.RsaSecurityKey_2048, - WrapAlgorithm = SecurityAlgorithms.RsaPKCS1, - WrapKey = KeyingMaterial.RsaSecurityKey_2048_Public - }, - new KeyWrapTheoryData - { - ExpectedException = ExpectedException.KeyWrapException("IDX10659:"), - TestId = "KeyMismatchRsa4096Rsa2048", - UnwrapAlgorithm = SecurityAlgorithms.RsaOAEP256, - UnwrapKey = KeyingMaterial.RsaSecurityKey_2048, - WrapAlgorithm = SecurityAlgorithms.RsaOAEP, - WrapKey = KeyingMaterial.RsaSecurityKey_4096_Public, - }, - new KeyWrapTheoryData - { - ExpectedException = ExpectedException.KeyWrapException("IDX10659:"), - TestId = "AlgorithmAndKeyMismatchRsaPKCS1Bits4096RsaOAEP256Key2048", - UnwrapAlgorithm = SecurityAlgorithms.RsaOAEP256, - UnwrapKey = KeyingMaterial.RsaSecurityKey_2048, - WrapAlgorithm = SecurityAlgorithms.RsaPKCS1, - WrapKey = KeyingMaterial.RsaSecurityKey_4096_Public, } }; } @@ -403,13 +368,6 @@ public static TheoryData RsaWrapUnwrapTheoryData() ExpectedException.ArgumentNullException(), theoryData); - AddWrapUnwrapTheoryData( - "Test4", - SecurityAlgorithms.RsaOAEP256, - KeyingMaterial.RsaSecurityKey_2048_Public, - KeyingMaterial.RsaSecurityKey_2048, - theoryData); - return theoryData; } diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs index b2c606189f..d11ad7c75b 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/SecurityKeyTests.cs @@ -14,11 +14,8 @@ public class SecurityKeyTests [Fact] public void ComputeJwkThumbprint() { - var exception = Assert.Throws(() => new ManagedKeyVaultSecurityKey.ManagedKeyVaultSecurityKey("keyid").ComputeJwkThumbprint()); - Assert.Contains("IDX10710", exception.Message); - #if NET461 || NET462 - exception = Assert.Throws(() => new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP256, false).ComputeJwkThumbprint()); + var exception = Assert.Throws(() => new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP256, false).ComputeJwkThumbprint()); Assert.Contains("IDX10695", exception.Message); #else var ex = Record.Exception(() => new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP256, false).ComputeJwkThumbprint()); diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs index 041de6d40d..a44e4c7152 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/TokenValidationParametersTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.IdentityModel.Tokens.Tests { public class TokenValidationParametersTests { - int ExpectedPropertyCount = 59; + int ExpectedPropertyCount = 60; [Fact] public void Publics() @@ -71,6 +71,7 @@ public void Publics() IssuerSigningKey = issuerSigningKey, IssuerSigningKeyResolver = (token, securityToken, keyIdentifier, tvp) => { return new List { issuerSigningKey }; }, IssuerSigningKeys = issuerSigningKeys, + IssuerValidationDelegateAsync = Validators.ValidateIssuerAsync, IssuerValidator = ValidationDelegates.IssuerValidatorEcho, LifetimeValidator = ValidationDelegates.LifetimeValidatorReturnsTrue, LogTokenId = true, @@ -290,8 +291,9 @@ private TokenValidationParameters CreateTokenValidationParameters() validationParameters.IssuerSigningKeyResolverUsingConfiguration = ValidationDelegates.IssuerSigningKeyResolverUsingConfiguration; validationParameters.IssuerSigningKeyValidator = ValidationDelegates.IssuerSigningKeyValidator; validationParameters.IssuerSigningKeyValidatorUsingConfiguration = ValidationDelegates.IssuerSigningKeyValidatorUsingConfiguration; + validationParameters.IssuerValidationDelegateAsync = Validators.ValidateIssuerAsync; validationParameters.IssuerValidator = ValidationDelegates.IssuerValidatorEcho; - validationParameters.IssuerValidatorAsync = ValidationDelegates.IssuerValidatorAsync; + validationParameters.IssuerValidatorAsync = ValidationDelegates.IssuerValidatorInternalAsync; validationParameters.IssuerValidatorUsingConfiguration = ValidationDelegates.IssuerValidatorUsingConfigEcho; validationParameters.LifetimeValidator = ValidationDelegates.LifetimeValidatorReturnsTrue; validationParameters.NameClaimTypeRetriever = ValidationDelegates.NameClaimTypeRetriever; diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs new file mode 100644 index 0000000000..f5bacfc0b7 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AsyncValidatorsTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +namespace Microsoft.IdentityModel.Tokens.Validation.Tests +{ + public class AsyncValidatorTests + { + [Theory, MemberData(nameof(AsyncIssuerValidatorTestCases))] + public async Task AsyncIssuerValidatorTests(IssuerValidatorTheoryData theoryData) + { + CompareContext context = TestUtilities.WriteHeader($"{this}.AsyncIssuerValidatorTests", theoryData); + try + { + IssuerValidationResult result = await Validators.ValidateIssuerAsync( + theoryData.Issuer, + theoryData.SecurityToken, + theoryData.ValidationParameters, + null, + CancellationToken.None).ConfigureAwait(false); + Exception exception = result.Exception; + context.Diffs.Add("Exception: " + exception.ToString()); + } + catch (Exception ex) + { + context.Diffs.Add("Exception: " + ex.ToString()); + } + } + + public static TheoryData AsyncIssuerValidatorTestCases + { + get + { + TheoryData theoryData = new TheoryData(); + + theoryData.Add(new IssuerValidatorTheoryData + { + Issuer = null, + ValidationParameters = new TokenValidationParameters(), + }); + + return theoryData; + } + } + } + + public class IssuerValidatorTheoryData : TheoryDataBase + { + public string Issuer { get; set; } + public TokenValidationParameters ValidationParameters { get; set; } + public SecurityToken SecurityToken { get; set; } + } +} diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs new file mode 100644 index 0000000000..3c3f403fa2 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens.Json.Tests; +using Xunit; + +namespace Microsoft.IdentityModel.Tokens.Validation.Tests +{ + public class IssuerValidationResultTests + { + [Theory, MemberData(nameof(IssuerValdationResultsTestCases), DisableDiscoveryEnumeration = true)] + public async Task IssuerValidatorAsyncTests(IssuerValidationResultsTheoryData theoryData) + { + CompareContext context = TestUtilities.WriteHeader($"{this}.IssuerValidatorAsyncTests", theoryData); + + try + { + IssuerValidationResult issuerValidationResult = await Validators.ValidateIssuerAsync( + theoryData.Issuer, + theoryData.SecurityToken, + theoryData.ValidationParameters, + new CallContext(), + CancellationToken.None).ConfigureAwait(false); + + theoryData.ExpectedException.ProcessException(issuerValidationResult.Exception, context); + IdentityComparer.AreIssuerValidationResultsEqual( + issuerValidationResult, + theoryData.IssuerValidationResult, + context); + } + catch (SecurityTokenInvalidIssuerException ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData IssuerValdationResultsTestCases + { + get + { + TheoryData theoryData = new(); + + string validIssuer = Guid.NewGuid().ToString(); + string issClaim = Guid.NewGuid().ToString(); + theoryData.Add(new IssuerValidationResultsTheoryData("Invalid_Issuer") + { + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10205:"), + Issuer = issClaim, + IssuerValidationResult = new IssuerValidationResult( + issClaim, + ValidationFailureType.IssuerValidationFailed, + new ExceptionDetail( + new MessageDetail( + LogMessages.IDX10205, + LogHelper.MarkAsNonPII(issClaim), + LogHelper.MarkAsNonPII(validIssuer), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(null)), + LogHelper.MarkAsNonPII(null)), + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true))), + IsValid = false, + SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim), + ValidationParameters = new TokenValidationParameters { ValidIssuer = validIssuer } + }); + + theoryData.Add(new IssuerValidationResultsTheoryData("NULL_Issuer") + { + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10211:"), + IssuerValidationResult = new IssuerValidationResult( + null, + ValidationFailureType.NullArgument, + new ExceptionDetail( + new MessageDetail( + LogMessages.IDX10211, + LogHelper.MarkAsNonPII(null), + LogHelper.MarkAsNonPII(validIssuer), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(null)), + LogHelper.MarkAsNonPII(null)), + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true))), + IsValid = false, + SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim), + ValidationParameters = new TokenValidationParameters(), + }); + + return theoryData; + } + } + } + + public class IssuerValidationResultsTheoryData : TheoryDataBase + { + public IssuerValidationResultsTheoryData(string testId) : base(testId) + { + } + + public BaseConfiguration Configuration { get; set; } + + public string Issuer { get; set; } + + internal IssuerValidationResult IssuerValidationResult { get; set; } + + public bool IsValid { get; set; } + + public SecurityToken SecurityToken { get; set; } + + public TokenValidationParameters ValidationParameters { get; set; } + + internal ValidationFailureType ValidationFailureType { get; set; } + } +} diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs similarity index 99% rename from test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs rename to test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs index d7af19095e..181c05e926 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/ValidatorsTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidatorsTests.cs @@ -8,8 +8,6 @@ using Microsoft.IdentityModel.TestUtils; using Xunit; -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - namespace Microsoft.IdentityModel.Tokens.Tests { public class ValidatorsTests @@ -510,5 +508,3 @@ public class AudienceValidationTheoryData : TheoryDataBase } } } - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/CreateAndValidateTokens.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/CreateAndValidateTokens.cs index 3bba45776e..220ed19b8e 100644 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/CreateAndValidateTokens.cs +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/CreateAndValidateTokens.cs @@ -799,30 +799,6 @@ public static TheoryData KeyWrapTokenTheoryData() var theoryData = new TheoryData(); var handler = new JwtSecurityTokenHandler(); var rsaOAEPEncryptingCredential = new EncryptingCredentials(KeyingMaterial.DefaultX509Key_2048, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512); - var rsaOAEP256EncryptingCredential = new EncryptingCredentials(KeyingMaterial.DefaultX509Key_2048, SecurityAlgorithms.RsaOAEP256, SecurityAlgorithms.Aes256CbcHmacSha512); var rsaPKCS1EncryptingCredential = new EncryptingCredentials(KeyingMaterial.DefaultX509Key_2048, SecurityAlgorithms.RsaPKCS1, SecurityAlgorithms.Aes256CbcHmacSha512); theoryData.Add(new KeyWrapTokenTheoryData @@ -2777,13 +2777,6 @@ public static TheoryData KeyWrapTokenTheoryData() TestId = "Key wrap token test using OAEP padding" }); - theoryData.Add(new KeyWrapTokenTheoryData - { - EncryptingCredentials = rsaOAEP256EncryptingCredential, - DecryptingCredentials = rsaOAEP256EncryptingCredential, - TestId = "Key wrap token test using OAEP-256 padding" - }); - theoryData.Add(new KeyWrapTokenTheoryData { EncryptingCredentials = rsaPKCS1EncryptingCredential, diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs index 64344d495b..4de6f67bce 100644 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj index 5ff3cf0408..b0ec53c7af 100644 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj @@ -19,6 +19,10 @@ + + + +