Digest authentication / session library

stateful authentication and session management for server using QT4

1 - Usage

Instanciate digest manager :

DigestManager digest_manager;

Set the algorithm and session type you want to use. For instance :

digest_manager.set_digest_algorithm(SHA1);
digest_manager.set_session_type(DIGEST);

Available algorithms :

Available session type :

For session key lifetime management see 2) and 3)

Create a class which inherit IDigestListener like following :

#include "string"
#include "IDigestListener.h"
#include "digeststruct.h"

class HashDigestListener : public IDigestListener {

    public:

    /**
     * @brief ClientSocketObj::ClientSocketObj
     *      Build one client
     */
    HashDigestListener();

    ~HashDigestListener();

    /**
     * @brief get_hash_for_username
     *      retrieve hash in database or in your configuration for specified username and realm
     * @return
     */
    std::string get_hash_for_username(std::string username,std::string realm,digest_algorithm algo);

};

In std::string get_hash_for_username(std::string username,std::string realm,digest_algorithm algo);, you will be able to return the hash taken from your storage

HashDigestListener digest_listener;
digest_manager.set_digest_listener(&digest_listener);

From now, you can use digest_manager object, to process all your client request with process_digest(std::string method,std::string uri,std::map<std::string,std::string> * headers) method to manage your authentication/session :

DigestInfo DigestManager::process_digest(std::string method,std::string uri,std::map<std::string,std::string> * headers)

DigestManager::process_digest will process authentication and session both. No need to distinguish the cases.

Input:

Output DigestInfo is composed of :

2 - Cookie session key

Cookie session key is very basic. It consists of a random string with a specific lifetime from the moment user has successfully authenticated with digest authentication method.

3 - Digest session key

Digest session key uses directly Digest protocol to generate session key composed of HASH(HASH("username:realm:password"):nonce:cnonce) according to RFC2617 way. HASH refer to MD5 or SHA1 depending if you store MD5 or SHA1 hash of "username:realm:password" in your storage/database (see part 5 for security consideration regarding database and digest authentication).

4 - Example

I have here a callback giving an incoming http request from client with an Ihttpframe object featuring http method, uri, headers, body etc... The following exemple will process digest only for a GET request on /login uri and display either 401 page, 500 page or 200 page according to processing result :

void ClientSocketHandler::onHttpRequestReceived(IHttpClient &client,Ihttpframe * frame,std::string peer_address)
{
    cout << "Http request received for client " << peer_address << endl;

    if (strcmp(frame->getMethod().data(),"GET")==0 && strcmp(frame->getUri().data(),"/login")==0)
    {
        DigestInfo digest_info = digest_manager->process_digest(frame->getMethod(),frame->getUri(),frame->getHeaders());

        cout << "Receive digest response with status code : " << digest_info.get_status_code() << endl;

        std::string content = "";
        stringstream contentLength;

        if (digest_info.get_status_code()==401){

            content = unauthorized_page;
            contentLength << content.size();

            string response = QString("HTTP/1.1 401 Unauthorized\r\n").toStdString() +
                    "Content-Type: text/html\r\nContent-Length: " + contentLength.str() + "\r\n";

            if (digest_info.get_headers()->size()>0){

                for (std::map<std::string, std::string>::iterator iter=digest_info.get_headers()->begin(); iter!=digest_info.get_headers()->end(); ++iter){
                    response+=iter->first+": " +iter->second + "\r\n";
                }
            }

            response+=QString("\r\n").toStdString() + content;

            cout << "Sending response with status code 401 Unauthorized" << endl;

            client.sendHttpMessage(response);
        }
        else if (digest_info.get_status_code()==500){

            content = internal_error_page;
            contentLength << content.size();

            string response = QString("HTTP/1.1 500 Internal Server Error\r\n").toStdString() +
                    "Content-Type: text/html\r\nContent-Length: " + contentLength.str() + "\r\n";

            if (digest_info.get_headers()->size()>0){
                for (std::map<std::string,std::string>::iterator it=digest_info.get_headers()->begin(); it!=digest_info.get_headers()->end(); ++it)
                    response+=it->first+": " +it->second + "\r\n";
            }

            response+=QString("\r\n").toStdString() + content;

            cout << "Sending response with status code 500 Internal Server Error" << endl;

            client.sendHttpMessage(response);
        }
        else if (digest_info.get_status_code()==200){

            content = login_success_page;
            contentLength << content.size();

            string response = QString("HTTP/1.1 200 OK\r\n").toStdString() +
                    "Content-Type: text/html\r\nContent-Length: " + contentLength.str() + "\r\n";

            if (digest_info.get_headers()->size()>0){
                for (std::map<std::string,std::string>::iterator it=digest_info.get_headers()->begin(); it!=digest_info.get_headers()->end(); ++it)
                    response+=it->first+": " +it->second + "\r\n";
            }

            response+=QString("\r\n").toStdString() + content;

            cout << "Sending response with status code 200 OK" << endl;

            client.sendHttpMessage(response);
        }
    }
}

A complete example is given in digest-auth-session-test folder project.

5 - Security consideration


6 - Testing project with curl

A complete test case is provided in a Bash script using curl named test_authentication_session.sh

It is designed to test both type of sessions and both algorithm with following tests :

# FOR DIGEST AUTHENTICATION - COOKIE SESSION
# 1) Test authentication success
# 2) Test cookie hsid is enabled
# 3) Test cookie hsid is not valid 
# 4) Test opaque invalid
# 5) Test digest-uri invalid
# 6) Test nonce count invalid
# 7) Test nonce count > 1

# FOR DIGEST AUTHENTICATION - DIGEST SESSION
# 1) Test authentication success
# 2) Test opaque invalid
# 3) Test digest-uri invalid
# 4) Test nonce count invalid
# 5) Test nonce count > 1
# 6) Test 5 consecutive session key with different nonce count

Usage is :

test_authentication_session.sh <algo> <session_type>
<algo>          : MD5    | SHA1
<session_type>  : COOKIE | DIGEST

test curl