From 0ba9ba4e23bdeedcb2db47c9f1a432bd965ed33c Mon Sep 17 00:00:00 2001 From: Scott Brady Date: Fri, 29 Mar 2024 09:21:31 +0000 Subject: [PATCH] Removed Branca and PASETO libraries --- ScottBrady.IdentityModel.sln | 14 - .../Controllers/HomeController.cs | 77 +-- .../SampleOptions.cs | 25 - ...dy.IdentityModel.Samples.AspNetCore.csproj | 2 - .../Startup.cs | 31 -- .../Views/Home/Index.cshtml | 9 - .../BrancaSecurityToken.cs | 23 - .../BrancaToken.cs | 34 -- .../BrancaTokenHandler.cs | 265 --------- ...ttBrady.IdentityModel.Tokens.Branca.csproj | 32 -- .../PasetoConstants.cs | 21 - .../PasetoSecurityToken.cs | 54 -- .../PasetoSecurityTokenDescriptor.cs | 20 - .../PasetoToken.cs | 57 -- .../PasetoTokenHandler.cs | 118 ---- ...ttBrady.IdentityModel.Tokens.Paseto.csproj | 27 - .../VersionStrategies/PasetoVersion1.cs | 103 ---- .../VersionStrategies/PasetoVersion2.cs | 96 ---- .../PasetoVersionStrategy.cs | 43 -- src/ScottBrady.IdentityModel/Assembly.cs | 3 - .../ExtendedCryptoProvider.cs | 0 .../ScottBrady.IdentityModel.csproj | 4 + .../Tokens/EdDsaSignatureProvider.cs | 2 - .../Tokens/JwtPayloadExtensions.cs | 111 ---- .../Tokens/JwtPayloadSecurityToken.cs | 46 -- .../Tokens/JwtPayloadTokenHandler.cs | 147 ----- .../ScottBrady.IdentityModel.Tests.csproj | 2 - .../Tokens/Branca/BrancaSecurityTokenTests.cs | 37 -- .../Tokens/Branca/BrancaTokenHandlerTests.cs | 515 ------------------ .../Tokens/Branca/BrancaTokenTests.cs | 92 ---- .../Branca/TestVectors/BrancaTestVectors.cs | 115 ---- .../Branca/TestVectors/testvectors.json | 268 --------- .../Tokens/JwtPayloadExtensionsTests.cs | 319 ----------- .../Tokens/JwtPayloadSecurityTokenTests.cs | 79 --- .../Tokens/JwtPayloadTokenHandlerTests.cs | 373 ------------- .../Tokens/Paseto/PasetoSecurityTokenTests.cs | 72 --- .../Tokens/Paseto/PasetoTokenHandlerTests.cs | 371 ------------- .../Tokens/Paseto/PasetoTokenTests.cs | 93 ---- .../Paseto/TestVectors/PasetoTestVectors.cs | 119 ---- .../Paseto/TestVectors/testvectors.json | 98 ---- .../VersionStrategies/PasetoVersion1Tests.cs | 213 -------- .../VersionStrategies/PasetoVersion2Tests.cs | 233 -------- .../PasetoVersionStrategyTests.cs | 53 -- 43 files changed, 21 insertions(+), 4395 deletions(-) delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Branca/ScottBrady.IdentityModel.Tokens.Branca.csproj delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/ScottBrady.IdentityModel.Tokens.Paseto.csproj delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion2.cs delete mode 100644 src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs delete mode 100644 src/ScottBrady.IdentityModel/Assembly.cs rename src/ScottBrady.IdentityModel/{Tokens => Crypto}/ExtendedCryptoProvider.cs (100%) delete mode 100644 src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs delete mode 100644 src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs delete mode 100644 src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/testvectors.json delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/testvectors.json delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs delete mode 100644 test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs diff --git a/ScottBrady.IdentityModel.sln b/ScottBrady.IdentityModel.sln index 5d80c29..d1901ec 100644 --- a/ScottBrady.IdentityModel.sln +++ b/ScottBrady.IdentityModel.sln @@ -17,10 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScottBrady.IdentityModel.Sa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScottBrady.IdentityModel.AspNetCore", "src\ScottBrady.IdentityModel.AspNetCore\ScottBrady.IdentityModel.AspNetCore.csproj", "{E2F2D4E3-A732-43FE-B082-9FD5ACBEA89B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScottBrady.IdentityModel.Tokens.Branca", "src\ScottBrady.IdentityModel.Tokens.Branca\ScottBrady.IdentityModel.Tokens.Branca.csproj", "{E27F8536-728B-4855-A8D5-921297CBD58C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScottBrady.IdentityModel.Tokens.Paseto", "src\ScottBrady.IdentityModel.Tokens.Paseto\ScottBrady.IdentityModel.Tokens.Paseto.csproj", "{EA07CA1B-4571-4FD1-9BCE-272A68FB48A6}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,14 +39,6 @@ Global {E2F2D4E3-A732-43FE-B082-9FD5ACBEA89B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2F2D4E3-A732-43FE-B082-9FD5ACBEA89B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2F2D4E3-A732-43FE-B082-9FD5ACBEA89B}.Release|Any CPU.Build.0 = Release|Any CPU - {E27F8536-728B-4855-A8D5-921297CBD58C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E27F8536-728B-4855-A8D5-921297CBD58C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E27F8536-728B-4855-A8D5-921297CBD58C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E27F8536-728B-4855-A8D5-921297CBD58C}.Release|Any CPU.Build.0 = Release|Any CPU - {EA07CA1B-4571-4FD1-9BCE-272A68FB48A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA07CA1B-4571-4FD1-9BCE-272A68FB48A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA07CA1B-4571-4FD1-9BCE-272A68FB48A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA07CA1B-4571-4FD1-9BCE-272A68FB48A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -60,8 +48,6 @@ Global {B57DDA53-D240-46ED-8275-F19600491681} = {FA25402D-0A81-48F1-9E83-7CA4801E59F9} {B7F49824-C721-4BD1-9BAF-6E442AEAB14D} = {0787B459-DE3E-4296-965C-5C891AE23840} {E2F2D4E3-A732-43FE-B082-9FD5ACBEA89B} = {2CE8E91B-6B6A-4C1F-B6FE-80A1F1199A7A} - {E27F8536-728B-4855-A8D5-921297CBD58C} = {2CE8E91B-6B6A-4C1F-B6FE-80A1F1199A7A} - {EA07CA1B-4571-4FD1-9BCE-272A68FB48A6} = {2CE8E91B-6B6A-4C1F-B6FE-80A1F1199A7A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7ACFEAF2-1A47-419B-989B-75A0A686D710} diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs index 166b31c..ad0b4f1 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Controllers/HomeController.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -10,9 +10,6 @@ using Microsoft.IdentityModel.Tokens; using ScottBrady.IdentityModel.Crypto; using ScottBrady.IdentityModel.Samples.AspNetCore.Models; -using ScottBrady.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Branca; -using ScottBrady.IdentityModel.Tokens.Paseto; namespace ScottBrady.IdentityModel.Samples.AspNetCore.Controllers; @@ -31,59 +28,6 @@ public IActionResult Index() { return View(); } - - [HttpGet] - public IActionResult Branca() - { - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(new SecurityTokenDescriptor - { - Issuer = "me", - Audience = "you", - EncryptingCredentials = options.BrancaEncryptingCredentials - }); - - var parsedToken = handler.DecryptToken(token, ((SymmetricSecurityKey) options.BrancaEncryptingCredentials.Key).Key); - - return View("Index", new TokenModel - { - Type = "Branca", - Token = token, - Payload = Encoding.UTF8.GetString(parsedToken.Payload) - }); - } - - [HttpGet] - public IActionResult Paseto(string version) - { - var handler = new PasetoTokenHandler(); - - SigningCredentials signingCredentials; - if (version == PasetoConstants.Versions.V1) - signingCredentials = new SigningCredentials(options.PasetoV1PrivateKey, SecurityAlgorithms.RsaSsaPssSha384); - else if (version == PasetoConstants.Versions.V2) - signingCredentials = new SigningCredentials(options.EdDsaPrivateKey, ExtendedSecurityAlgorithms.EdDsa); - else - throw new NotSupportedException("Unsupported version"); - - var descriptor = new PasetoSecurityTokenDescriptor(version, PasetoConstants.Purposes.Public) - { - Issuer = "me", - Audience = "you", - SigningCredentials = signingCredentials - }; - - var token = handler.CreateToken(descriptor); - var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - - return View("Index", new TokenModel - { - Type = "PASETO", - Token = token, - Payload = payload - }); - } [HttpGet] public IActionResult EdDsaJwt() @@ -98,18 +42,31 @@ public IActionResult EdDsaJwt() }; var token = handler.CreateToken(descriptor); - var payload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); + var payloadClaims = handler.ReadJsonWebToken(token).Claims; + + var claimsJson = new JsonObject(); + foreach (var claim in payloadClaims) + { + if (claim.ValueType.Contains("integer")) + { + claimsJson.Add(claim.Type, int.Parse(claim.Value)); + } + else + { + claimsJson.Add(claim.Type, claim.Value); + } + } return View("Index", new TokenModel { Type = "EdDSA JWT", Token = token, - Payload = payload + Payload = claimsJson.ToString() }); } [HttpGet] - [Authorize(AuthenticationSchemes = "branca-bearer,paseto-bearer-v1,paseto-bearer-v2,eddsa")] + [Authorize(AuthenticationSchemes = "eddsa")] public IActionResult CallApi() { return Ok(); diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs index 930ef5e..57166f5 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/SampleOptions.cs @@ -1,6 +1,4 @@ using System; -using System.Security.Cryptography; -using Microsoft.IdentityModel.Tokens; using ScottBrady.IdentityModel.Crypto; using ScottBrady.IdentityModel.Tokens; @@ -8,29 +6,6 @@ namespace ScottBrady.IdentityModel.Samples.AspNetCore; public class SampleOptions { - private EncryptingCredentials encryptingCredentials; - - public EncryptingCredentials BrancaEncryptingCredentials - { - get - { - if (encryptingCredentials == null) - { - var key = new byte[32]; - RandomNumberGenerator.Create().GetBytes(key); - - encryptingCredentials = new EncryptingCredentials( - new SymmetricSecurityKey(key), - ExtendedSecurityAlgorithms.XChaCha20Poly1305); - } - - return encryptingCredentials; - } - } - - public RsaSecurityKey PasetoV1PrivateKey = new RsaSecurityKey(RSA.Create()); - public RsaSecurityKey PasetoV1PublicKey => new RsaSecurityKey(RSA.Create(PasetoV1PrivateKey.Rsa.ExportParameters(false))); - public readonly EdDsaSecurityKey EdDsaPublicKey = new EdDsaSecurityKey( EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X =Convert.FromBase64String("doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M=")})); diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/ScottBrady.IdentityModel.Samples.AspNetCore.csproj b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/ScottBrady.IdentityModel.Samples.AspNetCore.csproj index fd3a45c..ef444f9 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/ScottBrady.IdentityModel.Samples.AspNetCore.csproj +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/ScottBrady.IdentityModel.Samples.AspNetCore.csproj @@ -13,8 +13,6 @@ - - diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs index 6862804..be098d5 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Startup.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; @@ -6,8 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Logging; using ScottBrady.IdentityModel.AspNetCore.Identity; -using ScottBrady.IdentityModel.Tokens.Branca; -using ScottBrady.IdentityModel.Tokens.Paseto; namespace ScottBrady.IdentityModel.Samples.AspNetCore; @@ -24,34 +21,6 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(sampleOptions); services.AddAuthentication() - .AddJwtBearer("branca-bearer", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new BrancaTokenHandler()); - options.TokenValidationParameters.TokenDecryptionKey = sampleOptions.BrancaEncryptingCredentials.Key; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) - .AddJwtBearer("paseto-bearer-v1", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new PasetoTokenHandler( - new Dictionary {{PasetoConstants.Versions.V1, new PasetoVersion1()}})); - - options.TokenValidationParameters.IssuerSigningKey = sampleOptions.PasetoV1PublicKey; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) - .AddJwtBearer("paseto-bearer-v2", options => - { - options.SecurityTokenValidators.Clear(); - options.SecurityTokenValidators.Add(new PasetoTokenHandler( - new Dictionary {{PasetoConstants.Versions.V2, new PasetoVersion2()}})); - - options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; - options.TokenValidationParameters.ValidIssuer = "me"; - options.TokenValidationParameters.ValidAudience = "you"; - }) .AddJwtBearer("eddsa", options => { options.TokenValidationParameters.IssuerSigningKey = sampleOptions.EdDsaPublicKey; diff --git a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Views/Home/Index.cshtml b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Views/Home/Index.cshtml index 2e1acef..bfef413 100644 --- a/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Views/Home/Index.cshtml +++ b/samples/ScottBrady.IdentityModel.Samples.AspNetCore/Views/Home/Index.cshtml @@ -6,15 +6,6 @@

ScottBrady.IdentityModel

Identity & Crypto helpers

-

- Get Branca token -

-

- Get PASETO v1.public -

-

- Get PASETO v2.public -

Get EdDSA JWT

diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs deleted file mode 100644 index 2dc1a38..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaSecurityToken.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Text; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Branca; - -[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class BrancaSecurityToken : JwtPayloadSecurityToken -{ - public BrancaSecurityToken(BrancaToken token) : base(Encoding.UTF8.GetString(token.Payload)) - { - IssuedAt = token.Timestamp; - } - - public override DateTime IssuedAt { get; } - - public override SecurityKey SecurityKey => throw new NotSupportedException(); - public override SecurityKey SigningKey - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs deleted file mode 100644 index 3655925..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaToken.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace ScottBrady.IdentityModel.Tokens.Branca; - -[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class BrancaToken -{ - private static readonly DateTime MinDateTime = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc); - private static readonly DateTime MaxDateTime = new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc); - - public BrancaToken(byte[] payload, uint timestamp) - { - Payload = payload ?? throw new ArgumentNullException(nameof(payload)); - Timestamp = GetDateTime(timestamp); - BrancaFormatTimestamp = timestamp; - } - - public byte[] Payload { get; } - public DateTime Timestamp { get; } - public uint BrancaFormatTimestamp { get; } - - public static DateTime GetDateTime(uint timestamp) - { - return DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; - } - - public static uint GetBrancaTimestamp(DateTimeOffset dateTime) - { - if (dateTime < MinDateTime || MaxDateTime < dateTime) - throw new InvalidOperationException("Timestamp cannot be before 1970 or after 2106 (uint max)"); - - return Convert.ToUInt32(dateTime.ToUniversalTime().ToUnixTimeSeconds()); - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs b/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs deleted file mode 100644 index 836d21a..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/BrancaTokenHandler.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Tokens; -using NaCl.Core; -using ScottBrady.IdentityModel.Crypto; - -namespace ScottBrady.IdentityModel.Tokens.Branca; - -[Obsolete("Branca support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class BrancaTokenHandler : JwtPayloadTokenHandler -{ - private const int TagLength = 16; - - public override bool CanReadToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) return false; - if (token.Length > MaximumTokenSizeInBytes) return false; - if (token.Any(x => !Base62.CharacterSet.Contains(x))) return false; - - return true; - } - - /// - /// Branca specification-level token generation. - /// Timestamp set to UtcNow - /// - /// The utf-8 payload to be encrypted into the Branca token - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, byte[] key) - => CreateToken(payload, DateTime.UtcNow, key); - - /// - /// Branca specification-level token generation - /// - /// The utf-8 payload to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, DateTimeOffset timestamp, byte[] key) - => CreateToken(payload, BrancaToken.GetBrancaTimestamp(timestamp), key); - - /// - /// Branca specification-level token generation - /// - /// The utf-8 payload to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(string payload, uint timestamp, byte[] key) - => CreateToken(Encoding.UTF8.GetBytes(payload), timestamp, key); - - /// - /// Branca specification-level token generation - /// - /// The payload bytes to be encrypted into the Branca token - /// The timestamp included in the Branca token (iat: issued at) - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Base62 encoded Branca Token - public virtual string CreateToken(byte[] payload, uint timestamp, byte[] key) - { - if (payload == null) throw new ArgumentNullException(nameof(payload)); - if (!IsValidKey(key)) throw new InvalidOperationException("Invalid encryption key"); - - var nonce = GenerateNonce(); - - // header - var header = new byte[29]; - using (var stream = new MemoryStream(header)) - { - // version - stream.WriteByte(0xBA); - - // timestamp (big endian uint32) - stream.Write(BitConverter.GetBytes(timestamp).Reverse().ToArray(), 0, 4); - - // nonce - stream.Write(nonce, 0, nonce.Length); - } - - var ciphertext = new byte[payload.Length]; - var tag = new byte[TagLength]; - - new XChaCha20Poly1305(key).Encrypt(nonce, payload, ciphertext, tag, header); - - var tokenBytes = new byte[header.Length + ciphertext.Length + TagLength]; - Buffer.BlockCopy(header, 0, tokenBytes, 0, header.Length); - Buffer.BlockCopy(ciphertext, 0, tokenBytes, header.Length, ciphertext.Length); - Buffer.BlockCopy(tag, 0, tokenBytes, tokenBytes.Length - TagLength, tag.Length); - - return Base62.Encode(tokenBytes); - } - - /// - /// Creates Branca token using JWT rules - /// - /// Token descriptor - /// Base62 encoded Branca Token - public virtual string CreateToken(SecurityTokenDescriptor tokenDescriptor) - { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - - if (!IsValidKey(tokenDescriptor.EncryptingCredentials)) - throw new SecurityTokenEncryptionFailedException( - "Invalid encrypting credentials. Branca tokens require a symmetric key using the XC20P algorithm and no key wrapping"); - - var jwtStylePayload = tokenDescriptor.ToJwtPayload(); - - // Remove iat claim in favour of timestamp - var jObject = JsonSerializer.Deserialize>(jwtStylePayload); - jObject.Remove(JwtRegisteredClaimNames.Iat); - - var symmetricKey = (SymmetricSecurityKey) tokenDescriptor.EncryptingCredentials.Key; - - return CreateToken(JsonSerializer.Serialize(jObject), symmetricKey.Key); - } - - /// - /// Branca specification level token decryption. - /// - /// Base62 encoded Branca token - /// 32-byte private key used to encrypt and decrypt the Branca token - /// Pared and decrypted Branca Token - public virtual BrancaToken DecryptToken(string token, byte[] key) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - if (!CanReadToken(token)) throw new InvalidCastException("Unable to read token"); - if (!IsValidKey(key)) throw new InvalidOperationException("Invalid decryption key"); - - var tokenBytes = Base62.Decode(token); - - using (var stream = new MemoryStream(tokenBytes, false)) - { - // header - var header = GuaranteedRead(stream, 29); - - byte[] nonce; - uint timestamp; - using (var headerStream = new MemoryStream(header)) - { - // version - var version = headerStream.ReadByte(); - if (version != 0xBA) throw new SecurityTokenException("Unsupported Branca version"); - - // timestamp (big endian uint32) - var timestampBytes = GuaranteedRead(headerStream, 4).Reverse().ToArray(); - timestamp = BitConverter.ToUInt32(timestampBytes, 0); - - // nonce - nonce = GuaranteedRead(headerStream, 24); - } - - // ciphertext - var ciphertextLength = stream.Length - stream.Position - TagLength; - var ciphertext = GuaranteedRead(stream, (int) ciphertextLength); - var tag = GuaranteedRead(stream, TagLength); - - var plaintext = new byte[ciphertextLength]; - new XChaCha20Poly1305(key).Decrypt(nonce, ciphertext, tag, plaintext, header); - - return new BrancaToken( - plaintext, - timestamp); - } - } - - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { - if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; - - // get decryption keys - var securityKeys = GetBrancaDecryptionKeys(token, validationParameters); - - BrancaToken decryptedToken = null; - - foreach (var securityKey in securityKeys) - { - try - { - decryptedToken = DecryptToken(token, securityKey.Key); - if (decryptedToken != null) break; - } - catch (Exception) - { - // ignored - } - } - - if (decryptedToken == null) - return new TokenValidationResult {Exception = new SecurityTokenDecryptionFailedException("Unable to decrypt token")}; - - BrancaSecurityToken brancaToken; - try - { - brancaToken = new BrancaSecurityToken(decryptedToken); - } - catch (Exception e) - { - return new TokenValidationResult {Exception = e}; - } - - var innerValidationResult = ValidateTokenPayload(brancaToken, validationParameters); - if (!innerValidationResult.IsValid) return innerValidationResult; - - var identity = innerValidationResult.ClaimsIdentity; - if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; - - return new TokenValidationResult - { - SecurityToken = brancaToken, - ClaimsIdentity = identity, - IsValid = true - }; - } - - protected virtual IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) - { - var keys = base.GetDecryptionKeys(token, validationParameters); - - return keys.Where(IsValidKey).Select(x => (SymmetricSecurityKey) x).ToList(); - } - - protected virtual bool IsValidKey(byte[] key) => key?.Length == 32; - - protected virtual bool IsValidKey(SecurityKey securityKey) - { - if (securityKey == null) return false; - if (!(securityKey is SymmetricSecurityKey symmetricKey)) return false; - - return IsValidKey(symmetricKey.Key); - } - - protected virtual bool IsValidKey(EncryptingCredentials credentials) - { - if (credentials == null) return false; - if (credentials.Enc != ExtendedSecurityAlgorithms.XChaCha20Poly1305) return false; - if (string.IsNullOrWhiteSpace(credentials.Alg) || credentials.Alg != SecurityAlgorithms.None) - { - return false; - } - - return IsValidKey(credentials.Key); - } - - protected virtual byte[] GenerateNonce() - { - var nonce = new byte[24]; - RandomNumberGenerator.Fill(nonce); - return nonce; - } - - private static byte[] GuaranteedRead(Stream stream, int length) - { - if (!stream.TryRead(length, out var bytes)) throw new SecurityTokenException(""); - return bytes; - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Branca/ScottBrady.IdentityModel.Tokens.Branca.csproj b/src/ScottBrady.IdentityModel.Tokens.Branca/ScottBrady.IdentityModel.Tokens.Branca.csproj deleted file mode 100644 index 069c26a..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Branca/ScottBrady.IdentityModel.Tokens.Branca.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net6.0;net8.0 - Scott Brady - .NET support for Branca tokens using APIs from Microsoft.IdentityModel.Tokens. - icon.png - https://github.com/scottbrady91/IdentityModel - https://github.com/scottbrady91/IdentityModel/releases - Copyright 2022 (c) Scott Brady - Branca Token - true - true - 3.1.0 - Apache-2.0 - 1591 - - - - - - - - - - - - - - - - diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs deleted file mode 100644 index 8d94fae..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoConstants.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class PasetoConstants -{ - public const int MaxPasetoSegmentCount = 4; - - public class Versions - { - public const string V1 = "v1"; - public const string V2 = "v2"; - } - - public class Purposes - { - public const string Local = "local"; - public const string Public = "public"; - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs deleted file mode 100644 index bf5b794..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityToken.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Globalization; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class PasetoSecurityToken : JwtPayloadSecurityToken -{ - protected PasetoSecurityToken() { } - - public PasetoSecurityToken(PasetoToken token) : base(token.Payload) - { - Version = token.Version; - Purpose = token.Purpose; - - EncodedFooter = token.EncodedFooter; - Footer = token.Footer; - - RawToken = token.RawToken; - } - - public virtual string Version { get; } - public virtual string Purpose { get; } - - public virtual string EncodedFooter { get; } - public virtual string Footer { get; } - - public virtual string RawToken { get; } - - public override DateTime IssuedAt => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Iat); - public override DateTime ValidFrom => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Nbf); - public override DateTime ValidTo => ParsePasetoDateTimeClaim(JwtRegisteredClaimNames.Exp); - - public override SecurityKey SecurityKey => throw new NotSupportedException(); - public override SecurityKey SigningKey { get; set; } - - protected virtual DateTime ParsePasetoDateTimeClaim(string claimType) - { - if (InnerToken.TryGetPayloadValue(claimType, out var claimValue)) - { - // ISO 8601 format - if (DateTime.TryParse(claimValue, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.RoundtripKind, out var dateTime)) - { - return dateTime.ToUniversalTime(); - } - - throw new SecurityTokenInvalidLifetimeException($"Unable to parse date time from '{claimType}'. Failing value: '{claimValue}'"); - } - - return DateTime.MinValue; - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs deleted file mode 100644 index 63bb39c..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoSecurityTokenDescriptor.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class PasetoSecurityTokenDescriptor : SecurityTokenDescriptor -{ - public PasetoSecurityTokenDescriptor(string version, string purpose) - { - if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version)); - if (string.IsNullOrWhiteSpace(purpose)) throw new ArgumentNullException(nameof(purpose)); - - Version = version; - Purpose = purpose; - } - - public string Version { get; } - public string Purpose { get; } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs deleted file mode 100644 index b90ef1b..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoToken.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class PasetoToken -{ - protected PasetoToken() { } - - public PasetoToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); - - var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); - if (tokenParts.Length != 3 && tokenParts.Length != 4) throw new ArgumentException("Invalid number of token segments"); - - RawToken = token; - - Version = tokenParts[0]; - Purpose = tokenParts[1]; - EncodedPayload = tokenParts[2]; - if (tokenParts.Length == 4) - { - EncodedFooter = tokenParts[3]; - Footer = Base64UrlEncoder.Decode(EncodedFooter); - } - } - - public string RawToken { get; } - - public string Version { get; } - public string Purpose { get; } - - public string EncodedPayload { get; } - public string Payload { get; protected set; } - - public string EncodedFooter { get; } - public string Footer { get; } - - public void SetPayload(string payload) - { - if (string.IsNullOrWhiteSpace(payload)) throw new ArgumentNullException(nameof(payload)); - - try - { - JsonSerializer.Deserialize>(payload); - Payload = payload; - } - catch (Exception e) - { - throw new ArgumentException("Token does contain valid JSON", e); - } - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs deleted file mode 100644 index c2f799d..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/PasetoTokenHandler.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -[Obsolete("PASETO support is now deprecated. Please reach out via GitHub if you would like to see this feature maintained.")] -public class PasetoTokenHandler : JwtPayloadTokenHandler -{ - private readonly Dictionary supportedVersions; - - public PasetoTokenHandler(Dictionary supportedVersions = null) - { - this.supportedVersions = supportedVersions ?? new Dictionary - { - {PasetoConstants.Versions.V1, new PasetoVersion1()}, - {PasetoConstants.Versions.V2, new PasetoVersion2()} - }; - } - - public override bool CanReadToken(string token) - { - if (string.IsNullOrWhiteSpace(token)) return false; - if (token.Length > MaximumTokenSizeInBytes) return false; - - var tokenParts = token.Split(new[] {'.'}, PasetoConstants.MaxPasetoSegmentCount + 1); - if (tokenParts.Length != 3 && tokenParts.Length != 4) return false; - - return true; - } - - public virtual string CreateToken(PasetoSecurityTokenDescriptor tokenDescriptor) - { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - if (!(tokenDescriptor is PasetoSecurityTokenDescriptor pasetoSecurityTokenDescriptor)) - throw new ArgumentException($"Token descriptor must be of type '{typeof(PasetoSecurityTokenDescriptor)}'", nameof(tokenDescriptor)); - - // get strategy for version + purpose - if (!supportedVersions.TryGetValue(pasetoSecurityTokenDescriptor.Version, out var strategy)) - { - throw new SecurityTokenException("Unsupported PASETO version"); - } - - // create payload - var payload = tokenDescriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - - // generate token - string token; - if (pasetoSecurityTokenDescriptor.Purpose == "local") - { - token = strategy.Encrypt(payload, null, pasetoSecurityTokenDescriptor.EncryptingCredentials); - } - else if (pasetoSecurityTokenDescriptor.Purpose == "public") - { - token = strategy.Sign(payload, null, pasetoSecurityTokenDescriptor.SigningCredentials); - } - else - { - throw new SecurityTokenException("Unsupported PASETO purpose"); - } - - return token; - } - - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { - if (string.IsNullOrWhiteSpace(token)) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - if (!CanReadToken(token)) return new TokenValidationResult {Exception = new SecurityTokenException("Unable to read token")}; - - var pasetoToken = new PasetoToken(token); - - // get strategy for version + purpose - if (!supportedVersions.TryGetValue(pasetoToken.Version, out var strategy)) - { - return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO version")}; - } - - PasetoSecurityToken pasetoSecurityToken; - try - { - if (pasetoToken.Purpose == "local") - { - var keys = GetDecryptionKeys(token, validationParameters); - pasetoSecurityToken = strategy.Decrypt(pasetoToken, keys); - } - else if (pasetoToken.Purpose == "public") - { - var keys = GetSigningKeys(token, validationParameters); - - // TODO: kid handling (footer?) - - pasetoSecurityToken = strategy.Verify(pasetoToken, keys); - } - else - { - return new TokenValidationResult {Exception = new SecurityTokenException("Unsupported PASETO purpose")}; - } - } - catch (Exception e) - { - return new TokenValidationResult {Exception = e}; - } - - var innerValidationResult = ValidateTokenPayload(pasetoSecurityToken, validationParameters); - if (!innerValidationResult.IsValid) return innerValidationResult; - - var identity = innerValidationResult.ClaimsIdentity; - if (validationParameters.SaveSigninToken) identity.BootstrapContext = token; - - return new TokenValidationResult - { - SecurityToken = pasetoSecurityToken, - ClaimsIdentity = identity, - IsValid = true - }; - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/ScottBrady.IdentityModel.Tokens.Paseto.csproj b/src/ScottBrady.IdentityModel.Tokens.Paseto/ScottBrady.IdentityModel.Tokens.Paseto.csproj deleted file mode 100644 index 1fe646d..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/ScottBrady.IdentityModel.Tokens.Paseto.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net6.0;net8.0 - Scott Brady - .NET support for PASETO (v1 & v2) using APIs from Microsoft.IdentityModel.Tokens. - icon.png - https://github.com/scottbrady91/IdentityModel - https://github.com/scottbrady91/IdentityModel/releases - Copyright 2022 (c) Scott Brady - PASETO Token - true - true - 3.1.0 - Apache-2.0 - 1591 - - - - - - - - - - - diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs deleted file mode 100644 index f4d66fd..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion1.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -public class PasetoVersion1 : PasetoVersionStrategy -{ - private const string PublicHeader = "v1.public."; - - public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) - { - throw new NotSupportedException("v1.local not supported"); - } - - public override string Sign(string payload, string footer, SigningCredentials signingCredentials) - { - if (payload == null) throw new ArgumentNullException(nameof(payload)); - if (signingCredentials == null) throw new ArgumentNullException(nameof(signingCredentials)); - - if (signingCredentials.Key.GetType() != typeof(RsaSecurityKey)) - throw new SecurityTokenInvalidSigningKeyException($"PASETO v1 requires a key of type {typeof(RsaSecurityKey)}"); - if (signingCredentials.Algorithm != SecurityAlgorithms.RsaSsaPssSha384) - throw new SecurityTokenInvalidSigningKeyException($"PASETO v1 requires a key for configured for the '{SecurityAlgorithms.RsaSsaPssSha384}' algorithm"); - - var privateKey = (RsaSecurityKey) signingCredentials.Key; - if (privateKey.PrivateKeyStatus != PrivateKeyStatus.Exists) - throw new SecurityTokenInvalidSigningKeyException($"Missing private key"); - - var payloadBytes = Encoding.UTF8.GetBytes(payload); - - var messageToSign = PreAuthEncode(new[] - { - Encoding.UTF8.GetBytes(PublicHeader), - payloadBytes, - Encoding.UTF8.GetBytes(footer ?? string.Empty) - }); - - var signature = privateKey.Rsa.SignData(messageToSign, HashAlgorithmName.SHA384, RSASignaturePadding.Pss); - - var token = $"{PublicHeader}{Base64UrlEncoder.Encode(payloadBytes.Combine(signature))}"; - if (!string.IsNullOrWhiteSpace(footer)) token += $".{Base64UrlEncoder.Encode(footer)}"; - - return token; - } - - public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) - { - throw new NotSupportedException("v1.local not supported"); - } - - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) - { - if (token == null) throw new ArgumentNullException(nameof(token)); - if (signingKeys == null || !signingKeys.Any()) throw new ArgumentNullException(nameof(signingKeys)); - - var keys = signingKeys.OfType().ToList(); - if (!keys.Any()) throw new SecurityTokenInvalidSigningKeyException($"PASETO v1 requires key of type {typeof(RsaSecurityKey)}"); - - if (token.Version != PasetoConstants.Versions.V1) throw new ArgumentException("Invalid PASETO version"); - if (token.Purpose != PasetoConstants.Purposes.Public) throw new ArgumentException("Invalid PASETO purpose"); - - // decode payload - var payload = Base64UrlEncoder.DecodeBytes(token.EncodedPayload); - if (payload.Length < 256) throw new SecurityTokenInvalidSignatureException("Payload does not contain signature"); - - // extract signature from payload (rightmost 64 bytes) - var signature = new byte[256]; - Buffer.BlockCopy(payload, payload.Length - 256, signature, 0, 256); - - // decode payload JSON - var message = new byte[payload.Length - 256]; - Buffer.BlockCopy(payload, 0, message, 0, payload.Length - 256); - token.SetPayload(Encoding.UTF8.GetString(message)); - - // pack - var signedMessage = PreAuthEncode(new[] - { - Encoding.UTF8.GetBytes(PublicHeader), - message, - Base64UrlEncoder.DecodeBytes(token.EncodedFooter ?? string.Empty) - }); - - // verify signature using valid keys - foreach (var publicKey in keys) - { - try - { - var isValidSignature = publicKey.Rsa.VerifyData(signedMessage, signature, HashAlgorithmName.SHA384, RSASignaturePadding.Pss); - if (isValidSignature) return new PasetoSecurityToken(token); - } - catch (Exception) - { - // ignored - } - } - - throw new SecurityTokenInvalidSignatureException("Invalid PASETO signature"); - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion2.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion2.cs deleted file mode 100644 index b5bfe74..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersion2.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Crypto; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -public class PasetoVersion2 : PasetoVersionStrategy -{ - private const string PublicHeader = "v2.public."; - - public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) - { - throw new NotSupportedException("v2.local not supported"); - } - - public override string Sign(string payload, string footer, SigningCredentials signingCredentials) - { - if (payload == null) throw new ArgumentNullException(nameof(payload)); - if (signingCredentials == null) throw new ArgumentNullException(nameof(signingCredentials)); - - if (signingCredentials.Key.GetType() != typeof(EdDsaSecurityKey)) - throw new SecurityTokenInvalidSigningKeyException($"PASETO v2 requires a key of type {typeof(EdDsaSecurityKey)}"); - if (signingCredentials.Algorithm != ExtendedSecurityAlgorithms.EdDsa) - throw new SecurityTokenInvalidSigningKeyException($"PASETO v2 requires a key for configured for the '{ExtendedSecurityAlgorithms.EdDsa}' algorithm"); - - var privateKey = (EdDsaSecurityKey) signingCredentials.Key; - if (privateKey.PrivateKeyStatus != PrivateKeyStatus.Exists) - throw new SecurityTokenInvalidSigningKeyException($"Missing private key"); - - var payloadBytes = Encoding.UTF8.GetBytes(payload); - - var messageToSign = PreAuthEncode(new[] - { - Encoding.UTF8.GetBytes(PublicHeader), - payloadBytes, - Encoding.UTF8.GetBytes(footer ?? string.Empty) - }); - - var signature = privateKey.EdDsa.Sign(messageToSign); - - var token = $"{PublicHeader}{Base64UrlEncoder.Encode(payloadBytes.Combine(signature))}"; - if (!string.IsNullOrWhiteSpace(footer)) token += $".{Base64UrlEncoder.Encode(footer)}"; - - return token; - } - - public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) - { - throw new NotSupportedException("v2.local not supported"); - } - - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) - { - if (token == null) throw new ArgumentNullException(nameof(token)); - if (signingKeys == null || !signingKeys.Any()) throw new ArgumentNullException(nameof(signingKeys)); - - var keys = signingKeys.OfType().ToList(); - if (!keys.Any()) throw new SecurityTokenInvalidSigningKeyException($"PASETO v2 requires key of type {typeof(EdDsaSecurityKey)}"); - - if (token.Version != PasetoConstants.Versions.V2) throw new ArgumentException("Invalid PASETO version"); - if (token.Purpose != PasetoConstants.Purposes.Public) throw new ArgumentException("Invalid PASETO purpose"); - - // decode payload - var payload = Base64UrlEncoder.DecodeBytes(token.EncodedPayload); - if (payload.Length < 64) throw new SecurityTokenInvalidSignatureException("Payload does not contain signature"); - - // extract signature from payload (rightmost 64 bytes) - var signature = new byte[64]; - Buffer.BlockCopy(payload, payload.Length - 64, signature, 0, 64); - - // decode payload JSON - var message = new byte[payload.Length - 64]; - Buffer.BlockCopy(payload, 0, message, 0, payload.Length - 64); - token.SetPayload(Encoding.UTF8.GetString(message)); - - // pack - var signedMessage = PreAuthEncode(new[] - { - Encoding.UTF8.GetBytes(PublicHeader), - message, - Base64UrlEncoder.DecodeBytes(token.EncodedFooter ?? string.Empty) - }); - - // verify signature using valid keys - foreach (var publicKey in keys) - { - var isValidSignature = publicKey.EdDsa.Verify(signedMessage, signature); - if (isValidSignature) return new PasetoSecurityToken(token); - } - - throw new SecurityTokenInvalidSignatureException("Invalid PASETO signature"); - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs b/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs deleted file mode 100644 index 6394906..0000000 --- a/src/ScottBrady.IdentityModel.Tokens.Paseto/VersionStrategies/PasetoVersionStrategy.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens.Paseto; - -public abstract class PasetoVersionStrategy -{ - /// - /// Creates an encrypted local token - /// - public abstract string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials); - - /// - /// Creates a signed public token - /// - public abstract string Sign(string payload, string footer, SigningCredentials signingCredentials); - - /// - /// Decrypts a local token - /// - public abstract PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys); - - /// - /// Verifies the a public token's signature - /// - public abstract PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys); - - protected static byte[] PreAuthEncode(IReadOnlyList pieces) - { - if (pieces == null) throw new ArgumentNullException(nameof(pieces)); - - var output = BitConverter.GetBytes((long) pieces.Count); - - foreach (var piece in pieces) - { - output = output.Combine(BitConverter.GetBytes((long) piece.Length), piece); - } - - return output.ToArray(); - } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Assembly.cs b/src/ScottBrady.IdentityModel/Assembly.cs deleted file mode 100644 index cfb61cd..0000000 --- a/src/ScottBrady.IdentityModel/Assembly.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly:InternalsVisibleTo("ScottBrady.IdentityModel.Tests")] \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs b/src/ScottBrady.IdentityModel/Crypto/ExtendedCryptoProvider.cs similarity index 100% rename from src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs rename to src/ScottBrady.IdentityModel/Crypto/ExtendedCryptoProvider.cs diff --git a/src/ScottBrady.IdentityModel/ScottBrady.IdentityModel.csproj b/src/ScottBrady.IdentityModel/ScottBrady.IdentityModel.csproj index c9d2efb..0af5167 100644 --- a/src/ScottBrady.IdentityModel/ScottBrady.IdentityModel.csproj +++ b/src/ScottBrady.IdentityModel/ScottBrady.IdentityModel.csproj @@ -26,5 +26,9 @@ + + + + diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs index a0e74fc..559f379 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Microsoft.IdentityModel.Tokens; namespace ScottBrady.IdentityModel.Tokens; diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs deleted file mode 100644 index fcffa59..0000000 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadExtensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Text.Json; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens; - -public static class JwtPayloadExtensions -{ - /// - /// Creates a JWT payload from a SecurityTokenDescriptor. - /// Inspired by logic found in Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler - /// - public static string ToJwtPayload(this SecurityTokenDescriptor tokenDescriptor, JwtDateTimeFormat dateTimeFormat = JwtDateTimeFormat.Unix) - { - if (tokenDescriptor == null) throw new ArgumentNullException(nameof(tokenDescriptor)); - - Dictionary payload; - if (tokenDescriptor.Subject != null) - { - payload = ToJwtClaimDictionary(tokenDescriptor.Subject.Claims); - } - else - { - payload = new Dictionary(); - } - - if (tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0) - { - foreach (var pair in tokenDescriptor.Claims) - payload[pair.Key] = pair.Value; - } - - if (tokenDescriptor.Issuer != null) - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iss, tokenDescriptor.Issuer); - if (tokenDescriptor.Audience != null) - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Aud, tokenDescriptor.Audience); - - Func dateTimeFormatFunc = null; - if (dateTimeFormat == JwtDateTimeFormat.Unix) dateTimeFormatFunc = GetUnixClaimValueOrDefault; - if (dateTimeFormat == JwtDateTimeFormat.Iso) dateTimeFormatFunc = GetIsoClaimValueOrDefault; - if (dateTimeFormatFunc == null) throw new NotSupportedException("Unsupported DateTime formatting type"); - - var now = DateTime.UtcNow; - - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Exp, dateTimeFormatFunc(tokenDescriptor.Expires, now.AddMinutes(60))); - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Iat, dateTimeFormatFunc(tokenDescriptor.IssuedAt, now)); - payload.AddClaimIfNotPresent(JwtRegisteredClaimNames.Nbf, dateTimeFormatFunc(tokenDescriptor.NotBefore, now)); - - return JsonSerializer.Serialize(payload, new JsonSerializerOptions{Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}); - } - - /// - /// Handling for serializing claims in a ClaimsIdentity. - /// Adapted from Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateDictionaryFromClaims - /// - public static Dictionary ToJwtClaimDictionary(IEnumerable claims) - { - var payload = new Dictionary(); - - foreach (var claim in claims) - { - if (claim == null) continue; - - if (payload.TryGetValue(claim.Type, out var existingValue)) - { - var existingValues = existingValue as IList; - - if (existingValues == null) - { - existingValues = new List(); - existingValues.Add(existingValue); - } - - existingValues.Add(claim.Value); - payload[claim.Type] = existingValues; - } - else - { - payload[claim.Type] = claim.Value; - } - } - - return payload; - } - - private static void AddClaimIfNotPresent(this Dictionary payload, string type, object value) - { - if (payload.TryGetValue(type, out _)) return; - payload[type] = value; - } - - private static Func GetUnixClaimValueOrDefault - => (value, defaultValue) => value.HasValue - ? EpochTime.GetIntDate(value.Value) - : EpochTime.GetIntDate(defaultValue); - - private static Func GetIsoClaimValueOrDefault - => (value, defaultValue) => value.HasValue - ? value.Value.ToString("yyyy-MM-ddTHH:mm:sszzz") - : defaultValue.ToString("yyyy-MM-ddTHH:mm:sszzz"); -} - -public enum JwtDateTimeFormat -{ - Unix, - Iso -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs deleted file mode 100644 index 776a98c..0000000 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadSecurityToken.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens; - -public abstract class JwtPayloadSecurityToken : SecurityToken -{ - protected JwtPayloadSecurityToken() { } - - public JwtPayloadSecurityToken(string payload) - { - try - { - InnerToken = new JsonWebToken("{}", payload); - - using (var hasher = SHA256.Create()) - { - var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(payload)); - TokenHash = Convert.ToBase64String(hash); - } - } - catch (Exception e) - { - throw new ArgumentException("Token does not contain valid JSON", e); - } - } - - public override string Id => InnerToken.Id; - public override string Issuer => InnerToken.Issuer; - public virtual IEnumerable Audiences => InnerToken.Audiences; - public virtual string Subject => InnerToken.Subject; - public virtual string Actor => InnerToken.Actor; - public virtual IEnumerable Claims => InnerToken.Claims; - - public virtual DateTime IssuedAt => InnerToken.IssuedAt; - public override DateTime ValidFrom => InnerToken.ValidFrom; - public override DateTime ValidTo => InnerToken.ValidTo; - - protected JsonWebToken InnerToken { get; } - public virtual string TokenHash { get; } -} \ No newline at end of file diff --git a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs b/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs deleted file mode 100644 index f6729a3..0000000 --- a/src/ScottBrady.IdentityModel/Tokens/JwtPayloadTokenHandler.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace ScottBrady.IdentityModel.Tokens; - -public abstract class JwtPayloadTokenHandler : TokenHandler, ISecurityTokenValidator -{ - public abstract bool CanReadToken(string securityToken); - public bool CanValidateToken => true; - - public virtual ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) - { - var result = ValidateToken(token, validationParameters); - - if (result.IsValid) - { - validatedToken = result.SecurityToken; - return new ClaimsPrincipal(result.ClaimsIdentity); - } - - throw result.Exception; - } - - public abstract TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters); - - /// - /// Validates a tokens lifetime, audience, and issuer using JWT payload validation rules. - /// Also checks for token replay - /// - protected virtual TokenValidationResult ValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - { - if (token == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(token))}; - if (validationParameters == null) return new TokenValidationResult {Exception = new ArgumentNullException(nameof(validationParameters))}; - - var expires = token.ValidTo == DateTime.MinValue ? null : new DateTime?(token.ValidTo); - var notBefore = token.ValidFrom == DateTime.MinValue ? null : new DateTime?(token.ValidFrom); - - try - { - ValidateLifetime(notBefore, expires, token, validationParameters); - ValidateAudience(token.Audiences, token, validationParameters); - ValidateIssuer(token.Issuer, token, validationParameters); - ValidateTokenReplay(expires, token.TokenHash, validationParameters); - } - catch (Exception e) - { - return new TokenValidationResult {Exception = e}; - } - - return new TokenValidationResult - { - SecurityToken = token, - ClaimsIdentity = CreateClaimsIdentity(token, validationParameters), - IsValid = true - }; - } - - protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters); - - protected virtual void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateAudience(audiences, securityToken, validationParameters); - - protected virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateIssuer(issuer, securityToken, validationParameters); - - protected virtual void ValidateTokenReplay(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateTokenReplay(expirationTime, securityToken, validationParameters); - - protected virtual void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) - => Validators.ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters); - - protected virtual ClaimsIdentity CreateClaimsIdentity(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - { - if (token == null) throw LogHelper.LogArgumentNullException(nameof(token)); - if (validationParameters == null) throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - var issuer = token.Issuer; - if (string.IsNullOrWhiteSpace(issuer)) issuer = ClaimsIdentity.DefaultIssuer; - - var identity = validationParameters.CreateClaimsIdentity(token, issuer); - foreach (var claim in token.Claims) - { - if (claim.Properties.Count == 0) - { - identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity)); - } - else - { - var mappedClaim = new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity); - - foreach (var kv in claim.Properties) - mappedClaim.Properties[kv.Key] = kv.Value; - - identity.AddClaim(mappedClaim); - } - } - - return identity; - } - - protected virtual IEnumerable GetDecryptionKeys(string token, TokenValidationParameters validationParameters) - { - List keys = null; - - if (validationParameters.TokenDecryptionKeyResolver != null) - { - keys = validationParameters.TokenDecryptionKeyResolver(token, null, null, validationParameters)?.ToList(); - } - - if (keys == null || !keys.Any()) - { - keys = new List(); - if (validationParameters.TokenDecryptionKey != null) - keys.Add(validationParameters.TokenDecryptionKey); - if (validationParameters.TokenDecryptionKeys != null && validationParameters.TokenDecryptionKeys.Any()) - keys.AddRange(validationParameters.TokenDecryptionKeys); - } - - return keys; - } - - protected virtual IEnumerable GetSigningKeys(string token, TokenValidationParameters validationParameters) - { - List keys = null; - - if (validationParameters.IssuerSigningKeyResolver != null) - { - keys = validationParameters.IssuerSigningKeyResolver(token, null, null, validationParameters)?.ToList(); - } - - if (keys == null || !keys.Any()) - { - keys = new List(); - if (validationParameters.IssuerSigningKey != null) - keys.Add(validationParameters.IssuerSigningKey); - if (validationParameters.IssuerSigningKeys != null && validationParameters.IssuerSigningKeys.Any()) - keys.AddRange(validationParameters.IssuerSigningKeys); - } - - return keys; - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/ScottBrady.IdentityModel.Tests.csproj b/test/ScottBrady.IdentityModel.Tests/ScottBrady.IdentityModel.Tests.csproj index 14cd293..e2ce262 100644 --- a/test/ScottBrady.IdentityModel.Tests/ScottBrady.IdentityModel.Tests.csproj +++ b/test/ScottBrady.IdentityModel.Tests/ScottBrady.IdentityModel.Tests.csproj @@ -17,8 +17,6 @@ - - diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs deleted file mode 100644 index f6911bc..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaSecurityTokenTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Text; -using FluentAssertions; -using Newtonsoft.Json; -using ScottBrady.IdentityModel.Tokens.Branca; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; - -public class BrancaSecurityTokenTests -{ - [Fact] - public void ctor_ExpectBrancaTokenTimestampUsedForIssuedAt() - { - const uint expectedIssuedAt = 1588341499; - - var jwt = JsonConvert.SerializeObject( - new - { - iss = "me", - aud = "you", - iat = expectedIssuedAt - 1000 - }); - - var token = new BrancaSecurityToken(new BrancaToken(Encoding.UTF8.GetBytes(jwt), expectedIssuedAt)); - - token.IssuedAt.Should().Be(DateTimeOffset.FromUnixTimeSeconds(expectedIssuedAt).UtcDateTime); - } - - [Fact] - public void ctor_WhenPayloadIsNotUtf8_ExpectException() - { - var payload = Encoding.Unicode.GetBytes("������"); - var exception = Assert.Throws(() => new BrancaSecurityToken(new BrancaToken(payload, 0))); - exception.Message.Should().Contain("Token does not contain valid JSON"); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs deleted file mode 100644 index c17e2ee..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenHandlerTests.cs +++ /dev/null @@ -1,515 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using Moq; -using Moq.Protected; -using Newtonsoft.Json.Linq; -using ScottBrady.IdentityModel.Crypto; -using ScottBrady.IdentityModel.Tokens.Branca; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; - -public class BrancaTokenHandlerTests -{ - private const string ValidToken = "5K6fDIqRhrSuqGE3FbuxAPd19P2toAsbBxOn4bgSame9ti6QZUQJkrggCypBJIEXF6tvhgjeMZTV76UkiqXNSvqHebeplccFrhepHkxU1SlSSFoAMKs5TUomcg6ZgDhiaYDs3IlypSxafP4uvKmu0VD"; - private readonly byte[] validKey = Encoding.UTF8.GetBytes("supersecretkeyyoushouldnotcommit"); - private static readonly byte[] ExpectedPayload = Encoding.UTF8.GetBytes("{\"user\":\"scott@scottbrady91.com\",\"scope\":[\"read\",\"write\",\"delete\"]}"); - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) - { - var handler = new BrancaTokenHandler(); - var canReadToken = handler.CanReadToken(token); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() - { - var tokenBytes = new byte[TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1]; - new Random().NextBytes(tokenBytes); - - var canReadToken = new BrancaTokenHandler().CanReadToken(Convert.ToBase64String(tokenBytes)); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenJwtToken_ExpectFalse() - { - const string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiU2NvdHQgQnJhZHkiLCJpYXQiOjE1ODU3Njc0Mjl9.DcGCOpx19JQzVVeZPHgqB73rbLaCUsx-k6PuFdit6IM"; - - var canReadToken = new BrancaTokenHandler().CanReadToken(jwt); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenTokenContainsNonBase64Characters_ExpectFalse() - { - const string token = "token=="; - - var canReadToken = new BrancaTokenHandler().CanReadToken(token); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenBrancaToken_ExpectTrue() - { - var canReadToken = new BrancaTokenHandler().CanReadToken(ValidToken); - - canReadToken.Should().BeTrue(); - } - - [Fact] - public void CanValidateToken_ExpectTrue() - => new BrancaTokenHandler().CanValidateToken.Should().BeTrue(); - - [Fact] - public void CreateToken_WhenPayloadIsNull_ExpectArgumentNullException() - { - var handler = new BrancaTokenHandler(); - Assert.Throws(() => handler.CreateToken(null, validKey)); - } - - [Fact] - public void CreateToken_WhenKeyIsNull_ExpectInvalidOperationException() - => Assert.Throws(() => new BrancaTokenHandler().CreateToken("test", null)); - - [Fact] - public void CreateToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() - => Assert.Throws(() => - new BrancaTokenHandler().CreateToken("test", Encoding.UTF8.GetBytes("iamonly14bytes"))); - - [Fact] - public void CreateToken_WhenTokenGenerated_ExpectBas62EncodedTokenWithCorrectLength() - { - var payload = CreateTestPayload(); - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(payload, 0, validKey); - - token.Any(x => !Base62.CharacterSet.Contains(x)).Should().BeFalse(); - Base62.Decode(token).Length.Should().Be(payload.Length + 29 + 16); - } - - [Fact] - public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() - => Assert.Throws(() => new BrancaTokenHandler().CreateToken(null)); - - - [Fact] - public void CreateAndDecryptToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() - { - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(new SecurityTokenDescriptor - { - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) - }); - - var parsedToken = handler.DecryptToken(token, validKey); - var jObject = JObject.Parse(Encoding.UTF8.GetString(parsedToken.Payload)); - jObject["iat"].Should().BeNull(); - - parsedToken.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1500)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void DecryptToken_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) - { - var handler = new BrancaTokenHandler(); - Assert.Throws(() => handler.DecryptToken(token, validKey)); - } - - [Fact] - public void DecryptToken_WhenKeyIsNull_ExpectInvalidOperationException() - => Assert.Throws(() => new BrancaTokenHandler().DecryptToken(ValidToken, null)); - - [Fact] - public void DecryptToken_WhenKeyIsNot32Bytes_ExpectInvalidOperationException() - => Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(ValidToken, Encoding.UTF8.GetBytes("iamonly14bytes"))); - - [Fact] - public void DecryptToken_WhenTokenHasInvalidLength_ExpectSecurityTokenException() - { - var bytes = new byte[20]; - new Random().NextBytes(bytes); - - Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); - } - - [Fact] - public void DecryptToken_WhenTokenHasIncorrectVersion_ExpectSecurityTokenException() - { - var bytes = new byte[120]; - new Random().NextBytes(bytes); - bytes[0] = 0x00; - - Assert.Throws(() => - new BrancaTokenHandler().DecryptToken(Base62.Encode(bytes), validKey)); - } - - [Fact] - public void DecryptToken_WhenValidToken_ExpectCorrectPayload() - { - var parsedToken = new BrancaTokenHandler().DecryptToken(ValidToken, validKey); - parsedToken.Payload.Should().BeEquivalentTo(ExpectedPayload); - } - - [Fact] - public void EncryptAndDecryptToken_ExpectCorrectPayloadAndTimestamp() - { - var payload = Guid.NewGuid().ToString(); - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(payload, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); - - decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); - decryptedPayload.Timestamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMilliseconds(1000)); - } - - [Fact] - public void EncryptAndDecryptToken_WithExplicitTimestamp_ExpectCorrectPayloadAndTimestamp() - { - var payload = Guid.NewGuid().ToString(); - var timestamp = new DateTime(2020, 08, 22).ToUniversalTime(); - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(payload, timestamp, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); - - decryptedPayload.Payload.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(payload)); - decryptedPayload.Timestamp.Should().Be(timestamp); - } - - [Fact] - public void EncryptAndDecryptToken_WithExplicitBrancaTimestamp_ExpectCorrectPayloadAndTimestamp() - { - var payload = CreateTestPayload(); - var timestamp = uint.MinValue; - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(payload, timestamp, validKey); - var decryptedPayload = handler.DecryptToken(token, validKey); - - decryptedPayload.Payload.Should().BeEquivalentTo(payload); - decryptedPayload.Timestamp.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); - decryptedPayload.BrancaFormatTimestamp.Should().Be(timestamp); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) - { - var result = new BrancaTokenHandler().ValidateToken(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() - { - var result = new BrancaTokenHandler().ValidateToken(ValidToken, null); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() - { - var result = new BrancaTokenHandler().ValidateToken("=====", new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenIncorrectDecryptionKey_ExpectFailureWithSecurityTokenDecryptionFailedException() - { - var key = new byte[32]; - new Random().NextBytes(key); - - var result = new BrancaTokenHandler().ValidateToken( - ValidToken, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(key)}); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenPayloadIsNotJson_ExpectFailureWithArgumentException() - { - const string tokenWithInvalidPayload = "Mvm6wbsyZMgClkmtiBf0lW3rEkvnCK5RgytoerJJex40b9yqh6GbSlfkFJHgFX9ocF"; - - var result = new BrancaTokenHandler().ValidateToken( - tokenWithInvalidPayload, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenValidToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { - var expectedIdentity = new ClaimsIdentity("test"); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = mockHandler.Object.ValidateToken( - ValidToken, - new TokenValidationParameters {TokenDecryptionKey = new SymmetricSecurityKey(validKey)}); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.Should().Be(expectedIdentity); - result.SecurityToken.Should().NotBeNull(); - } - - [Fact] - public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() - { - const string expectedToken = ValidToken; - var expectedIdentity = new ClaimsIdentity("test"); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = mockHandler.Object.ValidateToken( - expectedToken, - new TokenValidationParameters - { - TokenDecryptionKey = new SymmetricSecurityKey(validKey), - SaveSigninToken = true - }); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.BootstrapContext.Should().Be(expectedToken); - } - - [Fact] - public void CreateAndValidateToken_WithSecurityTokenDescriptor_ExpectCorrectBrancaTimestampAndNoIatClaim() - { - const string issuer = "me"; - const string audience = "you"; - const string subject = "123"; - var expires = DateTime.UtcNow.AddDays(1); - var notBefore = DateTime.UtcNow; - - var handler = new BrancaTokenHandler(); - - var token = handler.CreateToken(new SecurityTokenDescriptor - { - Issuer = issuer, - Audience = audience, - Expires = expires, - NotBefore = notBefore, - Claims = new Dictionary {{"sub", subject}}, - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(validKey), ExtendedSecurityAlgorithms.XChaCha20Poly1305) - }); - - var validatedToken = handler.ValidateToken(token, new TokenValidationParameters - { - ValidIssuer = issuer, - ValidAudience = audience, - TokenDecryptionKey = new SymmetricSecurityKey(validKey) - }); - - validatedToken.IsValid.Should().BeTrue(); - validatedToken.ClaimsIdentity.Claims.Should().Contain( - x => x.Type == "sub" && x.Value == subject); - - var brancaToken = (BrancaSecurityToken) validatedToken.SecurityToken; - brancaToken.Issuer.Should().Be(issuer); - brancaToken.Audiences.Should().Contain(audience); - brancaToken.Subject.Should().Be(subject); - brancaToken.IssuedAt.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); - brancaToken.ValidFrom.Should().BeCloseTo(notBefore, TimeSpan.FromSeconds(1)); - brancaToken.ValidTo.Should().BeCloseTo(expires, TimeSpan.FromSeconds(1)); - } - - [Fact] - public void GetBrancaDecryptionKeys_WheInvalidKeysInParameters_ExpectInvalidKeysRemoved() - { - var expectedKey = new byte[32]; - new Random().NextBytes(expectedKey); - - var handler = new TestBrancaTokenHandler(); - var keys = handler.GetBrancaDecryptionKeys("test", new TokenValidationParameters - { - TokenDecryptionKeyResolver = (token, securityToken, kid, parameters) => new List(), - TokenDecryptionKey = new SymmetricSecurityKey(expectedKey), - TokenDecryptionKeys = new[] {new RsaSecurityKey(RSA.Create())} - }).ToList(); - - keys.Count.Should().Be(1); - keys.Should().Contain(x => x.Key.SequenceEqual(expectedKey)); - } - - [Fact] - public void IsValidKey_WhenKeyIsNot32Bytes_ExpectFalse() - { - var key = new byte[16]; - new Random().NextBytes(key); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenKeyIsValid_ExpectTrue() - { - var key = new byte[32]; - new Random().NextBytes(key); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - - isValidKey.Should().BeTrue(); - } - - [Fact] - public void IsValidKey_WhenSecurityKeyIsNot32Bytes_ExpectFalse() - { - var keyBytes = new byte[16]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenSecurityKeyIsNotSymmetricSecurityKey_ExpectFalse() - { - var key = new RsaSecurityKey(RSA.Create()); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenSecurityKeyIsValid_ExpectTrue() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(key); - - isValidKey.Should().BeTrue(); - } - - [Fact] - public void IsValidKey_WhenEcryptingCredentialsKeyIsNot32Bytes_ExpectFalse() - { - var keyBytes = new byte[16]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenEcryptingCredentialsHasKeyWrappingSet_ExpectFalse() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials( - key, - SecurityAlgorithms.Aes256KeyWrap, - ExtendedSecurityAlgorithms.XChaCha20Poly1305); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenEcryptingCredentialsHasIncorrectEncryptionAlgorithm_ExpectFalse() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, SecurityAlgorithms.Aes128Encryption); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - - isValidKey.Should().BeFalse(); - } - - [Fact] - public void IsValidKey_WhenEcryptingCredentialsIsValid_ExpectTrue() - { - var keyBytes = new byte[32]; - new Random().NextBytes(keyBytes); - var key = new SymmetricSecurityKey(keyBytes); - var credentials = new EncryptingCredentials(key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - - var isValidKey = new TestBrancaTokenHandler().IsValidKey(credentials); - - isValidKey.Should().BeTrue(); - } - - private static byte[] CreateTestPayload() - { - var payload = new byte[32]; - RandomNumberGenerator.Fill(payload); - return payload; - } -} - -public class TestBrancaTokenHandler : BrancaTokenHandler -{ - public new IEnumerable GetBrancaDecryptionKeys(string token, TokenValidationParameters validationParameters) - => base.GetBrancaDecryptionKeys(token, validationParameters); - - public new bool IsValidKey(byte[] key) => base.IsValidKey(key); - public new bool IsValidKey(SecurityKey key) => base.IsValidKey(key); - public new bool IsValidKey(EncryptingCredentials credentials) => base.IsValidKey(credentials); -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs deleted file mode 100644 index 82c1d25..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/BrancaTokenTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Security.Cryptography; -using FluentAssertions; -using ScottBrady.IdentityModel.Tokens.Branca; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; - -public class BrancaTokenTests -{ - [Fact] - public void ctor_ExpectPropertiesSet() - { - var payload = new byte[32]; - RandomNumberGenerator.Fill(payload); - const uint timestamp = uint.MinValue; - - var token = new BrancaToken(payload, timestamp); - - token.Payload.Should().BeEquivalentTo(payload); - token.BrancaFormatTimestamp.Should().Be(timestamp); - token.Timestamp.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); - } - - [Fact] - public void GetDateTime_WhenTimestampIsZero_ExpectUnixTimeStart() - { - const uint timestamp = 0; - - var dateTime = BrancaToken.GetDateTime(timestamp); - - dateTime.Should().Be(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); - } - - [Fact] - public void GetDateTime_WhenTimestampIs27November_ExpectCorrectDateTime() - { - const uint timestamp = 123206400; - - var dateTime = BrancaToken.GetDateTime(timestamp); - - dateTime.Should().Be(new DateTime(1973, 11, 27, 0, 0, 0, DateTimeKind.Utc)); - } - - [Fact] - public void GetDateTime_WhenTimestampIsMaxValue_ExpectCorrectDateTime() - { - const uint timestamp = uint.MaxValue; - - var dateTime = BrancaToken.GetDateTime(timestamp); - - dateTime.Should().Be(new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc)); - } - - [Fact] - public void GetBrancaTimestamp_WhenDateBeforeUnixTimeStart_ExpectException() - { - Assert.Throws(() - => BrancaToken.GetBrancaTimestamp(new DateTime(1969, 01, 01))); - } - - [Fact] - public void GetBrancaTimestamp_WhenUnixTimeStart_ExpectZero() - { - var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc)); - - timestamp.Should().Be(uint.MinValue); - } - - [Fact] - public void GetBrancaTimestamp_When27November_ExpectZero() - { - var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(1973, 11, 27, 0, 0, 0, DateTimeKind.Utc)); - - timestamp.Should().Be(123206400); - } - - [Fact] - public void GetBrancaTimestamp_WhenMaxTimestamp_ExpectUintMax() - { - var timestamp = BrancaToken.GetBrancaTimestamp(new DateTime(2106, 02, 07, 06, 28, 15, DateTimeKind.Utc)); - - timestamp.Should().Be(uint.MaxValue); - } - - [Fact] - public void GetBrancaTimestamp_WhenAfterMaxTimestamp_ExpectInvalidOperationException() - { - Assert.Throws(() - => BrancaToken.GetBrancaTimestamp(new DateTime(2106, 02, 07, 06, 28, 16, DateTimeKind.Utc))); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs deleted file mode 100644 index 8325caa..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/BrancaTestVectors.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text.Json.Nodes; -using FluentAssertions; -using ScottBrady.IdentityModel.Tokens.Branca; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Branca; - -/// -/// Test vectors from https://github.com/tuupola/branca-spec -/// -public class BrancaTestVectors -{ - public static readonly TheoryData EncodingTestVectors = new TheoryData(); - public static readonly TheoryData DecodingTestVectors = new TheoryData(); - - static BrancaTestVectors() - { - var file = File.OpenRead("Tokens/Branca/TestVectors/testvectors.json"); - var data = JsonNode.Parse(file); - if (data == null) throw new Exception("Failed to load test vectors"); - - var testGroups = data["testGroups"].AsArray(); - var encodingTestVectors = testGroups.FirstOrDefault(x => x["testType"]?.GetValue() == "encoding"); - var decodingTestVectors = testGroups.FirstOrDefault(x => x["testType"]?.GetValue() == "decoding"); - - foreach (var testVector in encodingTestVectors?["tests"]?.AsArray() ?? throw new Exception("Failed to load encoding` test vectors")) - { - EncodingTestVectors.Add(new BrancaTestVector(testVector)); - } - foreach (var testVector in decodingTestVectors?["tests"]?.AsArray() ?? throw new Exception("Failed to load decoding test vectors")) - { - DecodingTestVectors.Add(new BrancaTestVector(testVector)); - } - } - - [Theory, MemberData(nameof(EncodingTestVectors))] - public void CreateToken_ExpectCorrectResult(BrancaTestVector testVector) - { - var handler = new TestBrancaTokenHandler {Nonce = testVector.Nonce}; - var token = handler.CreateToken(testVector.Message, testVector.TimeStamp, testVector.Key); - - token.Should().Be(testVector.Token); - } - - [Theory, MemberData(nameof(DecodingTestVectors))] - public void ValidateToken_ExpectCorrectResult(BrancaTestVector testVector) - { - var handler = new BrancaTokenHandler(); - - BrancaToken result = null; - Exception exception = null; - try - { - result = handler.DecryptToken(testVector.Token, testVector.Key); - } - catch (Exception e) - { - exception = e; - } - - if (testVector.IsValid) - { - result.Should().NotBeNull(); - exception.Should().BeNull(); - - result.Payload.Should().BeEquivalentTo(testVector.Message); - result.Timestamp.Should().Be(BrancaToken.GetDateTime(testVector.TimeStamp)); - } - else - { - result.Should().BeNull(); - exception.Should().NotBeNull(); - } - } - - public class TestBrancaTokenHandler : BrancaTokenHandler - { - public byte[] Nonce { get; set; } - protected override byte[] GenerateNonce() => Nonce ?? base.GenerateNonce(); - } - - public class BrancaTestVector - { - public BrancaTestVector(JsonNode data) - { - Id = data["id"]!.GetValue(); - Comment = data["comment"]?.GetValue(); - Token = data["token"]?.GetValue(); - TimeStamp = data["timestamp"]?.GetValue() ?? throw new Exception("Unable to parse timestamp"); - IsValid = data["isValid"]?.GetValue() ?? throw new Exception("Unable to parse isValid"); - - var messageHex = data["msg"]?.GetValue(); - if (!string.IsNullOrWhiteSpace(messageHex)) Message = Base16.Decode(messageHex); - else Message = Array.Empty(); - - var nonceHex = data["nonce"]?.GetValue(); - if (!string.IsNullOrEmpty(nonceHex)) Nonce = Base16.Decode(nonceHex); - - var keyHex = data["key"]?.GetValue() ?? throw new Exception("Failed to find key"); - Key = Base16.Decode(keyHex); - } - - public int Id { get; } - public string Comment { get; } - public byte[] Key { get; } - public byte[] Nonce { get; } - public uint TimeStamp { get; } - public string Token { get; } - public byte[] Message { get; } - public bool IsValid { get; } - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/testvectors.json b/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/testvectors.json deleted file mode 100644 index e7e1bb4..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Branca/TestVectors/testvectors.json +++ /dev/null @@ -1,268 +0,0 @@ -{ - "version": "0.3.0", - "numberOfTests": 25, - "testGroups": [ - { - "testType": "encoding", - "tests": [ - { - "id": 0, - "comment": "Hello world with zero timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 0, - "token": "870S4BYxgHw0KnP3W9fgVUHEhT5g86vJ17etaC5Kh5uIraWHCI1psNQGv298ZmjPwoYbjDQ9chy2z", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 1, - "comment": "Hello world with max timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 4294967295, - "token": "89i7YCwu5tWAJNHUDdmIqhzOi5hVHOd4afjZcGMcVmM4enl4yeLiDyYv41eMkNmTX6IwYEFErCSqr", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 2, - "comment": "Hello world with November 27 timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 123206400, - "token": "875GH23U0Dr6nHFA63DhOyd9LkYudBkX8RsCTOMz5xoYAMw9sMd5QwcEqLDRnTDHPenOX7nP2trlT", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 3, - "comment": "Eight null bytes with zero timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 0, - "token": "1jIBheHbDdkCDFQmtgw4RUZeQoOJgGwTFJSpwOAk3XYpJJr52DEpILLmmwYl4tjdSbbNqcF1", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 4, - "comment": "Eight null bytes with max timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 4294967295, - "token": "1jrx6DUu5q06oxykef2e2ZMyTcDRTQot9ZnwgifUtzAphGtjsxfbxXNhQyBEOGtpbkBgvIQx", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 5, - "comment": "Eight null bytes with November 27th timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 123206400, - "token": "1jJDJOEjuwVb9Csz1Ypw1KBWSkr0YDpeBeJN6NzJWx1VgPLmcBhu2SbkpQ9JjZ3nfUf7Aytp", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 6, - "comment": "Empty payload", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 0, - "token": "4sfD0vPFhIif8cy4nB3BQkHeJqkOkDvinI4zIhMjYX4YXZU5WIq9ycCVjGzB5", - "msg": "", - "isValid": true - }, - { - "id": 7, - "comment": "Non-UTF8 payload", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": "beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef", - "timestamp": 123206400, - "token": "K9u6d0zjXp8RXNUGDyXAsB9AtPo60CD3xxQ2ulL8aQoTzXbvockRff0y1eXoHm", - "msg": "80", - "isValid": true - } - ] - }, - { - "testType": "decoding", - "tests": [ - { - "id": 8, - "comment": "Hello world with zero timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "870S4BYxgHw0KnP3W9fgVUHEhT5g86vJ17etaC5Kh5uIraWHCI1psNQGv298ZmjPwoYbjDQ9chy2z", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 9, - "comment": "Hello world with max timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 4294967295, - "token": "89i7YCwu5tWAJNHUDdmIqhzOi5hVHOd4afjZcGMcVmM4enl4yeLiDyYv41eMkNmTX6IwYEFErCSqr", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 10, - "comment": "Hello world with November 27 timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 123206400, - "token": "875GH23U0Dr6nHFA63DhOyd9LkYudBkX8RsCTOMz5xoYAMw9sMd5QwcEqLDRnTDHPenOX7nP2trlT", - "msg": "48656c6c6f20776f726c6421", - "isValid": true - }, - { - "id": 11, - "comment": "Eight null bytes with zero timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "1jIBheHbDdkCDFQmtgw4RUZeQoOJgGwTFJSpwOAk3XYpJJr52DEpILLmmwYl4tjdSbbNqcF1", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 12, - "comment": "Eight null bytes with max timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 4294967295, - "token": "1jrx6DUu5q06oxykef2e2ZMyTcDRTQot9ZnwgifUtzAphGtjsxfbxXNhQyBEOGtpbkBgvIQx", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 13, - "comment": "Eight null bytes with November 27th timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 123206400, - "token": "1jJDJOEjuwVb9Csz1Ypw1KBWSkr0YDpeBeJN6NzJWx1VgPLmcBhu2SbkpQ9JjZ3nfUf7Aytp", - "msg": "0000000000000000", - "isValid": true - }, - { - "id": 14, - "comment": "Empty payload", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "4sfD0vPFhIif8cy4nB3BQkHeJqkOkDvinI4zIhMjYX4YXZU5WIq9ycCVjGzB5", - "msg": "", - "isValid": true - }, - { - "id": 15, - "comment": "Non-UTF8 payload", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 123206400, - "token": "K9u6d0zjXp8RXNUGDyXAsB9AtPo60CD3xxQ2ulL8aQoTzXbvockRff0y1eXoHm", - "msg": "80", - "isValid": true - }, - { - "id": 16, - "comment": "Wrong version 0xBB", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "89mvl3RkwXjpEj5WMxK7GUDEHEeeeZtwjMIOogTthvr44qBfYtQSIZH5MHOTC0GzoutDIeoPVZk3w", - "msg": "", - "isValid": false - }, - { - "id": 17, - "comment": "Invalid base62 characters", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 123206400, - "token": "875GH23U0Dr6nHFA63DhOyd9LkYudBkX8RsCTOMz5xoYAMw9sMd5QwcEqLDRnTDHPenOX7nP2trlT_", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - { - "id": 18, - "comment": "Modified version", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "89mvl3S0BE0UCMIY94xxIux4eg1w5oXrhvCEXrDAjusSbO0Yk7AU6FjjTnbTWTqogLfNPJLzecHVb", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - { - "id": 19, - "comment": "Modified first byte of the nonce", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "875GH233SUysT7fQ711EWd9BXpwOjB72ng3ZLnjWFrmOqVy49Bv93b78JU5331LbcY0EEzhLfpmSx", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - - { - "id": 20, - "comment": "Modified timestamp", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "870g1RCk4lW1YInhaU3TP8u2hGtfol16ettLcTOSoA0JIpjCaQRW7tQeP6dQmTvFIB2s6wL5deMXr", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - - { - "id": 21, - "comment": "Modified last byte of the ciphertext", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "875GH23U0Dr6nHFA63DhOyd9LkYudBkX8RsCTOMz5xoYAMw9sMd5Qw6Jpo96myliI3hHD7VbKZBYh", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - { - "id": 22, - "comment": "Modified last byte of the Poly1305 tag", - "key": "73757065727365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "875GH23U0Dr6nHFA63DhOyd9LkYudBkX8RsCTOMz5xoYAMw9sMd5QwcEqLDRnTDHPenOX7nP2trk0", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - { - "id": 23, - "comment": "Wrong key", - "key": "77726f6e677365637265746b6579796f7573686f756c646e6f74636f6d6d6974", - "nonce": null, - "timestamp": 0, - "token": "870S4BYxgHw0KnP3W9fgVUHEhT5g86vJ17etaC5Kh5uIraWHCI1psNQGv298ZmjPwoYbjDQ9chy2z", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - }, - { - "id": 24, - "comment": "Invalid key", - "key": "746f6f73686f72746b6579", - "nonce": null, - "timestamp": 0, - "token": "870S4BYxgHw0KnP3W9fgVUHEhT5g86vJ17etaC5Kh5uIraWHCI1psNQGv298ZmjPwoYbjDQ9chy2z", - "msg": "48656c6c6f20776f726c6421", - "isValid": false - } - ] - } - ] -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs deleted file mode 100644 index 10d303b..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadExtensionsTests.cs +++ /dev/null @@ -1,319 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Linq; -using ScottBrady.IdentityModel.Tokens; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens; - -public class JwtPayloadExtensionsTests -{ - [Fact] - public void ToJwtPayload_WhenTokenDescriptorIsNull_ExpectArgumentNullException() - { - SecurityTokenDescriptor descriptor = null; - Assert.Throws(() => descriptor.ToJwtPayload()); - } - - [Fact] - public void ToJwtPayload_WhenMultipleSubjectClaimsOfSameType_ExpectJsonArray() - { - const string claimType = "email"; - var claimValues = new[] {"bob@test", "alice@test"}; - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List - { - new Claim(claimType, claimValues[0]), - new Claim(claimType, claimValues[1]) - }, "test"); - - var jwtPayload = descriptor.ToJwtPayload(); - - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Values().Should().Contain(claimValues); - } - - [Fact] - public void ToJwtPayload_WhenMultipleClaimsOfSameType_ExpectJsonArray() - { - const string claimType = "email"; - var claimValues = new[] {"bob@test", "alice@test"}; - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary - { - {claimType, claimValues} - }; - - var jwtPayload = descriptor.ToJwtPayload(); - - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Values().Should().Contain(claimValues); - } - - [Fact] - public void ToJwtPayload_WhenSubjectAndClaimsContainDuplicateTypes_ExpecSubjectClaimsReplaced() - { - var claimType = Guid.NewGuid().ToString(); - var expectedClaimValue = Guid.NewGuid().ToString(); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim(claimType, Guid.NewGuid().ToString())}); - descriptor.Claims = new Dictionary {{claimType, expectedClaimValue}}; - - var jwtPayload = descriptor.ToJwtPayload(); - - var claims = JObject.Parse(jwtPayload)[claimType]; - claims.Value().Should().Contain(expectedClaimValue); - } - - [Fact] - public void ToJwtPayload_WhenIssuerSet_ExpectIssuerClaim() - { - var descriptor = new SecurityTokenDescriptor(); - descriptor.Issuer = "me"; - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(descriptor.Issuer); - } - - [Fact] - public void ToJwtPayload_WhenIssuerSetInSubject_ExpectSubjectIssuerClaim() - { - var expectedIssuer = Guid.NewGuid().ToString(); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim("iss", expectedIssuer)}); - descriptor.Issuer = Guid.NewGuid().ToString(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(expectedIssuer); - } - - [Fact] - public void ToJwtPayload_WhenIssuerSetInClaims_ExpectClaimsIssuerClaim() - { - var expectedIssuer = Guid.NewGuid().ToString(); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary {{"iss", expectedIssuer}}; - descriptor.Issuer = Guid.NewGuid().ToString(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuer = JObject.Parse(jwtPayload)["iss"]; - issuer.Value().Should().Be(expectedIssuer); - } - - [Fact] - public void ToJwtPayload_WhenAudienceSet_ExpectAudienceClaim() - { - var descriptor = new SecurityTokenDescriptor(); - descriptor.Audience = "you"; - - var jwtPayload = descriptor.ToJwtPayload(); - - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(descriptor.Audience); - } - - [Fact] - public void ToJwtPayload_WhenAudienceSetInSubject_ExpectSubjectAudienceClaim() - { - var expectedAudience = Guid.NewGuid().ToString(); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Subject = new ClaimsIdentity(new List {new Claim("aud", expectedAudience)}); - descriptor.Audience = Guid.NewGuid().ToString(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(expectedAudience); - } - - [Fact] - public void ToJwtPayload_WhenAudienceSetInClaims_ExpectClaimsAudienceClaim() - { - var expectedAudience = Guid.NewGuid().ToString(); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary {{"aud", expectedAudience}}; - descriptor.Audience = Guid.NewGuid().ToString(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var audience = JObject.Parse(jwtPayload)["aud"]; - audience.Value().Should().Be(expectedAudience); - } - - [Fact] - public void ToJwtPayload_WhenExpiryNotSet_ExpectExpirySetToOneHour() - { - var descriptor = new SecurityTokenDescriptor(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().BeCloseTo( - (long) (EpochTime.GetIntDate(DateTime.UtcNow) + TimeSpan.FromMinutes(60).TotalSeconds), - 10); - } - - [Fact] - public void ToJwtPayload_WhenExpirySet_ExpectExpiryClaim() - { - var expectedExpiry = DateTime.UtcNow.AddMinutes(5); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Expires = expectedExpiry; - - var jwtPayload = descriptor.ToJwtPayload(); - - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().Be(EpochTime.GetIntDate(expectedExpiry)); - } - - [Fact] - public void ToJwtPayload_WhenExpirySetInClaims_ExpectClaimsExpiryClaim() - { - var expectedExpiry = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"exp", expectedExpiry}}; - descriptor.Expires = DateTime.UtcNow.AddHours(42); - - var jwtPayload = descriptor.ToJwtPayload(); - - var expiry = JObject.Parse(jwtPayload)["exp"]; - expiry.Value().Should().Be(expectedExpiry); - } - - [Fact] - public void ToJwtPayload_WhenIssuedAtNotSet_ExpectIssuedAtSetToNow() - { - var descriptor = new SecurityTokenDescriptor(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); - } - - [Fact] - public void ToJwtPayload_WhenIssuedAtSet_ExpectIssuedAtClaim() - { - var expectedIssuedAt = DateTime.UtcNow.AddMinutes(5); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.IssuedAt = expectedIssuedAt; - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().Be(EpochTime.GetIntDate(expectedIssuedAt)); - } - - [Fact] - public void ToJwtPayload_WhenIssuedAtSetInClaims_ExpectClaimsIssuedAtClaim() - { - var expectedIssuedAt = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"iat", expectedIssuedAt}}; - descriptor.IssuedAt = DateTime.UtcNow.AddHours(42); - - var jwtPayload = descriptor.ToJwtPayload(); - - var issuedAt = JObject.Parse(jwtPayload)["iat"]; - issuedAt.Value().Should().Be(expectedIssuedAt); - } - - [Fact] - public void ToJwtPayload_WhenNotBeforeNotSet_ExpectNotBeforeSetToNow() - { - var descriptor = new SecurityTokenDescriptor(); - - var jwtPayload = descriptor.ToJwtPayload(); - - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().BeCloseTo(EpochTime.GetIntDate(DateTime.UtcNow), 10); - } - - [Fact] - public void ToJwtPayload_WhenNotBeforeSet_ExpectNotBeforeClaim() - { - var expectedNotBefore = DateTime.UtcNow.AddMinutes(5); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.NotBefore = expectedNotBefore; - - var jwtPayload = descriptor.ToJwtPayload(); - - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().Be(EpochTime.GetIntDate(expectedNotBefore)); - } - - [Fact] - public void ToJwtPayload_WhenNotBeforeSetInClaims_ExpectClaimsNotBeforeClaim() - { - var expectedNotBefore = EpochTime.GetIntDate(DateTime.UtcNow.AddMinutes(5)); - - var descriptor = new SecurityTokenDescriptor(); - descriptor.Claims = new Dictionary{{"nbf", expectedNotBefore}}; - descriptor.NotBefore = DateTime.UtcNow.AddHours(42); - - var jwtPayload = descriptor.ToJwtPayload(); - - var notBefore = JObject.Parse(jwtPayload)["nbf"]; - notBefore.Value().Should().Be(expectedNotBefore); - } - - [Fact] - public void ToJwtPayload_WhenIsoDateFormat_ExpectNoOverencoding() - { - var descriptor = new SecurityTokenDescriptor(); - - var jwtPayload = descriptor.ToJwtPayload(JwtDateTimeFormat.Iso); - - var expiry = jwtPayload.Should().NotContain(@"\u002B"); - } - - [Fact] - public void ToJwtClaimDictionary_WhenClaimTypeHasSingleValue_ExpectSingleClaim() - { - var claim = new Claim("email", "bob@test"); - - var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List {claim}); - - var values = dictionary[claim.Type]; - values.ToString().Should().Be(claim.Value); - } - - [Fact] - public void ToJwtClaimDictionary_WhenClaimTypeHasMultipleValues_ExpectEntryWithArrayValue() - { - const string claimType = "email"; - const string value1 = "bob@test"; - const string value2 = "alice@test"; - - var dictionary = JwtPayloadExtensions.ToJwtClaimDictionary(new List - { - new Claim(claimType, value1), new Claim(claimType, value2) - }); - - var entry = dictionary[claimType]; - var values = entry as IList; - - values.Should().Contain(value1); - values.Should().Contain(value2); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs deleted file mode 100644 index 9e312aa..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadSecurityTokenTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; -using ScottBrady.IdentityModel.Tokens; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens; - -public class JwtPayloadSecurityTokenTests -{ - [Fact] - public void ctor_WhenTokenPayloadIsNotJson_ExpectArgumentException() - => Assert.Throws(() => new TestJwtPayloadSecurityToken("")); - - [Fact] - public void ctor_WhenTokenContainsJson_ExpectJwtClaimsParsed() - { - var jwt = new - { - jti = "xyz", - iss = "me", - aud = "you", - sub = "123", - actort = "them", // 🤷‍ - iat = 1588341409, - nbf = 1588341410, - exp = 1588341499 - }; - - var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); - - token.Id.Should().Be(jwt.jti); - token.Issuer.Should().Be(jwt.iss); - token.Audiences.Should().Contain(jwt.aud); - token.Subject.Should().Be(jwt.sub); - token.Actor.Should().Be(jwt.actort); - token.IssuedAt.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.iat).UtcDateTime); - token.ValidFrom.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.nbf).UtcDateTime); - token.ValidTo.Should().Be(DateTimeOffset.FromUnixTimeSeconds(jwt.exp).UtcDateTime); - } - - [Fact] - public void ctor_WhenTokenContainsJson_ExpectClaimsParsed() - { - var jwt = new - { - jti = "xyz", - iss = "me", - aud = "you", - sub = "123", - actort = "them", - iat = 1588341409, - nbf = 1588341410, - exp = 1588341499 - }; - - var token = new TestJwtPayloadSecurityToken(JsonConvert.SerializeObject(jwt)); - - token.Claims.Should().Contain(x => x.Type == "jti" && x.Value == jwt.jti); - token.Claims.Should().Contain(x => x.Type == "iss" && x.Value == jwt.iss); - token.Claims.Should().Contain(x => x.Type == "aud" && x.Value == jwt.aud); - token.Claims.Should().Contain(x => x.Type == "sub" && x.Value == jwt.sub); - token.Claims.Should().Contain(x => x.Type == "actort" && x.Value == jwt.actort); - token.Claims.Should().Contain(x => x.Type == "iat" && x.Value == jwt.iat.ToString()); - token.Claims.Should().Contain(x => x.Type == "nbf" && x.Value == jwt.nbf.ToString()); - token.Claims.Should().Contain(x => x.Type == "exp" && x.Value == jwt.exp.ToString()); - } -} - -internal class TestJwtPayloadSecurityToken : JwtPayloadSecurityToken -{ - public TestJwtPayloadSecurityToken(string payload) : base(payload) - { - } - - public override SecurityKey SecurityKey { get; } - public override SecurityKey SigningKey { get; set; } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs deleted file mode 100644 index 15675c9..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/JwtPayloadTokenHandlerTests.cs +++ /dev/null @@ -1,373 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Cryptography; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using Moq; -using Moq.Protected; -using ScottBrady.IdentityModel.Tokens; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens; - -public class JwtPayloadTokenHandlerTests -{ - [Fact] - public void CreateClaimsIdentity_WhenTokenIsNull_ExpectArgumentNullException() - { - var handler = new TestJwtPayloadTokenHandler(); - Assert.Throws(() => handler.TestCreateClaimsIdentity(null, new TokenValidationParameters())); - } - - [Fact] - public void CreateClaimsIdentity_WhenTokenValidationParametersAreNull_ExpectArgumentNullException() - { - var handler = new TestJwtPayloadTokenHandler(); - Assert.Throws(() => handler.TestCreateClaimsIdentity(new MockableJwtPayloadSecurityToken(), null)); - } - - [Fact] - public void CreateClaimsIdentity_WhenTokenHasIssuer_ExpectClaimsToUseTokenIdentity() - { - const string expectedIssuer = "ids"; - var mockToken = new Mock(); - mockToken.Setup(x => x.Issuer).Returns(expectedIssuer); - mockToken.Setup(x => x.Claims).Returns(new List {new Claim("sub", "123")}); - - var handler = new TestJwtPayloadTokenHandler(); - - var identity = handler.TestCreateClaimsIdentity(mockToken.Object, new TokenValidationParameters()); - - identity.Claims.All(x => x.Issuer == expectedIssuer).Should().BeTrue(); - identity.Claims.All(x => x.OriginalIssuer == expectedIssuer).Should().BeTrue(); - } - - [Fact] - public void CreateClaimsIdentity_WhenTokenHasNoIssuer_ExpectClaimsToUseDefaultIssuer() - { - var mockToken = new Mock(); - mockToken.Setup(x => x.Issuer).Returns((string) null); - mockToken.Setup(x => x.Claims).Returns(new List {new Claim("sub", "123")}); - - var handler = new TestJwtPayloadTokenHandler(); - - var identity = handler.TestCreateClaimsIdentity(mockToken.Object, new TokenValidationParameters()); - - identity.Claims.All(x => x.Issuer == ClaimsIdentity.DefaultIssuer).Should().BeTrue(); - identity.Claims.All(x => x.OriginalIssuer == ClaimsIdentity.DefaultIssuer).Should().BeTrue(); - } - - [Fact] - public void CreateClaimsIdentity_WhenTokenHasClaimsWithProperties_ExpectPropertiesPersisted() - { - var expectedProperty = new KeyValuePair("test", "test_val"); - var claimWithProperty = new Claim("sub", "123") {Properties = {expectedProperty}}; - - var mockToken = new Mock(); - mockToken.Setup(x => x.Issuer).Returns((string) null); - mockToken.Setup(x => x.Claims).Returns(new List {claimWithProperty}); - - var handler = new TestJwtPayloadTokenHandler(); - - var identity = handler.TestCreateClaimsIdentity(mockToken.Object, new TokenValidationParameters()); - - var mappedClaim = identity.Claims.Single(x => x.Type == claimWithProperty.Type && x.Value == claimWithProperty.Value); - mappedClaim.Properties.Should().Contain(expectedProperty); - } - - [Fact] - public void ValidateTokenPayload_WhenTokenIsNull_ExpectResultWithArgumentNullException() - { - var result = new TestJwtPayloadTokenHandler().TestValidateTokenPayload(null, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateTokenPayload_WhenTokenValidationParametersAreNull_ExpectResultWithArgumentNullException() - { - var token = CreateMockToken().Object; - - var result = new TestJwtPayloadTokenHandler().TestValidateTokenPayload(token, null); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateTokenPayload_WhenInvalidLifetime_ExpectFailureResultWithCorrectException() - { - var expectedException = new InvalidOperationException("correct error"); - - var token = CreateMockToken().Object; - var mockHandler = CreateMockHandler(); - mockHandler.Protected() - .Setup("ValidateLifetime", - ItExpr.IsAny(), - ItExpr.IsAny(), - ItExpr.IsAny(), - ItExpr.IsAny()) - .Throws(expectedException); - - var result = mockHandler.Object.TestValidateTokenPayload(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateTokenPayload_WhenInvalidAudience_ExpectFailureResultWithCorrectException() - { - var expectedException = new InvalidOperationException("correct error"); - - var token = CreateMockToken().Object; - var mockHandler = CreateMockHandler(); - mockHandler.Protected() - .Setup("ValidateAudience", - ItExpr.IsAny>(), - ItExpr.IsAny(), - ItExpr.IsAny()) - .Throws(expectedException); - - var result = mockHandler.Object.TestValidateTokenPayload(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateTokenPayload_WhenInvalidIssuer_ExpectFailureResultWithCorrectException() - { - var expectedException = new InvalidOperationException("correct error"); - - var token = CreateMockToken().Object; - var mockHandler = CreateMockHandler(); - mockHandler.Protected() - .Setup("ValidateIssuer", - ItExpr.IsAny(), - ItExpr.IsAny(), - ItExpr.IsAny()) - .Throws(expectedException); - - var result = mockHandler.Object.TestValidateTokenPayload(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateTokenPayload_WhenTokenReplay_ExpectFailureResultWithCorrectException() - { - var expectedException = new InvalidOperationException("correct error"); - - var token = CreateMockToken().Object; - var mockHandler = CreateMockHandler(); - mockHandler.Protected() - .Setup("ValidateTokenReplay", - ItExpr.IsAny(), - ItExpr.IsAny(), - ItExpr.IsAny()) - .Throws(expectedException); - - var result = mockHandler.Object.TestValidateTokenPayload(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateTokenPayload_WhenValidToken_ExpectSuccessResult() - { - var expectedIdentity = new ClaimsIdentity("test"); - - var token = CreateMockToken().Object; - var mockHandler = CreateMockHandler(); - mockHandler.Protected() - .Setup("CreateClaimsIdentity", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(expectedIdentity); - - var result = mockHandler.Object.TestValidateTokenPayload(token, new TokenValidationParameters()); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.Should().Be(expectedIdentity); - result.SecurityToken.Should().Be(token); - } - - [Fact] - public void GetDecryptionKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() - { - var expectedKey = new RsaSecurityKey(RSA.Create()); - - var handler = new TestJwtPayloadTokenHandler(); - var keys = handler.TestGetDecryptionKeys("test", new TokenValidationParameters - { - TokenDecryptionKeyResolver = (token, securityToken, kid, parameters) => new[] {expectedKey}, - TokenDecryptionKey = new RsaSecurityKey(RSA.Create()) - }).ToList(); - - keys.Count.Should().Be(1); - keys.Should().Contain(expectedKey); - } - - [Fact] - public void GetDecryptionKeys_WheKeysInParameters_ExpectAllKeys() - { - var expectedKey1 = new RsaSecurityKey(RSA.Create()); - var expectedKey2 = new RsaSecurityKey(RSA.Create()); - - var handler = new TestJwtPayloadTokenHandler(); - var keys = handler.TestGetDecryptionKeys("test", new TokenValidationParameters - { - TokenDecryptionKeyResolver = (token, securityToken, kid, parameters) => new List(), - TokenDecryptionKey = expectedKey1, - TokenDecryptionKeys = new[] {expectedKey2} - }).ToList(); - - keys.Count.Should().Be(2); - keys.Should().Contain(expectedKey1); - keys.Should().Contain(expectedKey2); - } - - [Fact] - public void GetSigningKeys_WhenKeyResolverReturnsKey_ExpectKeyFromResolver() - { - var expectedKey = new RsaSecurityKey(RSA.Create()); - - var handler = new TestJwtPayloadTokenHandler(); - var keys = handler.TestGetSigningKeys("test", new TokenValidationParameters - { - IssuerSigningKeyResolver = (token, securityToken, kid, parameters) => new[] {expectedKey}, - IssuerSigningKey = new RsaSecurityKey(RSA.Create()) - }).ToList(); - - keys.Count.Should().Be(1); - keys.Should().Contain(expectedKey); - } - - [Fact] - public void GetSigningKeys_WheKeysInParameters_ExpectAllKeys() - { - var expectedKey1 = new RsaSecurityKey(RSA.Create()); - var expectedKey2 = new RsaSecurityKey(RSA.Create()); - - var handler = new TestJwtPayloadTokenHandler(); - var keys = handler.TestGetSigningKeys("test", new TokenValidationParameters - { - IssuerSigningKeyResolver = (token, securityToken, kid, parameters) => new List(), - IssuerSigningKey = expectedKey1, - IssuerSigningKeys = new[] {expectedKey2} - }).ToList(); - - keys.Count.Should().Be(2); - keys.Should().Contain(expectedKey1); - keys.Should().Contain(expectedKey2); - } - - [Fact] - public void ValidateToken_ISecurityTokenValidator_WhenSuccess_ExpectInnerTokenAndIdentity() - { - var token = Guid.NewGuid().ToString(); - var validationParameters = new TokenValidationParameters {ValidIssuer = Guid.NewGuid().ToString()}; - - var expectedIdentity = new ClaimsIdentity(new List {new Claim("sub", "123")}, "test"); - var expectedSecurityToken = new MockableJwtPayloadSecurityToken(); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Setup(x => x.ValidateToken(token, validationParameters)) - .Returns(new TokenValidationResult - { - IsValid = true, - ClaimsIdentity = expectedIdentity, - SecurityToken = expectedSecurityToken - }); - - var claimsPrincipal = mockHandler.Object.ValidateToken(token, validationParameters, out var parsedToken); - - claimsPrincipal.Identity.Should().Be(expectedIdentity); - parsedToken.Should().Be(expectedSecurityToken); - } - - [Fact] - public void ValidateToken_ISecurityTokenValidator_WhenFailure_ExpectInnerException() - { - var token = Guid.NewGuid().ToString(); - var validationParameters = new TokenValidationParameters(); - - var expectedException = new InvalidOperationException("test"); - - var mockHandler = new Mock {CallBase = true}; - mockHandler.Setup(x => x.ValidateToken(token, validationParameters)) - .Returns(new TokenValidationResult - { - IsValid = false, - Exception = expectedException - }); - - SecurityToken parsedToken = null; - var exception = Assert.Throws( - expectedException.GetType(), - () => mockHandler.Object.ValidateToken(token, validationParameters, out parsedToken)); - - parsedToken.Should().BeNull(); - exception.Should().Be(expectedException); - } - - private static Mock CreateMockToken() - { - var mockToken = new Mock(); - - mockToken.Setup(x => x.ValidTo).Returns(null); - mockToken.Setup(x => x.ValidFrom).Returns(null); - mockToken.Setup(x => x.Audiences).Returns(new []{"you"}); - mockToken.Setup(x => x.Issuer).Returns("me"); - mockToken.Setup(x => x.TokenHash).Returns("xyz"); - - return mockToken; - } - - private static Mock CreateMockHandler() - { - var mockHandler = new Mock {CallBase = false}; - mockHandler.Setup(x => x.TestValidateTokenPayload(It.IsAny(), It.IsAny())) - .CallBase(); - mockHandler.Setup(x => x.TestCreateClaimsIdentity(It.IsAny(), It.IsAny())) - .CallBase(); - - return mockHandler; - } -} - -public class TestJwtPayloadTokenHandler : JwtPayloadTokenHandler -{ - public virtual TokenValidationResult TestValidateTokenPayload(JwtPayloadSecurityToken token, TokenValidationParameters validationParameters) - => base.ValidateTokenPayload(token, validationParameters); - public virtual ClaimsIdentity TestCreateClaimsIdentity(JwtPayloadSecurityToken jwtToken, TokenValidationParameters validationParameters) - => base.CreateClaimsIdentity(jwtToken, validationParameters); - - public virtual IEnumerable TestGetDecryptionKeys(string token, TokenValidationParameters validationParameters) - => base.GetDecryptionKeys(token, validationParameters); - - public virtual IEnumerable TestGetSigningKeys(string token, TokenValidationParameters validationParameters) - => base.GetSigningKeys(token, validationParameters); - - public override bool CanReadToken(string securityToken) - { - throw new NotImplementedException(); - } - - public override TokenValidationResult ValidateToken(string token, TokenValidationParameters validationParameters) - { - throw new NotImplementedException(); - } -} - -public class MockableJwtPayloadSecurityToken : JwtPayloadSecurityToken -{ - public override SecurityKey SecurityKey => throw new NotImplementedException(); - public override SecurityKey SigningKey { get; set; } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs deleted file mode 100644 index 79c4097..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoSecurityTokenTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using FluentAssertions; -using Newtonsoft.Json; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -public class PasetoSecurityTokenTests -{ - [Fact] - public void IssuedAt_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2038, 03, 17, 01, 02, 03, DateTimeKind.Utc); - - var jwt = new - { - iss = "me", - aud = "you", - iat = "2038-03-17T01:02:03+00:00" - }; - - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); - - token.IssuedAt.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } - - [Fact] - public void ValidFrom_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2028, 03, 17, 01, 02, 03, DateTimeKind.Utc); - - var jwt = new - { - iss = "me", - aud = "you", - nbf = "2028-03-17T01:02:03+00:00" - }; - - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); - - token.ValidFrom.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } - - [Fact] - public void ValidTo_WhenIatClaimHasIsoFormat_ExpectDateTime() - { - var expectedDateTime = new DateTime(2018, 03, 17, 01, 02, 03, DateTimeKind.Utc); - - var jwt = new - { - iss = "me", - aud = "you", - exp = "2018-03-17T01:02:03+00:00" - }; - - var innerToken = new TestPasetoToken(); - innerToken.SetPayload(JsonConvert.SerializeObject(jwt)); - var token = new PasetoSecurityToken(innerToken); - - token.ValidTo.Should().BeCloseTo(expectedDateTime, TimeSpan.FromMilliseconds(10)); - } -} - -public class TestPasetoToken : PasetoToken -{ - public new void SetPayload(string payload) => Payload = payload; -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs deleted file mode 100644 index 14eff68..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenHandlerTests.cs +++ /dev/null @@ -1,371 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using Moq; -using Moq.Protected; -using ScottBrady.IdentityModel.Crypto; -using ScottBrady.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -public class PasetoTokenHandlerTests -{ - private const string TestVersion = "test"; - private readonly Mock mockVersionStrategy = new Mock(); - - private readonly Mock mockedSut; - private readonly PasetoTokenHandler sut; - - public PasetoTokenHandlerTests() - { - mockedSut = new Mock( - new Dictionary{{TestVersion, mockVersionStrategy.Object}}) - { - CallBase = true - }; - - sut = mockedSut.Object; - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void CanReadToken_WhenTokenIsNullOrWhitespace_ExpectFalse(string token) - { - var handler = new PasetoTokenHandler(); - var canReadToken = handler.CanReadToken(token); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenTokenIsTooLong_ExpectFalse() - { - var tokenBytes = new byte[TokenValidationParameters.DefaultMaximumTokenSizeInBytes + 1]; - new Random().NextBytes(tokenBytes); - - var canReadToken = sut.CanReadToken(Convert.ToBase64String(tokenBytes)); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenTokenHasTooManySegments_ExpectFalse() - { - const string invalidToken = "ey.ey.ey.ey.ey.ey"; - - var canReadToken = sut.CanReadToken(invalidToken); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenBrancaToken_ExpectFalse() - { - const string brancaToken = "5K6Oid5pXkASEGvv63CHxpKhSX9passYQ4QhdSdCuOEnHlvBrvX414fWX6zUceAdg3DY9yTVQcmVZn0xr9lsBKBHDzOLNAGVlCs1SHlWIuFDfB8yGXO8EyNPnH9CBMueSEtNmISgcjM1ZmfmcD2EtE6"; - - var canReadToken = sut.CanReadToken(brancaToken); - - canReadToken.Should().BeFalse(); - } - - [Fact] - public void CanReadToken_WhenPasetoToken_ExpectTrue() - { - var canReadToken = sut.CanReadToken(CreateTestToken()); - - canReadToken.Should().BeTrue(); - } - - [Fact] - public void CanValidateToken_ExpectTrue() - => sut.CanValidateToken.Should().BeTrue(); - - [Fact] - public void CreateToken_WhenSecurityTokenDescriptorIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.CreateToken(null)); - - [Fact] - public void CreateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() - { - var tokenDescriptor = new PasetoSecurityTokenDescriptor("v42", PasetoConstants.Purposes.Public); - - Assert.Throws(() => sut.CreateToken(tokenDescriptor)); - } - - [Fact] - public void CreateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() - { - var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, "external"); - - Assert.Throws(() => sut.CreateToken(tokenDescriptor)); - } - - [Fact] - public void CreateToken_WhenLocalEncryptionThrowsException_ExpectSameException() - { - var expectedException = new ApplicationException("local"); - var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Local); - - mockVersionStrategy.Setup(x => x.Encrypt(It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(expectedException); - - var exception = Assert.Throws(expectedException.GetType(), () => sut.CreateToken(tokenDescriptor)); - exception.Should().Be(expectedException); - } - - [Fact] - public void CreateToken_WhenPublicSigningThrowsException_ExpectSameException() - { - var expectedException = new ApplicationException("public"); - var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Public); - - mockVersionStrategy.Setup(x => x.Sign(It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(expectedException); - - var exception = Assert.Throws(expectedException.GetType(), () => sut.CreateToken(tokenDescriptor)); - exception.Should().Be(expectedException); - } - - [Fact] - public void CreateToken_WhenLocalEncryptionSucceeds_ExpectLocalToken() - { - const string expectedToken = "local"; - var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Local); - - mockVersionStrategy.Setup(x => x.Encrypt(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(expectedToken); - - var token = sut.CreateToken(tokenDescriptor); - - token.Should().Be(expectedToken); - } - - [Fact] - public void CreateToken_WhenPublicSigningSucceeds_ExpectPublicToken() - { - const string expectedToken = "public"; - var tokenDescriptor = new PasetoSecurityTokenDescriptor(TestVersion, PasetoConstants.Purposes.Public); - - mockVersionStrategy.Setup(x => x.Sign(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(expectedToken); - - var token = sut.CreateToken(tokenDescriptor); - - token.Should().Be(expectedToken); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ValidateToken_WhenTokenIsNullOrWhitespace_ExpectFailureWithArgumentNullException(string token) - { - var result = sut.ValidateToken(token, new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenValidationParametersAreNull_ExpectFailureWithArgumentNullException() - { - var result = sut.ValidateToken(CreateTestToken(), null); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenCannotBeRead_ExpectFailureWithSecurityTokenException() - { - var result = sut.ValidateToken("ey.ey", new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenVersionIsNotSupported_ExpectSecurityTokenException() - { - var result = sut.ValidateToken(CreateTestToken(version: "v42"), new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenTokenPurposeNotSupported_ExpectSecurityTokenException() - { - var result = sut.ValidateToken(CreateTestToken(purpose: "notapurpose"), new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().BeOfType(); - } - - [Fact] - public void ValidateToken_WhenLocalTokenValidationFails_ExpectFailureResultWithInnerException() - { - var expectedException = new ApplicationException("local"); - - mockVersionStrategy.Setup(x => x.Decrypt(It.IsAny(), It.IsAny>())) - .Throws(expectedException); - - var result = sut.ValidateToken(CreateTestToken(purpose: PasetoConstants.Purposes.Local), new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateToken_WhenPublicTokenValidationFails_ExpectFailureResultWithInnerException() - { - var expectedException = new ApplicationException("public"); - - mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) - .Throws(expectedException); - - var result = sut.ValidateToken(CreateTestToken(purpose: PasetoConstants.Purposes.Public), new TokenValidationParameters()); - - result.IsValid.Should().BeFalse(); - result.Exception.Should().Be(expectedException); - } - - [Fact] - public void ValidateToken_WhenTokenPayloadValidationFails_ExpectPayloadValidationResult() - { - var expectedResult = new TokenValidationResult {IsValid = false, Exception = new ApplicationException("validation")}; - - mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) - .Returns(new TestPasetoSecurityToken()); - mockedSut.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(expectedResult); - - var result = sut.ValidateToken(CreateTestToken(purpose: PasetoConstants.Purposes.Public), new TokenValidationParameters()); - - result.Should().Be(expectedResult); - } - - [Fact] - public void ValidateToken_WhenValidLocalToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { - var expectedIdentity = new ClaimsIdentity("test"); - - mockVersionStrategy.Setup(x => x.Decrypt(It.IsAny(), It.IsAny>())) - .Returns(new TestPasetoSecurityToken()); - mockedSut.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = sut.ValidateToken(CreateTestToken(purpose: PasetoConstants.Purposes.Local), new TokenValidationParameters()); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.Should().Be(expectedIdentity); - result.SecurityToken.Should().NotBeNull(); - } - - [Fact] - public void ValidateToken_WhenValidPublicToken_ExpectSuccessResultWithSecurityTokenAndClaimsIdentity() - { - var expectedIdentity = new ClaimsIdentity("test"); - - mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) - .Returns(new TestPasetoSecurityToken()); - mockedSut.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = sut.ValidateToken(CreateTestToken(purpose: PasetoConstants.Purposes.Public), new TokenValidationParameters()); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.Should().Be(expectedIdentity); - result.SecurityToken.Should().NotBeNull(); - } - - [Fact] - public void ValidateToken_WhenSaveSignInTokenIsTrue_ExpectIdentityBootstrapContext() - { - var expectedToken = CreateTestToken(purpose: PasetoConstants.Purposes.Public); - var expectedIdentity = new ClaimsIdentity("test"); - - mockVersionStrategy.Setup(x => x.Verify(It.IsAny(), It.IsAny>())) - .Returns(new TestPasetoSecurityToken()); - mockedSut.Protected() - .Setup("ValidateTokenPayload", - ItExpr.IsAny(), - ItExpr.IsAny()) - .Returns(new TokenValidationResult - { - ClaimsIdentity = expectedIdentity, - IsValid = true - }); - - var result = sut.ValidateToken(expectedToken, new TokenValidationParameters {SaveSigninToken = true}); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.BootstrapContext.Should().Be(expectedToken); - } - - [Fact] - public void CreateAndValidateToken_WhenV2PublicToken_ExpectCorrectClaims() - { - const string expectedClaimType = "name"; - const string expectedClaimValue = "scott"; - const string issuer = "me"; - const string audience = "you"; - - var signingCredentials = new SigningCredentials( - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) - { - D = Convert.FromBase64String("TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw==") - })), ExtendedSecurityAlgorithms.EdDsa); - var verificationKeys = - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519){X = Convert.FromBase64String("doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M=")})); - - var handler = new PasetoTokenHandler(); - var token = handler.CreateToken(new PasetoSecurityTokenDescriptor(PasetoConstants.Versions.V2, PasetoConstants.Purposes.Public) - { - Issuer = issuer, - Audience = audience, - Claims = new Dictionary {{expectedClaimType, expectedClaimValue}}, - SigningCredentials = signingCredentials - }); - - var result = handler.ValidateToken(token, new TokenValidationParameters - { - ValidIssuer = issuer, - ValidAudience = audience, - IssuerSigningKey = verificationKeys - }); - - result.IsValid.Should().BeTrue(); - result.ClaimsIdentity.HasClaim(expectedClaimType, expectedClaimValue).Should().BeTrue(); - } - - private static string CreateTestToken(string version = TestVersion, string purpose = "public", string payload = "ey") - => $"{version}.{purpose}.{payload}"; -} - -internal class TestPasetoSecurityToken : PasetoSecurityToken { } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs deleted file mode 100644 index 47d59c1..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/PasetoTokenTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -public class PasetoTokenTests -{ - private const string ValidToken = "v2.local.xyz"; - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void ctor_WhenTokenIsNullOrWhitespace_ExpectArgumentNullException(string token) - => Assert.Throws(() => new PasetoToken(token)); - - [Fact] - public void ctor_WhenTokenHasTooManyParts_ExpectArgumentException() - => Assert.Throws(() => new PasetoToken("ey.ey.ey.ey.ey")); - - [Fact] - public void ctor_WhenValidPasetoToken_ExpectCorrectProperties() - { - const string expectedVersion = "v2"; - const string expectedPurpose = "public"; - const string expectedPayload = "fa919c9d3d1248f29213521a40fc2b57"; - var token = $"{expectedVersion}.{expectedPurpose}.{expectedPayload}"; - - var pasetoToken = new PasetoToken(token); - - pasetoToken.RawToken.Should().Be(token); - pasetoToken.Version.Should().Be(expectedVersion); - pasetoToken.Purpose.Should().Be(expectedPurpose); - pasetoToken.EncodedPayload.Should().Be(expectedPayload); - - pasetoToken.Payload.Should().BeNull(); - pasetoToken.EncodedFooter.Should().BeNull(); - } - - [Fact] - public void ctor_WhenValidPasetoTokenWithFooter_ExpectCorrectProperties() - { - const string expectedVersion = "v2"; - const string expectedPurpose = "public"; - const string expectedPayload = "fa919c9d3d1248f29213521a40fc2b57"; - const string expectedFooter = "{test}"; - var token = $"{expectedVersion}.{expectedPurpose}.{expectedPayload}.{Base64UrlEncoder.Encode(expectedFooter)}"; - - var pasetoToken = new PasetoToken(token); - - pasetoToken.RawToken.Should().Be(token); - pasetoToken.Version.Should().Be(expectedVersion); - pasetoToken.Purpose.Should().Be(expectedPurpose); - pasetoToken.EncodedPayload.Should().Be(expectedPayload); - pasetoToken.EncodedFooter.Should().Be(Base64UrlEncoder.Encode(expectedFooter)); - pasetoToken.Footer.Should().Be(expectedFooter); - - pasetoToken.Payload.Should().BeNull(); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void SetPayload_WhenPayloadIsNullOrWhitespace_ExpectArgumentNullException(string payload) - => Assert.Throws(() => new PasetoToken(ValidToken).SetPayload(payload)); - - [Fact] - public void SetPayload_WhenPayloadIsNotJson_ExpectArgumentException() - { - const string invalidPayload = "oops"; - var token = new PasetoToken(ValidToken); - - Assert.Throws(() => token.SetPayload(invalidPayload)); - } - - [Fact] - public void SetPayload_WhenValidPayload_ExpectParsedPayload() - { - const string expectedKey = "test"; - const string expectedValue = "test_val"; - - var payload = $"{{ \"{expectedKey}\": \"{expectedValue}\" }}"; - var token = new PasetoToken(ValidToken); - - token.SetPayload(payload); - - token.Payload.Should().Be(payload); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs deleted file mode 100644 index d46d908..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/PasetoTestVectors.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Security.Cryptography; -using System.Text.Json.Nodes; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Crypto; -using ScottBrady.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -/// -/// Test vectors from https://github.com/paseto-standard/test-vectors -/// -public class PasetoTestVectors -{ - public static readonly TheoryData TestVectors = new TheoryData(); - - static PasetoTestVectors() - { - var file = File.OpenRead("Tokens/Paseto/TestVectors/testvectors.json"); - var data = JsonNode.Parse(file); - if (data == null) throw new Exception("Failed to load test vectors"); - - foreach (var testVector in data["v1"]?.AsArray() ?? throw new Exception("Failed to load v1 test vectors")) - { - TestVectors.Add(new PasetoTestVector("v1", testVector)); - } - foreach (var testVector in data["v2"]?.AsArray() ?? throw new Exception("Failed to load v2 test vectors")) - { - TestVectors.Add(new PasetoTestVector("v2", testVector)); - } - } - - [Theory, MemberData(nameof(TestVectors))] - public void ValidateToken_ExpectCorrectResult(PasetoTestVector testVector) - { - var handler = new PasetoTokenHandler(); - var result = handler.ValidateToken(testVector.Token, new TokenValidationParameters - { - ValidateIssuer = false, - ValidateAudience = false, - ValidateLifetime = false, - IssuerSigningKey = testVector.Key - }); - - if (testVector.ShouldFail) - { - result.IsValid.Should().BeFalse(testVector.Name); - result.Claims.Should().BeEmpty(); - result.ClaimsIdentity.Should().BeNull(); - } - else - { - result.IsValid.Should().BeTrue(testVector.Name); - result.Claims.Should().NotBeEmpty(); - result.ClaimsIdentity.Should().NotBeNull(); - - foreach (var claim in testVector.ExpectedPayload.AsObject()) - { - result.Claims.Should().Contain(x => x.Key == claim.Key); - - var value = result.Claims[claim.Key]; - if (value is DateTime) - { - DateTime.TryParse(claim.Value.GetValue(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.RoundtripKind, out var dateTime).Should().BeTrue(); - value.Should().BeEquivalentTo(dateTime.ToUniversalTime()); - } - else - { - value.Should().BeEquivalentTo(claim.Value.GetValue()); - } - } - } - } -} - -public class PasetoTestVector -{ - public PasetoTestVector(string version, JsonNode data) - { - Name = data["name"]?.GetValue(); - ShouldFail = data["expect-fail"]?.GetValue() ?? throw new Exception("Unable to parse expect-fail"); - Token = data["token"]?.GetValue(); - - var payload = data["payload"]?.GetValue(); - if (payload != null) - { - ExpectedPayload = JsonNode.Parse(payload); - } - - var publicKey = data["public-key"]?.GetValue() ?? throw new Exception("Failed to find public key"); - if (version == "v1") - { - var rsaKey = RSA.Create(); - rsaKey.ImportFromPem(publicKey); - Key = new RsaSecurityKey(rsaKey); - } - else if (version == "v2") - { - if (Base16.Decode(publicKey).Length != 32) - { - - } - - Key = new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Base16.Decode(publicKey)})); - } - } - - public string Name { get; } - public bool ShouldFail { get; } - public SecurityKey Key { get; } - public string Token { get; } - public JsonNode ExpectedPayload { get; } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/testvectors.json b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/testvectors.json deleted file mode 100644 index 6440206..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/TestVectors/testvectors.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "v1": [ - { - "name": "1-S-1", - "expect-fail": false, - "public-key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p5GHgwoGW\nwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwx\nKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1\nOt0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAA\npVRuUI2Sd6L1E2vl9bSBumZ5IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6al\nUyhKC1+1w/FW6HWcp/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8\nowIDAQAB\n-----END PUBLIC KEY-----", - "secret-key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAyaTgTt53ph3p5GHgwoGWwz5hRfWXSQA08NCOwe0FEgALWos9\nGCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwxKheDp4kxo4YMN5trPaF0e9G6Bj1N\n02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1Ot0ZxDDDXS9wIQTtBE0ne3YbxgZJ\nAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAApVRuUI2Sd6L1E2vl9bSBumZ5IpNx\nkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6alUyhKC1+1w/FW6HWcp/JG1kKC8DPI\nidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8owIDAQABAoIBAF22jLDa34yKdns3\nqfd7to+C3D5hRzAcMn6Azvf9qc+VybEI6RnjTHxDZWK5EajSP4/sQ15e8ivUk0Jo\nWdJ53feL+hnQvwsab28gghSghrxM2kGwGA1XgO+SVawqJt8SjvE+Q+//01ZKK0Oy\nA0cDJjX3L9RoPUN/moMeAPFw0hqkFEhm72GSVCEY1eY+cOXmL3icxnsnlUD//SS9\nq33RxF2y5oiW1edqcRqhW/7L1yYMbxHFUcxWh8WUwjn1AAhoCOUzF8ZB+0X/PPh+\n1nYoq6xwqL0ZKDwrQ8SDhW/rNDLeO9gic5rl7EetRQRbFvsZ40AdsX2wU+lWFUkB\n42AjuoECgYEA5z/CXqDFfZ8MXCPAOeui8y5HNDtu30aR+HOXsBDnRI8huXsGND04\nFfmXR7nkghr08fFVDmE4PeKUk810YJb+IAJo8wrOZ0682n6yEMO58omqKin+iIUV\nrPXLSLo5CChrqw2J4vgzolzPw3N5I8FJdLomb9FkrV84H+IviPIylyECgYEA3znw\nAG29QX6ATEfFpGVOcogorHCntd4niaWCq5ne5sFL+EwLeVc1zD9yj1axcDelICDZ\nxCZynU7kDnrQcFkT0bjH/gC8Jk3v7XT9l1UDDqC1b7rm/X5wFIZ/rmNa1rVZhL1o\n/tKx5tvM2syJ1q95v7NdygFIEIW+qbIKbc6Wz0MCgYBsUZdQD+qx/xAhELX364I2\nepTryHMUrs+tGygQVrqdiJX5dcDgM1TUJkdQV6jLsKjPs4Vt6OgZRMrnuLMsk02R\n3M8gGQ25ok4f4nyyEZxGGWnVujn55KzUiYWhGWmhgp18UCkoYa59/Q9ss+gocV9h\nB9j9Q43vD80QUjiF4z0DQQKBgC7XQX1VibkMim93QAnXGDcAS0ij+w02qKVBjcHk\nb9mMBhz8GAxGOIu7ZJafYmxhwMyVGB0I1FQeEczYCJUKnBYN6Clsjg6bnBT/z5bJ\nx/Jx1qCzX3Uh6vLjpjc5sf4L39Tyye1u2NXQmZPwB5x9BdcsFConSq/s4K1LJtUT\n3KFxAoGBANGcQ8nObi3m4wROyKrkCWcWxFFMnpwxv0pW727Hn9wuaOs4UbesCnwm\npcMTfzGUDuzYXCtAq2pJl64HG6wsdkWmjBTJEpm6b9ibOBN3qFV2zQ0HyyKlMWxI\nuVSj9gOo61hF7UH9XB6R4HRdlpBOuIbgAWZ46dkj9/HM9ovdP0Iy\n-----END RSA PRIVATE KEY-----", - "token": "v1.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9cIZKahKeGM5kiAS_4D70Qbz9FIThZpxetJ6n6E6kXP_119SvQcnfCSfY_gG3D0Q2v7FEtm2Cmj04lE6YdgiZ0RwA41WuOjXq7zSnmmHK9xOSH6_2yVgt207h1_LphJzVztmZzq05xxhZsV3nFPm2cCu8oPceWy-DBKjALuMZt_Xj6hWFFie96SfQ6i85lOsTX8Kc6SQaG-3CgThrJJ6W9DC-YfQ3lZ4TJUoY3QNYdtEgAvp1QuWWK6xmIb8BwvkBPej5t88QUb7NcvZ15VyNw3qemQGn2ITSdpdDgwMtpflZOeYdtuxQr1DSGO2aQyZl7s0WYn1IjdQFx6VjSQ4yfw", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "", - "implicit-assertion": "" - }, - { - "name": "1-S-2", - "expect-fail": false, - "public-key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p5GHgwoGW\nwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwx\nKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1\nOt0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAA\npVRuUI2Sd6L1E2vl9bSBumZ5IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6al\nUyhKC1+1w/FW6HWcp/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8\nowIDAQAB\n-----END PUBLIC KEY-----", - "secret-key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAyaTgTt53ph3p5GHgwoGWwz5hRfWXSQA08NCOwe0FEgALWos9\nGCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwxKheDp4kxo4YMN5trPaF0e9G6Bj1N\n02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1Ot0ZxDDDXS9wIQTtBE0ne3YbxgZJ\nAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAApVRuUI2Sd6L1E2vl9bSBumZ5IpNx\nkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6alUyhKC1+1w/FW6HWcp/JG1kKC8DPI\nidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8owIDAQABAoIBAF22jLDa34yKdns3\nqfd7to+C3D5hRzAcMn6Azvf9qc+VybEI6RnjTHxDZWK5EajSP4/sQ15e8ivUk0Jo\nWdJ53feL+hnQvwsab28gghSghrxM2kGwGA1XgO+SVawqJt8SjvE+Q+//01ZKK0Oy\nA0cDJjX3L9RoPUN/moMeAPFw0hqkFEhm72GSVCEY1eY+cOXmL3icxnsnlUD//SS9\nq33RxF2y5oiW1edqcRqhW/7L1yYMbxHFUcxWh8WUwjn1AAhoCOUzF8ZB+0X/PPh+\n1nYoq6xwqL0ZKDwrQ8SDhW/rNDLeO9gic5rl7EetRQRbFvsZ40AdsX2wU+lWFUkB\n42AjuoECgYEA5z/CXqDFfZ8MXCPAOeui8y5HNDtu30aR+HOXsBDnRI8huXsGND04\nFfmXR7nkghr08fFVDmE4PeKUk810YJb+IAJo8wrOZ0682n6yEMO58omqKin+iIUV\nrPXLSLo5CChrqw2J4vgzolzPw3N5I8FJdLomb9FkrV84H+IviPIylyECgYEA3znw\nAG29QX6ATEfFpGVOcogorHCntd4niaWCq5ne5sFL+EwLeVc1zD9yj1axcDelICDZ\nxCZynU7kDnrQcFkT0bjH/gC8Jk3v7XT9l1UDDqC1b7rm/X5wFIZ/rmNa1rVZhL1o\n/tKx5tvM2syJ1q95v7NdygFIEIW+qbIKbc6Wz0MCgYBsUZdQD+qx/xAhELX364I2\nepTryHMUrs+tGygQVrqdiJX5dcDgM1TUJkdQV6jLsKjPs4Vt6OgZRMrnuLMsk02R\n3M8gGQ25ok4f4nyyEZxGGWnVujn55KzUiYWhGWmhgp18UCkoYa59/Q9ss+gocV9h\nB9j9Q43vD80QUjiF4z0DQQKBgC7XQX1VibkMim93QAnXGDcAS0ij+w02qKVBjcHk\nb9mMBhz8GAxGOIu7ZJafYmxhwMyVGB0I1FQeEczYCJUKnBYN6Clsjg6bnBT/z5bJ\nx/Jx1qCzX3Uh6vLjpjc5sf4L39Tyye1u2NXQmZPwB5x9BdcsFConSq/s4K1LJtUT\n3KFxAoGBANGcQ8nObi3m4wROyKrkCWcWxFFMnpwxv0pW727Hn9wuaOs4UbesCnwm\npcMTfzGUDuzYXCtAq2pJl64HG6wsdkWmjBTJEpm6b9ibOBN3qFV2zQ0HyyKlMWxI\nuVSj9gOo61hF7UH9XB6R4HRdlpBOuIbgAWZ46dkj9/HM9ovdP0Iy\n-----END RSA PRIVATE KEY-----", - "token": "v1.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9sBTIb0J_4misAuYc4-6P5iR1rQighzktpXhJ8gtrrp2MqSSDkbb8q5WZh3FhUYuW_rg2X8aflDlTWKAqJkM3otjYwtmfwfOhRyykxRL2AfmIika_A-_MaLp9F0iw4S1JetQQDV8GUHjosd87TZ20lT2JQLhxKjBNJSwWue8ucGhTgJcpOhXcthqaz7a2yudGyd0layzeWziBhdQpoBR6ryTdtIQX54hP59k3XCIxuYbB9qJMpixiPAEKBcjHT74sA-uukug9VgKO7heWHwJL4Rl9ad21xyNwaxAnwAJ7C0fN5oGv8Rl0dF11b3tRmsmbDoIokIM0Dba29x_T3YzOyg.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}", - "implicit-assertion": "" - }, - { - "name": "1-S-3", - "expect-fail": false, - "public-key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p5GHgwoGW\nwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwx\nKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1\nOt0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAA\npVRuUI2Sd6L1E2vl9bSBumZ5IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6al\nUyhKC1+1w/FW6HWcp/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8\nowIDAQAB\n-----END PUBLIC KEY-----", - "secret-key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAyaTgTt53ph3p5GHgwoGWwz5hRfWXSQA08NCOwe0FEgALWos9\nGCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwxKheDp4kxo4YMN5trPaF0e9G6Bj1N\n02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1Ot0ZxDDDXS9wIQTtBE0ne3YbxgZJ\nAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAApVRuUI2Sd6L1E2vl9bSBumZ5IpNx\nkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6alUyhKC1+1w/FW6HWcp/JG1kKC8DPI\nidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8owIDAQABAoIBAF22jLDa34yKdns3\nqfd7to+C3D5hRzAcMn6Azvf9qc+VybEI6RnjTHxDZWK5EajSP4/sQ15e8ivUk0Jo\nWdJ53feL+hnQvwsab28gghSghrxM2kGwGA1XgO+SVawqJt8SjvE+Q+//01ZKK0Oy\nA0cDJjX3L9RoPUN/moMeAPFw0hqkFEhm72GSVCEY1eY+cOXmL3icxnsnlUD//SS9\nq33RxF2y5oiW1edqcRqhW/7L1yYMbxHFUcxWh8WUwjn1AAhoCOUzF8ZB+0X/PPh+\n1nYoq6xwqL0ZKDwrQ8SDhW/rNDLeO9gic5rl7EetRQRbFvsZ40AdsX2wU+lWFUkB\n42AjuoECgYEA5z/CXqDFfZ8MXCPAOeui8y5HNDtu30aR+HOXsBDnRI8huXsGND04\nFfmXR7nkghr08fFVDmE4PeKUk810YJb+IAJo8wrOZ0682n6yEMO58omqKin+iIUV\nrPXLSLo5CChrqw2J4vgzolzPw3N5I8FJdLomb9FkrV84H+IviPIylyECgYEA3znw\nAG29QX6ATEfFpGVOcogorHCntd4niaWCq5ne5sFL+EwLeVc1zD9yj1axcDelICDZ\nxCZynU7kDnrQcFkT0bjH/gC8Jk3v7XT9l1UDDqC1b7rm/X5wFIZ/rmNa1rVZhL1o\n/tKx5tvM2syJ1q95v7NdygFIEIW+qbIKbc6Wz0MCgYBsUZdQD+qx/xAhELX364I2\nepTryHMUrs+tGygQVrqdiJX5dcDgM1TUJkdQV6jLsKjPs4Vt6OgZRMrnuLMsk02R\n3M8gGQ25ok4f4nyyEZxGGWnVujn55KzUiYWhGWmhgp18UCkoYa59/Q9ss+gocV9h\nB9j9Q43vD80QUjiF4z0DQQKBgC7XQX1VibkMim93QAnXGDcAS0ij+w02qKVBjcHk\nb9mMBhz8GAxGOIu7ZJafYmxhwMyVGB0I1FQeEczYCJUKnBYN6Clsjg6bnBT/z5bJ\nx/Jx1qCzX3Uh6vLjpjc5sf4L39Tyye1u2NXQmZPwB5x9BdcsFConSq/s4K1LJtUT\n3KFxAoGBANGcQ8nObi3m4wROyKrkCWcWxFFMnpwxv0pW727Hn9wuaOs4UbesCnwm\npcMTfzGUDuzYXCtAq2pJl64HG6wsdkWmjBTJEpm6b9ibOBN3qFV2zQ0HyyKlMWxI\nuVSj9gOo61hF7UH9XB6R4HRdlpBOuIbgAWZ46dkj9/HM9ovdP0Iy\n-----END RSA PRIVATE KEY-----", - "token": "v1.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9sBTIb0J_4misAuYc4-6P5iR1rQighzktpXhJ8gtrrp2MqSSDkbb8q5WZh3FhUYuW_rg2X8aflDlTWKAqJkM3otjYwtmfwfOhRyykxRL2AfmIika_A-_MaLp9F0iw4S1JetQQDV8GUHjosd87TZ20lT2JQLhxKjBNJSwWue8ucGhTgJcpOhXcthqaz7a2yudGyd0layzeWziBhdQpoBR6ryTdtIQX54hP59k3XCIxuYbB9qJMpixiPAEKBcjHT74sA-uukug9VgKO7heWHwJL4Rl9ad21xyNwaxAnwAJ7C0fN5oGv8Rl0dF11b3tRmsmbDoIokIM0Dba29x_T3YzOyg.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}", - "implicit-assertion": "discarded-anyway" - }, - { - "name": "1-F-1", - "expect-fail": true, - "public-key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyaTgTt53ph3p5GHgwoGW\nwz5hRfWXSQA08NCOwe0FEgALWos9GCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwx\nKheDp4kxo4YMN5trPaF0e9G6Bj1N02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1\nOt0ZxDDDXS9wIQTtBE0ne3YbxgZJAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAA\npVRuUI2Sd6L1E2vl9bSBumZ5IpNxkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6al\nUyhKC1+1w/FW6HWcp/JG1kKC8DPIidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8\nowIDAQAB\n-----END PUBLIC KEY-----", - "secret-key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAyaTgTt53ph3p5GHgwoGWwz5hRfWXSQA08NCOwe0FEgALWos9\nGCjNFCd723nCHxBtN1qd74MSh/uN88JPIbwxKheDp4kxo4YMN5trPaF0e9G6Bj1N\n02HnanxFLW+gmLbgYO/SZYfWF/M8yLBcu5Y1Ot0ZxDDDXS9wIQTtBE0ne3YbxgZJ\nAZTU5XqyQ1DxdzYyC5lF6yBaR5UQtCYTnXAApVRuUI2Sd6L1E2vl9bSBumZ5IpNx\nkRnAwIMjeTJB/0AIELh0mE5vwdihOCbdV6alUyhKC1+1w/FW6HWcp/JG1kKC8DPI\nidZ78Bbqv9YFzkAbNni5eSBOsXVBKG78Zsc8owIDAQABAoIBAF22jLDa34yKdns3\nqfd7to+C3D5hRzAcMn6Azvf9qc+VybEI6RnjTHxDZWK5EajSP4/sQ15e8ivUk0Jo\nWdJ53feL+hnQvwsab28gghSghrxM2kGwGA1XgO+SVawqJt8SjvE+Q+//01ZKK0Oy\nA0cDJjX3L9RoPUN/moMeAPFw0hqkFEhm72GSVCEY1eY+cOXmL3icxnsnlUD//SS9\nq33RxF2y5oiW1edqcRqhW/7L1yYMbxHFUcxWh8WUwjn1AAhoCOUzF8ZB+0X/PPh+\n1nYoq6xwqL0ZKDwrQ8SDhW/rNDLeO9gic5rl7EetRQRbFvsZ40AdsX2wU+lWFUkB\n42AjuoECgYEA5z/CXqDFfZ8MXCPAOeui8y5HNDtu30aR+HOXsBDnRI8huXsGND04\nFfmXR7nkghr08fFVDmE4PeKUk810YJb+IAJo8wrOZ0682n6yEMO58omqKin+iIUV\nrPXLSLo5CChrqw2J4vgzolzPw3N5I8FJdLomb9FkrV84H+IviPIylyECgYEA3znw\nAG29QX6ATEfFpGVOcogorHCntd4niaWCq5ne5sFL+EwLeVc1zD9yj1axcDelICDZ\nxCZynU7kDnrQcFkT0bjH/gC8Jk3v7XT9l1UDDqC1b7rm/X5wFIZ/rmNa1rVZhL1o\n/tKx5tvM2syJ1q95v7NdygFIEIW+qbIKbc6Wz0MCgYBsUZdQD+qx/xAhELX364I2\nepTryHMUrs+tGygQVrqdiJX5dcDgM1TUJkdQV6jLsKjPs4Vt6OgZRMrnuLMsk02R\n3M8gGQ25ok4f4nyyEZxGGWnVujn55KzUiYWhGWmhgp18UCkoYa59/Q9ss+gocV9h\nB9j9Q43vD80QUjiF4z0DQQKBgC7XQX1VibkMim93QAnXGDcAS0ij+w02qKVBjcHk\nb9mMBhz8GAxGOIu7ZJafYmxhwMyVGB0I1FQeEczYCJUKnBYN6Clsjg6bnBT/z5bJ\nx/Jx1qCzX3Uh6vLjpjc5sf4L39Tyye1u2NXQmZPwB5x9BdcsFConSq/s4K1LJtUT\n3KFxAoGBANGcQ8nObi3m4wROyKrkCWcWxFFMnpwxv0pW727Hn9wuaOs4UbesCnwm\npcMTfzGUDuzYXCtAq2pJl64HG6wsdkWmjBTJEpm6b9ibOBN3qFV2zQ0HyyKlMWxI\nuVSj9gOo61hF7UH9XB6R4HRdlpBOuIbgAWZ46dkj9/HM9ovdP0Iy\n-----END RSA PRIVATE KEY-----", - "token": "v1.local.uLSnLZApN_-wP2fM5d-0DgQ4RoR8zO6Sp4p5VbYkSW_KuWt_1sbtDalN3HBtGR8PAtU5M_-bes-iSqppFtmykiznxvxvz30K8yXJ2aXGbS-upJKXjO5cjWXBGJLNBPzzdpUo85Iv-gW8bbZ9e4tfnNdsRcJZ-g.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24", - "payload": null, - "footer": "arbitrary-string-that-isn't-json", - "implicit-assertion": "{\"test-vector\":\"1-F-1\"}" - } - ], - "v2": [ - { - "name": "2-S-1", - "expect-fail": false, - "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", - "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", - "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", - "token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGntTu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_DjJK2ZXC2SUYuOFM-Q_5Cw", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "", - "implicit-assertion": "" - }, - { - "name": "2-S-2", - "expect-fail": false, - "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", - "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", - "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", - "token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", - "implicit-assertion": "" - }, - { - "name": "2-S-3", - "expect-fail": false, - "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", - "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", - "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", - "token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", - "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}", - "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", - "implicit-assertion": "discarded-anyway" - }, - { - "name": "2-F-1", - "expect-fail": true, - "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", - "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", - "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", - "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", - "token": "v2.local.pN9Y9kTFKnCskKr7B13IoceBabSTMS0LkUg3SeAqONg6EJsq9h-CLWdWaA_rMZX4MhGsOQn5I0EsIgYeOA2NPJZU0uulsahH-k871PBq.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24", - "payload": null, - "footer": "arbitrary-string-that-isn't-json", - "implicit-assertion": "{\"test-vector\":\"2-F-1\"}" - } - ] -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs deleted file mode 100644 index 1d9c151..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion1Tests.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -public class PasetoVersion1Tests -{ - private const string ValidVersion = "v1"; - private const string ValidPublicPurpose = "public"; - private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTA3VDE3OjExOjM4LjM3MTE3MTJaIn1HginqDCa4m01vI75OaWrFyAYCA1k9_sx36XVDEcHosOkk6coBDwDfoOaSFA_wE3nkfyuy3fTr7g6BpdzPbIb5qhI4Wpdy_zhhyEz7kC8ZSaDNN0tnBT0sL1c6hSuWKGh3tT6qPmjUmJwIv2ZjosozSmRF7bhWKJDsvTzQN6EFBddcvQpPQok9Ekdgzd70_Yxjl9YlUizF7WOiDm-R6m3xy_Mk8IRGQwiArYGmJRmR82W97ajqdBUJD8kbaFQglDxwEMcX-T4AqXCttjhdQi-JcXX34SDTyxE-8m02X8eNrKg64L6ZAFDAzbaa2bz3EUo5ULW2XaG4DW2zZ4nFd9m2"; - private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; - - private const string ValidSigningPrivateKey = - "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48UD54Mlp3Y1N1Ni9KeTB5UFRqcVJXTlA1OVhtMk5hNDFEajdQeThsNHBXbWZKWkExTWxBMzRMNUpLVEpJMHZEWStyMTBhN1JRcVJSeHpseEVnQStvMHQwbW5uckRZbGJYbDB4OGlON04vb2w2OHZ0NEJtWFZCWGdxYURpNUJoaWtvLzVzd3EvOVhBd1ZKYm1zVFBCSjVGdi9DQWxUSytNbStacUt6MzlHVTYvMHM9PC9QPjxRPnp3WU80MVdGRUFyTS80R3hDOW1HZEhJeEFKR2RBaWoyZWJ0NjMrQk9QRUxpelZySjdudVR0ZGtxZnlhRHR5eXpGTWxVRHV2VVpIMU1YNnlsUldsQWQrSlhZcUFRZVlML2poYXZYL0llL2NsRE5YRnlRbTNoeitrOFRZZDV2KzFId0RjVTNWVUJlQnVTa3d3bmgrSXBHMU5HeWk4b0RJZldYZThIUXRsdVBZOD08L1E+PERQPllPbGN4T1FvSVJaWWwwTE9VeU55WHZXbXNwTDdYWGUzRHp0V3ZhQXlydWVtYzRNNWZoVUkycktTYVRWbEpRWXEwcHBCOGpCTW8yOWNES1dpTkNQaG5WNXpock5hUlhhK1Ywc1dENFpUbVVVL3Y4UGIvSVpMd2VnRUR4VEJFMkU2NVlWZGNMSUcyTzZhTHdKd1N5SlJiQlFMcW5mYkVOQkVza0krMEwxU2l6az08L0RQPjxEUT5XRTJyT0FpWVV6bG9LMnYwU3F1a0VETk05NE1reDNFVmdPTVpERGt1NWNGWjRHSGpWQmZkNzJrTUdXUWlOcFdZWlR0aTRXSnlHOUxlS3NrSFRjNFJNNUdWMkhtUnpXSzFBclJtWmJSdXg2MTdQMlorYUJ0YWdFWnA5Ri9lN0tDWFJFTzZZSllMcEdHT2FhNTdoaGhQbEZvM0RiS0RrS1M0S1NUMW9ld0FlNzA9PC9EUT48SW52ZXJzZVE+SVpvNFVZRzVOVWt6TlA3LzVXTzhkVW9BUFJJU2htVithNTh5dWZWRjZtMUl3NTRJVDNTVWhXOVh5dFdMa0ZsWmJZakhxMmZlaUhDOUw0OGFUTG1SMmlIejVKUjJKN2pENnJGbis1SFNZR2l0OGpZbnBvNGpvVjZwaDAraWhOVnMzbkk3OEZVcFhmVDdWd0hhZDF4SXJ0QzA0VHBPODFjSjZpbi91TTRBVDI4PTwvSW52ZXJzZVE+PEQ+bEtQSEFmR2prRlJSSHRHUW13VU9pVlRDelV3NXlDY2pzQUpuMnZZRlpKRTRxaUtIUzVnQ0VodWFuMWZUUjZ3Y2d2cGROaTJuWlF2YWttMTZWeXc1cHZmUUpiN1psT2lvNzdLZS9QYmM1SnMyM0piaFVLejk5TnRYWVVFaDJFdVIvMCtPc0VMQ0hsd29oOUFDMS9VdTVnRFIwNTRqcmVwWGpGU2hVcVNyOWdRaG5sejhreHlhdUpaK3hKQWorSWVnd3lzRkp0ZWVLUldJZjdDZ3ZESFZrMk8rcjBJcTZldTBWTFMxbHladlNNOWJyWDlxOVRYUzMvODJFQ2M2UW9PbUk5NEFTOXhmcGZWeGIyZjJEQ0dSN0dZL1M2WWJTRGpJMXpMQWxQRzZiWVJRTFlsR1FVd3NvdHkrYzFZK1ZCc1R3VEk4WTFneTVOemdES2x6S3I3ZXhRPT08L0Q+PC9SU0FLZXlWYWx1ZT4="; - private const string ValidSigningPublicKey = - "PFJTQUtleVZhbHVlPjxNb2R1bHVzPm9VQ002dEdieFc5eGZqWm1mbmQzTThLQkFzd0ZmbHVLY2IwV1RNMXMzeVh2c3dZci9MbkFVVGVhNjF4QWRma3BSbVVuT3VrODRwZUN5OEVkMzdZcXVHL05WMldsT0dRckxMRkh3UklIMGdmU0IzMjFpdlVzN2xqbDc5S25TRmpEU0ZqcUJNTEJTSS93ZlhobCtYTGZrTjczaGJmeTNSRzVTUDU4Vm5UUEQveWFRczlmNVdVVHhCSFBKNWx4Ump3cVpTemJjZE05cHNtcVFHWGcveUVCejFsMlJQaCtTK0R1aEw5TU1iRWdTb0lXanFKaWEzUFllRDF5WEt3RjlPdjlaa3V4L21ZZjRkRW9pWUZXV05jS3ZSSGFTVFBjTFd0NnZpUXZsekREd09FUG9HdlI4SkNreUJ2a1J0Q1VBeVAyMEpkYzFGK0xqdEp3dkIyNTRBTjU1UT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48L1JTQUtleVZhbHVlPg=="; - - private readonly SigningCredentials validSigningCredentials; - private readonly List validVerificationKeys; - - private readonly PasetoVersion1 sut = new PasetoVersion1(); - - public PasetoVersion1Tests() - { - var privateKey = RSA.Create(); - privateKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPrivateKey))); - - var publicKey = RSA.Create(); - publicKey.FromXmlString(Encoding.UTF8.GetString(Convert.FromBase64String(ValidSigningPublicKey))); - - validSigningCredentials = new SigningCredentials(new RsaSecurityKey(privateKey), SecurityAlgorithms.RsaSsaPssSha384); - validVerificationKeys = new List {new RsaSecurityKey(publicKey)}; - } - - [Fact] - public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); - - [Fact] - public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign("test", null, null)); - - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainRsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(new ECDsaSecurityKey(ECDsa.Create()), SecurityAlgorithms.EcdsaSha256); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenSigningCredentialsNotConfiguredForPs384_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validSigningCredentials.Key, SecurityAlgorithms.RsaSha384); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validVerificationKeys.First(), SecurityAlgorithms.RsaSsaPssSha384); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenTokenGenerated_ExpectThreeParts() - { - var token = sut.Sign("payload", null, validSigningCredentials); - - token.Split('.').Length.Should().Be(3); - } - - [Fact] - public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() - { - var token = sut.Sign("payload", "footer", validSigningCredentials); - - token.Split('.').Length.Should().Be(4); - } - - [Fact] - public void Verify_WhenTokenIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); - - [Fact] - public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); - - [Fact] - public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); - - [Fact] - public void Verify_WhenNoRsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() - { - var keys = new List {new ECDsaSecurityKey(ECDsa.Create())}; - - Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); - } - - [Fact] - public void Verify_WhenIncorrectVersion_ExpectArgumentException() - { - var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenIncorrectPurpose_ExpectArgumentException() - { - var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() - { - var payloadBytes = new byte[32]; - new Random().NextBytes(payloadBytes); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() - { - var payloadValue = "test"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - - var signature = new byte[256]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() - { - var payloadValue = "{ \"test\": \"test\" }"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - - var signature = new byte[256]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() - { - var token = new PasetoToken(validToken); - - var securityToken = sut.Verify(token, validVerificationKeys); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(token.RawToken); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, null, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - const string expectedFooter = "{\"kid\": \"123\"}"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - parsedToken.Footer.Should().Be(expectedFooter); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs deleted file mode 100644 index 7a71944..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersion2Tests.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Crypto; -using ScottBrady.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -public class PasetoVersion2Tests -{ - private const string ValidVersion = "v2"; - private const string ValidPublicPurpose = "public"; - private const string ValidPublicPayload = "eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAyVDE2OjIzOjQwLjI1Njg1MTVaIn08nP0mX2YJvYOcMLBpiFbFs1C2gyNAJg_kpuniow671AfrEZWRDZWmLAQbuKRQNiJ2gIrXVeC-tO20zrVQ58wK"; - private readonly string validToken = $"{ValidVersion}.{ValidPublicPurpose}.{ValidPublicPayload}"; - - private const string ValidSigningPrivateKey = "TYXei5+8Qd2ZqKIlEuJJ3S50WYuocFTrqK+3/gHVH9B2hpLtAgscF2c9QuWCzV9fQxal3XBqTXivXJPpp79vgw=="; - private const string ValidSigningPublicKey = "doaS7QILHBdnPULlgs1fX0MWpd1wak14r1yT6ae/b4M="; - - private readonly SigningCredentials validSigningCredentials = new SigningCredentials( - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = Convert.FromBase64String(ValidSigningPrivateKey)})), - ExtendedSecurityAlgorithms.EdDsa); - - private readonly List validVerificationKeys = new List - { - new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Convert.FromBase64String(ValidSigningPublicKey)})) - }; - - private readonly PasetoVersion2 sut = new PasetoVersion2(); - - [Fact] - public void Sign_WhenPayloadIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign(null, null, validSigningCredentials)); - - [Fact] - public void Sign_WhenSigningCredentialsAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Sign("test", null, null)); - - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainEdDsaSecurityKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(new RsaSecurityKey(RSA.Create()), ExtendedSecurityAlgorithms.EdDsa); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenSigningCredentialsNotConfigureForEdDSA_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validSigningCredentials.Key, ExtendedSecurityAlgorithms.XChaCha20Poly1305); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenSigningCredentialsDoNotContainPrivateKey_ExpectSecurityTokenInvalidSigningKeyException() - { - var signingCredentials = new SigningCredentials(validVerificationKeys.First(), ExtendedSecurityAlgorithms.EdDsa); - - Assert.Throws(() => sut.Sign("payload", null, signingCredentials)); - } - - [Fact] - public void Sign_WhenTokenGenerated_ExpectThreeParts() - { - var token = sut.Sign("payload", null, validSigningCredentials); - - token.Split('.').Length.Should().Be(3); - } - - [Fact] - public void Sign_WhenTokenGeneratedWithFooter_ExpectFourParts() - { - var token = sut.Sign("payload", "footer", validSigningCredentials); - - token.Split('.').Length.Should().Be(4); - } - - [Fact] - public void Verify_WhenTokenIsNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(null, validVerificationKeys)); - - [Fact] - public void Verify_WhenSecurityKeysAreNull_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), null)); - - [Fact] - public void Verify_WhenSecurityKeysAreEmpty_ExpectArgumentNullException() - => Assert.Throws(() => sut.Verify(new PasetoToken(validToken), new List())); - - [Fact] - public void Verify_WhenNoEdDsaSecurityKeysPresent_ExpectSecurityTokenInvalidSigningKeyException() - { - var keys = new List {new RsaSecurityKey(RSA.Create())}; - - Assert.Throws(() => sut.Verify(new PasetoToken(validToken), keys)); - } - - [Fact] - public void Verify_WhenIncorrectVersion_ExpectArgumentException() - { - var token = new PasetoToken($"v42.{ValidPublicPurpose}.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenIncorrectPurpose_ExpectArgumentException() - { - var token = new PasetoToken($"{ValidVersion}.local.{ValidPublicPayload}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadIsNotBase64UrlEncodedValue_ExpectFormatException() - { - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.ey!!"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainEnoughBytes_ExpectSecurityTokenInvalidSignatureException() - { - var payloadBytes = new byte[32]; - new Random().NextBytes(payloadBytes); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payloadBytes)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenPayloadDoesNotContainJson_ExpectSecurityTokenException() - { - var payloadValue = "test"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - - var signature = new byte[64]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureInvalid_ExpectSecurityTokenInvalidSignatureException() - { - var payloadValue = "{ \"test\": \"test\" }"; - var payloadValueBytes = Encoding.UTF8.GetBytes(payloadValue); - - var signature = new byte[64]; - new Random().NextBytes(signature); - - var payload = new byte[payloadValueBytes.Length + signature.Length]; - Buffer.BlockCopy(payloadValueBytes, 0, payload, 0, payloadValueBytes.Length); - Buffer.BlockCopy(signature, 0, payload, payloadValueBytes.Length, signature.Length); - - var token = new PasetoToken($"{ValidVersion}.{ValidPublicPurpose}.{Base64UrlEncoder.Encode(payload)}"); - - Assert.Throws(() => sut.Verify(token, validVerificationKeys)); - } - - [Fact] - public void Verify_WhenSignatureIsValid_ExpectCorrectSecurityToken() - { - // "wxFZtnkkIXbcNh4WTYbTS8WgEyWaYRhfT1603kN6SdQ=" - // "v2.public.eyJzdWIiOiIxMjMiLCJleHAiOiIyMDIwLTA1LTAzVDEzOjE0OjE0LjE5MDA1OFoiff5U7ni0Bd5yame3wT41v26UMyH56JA4Un077FPn_UkGpx78fVgbegW0FEMLw0J61ms0OJHarRzyRrX4dWn6LgA" - var token = new PasetoToken(validToken); - - var securityToken = sut.Verify(token, validVerificationKeys); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(token.RawToken); - } - - [Fact] - public void Verify_WhenFooterPresentAndSignatureIsValid_ExpectCorrectSecurityToken() - { - var tokenWithFooter = new PasetoToken( - "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); - var publicKey = Convert.FromBase64String("Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI="); - - var securityToken = sut.Verify(tokenWithFooter, - new[] {new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = publicKey}))}); - - securityToken.Should().NotBeNull(); - securityToken.RawToken.Should().Be(tokenWithFooter.RawToken); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValid_ExpectCorrectClaimsFromPayload() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, null, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - } - - [Fact] - public void SignAndVerify_WhenKeysAreValidAndWithFooter_ExpectCorrectClaimsFromPayloadAndCorrectFooter() - { - const string expectedClaimType = "test"; - const string expectedClaimValue = "test_val"; - const string expectedFooter = "{'kid': '123'}"; - var expectedPayload = $"{{ \"{expectedClaimType}\": \"{expectedClaimValue}\" }}"; - - var token = sut.Sign(expectedPayload, expectedFooter, validSigningCredentials); - var parsedToken = sut.Verify(new PasetoToken(token), validVerificationKeys); - - parsedToken.Claims.Should().Contain(x => x.Type == expectedClaimType && x.Value == expectedClaimValue); - parsedToken.RawToken.Should().Be(token); - parsedToken.Footer.Should().Be(expectedFooter); - } -} \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs deleted file mode 100644 index fc6d9cf..0000000 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/Paseto/VersionStrategies/PasetoVersionStrategyTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FluentAssertions; -using Microsoft.IdentityModel.Tokens; -using ScottBrady.IdentityModel.Tokens.Paseto; -using Xunit; - -namespace ScottBrady.IdentityModel.Tests.Tokens.Paseto; - -/// -/// https://github.com/paragonie/paseto/blob/master/docs/01-Protocol-Versions/Common.md -/// -public class PasetoVersionStrategyTests -{ - [Fact] - public void PreAuthEncode_WhenPiecesIsNull_ExpectArgumentNullException() - => Assert.Throws(() => TestPasetoVersionStrategy.PreAuthEncodeSpy(null)); - - [Fact] - public void PreAuthEncodeSpy_WhenEmptyCollection_ExpectKnownResponse() - { - var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new List()); - encodedValue.Should().BeEquivalentTo(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}); - } - - [Fact] - public void PreAuthEncodeSpy_WhenEmptyString_ExpectKnownResponse() - { - var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new[] {Encoding.UTF8.GetBytes(string.Empty)}); - encodedValue.Should().BeEquivalentTo(new byte[] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - } - - [Fact] - public void PreAuthEncodeSpy_WhenTestString_ExpectKnownResponse() - { - var testBytes = Encoding.UTF8.GetBytes("test"); - - var encodedValue = TestPasetoVersionStrategy.PreAuthEncodeSpy(new[] {testBytes}); - encodedValue.Should().BeEquivalentTo(new byte[] {1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0}.Concat(testBytes)); - } -} - -public class TestPasetoVersionStrategy : PasetoVersionStrategy -{ - public override string Encrypt(string payload, string footer, EncryptingCredentials encryptingCredentials) => throw new NotImplementedException(); - public override string Sign(string payload, string footer, SigningCredentials signingCredentials) => throw new NotImplementedException(); - public override PasetoSecurityToken Decrypt(PasetoToken token, IEnumerable decryptionKeys) => throw new System.NotImplementedException(); - public override PasetoSecurityToken Verify(PasetoToken token, IEnumerable signingKeys) => throw new System.NotImplementedException(); - - public static byte[] PreAuthEncodeSpy(IReadOnlyList pieces) => PreAuthEncode(pieces); -} \ No newline at end of file