From c6f4f446192e5f11df80186e391b636471c15984 Mon Sep 17 00:00:00 2001 From: Markus Goetz Date: Mon, 2 Jan 2017 08:34:02 +0100 Subject: [PATCH] Fix up SSL client certificates #5213 #69 (#5289) The re-enables the UI, uses Qt API for importing and stores the certificate/key in the system keychain. People who had set up client certs need to re-setup the account. This is ok since it was an undocumented feature anyway. --- csync/src/csync.h | 5 - csync/src/csync_private.h | 3 - src/3rdparty/certificates/p12topem.cpp | 110 ----------- src/3rdparty/certificates/p12topem.h | 62 ------ src/cmd/cmd.cpp | 2 +- src/gui/CMakeLists.txt | 1 - src/gui/accountmanager.cpp | 2 +- src/gui/addcertificatedialog.ui | 4 +- src/gui/creds/httpcredentialsgui.h | 2 +- .../wizard/owncloudconnectionmethoddialog.cpp | 5 +- src/gui/wizard/owncloudhttpcredspage.cpp | 2 +- src/gui/wizard/owncloudsetuppage.cpp | 60 +++--- src/gui/wizard/owncloudsetuppage.h | 1 - src/gui/wizard/owncloudwizard.cpp | 7 - src/gui/wizard/owncloudwizard.h | 11 +- src/libsync/CMakeLists.txt | 1 - src/libsync/account.cpp | 31 --- src/libsync/account.h | 3 +- src/libsync/connectionvalidator.cpp | 7 +- src/libsync/creds/httpcredentials.cpp | 176 +++++++++++++----- src/libsync/creds/httpcredentials.h | 24 ++- src/libsync/networkjobs.cpp | 2 +- 22 files changed, 198 insertions(+), 323 deletions(-) delete mode 100644 src/3rdparty/certificates/p12topem.cpp delete mode 100644 src/3rdparty/certificates/p12topem.h diff --git a/csync/src/csync.h b/csync/src/csync.h index 9bf21b7db..770301697 100644 --- a/csync/src/csync.h +++ b/csync/src/csync.h @@ -44,11 +44,6 @@ extern "C" { #endif -struct csync_client_certs_s { - char *certificatePath; - char *certificatePasswd; -}; - enum csync_status_codes_e { CSYNC_STATUS_OK = 0, diff --git a/csync/src/csync_private.h b/csync/src/csync_private.h index a28eb8210..f204107d2 100644 --- a/csync/src/csync_private.h +++ b/csync/src/csync_private.h @@ -103,9 +103,6 @@ struct csync_s { } callbacks; c_strlist_t *excludes; - - // needed for SSL client certificate support - struct csync_client_certs_s *clientCerts; struct { char *file; diff --git a/src/3rdparty/certificates/p12topem.cpp b/src/3rdparty/certificates/p12topem.cpp deleted file mode 100644 index bf6a83c4e..000000000 --- a/src/3rdparty/certificates/p12topem.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) by Pierre MOREAU - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -/** - * \file p12topem.cpp - * \brief Static library to convert p12 to pem - * \author Pierre MOREAU - * \version 1.0.0 - * \date 09 January 2014 - */ - -#include "p12topem.h" - -/** - * \fn string x509ToString (BIO) - * \brief Return string from BIO SSL - * \param BIO o PEM_write_BIO_... - * \return string PEM - */ -string x509ToString(BIO *o) { - BUF_MEM *bptr; - BIO_get_mem_ptr(o, &bptr); - int len = bptr->length; - void* data = calloc(len+10, sizeof(char)); - BIO_read(o, data, len); - string ret = std::string(static_cast(data)); - free(data); - - return ret; -} - -/** - * \fn resultP12ToPem p12ToPem (string, string) - * \brief Convert P12 to PEM - * \param string p12File Path to P12 file - * \param string p12Passwd Password to open P12 file - * \return result (bool ReturnCode, Int ErrorCode, String Comment, String PrivateKey, String Certificate) - */ -resultP12ToPem p12ToPem(string p12File, string p12Passwd) { - FILE *fp; - PKCS12 *p12 = NULL; - EVP_PKEY *pkey = NULL; - X509 *cert = NULL; - STACK_OF(X509) *ca = NULL; - - BIO *o = BIO_new(BIO_s_mem()); - - string privateKey = ""; - string certificate = ""; - - resultP12ToPem ret; - ret.ReturnCode = false; - ret.ErrorCode = 0; - ret.Comment = ""; - ret.PrivateKey = ""; - ret.Certificate = ""; - - SSLeay_add_all_algorithms(); - ERR_load_crypto_strings(); - if(!(fp = fopen(p12File.c_str(), "rb"))) { - ret.ErrorCode = 1; - ret.Comment = strerror(errno); - return ret; - } - - p12 = d2i_PKCS12_fp(fp, &p12); - fclose (fp); - - if (!p12) { - ret.ErrorCode = 2; - ret.Comment = "Unable to open PKCS#12 file"; - return ret; - } - if (!PKCS12_parse(p12, p12Passwd.c_str(), &pkey, &cert, &ca)) { - ret.ErrorCode = 3; - ret.Comment = "Unable to parse PKCS#12 file (wrong password ?)"; - return ret; - } - PKCS12_free(p12); - - if (!(pkey && cert)) { - ret.ErrorCode = 4; - ret.Comment = "Certificate and/or key file doesn't exists"; - } else { - PEM_write_bio_PrivateKey(o, pkey, 0, 0, 0, NULL, 0); - privateKey = x509ToString(o); - - PEM_write_bio_X509(o, cert); - certificate = x509ToString(o); - - BIO_free(o); - - ret.ReturnCode = true; - ret.ErrorCode = 0; - ret.Comment = "All is fine"; - ret.PrivateKey = privateKey; - ret.Certificate = certificate; - } - return ret; -} diff --git a/src/3rdparty/certificates/p12topem.h b/src/3rdparty/certificates/p12topem.h deleted file mode 100644 index 0798a907f..000000000 --- a/src/3rdparty/certificates/p12topem.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) by Pierre MOREAU - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef P12TOPEM_H -#define P12TOPEM_H - -/** - * \file p12topem.h - * \brief Static library to convert p12 to pem - * \author Pierre MOREAU - * \version 1.0.0 - * \date 09 January 2014 - */ - -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -/** - * \struct resultP12ToPem p12topem.h - */ -struct resultP12ToPem { - bool ReturnCode; - int ErrorCode; - string Comment; - string PrivateKey; - string Certificate; -}; - -/** - * \brief Return string from BIO SSL - * \param BIO o PEM_write_BIO_... - * \return string PEM - */ -string x509ToString(BIO *o); - -/** - * \brief Convert P12 to PEM - * \param string p12File Path to P12 file - * \param string p12Passwd Password to open P12 file - * \return result (bool ReturnCode, Int ErrorCode, String Comment, String PrivateKey, String Certificate) - */ -resultP12ToPem p12ToPem(string p12File, string p12Passwd); - -#endif /* P12TOPEM_H */ - diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index ca2d3c91d..e8911b1de 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -121,7 +121,7 @@ QString queryPassword(const QString &user) class HttpCredentialsText : public HttpCredentials { public: HttpCredentialsText(const QString& user, const QString& password) - : HttpCredentials(user, password, "", ""), // FIXME: not working with client certs yet (qknight) + : HttpCredentials(user, password), // FIXME: not working with client certs yet (qknight) _sslTrusted(false) {} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d38d14b52..8f2967466 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -151,7 +151,6 @@ set(3rdparty_SRC ../3rdparty/qtsingleapplication/qtlocalpeer.cpp ../3rdparty/qtsingleapplication/qtsingleapplication.cpp ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp - ../3rdparty/certificates/p12topem.cpp ) if (APPLE) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 0c1275c60..86f5ca256 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -237,7 +237,7 @@ AccountPtr AccountManager::loadAccountHelper(QSettings& settings) acc->setCredentials(CredentialsFactory::create(authType)); - // now the cert, it is in the general group + // now the server cert, it is in the general group settings.beginGroup(QLatin1String("General")); acc->setApprovedCerts(QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray())); settings.endGroup(); diff --git a/src/gui/addcertificatedialog.ui b/src/gui/addcertificatedialog.ui index 8a293eff3..a10ee27ab 100644 --- a/src/gui/addcertificatedialog.ui +++ b/src/gui/addcertificatedialog.ui @@ -10,7 +10,7 @@ 0 0 462 - 186 + 188 @@ -32,7 +32,7 @@ - Certificate : + Certificate & Key (pkcs12) : diff --git a/src/gui/creds/httpcredentialsgui.h b/src/gui/creds/httpcredentialsgui.h index f4e8c9a6d..4e80ae77a 100644 --- a/src/gui/creds/httpcredentialsgui.h +++ b/src/gui/creds/httpcredentialsgui.h @@ -27,7 +27,7 @@ class HttpCredentialsGui : public HttpCredentials { Q_OBJECT public: explicit HttpCredentialsGui() : HttpCredentials() {} - HttpCredentialsGui(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd) : HttpCredentials(user, password, certificatePath, certificatePasswd) {} + HttpCredentialsGui(const QString& user, const QString& password, const QSslCertificate& certificate, const QSslKey& key) : HttpCredentials(user, password, certificate, key) {} void askFromUser() Q_DECL_OVERRIDE; Q_INVOKABLE void askFromUserAsync(); diff --git a/src/gui/wizard/owncloudconnectionmethoddialog.cpp b/src/gui/wizard/owncloudconnectionmethoddialog.cpp index 367069ff6..1acd2203a 100644 --- a/src/gui/wizard/owncloudconnectionmethoddialog.cpp +++ b/src/gui/wizard/owncloudconnectionmethoddialog.cpp @@ -29,8 +29,11 @@ OwncloudConnectionMethodDialog::OwncloudConnectionMethodDialog(QWidget *parent) connect(ui->btnClientSideTLS, SIGNAL(clicked(bool)), this, SLOT(returnClientSideTLS())); connect(ui->btnBack, SIGNAL(clicked(bool)), this, SLOT(returnBack())); - // DM: TLS Client Cert GUI support disabled for now + +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) + // We support only from Qt 5.4.x because of https://doc.qt.io/qt-5/qsslcertificate.html#importPkcs12 ui->btnClientSideTLS->hide(); +#endif } void OwncloudConnectionMethodDialog::setUrl(const QUrl &url) diff --git a/src/gui/wizard/owncloudhttpcredspage.cpp b/src/gui/wizard/owncloudhttpcredspage.cpp index 4c33ed9d3..4c8bbd5a1 100644 --- a/src/gui/wizard/owncloudhttpcredspage.cpp +++ b/src/gui/wizard/owncloudhttpcredspage.cpp @@ -192,7 +192,7 @@ void OwncloudHttpCredsPage::setErrorString(const QString& err) AbstractCredentials* OwncloudHttpCredsPage::getCredentials() const { - return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text(), _ocWizard->ownCloudCertificatePath, _ocWizard->ownCloudCertificatePasswd); + return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text(), _ocWizard->_clientSslCertificate, _ocWizard->_clientSslKey); } diff --git a/src/gui/wizard/owncloudsetuppage.cpp b/src/gui/wizard/owncloudsetuppage.cpp index 7700e7cf5..e884abf84 100644 --- a/src/gui/wizard/owncloudsetuppage.cpp +++ b/src/gui/wizard/owncloudsetuppage.cpp @@ -21,13 +21,13 @@ #include #include #include +#include #include "QProgressIndicator.h" #include "wizard/owncloudwizardcommon.h" #include "wizard/owncloudsetuppage.h" #include "wizard/owncloudconnectionmethoddialog.h" -#include "../3rdparty/certificates/p12topem.h" #include "theme.h" #include "account.h" @@ -71,7 +71,6 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent) connect(_ui.leUrl, SIGNAL(editingFinished()), SLOT(slotUrlEditFinished())); addCertDial = new AddCertificateDialog(this); - connect(_ocWizard,SIGNAL(needCertificate()),this,SLOT(slotAskSSLClientCertificate())); } void OwncloudSetupPage::setServerUrl( const QString& newUrl ) @@ -269,7 +268,10 @@ void OwncloudSetupPage::setErrorString( const QString& err, bool retryHTTPonly ) } break; case OwncloudConnectionMethodDialog::Client_Side_TLS: - slotAskSSLClientCertificate(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) + addCertDial->show(); + connect(addCertDial, SIGNAL(accepted()),this,SLOT(slotCertificateAccepted())); +#endif break; case OwncloudConnectionMethodDialog::Closed: case OwncloudConnectionMethodDialog::Back: @@ -302,12 +304,6 @@ void OwncloudSetupPage::stopSpinner() _progressIndi->stopAnimation(); } -void OwncloudSetupPage::slotAskSSLClientCertificate() -{ - addCertDial->show(); - connect(addCertDial, SIGNAL(accepted()),this,SLOT(slotCertificateAccepted())); -} - QString subjectInfoHelper(const QSslCertificate& cert, const QByteArray &qa) { #if QT_VERSION < QT_VERSION_CHECK(5,0,0) @@ -320,36 +316,40 @@ QString subjectInfoHelper(const QSslCertificate& cert, const QByteArray &qa) //called during the validation of the client certificate. void OwncloudSetupPage::slotCertificateAccepted() { - QSslCertificate sslCertificate; - - resultP12ToPem certif = p12ToPem(addCertDial->getCertificatePath().toStdString() , addCertDial->getCertificatePasswd().toStdString()); - if(certif.ReturnCode){ - QString s = QString::fromStdString(certif.Certificate); - QByteArray ba = s.toLocal8Bit(); - - QList sslCertificateList = QSslCertificate::fromData(ba, QSsl::Pem); - sslCertificate = sslCertificateList.takeAt(0); - - _ocWizard->ownCloudCertificate = ba; - _ocWizard->ownCloudPrivateKey = certif.PrivateKey.c_str(); - _ocWizard->ownCloudCertificatePath = addCertDial->getCertificatePath(); - _ocWizard->ownCloudCertificatePasswd = addCertDial->getCertificatePasswd(); - +#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) + QList clientCaCertificates; + QFile certFile(addCertDial->getCertificatePath()); + certFile.open(QFile::ReadOnly); + if(QSslCertificate::importPkcs12(&certFile, + &_ocWizard->_clientSslKey, &_ocWizard->_clientSslCertificate, + &clientCaCertificates, + addCertDial->getCertificatePasswd().toLocal8Bit())){ AccountPtr acc = _ocWizard->account(); - acc->setCertificate(_ocWizard->ownCloudCertificate, _ocWizard->ownCloudPrivateKey); - addCertDial->reinit(); + + // to re-create the session ticket because we added a key/cert + acc->setSslConfiguration(QSslConfiguration()); + QSslConfiguration sslConfiguration = acc->getOrCreateSslConfig(); + + // We're stuffing the certificate into the configuration form here. Later the + // cert will come via the HttpCredentials + sslConfiguration.setLocalCertificate(_ocWizard->_clientSslCertificate); + sslConfiguration.setPrivateKey(_ocWizard->_clientSslKey); + acc->setSslConfiguration(sslConfiguration); + + // Make sure TCP connections get re-established + acc->networkAccessManager()->clearAccessCache(); + + addCertDial->reinit(); // FIXME: Why not just have this only created on use? validatePage(); } else { - QString message; - message = certif.Comment.c_str(); - addCertDial->showErrorMessage(message); + addCertDial->showErrorMessage("Could not load certificate"); addCertDial->show(); } +#endif } OwncloudSetupPage::~OwncloudSetupPage() { - delete addCertDial; } } // namespace OCC diff --git a/src/gui/wizard/owncloudsetuppage.h b/src/gui/wizard/owncloudsetuppage.h index 57a6d444d..e0b1ba852 100644 --- a/src/gui/wizard/owncloudsetuppage.h +++ b/src/gui/wizard/owncloudsetuppage.h @@ -59,7 +59,6 @@ public slots: void setErrorString( const QString&, bool retryHTTPonly ); void startSpinner(); void stopSpinner(); - void slotAskSSLClientCertificate(); void slotCertificateAccepted(); protected slots: diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index 28faaa933..edd0479a0 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -224,11 +224,4 @@ AbstractCredentials* OwncloudWizard::getCredentials() const return 0; } -// outputs the signal needed to authenticate a certificate -void OwncloudWizard::raiseCertificatePopup() -{ - emit needCertificate(); -} - - } // end namespace diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index 1f2e79739..5c805cae4 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -17,6 +17,8 @@ #define MIRALL_OWNCLOUD_WIZARD_H #include +#include +#include #include "wizard/owncloudwizardcommon.h" #include "accountfwd.h" @@ -63,11 +65,10 @@ public: void displayError( const QString&, bool retryHTTPonly); AbstractCredentials* getCredentials() const; - void raiseCertificatePopup(); - QByteArray ownCloudCertificate; - QString ownCloudPrivateKey; - QString ownCloudCertificatePath; - QString ownCloudCertificatePasswd; + // FIXME: Can those be local variables? + // Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage + QSslKey _clientSslKey; + QSslCertificate _clientSslCertificate; public slots: void setAuthType(WizardCommon::AuthType type); diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 92b69c152..cbece4b41 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -70,7 +70,6 @@ set(libsync_SRCS creds/abstractcredentials.cpp creds/credentialscommon.cpp ../3rdparty/qjson/json.cpp - ../3rdparty/certificates/p12topem.cpp ) if(TOKEN_AUTH_ONLY) diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 9b391ea78..6ddc86833 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -18,7 +18,6 @@ #include "configfile.h" #include "accessmanager.h" #include "creds/abstractcredentials.h" -#include "../3rdparty/certificates/p12topem.h" #include "capabilities.h" #include "theme.h" @@ -242,12 +241,6 @@ QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNet return _am->sendCustomRequest(req, verb, data); } -void Account::setCertificate(const QByteArray certficate, const QString privateKey) -{ - _pemCertificate=certficate; - _pemPrivateKey=privateKey; -} - void Account::setSslConfiguration(const QSslConfiguration &config) { _sslConfiguration = config; @@ -264,31 +257,7 @@ QSslConfiguration Account::getOrCreateSslConfig() // if setting the client certificate fails, you will probably get an error similar to this: // "An internal error number 1060 happened. SSL handshake failed, client certificate was requested: SSL error: sslv3 alert handshake failure" QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); - QSslCertificate sslClientCertificate; - ConfigFile cfgFile; - if(!cfgFile.certificatePath().isEmpty() && !cfgFile.certificatePasswd().isEmpty()) { - resultP12ToPem certif = p12ToPem(cfgFile.certificatePath().toStdString(), cfgFile.certificatePasswd().toStdString()); - QString s = QString::fromStdString(certif.Certificate); - QByteArray ba = s.toLocal8Bit(); - this->setCertificate(ba, QString::fromStdString(certif.PrivateKey)); - } - if((!_pemCertificate.isEmpty())&&(!_pemPrivateKey.isEmpty())) { - // Read certificates - QList sslCertificateList = QSslCertificate::fromData(_pemCertificate, QSsl::Pem); - if(sslCertificateList.length() != 0) { - sslClientCertificate = sslCertificateList.takeAt(0); - } - // Read key from file - QSslKey privateKey(_pemPrivateKey.toLocal8Bit(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey , ""); - - // SSL configuration - sslConfig.setCaCertificates(QSslSocket::systemCaCertificates()); - sslConfig.setLocalCertificate(sslClientCertificate); - sslConfig.setPrivateKey(privateKey); - qDebug() << "Added SSL client certificate to the query"; - } - #if QT_VERSION > QT_VERSION_CHECK(5, 2, 0) // Try hard to re-use session for different requests sslConfig.setSslOption(QSsl::SslOptionDisableSessionTickets, false); diff --git a/src/libsync/account.h b/src/libsync/account.h index b9820726a..2c21fa548 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -224,8 +224,7 @@ private: QList _rejectedCertificates; static QString _configFileName; - QByteArray _pemCertificate; - QString _pemPrivateKey; + QString _davPath; // defaults to value from theme, might be overwritten in brandings friend class AccountManager; }; diff --git a/src/libsync/connectionvalidator.cpp b/src/libsync/connectionvalidator.cpp index b2a1d5879..ec63685aa 100644 --- a/src/libsync/connectionvalidator.cpp +++ b/src/libsync/connectionvalidator.cpp @@ -144,7 +144,12 @@ void ConnectionValidator::slotStatusFound(const QUrl&url, const QVariantMap &inf // status.php could not be loaded (network or server issue!). void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply) { - qDebug() << Q_FUNC_INFO << reply->error() << reply->errorString(); + qDebug() << Q_FUNC_INFO << reply->error() << reply->errorString() << reply->peek(1024); + if (reply && !_account->credentials()->ready()) { + // This could be needed for SSL client certificates + // We need to load them from keychain and try + reportResult( CredentialsMissingOrWrong ); + } else if( reply && ! _account->credentials()->stillValid(reply)) { _errors.append(tr("Authentication error: Either username or password are wrong.")); } else { diff --git a/src/libsync/creds/httpcredentials.cpp b/src/libsync/creds/httpcredentials.cpp index fc372e1f2..980cb4e54 100644 --- a/src/libsync/creds/httpcredentials.cpp +++ b/src/libsync/creds/httpcredentials.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -36,8 +37,8 @@ namespace OCC namespace { const char userC[] = "user"; -const char certifPathC[] = "certificatePath"; -const char certifPasswdC[] = "certificatePasswd"; +const char clientCertificatePEMC[] = "_clientCertificatePEM"; +const char clientKeyPEMC[] = "_clientKeyPEM"; const char authenticationFailedC[] = "owncloud-authentication-failed"; } // ns @@ -50,24 +51,47 @@ protected: QByteArray credHash = QByteArray(_cred->user().toUtf8()+":"+_cred->password().toUtf8()).toBase64(); QNetworkRequest req(request); req.setRawHeader(QByteArray("Authorization"), QByteArray("Basic ") + credHash); - //qDebug() << "Request for " << req.url() << "with authorization" << QByteArray::fromBase64(credHash); + //qDebug() << "Request for " << req.url() << "with authorization" + // << QByteArray::fromBase64(credHash) + // << _cred->_clientSslKey << _cred->_clientSslCertificate + // << _cred->_clientSslKey.isNull() << _cred->_clientSslCertificate.isNull(); + + if (!_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) { + // SSL configuration + QSslConfiguration sslConfiguration = req.sslConfiguration(); + sslConfiguration.setLocalCertificate(_cred->_clientSslCertificate); + sslConfiguration.setPrivateKey(_cred->_clientSslKey); + req.setSslConfiguration(sslConfiguration); + } + + return AccessManager::createRequest(op, req, outgoingData); } private: const HttpCredentials *_cred; }; + +static void addSettingsToJob(Account *account, QKeychain::Job *job) +{ + Q_UNUSED(account); + auto settings = Utility::settingsWithGroup(Theme::instance()->appName()); + settings->setParent(job); // make the job parent to make setting deleted properly + job->setSettings(settings.release()); +} + HttpCredentials::HttpCredentials() : _ready(false) { } -HttpCredentials::HttpCredentials(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd) +// From wizard +HttpCredentials::HttpCredentials(const QString& user, const QString& password, const QSslCertificate& certificate, const QSslKey& key) : _user(user), _password(password), _ready(true), - _certificatePath(certificatePath), - _certificatePasswd(certificatePasswd) + _clientSslKey(key), + _clientSslCertificate(certificate) { } @@ -86,16 +110,6 @@ QString HttpCredentials::password() const return _password; } -QString HttpCredentials::certificatePath() const -{ - return _certificatePath; -} - -QString HttpCredentials::certificatePasswd() const -{ - return _certificatePasswd; -} - void HttpCredentials::setAccount(Account* account) { AbstractCredentials::setAccount(account); @@ -129,35 +143,83 @@ void HttpCredentials::fetchFromKeychain() { // User must be fetched from config file fetchUser(); - _certificatePath = _account->credentialSetting(QLatin1String(certifPathC)).toString(); - _certificatePasswd = _account->credentialSetting(QLatin1String(certifPasswdC)).toString(); - auto settings = Utility::settingsWithGroup(Theme::instance()->appName()); const QString kck = keychainKey(_account->url().toString(), _user ); - QString key = QString::fromLatin1( "%1/data" ).arg( kck ); - if( settings && settings->contains(key) ) { - // Clean the password from the config file if it is in there. - // we do not want a security problem. - settings->remove(key); - key = QString::fromLatin1( "%1/type" ).arg( kck ); - settings->remove(key); - settings->sync(); - } - if (_ready) { Q_EMIT fetched(); } else { + // Read client cert from keychain + const QString kck = keychainKey(_account->url().toString(), _user + clientCertificatePEMC); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); - settings->setParent(job); // make the job parent to make setting deleted properly - job->setSettings(settings.release()); - + addSettingsToJob(_account, job); job->setInsecureFallback(false); job->setKey(kck); - connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadJobDone(QKeychain::Job*))); + qDebug() << "-------- ----->" << _clientSslCertificate << _clientSslKey; + + connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadClientCertPEMJobDone(QKeychain::Job*))); job->start(); } } + +void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job* incoming) +{ + // Store PEM in memory + ReadPasswordJob *readJob = static_cast(incoming); + if (readJob->error() == NoError && readJob->binaryData().length() > 0) { + QList sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem); + if(sslCertificateList.length() >= 1) { + _clientSslCertificate = sslCertificateList.at(0); + } + } + + // Load key too + const QString kck = keychainKey(_account->url().toString(), _user + clientKeyPEMC); + ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); + addSettingsToJob(_account, job); + job->setInsecureFallback(false); + job->setKey(kck); + + connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadClientKeyPEMJobDone(QKeychain::Job*))); + job->start(); +} + +void HttpCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job* incoming) +{ + // Store key in memory + ReadPasswordJob *readJob = static_cast(incoming); + + if (readJob->error() == NoError && readJob->binaryData().length() > 0) { + QByteArray clientKeyPEM = readJob->binaryData(); + // FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it + // load whatever we have. So we try until it works. + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa); + if (_clientSslKey.isNull()) { + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa); + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + // ec keys are Qt 5.5 + if (_clientSslKey.isNull()) { + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec); + } +#endif + if (_clientSslKey.isNull()) { + qDebug() << "Warning: Could not load SSL key into Qt!"; + } + } + + // Now fetch the actual server password + const QString kck = keychainKey(_account->url().toString(), _user ); + ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); + addSettingsToJob(_account, job); + job->setInsecureFallback(false); + job->setKey(kck); + + connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadJobDone(QKeychain::Job*))); + job->start(); +} + + bool HttpCredentials::stillValid(QNetworkReply *reply) { return ((reply->error() != QNetworkReply::AuthenticationRequiredError) @@ -166,10 +228,10 @@ bool HttpCredentials::stillValid(QNetworkReply *reply) || !reply->property(authenticationFailedC).toBool())); } -void HttpCredentials::slotReadJobDone(QKeychain::Job *job) +void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) { - ReadPasswordJob *readJob = static_cast(job); - _password = readJob->textData(); + QKeychain::ReadPasswordJob *job = static_cast(incomingJob); + _password = job->textData(); if( _user.isEmpty()) { qDebug() << "Strange: User is empty!"; @@ -178,7 +240,6 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *job) QKeychain::Error error = job->error(); if( !_password.isEmpty() && error == NoError ) { - // All cool, the keychain did not come back with error. // Still, the password can be empty which indicates a problem and // the password dialog has to be opened. @@ -214,9 +275,7 @@ void HttpCredentials::invalidateToken() } DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); - auto settings = Utility::settingsWithGroup(Theme::instance()->appName()); - settings->setParent(job); // make the job parent to make setting deleted properly - job->setSettings(settings.release()); + addSettingsToJob(_account, job); job->setInsecureFallback(true); job->setKey(kck); job->start(); @@ -261,14 +320,37 @@ void HttpCredentials::persist() // We never connected or fetched the user, there is nothing to save. return; } - _account->setCredentialSetting(QLatin1String(userC), _user); - _account->setCredentialSetting(QLatin1String(certifPathC), _certificatePath); - _account->setCredentialSetting(QLatin1String(certifPasswdC), _certificatePasswd); - WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); - auto settings = Utility::settingsWithGroup(Theme::instance()->appName()); - settings->setParent(job); // make the job parent to make setting deleted properly - job->setSettings(settings.release()); + _account->setCredentialSetting(QLatin1String(userC), _user); + + // write cert + WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); + addSettingsToJob(_account, job); + job->setInsecureFallback(false); + connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteClientCertPEMJobDone(QKeychain::Job*))); + job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC)); + job->setBinaryData(_clientSslCertificate.toPem()); + job->start(); +} + +void HttpCredentials::slotWriteClientCertPEMJobDone(Job *incomingJob) +{ + Q_UNUSED(incomingJob); + // write ssl key + WritePasswordJob* job = new WritePasswordJob(Theme::instance()->appName()); + addSettingsToJob(_account, job); + job->setInsecureFallback(false); + connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteClientKeyPEMJobDone(QKeychain::Job*))); + job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC)); + job->setBinaryData(_clientSslKey.toPem()); + job->start(); +} + +void HttpCredentials::slotWriteClientKeyPEMJobDone(Job *incomingJob) +{ + Q_UNUSED(incomingJob); + WritePasswordJob* job = new WritePasswordJob(Theme::instance()->appName()); + addSettingsToJob(_account, job); job->setInsecureFallback(false); connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteJobDone(QKeychain::Job*))); job->setKey(keychainKey(_account->url().toString(), _user)); diff --git a/src/libsync/creds/httpcredentials.h b/src/libsync/creds/httpcredentials.h index 7cc09e383..860329d9b 100644 --- a/src/libsync/creds/httpcredentials.h +++ b/src/libsync/creds/httpcredentials.h @@ -17,7 +17,8 @@ #define MIRALL_CREDS_HTTP_CREDENTIALS_H #include - +#include +#include #include "creds/abstractcredentials.h" class QNetworkReply; @@ -25,6 +26,8 @@ class QAuthenticator; namespace QKeychain { class Job; +class WritePasswordJob; +class ReadPasswordJob; } namespace OCC @@ -33,10 +36,10 @@ namespace OCC class OWNCLOUDSYNC_EXPORT HttpCredentials : public AbstractCredentials { Q_OBJECT - + friend class HttpCredentialsAccessManager; public: explicit HttpCredentials(); - HttpCredentials(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd); + HttpCredentials(const QString& user, const QString& password, const QSslCertificate& certificate = QSslCertificate(), const QSslKey& key = QSslKey()); QString authType() const Q_DECL_OVERRIDE; QNetworkAccessManager* getQNAM() const Q_DECL_OVERRIDE; @@ -50,15 +53,19 @@ public: void forgetSensitiveData() Q_DECL_OVERRIDE; QString fetchUser(); virtual bool sslIsTrusted() { return false; } - QString certificatePath() const; - QString certificatePasswd() const; // To fetch the user name as early as possible void setAccount(Account* account) Q_DECL_OVERRIDE; private Q_SLOTS: void slotAuthentication(QNetworkReply*, QAuthenticator*); + + void slotReadClientCertPEMJobDone(QKeychain::Job*); + void slotReadClientKeyPEMJobDone(QKeychain::Job*); void slotReadJobDone(QKeychain::Job*); + + void slotWriteClientCertPEMJobDone(QKeychain::Job*); + void slotWriteClientKeyPEMJobDone(QKeychain::Job*); void slotWriteJobDone(QKeychain::Job*); void clearQNAMCache(); @@ -66,12 +73,11 @@ protected: QString _user; QString _password; QString _previousPassword; + QString _fetchErrorString; bool _ready; - -private: - QString _certificatePath; - QString _certificatePasswd; + QSslKey _clientSslKey; + QSslCertificate _clientSslCertificate; }; } // namespace OCC diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 974264045..4cc5801ae 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -472,7 +472,7 @@ bool CheckServerJob::finished() QVariantMap status = QtJson::parse(QString::fromUtf8(body), success).toMap(); // empty or invalid response if (!success || status.isEmpty()) { - qDebug() << "status.php from server is not valid JSON!"; + qDebug() << "status.php from server is not valid JSON!" << body << reply()->request().url(); } qDebug() << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply();