Fix up SSL client certificates #5213 #69 (#5289)
authorMarkus Goetz <markus@woboq.com>
Mon, 2 Jan 2017 07:34:02 +0000 (08:34 +0100)
committerGitHub <noreply@github.com>
Mon, 2 Jan 2017 07:34:02 +0000 (08:34 +0100)
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.

22 files changed:
csync/src/csync.h
csync/src/csync_private.h
src/3rdparty/certificates/p12topem.cpp [deleted file]
src/3rdparty/certificates/p12topem.h [deleted file]
src/cmd/cmd.cpp
src/gui/CMakeLists.txt
src/gui/accountmanager.cpp
src/gui/addcertificatedialog.ui
src/gui/creds/httpcredentialsgui.h
src/gui/wizard/owncloudconnectionmethoddialog.cpp
src/gui/wizard/owncloudhttpcredspage.cpp
src/gui/wizard/owncloudsetuppage.cpp
src/gui/wizard/owncloudsetuppage.h
src/gui/wizard/owncloudwizard.cpp
src/gui/wizard/owncloudwizard.h
src/libsync/CMakeLists.txt
src/libsync/account.cpp
src/libsync/account.h
src/libsync/connectionvalidator.cpp
src/libsync/creds/httpcredentials.cpp
src/libsync/creds/httpcredentials.h
src/libsync/networkjobs.cpp

index 9bf21b7db0a2c874a2da9d8cbb0d9006ae0f028b..7703016972780c7633c4e1afbf9897279b36ff41 100644 (file)
 extern "C" {
 #endif
 
-struct csync_client_certs_s {
-  char *certificatePath;
-  char *certificatePasswd;
-};  
-  
 enum csync_status_codes_e {
   CSYNC_STATUS_OK         = 0,
 
index a28eb82100c361055d6201ab920d28c937221362..f204107d2c3f7f692845d973ffbbd140c89105b9 100644 (file)
@@ -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 (file)
index bf6a83c..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) by Pierre MOREAU <p.moreau@agim.idshost.fr>
- *
- * 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 <p.moreau@agim.idshost.fr>
- * \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<char*>(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 (file)
index 0798a90..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) by Pierre MOREAU <p.moreau@agim.idshost.fr>
- *
- * 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 <p.moreau@agim.idshost.fr>
- * \version 1.0.0
- * \date 09 January 2014
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <string>
-#include <openssl/pem.h>
-#include <openssl/err.h>
-#include <openssl/pkcs12.h>
-
-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 */
-
index ca2d3c91db26875b81af99df0f487d91a88f8df4..e8911b1de79aecd630ced46b24689c239fc8905f 100644 (file)
@@ -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)
     {}
 
index d38d14b52807b59582294a90d55fe48b120fa3fc..8f29674663d1ffa5c8beea007c8fb2ee0e16bd90 100644 (file)
@@ -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)
index 0c1275c60465aa9c1f7046e561f36b2c555adabc..86f5ca256ec4e508a9dfe94e5688d4b350b162d4 100644 (file)
@@ -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();
index 8a293eff34bf97574988f06064ae358496943abc..a10ee27ab3d575c57668651bce4a7a24a8511ddf 100644 (file)
@@ -10,7 +10,7 @@
     <x>0</x>
     <y>0</y>
     <width>462</width>
-    <height>186</height>
+    <height>188</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -32,7 +32,7 @@
      <item row="0" column="0">
       <widget class="QLabel" name="labelCertificateFile">
        <property name="text">
-        <string>Certificate :</string>
+        <string>Certificate &amp; Key (pkcs12) :</string>
        </property>
       </widget>
      </item>
index f4e8c9a6d1e0519207054db1c4adef83c81ce550..4e80ae77a7bf51af6b5b6694fa8e5fed708f8d48 100644 (file)
@@ -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();
 
index 367069ff605faeb34820ed976b43a7a7ea191f88..1acd2203ae1082c26b6af0fadb798ba3f5cdd788 100644 (file)
@@ -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)
index 4c33ed9d38adcd60b4b2401c4898528e405dd82f..4c8bbd5a10ec36f76652f0c2637be52a53499b84 100644 (file)
@@ -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);
 }
 
 
index 7700e7cf50be4a249c457ce8b87e1596d4bc1139..e884abf849a7dae5f0918e76b6aaafc03528e4ba 100644 (file)
 #include <QMessageBox>
 #include <QSsl>
 #include <QSslCertificate>
+#include <QNetworkAccessManager>
 
 #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;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
+    QList<QSslCertificate> 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();
 
-    resultP12ToPem certif = p12ToPem(addCertDial->getCertificatePath().toStdString() , addCertDial->getCertificatePasswd().toStdString());
-    if(certif.ReturnCode){
-        QString s = QString::fromStdString(certif.Certificate);
-        QByteArray ba = s.toLocal8Bit();
+        // to re-create the session ticket because we added a key/cert
+        acc->setSslConfiguration(QSslConfiguration());
+        QSslConfiguration sslConfiguration = acc->getOrCreateSslConfig();
 
-        QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(ba, QSsl::Pem);
-        sslCertificate = sslCertificateList.takeAt(0);
+        // 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);
 
-        _ocWizard->ownCloudCertificate = ba;
-        _ocWizard->ownCloudPrivateKey = certif.PrivateKey.c_str();
-        _ocWizard->ownCloudCertificatePath = addCertDial->getCertificatePath();
-        _ocWizard->ownCloudCertificatePasswd = addCertDial->getCertificatePasswd();
+        // Make sure TCP connections get re-established
+        acc->networkAccessManager()->clearAccessCache();
 
-        AccountPtr acc = _ocWizard->account();
-        acc->setCertificate(_ocWizard->ownCloudCertificate, _ocWizard->ownCloudPrivateKey);
-        addCertDial->reinit();
+        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
index 57a6d444d029e1ad745d84ae2e5f511a440b6d62..e0b1ba8525470b65091d6cb5317fe7afde6bef63 100644 (file)
@@ -59,7 +59,6 @@ public slots:
   void setErrorString( const QString&, bool retryHTTPonly );
   void startSpinner();
   void stopSpinner();
-  void slotAskSSLClientCertificate();
   void slotCertificateAccepted();
 
 protected slots:
index 28faaa9332419298ae587a64313e030b68fd6667..edd0479a006343a27b1d51a21ded5183d5091fdb 100644 (file)
@@ -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
index 1f2e79739733954909751c2079e2ea0b2684d747..5c805cae4242dc2453a42daf8e68ad271aef8c78 100644 (file)
@@ -17,6 +17,8 @@
 #define MIRALL_OWNCLOUD_WIZARD_H
 
 #include <QWizard>
+#include <QSslKey>
+#include <QSslCertificate>
 
 #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);
index 92b69c152f3160bf57bb1e62bade4962ff3ea5dd..cbece4b41c709c89167162ad738a2df0dc9f9a7a 100644 (file)
@@ -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)
index 9b391ea78d747a8292b40c4da314b458c389841f..6ddc86833a3228eaec82836a7107c7c85e27a872 100644 (file)
@@ -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<QSslCertificate> 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);
index b9820726ae51e190253d9313a45b3f579075aef8..2c21fa548f725247b87c0a4fca3558887c05b901 100644 (file)
@@ -224,8 +224,7 @@ private:
     QList<QSslCertificate> _rejectedCertificates;
 
     static QString _configFileName;
-    QByteArray _pemCertificate; 
-    QString _pemPrivateKey;  
+
     QString _davPath; // defaults to value from theme, might be overwritten in brandings
     friend class AccountManager;
 };
index b2a1d5879bc7c432e4d616b6651218e54908b16d..ec63685aa4cf38560e8e7a4537d04ba22f11328e 100644 (file)
@@ -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 {
index fc372e1f29862187a49fa5c7b091e606f2ca5489..980cb4e5445f1ac686b6e5dbfd3e3bcdd5d7da36 100644 (file)
@@ -17,6 +17,7 @@
 #include <QDebug>
 #include <QNetworkReply>
 #include <QSettings>
+#include <QSslKey>
 
 #include <keychain.h>
 
@@ -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<ReadPasswordJob*>(incoming);
+    if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
+        QList<QSslCertificate> 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<ReadPasswordJob*>(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<ReadPasswordJob*>(job);
-    _password = readJob->textData();
+    QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob*>(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);
+
+    // write cert
     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());
+    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));
index 7cc09e3837f5627cdf34d8bd3a4ea4bfd1919528..860329d9b4c1f05f68e970f03875b546461fcba2 100644 (file)
@@ -17,7 +17,8 @@
 #define MIRALL_CREDS_HTTP_CREDENTIALS_H
 
 #include <QMap>
-
+#include <QSslCertificate>
+#include <QSslKey>
 #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
index 97426404522451b26fea85fc6a9b987152c27b03..4cc5801ae7c6de535b15ad8be7991e3b40106e98 100644 (file)
@@ -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();