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