fix aws login, workaround wss certificate issue

pull/43/head
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_password = settings.value("password").toString();
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_refreshToken = settings.value("refreshToken").toByteArray();
m_confirmationPending = settings.value("confirmationPending", false).toBool();
m_identityId = settings.value("identityId").toByteArray();
@ -302,12 +301,6 @@ void AWSClient::signup(const QString &username, const QString &password)
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;
emit confirmationPendingChanged();
});
@ -364,6 +357,8 @@ void AWSClient::confirmRegistration(const QString &code)
return;
}
m_confirmationPending = false;
emit confirmationPendingChanged();
emit confirmationResult(LoginErrorNoError);
login(m_username, m_password);
fetchDevices();
@ -591,9 +586,6 @@ void AWSClient::getId()
return;
}
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);
getCredentialsForIdentity(m_identityId);
@ -767,6 +759,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
QSettings settings;
qDebug() << "settings has:" << settings.childGroups();
bool newLogin = !settings.childGroups().contains("cloud");
@ -777,6 +770,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
settings.setValue("password", m_password);
settings.setValue("accessToken", m_accessToken);
settings.setValue("accessTokenExpiry", m_accessTokenExpiry);
settings.setValue("identityId", m_identityId);
settings.setValue("idToken", m_idToken);
settings.setValue("refreshToken", m_refreshToken);
settings.setValue("accessKeyId", m_accessKeyId);
@ -787,7 +781,7 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
emit loginResult(LoginErrorNoError);
if (newLogin) {
// qDebug() << "new login!";
qDebug() << "new login!";
emit isLoggedInChanged();
}

View File

@ -6,6 +6,9 @@
#include <QUrlQuery>
#include <QSettings>
#include <QMetaEnum>
#include <QStandardPaths>
#include <QFile>
#include <QDir>
#include "nymeatransportinterface.h"
@ -21,21 +24,31 @@ bool NymeaConnection::connect(const QString &url)
}
m_currentUrl = QUrl(url);
emit currentUrlChanged();
if (!m_transports.contains(m_currentUrl.scheme())) {
qWarning() << "Cannot connect to urls of scheme" << m_currentUrl.scheme() << "Supported schemes are" << m_transports.keys();
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::error, this, &NymeaConnection::onError);
QObject::connect(m_currentTransport, &NymeaTransportInterface::connected, this, &NymeaConnection::onConnected);
QObject::connect(m_currentTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected);
// signal forwarding
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);
}
@ -48,19 +61,27 @@ void NymeaConnection::disconnect()
m_currentTransport->disconnect();
}
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &fingerprint)
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem)
{
QSettings settings;
settings.beginGroup("acceptedCertificates");
settings.setValue(QUrl(url).host(), fingerprint);
settings.endGroup();
storePem(url, pem);
}
bool NymeaConnection::isTrusted(const QString &url)
{
// Do we have a legacy fingerprint
QSettings settings;
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()
@ -103,8 +124,15 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
qDebug() << "Ignoring host mismatch on certificate.";
ignoredErrors.append(error);
} 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;
settings.beginGroup("acceptedCertificates");
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());
}
// Check old style fingerprint storage
if (storedFingerPrint == certificateFingerprint) {
qDebug() << "This fingerprint is known to us.";
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 {
QStringList info;
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("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress);
emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint);
emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint, error.certificate().toPem());
}
} else {
// Reject the connection on all other errors...
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)
@ -170,6 +214,33 @@ void NymeaConnection::onDisconnected()
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)
{
foreach (const QString &scheme, transportFactory->supportedSchemes()) {

View File

@ -14,7 +14,7 @@ class NymeaConnection : public QObject
{
Q_OBJECT
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 bluetoothAddress READ bluetoothAddress NOTIFY connectedChanged)
@ -25,7 +25,7 @@ public:
Q_INVOKABLE bool connect(const QString &url);
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);
bool connected();
@ -37,7 +37,8 @@ public:
void sendData(const QByteArray &data);
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 connectionError(const QString &error);
void dataAvailable(const QByteArray &data);
@ -49,6 +50,8 @@ private slots:
void onDisconnected();
private:
bool storePem(const QUrl &host, const QByteArray &pem);
bool loadPem(const QUrl &host, QByteArray &pem);
private:
QHash<QString, NymeaTransportInterfaceFactory*> m_transports;

View File

@ -28,7 +28,7 @@
WebsocketTransport::WebsocketTransport(QObject *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::disconnected, this, &WebsocketTransport::disconnected);
@ -73,7 +73,14 @@ void WebsocketTransport::sendData(const QByteArray &data)
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)

View File

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

View File

@ -44,7 +44,7 @@ Page {
target: Engine.connection
onVerifyConnectionCertificate: {
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();
}
onConnectionError: {
@ -331,6 +331,7 @@ Page {
property string url
property var fingerprint
property var issuerInfo
property var pem
readonly property bool hasOldFingerprint: Engine.connection.isTrusted(url)
@ -419,9 +420,8 @@ Page {
}
}
onAccepted: {
Engine.connection.acceptCertificate(certDialog.url, certDialog.fingerprint)
Engine.connection.acceptCertificate(certDialog.url, certDialog.pem)
root.connectToHost(certDialog.url)
}
}