libqtaws  0.1.0
UnofficialAWSlibraryforQt-InternalDocumentation
awssignaturev2.cpp
1 /*
2  Copyright 2013-2015 Paul Colby
3 
4  This file is part of libqtaws.
5 
6  Libqtaws is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Libqtaws is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with libqtaws. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "awssignaturev2.h"
21 #include "awssignaturev2_p.h"
22 
23 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
24 #include "qmessageauthenticationcode.h"
25 #else
26 #include <QMessageAuthenticationCode>
27 #endif
28 
29 #include <QDebug>
30 #include <QNetworkRequest>
31 #include <QUrl>
32 
33 QTAWS_BEGIN_NAMESPACE
34 
35 /**
36  * @class AwsSignatureV2
37  *
38  * @brief Implements AWS Signature Version 2.
39  *
40  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
41  */
42 
43 /**
44  * @brief Constructs a new AwsSignatureV2 object.
45  *
46  * Use instances of this object to provide Version 2 signatures for AWS services.
47  *
48  * @param hashAlgorithm Hash algorithm for signatures. Must be either QCryptographicHash::Sha1
49  * or QCryptographicHash::Sha256 (default, recommended).
50  */
51 AwsSignatureV2::AwsSignatureV2(const QCryptographicHash::Algorithm hashAlgorithm)
53 {
54  Q_ASSERT((hashAlgorithm == QCryptographicHash::Sha1) || (hashAlgorithm == QCryptographicHash::Sha256));
55  Q_D(AwsSignatureV2);
56  d->hashAlgorithm = hashAlgorithm;
57 }
58 
59 void AwsSignatureV2::sign(const AwsAbstractCredentials &credentials, const QNetworkAccessManager::Operation operation,
60  QNetworkRequest &request, const QByteArray &data) const
61 {
62  Q_UNUSED(data) // Not included in V2 signatures.
63  Q_D(const AwsSignatureV2);
64 
65  // Set the AccessKeyId, SignatureMethod, SignatureVersion and Timestamp query items, if not already.
66  d->adornRequest(request, credentials);
67 
68  // Calculate the signature.
69  const QByteArray stringToSign = d->canonicalRequest(operation, request.url());
70  const QString signature = QString::fromUtf8(QUrl::toPercentEncoding(QString::fromUtf8(
71  QMessageAuthenticationCode::hash(stringToSign, credentials.secretKey().toUtf8(),
72  d->hashAlgorithm).toBase64())));
73 
74  // Append the signature to the request.
75  QUrl url = request.url();
76  url.setQuery(url.query() + QLatin1String("&Signature=") + signature);
77  request.setUrl(url);
78 }
79 
81 {
82  return 2;
83 }
84 
85 /**
86  * @internal
87  *
88  * @class AwsSignatureV2Private
89  *
90  * @brief Private implementation for AwsSignatureV2.
91  *
92  * @warning This is an internal private implementation class, and as such external should
93  * code should **not** depend directly on anything contained within this class.
94  *
95  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
96  */
97 
98 /**
99  * @internal
100  *
101  * @brief Constructs a new AwsSignatureV2Private object.
102  *
103  * @param q Pointer to this object's public AwsSignatureV2 instance.
104  */
106 {
107 
108 }
109 
110 /**
111  * @internal
112  *
113  * @brief Add AWS Signature Version 2 adornments to an AWS request.
114  *
115  * In addition to service-specific request parameters, Amazon requires that version
116  * 2 signatures contain a number of common query parameters. This functions adds
117  * those query parameters to \a request if they're not already present.
118  *
119  * The query parameters added by this function, as required by Amazon, are:
120  * * `AWSAccessKeyId` - set to \a credentials.accessKeyId().
121  * * `SignatureMethod` - set to `HMAC-SHA1` or `HMAC-SHA256`.
122  * * `SignatureVersion` - set to `2`.
123  * * `Timestamp` - set to a current UTC timestamp in an ISO 8601 format, like
124  * `2013-10-30T12:34:56Z`.
125  *
126  * @param request Request to adorn.
127  * @param credentials Credentials to use when adorning \a request.
128  *
129  * @see signatureMethod
130  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
131  */
132 void AwsSignatureV2Private::adornRequest(QNetworkRequest &request,
133  const AwsAbstractCredentials &credentials) const
134 {
135  // Set / add the necessary query items.
136  QUrl url = request.url();
137  QUrlQuery query(url);
138  setQueryItem(query, QLatin1String("AWSAccessKeyId"), credentials.accessKeyId());
139  setQueryItem(query, QLatin1String("SignatureVersion"), QLatin1String("2"));
140  setQueryItem(query, QLatin1String("SignatureMethod"), QString::fromUtf8(signatureMethod(hashAlgorithm)));
141  setQueryItem(query, QLatin1String("Timestamp"),
142  QString::fromUtf8(QUrl::toPercentEncoding(
143  QDateTime::currentDateTimeUtc().toString(QLatin1String("yyyy-MM-ddThh:mm:ssZ"))
144  )),
145  false); // Don't warn if its already set to something else.
146 
147  // If we've touched the query items (likely), then update the request.
148  if (query != QUrlQuery(url)) {
149  qDebug() << Q_FUNC_INFO << url;
150  url.setQuery(query);
151  qDebug() << Q_FUNC_INFO << url;
152  request.setUrl(url);
153  }
154 }
155 
156 /**
157  * @internal
158  *
159  * @brief Create an AWS V2 Signature canonical request.
160  *
161  * This function creates a canonical representation of an AWS request as defined by
162  * Amazon's V2 signature specification.
163  *
164  * For example, for the following HTTP `GET` request:
165  *
166  * https://elasticmapreduce.amazonaws.com?Action=DescribeJobFlows&Version=2009-03-31&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&SignatureVersion=2SignatureMethod=HmacSHA256Timestamp=2011-10-03T15%3A19%3A30
167  *
168  * this function will return the following canonical form:
169  *
170  * GET
171  * elasticmapreduce.amazonaws.com
172  * /
173  * AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31
174  *
175  * @note All URL components are encoded to UTF-8, as required by Amazon.
176  *
177  * @param operation The HTTP method being requested.
178  * @param url The URL being request.
179  *
180  * @return An AWS V2 Signature canonical request.
181  *
182  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
183  */
184 QByteArray AwsSignatureV2Private::canonicalRequest(const QNetworkAccessManager::Operation operation,
185  const QUrl &url) const
186 {
187  return httpMethod(operation).toUtf8() + '\n' +
188  url.host().toUtf8() + '\n' +
189  canonicalPath(url).toUtf8() + '\n' +
190  canonicalQuery(QUrlQuery(url));
191 }
192 
193 /**
194  * @brief Create an AWS V2 Signature method designation.
195  *
196  * This function returns a signature method designation, as defined by Amazon, for
197  * use with V2 signatures.
198  *
199  * For example, if the algorith is `QCryptographicHash::Sha256`, this function will
200  * return `HmacSHA256`.
201  *
202  * @note Amazon only supports two algorithms for V2 signatures - SHA1 and SHA256.
203  *
204  * @param algorithm The hash algorithm to get the canonical designation for.
205  *
206  * @return An AWS V2 Signature method designation.
207  *
208  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
209  */
210 QByteArray AwsSignatureV2Private::signatureMethod(const QCryptographicHash::Algorithm algorithm) const
211 {
212  switch (algorithm) {
213  case QCryptographicHash::Sha1: return "HmacSHA1";
214  case QCryptographicHash::Sha256: return "HmacSHA256";
215  default:
216  Q_ASSERT_X(false, Q_FUNC_INFO, "invalid algorithm");
217  return "invalid-algorithm";
218  }
219 }
220 
221 QTAWS_END_NAMESPACE
bool setQueryItem(QUrlQuery &query, const QString &key, const QString &value, const bool warnOnNonIdenticalDuplicate=true) const
Set a query item, checking for existing values first.
virtual void sign(const AwsAbstractCredentials &credentials, const QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QByteArray &data=QByteArray()) const
Sign an AWS request.
Implements AWS Signature Version 2.
Private implementation for AwsAbstractSignature.
QByteArray canonicalQuery(const QUrlQuery &query) const
Create an AWS Signature canonical query.
virtual QString secretKey() const =0
AWS secret access key for this credentials object.
QByteArray canonicalRequest(const QNetworkAccessManager::Operation operation, const QUrl &url) const
Create an AWS V2 Signature canonical request.
Interface class for providing AWS credentials.
QCryptographicHash::Algorithm hashAlgorithm
Hash algorithm to use when signing.
Private implementation for AwsSignatureV2.
AwsSignatureV2(const QCryptographicHash::Algorithm hashAlgorithm=QCryptographicHash::Sha256)
Constructs a new AwsSignatureV2 object.
virtual QString accessKeyId() const =0
AWS access key ID for this credentials object.
virtual int version() const
AWS Signature version implemented by this class.
void adornRequest(QNetworkRequest &request, const AwsAbstractCredentials &credentials) const
Add AWS Signature Version 2 adornments to an AWS request.
QString httpMethod(const QNetworkAccessManager::Operation operation) const
Create an AWS Signature request method string.
Interface class for providing AWS signatures.
QByteArray signatureMethod(const QCryptographicHash::Algorithm algorithm) const
Create an AWS V2 Signature method designation.
AwsSignatureV2Private(AwsSignatureV2 *const q)
Constructs a new AwsSignatureV2Private object.
QString canonicalPath(const QUrl &url) const
Create an AWS Signature canonical path.