mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-18 19:43:02 +01:00
Fix dynamic signing key switching
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user