/// <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 (!string.Equals(Encoding.UTF8.GetString(serverResponse), m_pResponse.ToRspauthResponse(m_UserName, m_Password), StringComparison.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 response from response-string. /// </summary> /// <param name="digestResponse">Response string.</param> /// <returns>Returns DIGEST-MD5 response.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>digestResponse</b> isnull reference.</exception> /// <exception cref="ParseException">Is raised when response parsing + validation fails.</exception> public static AUTH_SASL_DigestMD5_Response Parse(string digestResponse) { if (digestResponse == null) { throw new ArgumentNullException(digestResponse); } /* RFC 2831 2.1.2. * The client makes note of the "digest-challenge" and then responds * with a string formatted and computed according to the rules for a * "digest-response" defined as follows: * * digest-response = 1#( username | realm | nonce | cnonce | * nonce-count | qop | digest-uri | response | * maxbuf | charset | cipher | authzid | * auth-param ) * * username = "******" "=" <"> username-value <"> * username-value = qdstr-val * cnonce = "cnonce" "=" <"> cnonce-value <"> * cnonce-value = qdstr-val * nonce-count = "nc" "=" nc-value * nc-value = 8LHEX * qop = "qop" "=" qop-value * digest-uri = "digest-uri" "=" <"> digest-uri-value <"> * digest-uri-value = serv-type "/" host [ "/" serv-name ] * serv-type = 1*ALPHA * host = 1*( ALPHA | DIGIT | "-" | "." ) * serv-name = host * response = "response" "=" response-value * response-value = 32LHEX * LHEX = "0" | "1" | "2" | "3" | * "4" | "5" | "6" | "7" | * "8" | "9" | "a" | "b" | * "c" | "d" | "e" | "f" * cipher = "cipher" "=" cipher-value * authzid = "authzid" "=" <"> authzid-value <"> * authzid-value = qdstr-val */ AUTH_SASL_DigestMD5_Response retVal = new AUTH_SASL_DigestMD5_Response(); // Set default values. retVal.m_Realm = ""; 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() == "username") { retVal.m_UserName = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "realm") { retVal.m_Realm = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "nonce") { retVal.m_Nonce = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "cnonce") { retVal.m_Cnonce = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "nc") { retVal.m_NonceCount = Int32.Parse(TextUtils.UnQuoteString(name_value[1]), System.Globalization.NumberStyles.HexNumber); } else if (name.ToLower() == "qop") { retVal.m_Qop = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "digest-uri") { retVal.m_DigestUri = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "response") { retVal.m_Response = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "charset") { retVal.m_Charset = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "cipher") { retVal.m_Cipher = TextUtils.UnQuoteString(name_value[1]); } else if (name.ToLower() == "authzid") { retVal.m_Authzid = TextUtils.UnQuoteString(name_value[1]); } } } /* Validate required fields. * Per RFC 2831 2.1.2. Only [username nonce cnonce nc response] parameters are required. */ if (string.IsNullOrEmpty(retVal.UserName)) { throw new ParseException("The response-string doesn't contain required parameter 'username' value."); } if (string.IsNullOrEmpty(retVal.Nonce)) { throw new ParseException("The response-string doesn't contain required parameter 'nonce' value."); } if (string.IsNullOrEmpty(retVal.Cnonce)) { throw new ParseException("The response-string doesn't contain required parameter 'cnonce' value."); } if (retVal.NonceCount < 1) { throw new ParseException("The response-string doesn't contain required parameter 'nc' value."); } if (string.IsNullOrEmpty(retVal.Response)) { throw new ParseException("The response-string doesn't contain required parameter 'response' 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); }