From c7dd9e72aaab9dcb1d5d34e1df6815ed1dd8d66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 24 Aug 2018 13:30:51 +0200 Subject: [PATCH] Add native lambda invoce structure for first tests --- .../authentication/authenticationprocess.cpp | 108 +++++++++++-- .../authentication/authenticationprocess.h | 8 +- .../authentication/sigv4utils.cpp | 153 ++++++++++++++++++ .../authentication/sigv4utils.h | 33 ++++ libnymea-remoteproxy/libnymea-remoteproxy.pro | 2 + 5 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.cpp create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.h diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.cpp b/libnymea-remoteproxy/authentication/authenticationprocess.cpp index 0d5af68..dfb612b 100644 --- a/libnymea-remoteproxy/authentication/authenticationprocess.cpp +++ b/libnymea-remoteproxy/authentication/authenticationprocess.cpp @@ -27,6 +27,9 @@ #include #include + +#include "sigv4utils.h" + namespace remoteproxy { AuthenticationProcess::AuthenticationProcess(QNetworkAccessManager *manager, QObject *parent) : @@ -50,7 +53,53 @@ void AuthenticationProcess::requestDynamicCredentials() connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onDynamicCredentialsReady); } -void AuthenticationProcess::startVerificationProcess() +void AuthenticationProcess::invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken) +{ + // Known configurations + QString region = "eu-west-1"; + QString lambdaFunctionName = "system-services-authorizer-dev-checkToken"; + QString invocationType = "RequestResponse"; + QString service = "lambda"; + + // {'url_path': '/2015-03-31/functions/system-services-authorizer-dev-checkToken/invocations', 'query_string': {}, 'method': 'POST', 'headers': {'X-Amz-Invocation-Type': 'RequestResponse', 'User-Agent': 'aws-cli/1.14.44 Python/3.6.5 Linux/4.15.0-1019-aws botocore/1.8.48'}, 'body': b'{"token": "...."}', 'url': 'https://lambda.eu-west-1.amazonaws.com/2015-03-31/functions/system-services-authorizer-dev-checkToken/invocations', 'context': {'client_region': 'eu-west-1', 'client_config': , 'has_streaming_input': True, 'auth_type': None}} + + // Create request map + QVariantMap requestMap; + requestMap.insert("token", m_token); + QByteArray payload = QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact); + + QUrl requestUrl; + requestUrl.setScheme("https"); + requestUrl.setHost(QString("lambda.%1.amazonaws.com").arg(region)); + requestUrl.setPath(QString("/2015-03-31/functions/%1/invocations").arg(lambdaFunctionName)); + + QNetworkRequest request(requestUrl); + request.setRawHeader("User-Agent", QString("%1/%2 JSON-RPC/%3").arg(SERVER_NAME_STRING).arg(SERVER_VERSION_STRING).arg(API_VERSION_STRING).toUtf8()); + request.setRawHeader("Content-Type", "application/json"); + request.setRawHeader("host", requestUrl.host().toUtf8()); + request.setRawHeader("x-amz-invocation-type", invocationType.toUtf8()); + + SigV4Utils::signRequest(QNetworkAccessManager::PostOperation, request, region, service, accessKey.toUtf8(), secretAccessKey.toUtf8(), sessionToken.toUtf8(), payload); + + qCDebug(dcAuthenticationProcess()) << "Invoke lambda function" << lambdaFunctionName; + + qCDebug(dcAuthenticationProcess()) << "--------------------------------------------"; + qCDebug(dcAuthenticationProcess()) << request.url().toString(); + + foreach (const QByteArray &rawHeader, request.rawHeaderList()) { + qDebug(dcAuthenticationProcess()) << request.rawHeader(rawHeader); + } + qCDebug(dcAuthenticationProcess()) << payload; + qCDebug(dcAuthenticationProcess()) << "--------------------------------------------"; + + m_lambdaTimer.start(); + + QNetworkReply *reply = m_manager->get(request); + connect(reply, &QNetworkReply::finished, this, &AuthenticationProcess::onLambdaInvokeFunctionFinished); + +} + +void AuthenticationProcess::startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken) { if (m_process->state() != QProcess::NotRunning) { qCWarning(dcAuthenticationProcess()) << "Authentication process already running. Killing the running process and restart."; @@ -65,10 +114,10 @@ void AuthenticationProcess::startVerificationProcess() QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("AWS_DEFAULT_REGION", "eu-west-1"); if (m_dynamicCredentials) { - qCDebug(dcAuthenticationProcess()) << "Using dynamic credentials" << m_awsAccessKeyId << m_awsSecretAccessKey << m_awsSessionToken; - env.insert("AWS_ACCESS_KEY_ID", m_awsAccessKeyId); - env.insert("AWS_SECRET_ACCESS_KEY", m_awsSecretAccessKey); - env.insert("AWS_SESSION_TOKEN", m_awsSessionToken); + qCDebug(dcAuthenticationProcess()) << "Using dynamic credentials" << accessKey << secretAccessKey << sessionToken; + env.insert("AWS_ACCESS_KEY_ID", accessKey); + env.insert("AWS_SECRET_ACCESS_KEY", secretAccessKey); + env.insert("AWS_SESSION_TOKEN", sessionToken); } m_process->setProcessEnvironment(env); @@ -111,11 +160,49 @@ void AuthenticationProcess::onDynamicCredentialsReady() QVariantMap response = jsonDoc.toVariant().toMap(); qCDebug(dcAuthentication()) << "-->" << response; - m_awsAccessKeyId = response.value("AccessKeyId").toString(); - m_awsSecretAccessKey = response.value("SecretAccessKey").toString(); - m_awsSessionToken = response.value("Token").toString(); + startVerificationProcess(response.value("AccessKeyId").toString(), + response.value("SecretAccessKey").toString(), + response.value("Token").toString()); +} + +void AuthenticationProcess::onLambdaInvokeFunctionFinished() +{ + QNetworkReply *reply = static_cast(sender()); + reply->deleteLater(); + + qCDebug(dcAuthenticationProcess()) << "Lambda invoke request finished (" << m_lambdaTimer.elapsed() << "[ms] )"; + if (reply->error()) { + qCWarning(dcAuthenticationProcess()) << "Dynamic credentials reply error: " << reply->errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QByteArray data = reply->readAll(); + + qCDebug(dcAuthenticationProcess()) << "Invoke lambda function response ready"; + + qCDebug(dcAuthenticationProcess()) << "--------------------------------------------"; + qCDebug(dcAuthenticationProcess()) << reply->request().url().toString(); + + foreach (const QByteArray &rawHeader, reply->rawHeaderList()) { + qDebug(dcAuthenticationProcess()) << reply->rawHeader(rawHeader); + } + qCDebug(dcAuthenticationProcess()) << data; + qCDebug(dcAuthenticationProcess()) << "--------------------------------------------"; + + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + + if(error.error != QJsonParseError::NoError) { + qCWarning(dcAuthenticationProcess()) << "Failed to parse dynamic credentials reply data" << data << ":" << error.errorString(); + emit authenticationFinished(Authenticator::AuthenticationErrorProxyError); + return; + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + qCDebug(dcAuthentication()) << "-->" << response; - startVerificationProcess(); } void AuthenticationProcess::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) @@ -199,8 +286,9 @@ void AuthenticationProcess::authenticate(const QString &token) // Request the access information requestDynamicCredentials(); } else { + // FIXME: // Direct call aws cli and assume the credentials will be provided static - startVerificationProcess(); + // startVerificationProcess(); } } diff --git a/libnymea-remoteproxy/authentication/authenticationprocess.h b/libnymea-remoteproxy/authentication/authenticationprocess.h index 650d030..67421d7 100644 --- a/libnymea-remoteproxy/authentication/authenticationprocess.h +++ b/libnymea-remoteproxy/authentication/authenticationprocess.h @@ -45,17 +45,16 @@ private: QString m_resultFileName; bool m_dynamicCredentials = true; - QString m_awsAccessKeyId; - QString m_awsSecretAccessKey; - QString m_awsSessionToken; QNetworkAccessManager *m_manager = nullptr; QProcess *m_process = nullptr; QElapsedTimer m_requestTimer; + QElapsedTimer m_lambdaTimer; QElapsedTimer m_processTimer; void requestDynamicCredentials(); - void startVerificationProcess(); + void invokeLambdaFunction(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken); + void startVerificationProcess(const QString accessKey, const QString &secretAccessKey, const QString &sessionToken); void cleanUp(); signals: @@ -63,6 +62,7 @@ signals: private slots: void onDynamicCredentialsReady(); + void onLambdaInvokeFunctionFinished(); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); public slots: diff --git a/libnymea-remoteproxy/authentication/sigv4utils.cpp b/libnymea-remoteproxy/authentication/sigv4utils.cpp new file mode 100644 index 0000000..539a3fe --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.cpp @@ -0,0 +1,153 @@ +#include "sigv4utils.h" + + +#include +#include +#include +#include +#include +#include + +namespace remoteproxy { + + +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(); +} + +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; + 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; +} +} diff --git a/libnymea-remoteproxy/authentication/sigv4utils.h b/libnymea-remoteproxy/authentication/sigv4utils.h new file mode 100644 index 0000000..1e46063 --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.h @@ -0,0 +1,33 @@ +#ifndef SIGV4UTILS_H +#define SIGV4UTILS_H + +#include +#include +#include + +namespace remoteproxy { + +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(); + + 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 diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index 9f3b43e..76c18ad 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -24,6 +24,7 @@ HEADERS += \ authentication/userinformation.h \ authentication/authenticationprocess.h \ authentication/dummyauthenticator.h \ + authentication/sigv4utils.h SOURCES += \ engine.cpp \ @@ -46,6 +47,7 @@ SOURCES += \ authentication/userinformation.cpp \ authentication/authenticationprocess.cpp \ authentication/dummyauthenticator.cpp \ + authentication/sigv4utils.cpp # install header file with relative subdirectory