locked
Test Method for HMAC authentication for we api RRS feed

  • Question

  • User1306218145 posted

    Dear all,

    How can I write test method for HMAC Authentication for web api. Find the implementation of the class below;

    using System;
    using System.Web;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Http.Filters;
    using System.Security.Principal;
    using System.Web.Http.Results;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Security.Cryptography;
    using System.Web.Http;
    using System.Net;
    using System.Configuration;
    using XYZ.ABC.Contracts;
    using System.Web.Mvc;
    
    namespace XYZ.ABC.Client.Filters
    {
        public class HMACSecuredAttribute : Attribute, IAuthenticationFilter
        {
            private readonly IKeyManager _manager;
    
            public HMACSecuredAttribute()
            {
                this._manager = DependencyResolver.Current.GetService(typeof(IKeyManager)) as IKeyManager;
            }
    
    
            public bool AllowMultiple => false;
            private bool isValidAppId = false;
            private readonly UInt64 requestTimeOutInSeconds = UInt64.Parse(ConfigurationManager.AppSettings["requestTimeOutInSeconds"].ToString()); 
    
            public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
            {
                var req = context.Request;
    
                if(req.Headers.Authorization != null)
                {
                    var rawAuthzHeader = req.Headers.Authorization.Parameter.ToString();
                    var autherizationHeaderArray = GetAutherizationHeaderValues(rawAuthzHeader);
    
                    if (autherizationHeaderArray != null)
                    {
                        var APPId = autherizationHeaderArray[0];
                        var incomingBase64Signature = autherizationHeaderArray[1];
                        var nonce = autherizationHeaderArray[2];
                        var requestTimeStamp = autherizationHeaderArray[3];
    
                        var isValid = isValidRequest(req, APPId, incomingBase64Signature, nonce, requestTimeStamp);
    
                        if (isValid.Result)
                        {
                            var currentPrincipal = new GenericPrincipal(new GenericIdentity(APPId), null);
                            context.Principal = currentPrincipal;
                        }
                        else
                        {
                            context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
                        }
                    }
                    else
                    {
                        context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
                    }
                }
                else
                {
                    
                }
                return Task.FromResult(0);
            }
    
    
    
            public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
            {
                context.Result = new ResultWithChallenge(context.Result);
                return Task.FromResult(0);
            }
    
            private string[] GetAutherizationHeaderValues(string rawAuthzHeader)
            {
                var credArray = rawAuthzHeader.Split(':');
    
                if (credArray.Length == 4)
                {
                    return credArray;
                }
                else
                {
                    return null;
                }
            }
    
            private async Task<bool> isValidRequest(HttpRequestMessage req, string APPId, string incomingBase64Signature, string nonce, string requestTimeStamp)
            {
                string requestContentBase64String = "";
                string requestUri = HttpUtility.UrlEncode(req.RequestUri.AbsoluteUri.ToLower());
                string requestHttpMethod = req.Method.Method;
    
                isValidAppId = _manager.IsValidAppId(APPId);
    
                if (!isValidAppId)
                {
                    return false;
                }
    
                var sharedKey = _manager.ValidateKey(APPId);
    
                if (isReplayRequest(nonce, requestTimeStamp))
                {
                    return false;
                }
    
                byte[] hash = await ComputeHash(req.Content);
    
                if (hash != null)
                {
                    requestContentBase64String = Convert.ToBase64String(hash);
                }
    
                string data = String.Format("{0}{1}{2}{3}{4}{5}", APPId, requestHttpMethod, requestUri, requestTimeStamp, nonce, requestContentBase64String);
    
                var secretKeyBytes = Convert.FromBase64String(sharedKey);
    
                byte[] signature = Encoding.UTF8.GetBytes(data);
    
                using (HMACSHA256 hmac = new HMACSHA256(secretKeyBytes))
                {
                    byte[] signatureBytes = hmac.ComputeHash(signature);
    
                    return (incomingBase64Signature.Equals(Convert.ToBase64String(signatureBytes), StringComparison.Ordinal));
                }
    
            }
    
            private bool isReplayRequest(string nonce, string requestTimeStamp)
            {
                if (System.Runtime.Caching.MemoryCache.Default.Contains(nonce))
                {
                    return true;
                }
    
                DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
                TimeSpan currentTs = DateTime.UtcNow - epochStart;
    
                var serverTotalSeconds = Convert.ToUInt64(currentTs.TotalSeconds);
                var requestTotalSeconds = Convert.ToUInt64(requestTimeStamp);
    
                if ((serverTotalSeconds - requestTotalSeconds) > requestTimeOutInSeconds)
                {
                    return true;
                }
    
                System.Runtime.Caching.MemoryCache.Default.Add(nonce, requestTimeStamp, DateTimeOffset.UtcNow.AddSeconds(requestTimeOutInSeconds));
    
                return false;
            }
    
            private static async Task<byte[]> ComputeHash(HttpContent httpContent)
            {
                using (MD5 md5 = MD5.Create())
                {
                    byte[] hash = null;
                    var content = await httpContent.ReadAsByteArrayAsync();
                    if (content.Length != 0)
                    {
                        hash = md5.ComputeHash(content);
                    }
                    return hash;
                }
            }
        }
    
    
        public class ResultWithChallenge : IHttpActionResult
        {
            private readonly string authenticationScheme = "amx";
            private readonly IHttpActionResult next;
    
            public ResultWithChallenge(IHttpActionResult next)
            {
                this.next = next;
            }
    
            public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                var response = await next.ExecuteAsync(cancellationToken);
    
                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(authenticationScheme));
                }
    
                return response;
            }
        }
    }
    

    Kindly not that the authentication is attribute based.

    Thanks in advance

    Friday, June 1, 2018 10:32 AM

All replies