/// <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> /// 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; }