/// <summary>
        /// Parses DIGEST-MD5 challenge from challenge-string.
        /// </summary>
        /// <param name="challenge">Challenge string.</param>
        /// <returns>Returns DIGEST-MD5 challenge.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>challenge</b> is null reference.</exception>
        /// <exception cref="ParseException">Is raised when challenge parsing + validation fails.</exception>
        public static AUTH_SASL_DigestMD5_Challenge Parse(string challenge)
        {
            if(challenge == null){
                throw new ArgumentNullException("challenge");
            }

            AUTH_SASL_DigestMD5_Challenge retVal = new AUTH_SASL_DigestMD5_Challenge();

            string[] parameters = TextUtils.SplitQuotedString(challenge,',');
            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"){
                        retVal.m_Realm = TextUtils.UnQuoteString(name_value[1]).Split(',');
                    }
                    else if(name.ToLower() == "nonce"){
                        retVal.m_Nonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "qop"){
                        retVal.m_QopOptions = TextUtils.UnQuoteString(name_value[1]).Split(',');
                    }
                    else if(name.ToLower() == "stale"){
                        retVal.m_Stale = Convert.ToBoolean(TextUtils.UnQuoteString(name_value[1]));
                    }
                    else if(name.ToLower() == "maxbuf"){
                        retVal.m_Maxbuf = Convert.ToInt32(TextUtils.UnQuoteString(name_value[1]));
                    }
                    else if(name.ToLower() == "charset"){
                        retVal.m_Charset = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "algorithm"){
                        retVal.m_Algorithm = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if(name.ToLower() == "cipher-opts"){
                        retVal.m_CipherOpts = TextUtils.UnQuoteString(name_value[1]);
                    }
                    //else if(name.ToLower() == "auth-param"){
                    //    retVal.m_AuthParam = TextUtils.UnQuoteString(name_value[1]);
                    //}
                }
            }

            /* Validate required fields.
                Per RFC 2831 2.1.1. Only [nonce algorithm] parameters are required.
            */
            if(string.IsNullOrEmpty(retVal.Nonce)){
                throw new ParseException("The challenge-string doesn't contain required parameter 'nonce' value.");
            }
            if(string.IsNullOrEmpty(retVal.Algorithm)){
                throw new ParseException("The challenge-string doesn't contain required parameter 'algorithm' value.");
            }

            return retVal;
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="challenge">Client challenge.</param>
        /// <param name="realm">Realm value. This must be one value of the challenge Realm.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">User password.</param>
        /// <param name="cnonce">Client nonce value.</param>
        /// <param name="nonceCount">Nonce count. One-based client authentication attempt number. Normally this value is 1.</param>
        /// <param name="qop">Indicates what "quality of protection" the client accepted. This must be one value of the challenge QopOptions.</param>
        /// <param name="digestUri">Digest URI.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>challenge</b>,<b>realm</b>,<b>password</b>,<b>nonce</b>,<b>qop</b> or <b>digestUri</b> is null reference.</exception>
        public AUTH_SASL_DigestMD5_Response(AUTH_SASL_DigestMD5_Challenge challenge, string realm, string userName, string password, string cnonce, int nonceCount, string qop, string digestUri)
        {
            if (challenge == null)
            {
                throw new ArgumentNullException("challenge");
            }
            if (realm == null)
            {
                throw new ArgumentNullException("realm");
            }
            if (userName == null)
            {
                throw new ArgumentNullException("userName");
            }
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }
            if (cnonce == null)
            {
                throw new ArgumentNullException("cnonce");
            }
            if (qop == null)
            {
                throw new ArgumentNullException("qop");
            }
            if (digestUri == null)
            {
                throw new ArgumentNullException("digestUri");
            }

            m_pChallenge = challenge;
            m_Realm      = realm;
            m_UserName   = userName;
            m_Password   = password;
            m_Nonce      = m_pChallenge.Nonce;
            m_Cnonce     = cnonce;
            m_NonceCount = nonceCount;
            m_Qop        = qop;
            m_DigestUri  = digestUri;
            m_Response   = CalculateResponse(userName, password);
            m_Charset    = challenge.Charset;
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="challenge">Client challenge.</param>
        /// <param name="realm">Realm value. This must be one value of the challenge Realm.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">User password.</param>
        /// <param name="cnonce">Client nonce value.</param>
        /// <param name="nonceCount">Nonce count. One-based client authentication attempt number. Normally this value is 1.</param>
        /// <param name="qop">Indicates what "quality of protection" the client accepted. This must be one value of the challenge QopOptions.</param>
        /// <param name="digestUri">Digest URI.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>challenge</b>,<b>realm</b>,<b>password</b>,<b>nonce</b>,<b>qop</b> or <b>digestUri</b> is null reference.</exception>
        public AUTH_SASL_DigestMD5_Response(AUTH_SASL_DigestMD5_Challenge challenge,string realm,string userName,string password,string cnonce,int nonceCount,string qop,string digestUri)
        {
            if(challenge == null){
                throw new ArgumentNullException("challenge");
            }
            if(realm == null){
                throw new ArgumentNullException("realm");
            }
            if(userName == null){
                throw new ArgumentNullException("userName");
            }
            if(password == null){
                throw new ArgumentNullException("password");
            }
            if(cnonce == null){
                throw new ArgumentNullException("cnonce");
            }
            if(qop == null){
                throw new ArgumentNullException("qop");
            }
            if(digestUri == null){
                throw new ArgumentNullException("digestUri");
            }

            m_pChallenge = challenge;
            m_Realm      = realm;
            m_UserName   = userName;
            m_Password   = password;
            m_Nonce      = m_pChallenge.Nonce;
            m_Cnonce     = cnonce;
            m_NonceCount = nonceCount;
            m_Qop        = qop;
            m_DigestUri  = digestUri;
            m_Response   = CalculateResponse(userName,password);
            m_Charset    = challenge.Charset;
        }
Example #4
0
        /// <summary>
        /// Continues authentication process.
        /// </summary>
        /// <param name="serverResponse">Server sent SASL response.</param>
        /// <returns>Returns challange request what must be sent to server or null if authentication has completed.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>serverResponse</b> is null reference.</exception>
        /// <exception cref="InvalidOperationException">Is raised when this method is called when authentication is completed.</exception>
        public override byte[] Continue(byte[] serverResponse)
        {
            if (serverResponse == null)
            {
                throw new ArgumentNullException("serverResponse");
            }
            if (m_IsCompleted)
            {
                throw new InvalidOperationException("Authentication is completed.");
            }

            /* RFC 2831.
             *  The base64-decoded version of the SASL exchange is:
             *
             *  S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
             *     algorithm=md5-sess,charset=utf-8
             *  C: charset=utf-8,username="******",realm="elwood.innosoft.com",
             *     nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
             *     digest-uri="imap/elwood.innosoft.com",
             *     response=d388dad90d4bbd760a152321f2143af7,qop=auth
             *  S: rspauth=ea40f60335c427b5527b84dbabcdfffd
             *  C:
             *  S: ok
             *
             *  The password in this example was "secret".
             */

            if (m_State == 0)
            {
                m_State++;

                // Parse server challenge.
                AUTH_SASL_DigestMD5_Challenge challenge = AUTH_SASL_DigestMD5_Challenge.Parse(Encoding.UTF8.GetString(serverResponse));

                // Construct our response to server challenge.
                m_pResponse = new AUTH_SASL_DigestMD5_Response(
                    challenge,
                    challenge.Realm[0],
                    m_UserName,
                    m_Password,
                    Guid.NewGuid().ToString().Replace("-", ""),
                    1,
                    challenge.QopOptions[0],
                    m_Protocol + "/" + m_ServerName
                    );

                return(Encoding.UTF8.GetBytes(m_pResponse.ToResponse()));
            }
            else if (m_State == 1)
            {
                m_State++;
                m_IsCompleted = true;

                // Check rspauth value.
                if (!String2.Equals(Encoding.UTF8.GetString(serverResponse), m_pResponse.ToRspauthResponse(m_UserName, m_Password), StringComparison2.InvariantCultureIgnoreCase))
                {
                    throw new Exception("Server server 'rspauth' value mismatch with local 'rspauth' value.");
                }

                return(new byte[0]);
            }
            else
            {
                throw new InvalidOperationException("Authentication is completed.");
            }
        }
        /// <summary>
        /// Parses DIGEST-MD5 challenge from challenge-string.
        /// </summary>
        /// <param name="challenge">Challenge string.</param>
        /// <returns>Returns DIGEST-MD5 challenge.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>challenge</b> is null reference.</exception>
        /// <exception cref="ParseException">Is raised when challenge parsing + validation fails.</exception>
        public static AUTH_SASL_DigestMD5_Challenge Parse(string challenge)
        {
            if (challenge == null)
            {
                throw new ArgumentNullException("challenge");
            }

            AUTH_SASL_DigestMD5_Challenge retVal = new AUTH_SASL_DigestMD5_Challenge();

            string[] parameters = TextUtils.SplitQuotedString(challenge, ',');
            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")
                    {
                        retVal.m_Realm = TextUtils.UnQuoteString(name_value[1]).Split(',');
                    }
                    else if (name.ToLower() == "nonce")
                    {
                        retVal.m_Nonce = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "qop")
                    {
                        retVal.m_QopOptions = TextUtils.UnQuoteString(name_value[1]).Split(',');
                    }
                    else if (name.ToLower() == "stale")
                    {
                        retVal.m_Stale = Convert.ToBoolean(TextUtils.UnQuoteString(name_value[1]));
                    }
                    else if (name.ToLower() == "maxbuf")
                    {
                        retVal.m_Maxbuf = Convert.ToInt32(TextUtils.UnQuoteString(name_value[1]));
                    }
                    else if (name.ToLower() == "charset")
                    {
                        retVal.m_Charset = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "algorithm")
                    {
                        retVal.m_Algorithm = TextUtils.UnQuoteString(name_value[1]);
                    }
                    else if (name.ToLower() == "cipher-opts")
                    {
                        retVal.m_CipherOpts = TextUtils.UnQuoteString(name_value[1]);
                    }
                    //else if(name.ToLower() == "auth-param"){
                    //    retVal.m_AuthParam = TextUtils.UnQuoteString(name_value[1]);
                    //}
                }
            }

            /* Validate required fields.
             *  Per RFC 2831 2.1.1. Only [nonce algorithm] parameters are required.
             */
            if (string.IsNullOrEmpty(retVal.Nonce))
            {
                throw new ParseException("The challenge-string doesn't contain required parameter 'none' value.");
            }
            if (string.IsNullOrEmpty(retVal.Algorithm))
            {
                throw new ParseException("The challenge-string doesn't contain required parameter 'algorithm' value.");
            }

            return(retVal);
        }
Example #6
0
        /// <summary>
        /// Continues authentication process.
        /// </summary>
        /// <param name="clientResponse">Client sent SASL response.</param>
        /// <returns>Retunrns challange response what must be sent to client or null if authentication has completed.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>clientResponse</b> is null reference.</exception>
        public override byte[] Continue(byte[] clientResponse)
        {
            if (clientResponse == null)
            {
                throw new ArgumentNullException("clientResponse");
            }

            /* RFC 2831.
             *  The base64-decoded version of the SASL exchange is:
             *
             *  S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
             *     algorithm=md5-sess,charset=utf-8
             *  C: charset=utf-8,username="******",realm="elwood.innosoft.com",
             *     nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
             *     digest-uri="imap/elwood.innosoft.com",
             *     response=d388dad90d4bbd760a152321f2143af7,qop=auth
             *  S: rspauth=ea40f60335c427b5527b84dbabcdfffd
             *  C:
             *  S: ok
             *
             *  The password in this example was "secret".
             */

            if (m_State == 0)
            {
                m_State++;

                AUTH_SASL_DigestMD5_Challenge callenge = new AUTH_SASL_DigestMD5_Challenge(new string[] { m_Realm }, m_Nonce, new string[] { "auth" }, false);

                return(Encoding.UTF8.GetBytes(callenge.ToChallenge()));
            }
            else if (m_State == 1)
            {
                m_State++;

                try{
                    AUTH_SASL_DigestMD5_Response response = AUTH_SASL_DigestMD5_Response.Parse(Encoding.UTF8.GetString(clientResponse));

                    // Check realm and nonce value.
                    if (m_Realm != response.Realm || m_Nonce != response.Nonce)
                    {
                        return(Encoding.UTF8.GetBytes("rspauth=\"\""));
                    }

                    m_UserName = response.UserName;
                    AUTH_e_UserInfo result = OnGetUserInfo(response.UserName);
                    if (result.UserExists)
                    {
                        if (response.Authenticate(result.UserName, result.Password))
                        {
                            m_IsAuthenticated = true;

                            return(Encoding.UTF8.GetBytes(response.ToRspauthResponse(result.UserName, result.Password)));
                        }
                    }
                }
                catch {
                    // Authentication failed, just reject request.
                }

                return(Encoding.UTF8.GetBytes("rspauth=\"\""));
            }
            else
            {
                m_IsCompleted = true;
            }

            return(null);
        }
        /// <summary>
        /// Continues authentication process.
        /// </summary>
        /// <param name="clientResponse">Client sent SASL response.</param>
        /// <returns>Retunrns challange response what must be sent to client or null if authentication has completed.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>clientResponse</b> is null reference.</exception>
        public override byte[] Continue(byte[] clientResponse)
        {
            if(clientResponse == null){
                throw new ArgumentNullException("clientResponse");
            }

            /* RFC 2831.
                The base64-decoded version of the SASL exchange is:

                S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
                   algorithm=md5-sess,charset=utf-8
                C: charset=utf-8,username="******",realm="elwood.innosoft.com",
                   nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
                   digest-uri="imap/elwood.innosoft.com",
                   response=d388dad90d4bbd760a152321f2143af7,qop=auth
                S: rspauth=ea40f60335c427b5527b84dbabcdfffd
                C: 
                S: ok

                The password in this example was "secret".
            */

            if(m_State == 0){
                m_State++;
                
                AUTH_SASL_DigestMD5_Challenge callenge = new AUTH_SASL_DigestMD5_Challenge(new string[]{m_Realm},m_Nonce,new string[]{"auth"},false);
     
                return Encoding.UTF8.GetBytes(callenge.ToChallenge());
            }
            else if(m_State == 1){
                m_State++;

                try{
                    AUTH_SASL_DigestMD5_Response response = AUTH_SASL_DigestMD5_Response.Parse(Encoding.UTF8.GetString(clientResponse));

                    // Check realm and nonce value.
                    if(m_Realm != response.Realm || m_Nonce != response.Nonce){
                        return Encoding.UTF8.GetBytes("rspauth=\"\"");
                    }

                    m_UserName = response.UserName;
                    AUTH_e_UserInfo result = OnGetUserInfo(response.UserName);
                    if(result.UserExists){            
                        if(response.Authenticate(result.UserName,result.Password)){
                            m_IsAuthenticated = true;

                            return Encoding.UTF8.GetBytes(response.ToRspauthResponse(result.UserName,result.Password));
                        }
                    }
                }
                catch{
                    // Authentication failed, just reject request.
                }

                return Encoding.UTF8.GetBytes("rspauth=\"\"");
            }
            else{
                m_IsCompleted = true;
            }

            return null;
        }