Click here to Skip to main content
15,878,814 members
Articles / Programming Languages / C#

STUN Client

Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
20 Apr 2007CPOL 321.8K   14.8K   85  
STUN client C# implementation with sample application
using System;
using System.Collections.Generic;
using System.Text;

namespace LumiSoft.Net.AUTH
{
    /// <summary>
    /// Implements http digest access authentication. Defined in RFC 2617.
    /// </summary>
    public class Auth_HttpDigest
    {
        private string m_Method     = "";
        private string m_Realm      = "";
        private string m_Nonce      = "";
        private string m_Opaque     = "";
        private string m_Algorithm  = "";
        private string m_Response   = "";
        private string m_UserName   = "";
        private string m_Uri        = "";
        private string m_Qop        = "";
        private string m_Cnonce     = "";
        private string m_NonceCount = "";

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="digestResponse">Client returned digest response.</param>
        /// <param name="requestMethod">Request method.</param>
        public Auth_HttpDigest(string digestResponse,string requestMethod)
        {
            m_Method = requestMethod;

            Parse(digestResponse);
        }

        /// <summary>
        /// Client constructor. This is used to build valid Authorization response to server.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        /// <param name="cnonce">Client nonce value.</param>
        /// <param name="uri">Request URI.</param>
        /// <param name="digestResponse">Server authenticate resposne.</param>
        /// <param name="requestMethod">Request method.</param>
        public Auth_HttpDigest(string userName,string password,string cnonce,string uri,string digestResponse,string requestMethod)
        {            
            Parse(digestResponse);

            m_UserName   = userName;
            m_Method     = requestMethod;
            m_Cnonce     = cnonce;
            m_Uri        = uri;            
            m_Qop        = "auth";
            m_NonceCount = "00000001";
            m_Response   = CalculateResponse(userName,password);
        }

                
        #region method Authenticate

        /// <summary>
        /// Authenticates specified user and password using this class parameters.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        /// <returns>Returns true if authenticated, otherwise false.</returns>
        public bool Authenticate(string userName,string password)
        {                        
            // Check that our computed digest is same as client provided.
            if(this.Response == CalculateResponse(userName,password)){
                return true;
            }
            else{
                return false;
            }
        }

        #endregion


        #region method Parse

        /// <summary>
        /// Parses authetication info from client digest response.
        /// </summary>
        /// <param name="digestResponse">Client returned digest response.</param>
        private void Parse(string digestResponse)
        {
            string[] parameters = TextUtils.SplitQuotedString(digestResponse,',');
            foreach(string parameter in parameters){
                string[] name_value = parameter.Split(new char[]{'='},2);
                string   name       = name_value[0].Trim();

                if(name_value.Length == 2){
                    if(name.ToLower() == "realm"){
                        m_Realm = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "nonce"){
                        m_Nonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "uri"){
                        m_Uri = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "qop"){
                        m_Qop = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "nc"){
                        m_NonceCount = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "cnonce"){
                        m_Cnonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "response"){
                        m_Response = TextUtils.UnQuoteString(name_value[1]);
                    }
                    if(name.ToLower() == "opaque"){
                        m_Opaque = TextUtils.UnQuoteString(name_value[1]);
                    }                    
                    if(name.ToLower() == "username"){
                        m_UserName = TextUtils.UnQuoteString(name_value[1]);
                    }
                }
            }
        }

        #endregion


        #region method CalculateResponse

        /// <summary>
        /// Calculates response value.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <param name="password">User password.</param>
        /// <returns>Returns calculated rsponse value.</returns>
        private string CalculateResponse(string userName,string password)
        {
            /*
                MD5
                    A1 = username-value ":" realm-value ":" passwd
            
                MD5-sess
                    A1 = md5(username-value ":" realm-value ":" passwd) ":" nonce-value ":" cnonce-value
                                         
                qop not peresent or auth
                    A2 = Method ":" digest-uri-value
             
                qop auth-int
                    A2 = Method ":" digest-uri-value ":" md5(entity-body)
              
                qop present
                    response = md5(md5(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" md5(A2))
                          
                qop not present
                    response = md5(md5(A1) ":" nonce-value ":" md5(A2))
                        
            */

            string a1 = "";
            string a2 = "";
            // Create A1
            if(this.Algorithm == "" || this.Algorithm.ToLower() == "md5"){
                a1 = userName + ":" + this.Realm + ":" + password;
            }
            else if(this.Algorithm.ToLower() == "md5-sess"){
                a1 = Core.ComputeMd5(userName + ":" + this.Realm + ":" + password) + ":" + this.Nonce + ":" + this.CNonce;
            }
            else{
                throw new ArgumentException("Invalid Algorithm value '" + this.Algorithm + "' !");
            }
            // Create A2            
            if(this.Qop == "" || this.Qop.ToLower() == "auth"){
                a2 = m_Method.ToUpper() + ":" + this.Uri;
            }
            else{
                throw new ArgumentException("Invalid qop value '" + this.Qop + "' !");
            }

            // Calculate response value. we support auth only.
            string response = "";
            // qop
            if(this.Qop != ""){
                response = Core.ComputeMd5(Core.ComputeMd5(a1) + ":" + this.Nonce + ":" + this.NonceCount + ":" + this.CNonce + ":" + this.Qop + ":" + Core.ComputeMd5(a2));
            }
            // qop not present
            else{                
                response = Core.ComputeMd5(Core.ComputeMd5(a1) + ":" + this.Nonce + ":" + Core.ComputeMd5(a2));
            }

            return response;
        }

        #endregion


        #region method ToString

        /// <summary>
        /// Converts this to valid digest string.
        /// </summary>
        /// <returns>Returns digest string.</returns>
        public override string ToString()
        {
            StringBuilder retVal = new StringBuilder();
            retVal.Append("realm=\"" + m_Realm + "\",");
            retVal.Append("username=\"" + m_UserName + "\",");
            if(!string.IsNullOrEmpty(m_Qop)){
                retVal.Append("qop=\"" + m_Qop + "\",");
            }
            retVal.Append("nonce=\"" + m_Nonce + "\",");
            retVal.Append("nc=\"" + m_NonceCount + "\",");
            retVal.Append("cnonce=\"" + m_Cnonce + "\",");
            retVal.Append("response=\"" + m_Response + "\",");
            retVal.Append("opaque=\"" + m_Opaque + "\",");
            retVal.Append("uri=\"" + m_Uri + "\"");            

            return retVal.ToString();
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets a string to be displayed to users so they know which username and password 
        /// to use. This string should contain at least the name of the host performing the 
        /// authentication and might additionally indicate the collection of users who might have access.
        /// An example might be "registered_users@gotham.news.com".
        /// </summary>
        public string Realm
        {
            get{ return m_Realm; }
        }

        /*
        public string Domain
        {
            get{ return ""; }
        }*/

        /// <summary>
        /// Gets a server-specified unique data string. It is recommended that this 
        /// string be base64 or hexadecimal data. 
        /// Suggested value: base64(time-stamp hex(time-stamp ":" ETag ":" private-key)).
        /// </summary>
        public string Nonce
        {
            get{ return m_Nonce; }
        }

        /// <summary>
        /// Gets string of data, specified by the server, which should be returned by the client unchanged.
        /// It is recommended that this string be base64 or hexadecimal data.
        /// </summary>
        public string Opaque
        {
            get{ return m_Opaque; }
        }

        /*
        public bool Stale
        {
            get{ return false; }
        }
        */
        
        /// <summary>
        /// Gets algorithm to use to produce the digest and a checksum.
        /// This is normally MD5 or MD5-sess.
        /// </summary>
        public string Algorithm
        {
            get{ return m_Algorithm; }
        }
        

        /// <summary>
        /// Gets a string of 32 hex digits computed by HTTP digest algorithm, 
        /// which proves that the user knows a password.
        /// </summary>
        public string Response
        {
            get{ return m_Response; }
        }

        /// <summary>
        /// Gets user name.
        /// </summary>
        public string UserName
        {
            get{ return m_UserName; }
        }

        /// <summary>
        /// Gets the URI from Request-URI.
        /// </summary>
        public string Uri
        {
            get{ return m_Uri; }
        }

        /// <summary>
        /// Gets value what indicates "quality of protection" the client has applied to
        /// the message. If present, its value MUST be one of the alternatives the server indicated
        /// it supports in the WWW-Authenticate header. This directive is optional in order to preserve 
        /// backward compatibility.
        /// </summary>
        public string Qop
        {
            get{ return m_Qop; }
        }

        /// <summary>
        /// Gets Client nonce value. This MUST be specified if a qop directive is sent (see above), and
        /// MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field.
        /// </summary>
        public string CNonce
        {
            get{ return m_Cnonce; }
        }

        /// <summary>
        /// Gets nonce ount. This MUST be specified if a qop directive is sent (see above), and
        /// MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate 
        /// header field.  The nc-value is the hexadecimal count of the number of requests.
        /// </summary>
        public string NonceCount
        {
            get{ return m_NonceCount;}
        }
                
        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions