http://bertrandmartel.github.io/digest-auth-session-cpp
stateful authentication and session management for server using QT4
- digest authentication RFC2617 compliant
- digest session management (MD5-SESS and SHA1-SESS)
- cookie session management (independant from digest)
Instanciate digest manager :
DigestManager digest_manager;
Set the algorithm and session type you want to use. For instance :
digest_manager.set_digest_algorithm(ALGO_SHA1);
digest_manager.set_session_type(SESSION_DIGEST);
Available algorithms :
- MD5
- SHA1 You have to store hash of ALGO("username:realm:password") in your storage for digest authentication
Available session type :
- DIGEST : will use digest protocol to generate a session key base on pattern defined in RFC2617 : HASH(HASH("username:realm:password"):nonce:cnonce).
- COOKIE : will use a random generated string cookie as session key
For session key lifetime management see 2) and 3)
- define an implementation for retrieving MD5 or SHA1 hash of "username:realm:password"
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
- bind the latter listener to your digest manager :
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:
std::string method
: http method used in your client requeststd::string uri
: uri of your client requeststd::map<std::string,std::string> * headers
: headers issued from client request
Output DigestInfo
is composed of :
int status_code
which is http status code to return to client (with your specific page referred to this status code)std::map<std::string,std::string> * headers
: featuring headers to add to client response for authentication/session management (content-type and content-length is not included in these headers)
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.
-
You cant have the same cookie session key twice in the server
-
You can set cookie session lifetime with
DigestManager::set_max_session_time(int session_time)
. Input value is given in seconds -
Cookie is transmitted to client with
Set-Cookie
header key. Value is give in HSID property. -
Cookie session is not protected by replay attack contrary to digest session. See 5) for security considerations
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).
-
Digest session key is given in
WWW-Authenticate
header (the same as digest authentication) -
Digest session key is using a single nonce (random string generated by the server) and a single cnonce (random string generated by the client).
-
Digest session key lifetime depends on nonce lifetime, so you will have the session timeout will be the same as nonce timeout with can be set with
DigestManager::set_nonce_timeout_millis(long nonce_timeout)
with nonce timeout is given in milliseconds. -
Using the same protocol as digest authentication, nonce count is used to keep track of request where a single nonce is used. Client will increment the nonce and compute the hash with the count for each request. The server will do the same and check the result as in digest authentication process (see RFC2617 for more info)
-
Digest session key is protected against replay attack
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.
-
Digest authentication is not fully secured protocol, you MUST use SSL/TLS protocol to ensure security of your data.
-
Digest main flaw is authentication data storage in server-side. You must consider this before using digest in your application as your are compelled to store hash of "username:realm:password" which directly tells your not-trusted clients the format in which you are storing your authentication data in your server. Furthermore, if someone grants access to your database, he will be able to access all authentication data as it was plaintext ( database MUST be encrypted).
-
Digest is designed to replace basic authentication (which is very poor compared to digest)
-
Digest can be used as a basis to add additionnal security layer to your applications
-
Digest session can be used standalone (not depending on digest authentication) which provides good security performance for a user session
-
Compromises can be found between authentication, session management and database security, using digest protocol depending on application scope and architecture
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
- valgrind memcheck
cd ./digest-auth-session-test/release/
valgrind --tool=memcheck --leak-check=full --suppressions=../../memcheck.suppress ./digest-auth-session-test
External libraries used
https://github.com/bertrandmartel/http-streaming-decoder-cpp
https://github.com/bertrandmartel/socket-multiplatform/tree/master/server/server-socket/non-blocking
- Project is Qt4 compliant
- Development on QtCreator
- Specification from https://www.ietf.org/rfc/rfc2617.txt