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
28namespace
29{
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
59 std::string formatNonceCounter(int counter)
60 {
61 return Poco::NumberFormatter::formatHex(counter, 8);
62 }
63}
64
65
66namespace Poco {
67namespace Net {
68
69
70const std::string HTTPDigestCredentials::SCHEME = "Digest";
71const std::string HTTPDigestCredentials::DEFAULT_ALGORITHM("MD5");
72const std::string HTTPDigestCredentials::DEFAULT_QOP("");
73const std::string HTTPDigestCredentials::NONCE_PARAM("nonce");
74const std::string HTTPDigestCredentials::REALM_PARAM("realm");
75const std::string HTTPDigestCredentials::QOP_PARAM("qop");
76const std::string HTTPDigestCredentials::ALGORITHM_PARAM("algorithm");
77const std::string HTTPDigestCredentials::USERNAME_PARAM("username");
78const std::string HTTPDigestCredentials::OPAQUE_PARAM("opaque");
79const std::string HTTPDigestCredentials::URI_PARAM("uri");
80const std::string HTTPDigestCredentials::RESPONSE_PARAM("response");
81const std::string HTTPDigestCredentials::AUTH_PARAM("auth");
82const std::string HTTPDigestCredentials::CNONCE_PARAM("cnonce");
83const std::string HTTPDigestCredentials::NC_PARAM("nc");
84int HTTPDigestCredentials::_nonceCounter(0);
85Poco::FastMutex HTTPDigestCredentials::_nonceMutex;
86
87
88HTTPDigestCredentials::HTTPDigestCredentials()
89{
90}
91
92
93HTTPDigestCredentials::HTTPDigestCredentials(const std::string& username, const std::string& password):
94 _username(username),
95 _password(password)
96{
97}
98
99
100HTTPDigestCredentials::~HTTPDigestCredentials()
101{
102}
103
104
105void HTTPDigestCredentials::reset()
106{
107 _requestAuthParams.clear();
108 _nc.clear();
109}
110
111
112void HTTPDigestCredentials::setUsername(const std::string& username)
113{
114 _username = username;
115}
116
117
118void HTTPDigestCredentials::setPassword(const std::string& password)
119{
120 _password = password;
121}
122
123
124void HTTPDigestCredentials::authenticate(HTTPRequest& request, const HTTPResponse& response)
125{
126 authenticate(request, HTTPAuthenticationParams(response));
127}
128
129
130void HTTPDigestCredentials::authenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
131{
132 createAuthParams(request, responseAuthParams);
133 request.setCredentials(SCHEME, _requestAuthParams.toString());
134}
135
136
137void HTTPDigestCredentials::updateAuthInfo(HTTPRequest& request)
138{
139 updateAuthParams(request);
140 request.setCredentials(SCHEME, _requestAuthParams.toString());
141}
142
143
144void HTTPDigestCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPResponse& response)
145{
146 proxyAuthenticate(request, HTTPAuthenticationParams(response, HTTPAuthenticationParams::PROXY_AUTHENTICATE));
147}
148
149
150void HTTPDigestCredentials::proxyAuthenticate(HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
151{
152 createAuthParams(request, responseAuthParams);
153 request.setProxyCredentials(SCHEME, _requestAuthParams.toString());
154}
155
156
157void HTTPDigestCredentials::updateProxyAuthInfo(HTTPRequest& request)
158{
159 updateAuthParams(request);
160 request.setProxyCredentials(SCHEME, _requestAuthParams.toString());
161}
162
163
164std::string HTTPDigestCredentials::createNonce()
165{
166 Poco::FastMutex::ScopedLock lock(_nonceMutex);
167
168 MD5Engine md5;
169 Timestamp::TimeVal now = Timestamp().epochMicroseconds();
170
171 md5.update(&_nonceCounter, sizeof(_nonceCounter));
172 md5.update(&now, sizeof(now));
173
174 ++_nonceCounter;
175
176 return DigestEngine::digestToHex(md5.digest());
177}
178
179
180void HTTPDigestCredentials::createAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& responseAuthParams)
181{
182 // Not implemented: "domain" auth parameter and integrity protection.
183
184 if (!responseAuthParams.has(NONCE_PARAM) || !responseAuthParams.has(REALM_PARAM))
185 throw InvalidArgumentException("Invalid HTTP authentication parameters");
186
187 const std::string& algorithm = responseAuthParams.get(ALGORITHM_PARAM, DEFAULT_ALGORITHM);
188
189 if (icompare(algorithm, DEFAULT_ALGORITHM) != 0)
190 throw NotImplementedException("Unsupported digest algorithm", algorithm);
191
192 const std::string& nonce = responseAuthParams.get(NONCE_PARAM);
193 const std::string& qop = responseAuthParams.get(QOP_PARAM, DEFAULT_QOP);
194 const std::string& realm = responseAuthParams.getRealm();
195
196 _requestAuthParams.clear();
197 _requestAuthParams.set(USERNAME_PARAM, _username);
198 _requestAuthParams.set(NONCE_PARAM, nonce);
199 _requestAuthParams.setRealm(realm);
200 if (responseAuthParams.has(OPAQUE_PARAM))
201 {
202 _requestAuthParams.set(OPAQUE_PARAM, responseAuthParams.get(OPAQUE_PARAM));
203 }
204
205 if (qop.empty())
206 {
207 updateAuthParams(request);
208 }
209 else
210 {
211 Poco::StringTokenizer tok(qop, ",", Poco::StringTokenizer::TOK_TRIM);
212 bool qopSupported = false;
213 for (Poco::StringTokenizer::Iterator it = tok.begin(); it != tok.end(); ++it)
214 {
215 if (icompare(*it, AUTH_PARAM) == 0)
216 {
217 qopSupported = true;
218 _requestAuthParams.set(CNONCE_PARAM, createNonce());
219 _requestAuthParams.set(QOP_PARAM, *it);
220 updateAuthParams(request);
221 break;
222 }
223 }
224 if (!qopSupported)
225 throw NotImplementedException("Unsupported QoP requested", qop);
226 }
227}
228
229
230void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
231{
232 MD5Engine engine;
233 const std::string& qop = _requestAuthParams.get(QOP_PARAM, DEFAULT_QOP);
234 const std::string& realm = _requestAuthParams.getRealm();
235 const std::string& nonce = _requestAuthParams.get(NONCE_PARAM);
236
237 _requestAuthParams.set(URI_PARAM, request.getURI());
238
239 if (qop.empty())
240 {
241 const std::string ha1 = digest(engine, _username, realm, _password);
242 const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
243
244 _requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, ha2));
245 }
246 else if (icompare(qop, AUTH_PARAM) == 0)
247 {
248 const std::string& cnonce = _requestAuthParams.get(CNONCE_PARAM);
249
250 const std::string ha1 = digest(engine, _username, realm, _password);
251 const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
252 const std::string nc = formatNonceCounter(updateNonceCounter(nonce));
253
254 _requestAuthParams.set(NC_PARAM, nc);
255 _requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, nc, cnonce, qop, ha2));
256 }
257}
258
259
260bool HTTPDigestCredentials::verifyAuthInfo(const HTTPRequest& request) const
261{
262 HTTPAuthenticationParams params(request);
263 return verifyAuthParams(request, params);
264}
265
266
267bool HTTPDigestCredentials::verifyAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& params) const
268{
269 const std::string& nonce = params.get(NONCE_PARAM);
270 const std::string& realm = params.getRealm();
271 const std::string& qop = params.get(QOP_PARAM, DEFAULT_QOP);
272 std::string response;
273 MD5Engine engine;
274 if (qop.empty())
275 {
276 const std::string ha1 = digest(engine, _username, realm, _password);
277 const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
278 response = digest(engine, ha1, nonce, ha2);
279 }
280 else if (icompare(qop, AUTH_PARAM) == 0)
281 {
282 const std::string& cnonce = params.get(CNONCE_PARAM);
283 const std::string& nc = params.get(NC_PARAM);
284 const std::string ha1 = digest(engine, _username, realm, _password);
285 const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
286 response = digest(engine, ha1, nonce, nc, cnonce, qop, ha2);
287 }
288 return response == params.get(RESPONSE_PARAM);
289}
290
291
292int HTTPDigestCredentials::updateNonceCounter(const std::string& nonce)
293{
294 NonceCounterMap::iterator iter = _nc.find(nonce);
295
296 if (iter == _nc.end())
297 {
298 iter = _nc.insert(NonceCounterMap::value_type(nonce, 0)).first;
299 }
300 iter->second++;
301
302 return iter->second;
303}
304
305
306} } // namespace Poco::Net
307