sec: use HMAC to encrypt state

This commit is contained in:
Polianin Nikita 2024-12-18 07:24:33 +03:00
parent 08aeb7ea3c
commit 598ebabc5c
2 changed files with 14 additions and 7 deletions

View File

@ -61,7 +61,7 @@ public static class DependencyInjection
providers.Add(provider, (clientId, secret)); providers.Add(provider, (clientId, secret));
} }
services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers)); services.AddSingleton(provider => new OAuthService(provider.GetRequiredService<ILogger<OAuthService>>(), providers, configuration["SECURITY_ENCRYPTION_TOKEN"]!));
return services; return services;
} }

View File

@ -10,13 +10,15 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Security; using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Mirea.Api.Security.Services; namespace Mirea.Api.Security.Services;
public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers) public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider, (string ClientId, string Secret)> providers, string secretKey)
{ {
private static readonly Dictionary<OAuthProvider, OAuthProviderUrisData> ProviderData = new() private static readonly Dictionary<OAuthProvider, OAuthProviderUrisData> ProviderData = new()
{ {
@ -97,6 +99,12 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
return userInfo?.MapToInternalUser(); return userInfo?.MapToInternalUser();
} }
private static string GetHmacString(RequestContextInfo contextInfo, string secretKey)
{
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
return Convert.ToBase64String(hmac.ComputeHash(
Encoding.UTF8.GetBytes($"{contextInfo.Fingerprint}_{contextInfo.Ip}_{contextInfo.UserAgent}")));
}
public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider) public Uri GetProviderRedirect(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUri, OAuthProvider provider)
{ {
@ -106,7 +114,7 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
"&response_type=code" + "&response_type=code" +
$"&redirect_uri={redirectUri}" + $"&redirect_uri={redirectUri}" +
$"&scope={ProviderData[provider].Scope}" + $"&scope={ProviderData[provider].Scope}" +
$"&state={new RequestContextInfo(context, cookieOptions).Fingerprint}_{Enum.GetName(provider)}"; $"&state={GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey)}_{Enum.GetName(provider)}";
@ -121,8 +129,6 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default) public async Task<(OAuthProvider provider, OAuthUser User)> LoginOAuth(HttpContext context, CookieOptionsParameters cookieOptions, string redirectUrl, string code, string state, CancellationToken cancellation = default)
{ {
var requestContext = new RequestContextInfo(context, cookieOptions);
var partsState = state.Split('_'); var partsState = state.Split('_');
if (!Enum.TryParse<OAuthProvider>(partsState.Last(), true, out var provider) || if (!Enum.TryParse<OAuthProvider>(partsState.Last(), true, out var provider) ||
@ -133,9 +139,10 @@ public class OAuthService(ILogger<OAuthService> logger, Dictionary<OAuthProvider
throw new InvalidOperationException("Invalid authorization request."); throw new InvalidOperationException("Invalid authorization request.");
} }
var fingerprint = string.Join("_", partsState.SkipLast(1)); var secretStateData = string.Join("_", partsState.SkipLast(1));
var secretData = GetHmacString(new RequestContextInfo(context, cookieOptions), secretKey);
if (requestContext.Fingerprint != fingerprint) if (secretData != secretStateData)
{ {
logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected."); logger.LogWarning("Fingerprint mismatch. Possible CSRF attack detected.");
throw new SecurityException("Suspicious activity detected. Please try again."); throw new SecurityException("Suspicious activity detected. Please try again.");