Implementation:ClickHouse ClickHouse Poco HTTPDigestCredentials
| Source File | base/poco/Net/src/HTTPDigestCredentials.cpp |
|---|---|
| Lines of Code | 313 |
| Principle | Principle:ClickHouse_ClickHouse_HTTP_Authentication |
| Domain | Networking, Security |
| Language | C++ |
| Last Updated | 2026-02-08 00:00 GMT |
Overview
Implementation of HTTP Digest Access Authentication (RFC 2617), providing secure challenge-response authentication for HTTP requests with MD5-based cryptographic signatures.
Purpose
Enables secure HTTP authentication using digest authentication protocol, which transmits credentials as cryptographic hashes rather than plaintext, protecting against replay attacks and eavesdropping.
Key Classes
- HTTPDigestCredentials: Manages digest authentication credentials and signature generation
Core Functionality
Construction
HTTPDigestCredentials();
HTTPDigestCredentials(const std::string& username, const std::string& password);
Credential Management
void setUsername(const std::string& username);
void setPassword(const std::string& password);
const std::string& getUsername() const;
void clear(); // Clear credentials
void reset(); // Reset authentication state
Client Authentication
// Authenticate request based on server challenge
void authenticate(HTTPRequest& request, const HTTPResponse& response);
void authenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams);
// Update authentication info for new request with same nonce
void updateAuthInfo(HTTPRequest& request);
// Proxy authentication variants
void proxyAuthenticate(HTTPRequest& request, const HTTPResponse& response);
void proxyAuthenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams);
void updateProxyAuthInfo(HTTPRequest& request);
Server Verification
// Verify client's authentication response
bool verifyAuthInfo(const HTTPRequest& request) const;
bool verifyAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& params) const;
Utility Methods
// Create random nonce
static std::string createNonce();
Implementation Details
Digest Algorithm
MD5-based digest computation:
std::string digest(DigestEngine& engine,
const std::string& a, const std::string& b,
const std::string& c = "", const std::string& d = "",
const std::string& e = "", const std::string& f = "")
{
engine.reset();
engine.update(a);
engine.update(':');
engine.update(b);
if (!c.empty()) {
engine.update(':');
engine.update(c);
if (!d.empty()) {
engine.update(':');
engine.update(d);
engine.update(':');
engine.update(e);
engine.update(':');
engine.update(f);
}
}
return DigestEngine::digestToHex(engine.digest());
}
Authentication with QoP (Quality of Protection)
For `qop="auth"`:
void updateAuthParams(const HTTPRequest& request) {
MD5Engine engine;
const std::string& qop = _requestAuthParams.get("qop", "");
const std::string& realm = _requestAuthParams.getRealm();
const std::string& nonce = _requestAuthParams.get("nonce");
if (qop == "auth") {
const std::string& cnonce = _requestAuthParams.get("cnonce");
// H(A1) = MD5(username:realm:password)
const std::string ha1 = digest(engine, _username, realm, _password);
// H(A2) = MD5(method:uri)
const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
// Nonce counter
const std::string nc = formatNonceCounter(updateNonceCounter(nonce));
// Response = MD5(H(A1):nonce:nc:cnonce:qop:H(A2))
_requestAuthParams.set("nc", nc);
_requestAuthParams.set("response",
digest(engine, ha1, nonce, nc, cnonce, qop, ha2));
}
}
Authentication without QoP
Legacy format:
if (qop.empty()) {
// H(A1) = MD5(username:realm:password)
const std::string ha1 = digest(engine, _username, realm, _password);
// H(A2) = MD5(method:uri)
const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
// Response = MD5(H(A1):nonce:H(A2))
_requestAuthParams.set("response", digest(engine, ha1, nonce, ha2));
}
Nonce Generation
std::string HTTPDigestCredentials::createNonce() {
FastMutex::ScopedLock lock(_nonceMutex);
MD5Engine md5;
Timestamp::TimeVal now = Timestamp().epochMicroseconds();
md5.update(&_nonceCounter, sizeof(_nonceCounter));
md5.update(&now, sizeof(now));
++_nonceCounter;
return DigestEngine::digestToHex(md5.digest());
}
Combines counter and timestamp for uniqueness.
Nonce Counter Management
int updateNonceCounter(const std::string& nonce) {
NonceCounterMap::iterator iter = _nc.find(nonce);
if (iter == _nc.end()) {
iter = _nc.insert(NonceCounterMap::value_type(nonce, 0)).first;
}
iter->second++;
return iter->second;
}
std::string formatNonceCounter(int counter) {
return NumberFormatter::formatHex(counter, 8); // 8-digit hex
}
Tracks request count per nonce for replay protection.
Challenge Processing
void createAuthParams(const HTTPRequest& request,
const HTTPAuthenticationParams& responseAuthParams) {
// Validate required parameters
if (!responseAuthParams.has("nonce") || !responseAuthParams.has("realm"))
throw InvalidArgumentException("Invalid authentication parameters");
// Check algorithm
const std::string& algorithm = responseAuthParams.get("algorithm", "MD5");
if (icompare(algorithm, "MD5") != 0)
throw NotImplementedException("Unsupported digest algorithm", algorithm);
// Extract parameters
const std::string& nonce = responseAuthParams.get("nonce");
const std::string& qop = responseAuthParams.get("qop", "");
const std::string& realm = responseAuthParams.getRealm();
// Build request parameters
_requestAuthParams.clear();
_requestAuthParams.set("username", _username);
_requestAuthParams.set("nonce", nonce);
_requestAuthParams.setRealm(realm);
if (responseAuthParams.has("opaque"))
_requestAuthParams.set("opaque", responseAuthParams.get("opaque"));
// Handle QoP
if (!qop.empty()) {
StringTokenizer tok(qop, ",", StringTokenizer::TOK_TRIM);
for (auto& option : tok) {
if (icompare(option, "auth") == 0) {
_requestAuthParams.set("cnonce", createNonce());
_requestAuthParams.set("qop", option);
updateAuthParams(request);
break;
}
}
} else {
updateAuthParams(request);
}
}
Constants
static const std::string SCHEME = "Digest";
static const std::string DEFAULT_ALGORITHM = "MD5";
static const std::string NONCE_PARAM = "nonce";
static const std::string REALM_PARAM = "realm";
static const std::string QOP_PARAM = "qop";
static const std::string ALGORITHM_PARAM = "algorithm";
static const std::string USERNAME_PARAM = "username";
static const std::string RESPONSE_PARAM = "response";
static const std::string CNONCE_PARAM = "cnonce";
static const std::string NC_PARAM = "nc";
Usage Examples
Client Authentication
HTTPClientSession session("www.example.com");
HTTPRequest request(HTTPRequest::HTTP_GET, "/protected");
// First request (no auth)
session.sendRequest(request);
HTTPResponse response;
session.receiveResponse(response);
if (response.getStatus() == HTTPResponse::HTTP_UNAUTHORIZED) {
// Server challenged with 401
HTTPDigestCredentials creds("user", "password");
creds.authenticate(request, response);
// Retry with authentication
session.sendRequest(request);
session.receiveResponse(response);
}
Reusing Authentication
HTTPDigestCredentials creds("user", "password");
// First request
HTTPRequest request1(HTTPRequest::HTTP_GET, "/resource1");
creds.authenticate(request1, response);
session.sendRequest(request1);
// Second request (same nonce)
HTTPRequest request2(HTTPRequest::HTTP_GET, "/resource2");
creds.updateAuthInfo(request2);
session.sendRequest(request2);
Server Verification
HTTPDigestCredentials serverCreds;
serverCreds.setUsername("user");
serverCreds.setPassword("password"); // Lookup from database
HTTPRequest request;
// ... receive request ...
if (serverCreds.verifyAuthInfo(request)) {
// Authentication successful
} else {
// Send 401 challenge
HTTPResponse response(HTTPResponse::HTTP_UNAUTHORIZED);
HTTPAuthenticationParams params;
params.setRealm("Protected Area");
params.set("nonce", HTTPDigestCredentials::createNonce());
params.set("algorithm", "MD5");
params.set("qop", "auth");
response.set("WWW-Authenticate", "Digest " + params.toString());
}
Security Features
Password Protection
- Password never transmitted in clear text
- MD5 hash prevents recovery
- Challenge-response prevents replay
Nonce Management
- Unique nonce per challenge
- Client nonce (cnonce) for additional randomness
- Nonce counter prevents replay attacks
Quality of Protection (QoP)
- `qop="auth"`: Authentication only
- Includes request counter
- Binds response to specific request
Algorithm Support
- Currently supports MD5 only
- Extensible to MD5-sess and SHA variants
- Algorithm negotiation via parameters
Security Considerations
Limitations
- MD5 is cryptographically weak (consider SHA-256)
- Vulnerable to man-in-the-middle if no TLS
- Server must securely store passwords
- Nonce replay window exists
Best Practices
- Use with TLS/HTTPS
- Implement nonce expiration
- Enforce QoP="auth" mode
- Consider upgrading to modern schemes (OAuth, JWT)
Dependencies
- `Poco::Net::HTTPRequest`: HTTP request
- `Poco::Net::HTTPResponse`: HTTP response
- `Poco::Net::HTTPAuthenticationParams`: Parameter parser
- `Poco::MD5Engine`: MD5 hashing
- `Poco::DigestEngine`: Digest utilities
- `Poco::NumberFormatter`: Hex formatting
- `Poco::StringTokenizer`: QoP parsing
Testing Considerations
- Test with and without QoP
- Verify MD5 hash computation
- Test nonce counter increments
- Check parameter extraction
- Test with multiple requests
- Verify server verification
- Test opaque parameter handling