1 //
2 // HTTPDigestCredentials.cpp
3 //
4 // Library: Net
5 // Package: HTTP
6 // Module:	HTTPDigestCredentials
7 //
8 // Copyright (c) 2011, Anton V. Yabchinskiy (arn at bestmx dot ru).
9 // Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
10 // and Contributors.
11 //
12 // SPDX-License-Identifier:	BSL-1.0
13 //
14 
15 
16 #include "Poco/DateTime.h"
17 #include "Poco/DateTimeFormat.h"
18 #include "Poco/DateTimeFormatter.h"
19 #include "Poco/Exception.h"
20 #include "Poco/MD5Engine.h"
21 #include "Poco/Net/HTTPDigestCredentials.h"
22 #include "Poco/Net/HTTPRequest.h"
23 #include "Poco/Net/HTTPResponse.h"
24 #include "Poco/NumberFormatter.h"
25 #include "Poco/StringTokenizer.h"
26 
27 
28 namespace
29 {
digest(Poco::DigestEngine & engine,const std::string & a,const std::string & b,const std::string & c=std::string (),const std::string & d=std::string (),const std::string & e=std::string (),const std::string & f=std::string ())30 	std::string digest(Poco::DigestEngine& engine,
31 					   const std::string& a,
32 					   const std::string& b,
33 					   const std::string& c = std::string(),
34 					   const std::string& d = std::string(),
35 					   const std::string& e = std::string(),
36 					   const std::string& f = std::string())
37 	{
38 		engine.reset();
39 		engine.update(a);
40 		engine.update(':');
41 		engine.update(b);
42 		if (!c.empty())
43 		{
44 			engine.update(':');
45 			engine.update(c);
46 			if (!d.empty())
47 			{
48 				engine.update(':');
49 				engine.update(d);
50 				engine.update(':');
51 				engine.update(e);
52 				engine.update(':');
53 				engine.update(f);
54 			}
55 		}
56 		return Poco::DigestEngine::digestToHex(engine.digest());
57 	}
58 
formatNonceCounter(int counter)59 	std::string formatNonceCounter(int counter)
60 	{
61 		return Poco::NumberFormatter::formatHex(counter, 8);
62 	}
63 }
64 
65 
66 namespace Poco {
67 namespace Net {
68 
69 
70 const std::string HTTPDigestCredentials::SCHEME = "Digest";
71 const std::string HTTPDigestCredentials::DEFAULT_ALGORITHM("MD5");
72 const std::string HTTPDigestCredentials::DEFAULT_QOP("");
73 const std::string HTTPDigestCredentials::NONCE_PARAM("nonce");
74 const std::string HTTPDigestCredentials::REALM_PARAM("realm");
75 const std::string HTTPDigestCredentials::QOP_PARAM("qop");
76 const std::string HTTPDigestCredentials::ALGORITHM_PARAM("algorithm");
77 const std::string HTTPDigestCredentials::USERNAME_PARAM("username");
78 const std::string HTTPDigestCredentials::OPAQUE_PARAM("opaque");
79 const std::string HTTPDigestCredentials::URI_PARAM("uri");
80 const std::string HTTPDigestCredentials::RESPONSE_PARAM("response");
81 const std::string HTTPDigestCredentials::AUTH_PARAM("auth");
82 const std::string HTTPDigestCredentials::CNONCE_PARAM("cnonce");
83 const std::string HTTPDigestCredentials::NC_PARAM("nc");
84 int HTTPDigestCredentials::_nonceCounter(0);
85 Poco::FastMutex HTTPDigestCredentials::_nonceMutex;
86 
87 
HTTPDigestCredentials()88 HTTPDigestCredentials::HTTPDigestCredentials()
89 {
90 }
91 
92 
HTTPDigestCredentials(const std::string & username,const std::string & password)93 HTTPDigestCredentials::HTTPDigestCredentials(const std::string& username, const std::string& password):
94 	_username(username),
95 	_password(password)
96 {
97 }
98 
99 
~HTTPDigestCredentials()100 HTTPDigestCredentials::~HTTPDigestCredentials()
101 {
102 }
103 
104 
reset()105 void HTTPDigestCredentials::reset()
106 {
107 	_requestAuthParams.clear();
108 	_nc.clear();
109 }
110 
111 
setUsername(const std::string & username)112 void HTTPDigestCredentials::setUsername(const std::string& username)
113 {
114 	_username = username;
115 }
116 
117 
setPassword(const std::string & password)118 void HTTPDigestCredentials::setPassword(const std::string& password)
119 {
120 	_password = password;
121 }
122 
123 
clear()124 void HTTPDigestCredentials::clear()
125 {
126 	_username.clear();
127 	_password.clear();
128 }
129 
130 
authenticate(HTTPRequest & request,const HTTPResponse & response)131 void HTTPDigestCredentials::authenticate(HTTPRequest& request, const HTTPResponse& response)
132 {
133 	authenticate(request, HTTPAuthenticationParams(response));
134 }
135 
136 
authenticate(HTTPRequest & request,const HTTPAuthenticationParams & responseAuthParams)137 void HTTPDigestCredentials::authenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
138 {
139 	createAuthParams(request, responseAuthParams);
140 	request.setCredentials(SCHEME, _requestAuthParams.toString());
141 }
142 
143 
updateAuthInfo(HTTPRequest & request)144 void HTTPDigestCredentials::updateAuthInfo(HTTPRequest& request)
145 {
146 	updateAuthParams(request);
147 	request.setCredentials(SCHEME, _requestAuthParams.toString());
148 }
149 
150 
proxyAuthenticate(HTTPRequest & request,const HTTPResponse & response)151 void HTTPDigestCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPResponse& response)
152 {
153 	proxyAuthenticate(request, HTTPAuthenticationParams(response, HTTPAuthenticationParams::PROXY_AUTHENTICATE));
154 }
155 
156 
proxyAuthenticate(HTTPRequest & request,const HTTPAuthenticationParams & responseAuthParams)157 void HTTPDigestCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
158 {
159 	createAuthParams(request, responseAuthParams);
160 	request.setProxyCredentials(SCHEME, _requestAuthParams.toString());
161 }
162 
163 
updateProxyAuthInfo(HTTPRequest & request)164 void HTTPDigestCredentials::updateProxyAuthInfo(HTTPRequest& request)
165 {
166 	updateAuthParams(request);
167 	request.setProxyCredentials(SCHEME, _requestAuthParams.toString());
168 }
169 
170 
createNonce()171 std::string HTTPDigestCredentials::createNonce()
172 {
173 	Poco::FastMutex::ScopedLock lock(_nonceMutex);
174 
175 	MD5Engine md5;
176 	Timestamp::TimeVal now = Timestamp().epochMicroseconds();
177 
178 	md5.update(&_nonceCounter, sizeof(_nonceCounter));
179 	md5.update(&now, sizeof(now));
180 
181 	++_nonceCounter;
182 
183 	return DigestEngine::digestToHex(md5.digest());
184 }
185 
186 
createAuthParams(const HTTPRequest & request,const HTTPAuthenticationParams & responseAuthParams)187 void HTTPDigestCredentials::createAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
188 {
189 	// Not implemented: "domain" auth parameter and integrity protection.
190 
191 	if (!responseAuthParams.has(NONCE_PARAM) || !responseAuthParams.has(REALM_PARAM))
192 		throw InvalidArgumentException("Invalid HTTP authentication parameters");
193 
194 	const std::string& algorithm = responseAuthParams.get(ALGORITHM_PARAM, DEFAULT_ALGORITHM);
195 
196 	if (icompare(algorithm, DEFAULT_ALGORITHM) != 0)
197 		throw NotImplementedException("Unsupported digest algorithm", algorithm);
198 
199 	const std::string& nonce = responseAuthParams.get(NONCE_PARAM);
200 	const std::string& qop = responseAuthParams.get(QOP_PARAM, DEFAULT_QOP);
201 	const std::string& realm = responseAuthParams.getRealm();
202 
203 	_requestAuthParams.clear();
204 	_requestAuthParams.set(USERNAME_PARAM, _username);
205 	_requestAuthParams.set(NONCE_PARAM, nonce);
206 	_requestAuthParams.setRealm(realm);
207 	if (responseAuthParams.has(OPAQUE_PARAM))
208 	{
209 		_requestAuthParams.set(OPAQUE_PARAM, responseAuthParams.get(OPAQUE_PARAM));
210 	}
211 
212 	if (qop.empty())
213 	{
214 		updateAuthParams(request);
215 	}
216 	else
217 	{
218 		Poco::StringTokenizer tok(qop, ",", Poco::StringTokenizer::TOK_TRIM);
219 		bool qopSupported = false;
220 		for (Poco::StringTokenizer::Iterator it = tok.begin(); it != tok.end(); ++it)
221 		{
222 			if (icompare(*it, AUTH_PARAM) == 0)
223 			{
224 				qopSupported = true;
225 				_requestAuthParams.set(CNONCE_PARAM, createNonce());
226 				_requestAuthParams.set(QOP_PARAM, *it);
227 				updateAuthParams(request);
228 				break;
229 			}
230 		}
231 		if (!qopSupported)
232 			throw NotImplementedException("Unsupported QoP requested", qop);
233 	}
234 }
235 
236 
updateAuthParams(const HTTPRequest & request)237 void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
238 {
239 	MD5Engine engine;
240 	const std::string qop = _requestAuthParams.get(QOP_PARAM, DEFAULT_QOP);
241 	const std::string realm = _requestAuthParams.getRealm();
242 	const std::string nonce = _requestAuthParams.get(NONCE_PARAM);
243 
244 	_requestAuthParams.set(URI_PARAM, request.getURI());
245 
246 	if (qop.empty())
247 	{
248 		const std::string ha1 = digest(engine, _username, realm, _password);
249 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
250 
251 		_requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, ha2));
252 	}
253 	else if (icompare(qop, AUTH_PARAM) == 0)
254 	{
255 		const std::string cnonce = _requestAuthParams.get(CNONCE_PARAM);
256 
257 		const std::string ha1 = digest(engine, _username, realm, _password);
258 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
259 		const std::string nc = formatNonceCounter(updateNonceCounter(nonce));
260 
261 		_requestAuthParams.set(NC_PARAM, nc);
262 		_requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, nc, cnonce, qop, ha2));
263 	}
264 }
265 
266 
verifyAuthInfo(const HTTPRequest & request) const267 bool HTTPDigestCredentials::verifyAuthInfo(const HTTPRequest& request) const
268 {
269 	HTTPAuthenticationParams params(request);
270 	return verifyAuthParams(request, params);
271 }
272 
273 
verifyAuthParams(const HTTPRequest & request,const HTTPAuthenticationParams & params) const274 bool HTTPDigestCredentials::verifyAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& params) const
275 {
276 	const std::string& nonce = params.get(NONCE_PARAM);
277 	const std::string& realm = params.getRealm();
278 	const std::string& qop   = params.get(QOP_PARAM, DEFAULT_QOP);
279 	std::string response;
280 	MD5Engine engine;
281 	if (qop.empty())
282 	{
283 		const std::string ha1 = digest(engine, _username, realm, _password);
284 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
285 		response = digest(engine, ha1, nonce, ha2);
286 	}
287 	else if (icompare(qop, AUTH_PARAM) == 0)
288 	{
289 		const std::string& cnonce = params.get(CNONCE_PARAM);
290 		const std::string& nc = params.get(NC_PARAM);
291 		const std::string ha1 = digest(engine, _username, realm, _password);
292 		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
293 		response = digest(engine, ha1, nonce, nc, cnonce, qop, ha2);
294 	}
295 	return response == params.get(RESPONSE_PARAM);
296 }
297 
298 
updateNonceCounter(const std::string & nonce)299 int HTTPDigestCredentials::updateNonceCounter(const std::string& nonce)
300 {
301 	NonceCounterMap::iterator iter = _nc.find(nonce);
302 
303 	if (iter == _nc.end())
304 	{
305 		iter = _nc.insert(NonceCounterMap::value_type(nonce, 0)).first;
306 	}
307 	iter->second++;
308 
309 	return iter->second;
310 }
311 
312 
313 } } // namespace Poco::Net
314