Fix dynamic signing key switching

This commit is contained in:
Jordan Dominion
2024-09-12 23:38:41 -04:00
parent 99baac333e
commit b52938545c
4 changed files with 43 additions and 54 deletions
@@ -12,9 +12,9 @@ namespace Tgstation.Server.Host.Security
public interface ITokenFactory
{
/// <summary>
/// Gets or sets the <see cref="ITokenFactory"/>'s signing key.
/// Gets or sets the <see cref="ITokenFactory"/>'s signing key <see cref="byte"/>s.
/// </summary>
ReadOnlySpan<byte> SigningKey { get; set; }
ReadOnlySpan<byte> SigningKeyBytes { get; set; }
/// <summary>
/// The <see cref="TokenValidationParameters"/> for the <see cref="ITokenFactory"/>.
@@ -20,49 +20,49 @@ namespace Tgstation.Server.Host.Security
sealed class TokenFactory : ITokenFactory
{
/// <inheritdoc />
public TokenValidationParameters ValidationParameters { get; private set; }
public TokenValidationParameters ValidationParameters { get; }
/// <inheritdoc />
public ReadOnlySpan<byte> SigningKey
public ReadOnlySpan<byte> SigningKeyBytes
{
get => signingKey;
get => signingKey.Key;
[MemberNotNull(nameof(signingKey))]
[MemberNotNull(nameof(tokenHeader))]
set
{
signingKey = value.ToArray();
SetValidationParameters();
signingKey = new SymmetricSecurityKey(value.ToArray());
tokenHeader = new JwtHeader(
new SigningCredentials(
signingKey,
SecurityAlgorithms.HmacSha256));
}
}
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="TokenFactory"/>.
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="SecurityConfiguration"/> for the <see cref="TokenFactory"/>.
/// </summary>
readonly SecurityConfiguration securityConfiguration;
/// <summary>
/// The <see cref="JwtHeader"/> for generating tokens.
/// </summary>
readonly JwtHeader tokenHeader;
/// <summary>
/// The <see cref="JwtSecurityTokenHandler"/> used to generate <see cref="TokenResponse.Bearer"/> <see cref="string"/>s.
/// </summary>
readonly JwtSecurityTokenHandler tokenHandler;
/// <summary>
/// Backing field for <see cref="SigningKey"/>.
/// Backing field for <see cref="SigningKeyBytes"/>.
/// </summary>
byte[] signingKey;
SymmetricSecurityKey signingKey;
/// <summary>
/// The <see cref="JwtHeader"/> for generating tokens.
/// </summary>
JwtHeader tokenHeader;
/// <summary>
/// Initializes a new instance of the <see cref="TokenFactory"/> class.
/// </summary>
/// <param name="cryptographySuite">The <see cref="ICryptographySuite"/> used for generating the <see cref="ValidationParameters"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> used to generate the issuer name.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="securityConfiguration"/>.</param>
public TokenFactory(
ICryptographySuite cryptographySuite,
@@ -70,20 +70,33 @@ namespace Tgstation.Server.Host.Security
IOptions<SecurityConfiguration> securityConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(cryptographySuite);
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions));
signingKey = String.IsNullOrWhiteSpace(securityConfiguration.CustomTokenSigningKeyBase64)
SigningKeyBytes = String.IsNullOrWhiteSpace(securityConfiguration.CustomTokenSigningKeyBase64)
? cryptographySuite.GetSecureBytes(securityConfiguration.TokenSigningKeyByteCount)
: Convert.FromBase64String(securityConfiguration.CustomTokenSigningKeyBase64);
SetValidationParameters();
ValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (_, _, _, _) => Enumerable.Repeat(signingKey, 1),
ValidateIssuer = true,
ValidIssuer = assemblyInformationProvider.AssemblyName.Name,
ValidateLifetime = true,
ValidateAudience = true,
ValidAudience = typeof(TokenResponse).Assembly.GetName().Name,
ClockSkew = TimeSpan.FromMinutes(securityConfiguration.TokenClockSkewMinutes),
RequireSignedTokens = true,
RequireExpirationTime = true,
};
tokenHeader = new JwtHeader(
new SigningCredentials(
ValidationParameters.IssuerSigningKey,
SecurityAlgorithms.HmacSha256));
tokenHandler = new JwtSecurityTokenHandler();
}
@@ -133,29 +146,5 @@ namespace Tgstation.Server.Host.Security
return tokenResponse;
}
/// <summary>
/// Initializes <see cref="ValidationParameters"/> based on fields.
/// </summary>
[MemberNotNull(nameof(ValidationParameters))]
void SetValidationParameters()
=> ValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(signingKey),
ValidateIssuer = true,
ValidIssuer = assemblyInformationProvider.AssemblyName.Name,
ValidateLifetime = true,
ValidateAudience = true,
ValidAudience = typeof(TokenResponse).Assembly.GetName().Name,
ClockSkew = TimeSpan.FromMinutes(securityConfiguration.TokenClockSkewMinutes),
RequireSignedTokens = true,
RequireExpirationTime = true,
};
}
}
@@ -574,7 +574,7 @@ namespace Tgstation.Server.Host.Swarm
SwarmRegistrationResponse CreateResponse() => new()
{
TokenSigningKeyBase64 = Convert.ToBase64String(tokenFactory.SigningKey),
TokenSigningKeyBase64 = Convert.ToBase64String(tokenFactory.SigningKeyBytes),
};
var registrationIdsAndTimes = this.registrationIdsAndTimes!;
@@ -1320,7 +1320,7 @@ namespace Tgstation.Server.Host.Swarm
return SwarmRegistrationResult.PayloadFailure;
}
tokenFactory.SigningKey = Convert.FromBase64String(registrationResponse.TokenSigningKeyBase64);
tokenFactory.SigningKeyBytes = Convert.FromBase64String(registrationResponse.TokenSigningKeyBase64);
}
catch (Exception ex)
{
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Swarm.Tests
private class MockTokenFactory : ITokenFactory
{
public ReadOnlySpan<byte> SigningKey
public ReadOnlySpan<byte> SigningKeyBytes
{
get => [0, 1, 2, 3, 4];
set