Line data Source code
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 : */
57 8 : AwsSignatureV0::AwsSignatureV0() : AwsAbstractSignature(new AwsSignatureV0Private(this))
58 : {
59 :
60 8 : }
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 : */
75 8 : AwsSignatureV0::AwsSignatureV0(AwsSignatureV0Private * const d) : AwsAbstractSignature(d)
76 : {
77 :
78 8 : }
79 :
80 6 : 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 6 : 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 6 : if (request.url().scheme() != QString::fromLatin1("https")) {
90 3 : qWarning("AwsSignatureV%d::sign Refusing to sign insecure (non-HTTPS) request", version());
91 3 : Q_ASSERT_X(false, Q_FUNC_INFO, "insecure V1 signatures not enabled");
92 9 : return;
93 : }
94 : #endif
95 :
96 : // Set the AWSAccessKeyId, SignatureVersion and Timestamp query items, if not already.
97 3 : d->adornRequest(request, credentials);
98 :
99 : // Calculate the signature.
100 3 : const QByteArray stringToSign = d->canonicalQuery(QUrlQuery(request.url().query()));
101 : const QString signature = QString::fromUtf8(QUrl::toPercentEncoding(QString::fromUtf8(
102 3 : QMessageAuthenticationCode::hash(stringToSign, credentials.secretKey().toUtf8(),
103 9 : QCryptographicHash::Sha1).toBase64())));
104 :
105 : // Append the signature to the request.
106 6 : QUrl url = request.url();
107 3 : url.setQuery(url.query() + QLatin1String("&Signature=") + signature);
108 6 : request.setUrl(url);
109 : }
110 :
111 6 : int AwsSignatureV0::version() const
112 : {
113 6 : 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 : */
136 16 : AwsSignatureV0Private::AwsSignatureV0Private(AwsSignatureV0 * const q) : AwsAbstractSignaturePrivate(q)
137 : {
138 :
139 16 : }
140 :
141 : /**
142 : * @brief AwsSignatureV0Private destructor.
143 : */
144 24 : AwsSignatureV0Private::~AwsSignatureV0Private()
145 : {
146 :
147 24 : }
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 5 : void AwsSignatureV0Private::adornRequest(QNetworkRequest &request,
174 : const AwsAbstractCredentials &credentials) const
175 : {
176 5 : Q_Q(const AwsSignatureV0);
177 :
178 : // Set / add the necessary query items.
179 5 : QUrl url = request.url();
180 10 : QUrlQuery query(url);
181 5 : setQueryItem(query, QLatin1String("AWSAccessKeyId"), credentials.accessKeyId());
182 5 : 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 5 : 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 3 : 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 5 : if (query != QUrlQuery(url.query())) {
196 3 : qDebug() << Q_FUNC_INFO << url;
197 3 : url.setQuery(query);
198 3 : qDebug() << Q_FUNC_INFO << url;
199 3 : request.setUrl(url);
200 5 : }
201 5 : }
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 4 : QByteArray AwsSignatureV0Private::canonicalQuery(const QUrlQuery &query) const
230 : {
231 4 : 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 4 : Q_FUNC_INFO, "Timestamp or Expires query parameter required");
234 :
235 16 : return (query.queryItemValue(QLatin1String("Action")) + query.queryItemValue(QLatin1String(
236 16 : (query.hasQueryItem(QLatin1String("Timestamp"))) ? "Timestamp" : "Expires"))).toUtf8();
237 : }
238 :
239 : QTAWS_END_NAMESPACE
|