68 lines
2.1 KiB
C#
68 lines
2.1 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace Tiku.Application.Security;
|
|
|
|
public sealed class JwtOptions
|
|
{
|
|
[Required] public string Issuer { get; set; } = "tiku-backend";
|
|
|
|
[Required] public string Audience { get; set; } = "tiku-api";
|
|
|
|
[Required] public string KeyId { get; set; } = "development-ephemeral";
|
|
|
|
public string PrivateKeyPem { get; set; } = string.Empty;
|
|
|
|
public Dictionary<string, string> PublicKeys { get; set; } = new(StringComparer.Ordinal);
|
|
|
|
[Range(1, 1440)] public int AccessTokenMinutes { get; set; } = 15;
|
|
|
|
[Range(1, 365)] public int RefreshTokenDays { get; set; } = 30;
|
|
|
|
public static bool BeValid(JwtOptions options, bool isProduction)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(options.Issuer) ||
|
|
string.IsNullOrWhiteSpace(options.Audience) ||
|
|
string.IsNullOrWhiteSpace(options.KeyId) ||
|
|
options.AccessTokenMinutes != 15 ||
|
|
(isProduction && string.Equals(
|
|
options.KeyId,
|
|
"development-ephemeral",
|
|
StringComparison.Ordinal)))
|
|
return false;
|
|
|
|
if (string.IsNullOrWhiteSpace(options.PrivateKeyPem))
|
|
{
|
|
if (isProduction) return false;
|
|
}
|
|
else if (!IsValidRsaPem(options.PrivateKeyPem, true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return options.PublicKeys.All(pair =>
|
|
!string.IsNullOrWhiteSpace(pair.Key) &&
|
|
!string.Equals(pair.Key, options.KeyId, StringComparison.Ordinal) &&
|
|
IsValidRsaPem(pair.Value, false));
|
|
}
|
|
|
|
private static bool IsValidRsaPem(string pem, bool requirePrivateKey)
|
|
{
|
|
try
|
|
{
|
|
using var rsa = RSA.Create();
|
|
rsa.ImportFromPem(pem);
|
|
if (rsa.KeySize < 2048) return false;
|
|
|
|
if (requirePrivateKey) _ = rsa.ExportParameters(true);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception exception) when (
|
|
exception is ArgumentException or
|
|
CryptographicException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
} |