#include <QNetworkAccessManager>
#include <QPropertyAnimation>
#include <QGraphicsPixmapItem>
+#include <QBuffer>
#include "QProgressIndicator.h"
{
QFile certFile(addCertDial->getCertificatePath());
certFile.open(QFile::ReadOnly);
- if (QSslCertificate::importPkcs12(
- &certFile,
- &_ocWizard->_clientSslKey,
- &_ocWizard->_clientSslCertificate,
- &_ocWizard->_clientSslCaCertificates,
- addCertDial->getCertificatePasswd().toLocal8Bit())) {
- // The SSL cert gets added to the QSslConfiguration in checkServer()
+ QByteArray certData = certFile.readAll();
+ QByteArray certPassword = addCertDial->getCertificatePasswd().toLocal8Bit();
+
+ QBuffer certDataBuffer(&certData);
+ certDataBuffer.open(QIODevice::ReadOnly);
+ if (QSslCertificate::importPkcs12(&certDataBuffer,
+ &_ocWizard->_clientSslKey, &_ocWizard->_clientSslCertificate,
+ &_ocWizard->_clientSslCaCertificates, certPassword)) {
+ _ocWizard->_clientCertBundle = certData;
+ _ocWizard->_clientCertPassword = certPassword;
+
addCertDial->reinit(); // FIXME: Why not just have this only created on use?
+
+ // The extracted SSL key and cert gets added to the QSslConfiguration in checkServer()
validatePage();
} else {
addCertDial->showErrorMessage(tr("Could not load certificate. Maybe wrong password?"));
namespace {
const char userC[] = "user";
const char isOAuthC[] = "oauth";
+ const char clientCertBundleC[] = "clientCertPkcs12";
+ const char clientCertPasswordC[] = "_clientCertPassword";
const char clientCertificatePEMC[] = "_clientCertificatePEM";
const char clientKeyPEMC[] = "_clientKeyPEM";
const char authenticationFailedC[] = "owncloud-authentication-failed";
HttpCredentials::HttpCredentials() = default;
// From wizard
-HttpCredentials::HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate, const QSslKey &key)
+HttpCredentials::HttpCredentials(const QString &user, const QString &password, const QByteArray &clientCertBundle, const QByteArray &clientCertPassword)
: _user(user)
, _password(password)
, _ready(true)
- , _clientSslKey(key)
- , _clientSslCertificate(certificate)
+ , _clientCertBundle(clientCertBundle)
+ , _clientCertPassword(clientCertPassword)
, _retryOnKeyChainError(false)
{
+ if (!unpackClientCertBundle()) {
+ ASSERT(false, "pkcs12 client cert bundle passed to HttpCredentials must be valid");
+ }
}
QString HttpCredentials::authType() const
void HttpCredentials::fetchFromKeychainHelper()
{
- // Read client cert from keychain
+ _clientCertBundle = _account->credentialSetting(QLatin1String(clientCertBundleC)).toByteArray();
+ if (!_clientCertBundle.isEmpty()) {
+ // New case (>=2.6): We have a bundle in the settings and read the password from
+ // the keychain
+ ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
+ addSettingsToJob(_account, job);
+ job->setInsecureFallback(false);
+ job->setKey(keychainKey(_account->url().toString(), _user + clientCertPasswordC, _account->id()));
+ connect(job, &Job::finished, this, &HttpCredentials::slotReadClientCertPasswordJobDone);
+ job->start();
+ return;
+ }
+
+ // Old case (pre 2.6): Read client cert and then key from keychain
const QString kck = keychainKey(
_account->url().toString(),
_user + clientCertificatePEMC,
startDeleteJob(_user + clientCertificatePEMC);
}
-void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
+bool HttpCredentials::keychainUnavailableRetryLater(QKeychain::Job *incoming)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
Q_ASSERT(!incoming->insecureFallback()); // If insecureFallback is set, the next test would be pointless
qCInfo(lcHttpCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incoming->errorString();
QTimer::singleShot(10000, this, &HttpCredentials::fetchFromKeychainHelper);
_retryOnKeyChainError = false;
- return;
+ return true;
}
- _retryOnKeyChainError = false;
#endif
+ _retryOnKeyChainError = false;
+ return false;
+}
+
+void HttpCredentials::slotReadClientCertPasswordJobDone(QKeychain::Job *job)
+{
+ if (keychainUnavailableRetryLater(job))
+ return;
+
+ ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(job);
+ if (readJob->error() == NoError) {
+ _clientCertPassword = readJob->binaryData();
+ } else {
+ qCWarning(lcHttpCredentials) << "Could not retrieve client cert password from keychain" << job->errorString();
+ }
+
+ if (!unpackClientCertBundle()) {
+ qCWarning(lcHttpCredentials) << "Could not unpack client cert bundle";
+ }
+ _clientCertBundle.clear();
+ _clientCertPassword.clear();
+
+ slotReadPasswordFromKeychain();
+}
+
+void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
+{
+ if (keychainUnavailableRetryLater(incoming))
+ return;
// Store PEM in memory
auto *readJob = static_cast<ReadPasswordJob *>(incoming);
}
}
- // Now fetch the actual server password
+ slotReadPasswordFromKeychain();
+}
+
+void HttpCredentials::slotReadPasswordFromKeychain()
+{
const QString kck = keychainKey(
_account->url().toString(),
_user,
_account->setCredentialSetting(QLatin1String(userC), _user);
_account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth());
+ if (!_clientCertBundle.isEmpty()) {
+ // Note that the _clientCertBundle will often be cleared after usage,
+ // it's just written if it gets passed into the constructor.
+ _account->setCredentialSetting(QLatin1String(clientCertBundleC), _clientCertBundle);
+ }
_account->wantsAccountSaved(_account);
- // write cert if there is one
- if (!_clientSslCertificate.isNull()) {
+ // write secrets to the keychain
+ if (!_clientCertBundle.isEmpty()) {
+ // Option 1: If we have a pkcs12 bundle, that'll be written to the config file
+ // and we'll just store the bundle password in the keychain. That's prefered
+ // since the keychain on older Windows platforms can only store a limited number
+ // of bytes per entry and key/cert may exceed that.
+ auto *job = new WritePasswordJob(Theme::instance()->appName());
+ addSettingsToJob(_account, job);
+ job->setInsecureFallback(false);
+ connect(job, &Job::finished, this, &HttpCredentials::slotWriteClientCertPasswordJobDone);
+ job->setKey(keychainKey(_account->url().toString(), _user + clientCertPasswordC, _account->id()));
+ job->setBinaryData(_clientCertPassword);
+ job->start();
+ _clientCertBundle.clear();
+ _clientCertPassword.clear();
+ } else if (_account->credentialSetting(QLatin1String(clientCertBundleC)).isNull() && !_clientSslCertificate.isNull()) {
+ // Option 2, pre 2.6 configs: We used to store the raw cert/key in the keychain and
+ // still do so if no bundle is available. We can't currently migrate to Option 1
+ // because we have no functions for creating an encrypted pkcs12 bundle.
auto *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
job->setBinaryData(_clientSslCertificate.toPem());
job->start();
} else {
- slotWriteClientCertPEMJobDone(nullptr);
+ // Option 3: no client certificate at all (or doesn't need to be written)
+ slotWritePasswordToKeychain();
}
}
+void HttpCredentials::slotWriteClientCertPasswordJobDone(Job *finishedJob)
+{
+ if (finishedJob && finishedJob->error() != QKeychain::NoError) {
+ qCWarning(lcHttpCredentials) << "Could not write client cert password to credentials"
+ << finishedJob->error() << finishedJob->errorString();
+ }
+
+ slotWritePasswordToKeychain();
+}
+
void HttpCredentials::slotWriteClientCertPEMJobDone(Job *finishedJob)
{
if (finishedJob && finishedJob->error() != QKeychain::NoError) {
<< finishedJob->error() << finishedJob->errorString();
}
+ slotWritePasswordToKeychain();
+}
+
+void HttpCredentials::slotWritePasswordToKeychain()
+{
auto *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
return true;
}
+bool HttpCredentials::unpackClientCertBundle()
+{
+ if (_clientCertBundle.isEmpty())
+ return true;
+
+ QBuffer certBuffer(&_clientCertBundle);
+ certBuffer.open(QIODevice::ReadOnly);
+ QList<QSslCertificate> clientCaCertificates;
+ return QSslCertificate::importPkcs12(
+ &certBuffer, &_clientSslKey, &_clientSslCertificate, &clientCaCertificates, _clientCertPassword);
+}
+
} // namespace OCC
static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User;
HttpCredentials();
- explicit HttpCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey());
+ explicit HttpCredentials(const QString &user, const QString &password,
+ const QByteArray &clientCertBundle = QByteArray(), const QByteArray &clientCertPassword = QByteArray());
QString authType() const override;
QNetworkAccessManager *createQNAM() const override;
private Q_SLOTS:
void slotAuthentication(QNetworkReply *, QAuthenticator *);
+ void slotReadClientCertPasswordJobDone(QKeychain::Job *);
void slotReadClientCertPEMJobDone(QKeychain::Job *);
void slotReadClientKeyPEMJobDone(QKeychain::Job *);
+
+ void slotReadPasswordFromKeychain();
void slotReadJobDone(QKeychain::Job *);
+ void slotWriteClientCertPasswordJobDone(QKeychain::Job *);
void slotWriteClientCertPEMJobDone(QKeychain::Job *);
void slotWriteClientKeyPEMJobDone(QKeychain::Job *);
+
+ void slotWritePasswordToKeychain();
void slotWriteJobDone(QKeychain::Job *);
protected:
/// Wipes legacy keychain locations
void deleteOldKeychainEntries();
+ /** Whether to bow out now because a retry will happen later
+ *
+ * Sometimes the keychain needs a while to become available.
+ * This function should be called on first keychain-read to check
+ * whether it errored because the keychain wasn't available yet.
+ * If that happens, this function will schedule another try and
+ * return true.
+ */
+ bool keychainUnavailableRetryLater(QKeychain::Job *);
+
+ /** Takes client cert pkcs12 and unwraps the key/cert.
+ *
+ * Returns false on failure.
+ */
+ bool unpackClientCertBundle();
+
QString _user;
QString _password; // user's password, or access_token for OAuth
QString _refreshToken; // OAuth _refreshToken, set if OAuth is used.
QString _fetchErrorString;
bool _ready = false;
bool _isRenewingOAuthToken = false;
+ QByteArray _clientCertBundle;
+ QByteArray _clientCertPassword;
QSslKey _clientSslKey;
QSslCertificate _clientSslCertificate;
bool _keychainMigration = false;