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