some more work, still not connected to mqtt tho
This commit is contained in:
parent
383eb2f682
commit
d2170f1adb
@ -7,7 +7,13 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
|
#include "qmqtt.h"
|
||||||
|
#include "sigv4utils.h"
|
||||||
|
|
||||||
static QByteArray clientId = "8rjhfdlf9jf1suok2jcrltd6v";
|
static QByteArray clientId = "8rjhfdlf9jf1suok2jcrltd6v";
|
||||||
|
static QByteArray region = "eu-west-1";
|
||||||
|
//static QByteArray service = "iotdevicegateway";
|
||||||
|
static QByteArray service = "iotdata";
|
||||||
|
|
||||||
AWSClient::AWSClient(QObject *parent) : QObject(parent)
|
AWSClient::AWSClient(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
@ -18,6 +24,12 @@ AWSClient::AWSClient(QObject *parent) : QObject(parent)
|
|||||||
m_username = settings.value("username").toString();
|
m_username = settings.value("username").toString();
|
||||||
m_accessToken = settings.value("accessToken").toByteArray();
|
m_accessToken = settings.value("accessToken").toByteArray();
|
||||||
m_idToken = settings.value("idToken").toByteArray();
|
m_idToken = settings.value("idToken").toByteArray();
|
||||||
|
m_refreshToken = settings.value("refreshToken").toByteArray();
|
||||||
|
|
||||||
|
m_accessKeyId = settings.value("accessKeyId").toByteArray();
|
||||||
|
m_secretKey = settings.value("secretKey").toByteArray();
|
||||||
|
m_sessionToken = settings.value("sessionToken").toByteArray();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AWSClient::isLoggedIn() const
|
bool AWSClient::isLoggedIn() const
|
||||||
@ -55,46 +67,46 @@ void AWSClient::login(const QString &username, const QString &password)
|
|||||||
authParams.insert("PASSWORD", password);
|
authParams.insert("PASSWORD", password);
|
||||||
|
|
||||||
params.insert("AuthParameters", authParams);
|
params.insert("AuthParameters", authParams);
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
QNetworkReply *reply = m_nam->post(request, payload);
|
|
||||||
connect(reply, &QNetworkReply::finished, this, &AWSClient::initiateAuthReply);
|
|
||||||
qDebug() << "Logging in to AWS as user:" << username;
|
qDebug() << "Logging in to AWS as user:" << username;
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Error logging in to aws:" << reply->error() << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Failed to parse AWS login response" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap authenticationResult = jsonDoc.toVariant().toMap().value("AuthenticationResult").toMap();
|
||||||
|
m_accessToken = authenticationResult.value("AccessToken").toByteArray();
|
||||||
|
m_idToken = authenticationResult.value("IdToken").toByteArray();
|
||||||
|
m_refreshToken = authenticationResult.value("RefreshToken").toByteArray();
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("cloud");
|
||||||
|
settings.setValue("accessToken", m_accessToken);
|
||||||
|
settings.setValue("idToken", m_idToken);
|
||||||
|
settings.setValue("refreshToken", m_refreshToken);
|
||||||
|
|
||||||
|
qDebug() << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
|
||||||
|
emit isLoggedInChanged();
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AWSClient::initiateAuthReply()
|
void AWSClient::getId()
|
||||||
{
|
{
|
||||||
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
|
|
||||||
reply->deleteLater();
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
|
||||||
qWarning() << "Error logging in to aws:" << reply->error() << reply->errorString() << qUtf8Printable(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonParseError error;
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "Failed to parse AWS login response" << error.errorString();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap authenticationResult = jsonDoc.toVariant().toMap().value("AuthenticationResult").toMap();
|
|
||||||
|
|
||||||
m_accessToken = authenticationResult.value("AccessToken").toByteArray();
|
|
||||||
m_idToken = authenticationResult.value("IdToken").toByteArray();
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("cloud");
|
|
||||||
settings.setValue("accessToken", m_accessToken);
|
|
||||||
settings.setValue("idToken", m_idToken);
|
|
||||||
|
|
||||||
qDebug() << "AWS login successful";
|
|
||||||
emit isLoggedInChanged();
|
|
||||||
|
|
||||||
// return; // Why should we call GetId? Ask Luca
|
|
||||||
|
|
||||||
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
|
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
|
||||||
|
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
@ -114,19 +126,174 @@ void AWSClient::initiateAuthReply()
|
|||||||
params.insert("IdentityPoolId", "eu-west-1:108a174c-5786-40f9-966a-1a0cd33d6801");
|
params.insert("IdentityPoolId", "eu-west-1:108a174c-5786-40f9-966a-1a0cd33d6801");
|
||||||
params.insert("Logins", logins);
|
params.insert("Logins", logins);
|
||||||
|
|
||||||
jsonDoc = QJsonDocument::fromVariant(params);
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
reply = m_nam->post(request, payload);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
connect(reply, &QNetworkReply::finished, this, &AWSClient::getIdReply);
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Error calling GetId" << reply->error() << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error parsing json reply for GetId" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
|
||||||
|
|
||||||
|
qDebug() << "Received cognito identity id" << identityId;
|
||||||
|
getCredentialsForIdentity(identityId);
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AWSClient::getIdReply()
|
void AWSClient::getCredentialsForIdentity(const QString &identityId)
|
||||||
{
|
{
|
||||||
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
|
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
|
||||||
reply->deleteLater();
|
|
||||||
QByteArray data = reply->readAll();
|
QUrlQuery query;
|
||||||
qDebug() << "GetID reply" << reply->error() << reply->errorString() << qUtf8Printable(data);
|
query.addQueryItem("Action", "GetCredentialsForIdentity");
|
||||||
|
query.addQueryItem("Version", "2016-06-30");
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-amz-json-1.0");
|
||||||
|
request.setRawHeader("Host", "cognito-identity.eu-west-1.amazonaws.com");
|
||||||
|
request.setRawHeader("X-Amz-Target", "AWSCognitoIdentityService.GetCredentialsForIdentity");
|
||||||
|
|
||||||
|
QVariantMap logins;
|
||||||
|
logins.insert("cognito-idp.eu-west-1.amazonaws.com/eu-west-1_6eX6YjmXr", m_idToken);
|
||||||
|
|
||||||
|
QVariantMap params;
|
||||||
|
params.insert("IdentityId", identityId);
|
||||||
|
params.insert("Logins", logins);
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
|
||||||
|
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
|
qDebug() << "Calling GetCredentialsForIdentity:" << request.url();
|
||||||
|
qDebug() << "Headers:";
|
||||||
|
foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
||||||
|
qDebug() << headerName << ":" << request.rawHeader(headerName);
|
||||||
|
}
|
||||||
|
qDebug() << "Payload:" << qUtf8Printable(payload);
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Error calling GetCredentialsForIdentity" << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error parsing JSON reply from GetCredentialsForIdentity" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QVariantMap credentialsMap = jsonDoc.toVariant().toMap().value("Credentials").toMap();
|
||||||
|
|
||||||
|
m_accessKeyId = credentialsMap.value("AccessKeyId").toByteArray();
|
||||||
|
m_secretKey = credentialsMap.value("SecretKey").toByteArray();
|
||||||
|
m_sessionToken = credentialsMap.value("SessionToken").toByteArray();
|
||||||
|
m_expirationDate = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong());
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("cloud");
|
||||||
|
settings.setValue("accessKeyId", m_accessKeyId);
|
||||||
|
settings.setValue("secretKey", m_secretKey);
|
||||||
|
settings.setValue("sessionToken", m_sessionToken);
|
||||||
|
|
||||||
|
qDebug() << "Raw GetCredentialsForIdentity reply:" << qUtf8Printable(data);
|
||||||
|
qDebug() << "GetCredentialsForIdentity reply: \nAccess Key ID:" << m_accessKeyId << "\nSecret Key:" << m_secretKey << "\nsessionkey:" << m_sessionToken << "\nExpiration:" << m_expirationDate;
|
||||||
|
|
||||||
|
postToMQTT();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AWSClient::connectMQTT()
|
||||||
|
{
|
||||||
|
|
||||||
|
QString host = "a2addxakg5juii.iot.eu-west-1.amazonaws.com";
|
||||||
|
QString uri = "/mqtt";
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl("wss://" + host + uri));
|
||||||
|
request.setRawHeader("Host", host.toUtf8());
|
||||||
|
|
||||||
|
QByteArray dateTime = SigV4Utils::getCurrentDateTime();
|
||||||
|
// QByteArray canonicalQueryString = SigV4Utils::getCanonicalQueryString(request, m_accessKeyId, m_secretKey, m_sessionToken, region, service, QByteArray());
|
||||||
|
QByteArray canonicalQueryString = SigV4Utils::getCanonicalQueryString(request, m_accessKeyId, m_secretKey, QByteArray(), region, service, QByteArray());
|
||||||
|
|
||||||
|
QString signedRequestUrl = "wss://" + host + uri + '?' + canonicalQueryString;
|
||||||
|
|
||||||
|
qDebug() << "Connecting MQTT to" << signedRequestUrl;
|
||||||
|
|
||||||
|
QMQTT::Client *mqttClient = new QMQTT::Client(signedRequestUrl, QString(clientId), QWebSocketProtocol::VersionLatest, false);
|
||||||
|
mqttClient->setClientId(QString(clientId));
|
||||||
|
mqttClient->setPort(443);
|
||||||
|
mqttClient->setVersion(QMQTT::V3_1_1);
|
||||||
|
|
||||||
|
connect(mqttClient, &QMQTT::Client::connected, this, []{
|
||||||
|
qDebug() << "MQTT connected";
|
||||||
|
});
|
||||||
|
connect(mqttClient, &QMQTT::Client::disconnected, this, []{
|
||||||
|
qDebug() << "MQTT disconnected";
|
||||||
|
});
|
||||||
|
connect(mqttClient, &QMQTT::Client::error, this, [](const QMQTT::ClientError error){
|
||||||
|
qDebug() << "MQTT error" << error;
|
||||||
|
});
|
||||||
|
mqttClient->connectToHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
qDebug() << "Posting to MQTT:" << request.url().toString();
|
||||||
|
qDebug() << "HEADERS:";
|
||||||
|
foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
||||||
|
qDebug() << headerName << ":" << request.rawHeader(headerName);
|
||||||
|
}
|
||||||
|
qDebug() << "Payload:" << payload;
|
||||||
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [reply]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
qDebug() << "post reply" << reply->readAll();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AWSClient::fetchDevices()
|
void AWSClient::fetchDevices()
|
||||||
@ -160,6 +327,9 @@ void AWSClient::fetchDevices()
|
|||||||
ret.append(d);
|
ret.append(d);
|
||||||
}
|
}
|
||||||
emit devicesFetched(ret);
|
emit devicesFetched(ret);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -168,3 +338,4 @@ QByteArray AWSClient::accessToken() const
|
|||||||
{
|
{
|
||||||
return m_accessToken;
|
return m_accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QDate>
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
struct SRPUser;
|
|
||||||
|
|
||||||
class AWSDevice {
|
class AWSDevice {
|
||||||
public:
|
public:
|
||||||
@ -28,6 +28,9 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void fetchDevices();
|
Q_INVOKABLE void fetchDevices();
|
||||||
|
|
||||||
|
Q_INVOKABLE void postToMQTT();
|
||||||
|
Q_INVOKABLE void getId();
|
||||||
|
|
||||||
QByteArray accessToken() const;
|
QByteArray accessToken() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@ -35,9 +38,9 @@ signals:
|
|||||||
|
|
||||||
void devicesFetched(QList<AWSDevice> devices);
|
void devicesFetched(QList<AWSDevice> devices);
|
||||||
|
|
||||||
private slots:
|
private:
|
||||||
void initiateAuthReply();
|
void getCredentialsForIdentity(const QString &identityId);
|
||||||
void getIdReply();
|
void connectMQTT();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager *m_nam = nullptr;
|
QNetworkAccessManager *m_nam = nullptr;
|
||||||
@ -45,6 +48,12 @@ private:
|
|||||||
QString m_username;
|
QString m_username;
|
||||||
QByteArray m_accessToken;
|
QByteArray m_accessToken;
|
||||||
QByteArray m_idToken;
|
QByteArray m_idToken;
|
||||||
|
QByteArray m_refreshToken;
|
||||||
|
|
||||||
|
QByteArray m_accessKeyId;
|
||||||
|
QByteArray m_secretKey;
|
||||||
|
QByteArray m_sessionToken;
|
||||||
|
QDateTime m_expirationDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AWSCLIENT_H
|
#endif // AWSCLIENT_H
|
||||||
|
|||||||
122
libnymea-app-core/connection/sigv4utils.cpp
Normal file
122
libnymea-app-core/connection/sigv4utils.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#include "sigv4utils.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QMessageAuthenticationCode>
|
||||||
|
#include <QtDebug>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
SigV4Utils::SigV4Utils()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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().toUtf8();
|
||||||
|
QUrlQuery query(request.url());
|
||||||
|
QList<QPair<QString, QString> > queryItems = query.queryItems();
|
||||||
|
QStringList queryItemStrings;
|
||||||
|
for (int i = 0; i < queryItems.count(); i++) {
|
||||||
|
QPair<QString, QString> 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;
|
||||||
|
return canonicalRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray SigV4Utils::getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
27
libnymea-app-core/connection/sigv4utils.h
Normal file
27
libnymea-app-core/connection/sigv4utils.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SIGV4UTILS_H
|
||||||
|
#define SIGV4UTILS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
|
class SigV4Utils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SigV4Utils();
|
||||||
|
|
||||||
|
static QByteArray getCurrentDateTime();
|
||||||
|
|
||||||
|
|
||||||
|
static QByteArray getCanonicalQueryString(const QNetworkRequest &request, const QByteArray &accessKeyId, const QByteArray &secretAccessKey, const QByteArray &sessionToken, const QByteArray ®ion, const QByteArray &service, const QByteArray &payload);
|
||||||
|
static QByteArray getCanonicalRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, const QByteArray &payload);
|
||||||
|
static QByteArray getCanonicalHeaders(const QNetworkRequest &request);
|
||||||
|
static QByteArray getCredentialScope(const QByteArray &algorithm, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service);
|
||||||
|
static QByteArray getStringToSign(const QByteArray &canonicalRequest, const QByteArray &dateTime, const QByteArray ®ion, const QByteArray &service);
|
||||||
|
static QByteArray getSignatureKey(const QByteArray &key, const QByteArray &date, const QByteArray ®ion, const QByteArray &service);
|
||||||
|
static QByteArray getSignature(const QByteArray &stringToSign, const QByteArray &secretAccessKey, const QByteArray &dateTime, const QString ®ion, const QString &service);
|
||||||
|
static QByteArray getAuthorizationHeader(const QByteArray &accessKeyId, const QByteArray &dateTime, const QString ®ion, const QString &service, const QNetworkRequest &request, const QByteArray &signature);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SIGV4UTILS_H
|
||||||
@ -269,7 +269,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
|
|||||||
{
|
{
|
||||||
QVariantMap newRequest = request;
|
QVariantMap newRequest = request;
|
||||||
newRequest.insert("token", m_token);
|
newRequest.insert("token", m_token);
|
||||||
qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
|
// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
|
||||||
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n");
|
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,8 @@ include(../config.pri)
|
|||||||
}
|
}
|
||||||
DEFINES += QT_STATIC
|
DEFINES += QT_STATIC
|
||||||
include(../qmqtt/src/mqtt/mqtt.pri)
|
include(../qmqtt/src/mqtt/mqtt.pri)
|
||||||
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
|
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS \
|
||||||
|
connection/sigv4utils.h
|
||||||
|
|
||||||
QT -= gui
|
QT -= gui
|
||||||
QT += network websockets bluetooth
|
QT += network websockets bluetooth
|
||||||
@ -77,7 +78,8 @@ SOURCES += \
|
|||||||
ruletemplates/stateevaluatortemplate.cpp \
|
ruletemplates/stateevaluatortemplate.cpp \
|
||||||
ruletemplates/statedescriptortemplate.cpp \
|
ruletemplates/statedescriptortemplate.cpp \
|
||||||
discovery/bluetoothservicediscovery.cpp \
|
discovery/bluetoothservicediscovery.cpp \
|
||||||
connection/cloudtransport.cpp
|
connection/cloudtransport.cpp \
|
||||||
|
connection/sigv4utils.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
engine.h \
|
engine.h \
|
||||||
|
|||||||
@ -39,5 +39,16 @@ Page {
|
|||||||
Engine.awsClient.login(usernameTextField.text, passwordTextField.text);
|
Engine.awsClient.login(usernameTextField.text, passwordTextField.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "GetId"
|
||||||
|
onClicked: Engine.awsClient.getId();
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Post to MQTT"
|
||||||
|
onClicked: Engine.awsClient.postToMQTT();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ Page {
|
|||||||
ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "ManualConnectPage.qml" }
|
ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "ManualConnectPage.qml" }
|
||||||
ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "BluetoothDiscoveryPage.qml"; }
|
ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "BluetoothDiscoveryPage.qml"; }
|
||||||
ListElement { iconSource: "../images/cloud.svg"; text: qsTr("Cloud login"); page: "CloudLoginPage.qml" }
|
ListElement { iconSource: "../images/cloud.svg"; text: qsTr("Cloud login"); page: "CloudLoginPage.qml" }
|
||||||
ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "AppSettingsPage.qml" }
|
ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "../AppSettingsPage.qml" }
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
pageStack.push(model.get(index).page);
|
pageStack.push(model.get(index).page);
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#include <QtOpenGL/qgl.h>
|
#include <QtOpenGL/qgl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "libmea-core.h"
|
#include "libnymea-app-core.h"
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,8 +10,9 @@ INCLUDEPATH += ../../nymea-app/ \
|
|||||||
../../libnymea-common/ \
|
../../libnymea-common/ \
|
||||||
../../libnymea-app-core/
|
../../libnymea-app-core/
|
||||||
|
|
||||||
LIBS += -L$$top_builddir/libmea-core/ -lmea-core \
|
LIBS += -L$$top_builddir/libnymea-app-core/ -lnymea-app-core \
|
||||||
-L$$top_builddir/libnymea-common/ -lnymea-common
|
-L$$top_builddir/libnymea-common/ -lnymea-common \
|
||||||
|
-lavahi-common -lavahi-client
|
||||||
win32:Debug:LIBS += -L$$top_builddir/libmea-core/debug \
|
win32:Debug:LIBS += -L$$top_builddir/libmea-core/debug \
|
||||||
-L$$top_builddir/libnymea-common/debug
|
-L$$top_builddir/libnymea-common/debug
|
||||||
win32:Release:LIBS += -L$$top_builddir/libmea-core/release \
|
win32:Release:LIBS += -L$$top_builddir/libmea-core/release \
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
TEMPLATE = subdirs
|
TEMPLATE = subdirs
|
||||||
|
|
||||||
SUBDIRS = testrunner
|
SUBDIRS = testrunner unit
|
||||||
|
|||||||
Reference in New Issue
Block a user