diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index 6349b4e5..5c3b24c9 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -253,34 +253,29 @@ void AWSClient::postToMQTT() { QString host = "a2addxakg5juii.iot.eu-west-1.amazonaws.com"; QString topic = "850593e9-f2ab-4e89-913a-16f848d48867/eu-west-1:88c8b0f1-3f26-46cb-81f3-ccc37dcb543a/"; - QString path = "/topics/" + topic.toUtf8().toPercentEncoding().toPercentEncoding().toPercentEncoding() + "?qos=0"; -// QString path1 = "/topics/" + topic.toUtf8().toPercentEncoding().toPercentEncoding() + "?qos=0"; + + // This is somehow broken in AWS... + // The Signature needs to be created with having the topic percentage-encoded twice + // while the actual request needs to go out with it only being encoded once. + // Now one could think this is an issue in how the signature is made, but it can't really + // be fixed there as this concerns only the actual topic, not /topics/ + // so we can't percentage-encode the whole path inside the signature helper... + QString path = "/topics/" + topic.toUtf8().toPercentEncoding().toPercentEncoding() + "?qos=0"; + QString path1 = "/topics/" + topic.toUtf8().toPercentEncoding() + "?qos=0"; QVariantMap params; params.insert("message", "Hello box"); QByteArray payload = QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact); - QByteArray dateTime = SigV4Utils::getCurrentDateTime(); -// dateTime = "20180808T134011Z"; - QNetworkRequest request("https://" + host + path); request.setRawHeader("content-type", "application/json"); request.setRawHeader("host", host.toUtf8()); - request.setRawHeader("x-amz-date", dateTime); - request.setRawHeader("x-amz-security-token", m_sessionToken); -// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-amz-json-1.0"); + SigV4Utils::signRequest(QNetworkAccessManager::PostOperation, request, region, service, m_accessKeyId, m_secretKey, m_sessionToken, payload); - QByteArray canonicalRequest = SigV4Utils::getCanonicalRequest(QNetworkAccessManager::PostOperation, request, payload); - qDebug() << "canonical request:" << qUtf8Printable(canonicalRequest); - QByteArray stringToSign = SigV4Utils::getStringToSign(canonicalRequest, dateTime, region, service); - qDebug() << "string to sign:" << stringToSign; - QByteArray signature = SigV4Utils::getSignature(stringToSign, m_secretKey, dateTime, region, service); - qDebug() << "signature:" << signature; - QByteArray authorizeHeader = SigV4Utils::getAuthorizationHeader(m_accessKeyId, dateTime, region, service, request, signature); - - request.setRawHeader("Authorization", authorizeHeader); + // Workaround MQTT broker url weirdness as described above + request.setUrl("https://" + host + path1); qDebug() << "Posting to MQTT:" << request.url().toString(); qDebug() << "HEADERS:"; diff --git a/libnymea-app-core/connection/sigv4utils.cpp b/libnymea-app-core/connection/sigv4utils.cpp index 865aa23d..29100429 100644 --- a/libnymea-app-core/connection/sigv4utils.cpp +++ b/libnymea-app-core/connection/sigv4utils.cpp @@ -12,6 +12,31 @@ SigV4Utils::SigV4Utils() } +void SigV4Utils::signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString ®ion, const QString &service, 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); + } + + if (!sessionToken.isEmpty()) { + request.setRawHeader("x-amz-security-token", sessionToken); + } + + QByteArray canonicalRequest = SigV4Utils::getCanonicalRequest(operation, request, payload); +// qDebug() << "canonical request:" << qUtf8Printable(canonicalRequest); + QByteArray stringToSign = SigV4Utils::getStringToSign(canonicalRequest, dateTime, region.toUtf8(), service.toUtf8()); +// qDebug() << "string to sign:" << stringToSign; + QByteArray signature = SigV4Utils::getSignature(stringToSign, secretAccessKey, dateTime, region, service); +// qDebug() << "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(); @@ -70,7 +95,7 @@ QByteArray SigV4Utils::getCanonicalRequest(QNetworkAccessManager::Operation oper default: Q_ASSERT_X(false, "Network operation not implemented", "SigV4Utils"); } - QByteArray uri = request.url().path().toUtf8(); + QByteArray uri = request.url().path(QUrl::FullyEncoded).toUtf8(); QUrlQuery query(request.url()); QList > queryItems = query.queryItems(); QStringList queryItemStrings; diff --git a/libnymea-app-core/connection/sigv4utils.h b/libnymea-app-core/connection/sigv4utils.h index 4861869e..6df76411 100644 --- a/libnymea-app-core/connection/sigv4utils.h +++ b/libnymea-app-core/connection/sigv4utils.h @@ -10,6 +10,10 @@ class SigV4Utils public: SigV4Utils(); + // Signes a request by adding the "X-AMZ-Date" (if not present) and "X-AMZ-Signature" headers + static void signRequest(QNetworkAccessManager::Operation operation, QNetworkRequest &request, const QString ®ion, const QString &service, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken = QByteArray(), const QByteArray &payload = QByteArray()); + + static QByteArray getCurrentDateTime();