using System;
using System.Text;
using System.Security.Cryptography;

namespace LumiSoft.Net.AUTH
{
	/// <summary>
	/// Provides helper methods for authentications(APOP,CRAM-MD5,DIGEST-MD5).
	/// </summary>
	public class AuthHelper
	{
		#region method Apop

		/// <summary>
		/// Calculates APOP authentication compare value.
		/// </summary>
		/// <param name="password">Password.</param>
		/// <param name="passwordTag">Password tag.</param>
		/// <returns>Returns value what must be used for comparing passwords.</returns>
		public static string Apop(string password,string passwordTag)
		{
			/* RFC 1939 7. APOP
			 *
			 * value = Hex(Md5(passwordTag + password))
			*/

			return Hex(Md5(passwordTag + password));
		}

		#endregion


		#region method Cram_Md5

		/// <summary>
		/// Calculates CRAM-MD5 authentication compare value.
		/// </summary>
		/// <param name="password">Password.</param>
		/// <param name="hashKey">Hash calculation key</param>
		/// <returns>Returns value what must be used for comparing passwords.</returns>
		public static string Cram_Md5(string password,string hashKey)
		{
			/* RFC 2195 AUTH CRAM-MD5
			 * 
			 * value = Hex(HmacMd5(hashKey,password))
			*/
		
			return Hex(HmacMd5(hashKey,password));
		}

		#endregion


		#region method Digest_Md5

		/// <summary>
		/// Calculates DIGEST-MD5 authentication compare value.
		/// </summary>
		/// <param name="client_server">Specifies if client or server value calculated. 
		/// Client and server has diffrent calculation method.</param>
		/// <param name="realm">Use domain or machine name for this.</param>
		/// <param name="userName">User name.</param>
		/// <param name="password">Password.</param>
		/// <param name="nonce">Server password tag.</param>
		/// <param name="cnonce">Client password tag.</param>
		/// <param name="digest_uri"></param>
		/// <returns>Returns value what must be used for comparing passwords.</returns>
		public static string Digest_Md5(bool client_server,string realm,string userName,string password,string nonce,string cnonce,string digest_uri)
		{
			/* RFC 2831 AUTH DIGEST-MD5
			 * 
			 * qop = "auth";      // We support auth only auth-int and auth-conf isn't supported
			 * nc  = "00000001"
			 * 
			 * A1 = Md5(userName + ":" + realm + ":" + passw) + ":" + nonce + ":" + cnonce
			 * A2(client response) = "AUTHENTICATE:" + digest_uri
			 * A2(server response) = ":" + digest_uri
			 * 
			 * resp-value = Hex(Md5(Hex(Md5(a1)) + ":" + (nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + Hex(Md5(a2)))));
			*/

			//	string realm      = "elwood.innosoft.com";
			//	string userName   = "chris";			
			//	string passw      = "secret";
			//	string nonce      = "OA6MG9tEQGm2hh";
			//	string cnonce     = "OA6MHXh6VqTrRk";
			//	string digest_uri = "imap/elwood.innosoft.com";

			string qop = "auth";
			string nc  = "00000001";

			string a1 = Md5(userName + ":" + realm + ":" + password) + ":" + nonce + ":" + cnonce;
			string a2 = "";
			if(client_server){
				a2 = "AUTHENTICATE:" + digest_uri;
			}
			else{
				a2 = ":" + digest_uri;
			}

			return Hex(Md5(Hex(Md5(a1)) + ":" + (nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + Hex(Md5(a2)))));
		}

		#endregion

		#region method Create_Digest_Md5_ServerResponse

		/// <summary>
		/// Creates AUTH Digest-md5 server response what server must send to client.
		/// </summary>
		/// <param name="realm">Use domain or machine name for this.</param>
		/// <param name="nonce">Server password tag.Random hex string is suggested.</param>
		/// <returns></returns>
		public static string Create_Digest_Md5_ServerResponse(string realm,string nonce)
		{
			return "realm=\"" + realm + "\",nonce=\"" + nonce + "\",qop=\"auth\",algorithm=md5-sess";
		}

		#endregion

		#region method GenerateNonce

		/// <summary>
		/// Generates random nonce value.
		/// </summary>
		/// <returns></returns>
		public static string GenerateNonce()
		{
			return Guid.NewGuid().ToString().Replace("-","").Substring(0,16);
		}

		#endregion


		#region method HmacMd5

		/// <summary>
		/// Calculates keyed md5 hash from specifieed text and with specified hash key.
		/// </summary>
		/// <param name="hashKey"></param>
		/// <param name="text"></param>
		/// <returns></returns>
		public static string HmacMd5(string hashKey,string text)
		{
			HMACMD5 kMd5 = new HMACMD5(Encoding.Default.GetBytes(text));			
			return Encoding.Default.GetString(kMd5.ComputeHash(Encoding.ASCII.GetBytes(hashKey)));
		}

		#endregion

		#region method Md5

		/// <summary>
		/// Calculates md5 hash from specified string.
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns>
		public static string Md5(string text)
		{
			MD5 md5 = new MD5CryptoServiceProvider();			
			byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(text));

			return System.Text.Encoding.Default.GetString(hash);
		}

		#endregion

		#region method Hex

		/// <summary>
		/// Converts specified string to hexa string.
		/// </summary>
		/// <param name="text"></param>
		/// <returns></returns> 
		public static string Hex(string text)
		{
			return BitConverter.ToString(Encoding.Default.GetBytes(text)).ToLower().Replace("-","");
		}

		#endregion

		#region method Base64en

		/// <summary>
		/// Encodes specified string to base64 string.
		/// </summary>
		/// <param name="text">Text to encode.</param>
		/// <returns>Returns encoded string.</returns>
		public static string Base64en(string text)
		{
			return Convert.ToBase64String(Encoding.Default.GetBytes(text));
		}

		#endregion

		#region method Base64de

		/// <summary>
		/// Decodes specified base64 string.
		/// </summary>
		/// <param name="text">Base64 string to decode.</param>
		/// <returns>Returns decoded string.</returns>
		public static string Base64de(string text)
		{
			return Encoding.Default.GetString(Convert.FromBase64String(text));
		}

		#endregion
	}
}
