1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** Contact: Qt Software Information ([email protected])
|
---|
5 | **
|
---|
6 | ** This file is part of the QtNetwork module of the Qt Toolkit.
|
---|
7 | **
|
---|
8 | ** $QT_BEGIN_LICENSE:LGPL$
|
---|
9 | ** Commercial Usage
|
---|
10 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
11 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
12 | ** Software or, alternatively, in accordance with the terms contained in
|
---|
13 | ** a written agreement between you and Nokia.
|
---|
14 | **
|
---|
15 | ** GNU Lesser General Public License Usage
|
---|
16 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
---|
17 | ** General Public License version 2.1 as published by the Free Software
|
---|
18 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
---|
19 | ** packaging of this file. Please review the following information to
|
---|
20 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
---|
21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
---|
22 | **
|
---|
23 | ** In addition, as a special exception, Nokia gives you certain
|
---|
24 | ** additional rights. These rights are described in the Nokia Qt LGPL
|
---|
25 | ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
|
---|
26 | ** package.
|
---|
27 | **
|
---|
28 | ** GNU General Public License Usage
|
---|
29 | ** Alternatively, this file may be used under the terms of the GNU
|
---|
30 | ** General Public License version 3.0 as published by the Free Software
|
---|
31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
32 | ** packaging of this file. Please review the following information to
|
---|
33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
---|
34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
---|
35 | **
|
---|
36 | ** If you are unsure which license is appropriate for your use, please
|
---|
37 | ** contact the sales department at [email protected].
|
---|
38 | ** $QT_END_LICENSE$
|
---|
39 | **
|
---|
40 | ****************************************************************************/
|
---|
41 |
|
---|
42 | #include <qauthenticator.h>
|
---|
43 | #include <qauthenticator_p.h>
|
---|
44 | #include <qdebug.h>
|
---|
45 | #include <qhash.h>
|
---|
46 | #include <qbytearray.h>
|
---|
47 | #include <qcryptographichash.h>
|
---|
48 | #include <qhttp.h>
|
---|
49 | #include <qdatastream.h>
|
---|
50 | #include <qendian.h>
|
---|
51 | #include <qstring.h>
|
---|
52 |
|
---|
53 | QT_BEGIN_NAMESPACE
|
---|
54 |
|
---|
55 | #include "../../3rdparty/des/des.cpp"
|
---|
56 |
|
---|
57 | static QByteArray qNtlmPhase1();
|
---|
58 | static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
|
---|
59 |
|
---|
60 | /*!
|
---|
61 | \class QAuthenticator
|
---|
62 | \brief The QAuthenticator class provides an authentication object.
|
---|
63 | \since 4.3
|
---|
64 |
|
---|
65 | \reentrant
|
---|
66 | \ingroup io
|
---|
67 | \inmodule QtNetwork
|
---|
68 |
|
---|
69 | The QAuthenticator class is usually used in the
|
---|
70 | \l{QHttp::}{authenticationRequired()} and
|
---|
71 | \l{QHttp::}{proxyAuthenticationRequired()} signals of QHttp and
|
---|
72 | QAbstractSocket. The class provides a way to pass back the required
|
---|
73 | authentication information to the socket when accessing services that
|
---|
74 | require authentication.
|
---|
75 |
|
---|
76 | \sa QSslSocket
|
---|
77 | */
|
---|
78 |
|
---|
79 |
|
---|
80 | /*!
|
---|
81 | Constructs an empty authentication object
|
---|
82 | */
|
---|
83 | QAuthenticator::QAuthenticator()
|
---|
84 | : d(0)
|
---|
85 | {
|
---|
86 | }
|
---|
87 |
|
---|
88 | /*!
|
---|
89 | Destructs the object
|
---|
90 | */
|
---|
91 | QAuthenticator::~QAuthenticator()
|
---|
92 | {
|
---|
93 | if (d && !d->ref.deref())
|
---|
94 | delete d;
|
---|
95 | }
|
---|
96 |
|
---|
97 | /*!
|
---|
98 | Constructs a copy of \a other.
|
---|
99 | */
|
---|
100 | QAuthenticator::QAuthenticator(const QAuthenticator &other)
|
---|
101 | : d(other.d)
|
---|
102 | {
|
---|
103 | if (d)
|
---|
104 | d->ref.ref();
|
---|
105 | }
|
---|
106 |
|
---|
107 | /*!
|
---|
108 | Assigns the contents of \a other to this authenticator.
|
---|
109 | */
|
---|
110 | QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other)
|
---|
111 | {
|
---|
112 | if (d == other.d)
|
---|
113 | return *this;
|
---|
114 | detach();
|
---|
115 | d->user = other.d->user;
|
---|
116 | d->password = other.d->password;
|
---|
117 | return *this;
|
---|
118 | }
|
---|
119 |
|
---|
120 | /*!
|
---|
121 | Returns true if this authenticator is identical to \a other; otherwise
|
---|
122 | returns false.
|
---|
123 | */
|
---|
124 | bool QAuthenticator::operator==(const QAuthenticator &other) const
|
---|
125 | {
|
---|
126 | if (d == other.d)
|
---|
127 | return true;
|
---|
128 | return d->user == other.d->user
|
---|
129 | && d->password == other.d->password
|
---|
130 | && d->realm == other.d->realm
|
---|
131 | && d->method == other.d->method;
|
---|
132 | }
|
---|
133 |
|
---|
134 | /*!
|
---|
135 | \fn bool QAuthenticator::operator!=(const QAuthenticator &other) const
|
---|
136 |
|
---|
137 | Returns true if this authenticator is different from \a other; otherwise
|
---|
138 | returns false.
|
---|
139 | */
|
---|
140 |
|
---|
141 | /*!
|
---|
142 | returns the user used for authentication.
|
---|
143 | */
|
---|
144 | QString QAuthenticator::user() const
|
---|
145 | {
|
---|
146 | return d ? d->user : QString();
|
---|
147 | }
|
---|
148 |
|
---|
149 | /*!
|
---|
150 | Sets the \a user used for authentication.
|
---|
151 | */
|
---|
152 | void QAuthenticator::setUser(const QString &user)
|
---|
153 | {
|
---|
154 | detach();
|
---|
155 | d->user = user;
|
---|
156 | }
|
---|
157 |
|
---|
158 | /*!
|
---|
159 | returns the password used for authentication.
|
---|
160 | */
|
---|
161 | QString QAuthenticator::password() const
|
---|
162 | {
|
---|
163 | return d ? d->password : QString();
|
---|
164 | }
|
---|
165 |
|
---|
166 | /*!
|
---|
167 | Sets the \a password used for authentication.
|
---|
168 | */
|
---|
169 | void QAuthenticator::setPassword(const QString &password)
|
---|
170 | {
|
---|
171 | detach();
|
---|
172 | d->password = password;
|
---|
173 | }
|
---|
174 |
|
---|
175 | /*!
|
---|
176 | \internal
|
---|
177 | */
|
---|
178 | void QAuthenticator::detach()
|
---|
179 | {
|
---|
180 | if (!d) {
|
---|
181 | d = new QAuthenticatorPrivate;
|
---|
182 | d->ref = 1;
|
---|
183 | return;
|
---|
184 | }
|
---|
185 |
|
---|
186 | qAtomicDetach(d);
|
---|
187 | d->phase = QAuthenticatorPrivate::Start;
|
---|
188 | }
|
---|
189 |
|
---|
190 | /*!
|
---|
191 | returns the realm requiring authentication.
|
---|
192 | */
|
---|
193 | QString QAuthenticator::realm() const
|
---|
194 | {
|
---|
195 | return d ? d->realm : QString();
|
---|
196 | }
|
---|
197 |
|
---|
198 |
|
---|
199 | /*!
|
---|
200 | returns true if the authenticator is null.
|
---|
201 | */
|
---|
202 | bool QAuthenticator::isNull() const
|
---|
203 | {
|
---|
204 | return !d;
|
---|
205 | }
|
---|
206 |
|
---|
207 | QAuthenticatorPrivate::QAuthenticatorPrivate()
|
---|
208 | : ref(0)
|
---|
209 | , method(None)
|
---|
210 | , phase(Start)
|
---|
211 | , nonceCount(0)
|
---|
212 | {
|
---|
213 | cnonce = QCryptographicHash::hash(QByteArray::number(qrand(), 16) + QByteArray::number(qrand(), 16),
|
---|
214 | QCryptographicHash::Md5).toHex();
|
---|
215 | nonceCount = 0;
|
---|
216 | }
|
---|
217 |
|
---|
218 | #ifndef QT_NO_HTTP
|
---|
219 | void QAuthenticatorPrivate::parseHttpResponse(const QHttpResponseHeader &header, bool isProxy)
|
---|
220 | {
|
---|
221 | QList<QPair<QString, QString> > values = header.values();
|
---|
222 | const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
|
---|
223 |
|
---|
224 | method = None;
|
---|
225 | /*
|
---|
226 | Fun from the HTTP 1.1 specs, that we currently ignore:
|
---|
227 |
|
---|
228 | User agents are advised to take special care in parsing the WWW-
|
---|
229 | Authenticate field value as it might contain more than one challenge,
|
---|
230 | or if more than one WWW-Authenticate header field is provided, the
|
---|
231 | contents of a challenge itself can contain a comma-separated list of
|
---|
232 | authentication parameters.
|
---|
233 | */
|
---|
234 |
|
---|
235 | QString headerVal;
|
---|
236 | for (int i = 0; i < values.size(); ++i) {
|
---|
237 | const QPair<QString, QString> ¤t = values.at(i);
|
---|
238 | if (current.first.toLower() != QLatin1String(search))
|
---|
239 | continue;
|
---|
240 | QString str = current.second;
|
---|
241 | if (method < Basic && str.startsWith(QLatin1String("Basic"), Qt::CaseInsensitive)) {
|
---|
242 | method = Basic; headerVal = str.mid(6);
|
---|
243 | } else if (method < Ntlm && str.startsWith(QLatin1String("NTLM"), Qt::CaseInsensitive)) {
|
---|
244 | method = Ntlm;
|
---|
245 | headerVal = str.mid(5);
|
---|
246 | } else if (method < DigestMd5 && str.startsWith(QLatin1String("Digest"), Qt::CaseInsensitive)) {
|
---|
247 | method = DigestMd5;
|
---|
248 | headerVal = str.mid(7);
|
---|
249 | }
|
---|
250 | }
|
---|
251 |
|
---|
252 | challenge = headerVal.trimmed().toLatin1();
|
---|
253 | QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
|
---|
254 |
|
---|
255 | switch(method) {
|
---|
256 | case Basic:
|
---|
257 | realm = QString::fromLatin1(options.value("realm"));
|
---|
258 | if (user.isEmpty())
|
---|
259 | phase = Done;
|
---|
260 | break;
|
---|
261 | case Ntlm:
|
---|
262 | // #### extract from header
|
---|
263 | realm = QString();
|
---|
264 | break;
|
---|
265 | case DigestMd5: {
|
---|
266 | realm = QString::fromLatin1(options.value("realm"));
|
---|
267 | if (options.value("stale").toLower() == "true")
|
---|
268 | phase = Start;
|
---|
269 | if (user.isEmpty())
|
---|
270 | phase = Done;
|
---|
271 | break;
|
---|
272 | }
|
---|
273 | default:
|
---|
274 | realm = QString();
|
---|
275 | challenge = QByteArray();
|
---|
276 | phase = Invalid;
|
---|
277 | }
|
---|
278 | }
|
---|
279 | #endif
|
---|
280 |
|
---|
281 | QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path)
|
---|
282 | {
|
---|
283 | QByteArray response;
|
---|
284 | const char *methodString = 0;
|
---|
285 | switch(method) {
|
---|
286 | case QAuthenticatorPrivate::None:
|
---|
287 | methodString = "";
|
---|
288 | phase = Done;
|
---|
289 | break;
|
---|
290 | case QAuthenticatorPrivate::Plain:
|
---|
291 | response = '\0' + user.toUtf8() + '\0' + password.toUtf8();
|
---|
292 | phase = Done;
|
---|
293 | break;
|
---|
294 | case QAuthenticatorPrivate::Basic:
|
---|
295 | methodString = "Basic ";
|
---|
296 | response = user.toLatin1() + ':' + password.toLatin1();
|
---|
297 | response = response.toBase64();
|
---|
298 | phase = Done;
|
---|
299 | break;
|
---|
300 | case QAuthenticatorPrivate::Login:
|
---|
301 | if (challenge.contains("VXNlciBOYW1lAA==")) {
|
---|
302 | response = user.toUtf8().toBase64();
|
---|
303 | phase = Phase2;
|
---|
304 | } else if (challenge.contains("UGFzc3dvcmQA")) {
|
---|
305 | response = password.toUtf8().toBase64();
|
---|
306 | phase = Done;
|
---|
307 | }
|
---|
308 | break;
|
---|
309 | case QAuthenticatorPrivate::CramMd5:
|
---|
310 | break;
|
---|
311 | case QAuthenticatorPrivate::DigestMd5:
|
---|
312 | methodString = "Digest ";
|
---|
313 | response = digestMd5Response(challenge, requestMethod, path);
|
---|
314 | phase = Done;
|
---|
315 | break;
|
---|
316 | case QAuthenticatorPrivate::Ntlm:
|
---|
317 | methodString = "NTLM ";
|
---|
318 | if (challenge.isEmpty()) {
|
---|
319 | response = qNtlmPhase1().toBase64();
|
---|
320 | if (user.isEmpty())
|
---|
321 | phase = Done;
|
---|
322 | else
|
---|
323 | phase = Phase2;
|
---|
324 | } else {
|
---|
325 | response = qNtlmPhase3(this, QByteArray::fromBase64(challenge)).toBase64();
|
---|
326 | phase = Done;
|
---|
327 | }
|
---|
328 |
|
---|
329 | break;
|
---|
330 | }
|
---|
331 | return QByteArray(methodString) + response;
|
---|
332 | }
|
---|
333 |
|
---|
334 |
|
---|
335 | // ---------------------------- Digest Md5 code ----------------------------------------
|
---|
336 |
|
---|
337 | QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationChallenge(const QByteArray &challenge)
|
---|
338 | {
|
---|
339 | QHash<QByteArray, QByteArray> options;
|
---|
340 | // parse the challenge
|
---|
341 | const char *d = challenge.constData();
|
---|
342 | const char *end = d + challenge.length();
|
---|
343 | while (d < end) {
|
---|
344 | while (d < end && (*d == ' ' || *d == '\n' || *d == '\r'))
|
---|
345 | ++d;
|
---|
346 | const char *start = d;
|
---|
347 | while (d < end && *d != '=')
|
---|
348 | ++d;
|
---|
349 | QByteArray key = QByteArray(start, d - start);
|
---|
350 | ++d;
|
---|
351 | if (d >= end)
|
---|
352 | break;
|
---|
353 | bool quote = (*d == '"');
|
---|
354 | if (quote)
|
---|
355 | ++d;
|
---|
356 | if (d >= end)
|
---|
357 | break;
|
---|
358 | start = d;
|
---|
359 | QByteArray value;
|
---|
360 | while (d < end) {
|
---|
361 | bool backslash = false;
|
---|
362 | if (*d == '\\' && d < end - 1) {
|
---|
363 | ++d;
|
---|
364 | backslash = true;
|
---|
365 | }
|
---|
366 | if (!backslash) {
|
---|
367 | if (quote) {
|
---|
368 | if (*d == '"')
|
---|
369 | break;
|
---|
370 | } else {
|
---|
371 | if (*d == ',')
|
---|
372 | break;
|
---|
373 | }
|
---|
374 | }
|
---|
375 | value += *d;
|
---|
376 | ++d;
|
---|
377 | }
|
---|
378 | while (d < end && *d != ',')
|
---|
379 | ++d;
|
---|
|
---|