/// <summary> /// Raises <b>GetUserInfo</b> event. /// </summary> /// <param name="userName">User name.</param> /// <returns>Returns specified user info.</returns> private AUTH_e_UserInfo OnGetUserInfo(string userName) { AUTH_e_UserInfo retVal = new AUTH_e_UserInfo(userName); if (this.GetUserInfo != null) { this.GetUserInfo(this, retVal); } 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 2195 2. Challenge-Response Authentication Mechanism. * The authentication type associated with CRAM is "CRAM-MD5". * * The data encoded in the first ready response contains an * presumptively arbitrary string of random digits, a timestamp, and the * fully-qualified primary host name of the server. The syntax of the * unencoded form must correspond to that of an RFC 822 'msg-id' * [RFC822] as described in [POP3]. * * The client makes note of the data and then responds with a string * consisting of the user name, a space, and a 'digest'. The latter is * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where * the key is a shared secret and the digested text is the timestamp * (including angle-brackets). * * This shared secret is a string known only to the client and server. * The `digest' parameter itself is a 16-octet value which is sent in * hexadecimal format, using lower-case ASCII characters. * * When the server receives this client response, it verifies the digest * provided. If the digest is correct, the server should consider the * client authenticated and respond appropriately. * * Example: * The examples in this document show the use of the CRAM mechanism with * the IMAP4 AUTHENTICATE command [IMAP-AUTH]. The base64 encoding of * the challenges and responses is part of the IMAP4 AUTHENTICATE * command, not part of the CRAM specification itself. * * S: * OK IMAP4 Server * C: A0001 AUTHENTICATE CRAM-MD5 * S: + PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+ * C: dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw * S: A0001 OK CRAM authentication successful * * In this example, the shared secret is the string * 'tanstaaftanstaaf'. Hence, the Keyed MD5 digest is produced by * calculating * * MD5((tanstaaftanstaaf XOR opad), * MD5((tanstaaftanstaaf XOR ipad), * <*****@*****.**>)) * * where ipad and opad are as defined in the keyed-MD5 Work in * Progress [KEYED-MD5] and the string shown in the challenge is the * base64 encoding of <*****@*****.**>. The * shared secret is null-padded to a length of 64 bytes. If the * shared secret is longer than 64 bytes, the MD5 digest of the * shared secret is used as a 16 byte input to the keyed MD5 * calculation. * * This produces a digest value (in hexadecimal) of * * b913a602c7eda7a495b4e6e7334d3890 * * The user name is then prepended to it, forming * * tim b913a602c7eda7a495b4e6e7334d3890 * * Which is then base64 encoded to meet the requirements of the IMAP4 * AUTHENTICATE command (or the similar POP3 AUTH command), yielding * * dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw */ if (m_State == 0) { m_State++; m_Key = "<" + Guid.NewGuid().ToString() + "@host" + ">"; return(Encoding.UTF8.GetBytes(m_Key)); } else { // Parse client response. response = userName SP hash. string[] user_hash = Encoding.UTF8.GetString(clientResponse).Split(' '); if (user_hash.Length == 2 && !string.IsNullOrEmpty(user_hash[0])) { m_UserName = user_hash[0]; AUTH_e_UserInfo result = OnGetUserInfo(user_hash[0]); if (result.UserExists) { // hash = Hex(HmacMd5(hashKey,password)) string hash = Net_Utils.ToHex(HmacMd5(m_Key, result.Password)); if (hash == user_hash[1]) { m_IsAuthenticated = true; } } } m_IsCompleted = true; } return(null); }