using Elsa.Identity.Options; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; /// /// Validates the . /// public class ValidateIdentityTokenOptions : IValidateOptions { private const int MinimumSigningKeyByteLength = 32; private const string DemoEnvironmentName = "Demo"; private static readonly HashSet KnownDefaultSigningKeys = new(StringComparer.OrdinalIgnoreCase) { "sufficiently-large-secret-signing-key", "CHANGE_ME_TO_A_SECURE_RANDOM_KEY" }; private readonly IHostEnvironment? _environment; /// /// Initializes a new instance of the class. /// public ValidateIdentityTokenOptions() { } /// /// Initializes a new instance of the class. /// public ValidateIdentityTokenOptions(IHostEnvironment environment) { _environment = environment; } /// public ValidateOptionsResult Validate(string? name, IdentityTokenOptions options) { if (string.IsNullOrWhiteSpace(options.SigningKey)) return ValidateOptionsResult.Fail("SigningKey is required. Configure a secure random JWT signing key through configuration, environment variables, or a secrets manager."); var signingKey = options.SigningKey.Trim(); if (!string.Equals(signingKey, options.SigningKey, StringComparison.Ordinal)) return ValidateOptionsResult.Fail("SigningKey must not contain leading or trailing whitespace. Configure the exact secure random JWT signing key through configuration, environment variables, or a secrets manager."); if (KnownDefaultSigningKeys.Contains(signingKey)) { if (!IsDemoOrDevelopment()) return ValidateOptionsResult.Fail("SigningKey uses a known public default value. Replace it with a secure random JWT signing key through configuration, environment variables, or a secrets manager. Known defaults are allowed only in the Development or Demo environment."); return ValidateOptionsResult.Success; } if (!IsPrintableAscii(signingKey)) return ValidateOptionsResult.Fail("SigningKey contains non-printable or non-ASCII characters. Configure a secure random JWT signing key using only printable ASCII characters (0x20-0x7E) through configuration, environment variables, or a secrets manager."); if (signingKey.Length < MinimumSigningKeyByteLength) return ValidateOptionsResult.Fail($"SigningKey must be at least {MinimumSigningKeyByteLength} ASCII characters long. Configure a secure random JWT signing key through configuration, environment variables, or a secrets manager."); return ValidateOptionsResult.Success; } private bool IsDemoOrDevelopment() { return _environment is not null && (_environment.IsDevelopment() || string.Equals(_environment.EnvironmentName, DemoEnvironmentName, StringComparison.OrdinalIgnoreCase)); } private static bool IsPrintableAscii(string value) => value.All(x => x is >= ' ' and <= '~'); }