diff --git a/README.md b/README.md index a84b34a42..2b3556f97 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ This library supports generating and decoding [JSON Web Tokens](https://tools.ie - .NET Framework 4.0 - 4.8 - .NET Standard 1.3 - .NET Standard 2.0 -- .NET 5.0 - .NET 6.0 ## Jwt.NET @@ -47,14 +46,13 @@ var payload = new Dictionary { "claim1", 0 }, { "claim2", "claim2-value" } }; -const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; -IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric +IJwtAlgorithm algorithm = new RS256Algorithm(certificate); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); -var token = encoder.Encode(payload, secret); +var token = encoder.Encode(payload); Console.WriteLine(token); ``` @@ -62,37 +60,33 @@ Console.WriteLine(token); ```c# var token = JwtBuilder.Create() - .WithAlgorithm(new HMACSHA256Algorithm()) // symmetric - .WithSecret(secret) + .WithAlgorithm(new RS256Algorithm(certificate)) .AddClaim("exp", DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds()) + .AddClaim("claim1", 0) .AddClaim("claim2", "claim2-value") .Encode(); Console.WriteLine(token); ``` - -The output would be: - ->eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s - ### Parsing (decoding) and verifying token ```c# -const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s"; -const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; - try { IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); - IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric + IJwtAlgorithm algorithm = new RS256Algorithm(certificate); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); - var json = decoder.Decode(token, secret, verify: true); + var json = decoder.Decode(token); Console.WriteLine(json); } +catch (TokenNotYetValidException) +{ + Console.WriteLine("Token is not valid yet"); +} catch (TokenExpiredException) { Console.WriteLine("Token has expired"); @@ -107,18 +101,7 @@ catch (SignatureVerificationException) ```c# var json = JwtBuilder.Create() - .WithAlgorithm(new HMACSHA256Algorithm()) // symmetric - .WithSecret(secret) - .MustVerifySignature() - .Decode(token); -Console.WriteLine(json); -``` - -or - -```c# -var json = JwtBuilder.Create() - .WithAlgorithm(new RS256Algorithm(certificate)) // asymmetric + .WithAlgorithm(new RS256Algorithm(certificate)) .MustVerifySignature() .Decode(token); Console.WriteLine(json); @@ -132,37 +115,21 @@ You can also deserialize the JSON payload directly to a .NET type: ```c# var payload = decoder.DecodeToObject>(token, secret); -Console.WriteLine(payload["claim2"]); - ``` +``` #### Or using the fluent builder API ```c# var payload = JwtBuilder.Create() - .WithAlgorithm(new HMACSHA256Algorithm()) // symmetric + .WithAlgorithm(new RS256Algorithm(certificate))         .WithSecret(secret) .MustVerifySignature() .Decode>(token); -Console.WriteLine(payload["claim2"]); -``` - -and - -```c# -var payload = JwtBuilder.Create() - .WithAlgorithm(new RS256Algorithm(certificate)) // asymmetric - .MustVerifySignature() - .Decode>(token); -Console.WriteLine(payload["claim2"]); ``` -The output would be: - ->claim2-value - ### Set and validate token expiration -As described in the [JWT RFC](https://tools.ietf.org/html/rfc7519#section-4.1.4): +As described in the [RFC 7519 section 4.1.4](https://tools.ietf.org/html/rfc7519#section-4.1.4): >The `exp` claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. @@ -170,20 +137,21 @@ If it is present in the payload and is prior to the current time the token will ```c# IDateTimeProvider provider = new UtcDateTimeProvider(); -var now = provider.GetNow(); +var now = provider.GetNow().AddMinutes(-5); // token has expired 5 minutes ago -double secondsSinceEpoch = UnixEpoch.GetSecondsSince(now); +double secondsSinceEpoch = UnixEpoch.GetSecondsSince(now); var payload = new Dictionary { { "exp", secondsSinceEpoch } }; -const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; -var token = encoder.Encode(payload, secret); +var token = encoder.Encode(payload); -var json = decoder.Decode(token, secret, validate: true); // throws TokenExpiredException +decoder.Decode(token); // throws TokenExpiredException ``` +Similarly, the `nbf` claim can be used to validate the token is not valid yet, as described in [RFC 7519 section 4.1.5](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5). + ### Parsing (decoding) token header ```c# @@ -211,32 +179,34 @@ var kid = header.KeyId; // CFAEAE2D650A6CA9862575DE54371EA980643849 ### Turning off parts of token validation -If you wish to validate a token but ignore certain parts of the validation (such as the lifetime of the token when refreshing the token), you can pass a `ValidateParameters` object to the constructor of the `JwtValidator` class. +If you'd like to validate a token but ignore certain parts of the validation (such as whether to the token has expired or not valid yet), you can pass a `ValidateParameters` object to the constructor of the `JwtValidator` class. ```c# var validationParameters = new ValidationParameters { ValidateSignature = true, - ValidateExpirationTime = true, - ValidateIssuedTime = true + ValidateExpirationTime = false, + ValidateIssuedTime = false, + TimeMargin = 100 }; IJwtValidator validator = new JwtValidator(serializer, provider, validationParameters); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); -var json = decoder.Decode(expiredToken, secret, verify: true); // will not throw because of expired token +var json = decoder.Decode(expiredToken); // will not throw because of expired token ``` #### Or using the fluent builder API ```c# var json = JwtBuilder.Create() - .WithAlgorithm(new HMACSHA256Algorithm()) + .WithAlgorithm(new RS256Algorirhm(certificate)) .WithSecret(secret) .WithValidationParameters( new ValidationParameters { ValidateSignature = true, - ValidateExpirationTime = true, - ValidateIssuedTime = true + ValidateExpirationTime = false, + ValidateIssuedTime = false, + TimeMargin = 100 }) .Decode(expiredToken); ``` @@ -263,7 +233,7 @@ public sealed class CustomJsonSerializer : IJsonSerializer And then pass this serializer to JwtEncoder constructor: ```c# -IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric +IJwtAlgorithm algorithm = new RS256Algorirhm(certificate); IJsonSerializer serializer = new CustomJsonSerializer(); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); @@ -308,19 +278,19 @@ public void ConfigureServices(IServiceCollection services) }) .AddJwt(options => { - // secrets, required only for symmetric algorithms - options.Keys = new[] { "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk" }; + // secrets, required only for symmetric algorithms, such as HMACSHA256Algorithm + // options.Keys = new[] { "mySecret" }; - // force JwtDecoder to throw exception if JWT signature is invalid - options.VerifySignature = true; + // optionally; disable throwing an exception if JWT signature is invalid + // options.VerifySignature = false; }); - // the non-generic version AddJwt() requires you to register an instance of IAlgorithmFactory manually + // the non-generic version AddJwt() requires registering an instance of IAlgorithmFactory manually services.AddSingleton(new RSAlgorithmFactory(certificate)); // or services.AddSingleton(new DelegateAlgorithmFactory(algorithm)); - // or use the generic version AddJwt ...); + // or use the generic version AddJwt ...); } public void Configure(IApplicationBuilder app) @@ -336,26 +306,6 @@ services.AddSingleton(); services.AddSingleton(); ``` -### Register authentication handler to validate JWT - -```c# -services.AddAuthentication(options => - { - // Prevents from System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. - options.DefaultAuthenticateScheme = JwtAuthenticationDefaults.AuthenticationScheme; - - // Prevents from System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. - options.DefaultChallengeScheme = JwtAuthenticationDefaults.AuthenticationScheme; - }) -.AddJwt(options => - { - options.Keys = configureOptions.Keys; - options.VerifySignature = configureOptions.VerifySignature; - - // optionally customize - }); -``` - ## License The following projects and their resulting packages are licensed under Public Domain, see the [LICENSE#Public-Domain](LICENSE.md#Public-Domain) file. @@ -366,5 +316,3 @@ The following projects and their resulting packages are licensed under the MIT L - JWT.Extensions.AspNetCore - JWT.Extensions.DependencyInjection - -**Note:** work in progress as the scenario/usage is not designed yet. The registered component will do nothing but throw an exception. diff --git a/src/JWT.Extensions.AspNetCore/JWT.Extensions.AspNetCore.csproj b/src/JWT.Extensions.AspNetCore/JWT.Extensions.AspNetCore.csproj index bde364972..17585f417 100644 --- a/src/JWT.Extensions.AspNetCore/JWT.Extensions.AspNetCore.csproj +++ b/src/JWT.Extensions.AspNetCore/JWT.Extensions.AspNetCore.csproj @@ -12,7 +12,7 @@ jwt;json;asp.net;asp.net core;.net core;authorization MIT 9.1.0 - 9.0.0.0 + 9.1.0.0 9.0.0.0 JWT.Extensions.AspNetCore true diff --git a/src/JWT.Extensions.DependencyInjection/JWT.Extensions.DependencyInjection.csproj b/src/JWT.Extensions.DependencyInjection/JWT.Extensions.DependencyInjection.csproj index 98b439188..32a69639e 100644 --- a/src/JWT.Extensions.DependencyInjection/JWT.Extensions.DependencyInjection.csproj +++ b/src/JWT.Extensions.DependencyInjection/JWT.Extensions.DependencyInjection.csproj @@ -11,7 +11,7 @@ Alexander Batishchev jwt;json;asp.net;asp.net core;.net core;authorization;dependenсy injection MIT - 2.1.0-beta2 + 2.1.0 2.0.0.0 2.0.0.0 JWT @@ -30,7 +30,7 @@ - + diff --git a/src/JWT/Algorithms/NoneAlgorithm.cs b/src/JWT/Algorithms/NoneAlgorithm.cs index a7b196819..f4a6d141e 100644 --- a/src/JWT/Algorithms/NoneAlgorithm.cs +++ b/src/JWT/Algorithms/NoneAlgorithm.cs @@ -1,23 +1,23 @@ -using System; -using System.Security.Cryptography; - -namespace JWT.Algorithms -{ - /// - /// Implements the "None" algorithm. - /// - /// RFC-7519 - public sealed class NoneAlgorithm : IJwtAlgorithm - { - /// - public string Name => "none"; - - /// +using System; +using System.Security.Cryptography; + +namespace JWT.Algorithms +{ + /// + /// Implements the "None" algorithm. + /// + /// RFC-7519 + public sealed class NoneAlgorithm : IJwtAlgorithm + { + /// + public string Name => "none"; + + /// public HashAlgorithmName HashAlgorithmName => - throw new NotSupportedException("The None algorithm doesn't have any hash algorithm."); - - /// - public byte[] Sign(byte[] key, byte[] bytesToSign) => - throw new NotSupportedException("The None algorithm doesn't support signing."); - } -} \ No newline at end of file + throw new NotSupportedException("The None algorithm doesn't have any hash algorithm."); + + /// + public byte[] Sign(byte[] key, byte[] bytesToSign) => + throw new NotSupportedException("The None algorithm doesn't support signing."); + } +} diff --git a/src/JWT/Builder/JwtBuilder.cs b/src/JWT/Builder/JwtBuilder.cs index 31cecbdfb..7427b43fc 100644 --- a/src/JWT/Builder/JwtBuilder.cs +++ b/src/JWT/Builder/JwtBuilder.cs @@ -160,14 +160,18 @@ public JwtBuilder WithAlgorithmFactory(IAlgorithmFactory algFactory) public JwtBuilder WithAlgorithm(IJwtAlgorithm algorithm) { _algorithm = algorithm; + + if (_algorithm is NoneAlgorithm) + _valParams.ValidateSignature = false; + return this; } /// - /// Sets certificate secret. + /// Sets secrets. /// /// - /// Required to create new token that uses an symmetric algorithm such as + /// Required to create a new token that uses an symmetric algorithm such as /// /// Current builder instance public JwtBuilder WithSecret(params string[] secrets) @@ -177,10 +181,10 @@ public JwtBuilder WithSecret(params string[] secrets) } /// - /// Sets certificate secret. + /// Sets secrets. /// /// - /// Required to create new token that uses an symmetric algorithm such as + /// Required to create a new token that uses an symmetric algorithm such as /// /// Current builder instance public JwtBuilder WithSecret(params byte[][] secrets) @@ -190,7 +194,7 @@ public JwtBuilder WithSecret(params byte[][] secrets) } /// - /// Instructs to do verify the JWT signature. + /// Instructs to verify the JWT signature. /// /// Current builder instance public JwtBuilder MustVerifySignature() => @@ -207,29 +211,40 @@ public JwtBuilder DoNotVerifySignature() => /// Instructs whether to verify the JWT signature. /// /// Current builder instance - public JwtBuilder WithVerifySignature(bool verify) - { - _valParams = _valParams.With(p => p.ValidateSignature = verify); - - return this; - } + public JwtBuilder WithVerifySignature(bool verify) => + WithValidationParameters(p => p.ValidateSignature = verify); /// - /// Instructs whether to verify the JWT signature and what parts of the validation to perform. + /// Sets the JWT signature validation parameters. /// /// Parameters to be used for validation + /// /// Current builder instance public JwtBuilder WithValidationParameters(ValidationParameters valParams) { + if (valParams.ValidateSignature && _algorithm is NoneAlgorithm) + throw new InvalidOperationException("Verify signature is not allowed for algorithm None"); + _valParams = valParams; + return this; } + /// + /// Sets the JWT signature validation parameters. + /// + /// Delegate to produce parameters to be used for validation + /// + /// Current builder instance + public JwtBuilder WithValidationParameters(Action action) => + WithValidationParameters(_valParams.With(action)); + /// /// Encodes a token using the supplied dependencies. /// /// The generated JWT - /// Thrown if either algorithm, serializer, encoder or secret is null + /// + /// Current builder instance public string Encode() { EnsureCanEncode(); diff --git a/src/JWT/Exceptions/TokenNotYetValidException.cs b/src/JWT/Exceptions/TokenNotYetValidException.cs new file mode 100644 index 000000000..de2b69fa2 --- /dev/null +++ b/src/JWT/Exceptions/TokenNotYetValidException.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +#if NET35 || NET40 +using IReadOnlyPayloadDictionary = System.Collections.Generic.IDictionary; +#else +using IReadOnlyPayloadDictionary = System.Collections.Generic.IReadOnlyDictionary; +#endif + +namespace JWT.Exceptions +{ + /// + /// Represents an exception thrown when a token is not yet valid. + /// + public class TokenNotYetValidException : SignatureVerificationException + { + private const string PayloadDataKey = "PayloadData"; + private const string NotBeforeKey = "NotBefore"; + + /// + /// Creates an instance of + /// + /// The error message + public TokenNotYetValidException(string message) + : base(message) + { + } + + /// + /// The payload. + /// + public IReadOnlyPayloadDictionary PayloadData + { + get => GetOrDefault>(PayloadDataKey); + internal set => this.Data.Add(PayloadDataKey, value); + } + + /// + /// The not before DateTime of the token. + /// + public DateTime? NotBefore + { + get => GetOrDefault(NotBeforeKey); + internal set => this.Data.Add(NotBeforeKey, value); + } + } +} diff --git a/src/JWT/IJwtDecoder.cs b/src/JWT/IJwtDecoder.cs index 519eb3b99..790f13ce1 100644 --- a/src/JWT/IJwtDecoder.cs +++ b/src/JWT/IJwtDecoder.cs @@ -32,8 +32,9 @@ public interface IJwtDecoder /// Given a JWT, decodes it and return the payload. /// /// The JWT + /// Whether to verify the signature (default is true) /// A string containing the JSON payload - string Decode(JwtParts jwt); + string Decode(JwtParts jwt, bool verify); /// /// Given a JWT, decodes it and return the payload. @@ -62,8 +63,9 @@ public interface IJwtDecoder /// /// The type to deserialize to. /// The JWT + /// Whether to verify the signature (default is true) /// An object representing the payload - object DecodeToObject(Type type, JwtParts jwt); + object DecodeToObject(Type type, JwtParts jwt, bool verify); /// /// Given a JWT, decodes it and return the payload as an object. @@ -89,7 +91,7 @@ public interface IJwtDecoder } /// - /// Extension methods for + /// Extension methods for > /// public static class JwtDecoderExtensions { @@ -110,75 +112,67 @@ public static IDictionary DecodeHeaderToDictionary(this IJwtDeco /// /// The decoder instance /// The JWT + /// Whether to verify the signature (default is true) /// A string containing the JSON payload /// /// /// - public static string Decode(this IJwtDecoder decoder, string token) => - decoder.Decode(new JwtParts(token)); - - /// - /// Given a JWT, decodes it and return the payload. - /// - /// The decoder instance - /// The JWT - /// A string containing the JSON payload - public static string Decode(this IJwtDecoder decoder, JwtParts jwt) => - decoder.Decode(jwt, (byte[])null, verify: false); + public static string Decode(this IJwtDecoder decoder, string token, bool verify = true) => + decoder.Decode(new JwtParts(token), (byte[])null, verify); /// - /// Given a JWT, decodes it and return the payload. + /// Given a JWT, decodes it, and return the payload. /// /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// A string containing the JSON payload /// /// /// - public static string Decode(this IJwtDecoder decoder, string token, byte[] key, bool verify) => - decoder.Decode(new JwtParts(token), key, verify); + public static string Decode(this IJwtDecoder decoder, string token, byte[] key, bool verify = true) => + decoder.Decode(new JwtParts(token), new[] { key }, verify); /// - /// Given a JWT, decodes it and return the payload. + /// Given a JWT, decodes it, and return the payload. /// /// The decoder instance /// The JWT /// The keys that were used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// A string containing the JSON payload /// /// /// - public static string Decode(this IJwtDecoder decoder, string token, byte[][] keys, bool verify) => + public static string Decode(this IJwtDecoder decoder, string token, byte[][] keys, bool verify = true) => decoder.Decode(new JwtParts(token), keys, verify); /// - /// Given a JWT, decodes it and return the payload as an dictionary. + /// Given a JWT, decodes it, and return the payload as an dictionary. /// /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// - public static string Decode(this IJwtDecoder decoder, string token, string key, bool verify) => - decoder.Decode(token, GetBytes(key), verify); + public static string Decode(this IJwtDecoder decoder, string token, string key, bool verify = true) => + decoder.Decode(token, new[] { key }, verify); /// - /// Given a JWT, decodes it and return the payload as an dictionary. + /// Given a JWT, decodes it, and return the payload as an dictionary. /// /// The decoder instance /// The JWT /// The key which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// - public static string Decode(this IJwtDecoder decoder, string token, string[] keys, bool verify) => - decoder.Decode(token, keys is object ? GetBytes(keys) : null, verify); + public static string Decode(this IJwtDecoder decoder, string token, string[] keys, bool verify = true) => + decoder.Decode(token, GetBytes(keys), verify); #endregion @@ -189,9 +183,10 @@ public static string Decode(this IJwtDecoder decoder, string token, string[] key /// /// The decoder instance /// The JWT + /// Whether to verify the signature (default is true) /// An object representing the payload - public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token) => - decoder.DecodeToObject>(token); + public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, bool verify = true) => + decoder.DecodeToObject>(token, (byte[])null, verify); /// /// Given a JWT, decodes it and return the payload as a dictionary. @@ -199,10 +194,10 @@ public static IDictionary DecodeToObject(this IJwtDecoder decode /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, string key, bool verify) => - decoder.DecodeToObject(token, GetBytes(key), verify); + public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, string key, bool verify = true) => + decoder.DecodeToObject(token, new[] { key }, verify); /// /// Given a JWT, decodes it and return the payload as a dictionary. @@ -210,10 +205,10 @@ public static IDictionary DecodeToObject(this IJwtDecoder decode /// The decoder instance /// The JWT /// The keys provided which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, string[] keys, bool verify) => - decoder.DecodeToObject(token, keys is object ? GetBytes(keys) : null, verify); + public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, string[] keys, bool verify = true) => + decoder.DecodeToObject(token, GetBytes(keys), verify); /// /// Given a JWT, decodes it and return the payload as a dictionary. @@ -221,13 +216,13 @@ public static IDictionary DecodeToObject(this IJwtDecoder decode /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// /// - public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, byte[] key, bool verify) => - decoder.DecodeToObject>(new JwtParts(token), key, verify); + public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, byte[] key, bool verify = true) => + decoder.DecodeToObject>(new JwtParts(token), new[] { key }, verify); /// /// Given a JWT, decodes it and return the payload as a dictionary. @@ -235,12 +230,12 @@ public static IDictionary DecodeToObject(this IJwtDecoder decode /// The decoder instance /// The JWT /// The keys that were used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// A string containing the JSON payload /// /// /// - public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, byte[][] keys, bool verify) => + public static IDictionary DecodeToObject(this IJwtDecoder decoder, string token, byte[][] keys, bool verify = true) => decoder.DecodeToObject>(new JwtParts(token), keys, verify); /// @@ -250,13 +245,13 @@ public static IDictionary DecodeToObject(this IJwtDecoder decode /// The type to deserialize to. /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// /// - public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, byte[] key, bool verify) => - decoder.DecodeToObject(type, new JwtParts(token), key, verify); + public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, byte[] key, bool verify = true) => + decoder.DecodeToObject(type, new JwtParts(token), new[] { key }, verify); /// /// Given a JWT, decodes it and return the payload as a dictionary. @@ -265,12 +260,12 @@ public static object DecodeToObject(this IJwtDecoder decoder, Type type, string /// The type to deserialize to. /// The JWT /// The keys that were used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// A string containing the JSON payload /// /// /// - public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, byte[][] keys, bool verify) => + public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, byte[][] keys, bool verify = true) => decoder.DecodeToObject(type, new JwtParts(token), keys, verify); /// @@ -280,12 +275,12 @@ public static object DecodeToObject(this IJwtDecoder decoder, Type type, string /// The type to deserialize to. /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// - public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, string key, bool verify) => - decoder.DecodeToObject(token, GetBytes(key), verify); + public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, string key, bool verify = true) => + decoder.DecodeToObject(type, token, new[] { key }, verify); /// /// Given a JWT, decodes it and return the payload as an dictionary. @@ -294,12 +289,12 @@ public static object DecodeToObject(this IJwtDecoder decoder, Type type, string /// The type to deserialize to. /// The JWT /// The key which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload /// /// - public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, string[] keys, bool verify) => - decoder.DecodeToObject(type, token, keys is object ? GetBytes(keys) : null, verify); + public static object DecodeToObject(this IJwtDecoder decoder, Type type, string token, string[] keys, bool verify = true) => + decoder.DecodeToObject(type, token, GetBytes(keys), verify); #endregion @@ -311,9 +306,10 @@ public static object DecodeToObject(this IJwtDecoder decoder, Type type, string /// The type to deserialize to. /// The decoder instance /// The JWT + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt) => - (T)decoder.DecodeToObject(typeof(T), jwt); + public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, bool verify = true) => + (T)decoder.DecodeToObject(typeof(T), jwt, verify); /// /// Given a JWT, decodes it and return the payload as an object. @@ -322,9 +318,9 @@ public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt) => /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, byte[] key, bool verify) => + public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, byte[] key, bool verify = true) => (T)decoder.DecodeToObject(typeof(T), jwt, key, verify); /// @@ -334,9 +330,9 @@ public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, byte[] /// The decoder instance /// The JWT /// The keys which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, byte[][] keys, bool verify) => + public static T DecodeToObject(this IJwtDecoder decoder, JwtParts jwt, byte[][] keys, bool verify = true) => (T)decoder.DecodeToObject(typeof(T), jwt, keys, verify); /// @@ -356,9 +352,9 @@ public static T DecodeToObject(this IJwtDecoder decoder, string token) => /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, string token, string key, bool verify) => + public static T DecodeToObject(this IJwtDecoder decoder, string token, string key, bool verify = true) => decoder.DecodeToObject(token, GetBytes(key), verify); /// @@ -368,9 +364,9 @@ public static T DecodeToObject(this IJwtDecoder decoder, string token, string /// The decoder instance /// The JWT /// The key that was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[] key, bool verify) => + public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[] key, bool verify = true) => decoder.DecodeToObject(new JwtParts(token), key, verify); /// @@ -380,9 +376,9 @@ public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[] /// The decoder instance /// The JWT /// The keys provided which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[][] keys, bool verify) => + public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[][] keys, bool verify = true) => decoder.DecodeToObject(new JwtParts(token), keys, verify); /// @@ -392,11 +388,11 @@ public static T DecodeToObject(this IJwtDecoder decoder, string token, byte[] /// The decoder instance /// The JWT /// The keys provided which one of them was used to sign the JWT - /// Whether to verify the signature (default is true) + /// Whether to verify the signature (default is true) /// An object representing the payload - public static T DecodeToObject(this IJwtDecoder decoder, string token, string[] keys, bool verify) => - decoder.DecodeToObject(token, keys is object ? GetBytes(keys) : null, verify); + public static T DecodeToObject(this IJwtDecoder decoder, string token, string[] keys, bool verify = true) => + decoder.DecodeToObject(token, GetBytes(keys), verify); #endregion } -} \ No newline at end of file +} diff --git a/src/JWT/Internal/EncodingHelper.cs b/src/JWT/Internal/EncodingHelper.cs index ef17e676a..d8b93b632 100644 --- a/src/JWT/Internal/EncodingHelper.cs +++ b/src/JWT/Internal/EncodingHelper.cs @@ -1,34 +1,40 @@ -using System.Text; - -namespace JWT.Internal -{ - internal static class EncodingHelper - { - private static readonly UTF8Encoding utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - - public static byte[] GetBytes(string input) => - utf8Encoding.GetBytes(input); - - public static byte[] GetBytes(string input1, byte separator, string input2) - { - byte[] output = new byte[utf8Encoding.GetByteCount(input1) + utf8Encoding.GetByteCount(input2) + 1]; - int bytesWritten = utf8Encoding.GetBytes(input1, 0, input1.Length, output, 0); - output[bytesWritten++] = separator; - utf8Encoding.GetBytes(input2, 0, input2.Length, output, bytesWritten); - return output; - } - - public static byte[][] GetBytes(string[] input) - { - byte[][] results = new byte[input.Length][]; - for (int i = 0; i < input.Length; i++) - { - results[i] = GetBytes(input[i]); - } - return results; - } - - public static string GetString(byte[] bytes) => - utf8Encoding.GetString(bytes); - } +using System.Text; + +namespace JWT.Internal +{ + internal static class EncodingHelper + { + private static readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + public static byte[] GetBytes(string input) => + _utf8Encoding.GetBytes(input); + + public static byte[] GetBytes(string input1, char separator, string input2) => + GetBytes(input1, (byte)separator, input2); + + public static byte[] GetBytes(string input1, byte separator, string input2) + { + byte[] output = new byte[_utf8Encoding.GetByteCount(input1) + _utf8Encoding.GetByteCount(input2) + 1]; + int bytesWritten = _utf8Encoding.GetBytes(input1, 0, input1.Length, output, 0); + output[bytesWritten++] = separator; + _utf8Encoding.GetBytes(input2, 0, input2.Length, output, bytesWritten); + return output; + } + + public static byte[][] GetBytes(string[] input) + { + if (input is null) + return null; + + byte[][] results = new byte[input.Length][]; + for (int i = 0; i < input.Length; i++) + { + results[i] = GetBytes(input[i]); + } + return results; + } + + public static string GetString(byte[] bytes) => + _utf8Encoding.GetString(bytes); + } } \ No newline at end of file diff --git a/src/JWT/JWT.csproj b/src/JWT/JWT.csproj index 9253d620e..7c7761f4e 100644 --- a/src/JWT/JWT.csproj +++ b/src/JWT/JWT.csproj @@ -54,4 +54,4 @@ - + \ No newline at end of file diff --git a/src/JWT/JwtDecoder.cs b/src/JWT/JwtDecoder.cs index 11805b868..e4ec4c814 100644 --- a/src/JWT/JwtDecoder.cs +++ b/src/JWT/JwtDecoder.cs @@ -6,7 +6,6 @@ using static JWT.Internal.EncodingHelper; #if NET35 using static JWT.Compatibility.String; -#else #endif namespace JWT @@ -93,15 +92,16 @@ public T DecodeHeader(JwtParts jwt) } /// + /// /// - public string Decode(JwtParts jwt) - { - if (jwt is null) - throw new ArgumentNullException(nameof(jwt)); - - var decoded = _urlEncoder.Decode(jwt.Payload); - return GetString(decoded); - } + /// + /// + /// + /// + /// + /// + public string Decode(JwtParts jwt, bool verify) => + Decode(jwt, (byte[])null, verify); /// /// @@ -110,23 +110,10 @@ public string Decode(JwtParts jwt) /// /// /// + /// /// - public string Decode(JwtParts jwt, byte[] key, bool verify) - { - if (jwt is null) - throw new ArgumentNullException(nameof(jwt)); - - if (verify) - { - if (_jwtValidator is null) - throw new InvalidOperationException("This instance was constructed without validator so cannot be used for signature validation"); - if (_algFactory is null) - throw new InvalidOperationException("This instance was constructed without algorithm factory so cannot be used for signature validation"); - - Validate(jwt, key); - } - return Decode(jwt); - } + public string Decode(JwtParts jwt, byte[] key, bool verify) => + Decode(jwt, new[] { key }, verify); /// /// @@ -134,6 +121,7 @@ public string Decode(JwtParts jwt, byte[] key, bool verify) /// /// /// + /// /// public string Decode(JwtParts jwt, byte[][] keys, bool verify) { @@ -142,11 +130,17 @@ public string Decode(JwtParts jwt, byte[][] keys, bool verify) if (verify) { - if (_jwtValidator is null || _algFactory is null) - throw new InvalidOperationException("This instance was constructed without validator and algorithm so cannot be used for signature validation"); + if (_jwtValidator is null) + throw new InvalidOperationException("This instance was constructed without validator so cannot be used for signature validation"); + if (_algFactory is null) + throw new InvalidOperationException("This instance was constructed without algorithm factory so cannot be used for signature validation"); Validate(jwt, keys); } + else + { + ValidateNoneAlgorithm(jwt); + } return Decode(jwt); } @@ -155,9 +149,12 @@ public string Decode(JwtParts jwt, byte[][] keys, bool verify) /// /// /// - public object DecodeToObject(Type type, JwtParts jwt) + /// + /// + /// + public object DecodeToObject(Type type, JwtParts jwt, bool verify) { - var payload = Decode(jwt); + var payload = Decode(jwt, verify); return _jsonSerializer.Deserialize(type, payload); } @@ -167,6 +164,7 @@ public object DecodeToObject(Type type, JwtParts jwt) /// /// /// + /// /// public object DecodeToObject(Type type, JwtParts jwt, byte[] key, bool verify) { @@ -180,6 +178,7 @@ public object DecodeToObject(Type type, JwtParts jwt, byte[] key, bool verify) /// /// /// + /// /// public object DecodeToObject(Type type, JwtParts jwt, byte[][] keys, bool verify) { @@ -196,6 +195,7 @@ public object DecodeToObject(Type type, JwtParts jwt, byte[][] keys, bool verify /// /// /// + /// /// public void Validate(string[] parts, byte[] key) => Validate(new JwtParts(parts), key); @@ -209,6 +209,7 @@ public void Validate(string[] parts, byte[] key) => /// /// /// + /// /// public void Validate(string[] parts, params byte[][] keys) => Validate(new JwtParts(parts), keys); @@ -223,11 +224,16 @@ public void Validate(string[] parts, params byte[][] keys) => /// /// /// + /// /// public void Validate(JwtParts jwt, params byte[][] keys) { if (jwt is null) throw new ArgumentNullException(nameof(jwt)); + if (jwt.Payload is null) + throw new ArgumentNullException(nameof(JwtParts.Payload)); + if (jwt.Signature is null) + throw new ArgumentNullException(nameof(JwtParts.Signature)); var decodedPayload = GetString(_urlEncoder.Decode(jwt.Payload)); var decodedSignature = _urlEncoder.Decode(jwt.Signature); @@ -237,7 +243,7 @@ public void Validate(JwtParts jwt, params byte[][] keys) if (algorithm is null) throw new ArgumentNullException(nameof(algorithm)); - var bytesToSign = GetBytes(jwt.Header, (byte)'.', jwt.Payload); + var bytesToSign = GetBytes(jwt.Header, '.', jwt.Payload); if (algorithm is IAsymmetricAlgorithm asymmAlg) { @@ -245,17 +251,31 @@ public void Validate(JwtParts jwt, params byte[][] keys) } else { - if (!AllKeysHaveValues(keys)) - throw new ArgumentOutOfRangeException(nameof(keys)); + ValidSymmetricAlgorithm(keys, decodedPayload, algorithm, bytesToSign, decodedSignature); + } + } - // the signature on the token, with the leading = - var rawSignature = Convert.ToBase64String(decodedSignature); + private string Decode(JwtParts jwt) + { + if (jwt is null) + throw new ArgumentNullException(nameof(jwt)); - // the signatures re-created by the algorithm, with the leading = - var recreatedSignatures = keys.Select(key => Convert.ToBase64String(algorithm.Sign(key, bytesToSign))).ToArray(); + var decoded = _urlEncoder.Decode(jwt.Payload); + return GetString(decoded); + } - _jwtValidator.Validate(decodedPayload, rawSignature, recreatedSignatures); - } + private void ValidSymmetricAlgorithm(byte[][] keys, string decodedPayload, IJwtAlgorithm algorithm, byte[] bytesToSign, byte[] decodedSignature) + { + if (!AllKeysHaveValues(keys)) + throw new ArgumentOutOfRangeException(nameof(keys)); + + // the signature on the token, with the leading = + var rawSignature = Convert.ToBase64String(decodedSignature); + + // the signatures re-created by the algorithm, with the leading = + var recreatedSignatures = keys.Select(key => Convert.ToBase64String(algorithm.Sign(key, bytesToSign))).ToArray(); + + _jwtValidator.Validate(decodedPayload, rawSignature, recreatedSignatures); } private static bool AllKeysHaveValues(byte[][] keys) @@ -268,5 +288,15 @@ private static bool AllKeysHaveValues(byte[][] keys) return Array.TrueForAll(keys, key => key.Length > 0); } + + private void ValidateNoneAlgorithm(JwtParts jwt) + { + var header = DecodeHeader(jwt); + if (String.Equals(header.Algorithm, nameof(JwtAlgorithmName.None), StringComparison.OrdinalIgnoreCase) && + !String.IsNullOrEmpty(jwt.Signature)) + { + throw new InvalidOperationException("Signature is not acceptable for algorithm None"); + } + } } -} \ No newline at end of file +} diff --git a/src/JWT/JwtValidator.cs b/src/JWT/JwtValidator.cs index e8ff7282e..1ba6c6539 100644 --- a/src/JWT/JwtValidator.cs +++ b/src/JWT/JwtValidator.cs @@ -255,10 +255,14 @@ private Exception ValidateNbfClaim(IReadOnlyPayloadDictionary payloadData, doubl if (secondsSinceEpoch + _valParams.TimeMargin < nbfValue) { - return new SignatureVerificationException("Token is not yet valid."); + return new TokenNotYetValidException("Token is not yet valid.") + { + NotBefore = UnixEpoch.Value.AddSeconds(nbfValue), + PayloadData = payloadData + }; } return null; } } -} \ No newline at end of file +} diff --git a/src/JWT/ValidationParameters.cs b/src/JWT/ValidationParameters.cs index ec0642fb9..23f5ee780 100644 --- a/src/JWT/ValidationParameters.cs +++ b/src/JWT/ValidationParameters.cs @@ -31,29 +31,29 @@ private ValidationParameters() public bool ValidateIssuedTime { get; set; } /// - /// Gets or sets an integer to control the time margin in seconds for exp and nbf during token validation. + /// Gets or sets the time margin in seconds for exp and nbf during token validation. /// public int TimeMargin { get; set; } /// - /// Returns a with all properties set to . + /// Returns a with all properties set to . /// - public static ValidationParameters None => new ValidationParameters + public static ValidationParameters Default => new ValidationParameters { - ValidateSignature = false, - ValidateExpirationTime = false, - ValidateIssuedTime = false, + ValidateSignature = true, + ValidateExpirationTime = true, + ValidateIssuedTime = true, TimeMargin = 0 }; /// - /// Returns a with all properties set to . + /// Returns a with all properties set to . /// - public static ValidationParameters Default => new ValidationParameters + public static ValidationParameters None => new ValidationParameters { - ValidateSignature = true, - ValidateExpirationTime = true, - ValidateIssuedTime = true, + ValidateSignature = false, + ValidateExpirationTime = false, + ValidateIssuedTime = false, TimeMargin = 0 }; } @@ -70,4 +70,4 @@ public static ValidationParameters With(this ValidationParameters @this, Action< return @this; } } -} \ No newline at end of file +} diff --git a/tests/JWT.Extensions.AspNetCore.Tests/JWT.Extensions.AspNetCore.Tests.csproj b/tests/JWT.Extensions.AspNetCore.Tests/JWT.Extensions.AspNetCore.Tests.csproj index f474bef31..705e91f1f 100644 --- a/tests/JWT.Extensions.AspNetCore.Tests/JWT.Extensions.AspNetCore.Tests.csproj +++ b/tests/JWT.Extensions.AspNetCore.Tests/JWT.Extensions.AspNetCore.Tests.csproj @@ -5,13 +5,12 @@ - - + + - - - - + + + diff --git a/tests/JWT.Extensions.DependencyInjection.Tests/JWT.Extensions.DependencyInjection.Tests.csproj b/tests/JWT.Extensions.DependencyInjection.Tests/JWT.Extensions.DependencyInjection.Tests.csproj index ce189eafa..b1449bff8 100644 --- a/tests/JWT.Extensions.DependencyInjection.Tests/JWT.Extensions.DependencyInjection.Tests.csproj +++ b/tests/JWT.Extensions.DependencyInjection.Tests/JWT.Extensions.DependencyInjection.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/JWT.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/JWT.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index 1695986f3..e3b08fd15 100644 --- a/tests/JWT.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/JWT.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -27,7 +27,7 @@ public void AddJwtEncoder_And_Register_AlgorithmFactory_Should_Register_JwtEncod { var services = new ServiceCollection(); services.AddJwtEncoder() - .AddSingleton(p => new DelegateAlgorithmFactory(TestData.HMACSHA256Algorithm)); + .AddSingleton(_ => new DelegateAlgorithmFactory(TestData.HMACSHA256Algorithm)); var provider = services.BuildServiceProvider(); var encoder = provider.GetRequiredService(); @@ -113,7 +113,7 @@ public void AddJwtDecoder_And_Register_AlgorithmFactory_Should_Register_JwtDecod decoder.Should().NotBeNull(); const string token = TestData.Token; - var payload = decoder.Decode(token); + var payload = decoder.Decode(token, TestData.Secret); payload.Should().NotBeNull(); } @@ -128,7 +128,7 @@ public void AddJwtDecoder_With_AlgorithmFactory_Should_Register_JwtDecoder() decoder.Should().NotBeNull(); const string token = TestData.Token; - var payload = decoder.Decode(token); + var payload = decoder.Decode(token, TestData.Secret); payload.Should().NotBeNull(); } } diff --git a/tests/JWT.Tests.Common/Algorithms/ECDSAAlgorithmTests.cs b/tests/JWT.Tests.Common/Algorithms/ECDSAAlgorithmTests.cs index e3e555043..9b8aa87b9 100644 --- a/tests/JWT.Tests.Common/Algorithms/ECDSAAlgorithmTests.cs +++ b/tests/JWT.Tests.Common/Algorithms/ECDSAAlgorithmTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; using FluentAssertions; using JWT.Algorithms; using JWT.Tests.Models; @@ -57,8 +56,7 @@ public void Sign_Should_Throw_Exception_When_PrivateKey_Is_Null(Func algFactory) { - var bytes = Convert.FromBase64String(TestData.ServerEcdsaPublicKey); - var cert = new X509Certificate2(bytes); + var cert = TestData.CertificateWithPublicKeyEcdsa; var alg = algFactory(cert); diff --git a/tests/JWT.Tests.Common/Algorithms/RSAlgorithmTests.cs b/tests/JWT.Tests.Common/Algorithms/RSAlgorithmTests.cs index 1a59ad7a3..10fce85ec 100644 --- a/tests/JWT.Tests.Common/Algorithms/RSAlgorithmTests.cs +++ b/tests/JWT.Tests.Common/Algorithms/RSAlgorithmTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; using AutoFixture; using FluentAssertions; using JWT.Algorithms; @@ -59,8 +58,7 @@ public void Sign_Should_Throw_Exception_When_PrivateKey_Is_Null(Func algFactory) { - var bytes = Convert.FromBase64String(TestData.ServerRsaPublicKey1); - var cert = new X509Certificate2(bytes); + var cert = TestData.CertificateWithPublicKey; var alg = algFactory(cert); diff --git a/tests/JWT.Tests.Common/Builder/JwtBuilderDecodeTests.cs b/tests/JWT.Tests.Common/Builder/JwtBuilderDecodeTests.cs index 376744870..395e30e6c 100644 --- a/tests/JWT.Tests.Common/Builder/JwtBuilderDecodeTests.cs +++ b/tests/JWT.Tests.Common/Builder/JwtBuilderDecodeTests.cs @@ -83,17 +83,43 @@ public void Decode_Using_Asymmetric_Algorithm_Should_Return_Token() } [TestMethod] - public void Decode_Using_None_Algorithm_Should_Return_Token() + public void Decode_Using_Signature_Is_Not_Accepted_For_None_Algorithm() + { + Action action = + () => JwtBuilder.Create() + .WithAlgorithm(new NoneAlgorithm()) + .DoNotVerifySignature() + .Decode(TestData.TokenWithAlgNoneMissingSignature + "ANY"); + + action.Should() + .Throw("Signature must be empty per https://datatracker.ietf.org/doc/html/rfc7518#section-3.6"); + } + + [TestMethod] + public void Decode_Using_Empty_Signature_Should_Work_For_None_Algorithm() { var token = JwtBuilder.Create() .WithAlgorithm(new NoneAlgorithm()) - .DoNotVerifySignature() - .Decode(TestData.Token); + .WithSecret(TestData.Secret) + .Decode(TestData.TokenWithAlgNoneMissingSignature); token.Should() - .NotBeNullOrEmpty("because the decoded token contains values and they should have been decoded"); + .NotBeNullOrEmpty("Using none algorithm should be valid without any signature"); } + + [TestMethod] + public void Decode_With_MustVerifySignature_Should_Not_Be_Allowed_For_For_None_Algorithm() + { + Action action = + () => JwtBuilder.Create() + .WithAlgorithm(new NoneAlgorithm()) + .MustVerifySignature() + .Decode(TestData.TokenWithAlgNoneMissingSignature); + action.Should() + .Throw("verify signature is not supported for none algorithm"); + } + [TestMethod] public void Decode_With_VerifySignature_And_Without_Algorithm_Should_Throw_Exception() { @@ -155,7 +181,7 @@ public void Decode_Without_Token_Should_Throw_Exception() .Decode(null); action.Should() - .Throw("because null is not valid value for token"); + .Throw("because null is not valid value for token"); } [TestMethod] @@ -253,7 +279,7 @@ public void Decode_With_VerifySignature_With_Multiple_String_Secrets_Empty_Shoul Action action = () => JwtBuilder.Create() .WithAlgorithm(TestData.HMACSHA256Algorithm) - .WithSecret(new string[0]) + .WithSecret(Array.Empty()) .MustVerifySignature() .Decode(TestData.Token); @@ -267,7 +293,7 @@ public void Decode_With_VerifySignature_With_Multiple_Byte_Secrets_Empty_All_Sho Action action = () => JwtBuilder.Create() .WithAlgorithm(TestData.HMACSHA256Algorithm) - .WithSecret(new byte[0]) + .WithSecret(Array.Empty()) .MustVerifySignature() .Decode(TestData.Token); @@ -281,7 +307,7 @@ public void Decode_With_VerifySignature_With_Byte_Multiple_Secrets_Empty_One_Sho Action action = () => JwtBuilder.Create() .WithAlgorithm(TestData.HMACSHA256Algorithm) - .WithSecret(new byte[0], new byte[1]) + .WithSecret(Array.Empty(), new byte[1]) .MustVerifySignature() .Decode(TestData.Token); @@ -375,4 +401,4 @@ public void Decode_ToDictionary_Without_Serializer_Should_Throw_Exception() .Throw("because token can't be decoded without valid serializer"); } } -} \ No newline at end of file +} diff --git a/tests/JWT.Tests.Common/JWT.Tests.Common.csproj b/tests/JWT.Tests.Common/JWT.Tests.Common.csproj index 262634e9c..796cee7f5 100644 --- a/tests/JWT.Tests.Common/JWT.Tests.Common.csproj +++ b/tests/JWT.Tests.Common/JWT.Tests.Common.csproj @@ -1,16 +1,16 @@  - net46;netstandard2.0;netcoreapp3.1;net6.0 + net6.0;netstandard2.0;net462 - - - - + + + + diff --git a/tests/JWT.Tests.Common/JwtBuilderEndToEndTests.cs b/tests/JWT.Tests.Common/JwtBuilderEndToEndTests.cs index c4b51f889..fda5d4c48 100644 --- a/tests/JWT.Tests.Common/JwtBuilderEndToEndTests.cs +++ b/tests/JWT.Tests.Common/JwtBuilderEndToEndTests.cs @@ -1,9 +1,7 @@ -#if NETSTANDARD2_0 || NET6_0 +#if NETSTANDARD2_1 || NET6_0 using System; using System.Collections.Generic; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; using FluentAssertions; using JWT.Algorithms; using JWT.Builder; @@ -18,7 +16,7 @@ public class JwtBuilderEndToEndTests [TestMethod] public void Encode_and_Decode_With_Certificate() { - var cert = CreateCertificate(); + var cert = TestData.CertificateWithPrivateKey; var algorithm = new RS256Algorithm(cert); const string iss = "test"; @@ -61,17 +59,6 @@ public void Encode_and_Decode_With_Certificate() .Should() .Be(TestData.ServerRsaPublicThumbprint1); } - - private static X509Certificate2 CreateCertificate() - { - var rsa = RSA.Create(); - rsa.FromXmlString(TestData.ServerRsaPrivateKey); - - var certPub = new X509Certificate2(Encoding.ASCII.GetBytes(TestData.ServerRsaPublicKey2)); - var certPriv = new X509Certificate2(certPub.CopyWithPrivateKey(rsa).Export(X509ContentType.Pfx)); - - return certPriv; - } } } #endif \ No newline at end of file diff --git a/tests/JWT.Tests.Common/JwtDecoderTests.cs b/tests/JWT.Tests.Common/JwtDecoderTests.cs index 26a69b8a4..bd7871a15 100644 --- a/tests/JWT.Tests.Common/JwtDecoderTests.cs +++ b/tests/JWT.Tests.Common/JwtDecoderTests.cs @@ -121,7 +121,7 @@ public void Decode_Should_Decode_Token_To_Json_String() } [TestMethod] - public void Decode_With_Multiple_Secrets_Should_Return_Token() + public void Decode_With_Multiple_Keys_Should_Return_Token() { const string key = TestData.Secret; const string token = TestData.Token; @@ -147,8 +147,7 @@ public void Decode_Should_Call_Custom_AlgorithmFactory() var factory = new Mock(); factory.Setup(f => f.Create(It.IsAny())) - .Returns(TestData.RS256Algorithm) - .Verifiable("because custom algorithm factory must be called"); + .Returns(TestData.RS256Algorithm); var serializer = CreateSerializer(); var dateTimeProvider = new UtcDateTimeProvider(); @@ -156,13 +155,13 @@ public void Decode_Should_Call_Custom_AlgorithmFactory() var urlEncoder = new JwtBase64UrlEncoder(); var decoder = new JwtDecoder(serializer, validator, urlEncoder, factory.Object); - decoder.Decode(token, (byte[][])null, verify: true); + decoder.Decode(token); factory.VerifyAll(); } [TestMethod] - public void Decode_Without_VerifySignature_And_Without_Algorithm_Should_Return_Token() + public void Decode_Without_Verify_And_Without_Algorithm_Should_Return_Token() { const string token = TestData.Token; var payload = TestData.Customer; @@ -172,7 +171,7 @@ public void Decode_Without_VerifySignature_And_Without_Algorithm_Should_Return_T var decoder = new JwtDecoder(serializer, urlEncoder); - var actual = decoder.Decode(token); + var actual = decoder.Decode(token, verify: false); var expected = serializer.Serialize(payload); actual.Should() @@ -180,7 +179,7 @@ public void Decode_Without_VerifySignature_And_Without_Algorithm_Should_Return_T } [TestMethod] - public void Decode_Token_Missing_Signature_Without_VerifySignature_And_Without_Algorithm_Should_Return_Token() + public void Decode_Token_Missing_Signature_And_Without_Verify_And_Without_Algorithm_Should_Return_Token() { const string token = TestData.TokenWithoutSignature; var payload = TestData.Customer; @@ -190,7 +189,7 @@ public void Decode_Token_Missing_Signature_Without_VerifySignature_And_Without_A var decoder = new JwtDecoder(serializer, urlEncoder); - var actual = decoder.Decode(token); + var actual = decoder.Decode(token, verify: false); var expected = serializer.Serialize(payload); actual.Should() @@ -217,7 +216,7 @@ public void DecodeToObject_Should_Decode_Token_To_Dictionary() } [TestMethod] - public void DecodeToObject_Should_Decode_Token_To_Dictionary_Multiple_Secrets() + public void DecodeToObject_Should_Decode_Token_To_Dictionary_Multiple_Keys() { var expected = TestData.DictionaryPayload; const string key = TestData.Secret; @@ -255,7 +254,7 @@ public void DecodeToObject_Should_Decode_Token_To_Generic_Type() } [TestMethod] - public void DecodeToObject_Should_Decode_Token_To_Generic_Type_With_Multiple_Secrets() + public void DecodeToObject_Should_Decode_Token_To_Generic_Type_With_Multiple_Keys() { const string key = TestData.Secret; const string token = TestData.Token; @@ -284,15 +283,14 @@ public void DecodeToObject_Should_Throw_Exception_On_Malformed_Token() var urlEncoder = new JwtBase64UrlEncoder(); var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); - Action action = - () => decoder.DecodeToObject(badToken, key, verify: true); + Action action = () => decoder.DecodeToObject(badToken, key, verify: true); action.Should() .Throw("because the provided token does not contains the required three parts"); } [TestMethod] - public void DecodeToObject_Should_Throw_Exception_On_Malformed_Token_With_Multiple_Secrets() + public void DecodeToObject_Should_Throw_Exception_On_Malformed_Token_With_Multiple_Keys() { const string badToken = TestData.TokenWithoutHeader; var keys = new[] { TestData.Secret, TestData.Secret2 }; @@ -303,8 +301,7 @@ public void DecodeToObject_Should_Throw_Exception_On_Malformed_Token_With_Multip var urlEncoder = new JwtBase64UrlEncoder(); var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); - Action action = - () => decoder.DecodeToObject(badToken, keys, verify: true); + Action action = () => decoder.DecodeToObject(badToken, keys, verify: true); action.Should() .Throw("because the provided token does not contains the required three parts"); @@ -321,15 +318,14 @@ public void DecodeToObject_Should_Throw_Exception_On_Invalid_Key() var urlEncoder = new JwtBase64UrlEncoder(); var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); - Action action = - () => decoder.DecodeToObject(token, key, verify: true); + Action action = () => decoder.DecodeToObject(token, key, verify: true); action.Should() .Throw("because providing the wrong key must raise an error when the signature is verified"); } [TestMethod] - public void DecodeToObject_Should_Throw_Exception_On_Invalid_Key_Multiple_Secrets() + public void DecodeToObject_Should_Throw_Exception_On_Invalid_Multiple_Keys() { const string token = TestData.Token; var keys = _fixture.Create(); @@ -339,8 +335,7 @@ public void DecodeToObject_Should_Throw_Exception_On_Invalid_Key_Multiple_Secret var urlEncoder = new JwtBase64UrlEncoder(); var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); - Action action = - () => decoder.DecodeToObject(token, keys, verify: true); + Action action = () => decoder.DecodeToObject(token, keys, verify: true); action.Should() .Throw("because providing the wrong key must raise an error when the signature is verified"); @@ -360,17 +355,17 @@ public void DecodeToObject_Should_Throw_Exception_On_Invalid_Expiration_Claim() var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); var token = encoder.Encode(new { exp = _fixture.Create() }, key); - Action action = - () => decoder.DecodeToObject(token, key, verify: true); + Action action = () => decoder.DecodeToObject(token, key, verify: true); action.Should() .Throw("because the invalid 'exp' must result in an exception on decoding"); } [TestMethod] - public void DecodeToObject_Should_Throw_Exception_On_Invalid_Expiration_Claim_MultipleKeys() + public void DecodeToObject_Should_Throw_Exception_On_Invalid_Expiration_Claim_And_Multiple_Keys() { - const string key = TestData.Secret; + const string key1 = TestData.Secret; + const string key2 = TestData.Secret; var serializer = CreateSerializer(); var validator = new JwtValidator(serializer, new UtcDateTimeProvider()); @@ -379,10 +374,9 @@ public void DecodeToObject_Should_Throw_Exception_On_Invalid_Expiration_Claim_Mu var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); - var token = encoder.Encode(new { exp = _fixture.Create() }, key); + var token = encoder.Encode(new { exp = _fixture.Create() }, key1); - Action action = - () => decoder.DecodeToObject(token, new[] { key }, verify: true); + Action action = () => decoder.DecodeToObject(token, new[] { key1, key2 }, verify: true); action.Should() .Throw("because the invalid 'exp' must result in an exception on decoding"); @@ -402,8 +396,7 @@ public void DecodeToObject_Should_Throw_Exception_On_Null_Expiration_Claim() var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); var token = encoder.Encode(new { exp = (object)null }, key); - Action action = - () => decoder.DecodeToObject(token, key, verify: true); + Action action = () => decoder.DecodeToObject(token, key, verify: true); action.Should() .Throw() @@ -411,9 +404,10 @@ public void DecodeToObject_Should_Throw_Exception_On_Null_Expiration_Claim() } [TestMethod] - public void DecodeToObject_Should_Throw_Exception_On_Null_Expiration_Claim_MultipleKeys() + public void DecodeToObject_Should_Throw_Exception_On_Null_Expiration_Claim_And_Multiple_Keys() { - const string key = TestData.Secret; + const string key1 = TestData.Secret; + const string key2 = TestData.Secret2; var serializer = CreateSerializer(); var validator = new JwtValidator(serializer, new UtcDateTimeProvider()); @@ -422,10 +416,9 @@ public void DecodeToObject_Should_Throw_Exception_On_Null_Expiration_Claim_Multi var decoder = new JwtDecoder(serializer, validator, urlEncoder, TestData.HMACSHA256Algorithm); var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); - var token = encoder.Encode(new { exp = (object)null }, key); + var token = encoder.Encode(new { exp = (object)null }, key1); - Action action = - () => decoder.DecodeToObject(token, new[] { key }, verify: true); + Action action = () => decoder.DecodeToObject(token, new[] { key1, key2 }, verify: true); action.Should() .Throw() @@ -451,8 +444,7 @@ public void DecodeToObject_Should_Throw_Exception_On_Expired_Claim() var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); var token = encoder.Encode(new { exp }, key); - Action action = - () => decoder.DecodeToObject(token, key, verify: true); + Action action = () => decoder.DecodeToObject(token, key, verify: true); action.Should() .Throw("because decoding an expired token should raise an exception when verified"); @@ -475,7 +467,7 @@ public void DecodeToObject_Should_Decode_Token_On_Exp_Claim_After_Year2038() var validToken = encoder.Encode(payload, key); var expected = serializer.Serialize(payload); - var actual = decoder.Decode(validToken, key, true); + var actual = decoder.Decode(validToken, key); expected.Should() .Be(actual, "because the token should be correctly decoded"); @@ -496,8 +488,7 @@ public void DecodeToObject_Should_Throw_Exception_Before_NotBefore_Becomes_Valid var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); var token = encoder.Encode(new { nbf }, TestData.Secret); - Action action = - () => decoder.DecodeToObject(token, TestData.Secret, verify: true); + Action action = () => decoder.DecodeToObject(token, TestData.Secret, verify: true); action.Should() .Throw(); @@ -541,8 +532,7 @@ public void DecodeToObject_Should_Throw_Exception_On_Null_NotBefore_Claim() var encoder = new JwtEncoder(TestData.HMACSHA256Algorithm, serializer, urlEncoder); var token = encoder.Encode(new { nbf = (object)null }, key); - Action action = - () => decoder.DecodeToObject(token, key, verify: true); + Action action = () => decoder.DecodeToObject(token, key, verify: true); action.Should() .Throw() diff --git a/tests/JWT.Tests.Common/JwtSecurityTests.cs b/tests/JWT.Tests.Common/JwtSecurityTests.cs index e793ba5d9..2f8e04a6a 100644 --- a/tests/JWT.Tests.Common/JwtSecurityTests.cs +++ b/tests/JWT.Tests.Common/JwtSecurityTests.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography.X509Certificates; using AutoFixture; using FluentAssertions; using JWT.Algorithms; @@ -65,7 +64,7 @@ public void Decode_Should_Throw_Exception_When_Jwt_Contains_HMA_Algorithm_But_RS const string key = TestData.ServerRsaPublicKey1; var validator = new JwtValidator(serializer, new UtcDateTimeProvider()); - var algFactory = new RSAlgorithmFactory(() => new X509Certificate2(TestData.ServerRsaPublicKey1)); + var algFactory = new RSAlgorithmFactory(() => TestData.CertificateWithPublicKey); var decoder = new JwtDecoder(serializer, validator, urlEncoder, algFactory); Action action = @@ -86,7 +85,7 @@ public void Decode_Should_Throw_Exception_When_Jwt_Contains_HMA_Algorithm_But_RS var encodedToken = encoder.Encode(TestData.Customer, TestData.ServerRsaPublicKey1); var validator = new JwtValidator(serializer, new UtcDateTimeProvider()); - var algFactory = new RSAlgorithmFactory(() => new X509Certificate2(TestData.ServerRsaPublicKey1)); + var algFactory = new RSAlgorithmFactory(() => TestData.CertificateWithPublicKey); var decoder = new JwtDecoder(serializer, validator, urlEncoder, algFactory); var keys = new[] { TestData.ServerRsaPublicKey1, TestData.ServerRsaPublicKey2 }; diff --git a/tests/JWT.Tests.Common/JwtValidatorTests.cs b/tests/JWT.Tests.Common/JwtValidatorTests.cs index 48843fb84..0c326ec47 100644 --- a/tests/JWT.Tests.Common/JwtValidatorTests.cs +++ b/tests/JWT.Tests.Common/JwtValidatorTests.cs @@ -316,7 +316,7 @@ public void TryValidate_Should_Return_False_And_Exception_Not_Null_When_Token_Is ex.Should() .NotBeNull("because invalid token should thrown exception") - .And.BeOfType(typeof(SignatureVerificationException), "because not yet usable token should thrown SignatureVerificationException"); + .And.BeOfType(typeof(TokenNotYetValidException), "because not yet usable token should thrown TokenNotYetValidException"); } [TestMethod] @@ -379,4 +379,4 @@ public void TryValidate_Should_Return_True_And_Exception_Null_When_Token_Is_Not_ } } -} \ No newline at end of file +} diff --git a/tests/JWT.Tests.Common/Models/TestData.cs b/tests/JWT.Tests.Common/Models/TestData.cs index 8a4f645d0..48505f7a3 100644 --- a/tests/JWT.Tests.Common/Models/TestData.cs +++ b/tests/JWT.Tests.Common/Models/TestData.cs @@ -3,6 +3,11 @@ using System.Security.Cryptography.X509Certificates; using JWT.Algorithms; +#if NETSTANDARD2_1 || NET6_0 +using System.Security.Cryptography; +using System.Text; +#endif + namespace JWT.Tests.Models { public static class TestData @@ -16,7 +21,7 @@ public static class TestData public const string Secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; public const string Secret2 = "QWORIJkmQWEDIHbjhOIHAUSDFOYnUGWEYT"; - public static string[] Secrets = { Secret, Secret2 }; + public static readonly string[] Secrets = { Secret, Secret2 }; public const string Token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJKZXN1cyIsIkFnZSI6MzN9.jBdQNPhChZpZSMZX6Z5okc7YJ3dc5esWp4YCtasYXFU"; public const string TokenWithoutSignature = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJGaXJzdE5hbWUiOiJKZXN1cyIsIkFnZSI6MzN9."; @@ -29,6 +34,7 @@ public static class TestData public const long TokenTimestamp = 1605834255; public const string TokenWithIncorrectSignature = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + public const string TokenWithAlgNoneMissingSignature = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ."; public const string TokenWithoutHeader = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.oj1ROhq6SyGDG3C0WIPe8wDuMJjA47uKwXCHkxl6Zy0"; public const string TokenWithoutAlgorithm = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.ANY"; @@ -41,18 +47,37 @@ public static class TestData }; public const string ServerRsaPublicKey1 = "MIICPDCCAaWgAwIBAgIBADANBgkqhkiG9w0BAQ0FADA7MQswCQYDVQQGEwJ1czELMAkGA1UECAwCVVMxETAPBgNVBAoMCENlcnR0ZXN0MQwwCgYDVQQDDANqd3QwHhcNMTcwNjI3MTgzNjM3WhcNMjAwMzIzMTgzNjM3WjA7MQswCQYDVQQGEwJ1czELMAkGA1UECAwCVVMxETAPBgNVBAoMCENlcnR0ZXN0MQwwCgYDVQQDDANqd3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALsspKjcF/mB0nheaT+9KizOeBM6Qpi69LzOLBw8rxohSFJw/BFB/ch+8jXbtq23IwtavJTwSeY6a7pbZgrwCwUK/27gy04m/tum5FJBfCVGTTI4vqUYeTKimQzxj2pupQ+wx//1tKrXMIDGdllmQ/tffQHXxYGBR5Ol543YRN+dAgMBAAGjUDBOMB0GA1UdDgQWBBQMfi0akrZdtPpiYSbE4h2/9vlaozAfBgNVHSMEGDAWgBQMfi0akrZdtPpiYSbE4h2/9vlaozAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAF9pg6H7C7O5/oHeRtKSOPb9WnrHZ/mxAl30wCh2pCtJLkgMKKhquYmKqTA+QWpSF/Qeaq17DAf7Wq8VQ2QES9WCQm+PlBlTeZAf3UTLkxIUDchuS8mR7QAgG67QNLl2OKMC4NWzq0d6ZYNzVqHHPe2AKgsRro6SEAv0Sf2QhE3j"; - public const string ServerRsaPublicThumbprint1 = "CFAEAE2D650A6CA9862575DE54371EA980643849"; public const string ServerRsaPublicKey2 = "MIIDfDCCAmSgAwIBAgIQQDCxkdjCQqmQsnSLtcHj3TANBgkqhkiG9w0BAQsFADA7MQswCQYDVQQGEwJ1czELMAkGA1UECBMCVVMxETAPBgNVBAoTCENlcnR0ZXN0MQwwCgYDVQQDEwNqd3QwHhcNMjAwMzIzMDI1NDAzWhcNMjMwMzIzMDMwNDAzWjA7MQswCQYDVQQGEwJ1czELMAkGA1UECBMCVVMxETAPBgNVBAoTCENlcnR0ZXN0MQwwCgYDVQQDEwNqd3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5hM+0cIjO0oLcxQPGdnSS0ZVJDSNVsPmtiXimSLhEIPczbZ35OSWa9PI+PRIztr/yjtwjTlCES4EjyEoJ8LYIQmGVLdYV5ULS/CyXVpgWpDdiSv6QOwB2qMv3mKiPcmaKxy+oo4zfihBqGkCC6QnspyvUFPZiWTx86Apw3u3WqBRE3HQ+PjMnjDSnWdPaAsb75ti61RU+9qYj3BwxDJR6xnAaYz1RSkxHOw4+Ty+/tNtObrZmTH7msVRpV7kMU1QgyD3Y2/JTTf3YUU0LCm1J+WJ0cMbVrILAvVlOQnRn3IlcI1LOL/e6XEyET5tVymv8S5EoJjGf2o8VnTsF3vttAgMBAAGjfDB6MA4GA1UdDwEB/wQEAwIFoDAJBgNVHRMEAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAfBgNVHSMEGDAWgBTTMvXgytSFWwQk58CpxCpZAr5G1jAdBgNVHQ4EFgQU0zL14MrUhVsEJOfAqcQqWQK+RtYwDQYJKoZIhvcNAQELBQADggEBAK5vSwzh0x0pJm6njJX29rsd53ktyph+L90Enh0xzLFN0Ku9p+tM8E9TmKR+9ppdPqIEe4G/AuR1fHvmWenEw44M85Y/pBIPZDM2QVQngjg6iRQ42yD5hb/P4+UnvP9a5uI4Xc3f4NlJi3n54qBmdD5Hg52tNYgr8FKRoNzAoUCHelLk5PW0llF8Nc6cjJf0JfrSA1lVua488Dd34sPt798xM3IoISof1dqKslTypHP4BCyZ55SSfQJ+GrY7T9J3ct23BTrPnhhq0sPDogN4j258RmDriBGZmRcnrlmuBD5v+lvjYk0fISYNMfkrCQg5zae4d6BJIZVLY3gITGbaNoA="; + private const string ServerRsaPrivateKey2 = "uYTPtHCIztKC3MUDxnZ0ktGVSQ0jVbD5rYl4pki4RCD3M22d+TklmvTyPj0SM7a/8o7cI05QhEuBI8hKCfC2CEJhlS3WFeVC0vwsl1aYFqQ3Ykr+kDsAdqjL95ioj3JmiscvqKOM34oQahpAgukJ7Kcr1BT2Ylk8fOgKcN7t1qgURNx0Pj4zJ4w0p1nT2gLG++bYutUVPvamI9wcMQyUesZwGmM9UUpMRzsOPk8vv7TbTm62Zkx+5rFUaVe5DFNUIMg92NvyU0392FFNCwptSflidHDG1ayCwL1ZTkJ0Z9yJXCNSzi/3ulxMhE+bVcpr/EuRKCYxn9qPFZ07Bd77bQ==AQAB

2Mrzzbb8Gh7aoW0YXdtdO7WEZ7+pOvbxdp4Qw8sp8dF5cF5vss3I2FoJ9kssy/DsUsreBUhD0HKrADBus7BHKXp7Q/9hhu1nAJxpng255cUfngVD9k1xQdfWEHCeWrr7XJHcplTkh4ysH4nWK+8S+RoCpiuphkJJqVxPzDaY1+M=

2xHwflmaMbNs9dXi3wx10SyG5KQJeRIXlKkhlUYlAU+7598AdmTiUPHfhj4WDRCmcJGHjSWqdiuQuwmRYsBXRhtk7XjGAjcefloSpXSR9G+tpVFuIthBU337g2pK1o8z/29LKiWZvcytgxQLEWwGIyduj2I9BoDw1jgFmVd/IG8=b9n+ghO37G4g1QqpeLtWVhkoEDNFyANiv5V8BtjKclZmdoBy1ujviBikbSuKGErcUzcR593KB0EyUu2qIBGCFbd447NeiTPxYdJRd9eTIyZaUrhawThhh9wpOOAyA5PXXoJvOm4wXnNI1xjRpGc7/cPavAto8rk+sh/LmAxPPYs=b2l2N6v2IWSw+22lje5WVOUiTVGnh61N1MsXS0V7OGmGlOvy3kN8XdJE7Y7RxB89pm480+neAW8ykgzRpblQKVVxRNxxR1sk5PmGFiNsvzW0yCjbrFjzEDU4HqOGIAyAU14UigDJaZ+YdttQrbGUhXheYAmEI7SbxzaCknPPMX0=SpRpqI+Z4g3jMbb0iE0oD+FAUaBXGp00DjKVbeYH8WQl2rVGFkspFYeN69u3ZFUL3JJd4rCF6zbuLq6iyDJq/F+Jo4zSzXChepr/dSEH1TszaA6imdqFyj3pjOT/ZXNK4YPCRijRM3fy8GdNybZDQljL1djY8D1YK3CWEtKuogs=ADJKztC1SseTfRmPgnZ+DLXAgbflpK6WS3+/9/UcKAsc5LOmA8bytwvkjpPqYNGkH5g7iKU8yP16rbrSXgy6NJ7VYAVENJIhYWKdxxJzAMfvVkeCc4A/sa1GFThwXUG5KBND7EExrsu3oe67LyhOBJXv7vHCvQhSwkZNbiDEtOh7y6bKOOb0aluzPir3eY3HyN7TP2uS5mEeokMvwk9yGOUvCeKoz8t9WJf8HoP2OsDqFsbs5qA66qC6DWaU9OZ0VrO3zgmceIDP2ZXFkWmz2cVJ/Yvfi5zCvc0+g670twnuG8P00Syr/3xNCVuhwwuZbDcILjNvc9uOu9iDbY5xZQ==
"; - public const string ServerRsaPrivateKey = "uYTPtHCIztKC3MUDxnZ0ktGVSQ0jVbD5rYl4pki4RCD3M22d+TklmvTyPj0SM7a/8o7cI05QhEuBI8hKCfC2CEJhlS3WFeVC0vwsl1aYFqQ3Ykr+kDsAdqjL95ioj3JmiscvqKOM34oQahpAgukJ7Kcr1BT2Ylk8fOgKcN7t1qgURNx0Pj4zJ4w0p1nT2gLG++bYutUVPvamI9wcMQyUesZwGmM9UUpMRzsOPk8vv7TbTm62Zkx+5rFUaVe5DFNUIMg92NvyU0392FFNCwptSflidHDG1ayCwL1ZTkJ0Z9yJXCNSzi/3ulxMhE+bVcpr/EuRKCYxn9qPFZ07Bd77bQ==AQAB

2Mrzzbb8Gh7aoW0YXdtdO7WEZ7+pOvbxdp4Qw8sp8dF5cF5vss3I2FoJ9kssy/DsUsreBUhD0HKrADBus7BHKXp7Q/9hhu1nAJxpng255cUfngVD9k1xQdfWEHCeWrr7XJHcplTkh4ysH4nWK+8S+RoCpiuphkJJqVxPzDaY1+M=

2xHwflmaMbNs9dXi3wx10SyG5KQJeRIXlKkhlUYlAU+7598AdmTiUPHfhj4WDRCmcJGHjSWqdiuQuwmRYsBXRhtk7XjGAjcefloSpXSR9G+tpVFuIthBU337g2pK1o8z/29LKiWZvcytgxQLEWwGIyduj2I9BoDw1jgFmVd/IG8=b9n+ghO37G4g1QqpeLtWVhkoEDNFyANiv5V8BtjKclZmdoBy1ujviBikbSuKGErcUzcR593KB0EyUu2qIBGCFbd447NeiTPxYdJRd9eTIyZaUrhawThhh9wpOOAyA5PXXoJvOm4wXnNI1xjRpGc7/cPavAto8rk+sh/LmAxPPYs=b2l2N6v2IWSw+22lje5WVOUiTVGnh61N1MsXS0V7OGmGlOvy3kN8XdJE7Y7RxB89pm480+neAW8ykgzRpblQKVVxRNxxR1sk5PmGFiNsvzW0yCjbrFjzEDU4HqOGIAyAU14UigDJaZ+YdttQrbGUhXheYAmEI7SbxzaCknPPMX0=SpRpqI+Z4g3jMbb0iE0oD+FAUaBXGp00DjKVbeYH8WQl2rVGFkspFYeN69u3ZFUL3JJd4rCF6zbuLq6iyDJq/F+Jo4zSzXChepr/dSEH1TszaA6imdqFyj3pjOT/ZXNK4YPCRijRM3fy8GdNybZDQljL1djY8D1YK3CWEtKuogs=ADJKztC1SseTfRmPgnZ+DLXAgbflpK6WS3+/9/UcKAsc5LOmA8bytwvkjpPqYNGkH5g7iKU8yP16rbrSXgy6NJ7VYAVENJIhYWKdxxJzAMfvVkeCc4A/sa1GFThwXUG5KBND7EExrsu3oe67LyhOBJXv7vHCvQhSwkZNbiDEtOh7y6bKOOb0aluzPir3eY3HyN7TP2uS5mEeokMvwk9yGOUvCeKoz8t9WJf8HoP2OsDqFsbs5qA66qC6DWaU9OZ0VrO3zgmceIDP2ZXFkWmz2cVJ/Yvfi5zCvc0+g670twnuG8P00Syr/3xNCVuhwwuZbDcILjNvc9uOu9iDbY5xZQ==
"; - - public const string ServerEcdsaPublicKey = "MIIByzCCAXGgAwIBAgIUPDQy5M4HxMqW8+X2KfoYUUj5l5MwCgYIKoZIzj0EAwIwOzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMREwDwYDVQQKDAhDZXJ0dGVzdDEMMAoGA1UEAwwDand0MB4XDTIxMDIwNjEzNTQ0NVoXDTIxMDMwODEzNTQ0NVowOzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMREwDwYDVQQKDAhDZXJ0dGVzdDEMMAoGA1UEAwwDand0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3Ympn6KCBHXmOmlMNfdncXBotz+0/V9JbMhMrbK8K2Oig+lrUHZL/+Ny23YWapp0laUeUfrtkfD1YyNKsPmRkaNTMFEwHQYDVR0OBBYEFCq4dD2oz6a4PYklRdKQYOe4XYiAMB8GA1UdIwQYMBaAFCq4dD2oz6a4PYklRdKQYOe4XYiAMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAKvagRL0NpbXyGyOoRHNhHjZhZT0j/VtEmi765QH6VCvAiAhEarI4dCcEWX9EwzrUI0+GUlulQZR+LtXzeoIvfUrAQ=="; + private const string ServerPublicKeyEcdsa = "MIIByzCCAXGgAwIBAgIUPDQy5M4HxMqW8+X2KfoYUUj5l5MwCgYIKoZIzj0EAwIwOzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMREwDwYDVQQKDAhDZXJ0dGVzdDEMMAoGA1UEAwwDand0MB4XDTIxMDIwNjEzNTQ0NVoXDTIxMDMwODEzNTQ0NVowOzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlVTMREwDwYDVQQKDAhDZXJ0dGVzdDEMMAoGA1UEAwwDand0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3Ympn6KCBHXmOmlMNfdncXBotz+0/V9JbMhMrbK8K2Oig+lrUHZL/+Ny23YWapp0laUeUfrtkfD1YyNKsPmRkaNTMFEwHQYDVR0OBBYEFCq4dD2oz6a4PYklRdKQYOe4XYiAMB8GA1UdIwQYMBaAFCq4dD2oz6a4PYklRdKQYOe4XYiAMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAKvagRL0NpbXyGyOoRHNhHjZhZT0j/VtEmi765QH6VCvAiAhEarI4dCcEWX9EwzrUI0+GUlulQZR+LtXzeoIvfUrAQ=="; public static readonly IJwtAlgorithm HMACSHA256Algorithm = new HMACSHA256Algorithm(); public static readonly IJwtAlgorithm RS256Algorithm = new RS256Algorithm( new X509Certificate2(Convert.FromBase64String(ServerRsaPublicKey2))); + + public static readonly X509Certificate2 CertificateWithPublicKey = new X509Certificate2( + Convert.FromBase64String(ServerRsaPublicKey1)); + + public static readonly X509Certificate2 CertificateWithPublicKeyEcdsa = new X509Certificate2( + Convert.FromBase64String(ServerPublicKeyEcdsa)); + +#if NETSTANDARD2_1 || NET6_0 + public static readonly X509Certificate2 CertificateWithPrivateKey = CreateCertificate(); + + private static X509Certificate2 CreateCertificate() + { + var rsa = RSA.Create(); + rsa.FromXmlString(ServerRsaPrivateKey2); + + var certPub = new X509Certificate2(Encoding.ASCII.GetBytes(TestData.ServerRsaPublicKey2)); + var certPriv = new X509Certificate2(certPub.CopyWithPrivateKey(rsa).Export(X509ContentType.Pfx)); + + return certPriv; + } +#endif } } \ No newline at end of file diff --git a/tests/JWT.Tests.Net35/JWT.Tests.Net35.csproj b/tests/JWT.Tests.Net35/JWT.Tests.Net35.csproj index d96922526..1f37550a0 100644 --- a/tests/JWT.Tests.Net35/JWT.Tests.Net35.csproj +++ b/tests/JWT.Tests.Net35/JWT.Tests.Net35.csproj @@ -1,7 +1,7 @@  - net46 + net462 @@ -13,10 +13,10 @@ - - - - + + + + diff --git a/tests/JWT.Tests.Net40/JWT.Tests.Net40.csproj b/tests/JWT.Tests.Net40/JWT.Tests.Net40.csproj index c2b022838..805adb97f 100644 --- a/tests/JWT.Tests.Net40/JWT.Tests.Net40.csproj +++ b/tests/JWT.Tests.Net40/JWT.Tests.Net40.csproj @@ -1,7 +1,7 @@  - net46 + net462 @@ -13,10 +13,10 @@ - - - - + + + + diff --git a/tests/JWT.Tests.Net46/JWT.Tests.Net46.csproj b/tests/JWT.Tests.Net46/JWT.Tests.Net46.csproj index 7aa8b5fd8..6a1a4ceec 100644 --- a/tests/JWT.Tests.Net46/JWT.Tests.Net46.csproj +++ b/tests/JWT.Tests.Net46/JWT.Tests.Net46.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/tests/JWT.Tests.Net60/JWT.Tests.Net60.csproj b/tests/JWT.Tests.Net60/JWT.Tests.Net60.csproj index 1caf4dc0a..42143cc1a 100644 --- a/tests/JWT.Tests.Net60/JWT.Tests.Net60.csproj +++ b/tests/JWT.Tests.Net60/JWT.Tests.Net60.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/tests/JWT.Tests.NetCore3/JWT.Tests.NetCore3.csproj b/tests/JWT.Tests.NetCore3/JWT.Tests.NetCore3.csproj index 37d8eaaa8..ed750487a 100644 --- a/tests/JWT.Tests.NetCore3/JWT.Tests.NetCore3.csproj +++ b/tests/JWT.Tests.NetCore3/JWT.Tests.NetCore3.csproj @@ -7,10 +7,10 @@ - - - - + + + +