From cac4a1491bfe3d26595282a580e328bfee07351c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 13 Aug 2018 17:49:49 +0200 Subject: [PATCH] Start authenticator work --- README.md | 32 +++++ .../authentication/awsauthenticator.cpp | 8 ++ .../authentication/awsauthenticator.h | 5 +- .../authentication/sigv4utils.cpp | 123 +++++++++++++++++ .../authentication/sigv4utils.h | 27 ++++ libnymea-remoteproxy/libnymea-remoteproxy.pro | 12 +- libnymea-remoteproxy/proxyconfiguration.cpp | 124 +++++++++++++++++- libnymea-remoteproxy/proxyconfiguration.h | 46 ++++++- nymea-remoteproxy.pri | 3 + server/main.cpp | 14 +- 10 files changed, 376 insertions(+), 18 deletions(-) create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.cpp create mode 100644 libnymea-remoteproxy/authentication/sigv4utils.h diff --git a/README.md b/README.md index d0e1af6..aef9bb0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,38 @@ If you want to start the proxy server from the build directory, you need to expo $ ./server/nymea-remoteproxy -c ../nymea-remoteproxy/tests/test-certificate.crt -k ../nymea-remoteproxy/tests/test-certificate.key +## AWS SDK + +Get the latest source code and build dependecies + + $ apt update + $ apt install git build-essential cmake libcurl4-openssl-dev libssl-dev uuid-dev zlib1g-dev libpulse-dev + + $ git clone https://github.com/aws/aws-sdk-cpp.git + +Create the build and install folder + + $ cd aws-sdk-cpp + $ mkdir -p build/install + $ cd build + + $ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_ONLY="lambda" -DCMAKE_INSTALL_PREFIX=$(pwd)/install ../ + $ make -j$(nproc) + +Install build output into install directory + + $ make install + +#### Building debian package + + $ git clone https://github.com/aws/aws-sdk-cpp.git + $ cd aws-sdk-cpp + + $ git clone git@gitlab.guh.io:cloud/aws-sdk-cpp-debian.git debian + + $ crossbuilder + + # Install diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.cpp b/libnymea-remoteproxy/authentication/awsauthenticator.cpp index 5b601a7..cf95729 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.cpp +++ b/libnymea-remoteproxy/authentication/awsauthenticator.cpp @@ -10,6 +10,11 @@ AwsAuthenticator::AwsAuthenticator(QObject *parent) : } +AwsAuthenticator::~AwsAuthenticator() +{ + qCDebug(dcAuthenticator()) << "Shutting down" << name(); +} + QString AwsAuthenticator::name() const { return "AWS authenticator"; @@ -19,6 +24,9 @@ AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient) { qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token(); AuthenticationReply *reply = createAuthenticationReply(proxyClient, this); + + // TODO: start authentication request + return reply; } diff --git a/libnymea-remoteproxy/authentication/awsauthenticator.h b/libnymea-remoteproxy/authentication/awsauthenticator.h index 06da9ee..f5f921f 100644 --- a/libnymea-remoteproxy/authentication/awsauthenticator.h +++ b/libnymea-remoteproxy/authentication/awsauthenticator.h @@ -13,10 +13,13 @@ class AwsAuthenticator : public Authenticator Q_OBJECT public: explicit AwsAuthenticator(QObject *parent = nullptr); - ~AwsAuthenticator() override = default; + ~AwsAuthenticator() override; QString name() const override; +private: + + public slots: AuthenticationReply *authenticate(ProxyClient *proxyClient) override; diff --git a/libnymea-remoteproxy/authentication/sigv4utils.cpp b/libnymea-remoteproxy/authentication/sigv4utils.cpp new file mode 100644 index 0000000..147ce9c --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.cpp @@ -0,0 +1,123 @@ +#include "sigv4utils.h" + +#include +#include +#include +#include +#include +#include + +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 > 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..4861869 --- /dev/null +++ b/libnymea-remoteproxy/authentication/sigv4utils.h @@ -0,0 +1,27 @@ +#ifndef SIGV4UTILS_H +#define SIGV4UTILS_H + +#include +#include +#include + +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 diff --git a/libnymea-remoteproxy/libnymea-remoteproxy.pro b/libnymea-remoteproxy/libnymea-remoteproxy.pro index ec62305..e73399c 100644 --- a/libnymea-remoteproxy/libnymea-remoteproxy.pro +++ b/libnymea-remoteproxy/libnymea-remoteproxy.pro @@ -3,6 +3,12 @@ include(../nymea-remoteproxy.pri) TEMPLATE = lib TARGET = nymea-remoteproxy +# -L/home/timon/guh/development/cloud/aws-sdk-cpp/build/install/lib +# -laws-cpp-sdk-access-management \ +# -laws-cpp-sdk-cognito-identity \ +# -laws-cpp-sdk-iam \ +# -laws-cpp-sdk-kinesis\ + HEADERS += \ engine.h \ loggingcategories.h \ @@ -19,7 +25,8 @@ HEADERS += \ authentication/awsauthenticator.h \ authentication/authenticationreply.h \ proxyconfiguration.h \ - tunnelconnection.h + tunnelconnection.h \ + authentication/sigv4utils.h SOURCES += \ engine.cpp \ @@ -37,7 +44,8 @@ SOURCES += \ authentication/awsauthenticator.cpp \ authentication/authenticationreply.cpp \ proxyconfiguration.cpp \ - tunnelconnection.cpp + tunnelconnection.cpp \ + authentication/sigv4utils.cpp # install header file with relative subdirectory diff --git a/libnymea-remoteproxy/proxyconfiguration.cpp b/libnymea-remoteproxy/proxyconfiguration.cpp index 84cfc55..e91665c 100644 --- a/libnymea-remoteproxy/proxyconfiguration.cpp +++ b/libnymea-remoteproxy/proxyconfiguration.cpp @@ -1,10 +1,132 @@ +#include "loggingcategories.h" #include "proxyconfiguration.h" +#include + namespace remoteproxy { -ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent) +ProxyConfiguration::ProxyConfiguration(QObject *parent) : + QObject(parent) { } +bool ProxyConfiguration::loadConfiguration(const QString &fileName) +{ + QFileInfo fileInfo(fileName); + + if (!fileInfo.exists()) { + qCWarning(dcApplication()) << "Could not find configuration file" << fileName; + return false; + } + + if (!fileInfo.isReadable()) { + qCWarning(dcApplication()) << "Cannot read configuration file" << fileName; + return false; + } + + QSettings settings(fileName, QSettings::IniFormat); + + settings.beginGroup("General"); + setWriteLogFile(settings.value("writeLogs", false).toBool()); + setLogFileName(settings.value("logFile", "/var/log/nymea-remoteproxy.log").toString()); + setSslCertificateFileName(settings.value("certificate", "/etc/ssl/certs/ssl-cert-snakeoil.pem").toString()); + setSslCertificateKeyFileName(settings.value("certificateKey", "/etc/ssl/private/ssl-cert-snakeoil.key").toString()); + settings.endGroup(); + + settings.beginGroup("WebSocketServer"); + setWebSocketServerHost(QHostAddress(settings.value("host", "127.0.0.1").toString())); + setWebSocketServerPort(static_cast(settings.value("port", 1212).toInt())); + settings.endGroup(); + + settings.beginGroup("TcpServer"); + setWebSocketServerHost(QHostAddress(settings.value("host", "127.0.0.1").toString())); + setWebSocketServerPort(static_cast(settings.value("port", 1213).toInt())); + settings.endGroup(); + + return true; +} + +bool ProxyConfiguration::writeLogFile() const +{ + return m_writeLogFile; +} + +void ProxyConfiguration::setWriteLogFile(bool enabled) +{ + m_writeLogFile = enabled; +} + +QString ProxyConfiguration::logFileName() const +{ + return m_logFileName; +} + +void ProxyConfiguration::setLogFileName(const QString &logFileName) +{ + m_logFileName = logFileName; +} + +QString ProxyConfiguration::sslCertificateFileName() const +{ + return m_sslCertificateFileName; +} + +void ProxyConfiguration::setSslCertificateFileName(const QString &fileName) +{ + m_logFileName = fileName; +} + +QString ProxyConfiguration::sslCertificateKeyFileName() const +{ + return m_sslCertificateKeyFileName; +} + +void ProxyConfiguration::setSslCertificateKeyFileName(const QString &fileName) +{ + m_sslCertificateKeyFileName = fileName; +} + +QHostAddress ProxyConfiguration::webSocketServerHost() const +{ + return m_webSocketServerHost; +} + +void ProxyConfiguration::setWebSocketServerHost(const QHostAddress &address) +{ + m_webSocketServerHost = address; +} + +quint16 ProxyConfiguration::webSocketServerPort() const +{ + return m_webSocketServerPort; +} + +void ProxyConfiguration::setWebSocketServerPort(quint16 port) +{ + m_webSocketServerPort = port; +} + +QHostAddress ProxyConfiguration::tcpServerHost() const +{ + return m_tcpServerHost; +} + +void ProxyConfiguration::setTcpServerHost(const QHostAddress &address) +{ + m_tcpServerHost = address; +} + +quint16 ProxyConfiguration::tcpServerPort() const +{ + return m_tcpServerPort; +} + +void ProxyConfiguration::setTcpServerPort(quint16 port) +{ + m_tcpServerPort = port; +} + + + } diff --git a/libnymea-remoteproxy/proxyconfiguration.h b/libnymea-remoteproxy/proxyconfiguration.h index 24329b5..2f203d9 100644 --- a/libnymea-remoteproxy/proxyconfiguration.h +++ b/libnymea-remoteproxy/proxyconfiguration.h @@ -2,6 +2,8 @@ #define PROXYCONFIGURATION_H #include +#include +#include namespace remoteproxy { @@ -11,9 +13,49 @@ class ProxyConfiguration : public QObject public: explicit ProxyConfiguration(QObject *parent = nullptr); -signals: + bool loadConfiguration(const QString &fileName); -public slots: + // General + bool writeLogFile() const; + void setWriteLogFile(bool enabled); + + QString logFileName() const; + void setLogFileName(const QString &logFileName); + + QString sslCertificateFileName() const; + void setSslCertificateFileName(const QString &fileName); + + QString sslCertificateKeyFileName() const; + void setSslCertificateKeyFileName(const QString &fileName); + + // WebSocketServer + QHostAddress webSocketServerHost() const; + void setWebSocketServerHost(const QHostAddress &address); + + quint16 webSocketServerPort() const; + void setWebSocketServerPort(quint16 port); + + // TcpServer + QHostAddress tcpServerHost() const; + void setTcpServerHost(const QHostAddress &address); + + quint16 tcpServerPort() const; + void setTcpServerPort(quint16 port); + +private: + // General + bool m_writeLogFile = false; + QString m_logFileName = "/var/log/nymea-remoteproxy.log"; + QString m_sslCertificateFileName = "/etc/ssl/certs/ssl-cert-snakeoil.pem"; + QString m_sslCertificateKeyFileName = "/etc/ssl/private/ssl-cert-snakeoil.key"; + + // WebSocketServer + QHostAddress m_webSocketServerHost = QHostAddress::LocalHost; + quint16 m_webSocketServerPort = 1212; + + // TcpServer + QHostAddress m_tcpServerHost = QHostAddress::LocalHost; + quint16 m_tcpServerPort = 1213; }; diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 2b077fc..d1914bd 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -16,6 +16,9 @@ CONFIG += c++11 console QMAKE_CXXFLAGS *= -Werror -std=c++11 -g QMAKE_LFLAGS *= -std=c++11 +INCLUDEPATH += /home/timon/guh/development/cloud/aws-sdk-cpp/build/install/include +LIBS += -L/home/timon/guh/development/cloud/aws-sdk-cpp/build/install/lib -laws-cpp-sdk-core -laws-cpp-sdk-lambda + top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) diff --git a/server/main.cpp b/server/main.cpp index 71e28f8..329f48f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -124,16 +124,14 @@ int main(int argc, char *argv[]) QCommandLineOption certOption(QStringList() << "c" <<"certificate", "The path to the SSL certificate used for " "this proxy server.", "certificate"); + certOption.setDefaultValue("/etc/ssl/certs/ssl-cert-snakeoil.pem"); parser.addOption(certOption); QCommandLineOption certKeyOption(QStringList() << "k" << "certificate-key", "The path to the SSL certificate key " "used for this proxy server.", "certificate-key"); + certKeyOption.setDefaultValue("/etc/ssl/private/ssl-cert-snakeoil.key"); parser.addOption(certKeyOption); - QCommandLineOption authenticationUrlOption(QStringList() << "a" << "authentication-server", - "The server url of the AWS authentication server.", "url", "https://127.0.0.1"); - parser.addOption(authenticationUrlOption); - QCommandLineOption verboseOption(QStringList() << "v" << "verbose", "Print more verbose."); parser.addOption(verboseOption); @@ -221,13 +219,6 @@ int main(int argc, char *argv[]) exit(-1); } - // Authentication server url - QUrl authenticationServerUrl(parser.value(authenticationUrlOption)); - if (!authenticationServerUrl.isValid()) { - qCCritical(dcApplication()) << "Invalid authentication server url:" << parser.value(authenticationUrlOption); - exit(-1); - } - qCDebug(dcApplication()) << "=============================================="; qCDebug(dcApplication()) << "Starting" << application.applicationName() << application.applicationVersion(); qCDebug(dcApplication()) << "=============================================="; @@ -243,7 +234,6 @@ int main(int argc, char *argv[]) Engine::instance()->setWebSocketServerHostAddress(serverHostAddress); Engine::instance()->setWebSocketServerPort(static_cast(port)); Engine::instance()->setSslConfiguration(sslConfiguration); - Engine::instance()->setAuthenticationServerUrl(authenticationServerUrl); Engine::instance()->start(); return application.exec();