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