/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2018 Simon Stürz * * Copyright (C) 2018 Michael Zanetti * * * * This file is part of nymea-remoteproxy. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "sigv4utils.h" #include #include #include #include #include #include #include "loggingcategories.h" namespace remoteproxy { SigV4Utils::SigV4Utils() { } void SigV4Utils::signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString ®ion, const QString &service, const QString &invocationType, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray &payload) { QByteArray dateTime; if (request.rawHeaderList().contains("x-amz-date")) { dateTime = request.rawHeader("x-amz-date"); } else { dateTime = SigV4Utils::getCurrentDateTime(); request.setRawHeader("x-amz-date", dateTime); } request.setRawHeader("x-amz-invocation-type", invocationType.toUtf8()); if (!sessionToken.isEmpty()) { request.setRawHeader("x-amz-security-token", sessionToken); } QByteArray canonicalRequest = SigV4Utils::getCanonicalRequest(operation, request, payload); qCDebug(dcAuthenticationProcess()) << "canonical request:" << endl << qUtf8Printable(canonicalRequest); QByteArray stringToSign = SigV4Utils::getStringToSign(canonicalRequest, dateTime, region.toUtf8(), service.toUtf8()); qCDebug(dcAuthenticationProcess()) << "string to sign:" << endl << qUtf8Printable(stringToSign); QByteArray signature = SigV4Utils::getSignature(stringToSign, secretAccessKey, dateTime, region, service); qCDebug(dcAuthenticationProcess()) << "signature:" << signature; QByteArray authorizeHeader = SigV4Utils::getAuthorizationHeader(accessKeyId, dateTime, region, service, request, signature); request.setRawHeader("Authorization", authorizeHeader); } QByteArray SigV4Utils::getCurrentDateTime() { return QDateTime::currentDateTime().toUTC().toString("yyyyMMddThhmmssZ").toUtf8(); } QByteArray SigV4Utils::getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload) { QByteArray algorithm = "AWS4-HMAC-SHA256"; QByteArray dateTime = getCurrentDateTime(); QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); QByteArray canonicalQueryString; canonicalQueryString += "X-Amz-Algorithm=AWS4-HMAC-SHA256"; canonicalQueryString += "&X-Amz-Credential=" + QByteArray(accessKeyId + '/' + credentialScope).toPercentEncoding(); canonicalQueryString += "&X-Amz-Date=" + dateTime; if (request.rawHeaderList().count() > 0){ canonicalQueryString += "&X-Amz-SignedHeaders=" + request.rawHeaderList().join(';').toLower(); } QByteArray canonicalRequest = getCanonicalRequest(QNetworkAccessManager::GetOperation, request, payload); QByteArray stringToSign = getStringToSign(canonicalRequest, dateTime, region, service); QByteArray signature = getSignature(stringToSign, secretAccessKey, dateTime, region, service); canonicalQueryString += "&X-Amz-Signature=" + signature; if (!sessionToken.isEmpty()) { canonicalQueryString += "&X-Amz-Security-Token=" + sessionToken.toPercentEncoding(); } return canonicalQueryString; } QByteArray SigV4Utils::getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service) { QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; return QMessageAuthenticationCode::hash("aws4_request", QMessageAuthenticationCode::hash(service, QMessageAuthenticationCode::hash(region, QMessageAuthenticationCode::hash(date, "AWS4" + key, hashAlgorithm), hashAlgorithm), hashAlgorithm), hashAlgorithm); } QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload) { QByteArray canonicalRequest; QByteArray method; switch (operation) { case QNetworkAccessManager::GetOperation: method = "GET"; break; case QNetworkAccessManager::PostOperation: method = "POST"; break; default: Q_ASSERT_X(false, "Network operation not implemented", "SigV4Utils"); } QByteArray uri = request.url().path(QUrl::FullyEncoded).toUtf8(); QUrlQuery query(request.url()); QList > queryItems = query.queryItems(); QStringList queryItemStrings; for (int i = 0; i < queryItems.count(); i++) { QPair queryItem = queryItems.at(i); queryItemStrings.append(queryItem.first + '=' + queryItem.second); } queryItemStrings.sort(Qt::CaseInsensitive); QByteArray canonicalQueryString = queryItemStrings.join('&').toUtf8(); QByteArray canonicalHeaders; foreach(const QByteArray &headerName, request.rawHeaderList()) { canonicalHeaders += headerName.toLower() + ':' + request.rawHeader(headerName) + '\n'; } QByteArray payloadHash = QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex(); canonicalRequest = method + '\n' + uri + '\n' + canonicalQueryString + '\n' + canonicalHeaders + '\n' + request.rawHeaderList().join(';').toLower() + '\n' + payloadHash.toLower(); return canonicalRequest; } QByteArray SigV4Utils::getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) { Q_UNUSED(algorithm) QByteArray credentialScope = dateTime.left(8) + '/' + region + '/' + service + "/aws4_request"; return credentialScope; } QByteArray SigV4Utils::getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service) { QByteArray algorithm = "AWS4-HMAC-SHA256"; QByteArray credentialScope = getCredentialScope(algorithm, dateTime, region, service); QByteArray stringToSign = algorithm + '\n' + dateTime + '\n' + credentialScope + '\n' + QCryptographicHash::hash(canonicalRequest, QCryptographicHash::Sha256).toHex(); return stringToSign; } QByteArray SigV4Utils::getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service) { QByteArray signingKey = getSignatureKey(secretAccessKey, dateTime.left(8), region.toUtf8(), service.toUtf8()); QByteArray signature = QMessageAuthenticationCode::hash(stringToSign, signingKey, QCryptographicHash::Sha256).toHex(); return signature; } QByteArray SigV4Utils::getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature) { QByteArray authHeader = "AWS4-HMAC-SHA256 Credential=" + accessKeyId + '/' + dateTime.left(8) + '/' + region.toUtf8() + '/' + service.toUtf8() + '/' + "aws4_request, SignedHeaders=" + request.rawHeaderList().join(';').toLower() + ", Signature=" + signature; return authHeader; } }