fix aws login, workaround wss certificate issue

This commit is contained in:
Michael Zanetti 2018-09-28 11:23:27 +02:00
parent 568b17004c
commit 8af4cd49d5
6 changed files with 117 additions and 40 deletions

View File

@ -104,10 +104,9 @@ AWSClient::AWSClient(QObject *parent) : QObject(parent),
m_userId = settings.value("userId").toString(); m_userId = settings.value("userId").toString();
m_password = settings.value("password").toString(); m_password = settings.value("password").toString();
m_accessToken = settings.value("accessToken").toByteArray(); m_accessToken = settings.value("accessToken").toByteArray();
m_accessTokenExpiry = settings.value("accessTokenExpiry").toDateTime(); // m_accessTokenExpiry = settings.value("accessTokenExpiry").toDateTime();
m_idToken = settings.value("idToken").toByteArray(); m_idToken = settings.value("idToken").toByteArray();
m_refreshToken = settings.value("refreshToken").toByteArray(); m_refreshToken = settings.value("refreshToken").toByteArray();
m_confirmationPending = settings.value("confirmationPending", false).toBool();
m_identityId = settings.value("identityId").toByteArray(); m_identityId = settings.value("identityId").toByteArray();
@ -302,12 +301,6 @@ void AWSClient::signup(const QString &username, const QString &password)
emit signupResult(LoginErrorNoError); emit signupResult(LoginErrorNoError);
QSettings settings;
settings.beginGroup("cloud");
settings.setValue("username", m_username);
settings.setValue("password", m_password);
settings.setValue("confirmationPending", true);
m_confirmationPending = true; m_confirmationPending = true;
emit confirmationPendingChanged(); emit confirmationPendingChanged();
}); });
@ -364,6 +357,8 @@ void AWSClient::confirmRegistration(const QString &code)
return; return;
} }
m_confirmationPending = false;
emit confirmationPendingChanged();
emit confirmationResult(LoginErrorNoError); emit confirmationResult(LoginErrorNoError);
login(m_username, m_password); login(m_username, m_password);
fetchDevices(); fetchDevices();
@ -591,9 +586,6 @@ void AWSClient::getId()
return; return;
} }
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray(); m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
QSettings settings;
settings.beginGroup("cloud");
settings.setValue("identityId", m_identityId);
qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data); qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data);
getCredentialsForIdentity(m_identityId); getCredentialsForIdentity(m_identityId);
@ -767,6 +759,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
QSettings settings; QSettings settings;
qDebug() << "settings has:" << settings.childGroups();
bool newLogin = !settings.childGroups().contains("cloud"); bool newLogin = !settings.childGroups().contains("cloud");
@ -777,6 +770,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
settings.setValue("password", m_password); settings.setValue("password", m_password);
settings.setValue("accessToken", m_accessToken); settings.setValue("accessToken", m_accessToken);
settings.setValue("accessTokenExpiry", m_accessTokenExpiry); settings.setValue("accessTokenExpiry", m_accessTokenExpiry);
settings.setValue("identityId", m_identityId);
settings.setValue("idToken", m_idToken); settings.setValue("idToken", m_idToken);
settings.setValue("refreshToken", m_refreshToken); settings.setValue("refreshToken", m_refreshToken);
settings.setValue("accessKeyId", m_accessKeyId); settings.setValue("accessKeyId", m_accessKeyId);
@ -787,7 +781,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
emit loginResult(LoginErrorNoError); emit loginResult(LoginErrorNoError);
if (newLogin) { if (newLogin) {
// qDebug() << "new login!"; qDebug() << "new login!";
emit isLoggedInChanged(); emit isLoggedInChanged();
} }

View File

@ -6,6 +6,9 @@
#include <QUrlQuery> #include <QUrlQuery>
#include <QSettings> #include <QSettings>
#include <QMetaEnum> #include <QMetaEnum>
#include <QStandardPaths>
#include <QFile>
#include <QDir>
#include "nymeatransportinterface.h" #include "nymeatransportinterface.h"
@ -21,21 +24,31 @@ bool NymeaConnection::connect(const QString &url)
} }
m_currentUrl = QUrl(url); m_currentUrl = QUrl(url);
emit currentUrlChanged();
if (!m_transports.contains(m_currentUrl.scheme())) { if (!m_transports.contains(m_currentUrl.scheme())) {
qWarning() << "Cannot connect to urls of scheme" << m_currentUrl.scheme() << "Supported schemes are" << m_transports.keys(); qWarning() << "Cannot connect to urls of scheme" << m_currentUrl.scheme() << "Supported schemes are" << m_transports.keys();
return false; return false;
} }
m_currentTransport = m_transports.value(m_currentUrl.scheme())->createTransport();
// Create a new transport
m_currentTransport = m_transports.value(m_currentUrl.scheme())->createTransport();
QObject::connect(m_currentTransport, &NymeaTransportInterface::sslErrors, this, &NymeaConnection::onSslErrors); QObject::connect(m_currentTransport, &NymeaTransportInterface::sslErrors, this, &NymeaConnection::onSslErrors);
QObject::connect(m_currentTransport, &NymeaTransportInterface::error, this, &NymeaConnection::onError); QObject::connect(m_currentTransport, &NymeaTransportInterface::error, this, &NymeaConnection::onError);
QObject::connect(m_currentTransport, &NymeaTransportInterface::connected, this, &NymeaConnection::onConnected); QObject::connect(m_currentTransport, &NymeaTransportInterface::connected, this, &NymeaConnection::onConnected);
QObject::connect(m_currentTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected); QObject::connect(m_currentTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected);
// signal forwarding
QObject::connect(m_currentTransport, &NymeaTransportInterface::dataReady, this, &NymeaConnection::dataAvailable); QObject::connect(m_currentTransport, &NymeaTransportInterface::dataReady, this, &NymeaConnection::dataAvailable);
qDebug() << "Should connect to url" << m_currentUrl; // Load any certificate we might have for this url
QByteArray pem;
if (loadPem(m_currentUrl, pem)) {
qDebug() << "Loaded SSL certificate for" << m_currentUrl.host();
QList<QSslError> expectedSslErrors;
expectedSslErrors.append(QSslError::HostNameMismatch);
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, QSslCertificate(pem)));
m_currentTransport->ignoreSslErrors(expectedSslErrors);
}
qDebug() << "Connecting to:" << m_currentUrl;
return m_currentTransport->connect(m_currentUrl); return m_currentTransport->connect(m_currentUrl);
} }
@ -48,19 +61,27 @@ void NymeaConnection::disconnect()
m_currentTransport->disconnect(); m_currentTransport->disconnect();
} }
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &fingerprint) void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem)
{ {
QSettings settings; storePem(url, pem);
settings.beginGroup("acceptedCertificates");
settings.setValue(QUrl(url).host(), fingerprint);
settings.endGroup();
} }
bool NymeaConnection::isTrusted(const QString &url) bool NymeaConnection::isTrusted(const QString &url)
{ {
// Do we have a legacy fingerprint
QSettings settings; QSettings settings;
settings.beginGroup("acceptedCertificates"); settings.beginGroup("acceptedCertificates");
return settings.contains(QUrl(url).host()); if (settings.contains(QUrl(url).host())) {
return true;
}
// Do we have a PEM file?
QByteArray pem;
if (loadPem(url, pem)) {
return true;
}
return false;
} }
bool NymeaConnection::connected() bool NymeaConnection::connected()
@ -103,8 +124,15 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
qDebug() << "Ignoring host mismatch on certificate."; qDebug() << "Ignoring host mismatch on certificate.";
ignoredErrors.append(error); ignoredErrors.append(error);
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) { } else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
qDebug() << "have a self signed certificate." << error.certificate() << error.certificate().issuerInfoAttributes(); qDebug() << "have a self signed certificate." << error.certificate();
// Check our cert DB
QByteArray pem;
// Keep this for compatibility with old versions for a bit...
// New code will look up the PEM instead and set it before the connection attempt
// However, we want to emit verifyConnectionCertificate in any case here.
QSettings settings; QSettings settings;
settings.beginGroup("acceptedCertificates"); settings.beginGroup("acceptedCertificates");
QByteArray storedFingerPrint = settings.value(m_currentUrl.host()).toByteArray(); QByteArray storedFingerPrint = settings.value(m_currentUrl.host()).toByteArray();
@ -119,8 +147,20 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
certificateFingerprint.append(digest.mid(i,1).toHex().toUpper()); certificateFingerprint.append(digest.mid(i,1).toHex().toUpper());
} }
// Check old style fingerprint storage
if (storedFingerPrint == certificateFingerprint) { if (storedFingerPrint == certificateFingerprint) {
qDebug() << "This fingerprint is known to us.";
ignoredErrors.append(error); ignoredErrors.append(error);
// Update the config to use the new system:
storePem(m_currentUrl, error.certificate().toPem());
// Check new style PEM storage
} else if (loadPem(m_currentUrl, pem) && pem == error.certificate().toPem()) {
qDebug() << "Found a SSL certificate for this host. Ignoring error.";
ignoredErrors.append(error);
// Ok... nothing found... Pop up the message
} else { } else {
QStringList info; QStringList info;
info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName); info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName);
@ -132,14 +172,18 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
// info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier); // info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier);
// info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress); // info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress);
emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint); emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint, error.certificate().toPem());
} }
} else { } else {
// Reject the connection on all other errors... // Reject the connection on all other errors...
qDebug() << "SSL Error:" << error.errorString() << error.certificate(); qDebug() << "SSL Error:" << error.errorString() << error.certificate();
} }
} }
m_currentTransport->ignoreSslErrors(ignoredErrors); if (ignoredErrors == errors) {
// Note, due to a workaround in the WebSocketTransport we must not call this
// unless we've handled all the errors or the websocket will ignore unhandled errors too...
m_currentTransport->ignoreSslErrors(ignoredErrors);
}
} }
void NymeaConnection::onError(QAbstractSocket::SocketError error) void NymeaConnection::onError(QAbstractSocket::SocketError error)
@ -170,6 +214,33 @@ void NymeaConnection::onDisconnected()
emit connectedChanged(false); emit connectedChanged(false);
} }
bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
if (!dir.exists()) {
dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
}
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
if (!certFile.open(QFile::WriteOnly)) {
return false;
}
certFile.write(pem);
certFile.close();
return true;
}
bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem)
{
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
if (!certFile.open(QFile::ReadOnly)) {
return false;
}
pem.clear();
pem.append(certFile.readAll());
return true;
}
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory) void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
{ {
foreach (const QString &scheme, transportFactory->supportedSchemes()) { foreach (const QString &scheme, transportFactory->supportedSchemes()) {

View File

@ -14,7 +14,7 @@ class NymeaConnection : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY(QString url READ url NOTIFY connectedChanged) Q_PROPERTY(QString url READ url NOTIFY currentUrlChanged)
Q_PROPERTY(QString hostAddress READ hostAddress NOTIFY connectedChanged) Q_PROPERTY(QString hostAddress READ hostAddress NOTIFY connectedChanged)
Q_PROPERTY(QString bluetoothAddress READ bluetoothAddress NOTIFY connectedChanged) Q_PROPERTY(QString bluetoothAddress READ bluetoothAddress NOTIFY connectedChanged)
@ -25,7 +25,7 @@ public:
Q_INVOKABLE bool connect(const QString &url); Q_INVOKABLE bool connect(const QString &url);
Q_INVOKABLE void disconnect(); Q_INVOKABLE void disconnect();
Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &fingerprint); Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &pem);
Q_INVOKABLE bool isTrusted(const QString &url); Q_INVOKABLE bool isTrusted(const QString &url);
bool connected(); bool connected();
@ -37,7 +37,8 @@ public:
void sendData(const QByteArray &data); void sendData(const QByteArray &data);
signals: signals:
void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint); void currentUrlChanged();
void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint, const QByteArray &pem);
void connectedChanged(bool connected); void connectedChanged(bool connected);
void connectionError(const QString &error); void connectionError(const QString &error);
void dataAvailable(const QByteArray &data); void dataAvailable(const QByteArray &data);
@ -49,6 +50,8 @@ private slots:
void onDisconnected(); void onDisconnected();
private: private:
bool storePem(const QUrl &host, const QByteArray &pem);
bool loadPem(const QUrl &host, QByteArray &pem);
private: private:
QHash<QString, NymeaTransportInterfaceFactory*> m_transports; QHash<QString, NymeaTransportInterfaceFactory*> m_transports;

View File

@ -28,7 +28,7 @@
WebsocketTransport::WebsocketTransport(QObject *parent) : WebsocketTransport::WebsocketTransport(QObject *parent) :
NymeaTransportInterface(parent) NymeaTransportInterface(parent)
{ {
m_socket = new QWebSocket(QCoreApplication::applicationName(), QWebSocketProtocol::Version13, this); m_socket = new QWebSocket(QCoreApplication::applicationName(), QWebSocketProtocol::VersionLatest, this);
QObject::connect(m_socket, &QWebSocket::connected, this, &WebsocketTransport::connected); QObject::connect(m_socket, &QWebSocket::connected, this, &WebsocketTransport::connected);
QObject::connect(m_socket, &QWebSocket::disconnected, this, &WebsocketTransport::disconnected); QObject::connect(m_socket, &QWebSocket::disconnected, this, &WebsocketTransport::disconnected);
@ -73,7 +73,14 @@ void WebsocketTransport::sendData(const QByteArray &data)
void WebsocketTransport::ignoreSslErrors(const QList<QSslError> &errors) void WebsocketTransport::ignoreSslErrors(const QList<QSslError> &errors)
{ {
m_socket->ignoreSslErrors(errors); // FIXME: We really should provide the exact errors here, like we do on other transports,
// however, for some reason I just fail to connect to any wss:// socket if I specify the
// errors. It would only continue if calling it without errors parameter...
// m_socket->ignoreSslErrors(errors);
Q_UNUSED(errors)
m_socket->ignoreSslErrors();
} }
void WebsocketTransport::onTextMessageReceived(const QString &data) void WebsocketTransport::onTextMessageReceived(const QString &data)

View File

@ -86,13 +86,15 @@ Page {
secondaryIconName: !model.online ? "../images/cloud-error.svg" : "" secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
onClicked: { onClicked: {
var page = pageStack.push(Qt.resolvedUrl("../connection/ConnectingPage.qml")) if (!Engine.connection.connected) {
page.cancel.connect(function() { var page = pageStack.push(Qt.resolvedUrl("../connection/ConnectingPage.qml"))
Engine.connection.disconnect() page.cancel.connect(function() {
pageStack.pop(root, StackView.Immediate); Engine.connection.disconnect()
pageStack.push(discoveryPage) pageStack.pop(root, StackView.Immediate);
}) pageStack.push(discoveryPage)
Engine.connection.connect("cloud://" + model.id) })
Engine.connection.connect("cloud://" + model.id)
}
} }
onDeleteClicked: { onDeleteClicked: {

View File

@ -44,7 +44,7 @@ Page {
target: Engine.connection target: Engine.connection
onVerifyConnectionCertificate: { onVerifyConnectionCertificate: {
print("verify cert!") print("verify cert!")
var popup = certDialogComponent.createObject(app, {url: url, issuerInfo: issuerInfo, fingerprint: fingerprint}); var popup = certDialogComponent.createObject(root, {url: url, issuerInfo: issuerInfo, fingerprint: fingerprint, pem: pem});
popup.open(); popup.open();
} }
onConnectionError: { onConnectionError: {
@ -331,6 +331,7 @@ Page {
property string url property string url
property var fingerprint property var fingerprint
property var issuerInfo property var issuerInfo
property var pem
readonly property bool hasOldFingerprint: Engine.connection.isTrusted(url) readonly property bool hasOldFingerprint: Engine.connection.isTrusted(url)
@ -419,9 +420,8 @@ Page {
} }
} }
onAccepted: { onAccepted: {
Engine.connection.acceptCertificate(certDialog.url, certDialog.fingerprint) Engine.connection.acceptCertificate(certDialog.url, certDialog.pem)
root.connectToHost(certDialog.url) root.connectToHost(certDialog.url)
} }
} }