libqtaws  0.1.0
UnofficialAWSlibraryforQt-InternalDocumentation
awssignaturev0.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 "awssignaturev0.h"
21 #include "awssignaturev0_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 <QCryptographicHash>
30 #include <QDebug>
31 #include <QNetworkRequest>
32 #include <QUrl>
33 
34 QTAWS_BEGIN_NAMESPACE
35 
36 /**
37  * @class AwsSignatureV0
38  *
39  * @brief Implements AWS Signature Version 0 \ref deprecated "(deprecated by Amazon)"
40  *
41  * @deprecated Amazon has officially deprecated signature Version 0 in favor of later,
42  * more secure signatures, such as AwsSignatureV2 and AwsSignatureV4.
43  *
44  * As version 0 signatures are rightly regarded as *insecure*, this class will refuse to sign
45  * requests that use insecure transports such as HTTP instead of HTTPS. However, insecure
46  * signatures can be enabled (why would you want to?) by defining `QTAWS_ALLOW_INSECURE_SIGNATURES`
47  * when compiling this library.
48  *
49  * @see http://s3.amazonaws.com/awsdocs/SQS/20070501/sqs-dg-20070501.pdf
50  */
51 
52 /**
53  * @brief Constructs a new AwsSignatureV0 object.
54  *
55  * Use instances of this object to provide Version 0 signatures for AWS services.
56  */
58 {
59 
60 }
61 
62 /**
63  * @internal
64  *
65  * @brief Constructs a new AwsSignatureV0 object with a specific private implementation.
66  *
67  * This internal constructor allows derived classes to provide their own derived
68  * private implementation. Specifically, AwsSignatureV1 uses this constructor to
69  * extend this class.
70  *
71  * @param d Internal private implementation to use.
72  *
73  * @see AwsSignatureV1
74  */
76 {
77 
78 }
79 
80 void AwsSignatureV0::sign(const AwsAbstractCredentials &credentials, const QNetworkAccessManager::Operation operation,
81  QNetworkRequest &request, const QByteArray &data) const
82 {
83  Q_UNUSED(operation) // Not included in V0 signatures.
84  Q_UNUSED(data) // Not included in V0 signatures.
85  Q_D(const AwsSignatureV0);
86 
87  // Refuse to sign non-HTTPS requests, unless built with QTAWS_ALLOW_INSECURE_SIGNATURES defined.
88 #ifndef QTAWS_ALLOW_INSECURE_SIGNATURES
89  if (request.url().scheme() != QString::fromLatin1("https")) {
90  qWarning("AwsSignatureV%d::sign Refusing to sign insecure (non-HTTPS) request", version());
91  Q_ASSERT_X(false, Q_FUNC_INFO, "insecure V1 signatures not enabled");
92  return;
93  }
94 #endif
95 
96  // Set the AWSAccessKeyId, SignatureVersion and Timestamp query items, if not already.
97  d->adornRequest(request, credentials);
98 
99  // Calculate the signature.
100  const QByteArray stringToSign = d->canonicalQuery(QUrlQuery(request.url().query()));
101  const QString signature = QString::fromUtf8(QUrl::toPercentEncoding(QString::fromUtf8(
102  QMessageAuthenticationCode::hash(stringToSign, credentials.secretKey().toUtf8(),
103  QCryptographicHash::Sha1).toBase64())));
104 
105  // Append the signature to the request.
106  QUrl url = request.url();
107  url.setQuery(url.query() + QLatin1String("&Signature=") + signature);
108  request.setUrl(url);
109 }
110 
112 {
113  return 0;
114 }
115 
116 /**
117  * @internal
118  *
119  * @class AwsSignatureV0Private
120  *
121  * @brief Private implementation for AwsSignatureV0.
122  *
123  * @warning This is an internal private implementation class, and as such external should
124  * code should **not** depend directly on anything contained within this class.
125  *
126  * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
127  */
128 
129 /**
130  * @internal
131  *
132  * @brief Constructs a new AwsSignatureV0Private object.
133  *
134  * @param q Pointer to this object's public AwsSignatureV0 instance.
135  */
137 {
138 
139 }
140 
141 /**
142  * @brief AwsSignatureV0Private destructor.
143  */
145 {
146 
147 }
148 
149 /**
150  * @internal
151  *
152  * @brief Add AWS Signature Version 0 adornments to an AWS request.
153  *
154  * In addition to service-specific request parameters, Amazon requires that version
155  * 1 signatures contain a number of common query parameters. This functions adds
156  * those query parameters to \a request if they're not already present.
157  *
158  * The query parameters added by this function, as required by Amazon, are:
159  * * `AWSAccessKeyId` - set to \a credentials.accessKeyId().
160  * * `SignatureVersion` - set to `0`.
161  * * `Timestamp` - set to a current UTC timestamp in an ISO 8601 format, like
162  * `2013-10-30T12:34:56Z`, unless an `Expires` value is present,
163  * in which case no `Timestamp` parameter is added.
164  *
165  * @note The `SignatureVersion` header is optional for version 0 signatures, but
166  * this function always includes it for clarity.
167  *
168  * @param request Request to adorn.
169  * @param credentials Credentials to use when adorning \a request.
170  *
171  * @see http://s3.amazonaws.com/awsdocs/SQS/20070501/sqs-dg-20070501.pdf
172  */
173 void AwsSignatureV0Private::adornRequest(QNetworkRequest &request,
174  const AwsAbstractCredentials &credentials) const
175 {
176  Q_Q(const AwsSignatureV0);
177 
178  // Set / add the necessary query items.
179  QUrl url = request.url();
180  QUrlQuery query(url);
181  setQueryItem(query, QLatin1String("AWSAccessKeyId"), credentials.accessKeyId());
182  setQueryItem(query, QLatin1String("SignatureVersion"), QString::fromLatin1("%1").arg(q->version()));
183 
184  // Amazon: "Query requests must include either Timestamp or Expires, but not both."
185  // See http://s3.amazonaws.com/awsdocs/SQS/20070501/sqs-dg-20070501.pdf
186  if (!query.hasQueryItem(QLatin1String("Expires"))) {
187  setQueryItem(query, QLatin1String("Timestamp"),
188  QString::fromUtf8(QUrl::toPercentEncoding(
189  QDateTime::currentDateTimeUtc().toString(QLatin1String("yyyy-MM-ddThh:mm:ssZ"))
190  )),
191  false); // Don't warn if its already set to something else.
192  }
193 
194  // If we've touched the query items (likely), then update the request.
195  if (query != QUrlQuery(url.query())) {
196  qDebug() << Q_FUNC_INFO << url;
197  url.setQuery(query);
198  qDebug() << Q_FUNC_INFO << url;
199  request.setUrl(url);
200  }
201 }
202 
203 /**
204  * @internal
205  *
206  * @brief Create an AWS Signature version 0 canonical query.
207  *
208  * This function returns a string containing the concatenation of `Action` and
209  * `timestamp` (or `Expires`) query parameters.
210  *
211  * For example, for the following SQS query string:
212  *
213  * ?Action=CreateQueue&QueueName=queue2&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&SignatureVersion=1&Expires=2007-01-12T12:00:00Z&Version=2006-04-01
214  *
215  * this function will return the following canonical form:
216  *
217  * CreateQueue2007-01-12T12:00:00Z
218  *
219  * @param query Query to encode the HTTP query string from.
220  *
221  * @pre \a query must already contain an `Action` and either a `Timestamp` or
222  * `Expires` query paramter. See adornRequest().
223  *
224  * @return An AWS Signature canonical query string.
225  *
226  * @see adornRequest()
227  * @see http://s3.amazonaws.com/awsdocs/SQS/20070501/sqs-dg-20070501.pdf
228  */
229 QByteArray AwsSignatureV0Private::canonicalQuery(const QUrlQuery &query) const
230 {
231  Q_ASSERT_X(query.hasQueryItem(QLatin1String("Action")), Q_FUNC_INFO, "Action query parameter required");
232  Q_ASSERT_X(query.hasQueryItem(QLatin1String("Timestamp")) || query.hasQueryItem(QLatin1String("Expires")),
233  Q_FUNC_INFO, "Timestamp or Expires query parameter required");
234 
235  return (query.queryItemValue(QLatin1String("Action")) + query.queryItemValue(QLatin1String(
236  (query.hasQueryItem(QLatin1String("Timestamp"))) ? "Timestamp" : "Expires"))).toUtf8();
237 }
238 
239 QTAWS_END_NAMESPACE
AwsSignatureV0()
Constructs a new AwsSignatureV0 object.
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 int version() const
AWS Signature version implemented by this class.
virtual QByteArray canonicalQuery(const QUrlQuery &query) const
Create an AWS Signature version 0 canonical query.
Private implementation for AwsAbstractSignature.
virtual QString secretKey() const =0
AWS secret access key for this credentials object.
Interface class for providing AWS credentials.
Private implementation for AwsSignatureV0.
virtual void sign(const AwsAbstractCredentials &credentials, const QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QByteArray &data=QByteArray()) const
Sign an AWS request.
AwsSignatureV0Private(AwsSignatureV0 *const q)
Constructs a new AwsSignatureV0Private object.
void adornRequest(QNetworkRequest &request, const AwsAbstractCredentials &credentials) const
Add AWS Signature Version 0 adornments to an AWS request.
virtual ~AwsSignatureV0Private()
AwsSignatureV0Private destructor.
virtual QString accessKeyId() const =0
AWS access key ID for this credentials object.
Implements AWS Signature Version 0 (deprecated by Amazon).
Interface class for providing AWS signatures.