1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:authorization-protocol
4
5#include "qoauth1signature.h"
6#include "qoauth1signature_p.h"
7
8#include <QtCore/qurlquery.h>
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qmessageauthenticationcode.h>
11
12#include <QtNetwork/qnetworkaccessmanager.h>
13
14QT_BEGIN_NAMESPACE
15
16Q_STATIC_LOGGING_CATEGORY(loggingCategory, "qt.networkauth.oauth1.signature")
17
18/*!
19 \class QOAuth1Signature
20 \inmodule QtNetworkAuth
21 \ingroup oauth
22 \brief Implements OAuth 1 signature methods.
23 \since 5.8
24
25 OAuth-authenticated requests can have two sets of credentials:
26 those passed via the "oauth_consumer_key" parameter and those in
27 the "oauth_token" parameter. In order for the server to verify
28 the authenticity of the request and prevent unauthorized access,
29 the client needs to prove that it is the rightful owner of the
30 credentials. This is accomplished using the shared-secret (or
31 RSA key) part of each set of credentials.
32
33 OAuth specifies three methods for the client to establish its
34 rightful ownership of the credentials: "HMAC-SHA1", "RSA-SHA1",
35 and "PLAINTEXT". Each generates a "signature" with which the
36 request is "signed"; the first two use a digest of the data
37 signed in generating this, though the last does not. The
38 "RSA-SHA1" method is not supported here; it would use an RSA key
39 rather than the shared-secret associated with the client
40 credentials.
41*/
42
43/*!
44 \enum QOAuth1Signature::HttpRequestMethod
45
46 Indicates the HTTP request method.
47
48 \value Head HEAD method.
49 \value Get GET method.
50 \value Put PUT method.
51 \value Post POST method.
52 \value Delete DELETE method.
53 \value Custom Identifies a custom method.
54 \value Unknown Method not set.
55*/
56
57static_assert(static_cast<int>(QOAuth1Signature::HttpRequestMethod::Head) ==
58 static_cast<int>(QNetworkAccessManager::HeadOperation) &&
59 static_cast<int>(QOAuth1Signature::HttpRequestMethod::Get) ==
60 static_cast<int>(QNetworkAccessManager::GetOperation) &&
61 static_cast<int>(QOAuth1Signature::HttpRequestMethod::Put) ==
62 static_cast<int>(QNetworkAccessManager::PutOperation) &&
63 static_cast<int>(QOAuth1Signature::HttpRequestMethod::Post) ==
64 static_cast<int>(QNetworkAccessManager::PostOperation) &&
65 static_cast<int>(QOAuth1Signature::HttpRequestMethod::Delete) ==
66 static_cast<int>(QNetworkAccessManager::DeleteOperation),
67 "Invalid QOAuth1Signature::HttpRequestMethod enumeration values");
68
69QOAuth1SignaturePrivate QOAuth1SignaturePrivate::shared_null;
70
71QOAuth1SignaturePrivate::QOAuth1SignaturePrivate(const QUrl &url,
72 QOAuth1Signature::HttpRequestMethod method,
73 const QMultiMap<QString, QVariant> &parameters,
74 const QString &clientSharedKey,
75 const QString &tokenSecret) :
76 method(method), url(url), clientSharedKey(clientSharedKey), tokenSecret(tokenSecret),
77 parameters(parameters)
78{}
79
80QByteArray QOAuth1SignaturePrivate::signatureBaseString() const
81{
82 // https://tools.ietf.org/html/rfc5849#section-3.4.1
83 QByteArray base;
84
85 switch (method) {
86 case QOAuth1Signature::HttpRequestMethod::Head:
87 base.append(s: "HEAD");
88 break;
89 case QOAuth1Signature::HttpRequestMethod::Get:
90 base.append(s: "GET");
91 break;
92 case QOAuth1Signature::HttpRequestMethod::Put:
93 base.append(s: "PUT");
94 break;
95 case QOAuth1Signature::HttpRequestMethod::Post:
96 base.append(s: "POST");
97 break;
98 case QOAuth1Signature::HttpRequestMethod::Delete:
99 base.append(s: "DELETE");
100 break;
101 case QOAuth1Signature::HttpRequestMethod::Custom:
102 if (!customVerb.isEmpty()) {
103 base.append(a: customVerb);
104 } else {
105 qCCritical(loggingCategory, "QOAuth1Signature: HttpRequestMethod::Custom requires "
106 "the verb to be set via setCustomMethodString");
107 }
108 break;
109 default:
110 qCCritical(loggingCategory, "QOAuth1Signature: HttpRequestMethod not supported");
111 }
112 base.append(c: '&');
113 base.append(a: QUrl::toPercentEncoding(url.toString(options: QUrl::RemoveQuery)) + "&");
114
115 QMultiMap<QString, QVariant> p = parameters;
116 {
117 // replace '+' with spaces now before decoding so that '%2B' gets left as '+'
118 const QString query = url.query().replace(before: QLatin1Char('+'), after: QLatin1Char(' '));
119 const auto queryItems = QUrlQuery(query).queryItems(encoding: QUrl::FullyDecoded);
120 for (auto it = queryItems.begin(), end = queryItems.end(); it != end; ++it)
121 p.insert(key: it->first, value: it->second);
122 }
123 base.append(a: encodeHeaders(headers: p));
124 return base;
125}
126
127QByteArray QOAuth1SignaturePrivate::secret() const
128{
129 QByteArray secret;
130 secret.append(a: QUrl::toPercentEncoding(clientSharedKey));
131 secret.append(c: '&');
132 secret.append(a: QUrl::toPercentEncoding(tokenSecret));
133 return secret;
134}
135
136QByteArray QOAuth1SignaturePrivate::parameterString(const QMultiMap<QString, QVariant> &parameters)
137{
138 QByteArray ret;
139 auto previous = parameters.end();
140 for (auto it = parameters.begin(), end = parameters.end(); it != end; previous = it++) {
141 if (previous != parameters.end()) {
142 if (Q_UNLIKELY(previous.key() == it.key()))
143 qCWarning(loggingCategory, "duplicated key %s", qPrintable(it.key()));
144 ret.append(s: "&");
145 }
146 ret.append(a: QUrl::toPercentEncoding(it.key()));
147 ret.append(s: "=");
148 ret.append(a: QUrl::toPercentEncoding(it.value().toString()));
149 }
150 return ret;
151}
152
153QByteArray QOAuth1SignaturePrivate::encodeHeaders(const QMultiMap<QString, QVariant> &headers)
154{
155 return QUrl::toPercentEncoding(QString::fromLatin1(ba: parameterString(parameters: headers)));
156}
157
158/*!
159 Creates a QOAuth1Signature using
160 \list
161 \li \a url as the target address
162 \li \a method as the HTTP method used to send the request
163 \li and the given user \a parameters to augment the request.
164 \endlist
165*/
166QOAuth1Signature::QOAuth1Signature(const QUrl &url, QOAuth1Signature::HttpRequestMethod method,
167 const QMultiMap<QString, QVariant> &parameters) :
168 d(new QOAuth1SignaturePrivate(url, method, parameters))
169{}
170
171/*!
172 Creates a QOAuth1Signature using
173 \list
174 \li \a url as the target address
175 \li \a clientSharedKey as the user token used to verify the
176 signature
177 \li \a tokenSecret as the negotiated token used to verify
178 the signature
179 \li \a method as the HTTP method used to send the request
180 \li and the given user \a parameters to augment the request.
181 \endlist
182*/
183QOAuth1Signature::QOAuth1Signature(const QUrl &url, const QString &clientSharedKey,
184 const QString &tokenSecret, HttpRequestMethod method,
185 const QMultiMap<QString, QVariant> &parameters) :
186 d(new QOAuth1SignaturePrivate(url, method, parameters, clientSharedKey, tokenSecret))
187{}
188
189/*!
190 Creates a copy of \a other.
191*/
192QOAuth1Signature::QOAuth1Signature(const QOAuth1Signature &other) : d(other.d)
193{}
194
195/*!
196 Move-constructs a QOAuth1Signature instance, taking over the
197 private data \a other was using.
198*/
199QOAuth1Signature::QOAuth1Signature(QOAuth1Signature &&other) : d(std::move(other.d))
200{
201}
202
203/*!
204 Destroys the QOAuth1Signature.
205*/
206QOAuth1Signature::~QOAuth1Signature()
207{}
208
209/*!
210 Returns the request method.
211*/
212QOAuth1Signature::HttpRequestMethod QOAuth1Signature::httpRequestMethod() const
213{
214 return d->method;
215}
216
217/*!
218 Sets the request \a method.
219*/
220void QOAuth1Signature::setHttpRequestMethod(QOAuth1Signature::HttpRequestMethod method)
221{
222 d->method = method;
223}
224
225/*!
226 \since 5.13
227
228 Returns the custom method string.
229
230 \sa httpRequestMethod()
231*/
232QByteArray QOAuth1Signature::customMethodString() const
233{
234 return d->customVerb;
235}
236
237/*!
238 \since 5.13
239
240 Sets a custom request method. Will set the httpRequestMethod
241 to QOAuth1Signature::HttpRequestMethod::Custom and store the
242 \a verb to use it for the generation of the signature.
243
244 \note Using this method is required when working with custom verbs.
245 Setting only the request method will fail, as the signure needs to
246 know the actual verb.
247
248 \sa setHttpRequestMethod(), HttpRequestMethod
249*/
250void QOAuth1Signature::setCustomMethodString(const QByteArray &verb)
251{
252 d->method = QOAuth1Signature::HttpRequestMethod::Custom;
253 d->customVerb = verb;
254}
255
256/*!
257 Returns the URL.
258*/
259QUrl QOAuth1Signature::url() const
260{
261 return d->url;
262}
263
264/*!
265 Sets the URL to \a url.
266*/
267void QOAuth1Signature::setUrl(const QUrl &url)
268{
269 d->url = url;
270}
271
272/*!
273 Returns the parameters.
274*/
275QMultiMap<QString, QVariant> QOAuth1Signature::parameters() const
276{
277 return d->parameters;
278}
279
280/*!
281 Sets the \a parameters.
282*/
283void QOAuth1Signature::setParameters(const QMultiMap<QString, QVariant> &parameters)
284{
285 d->parameters.clear();
286 for (auto it = parameters.cbegin(), end = parameters.cend(); it != end; ++it)
287 d->parameters.insert(key: it.key(), value: it.value());
288}
289
290/*!
291 Adds the request \a body to the signature. When a POST request
292 body contains arguments they should be included in the signed
293 data.
294*/
295void QOAuth1Signature::addRequestBody(const QUrlQuery &body)
296{
297 const auto list = body.queryItems();
298 for (auto it = list.begin(), end = list.end(); it != end; ++it)
299 d->parameters.replace(key: it->first, value: it->second);
300}
301
302/*!
303 Inserts a new pair \a key, \a value into the signature. When a
304 POST request body contains arguments they should be included in
305 the signed data.
306*/
307void QOAuth1Signature::insert(const QString &key, const QVariant &value)
308{
309 d->parameters.replace(key, value);
310}
311
312/*!
313 Retrieves the list of keys of parameters included in the signed
314 data.
315*/
316QList<QString> QOAuth1Signature::keys() const
317{
318 return d->parameters.uniqueKeys();
319}
320
321/*!
322 Removes \a key and any associated value from the signed data.
323*/
324QVariant QOAuth1Signature::take(const QString &key)
325{
326 return d->parameters.take(key);
327}
328
329/*!
330 Returns the value associated with \a key, if present in the
331 signed data, otherwise \a defaultValue.
332*/
333QVariant QOAuth1Signature::value(const QString &key, const QVariant &defaultValue) const
334{
335 return d->parameters.value(key, defaultValue);
336}
337
338/*!
339 Returns the user secret used to generate the signature.
340*/
341QString QOAuth1Signature::clientSharedKey() const
342{
343 return d->clientSharedKey;
344}
345
346/*!
347 Sets \a secret as the user secret used to generate the signature.
348*/
349void QOAuth1Signature::setClientSharedKey(const QString &secret)
350{
351 d->clientSharedKey = secret;
352}
353
354/*!
355 Returns the negotiated secret used to generate the signature.
356*/
357QString QOAuth1Signature::tokenSecret() const
358{
359 return d->tokenSecret;
360}
361
362/*!
363 Sets \a secret as the negotiated secret used to generate the
364 signature.
365*/
366void QOAuth1Signature::setTokenSecret(const QString &secret)
367{
368 d->tokenSecret = secret;
369}
370
371/*!
372 Generates the HMAC-SHA1 signature using the client shared secret
373 and, where available, token secret.
374*/
375QByteArray QOAuth1Signature::hmacSha1() const
376{
377 return QMessageAuthenticationCode::hash(message: d->signatureBaseString(), key: d->secret(),
378 method: QCryptographicHash::Sha1);
379}
380
381/*!
382 Generates the RSA-SHA1 signature.
383
384 \note Currently this method is not supported.
385*/
386QByteArray QOAuth1Signature::rsaSha1() const
387{
388 qCCritical(loggingCategory, "RSA-SHA1 signing method not supported");
389 return QByteArray();
390}
391
392/*!
393 Generates the PLAINTEXT signature.
394*/
395QByteArray QOAuth1Signature::plainText() const
396{
397 return plainText(clientSharedSecret: d->clientSharedKey, tokenSecret: d->tokenSecret);
398}
399
400/*!
401 Generates a PLAINTEXT signature from the client secret
402 \a clientSharedKey and the token secret \a tokenSecret.
403*/
404QByteArray QOAuth1Signature::plainText(const QString &clientSharedKey,
405 const QString &tokenSecret)
406{
407 QByteArray ret;
408 ret += clientSharedKey.toUtf8() + '&' + tokenSecret.toUtf8();
409 return ret;
410}
411
412/*!
413 Swaps signature \a other with this signature. This operation is
414 very fast and never fails.
415*/
416void QOAuth1Signature::swap(QOAuth1Signature &other)
417{
418 qSwap(value1&: d, value2&: other.d);
419}
420
421QOAuth1Signature &QOAuth1Signature::operator=(const QOAuth1Signature &other)
422{
423 if (d != other.d) {
424 QOAuth1Signature tmp(other);
425 tmp.swap(other&: *this);
426 }
427 return *this;
428}
429
430/*!
431 Move-assigns \a other to this signature and returns a reference to this QOAuth1Signature.
432*/
433QOAuth1Signature &QOAuth1Signature::operator=(QOAuth1Signature &&other)
434{
435 QOAuth1Signature moved(std::move(other));
436 swap(other&: moved);
437 return *this;
438}
439
440QT_END_NAMESPACE
441

source code of qtnetworkauth/src/oauth/qoauth1signature.cpp