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 "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 18 : AwsSignatureV2::AwsSignatureV2(const QCryptographicHash::Algorithm hashAlgorithm)
52 18 : : AwsAbstractSignature(new AwsSignatureV2Private(this))
53 : {
54 18 : Q_ASSERT((hashAlgorithm == QCryptographicHash::Sha1) || (hashAlgorithm == QCryptographicHash::Sha256));
55 18 : Q_D(AwsSignatureV2);
56 18 : d->hashAlgorithm = hashAlgorithm;
57 18 : }
58 :
59 1 : 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 1 : Q_D(const AwsSignatureV2);
64 :
65 : // Set the AccessKeyId, SignatureMethod, SignatureVersion and Timestamp query items, if not already.
66 1 : d->adornRequest(request, credentials);
67 :
68 : // Calculate the signature.
69 1 : const QByteArray stringToSign = d->canonicalRequest(operation, request.url());
70 : const QString signature = QString::fromUtf8(QUrl::toPercentEncoding(QString::fromUtf8(
71 1 : QMessageAuthenticationCode::hash(stringToSign, credentials.secretKey().toUtf8(),
72 3 : d->hashAlgorithm).toBase64())));
73 :
74 : // Append the signature to the request.
75 2 : QUrl url = request.url();
76 1 : url.setQuery(url.query() + QLatin1String("&Signature=") + signature);
77 2 : request.setUrl(url);
78 1 : }
79 :
80 1 : int AwsSignatureV2::version() const
81 : {
82 1 : 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 : */
105 18 : AwsSignatureV2Private::AwsSignatureV2Private(AwsSignatureV2 * const q) : AwsAbstractSignaturePrivate(q)
106 : {
107 :
108 18 : }
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 3 : void AwsSignatureV2Private::adornRequest(QNetworkRequest &request,
133 : const AwsAbstractCredentials &credentials) const
134 : {
135 : // Set / add the necessary query items.
136 3 : QUrl url = request.url();
137 6 : QUrlQuery query(url);
138 3 : setQueryItem(query, QLatin1String("AWSAccessKeyId"), credentials.accessKeyId());
139 3 : setQueryItem(query, QLatin1String("SignatureVersion"), QLatin1String("2"));
140 3 : 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 3 : 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 3 : if (query != QUrlQuery(url)) {
149 2 : qDebug() << Q_FUNC_INFO << url;
150 2 : url.setQuery(query);
151 2 : qDebug() << Q_FUNC_INFO << url;
152 2 : request.setUrl(url);
153 3 : }
154 3 : }
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 2 : QByteArray AwsSignatureV2Private::canonicalRequest(const QNetworkAccessManager::Operation operation,
185 : const QUrl &url) const
186 : {
187 4 : return httpMethod(operation).toUtf8() + '\n' +
188 8 : url.host().toUtf8() + '\n' +
189 4 : canonicalPath(url).toUtf8() + '\n' +
190 6 : 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 16 : QByteArray AwsSignatureV2Private::signatureMethod(const QCryptographicHash::Algorithm algorithm) const
211 : {
212 16 : switch (algorithm) {
213 3 : case QCryptographicHash::Sha1: return "HmacSHA1";
214 4 : case QCryptographicHash::Sha256: return "HmacSHA256";
215 : default:
216 9 : Q_ASSERT_X(false, Q_FUNC_INFO, "invalid algorithm");
217 9 : return "invalid-algorithm";
218 : }
219 : }
220 :
221 : QTAWS_END_NAMESPACE
|