using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace AipGateway.Log
{
///
/// Token-based authentication for ASP .NET MVC REST web services.
/// Copyright (c) 2015 Kory Becker
/// http://primaryobjects.com/kory-becker
/// License MIT
///
public static class SecurityManager
{
private const string _alg = "HmacSHA256";
private const string _salt = "rz8LuOtFBXphj9WQfvFh";
private static int _expirationMinutes = 10;
///
/// Generates a token to be used in API calls.
/// The token is generated by hashing a message with a key, using HMAC SHA256.
/// The message is: username:ip:userAgent:timeStamp
/// The key is: password:ip:salt
/// The resulting token is then concatenated with username:timeStamp and the result base64 encoded.
///
/// API calls may then be validated by:
/// 1. Base64 decode the string, obtaining the token, username, and timeStamp.
/// 2. Ensure the timestamp is not expired.
/// 2. Lookup the user's password from the db (cached).
/// 3. Hash the username:ip:userAgent:timeStamp with the key of password:salt to compute a token.
/// 4. Compare the computed token with the one supplied and ensure they match.
///
public static string GenerateToken(string username, string password, string ip, string userAgent, long ticks)
{
string hash = string.Join(":", new string[] { username, ip, userAgent, ticks.ToString() });
string hashLeft = "";
string hashRight = "";
using (HMAC hmac = HMACSHA256.Create(_alg))
{
hmac.Key = Encoding.UTF8.GetBytes(GetHashedPassword(password));
hmac.ComputeHash(Encoding.UTF8.GetBytes(hash));
hashLeft = Convert.ToBase64String(hmac.Hash);
hashRight = string.Join(":", new string[] { username, ticks.ToString() });
}
return Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", hashLeft, hashRight)));
}
///
/// Returns a hashed password + salt, to be used in generating a token.
///
/// string - user's password
/// string - hashed password
public static string GetHashedPassword(string password)
{
string key = string.Join(":", new string[] { password, _salt });
using (HMAC hmac = HMACSHA256.Create(_alg))
{
// Hash the key.
hmac.Key = Encoding.UTF8.GetBytes(_salt);
hmac.ComputeHash(Encoding.UTF8.GetBytes(key));
return Convert.ToBase64String(hmac.Hash);
}
}
///
/// Checks if a token is valid.
///
/// string - generated either by GenerateToken() or via client with cryptojs etc.
/// string - IP address of client, passed in by RESTAuthenticate attribute on controller.
/// string - user-agent of client, passed in by RESTAuthenticate attribute on controller.
/// bool
public static bool IsTokenValid(string token, string ip, string userAgent)
{
bool result = false;
try
{
// Base64 decode the string, obtaining the token:username:timeStamp.
string key = Encoding.UTF8.GetString(Convert.FromBase64String(token));
// Split the parts.
string[] parts = key.Split(new char[] { ':' });
if (parts.Length == 3)
{
// Get the hash message, username, and timestamp.
string hash = parts[0];
string username = parts[1];
long ticks = long.Parse(parts[2]);
DateTime timeStamp = new DateTime(ticks);
// Ensure the timestamp is valid.
bool expired = Math.Abs((DateTime.UtcNow - timeStamp).TotalMinutes) > _expirationMinutes;
if (!expired)
{
//
// Lookup the user's account from the db.
//
if (username == "john")
{
string password = "password";
// Hash the message with the key to generate a token.
string computedToken = GenerateToken(username, password, ip, userAgent, ticks);
// Compare the computed token with the one supplied and ensure they match.
result = (token == computedToken);
}
}
}
}
catch
{
}
return result;
}
}
}