first commit
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Landmark.OLA.Application.Common.Security
|
||||
{
|
||||
public static class CookieCryptor
|
||||
{
|
||||
private static byte[] RandomBytes(int len)
|
||||
{
|
||||
var b = new byte[len];
|
||||
RandomNumberGenerator.Fill(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
private static byte[] DeriveKey(string pass, byte[] salt, int keyBytes = 32, int iterations = 100_000)
|
||||
=> new Rfc2898DeriveBytes(pass, salt, iterations, HashAlgorithmName.SHA256).GetBytes(keyBytes);
|
||||
|
||||
|
||||
public static string EncryptJwt(string jwt, string key)
|
||||
{
|
||||
var salt = RandomBytes(32);
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
aes.GenerateIV();
|
||||
aes.Key = DeriveKey(key, salt, 32);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
ms.Write(salt, 0, salt.Length);
|
||||
ms.Write(aes.IV, 0, aes.IV.Length);
|
||||
|
||||
using (var crypto = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
using (var writer = new StreamWriter(crypto, Encoding.UTF8))
|
||||
{
|
||||
writer.Write(jwt);
|
||||
writer.Flush();
|
||||
crypto.FlushFinalBlock();
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(ms.ToArray());
|
||||
}
|
||||
|
||||
public static string DecryptJwt(string cipherText, string passPhrase)
|
||||
{
|
||||
var all = Convert.FromBase64String(cipherText);
|
||||
|
||||
var saltLen = 32;
|
||||
var ivLen = 16;
|
||||
|
||||
if (all.Length < saltLen + ivLen)
|
||||
throw new CryptographicException("Ciphertext too short.");
|
||||
|
||||
var salt = new byte[saltLen];
|
||||
Buffer.BlockCopy(all, 0, salt, 0, saltLen);
|
||||
|
||||
var iv = new byte[ivLen];
|
||||
Buffer.BlockCopy(all, saltLen, iv, 0, ivLen);
|
||||
|
||||
var cipher = new byte[all.Length - saltLen - ivLen];
|
||||
Buffer.BlockCopy(all, saltLen + ivLen, cipher, 0, cipher.Length);
|
||||
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = DeriveKey(passPhrase, salt, 32);
|
||||
aes.IV = iv;
|
||||
|
||||
using var ms = new MemoryStream(cipher);
|
||||
using var crypto = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read);
|
||||
using var reader = new StreamReader(crypto, Encoding.UTF8);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Landmark.OLA.Models.Core;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace OnlineBankingAuth.Tokens
|
||||
{
|
||||
public interface ITokenService
|
||||
{
|
||||
string CreateOnlineBankingJWT(OnlineBankingToken payload);
|
||||
string RenewOnlineBankingJWT(HttpContext context);
|
||||
string RenewAuthorization(HttpContext context);
|
||||
OnlineBankingToken GetAuthenticatedDataFromCookie(HttpContext context);
|
||||
OnlineBankingToken GetAuthenticatedUserDataFromJwt(string jwt);
|
||||
string CreateAuthMetaToken(MemberMeta meta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using Landmark.OLA.Application.Common.Security;
|
||||
using Landmark.OLA.Models.Core;
|
||||
using Landmark.OLA.Models.OnlineBanking;
|
||||
using OnlineBankingAuth.Configuration;
|
||||
|
||||
namespace OnlineBankingAuth.Tokens
|
||||
{
|
||||
public class TokenService : ITokenService
|
||||
{
|
||||
private readonly SecuritySettings _settings;
|
||||
|
||||
public TokenService(
|
||||
IOptions<SecuritySettings> settings)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public string CreateOnlineBankingJWT(OnlineBankingToken payload)
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(Constants.MemberID, payload.MemberID ?? string.Empty),
|
||||
new(Constants.IndividualID, payload.IndividualID ?? string.Empty),
|
||||
new(Constants.FI_ID, payload.FI_ID ?? string.Empty),
|
||||
new(Constants.InProgressLoanId, payload.LoanId ?? string.Empty),
|
||||
new(Constants.IsTwoFactor, payload.IsTwoFactor.ToString())
|
||||
};
|
||||
|
||||
var jwt = new JwtSecurityToken(
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddMinutes(15)
|
||||
);
|
||||
|
||||
var token = handler.WriteToken(jwt);
|
||||
var encrypted = CookieCryptor.EncryptJwt(token, _settings.TokenSecret);
|
||||
return Constants.TokenPrefix + encrypted;
|
||||
}
|
||||
|
||||
public string RenewOnlineBankingJWT(HttpContext context)
|
||||
{
|
||||
var cookies = context.Request.Cookies;
|
||||
string cookieValue = null;
|
||||
|
||||
if (cookies[Constants.TokenPayload] != null)
|
||||
{
|
||||
var jwtCookie = cookies[Constants.TokenPayload][Constants.TokenPrefix.Length..];
|
||||
var jwt = CookieCryptor.DecryptJwt(jwtCookie, _settings.TokenSecret);
|
||||
|
||||
if (!IsTokenReadable(jwt))
|
||||
{
|
||||
throw new AuthenticationException("Could not parse token.");
|
||||
}
|
||||
|
||||
var prevToken = ReadJwtSecurityToken(jwt);
|
||||
cookieValue = Constants.TokenPrefix + GenerateEncodedToken(prevToken);
|
||||
}
|
||||
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
public string RenewAuthorization(HttpContext context)
|
||||
{
|
||||
var cookies = context.Request.Cookies;
|
||||
string cookieValue = null;
|
||||
|
||||
if (cookies[Constants.TokenAuthorization] != null)
|
||||
{
|
||||
cookieValue = cookies[Constants.TokenAuthorization];
|
||||
}
|
||||
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
public OnlineBankingToken GetAuthenticatedDataFromCookie(HttpContext context)
|
||||
{
|
||||
var cookies = context.Request.Cookies;
|
||||
|
||||
if (!string.IsNullOrEmpty(cookies[Constants.TokenPayload]))
|
||||
{
|
||||
return GetAuthenticatedUserDataFromJwt(cookies[Constants.TokenPayload][Constants.TokenPrefix.Length..]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GenerateEncodedToken(JwtSecurityToken jwtToken)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler { };
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(jwtToken.Claims),
|
||||
Expires = DateTime.UtcNow.AddMinutes(15)
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateEncodedJwt(tokenDescriptor);
|
||||
var encryptedToken = CookieCryptor.EncryptJwt(token, _settings.TokenSecret);
|
||||
|
||||
return encryptedToken;
|
||||
}
|
||||
|
||||
private static bool TryGet(JwtSecurityToken jwt, string key, out string? value)
|
||||
{
|
||||
var claim = jwt.Claims.FirstOrDefault(c => c.Type == key)
|
||||
?? jwt.Claims.FirstOrDefault(c => string.Equals(c.Type, key, StringComparison.OrdinalIgnoreCase));
|
||||
if (claim is not null) { value = claim.Value; return true; }
|
||||
|
||||
foreach (var kv in jwt.Payload)
|
||||
if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = Convert.ToString(kv.Value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public OnlineBankingToken GetAuthenticatedUserDataFromJwt(string encryptedJwt)
|
||||
{
|
||||
var jwt = CookieCryptor.DecryptJwt(encryptedJwt, _settings.TokenSecret);
|
||||
var token = ReadJwtSecurityToken(jwt);
|
||||
|
||||
var data = new OnlineBankingToken();
|
||||
|
||||
if (!TryGet(token, Constants.MemberID, out var memberId)) return null;
|
||||
if (!TryGet(token, Constants.FI_ID, out var fiId)) return null;
|
||||
|
||||
data.MemberID = memberId!;
|
||||
data.FI_ID = fiId!;
|
||||
|
||||
if (TryGet(token, Constants.IsTwoFactor, out var twoFa))
|
||||
data.IsTwoFactor = bool.TryParse(twoFa, out var b) && b;
|
||||
|
||||
if (TryGet(token, Constants.IndividualID, out var indId))
|
||||
data.IndividualID = indId;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static JwtSecurityToken ReadJwtSecurityToken(string jwt)
|
||||
{
|
||||
return new JwtSecurityTokenHandler().ReadJwtToken(jwt);
|
||||
}
|
||||
|
||||
private static bool IsTokenReadable(string jwt)
|
||||
{
|
||||
return new JwtSecurityTokenHandler().CanReadToken(jwt);
|
||||
}
|
||||
|
||||
public string CreateAuthMetaToken(MemberMeta meta)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(meta);
|
||||
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
return Convert.ToBase64String(plainTextBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user