[Pkg-privacy-commits] [ricochet-im] 02/05: New upstream version 1.1.3
Ximin Luo
infinity0 at debian.org
Thu Oct 13 15:22:59 UTC 2016
This is an automated email from the git hooks/post-receive script.
infinity0 pushed a commit to branch master
in repository ricochet-im.
commit 3789ad4a51c3eea1d867c1c55077cd3e24785244
Author: Ximin Luo <infinity0 at debian.org>
Date: Thu Oct 13 17:14:19 2016 +0200
New upstream version 1.1.3
---
BUILDING.md | 9 ++
contrib/ricochet-seccomp-amd64.policy | 3 +
hardened.pri | 3 +-
ricochet.pro | 4 +-
src/core/ContactUser.cpp | 2 +-
src/core/UserIdentity.cpp | 94 +++++++++++------
src/core/UserIdentity.h | 1 +
src/main.cpp | 18 +++-
.../{SetConfCommand.cpp => AddOnionCommand.cpp} | 87 ++++++++--------
src/tor/{SetConfCommand.h => AddOnionCommand.h} | 29 +++---
src/tor/HiddenService.cpp | 95 +++++++++--------
src/tor/HiddenService.h | 27 +++--
src/tor/TorControl.cpp | 113 +++++++++++++++++----
src/tor/TorControl.h | 7 ++
src/tor/TorManager.cpp | 42 ++++++--
src/ui/LinkedText.cpp | 2 +-
src/ui/qml/ContactIDField.qml | 1 +
src/ui/qml/NetworkSetupWizard.qml | 9 --
src/ui/qml/TorConfigurationPage.qml | 13 +--
src/ui/qml/TorPreferences.qml | 1 +
src/utils/CryptoKey.cpp | 51 +++++++++-
src/utils/CryptoKey.h | 3 +-
src/utils/Settings.cpp | 2 +-
tests/tests.pro | 2 +-
.../{cryptokey => tst_cryptokey}/tst_cryptokey.cpp | 27 ++++-
.../{cryptokey => tst_cryptokey}/tst_cryptokey.pro | 2 +-
translation/ricochet_da.ts | 44 ++++----
27 files changed, 459 insertions(+), 232 deletions(-)
diff --git a/BUILDING.md b/BUILDING.md
index e67ba68..9891df9 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -82,6 +82,15 @@ Normally, configuration will be stored in a `config.ricochet` folder, in the sam
The `packaging/osx/release_osx.sh` script demonstrates how to build a redistributable app bundle.
+Since the openssl header files were removed in El Capitan, have qmake use the openssl that comes with brew (see the OPENSSLDIR var below).
+
+Steps:
+```
+brew install protobuf qt5 tor
+/usr/local/opt/qt5/bin/qmake OPENSSLDIR=/usr/local/opt/openssl/ CONFIG+=debug
+make
+```
+
## Windows
Building for Windows is difficult. The process and scripts used for release builds are documented in the [buildscripts repository](https://github.com/ricochet-im/buildscripts/tree/master/mingw).
diff --git a/contrib/ricochet-seccomp-amd64.policy b/contrib/ricochet-seccomp-amd64.policy
index 96e7261..eba5fe9 100644
--- a/contrib/ricochet-seccomp-amd64.policy
+++ b/contrib/ricochet-seccomp-amd64.policy
@@ -42,12 +42,14 @@ getpeername: 1
getpgrp: 1
getpid: 1
getppid: 1
+getrandom: 1
getresgid: 1
getresuid: 1
getrlimit: 1
getrusage: 1
getsockname: 1
getsockopt: 1
+gettid: 1
getuid: 1
ioctl: 1
kill: 1
@@ -70,6 +72,7 @@ prctl: 1
pselect6: 1
read: 1
readlink: 1
+readlinkat: 1
recvfrom: 1
recvmsg: 1
rename: 1
diff --git a/hardened.pri b/hardened.pri
index 1b6c3b0..1bd3aaf 100644
--- a/hardened.pri
+++ b/hardened.pri
@@ -15,12 +15,13 @@ HARDENED_STACK_PROTECTOR_FLAGS = -fstack-protector --param=ssp-buffer-size=4
HARDENED_MINGW_64ASLR_FLAGS = -Wl,--dynamicbase -Wl,--high-entropy-va
+
# Run tests and apply options where possible
CONFIG(hardened) {
# mingw is always PIC, and complains about the flag
!mingw:HARDEN_FLAGS = -fPIC
- qtCompileTest(sanitize):HARDEN_FLAGS += $$HARDENED_SANITIZE_FLAGS
+ CONFIG(debug,debug|release): qtCompileTest(sanitize):HARDEN_FLAGS += $$HARDENED_SANITIZE_FLAGS
qtCompileTest(sanitize-ubsan):HARDEN_FLAGS += $$HARDENED_SANITIZE_UBSAN_FLAGS
qtCompileTest(sanitize-ubsan-more):HARDEN_FLAGS += $$HARDENED_SANITIZE_UBSAN_MORE_FLAGS
qtCompileTest(vtable-verify):HARDEN_FLAGS += $$HARDENED_VTABLE_VERIFY_FLAGS
diff --git a/ricochet.pro b/ricochet.pro
index 86a729c..fea5379 100644
--- a/ricochet.pro
+++ b/ricochet.pro
@@ -38,7 +38,7 @@ TEMPLATE = app
QT += core gui network quick widgets multimedia
CONFIG += c++11
-VERSION = 1.1.2
+VERSION = 1.1.3
# Use CONFIG+=no-hardened to disable compiler hardening options
!CONFIG(no-hardened) {
@@ -144,6 +144,7 @@ SOURCES += src/main.cpp \
src/tor/ProtocolInfoCommand.cpp \
src/tor/AuthenticateCommand.cpp \
src/tor/SetConfCommand.cpp \
+ src/tor/AddOnionCommand.cpp \
src/utils/StringUtil.cpp \
src/core/ContactsManager.cpp \
src/core/ContactUser.cpp \
@@ -173,6 +174,7 @@ HEADERS += src/ui/MainWindow.h \
src/tor/ProtocolInfoCommand.h \
src/tor/AuthenticateCommand.h \
src/tor/SetConfCommand.h \
+ src/tor/AddOnionCommand.h \
src/utils/StringUtil.h \
src/core/ContactsManager.h \
src/core/ContactUser.h \
diff --git a/src/core/ContactUser.cpp b/src/core/ContactUser.cpp
index 422caf7..6e5f766 100644
--- a/src/core/ContactUser.cpp
+++ b/src/core/ContactUser.cpp
@@ -159,7 +159,7 @@ void ContactUser::updateOutgoingSocket()
if (!m_outgoingSocket) {
m_outgoingSocket = new Protocol::OutboundConnector(this);
- m_outgoingSocket->setAuthPrivateKey(identity->hiddenService()->cryptoKey());
+ m_outgoingSocket->setAuthPrivateKey(identity->hiddenService()->privateKey());
connect(m_outgoingSocket, &Protocol::OutboundConnector::ready, this,
[this]() {
assignConnection(m_outgoingSocket->takeConnection());
diff --git a/src/core/UserIdentity.cpp b/src/core/UserIdentity.cpp
index e04e8ce..2c1d6c1 100644
--- a/src/core/UserIdentity.cpp
+++ b/src/core/UserIdentity.cpp
@@ -54,37 +54,7 @@ UserIdentity::UserIdentity(int id, QObject *parent)
m_settings = new SettingsObject(QStringLiteral("identity"), this);
connect(m_settings, &SettingsObject::modified, this, &UserIdentity::onSettingsModified);
- QString dir = m_settings->read("dataDirectory", QString::fromLatin1("data-%1").arg(uniqueID)).toString();
-
- m_hiddenService = new Tor::HiddenService(dir, this);
- connect(m_hiddenService, SIGNAL(statusChanged(int,int)), SLOT(onStatusChanged(int,int)));
-
- // Generally, these are not used, and we bind to localhost and port 0
- // for an automatic (and portable) selection.
- QHostAddress address(m_settings->read("localListenAddress").toString());
- if (address.isNull())
- address = QHostAddress::LocalHost;
- quint16 port = (quint16)m_settings->read("localListenPort").toInt();
-
- if (!m_settings->read("initializing").toBool() && m_hiddenService->status() == Tor::HiddenService::NotCreated)
- {
- qWarning("Hidden service data for identity %d in %s does not exist", uniqueID, qPrintable(dir));
- delete m_hiddenService;
- m_hiddenService = 0;
- }
- else
- {
- m_incomingServer = new QTcpServer(this);
- if (!m_incomingServer->listen(address, port)) {
- qWarning() << "Failed to open incoming socket:" << m_incomingServer->errorString();
- return;
- }
-
- connect(m_incomingServer, &QTcpServer::newConnection, this, &UserIdentity::onIncomingConnection);
-
- m_hiddenService->addTarget(9878, m_incomingServer->serverAddress(), m_incomingServer->serverPort());
- torControl->addHiddenService(m_hiddenService);
- }
+ setupService();
contacts.loadFromSettings();
}
@@ -106,6 +76,68 @@ UserIdentity *UserIdentity::createIdentity(int uniqueID, const QString &dataDire
return new UserIdentity(uniqueID);
}
+// TODO: Handle the error cases of this function in a useful way
+void UserIdentity::setupService()
+{
+ QString keyData = m_settings->read("serviceKey").toString();
+ QString legacyDir = m_settings->read("dataDirectory").toString();
+
+ if (!keyData.isEmpty()) {
+ CryptoKey key;
+ if (!key.loadFromData(QByteArray::fromBase64(keyData.toLatin1()), CryptoKey::PrivateKey, CryptoKey::DER)) {
+ qWarning() << "Cannot load service key from configuration";
+ return;
+ }
+
+ m_hiddenService = new Tor::HiddenService(key, legacyDir, this);
+ } else if (!legacyDir.isEmpty() && QFile::exists(legacyDir + QLatin1String("/private_key"))) {
+ qDebug() << "Attempting to load key from legacy filesystem format in" << legacyDir;
+
+ CryptoKey key;
+ if (!key.loadFromFile(legacyDir + QLatin1String("/private_key"), CryptoKey::PrivateKey)) {
+ qWarning() << "Cannot load legacy format key from" << legacyDir << "for conversion";
+ return;
+ } else {
+ keyData = QString::fromLatin1(key.encodedPrivateKey(CryptoKey::DER).toBase64());
+ m_settings->write("serviceKey", keyData);
+ m_hiddenService = new Tor::HiddenService(key, legacyDir, this);
+ }
+ } else if (!m_settings->read("initializing").toBool()) {
+ qWarning() << "Missing private key for initialized identity";
+ return;
+ } else {
+ m_hiddenService = new Tor::HiddenService(legacyDir, this);
+ connect(m_hiddenService, &Tor::HiddenService::privateKeyChanged, this,
+ [&]() {
+ QString key = QString::fromLatin1(m_hiddenService->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64());
+ m_settings->write("serviceKey", key);
+ }
+ );
+ }
+
+ Q_ASSERT(m_hiddenService);
+ connect(m_hiddenService, SIGNAL(statusChanged(int,int)), SLOT(onStatusChanged(int,int)));
+
+ // Generally, these are not used, and we bind to localhost and port 0
+ // for an automatic (and portable) selection.
+ QHostAddress address(m_settings->read("localListenAddress").toString());
+ if (address.isNull())
+ address = QHostAddress::LocalHost;
+ quint16 port = (quint16)m_settings->read("localListenPort").toInt();
+
+ m_incomingServer = new QTcpServer(this);
+ if (!m_incomingServer->listen(address, port)) {
+ // XXX error case
+ qWarning() << "Failed to open incoming socket:" << m_incomingServer->errorString();
+ return;
+ }
+
+ connect(m_incomingServer, &QTcpServer::newConnection, this, &UserIdentity::onIncomingConnection);
+
+ m_hiddenService->addTarget(9878, m_incomingServer->serverAddress(), m_incomingServer->serverPort());
+ torControl->addHiddenService(m_hiddenService);
+}
+
SettingsObject *UserIdentity::settings()
{
return m_settings;
diff --git a/src/core/UserIdentity.h b/src/core/UserIdentity.h
index 3d7febd..457227c 100644
--- a/src/core/UserIdentity.h
+++ b/src/core/UserIdentity.h
@@ -122,6 +122,7 @@ private:
static UserIdentity *createIdentity(int uniqueID, const QString &dataDirectory = QString());
void handleIncomingAuthedConnection(Protocol::Connection *connection);
+ void setupService();
};
Q_DECLARE_METATYPE(UserIdentity*)
diff --git a/src/main.cpp b/src/main.cpp
index b28b923..54f64b8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -56,8 +56,13 @@ static void initTranslation();
int main(int argc, char *argv[])
{
+ /* Disable rwx memory.
+ This will also ensure full PAX/Grsecurity protections. */
+ qputenv("QV4_FORCE_INTERPRETER", "1");
+ qputenv("QT_ENABLE_REGEXP_JIT", "0");
+
QApplication a(argc, argv);
- a.setApplicationVersion(QLatin1String("1.1.2"));
+ a.setApplicationVersion(QLatin1String("1.1.3"));
a.setOrganizationName(QStringLiteral("Ricochet"));
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
@@ -128,6 +133,13 @@ static QString appBundlePath()
}
#endif
+// Writes default settings to settings object. Does not care about any
+// preexisting values, therefore this is best used on a fresh object.
+static void loadDefaultSettings(SettingsFile *settings)
+{
+ settings->root()->write("ui.combinedChatWindow", true);
+}
+
static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &errorMessage)
{
/* If built in portable mode (default), configuration is stored in the 'config'
@@ -206,6 +218,10 @@ static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &
if (QFile::exists(filePath))
importLegacySettings(settings, filePath);
}
+ // if still empty, load defaults here
+ if (settings->root()->data().isEmpty()) {
+ loadDefaultSettings(settings);
+ }
return true;
}
diff --git a/src/tor/SetConfCommand.cpp b/src/tor/AddOnionCommand.cpp
similarity index 53%
copy from src/tor/SetConfCommand.cpp
copy to src/tor/AddOnionCommand.cpp
index eb3a13b..53add93 100644
--- a/src/tor/SetConfCommand.cpp
+++ b/src/tor/AddOnionCommand.cpp
@@ -1,5 +1,5 @@
/* Ricochet - https://ricochet.im/
- * Copyright (C) 2014, John Brooks <john.brooks at dereferenced.net>
+ * Copyright (C) 2016, John Brooks <john.brooks at dereferenced.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -30,77 +30,76 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "SetConfCommand.h"
+#include "AddOnionCommand.h"
+#include "tor/HiddenService.h"
+#include "utils/CryptoKey.h"
#include "utils/StringUtil.h"
using namespace Tor;
-SetConfCommand::SetConfCommand()
- : m_resetMode(false)
+AddOnionCommand::AddOnionCommand(HiddenService *service)
+ : m_service(service)
{
+ Q_ASSERT(m_service);
}
-void SetConfCommand::setResetMode(bool enabled)
+bool AddOnionCommand::isSuccessful() const
{
- m_resetMode = enabled;
+ return statusCode() == 250 && m_errorMessage.isEmpty();
}
-bool SetConfCommand::isSuccessful() const
+QByteArray AddOnionCommand::build()
{
- return statusCode() == 250;
-}
+ QByteArray out("ADD_ONION");
-QByteArray SetConfCommand::build(const QByteArray &key, const QByteArray &value)
-{
- return build(QList<QPair<QByteArray, QByteArray> >() << qMakePair(key, value));
-}
-
-QByteArray SetConfCommand::build(const QVariantMap &data)
-{
- QList<QPair<QByteArray, QByteArray> > out;
-
- for (QVariantMap::ConstIterator it = data.begin(); it != data.end(); it++) {
- QByteArray key = it.key().toLatin1();
-
- if (static_cast<QMetaType::Type>(it.value().type()) == QMetaType::QVariantList) {
- QVariantList values = it.value().value<QVariantList>();
- foreach (const QVariant &value, values)
- out.append(qMakePair(key, value.toString().toLatin1()));
- } else {
- out.append(qMakePair(key, it.value().toString().toLatin1()));
- }
+ if (m_service->privateKey().isLoaded()) {
+ out += " RSA1024:";
+ out += m_service->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64();
+ } else {
+ out += " NEW:RSA1024";
}
- return build(out);
-}
-
-QByteArray SetConfCommand::build(const QList<QPair<QByteArray, QByteArray> > &data)
-{
- QByteArray out(m_resetMode ? "RESETCONF" : "SETCONF");
-
- for (int i = 0; i < data.size(); i++) {
- out += " " + data[i].first;
- if (!data[i].second.isEmpty())
- out += "=" + quotedString(data[i].second);
+ foreach (const HiddenService::Target &target, m_service->targets()) {
+ out += " Port=";
+ out += QByteArray::number(target.servicePort);
+ out += ",";
+ out += target.targetAddress.toString().toLatin1();
+ out += ":";
+ out += QByteArray::number(target.targetPort);
}
out.append("\r\n");
return out;
}
-void SetConfCommand::onReply(int statusCode, const QByteArray &data)
+void AddOnionCommand::onReply(int statusCode, const QByteArray &data)
{
TorControlCommand::onReply(statusCode, data);
- if (statusCode != 250)
+ if (statusCode != 250) {
m_errorMessage = QString::fromLatin1(data);
+ return;
+ }
+
+ const QByteArray keyPrefix("PrivateKey=RSA1024:");
+ if (data.startsWith(keyPrefix)) {
+ QByteArray keyData(QByteArray::fromBase64(data.mid(keyPrefix.size())));
+ CryptoKey key;
+ if (!key.loadFromData(keyData, CryptoKey::PrivateKey, CryptoKey::DER)) {
+ m_errorMessage = QStringLiteral("Key decoding failed");
+ return;
+ }
+
+ m_service->setPrivateKey(key);
+ }
}
-void SetConfCommand::onFinished(int statusCode)
+void AddOnionCommand::onFinished(int statusCode)
{
TorControlCommand::onFinished(statusCode);
if (isSuccessful())
- emit setConfSucceeded();
+ emit succeeded();
else
- emit setConfFailed(statusCode);
+ emit failed(statusCode);
}
+
diff --git a/src/tor/SetConfCommand.h b/src/tor/AddOnionCommand.h
similarity index 78%
copy from src/tor/SetConfCommand.h
copy to src/tor/AddOnionCommand.h
index 5bdcb93..7c0afaf 100644
--- a/src/tor/SetConfCommand.h
+++ b/src/tor/AddOnionCommand.h
@@ -1,5 +1,5 @@
/* Ricochet - https://ricochet.im/
- * Copyright (C) 2014, John Brooks <john.brooks at dereferenced.net>
+ * Copyright (C) 2016, John Brooks <john.brooks at dereferenced.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -30,8 +30,8 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef SETCONFCOMMAND_H
-#define SETCONFCOMMAND_H
+#ifndef ADDONIONCOMMAND_H
+#define ADDONIONCOMMAND_H
#include "TorControlCommand.h"
#include <QList>
@@ -41,33 +41,31 @@
namespace Tor
{
-class SetConfCommand : public TorControlCommand
+class HiddenService;
+
+class AddOnionCommand : public TorControlCommand
{
Q_OBJECT
- Q_DISABLE_COPY(SetConfCommand)
+ Q_DISABLE_COPY(AddOnionCommand)
Q_PROPERTY(QString errorMessage READ errorMessage CONSTANT)
Q_PROPERTY(bool successful READ isSuccessful CONSTANT)
public:
- SetConfCommand();
-
- void setResetMode(bool resetMode);
+ AddOnionCommand(HiddenService *service);
- QByteArray build(const QByteArray &key, const QByteArray &value);
- QByteArray build(const QVariantMap &data);
- QByteArray build(const QList<QPair<QByteArray, QByteArray> > &data);
+ QByteArray build();
QString errorMessage() const { return m_errorMessage; }
bool isSuccessful() const;
signals:
- void setConfSucceeded();
- void setConfFailed(int code);
+ void succeeded();
+ void failed(int code);
protected:
+ HiddenService *m_service;
QString m_errorMessage;
- bool m_resetMode;
virtual void onReply(int statusCode, const QByteArray &data);
virtual void onFinished(int statusCode);
@@ -75,4 +73,5 @@ protected:
}
-#endif // SETCONFCOMMAND_H
+#endif // ADDONIONCOMMAND_H
+
diff --git a/src/tor/HiddenService.cpp b/src/tor/HiddenService.cpp
index ad9abc9..0621400 100644
--- a/src/tor/HiddenService.cpp
+++ b/src/tor/HiddenService.cpp
@@ -34,6 +34,7 @@
#include "TorControl.h"
#include "TorSocket.h"
#include "utils/CryptoKey.h"
+#include "utils/Useful.h"
#include <QDir>
#include <QFile>
#include <QTimer>
@@ -41,95 +42,91 @@
using namespace Tor;
-HiddenService::HiddenService(const QString &p, QObject *parent)
- : QObject(parent), dataPath(p), pStatus(NotCreated)
+HiddenService::HiddenService(QObject *parent)
+ : QObject(parent), m_status(NotCreated)
+{
+}
+
+HiddenService::HiddenService(const QString &path, QObject *parent)
+ : QObject(parent), m_dataPath(path), m_status(NotCreated)
{
/* Set the initial status and, if possible, load the hostname */
- QDir dir(dataPath);
- if (dir.exists(QLatin1String("hostname")) && dir.exists(QLatin1String("private_key")))
- {
- readHostname();
- if (!pHostname.isEmpty())
- pStatus = Offline;
+ if (QDir(m_dataPath).exists(QLatin1String("private_key"))) {
+ loadPrivateKey();
+ if (!m_hostname.isEmpty())
+ m_status = Offline;
}
}
+HiddenService::HiddenService(const CryptoKey &privateKey, const QString &path, QObject *parent)
+ : QObject(parent), m_dataPath(path), m_status(NotCreated)
+{
+ setPrivateKey(privateKey);
+ m_status = Offline;
+}
+
void HiddenService::setStatus(Status newStatus)
{
- if (pStatus == newStatus)
+ if (m_status == newStatus)
return;
- Status old = pStatus;
- pStatus = newStatus;
+ Status old = m_status;
+ m_status = newStatus;
- emit statusChanged(pStatus, old);
+ emit statusChanged(m_status, old);
- if (pStatus == Online)
+ if (m_status == Online)
emit serviceOnline();
}
void HiddenService::addTarget(const Target &target)
{
- pTargets.append(target);
+ m_targets.append(target);
}
void HiddenService::addTarget(quint16 servicePort, QHostAddress targetAddress, quint16 targetPort)
{
Target t = { targetAddress, servicePort, targetPort };
- pTargets.append(t);
+ m_targets.append(t);
}
-void HiddenService::readHostname()
+void HiddenService::setPrivateKey(const CryptoKey &key)
{
- pHostname.clear();
-
- QFile file(dataPath + QLatin1String("/hostname"));
- if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
- {
- qDebug() << "Failed to open hostname file for hidden service" << dataPath << "-" << file.errorString();
+ if (m_privateKey.isLoaded()) {
+ BUG() << "Cannot change the private key on an existing HiddenService";
return;
}
- QByteArray data;
- data.resize(32);
-
- int rd = file.readLine(data.data(), data.size());
- if (rd < 0)
- {
- qDebug() << "Failed to read hostname file for hidden service" << dataPath << "-" << file.errorString();
+ if (!key.isPrivate()) {
+ BUG() << "Cannot create a hidden service with a public key";
return;
}
- data.resize(rd);
-
- int sep = data.lastIndexOf('.');
- if (sep != 16 || data.mid(sep) != ".onion\n")
- {
- qDebug() << "Failed to read hostname file for hidden service" << dataPath << "- invalid contents";
- return;
- }
-
- pHostname = QString::fromLatin1(data.constData(), sep) + QLatin1String(".onion");
- qDebug() << "Hidden service hostname is" << pHostname;
+ m_privateKey = key;
+ m_hostname = m_privateKey.torServiceID() + QStringLiteral(".onion");
+ emit privateKeyChanged();
}
-CryptoKey HiddenService::cryptoKey()
+void HiddenService::loadPrivateKey()
{
- if (!pCryptoKey.isLoaded()) {
- bool ok = pCryptoKey.loadFromFile(dataPath + QLatin1String("/private_key"), CryptoKey::PrivateKey);
- if (!ok)
- qWarning() << "Failed to load hidden service key";
+ if (m_privateKey.isLoaded() || m_dataPath.isEmpty())
+ return;
+
+ bool ok = m_privateKey.loadFromFile(m_dataPath + QLatin1String("/private_key"), CryptoKey::PrivateKey);
+ if (!ok) {
+ qWarning() << "Failed to load hidden service key";
+ return;
}
- return pCryptoKey;
+ m_hostname = m_privateKey.torServiceID();
+ emit privateKeyChanged();
}
void HiddenService::servicePublished()
{
- readHostname();
+ loadPrivateKey();
- if (pHostname.isEmpty())
- {
+ if (m_hostname.isEmpty()) {
qDebug() << "Failed to read hidden service hostname";
return;
}
diff --git a/src/tor/HiddenService.h b/src/tor/HiddenService.h
index 5aa9f71..af7790e 100644
--- a/src/tor/HiddenService.h
+++ b/src/tor/HiddenService.h
@@ -64,34 +64,39 @@ public:
Online /* Published */
};
- const QString dataPath;
-
+ HiddenService(QObject *parent = 0);
HiddenService(const QString &dataPath, QObject *parent = 0);
+ HiddenService(const CryptoKey &privateKey, const QString &dataPath = QString(), QObject *parent = 0);
+
+ Status status() const { return m_status; }
- Status status() const { return pStatus; }
+ const QString &hostname() const { return m_hostname; }
+ const QString &dataPath() const { return m_dataPath; }
- const QString &hostname() const { return pHostname; }
- CryptoKey cryptoKey();
+ CryptoKey privateKey() { return m_privateKey; }
+ void setPrivateKey(const CryptoKey &privateKey);
- const QList<Target> &targets() const { return pTargets; }
+ const QList<Target> &targets() const { return m_targets; }
void addTarget(const Target &target);
void addTarget(quint16 servicePort, QHostAddress targetAddress, quint16 targetPort);
signals:
void statusChanged(int newStatus, int oldStatus);
void serviceOnline();
+ void privateKeyChanged();
private slots:
void servicePublished();
private:
- QList<Target> pTargets;
- QString pHostname;
- Status pStatus;
- CryptoKey pCryptoKey;
+ QString m_dataPath;
+ QList<Target> m_targets;
+ QString m_hostname;
+ Status m_status;
+ CryptoKey m_privateKey;
+ void loadPrivateKey();
void setStatus(Status newStatus);
- void readHostname();
};
}
diff --git a/src/tor/TorControl.cpp b/src/tor/TorControl.cpp
index a5c5b0f..d5a7091 100644
--- a/src/tor/TorControl.cpp
+++ b/src/tor/TorControl.cpp
@@ -37,6 +37,7 @@
#include "AuthenticateCommand.h"
#include "SetConfCommand.h"
#include "GetConfCommand.h"
+#include "AddOnionCommand.h"
#include "utils/StringUtil.h"
#include "utils/Settings.h"
#include "utils/PendingOperation.h"
@@ -46,6 +47,7 @@
#include <QQmlEngine>
#include <QTimer>
#include <QSaveFile>
+#include <QRegularExpression>
#include <QDebug>
Tor::TorControl *torControl = 0;
@@ -72,6 +74,7 @@ public:
TorControl::Status status;
TorControl::TorStatus torStatus;
QVariantMap bootstrapStatus;
+ bool hasOwnership;
TorControlPrivate(TorControl *parent);
@@ -104,7 +107,8 @@ TorControl::TorControl(QObject *parent)
TorControlPrivate::TorControlPrivate(TorControl *parent)
: QObject(parent), q(parent), controlPort(0), socksPort(0),
- status(TorControl::NotConnected), torStatus(TorControl::TorUnknown)
+ status(TorControl::NotConnected), torStatus(TorControl::TorUnknown),
+ hasOwnership(false)
{
socket = new TorControlSocket(this);
QObject::connect(socket, SIGNAL(connected()), this, SLOT(socketConnected()));
@@ -272,7 +276,8 @@ void TorControlPrivate::authenticateReply()
// XXX Fix old configurations that would store unwanted options in torrc.
// This can be removed some suitable amount of time after 1.0.4.
- q->saveConfiguration();
+ if (hasOwnership)
+ q->saveConfiguration();
}
void TorControlPrivate::socketConnected()
@@ -479,40 +484,71 @@ void TorControlPrivate::publishServices()
return;
}
- SetConfCommand *command = new SetConfCommand;
- QList<QPair<QByteArray,QByteArray> > torConfig;
+ if (q->torVersionAsNewAs(QStringLiteral("0.2.7"))) {
+ foreach (HiddenService *service, services) {
+ if (service->hostname().isEmpty())
+ qDebug() << "torctrl: Creating a new hidden service";
+ else
+ qDebug() << "torctrl: Publishing hidden service" << service->hostname();
+ AddOnionCommand *onionCommand = new AddOnionCommand(service);
+ QObject::connect(onionCommand, &AddOnionCommand::succeeded, service, &HiddenService::servicePublished);
+ socket->sendCommand(onionCommand, onionCommand->build());
+ }
+ } else {
+ qDebug() << "torctrl: Using legacy SETCONF hidden service configuration for tor" << torVersion;
+ SetConfCommand *command = new SetConfCommand;
+ QList<QPair<QByteArray,QByteArray> > torConfig;
- for (QList<HiddenService*>::Iterator it = services.begin(); it != services.end(); ++it)
- {
- HiddenService *service = *it;
- QDir dir(service->dataPath);
+ foreach (HiddenService *service, services)
+ {
+ if (service->dataPath().isEmpty())
+ continue;
+
+ if (service->privateKey().isLoaded() && !QFile::exists(service->dataPath() + QStringLiteral("/private_key"))) {
+ // This case can happen if tor is downgraded after the profile is created
+ qWarning() << "Cannot publish ephemeral hidden services with this version of tor; skipping";
+ continue;
+ }
- qDebug() << "torctrl: Configuring hidden service at" << service->dataPath;
+ qDebug() << "torctrl: Configuring hidden service at" << service->dataPath();
- torConfig.append(qMakePair(QByteArray("HiddenServiceDir"), dir.absolutePath().toLocal8Bit()));
+ QDir dir(service->dataPath());
+ torConfig.append(qMakePair(QByteArray("HiddenServiceDir"), dir.absolutePath().toLocal8Bit()));
- const QList<HiddenService::Target> &targets = service->targets();
- for (QList<HiddenService::Target>::ConstIterator tit = targets.begin(); tit != targets.end(); ++tit)
- {
- QString target = QString::fromLatin1("%1 %2:%3").arg(tit->servicePort)
- .arg(tit->targetAddress.toString())
- .arg(tit->targetPort);
- torConfig.append(qMakePair(QByteArray("HiddenServicePort"), target.toLatin1()));
+ const QList<HiddenService::Target> &targets = service->targets();
+ for (QList<HiddenService::Target>::ConstIterator tit = targets.begin(); tit != targets.end(); ++tit)
+ {
+ QString target = QString::fromLatin1("%1 %2:%3").arg(tit->servicePort)
+ .arg(tit->targetAddress.toString())
+ .arg(tit->targetPort);
+ torConfig.append(qMakePair(QByteArray("HiddenServicePort"), target.toLatin1()));
+ }
+
+ QObject::connect(command, &SetConfCommand::setConfSucceeded, service, &HiddenService::servicePublished);
}
- QObject::connect(command, &SetConfCommand::setConfSucceeded, service, &HiddenService::servicePublished);
+ if (!torConfig.isEmpty())
+ socket->sendCommand(command, command->build(torConfig));
}
-
- socket->sendCommand(command, command->build(torConfig));
}
void TorControl::shutdown()
{
+ if (!hasOwnership()) {
+ qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own";
+ return;
+ }
+
d->socket->sendCommand("SIGNAL SHUTDOWN\r\n");
}
void TorControl::shutdownSync()
{
+ if (!hasOwnership()) {
+ qWarning() << "torctrl: Ignoring shutdown command for a tor instance I don't own";
+ return;
+ }
+
shutdown();
while (d->socket->bytesToWrite())
{
@@ -670,14 +706,27 @@ private:
PendingOperation *TorControl::saveConfiguration()
{
+ if (!hasOwnership()) {
+ qWarning() << "torctrl: Ignoring save configuration command for a tor instance I don't own";
+ return 0;
+ }
+
SaveConfigOperation *operation = new SaveConfigOperation(this);
QObject::connect(operation, &PendingOperation::finished, operation, &QObject::deleteLater);
operation->start(d->socket);
+
+ QQmlEngine::setObjectOwnership(operation, QQmlEngine::CppOwnership);
return operation;
}
+bool TorControl::hasOwnership() const
+{
+ return d->hasOwnership;
+}
+
void TorControl::takeOwnership()
{
+ d->hasOwnership = true;
d->socket->sendCommand("TAKEOWNERSHIP\r\n");
// Reset PID-based polling
@@ -686,5 +735,29 @@ void TorControl::takeOwnership()
setConfiguration(options);
}
+bool TorControl::torVersionAsNewAs(const QString &match) const
+{
+ QRegularExpression r(QStringLiteral("[.-]"));
+ QStringList split = torVersion().split(r);
+ QStringList matchSplit = match.split(r);
+
+ for (int i = 0; i < matchSplit.size(); i++) {
+ if (i >= split.size())
+ return false;
+ bool ok1 = false, ok2 = false;
+ int currentVal = split[i].toInt(&ok1);
+ int matchVal = matchSplit[i].toInt(&ok2);
+ if (!ok1 || !ok2)
+ return false;
+ if (currentVal > matchVal)
+ return true;
+ if (currentVal < matchVal)
+ return false;
+ }
+
+ // Versions are equal, up to the length of match
+ return true;
+}
+
#include "TorControl.moc"
diff --git a/src/tor/TorControl.h b/src/tor/TorControl.h
index 15c1ccf..23b9b71 100644
--- a/src/tor/TorControl.h
+++ b/src/tor/TorControl.h
@@ -59,6 +59,7 @@ class TorControl : public QObject
Q_PROPERTY(QString torVersion READ torVersion NOTIFY connected)
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY statusChanged)
Q_PROPERTY(QVariantMap bootstrapStatus READ bootstrapStatus NOTIFY bootstrapStatusChanged)
+ Q_PROPERTY(bool hasOwnership READ hasOwnership NOTIFY hasOwnershipChanged)
public:
enum Status
@@ -83,6 +84,7 @@ public:
Status status() const;
TorStatus torStatus() const;
QString torVersion() const;
+ bool torVersionAsNewAs(const QString &version) const;
QString errorMessage() const;
bool hasConnectivity() const;
@@ -96,6 +98,10 @@ public:
/* Connection */
bool isConnected() const { return status() == Connected; }
void connect(const QHostAddress &address, quint16 port);
+
+ /* Ownership means that tor is managed by this socket, and we
+ * can shut it down, own its configuration, etc. */
+ bool hasOwnership() const;
void takeOwnership();
/* Hidden Services */
@@ -114,6 +120,7 @@ signals:
void disconnected();
void connectivityChanged();
void bootstrapStatusChanged();
+ void hasOwnershipChanged();
public slots:
/* Instruct Tor to shutdown */
diff --git a/src/tor/TorManager.cpp b/src/tor/TorManager.cpp
index f63574b..27f814c 100644
--- a/src/tor/TorManager.cpp
+++ b/src/tor/TorManager.cpp
@@ -148,7 +148,39 @@ void TorManager::start()
}
SettingsObject settings(QStringLiteral("tor"));
- if (settings.read("controlPort").isUndefined()) {
+
+ // If a control port is defined by config or environment, skip launching tor
+ if (!settings.read("controlPort").isUndefined() ||
+ !qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT"))
+ {
+ QHostAddress address(settings.read("controlAddress").toString());
+ quint16 port = (quint16)settings.read("controlPort").toInt();
+ QByteArray password = settings.read("controlPassword").toString().toLatin1();
+
+ if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_HOST"))
+ address = QHostAddress(QString::fromLatin1(qgetenv("TOR_CONTROL_HOST")));
+
+ if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) {
+ bool ok = false;
+ port = qgetenv("TOR_CONTROL_PORT").toUShort(&ok);
+ if (!ok)
+ port = 0;
+ }
+
+ if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PASSWD"))
+ password = qgetenv("TOR_CONTROL_PASSWD");
+
+ if (!port) {
+ d->setError(QStringLiteral("Invalid control port settings from environment or configuration"));
+ return;
+ }
+
+ if (address.isNull())
+ address = QHostAddress::LocalHost;
+
+ d->control->setAuthPassword(password);
+ d->control->connect(address, port);
+ } else {
// Launch a bundled Tor instance
QString executable = d->torExecutablePath();
if (executable.isEmpty()) {
@@ -185,14 +217,6 @@ void TorManager::start()
d->process->setDataDir(d->dataDir);
d->process->setDefaultTorrc(defaultTorrc);
d->process->start();
- } else {
- QHostAddress address(settings.read("controlAddress").toString());
- quint16 port = (quint16)settings.read("controlPort").toInt();
- if (address.isNull())
- address = QHostAddress::LocalHost;
-
- d->control->setAuthPassword(settings.read("controlPassword").toString().toLatin1());
- d->control->connect(address, port);
}
}
diff --git a/src/ui/LinkedText.cpp b/src/ui/LinkedText.cpp
index ecb2f15..ec9ae68 100644
--- a/src/ui/LinkedText.cpp
+++ b/src/ui/LinkedText.cpp
@@ -64,7 +64,7 @@ QString LinkedText::parsed(const QString &input)
if (start > p)
re.append(input.mid(p, start - p).toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("<br/>")));
- re.append(QStringLiteral("<a href=\"%1\">%2</a>").arg(QString::fromLatin1(url.toEncoded()).toHtmlEscaped()).arg(match.capturedRef().toString().toHtmlEscaped()));
+ re.append(QStringLiteral("<a href=\"%1\">%2</a>").arg(QString::fromLatin1(url.toEncoded()).toHtmlEscaped(), match.capturedRef().toString().toHtmlEscaped()));
p = match.capturedEnd();
}
diff --git a/src/ui/qml/ContactIDField.qml b/src/ui/qml/ContactIDField.qml
index afd8633..128111e 100644
--- a/src/ui/qml/ContactIDField.qml
+++ b/src/ui/qml/ContactIDField.qml
@@ -3,6 +3,7 @@ import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.0
import QtQuick.Layouts 1.0
import im.ricochet 1.0
+import "utils.js" as Utils
FocusScope {
id: contactId
diff --git a/src/ui/qml/NetworkSetupWizard.qml b/src/ui/qml/NetworkSetupWizard.qml
index b1c398e..794a2f2 100644
--- a/src/ui/qml/NetworkSetupWizard.qml
+++ b/src/ui/qml/NetworkSetupWizard.qml
@@ -134,15 +134,6 @@ ApplicationWindow {
}
}
- Behavior on height {
- // This window animation causes bad graphical behavior on Windows with 5.4.1
- enabled: Qt.platform.os !== "windows"
- SmoothedAnimation {
- easing.type: Easing.InOutQuad
- velocity: 1500
- }
- }
-
Action {
shortcut: StandardKey.Close
onTriggered: window.close()
diff --git a/src/ui/qml/TorConfigurationPage.qml b/src/ui/qml/TorConfigurationPage.qml
index cf285b9..5637600 100644
--- a/src/ui/qml/TorConfigurationPage.qml
+++ b/src/ui/qml/TorConfigurationPage.qml
@@ -28,7 +28,7 @@ Column {
// null value is reset
var conf = {
'Socks4Proxy': null, 'Socks5Proxy': null, 'Socks5ProxyUsername': null,
- 'Socks5ProxyPassword': null, 'HTTPProxy': null, 'HTTPProxyAuthenticator': null,
+ 'Socks5ProxyPassword': null, 'HTTPSProxy': null, 'HTTPSProxyAuthenticator': null,
'FirewallPorts': null, 'FascistFirewall': null, 'Bridge': null, 'UseBridges': null,
'DisableNetwork': '0'
}
@@ -41,10 +41,10 @@ Column {
conf['Socks5ProxyUsername'] = proxyUsername
if (proxyPassword.length > 0)
conf['Socks5ProxyPassword'] = proxyPassword
- } else if (proxyType === "http") {
- conf['HTTPProxy'] = proxyAddress + ":" + proxyPort
+ } else if (proxyType === "https") {
+ conf['HTTPSProxy'] = proxyAddress + ":" + proxyPort
if (proxyUsername.length > 0 || proxyPassword.length > 0)
- conf['HTTPProxyAuthenticator'] = proxyUsername + ":" + proxyPassword
+ conf['HTTPSProxyAuthenticator'] = proxyUsername + ":" + proxyPassword
}
if (allowedPorts.length > 0) {
@@ -60,7 +60,8 @@ Column {
var command = torControl.setConfiguration(conf)
command.finished.connect(function() {
if (command.successful) {
- torControl.saveConfiguration()
+ if (torControl.hasOwnership)
+ torControl.saveConfiguration()
window.openBootstrap()
} else
console.log("SETCONF error:", command.errorMessage)
@@ -91,7 +92,7 @@ Column {
{ "text": qsTr("None"), "type": "" },
{ "text": "SOCKS 4", "type": "socks4" },
{ "text": "SOCKS 5", "type": "socks5" },
- { "text": "HTTP", "type": "http" },
+ { "text": "HTTPS", "type": "https" },
]
textRole: "text"
property string selectedType: currentIndex >= 0 ? model[currentIndex].type : ""
diff --git a/src/ui/qml/TorPreferences.qml b/src/ui/qml/TorPreferences.qml
index 2e01e42..273b3f9 100644
--- a/src/ui/qml/TorPreferences.qml
+++ b/src/ui/qml/TorPreferences.qml
@@ -74,6 +74,7 @@ Item {
Button {
text: qsTr("Configure")
+ visible: torControl.hasOwnership
onClicked: {
var object = createDialog("NetworkSetupWizard.qml")
object.visible = true
diff --git a/src/utils/CryptoKey.cpp b/src/utils/CryptoKey.cpp
index ed99aaa..cf921e8 100644
--- a/src/utils/CryptoKey.cpp
+++ b/src/utils/CryptoKey.cpp
@@ -83,12 +83,11 @@ bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat for
BIO_free(b);
} else if (format == DER) {
const uchar *dp = reinterpret_cast<const uchar*>(data.constData());
- if (type == PrivateKey) {
- BUG() << "Parsing DER-encoded private keys is not implemented";
- return false;
- }
- key = d2i_RSAPublicKey(NULL, &dp, data.size());
+ if (type == PrivateKey)
+ key = d2i_RSAPrivateKey(NULL, &dp, data.size());
+ else
+ key = d2i_RSAPublicKey(NULL, &dp, data.size());
} else {
Q_UNREACHABLE();
}
@@ -190,6 +189,48 @@ QByteArray CryptoKey::encodedPublicKey(KeyFormat format) const
return QByteArray();
}
+QByteArray CryptoKey::encodedPrivateKey(KeyFormat format) const
+{
+ if (!isLoaded() || !isPrivate())
+ return QByteArray();
+
+ if (format == PEM) {
+ BIO *b = BIO_new(BIO_s_mem());
+
+ if (!PEM_write_bio_RSAPrivateKey(b, d->key, NULL, NULL, 0, NULL, NULL)) {
+ BUG() << "Failed to encode private key in PEM format";
+ BIO_free(b);
+ return QByteArray();
+ }
+
+ BUF_MEM *buf;
+ BIO_get_mem_ptr(b, &buf);
+
+ /* Close BIO, but don't free buf. */
+ (void)BIO_set_close(b, BIO_NOCLOSE);
+ BIO_free(b);
+
+ QByteArray re((const char *)buf->data, (int)buf->length);
+ BUF_MEM_free(buf);
+ return re;
+ } else if (format == DER) {
+ uchar *buf = NULL;
+ int len = i2d_RSAPrivateKey(d->key, &buf);
+ if (len <= 0 || !buf) {
+ BUG() << "Failed to encode private key in DER format";
+ return QByteArray();
+ }
+
+ QByteArray re((const char*)buf, len);
+ OPENSSL_free(buf);
+ return re;
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ return QByteArray();
+}
+
QString CryptoKey::torServiceID() const
{
if (!isLoaded())
diff --git a/src/utils/CryptoKey.h b/src/utils/CryptoKey.h
index ee88cfc..70ada19 100644
--- a/src/utils/CryptoKey.h
+++ b/src/utils/CryptoKey.h
@@ -62,7 +62,8 @@ public:
bool isPrivate() const;
QByteArray publicKeyDigest() const;
- QByteArray encodedPublicKey(KeyFormat format = PEM) const;
+ QByteArray encodedPublicKey(KeyFormat format) const;
+ QByteArray encodedPrivateKey(KeyFormat format) const;
QString torServiceID() const;
int bits() const;
diff --git a/src/utils/Settings.cpp b/src/utils/Settings.cpp
index 5a15e32..b20d330 100644
--- a/src/utils/Settings.cpp
+++ b/src/utils/Settings.cpp
@@ -209,7 +209,7 @@ bool SettingsFilePrivate::readFile()
}
QJsonParseError parseError;
- QJsonDocument document = QJsonDocument::fromJson(data);
+ QJsonDocument document = QJsonDocument::fromJson(data, &parseError);
if (document.isNull()) {
setError(parseError.errorString());
return false;
diff --git a/tests/tests.pro b/tests/tests.pro
index 88d92d9..31fbc40 100644
--- a/tests/tests.pro
+++ b/tests/tests.pro
@@ -1,2 +1,2 @@
TEMPLATE = subdirs
-SUBDIRS += cryptokey
+SUBDIRS += tst_cryptokey
diff --git a/tests/cryptokey/tst_cryptokey.cpp b/tests/tst_cryptokey/tst_cryptokey.cpp
similarity index 87%
rename from tests/cryptokey/tst_cryptokey.cpp
rename to tests/tst_cryptokey/tst_cryptokey.cpp
index 48888da..1b2f070 100644
--- a/tests/cryptokey/tst_cryptokey.cpp
+++ b/tests/tst_cryptokey/tst_cryptokey.cpp
@@ -41,6 +41,7 @@ private slots:
void load();
void publicKeyDigest();
void encodedPublicKey();
+ void encodedPrivateKey();
void torServiceID();
void sign();
};
@@ -137,12 +138,12 @@ void TestCryptoKey::encodedPublicKey()
CryptoKey key2;
QVERIFY(key2.loadFromData(pemEncoded, CryptoKey::PublicKey));
- QCOMPARE(key.encodedPublicKey(), key2.encodedPublicKey());
+ QCOMPARE(key.encodedPublicKey(CryptoKey::PEM), key2.encodedPublicKey(CryptoKey::PEM));
QCOMPARE(key.publicKeyDigest(), key2.publicKeyDigest());
CryptoKey key3;
QVERIFY(key3.loadFromData(derEncoded, CryptoKey::PublicKey, CryptoKey::DER));
- QCOMPARE(key.encodedPublicKey(), key3.encodedPublicKey());
+ QCOMPARE(key.encodedPublicKey(CryptoKey::DER), key3.encodedPublicKey(CryptoKey::DER));
QCOMPARE(key.publicKeyDigest(), key3.publicKeyDigest());
// Doesn't contain a private key
@@ -150,6 +151,28 @@ void TestCryptoKey::encodedPublicKey()
QVERIFY(!key4.loadFromData(pemEncoded, CryptoKey::PrivateKey));
}
+void TestCryptoKey::encodedPrivateKey()
+{
+ CryptoKey key;
+ QVERIFY(key.loadFromData(alice, CryptoKey::PrivateKey));
+
+ QByteArray pemEncoded = key.encodedPrivateKey(CryptoKey::PEM);
+ QVERIFY(pemEncoded.contains("BEGIN RSA PRIVATE KEY"));
+
+ QByteArray derEncoded = key.encodedPrivateKey(CryptoKey::DER);
+ QVERIFY(!derEncoded.isEmpty());
+
+ CryptoKey key2;
+ QVERIFY(key2.loadFromData(pemEncoded, CryptoKey::PrivateKey));
+ QCOMPARE(key.encodedPrivateKey(CryptoKey::PEM), key2.encodedPrivateKey(CryptoKey::PEM));
+ QCOMPARE(key.publicKeyDigest(), key2.publicKeyDigest());
+
+ CryptoKey key3;
+ QVERIFY(key3.loadFromData(derEncoded, CryptoKey::PrivateKey, CryptoKey::DER));
+ QCOMPARE(key.encodedPrivateKey(CryptoKey::DER), key3.encodedPrivateKey(CryptoKey::DER));
+ QCOMPARE(key.publicKeyDigest(), key3.publicKeyDigest());
+}
+
void TestCryptoKey::torServiceID()
{
CryptoKey key;
diff --git a/tests/cryptokey/tst_cryptokey.pro b/tests/tst_cryptokey/tst_cryptokey.pro
similarity index 97%
rename from tests/cryptokey/tst_cryptokey.pro
rename to tests/tst_cryptokey/tst_cryptokey.pro
index 4dfc438..0bdde39 100644
--- a/tests/cryptokey/tst_cryptokey.pro
+++ b/tests/tst_cryptokey/tst_cryptokey.pro
@@ -4,7 +4,7 @@ SOURCES += tst_cryptokey.cpp \
$${SRC}/utils/CryptoKey.cpp \
$${SRC}/utils/SecureRNG.cpp
-unix:!macx {
+unix {
!isEmpty(OPENSSLDIR) {
INCLUDEPATH += $${OPENSSLDIR}/include
LIBS += -L$${OPENSSLDIR}/lib -lcrypto
diff --git a/translation/ricochet_da.ts b/translation/ricochet_da.ts
index 8177a5e..03a1fed 100644
--- a/translation/ricochet_da.ts
+++ b/translation/ricochet_da.ts
@@ -33,7 +33,7 @@
<message>
<location filename="../src/ui/qml/ContactActions.qml" line="40"/>
<source>Open Window</source>
- <translation>Åben Vindue</translation>
+ <translation>Åbn Vindue</translation>
</message>
<message>
<location filename="../src/ui/qml/ContactActions.qml" line="44"/>
@@ -76,7 +76,7 @@
<message>
<location filename="../src/ui/qml/ContactIDField.qml" line="92"/>
<source>Copy</source>
- <translation>Kopier</translation>
+ <translation>Kopiér</translation>
</message>
</context>
<context>
@@ -104,7 +104,7 @@
<message>
<location filename="../src/ui/qml/ContactList.qml" line="80"/>
<source>Outdated</source>
- <translation>Uddateret</translation>
+ <translation>Forældet</translation>
</message>
</context>
<context>
@@ -132,7 +132,7 @@
<message>
<location filename="../src/ui/qml/ContactPreferences.qml" line="127"/>
<source>Delivered</source>
- <translation>Modtaget</translation>
+ <translation>Afleveret</translation>
</message>
<message>
<location filename="../src/ui/qml/ContactPreferences.qml" line="128"/>
@@ -158,7 +158,7 @@
<message>
<location filename="../src/ui/qml/ContactPreferences.qml" line="140"/>
<source>Response:</source>
- <translation>Tilbagesvar:</translation>
+ <translation>Svar:</translation>
</message>
<message>
<location filename="../src/ui/qml/ContactPreferences.qml" line="163"/>
@@ -212,12 +212,12 @@
<message>
<location filename="../src/ui/qml/GeneralPreferences.qml" line="12"/>
<source>Use a single window for conversations</source>
- <translation>Brug et samlet vindue til samtaler</translation>
+ <translation>Hold samtaler i ét vindue</translation>
</message>
<message>
<location filename="../src/ui/qml/GeneralPreferences.qml" line="20"/>
<source>Open links in default browser without prompting</source>
- <translation>Åben links i browser uden at spørge</translation>
+ <translation>Åbn links i browser uden at spørge</translation>
</message>
<message>
<location filename="../src/ui/qml/GeneralPreferences.qml" line="28"/>
@@ -311,12 +311,12 @@
<message>
<location filename="../src/ui/qml/MessageDelegate.qml" line="140"/>
<source>Open with Browser</source>
- <translation>Åben med Browser</translation>
+ <translation>Åbn i browser</translation>
</message>
<message>
<location filename="../src/ui/qml/MessageDelegate.qml" line="154"/>
<source>Add as Contact</source>
- <translation>Tilføj som Kontaktperson</translation>
+ <translation>Tilføj som kontaktperson</translation>
</message>
<message>
<location filename="../src/ui/qml/MessageDelegate.qml" line="166"/>
@@ -345,7 +345,7 @@
<message>
<location filename="../src/ui/qml/MessageDialogWrapper.qml" line="11"/>
<source>This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request.</source>
- <translation>Denne kontaktperson vil ikke længere være i stand til at sende dig beskeder, og vil blive notificeret om fjernelsen. Kontaktpersonen kan vælge at sende dig en ny anmodning.</translation>
+ <translation>Denne kontaktperson vil ikke længere være i stand til at sende dig beskeder, og vil blive notificeret om fjernelsen. Kontaktpersonen kan vælge at sende dig en ny kontaktanmodning.</translation>
</message>
</context>
<context>
@@ -363,12 +363,12 @@
<message>
<location filename="../src/ui/qml/NetworkSetupWizard.qml" line="124"/>
<source>This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings.</source>
- <translation>Denne computers internetadgang er censureret, filtreret eller proxied. Jeg har brug for at konfigurere netværksindstillinger.</translation>
+ <translation>Denne computers internetadgang er censureret, filtreret eller bag en proxy. Jeg har brug for at konfigurere netværksindstillinger.</translation>
</message>
<message>
<location filename="../src/ui/qml/NetworkSetupWizard.qml" line="131"/>
<source>Configure</source>
- <translation>Konfigurer</translation>
+ <translation>Konfigurér</translation>
</message>
</context>
<context>
@@ -376,7 +376,7 @@
<message>
<location filename="../src/ui/qml/OfflineStateItem.qml" line="107"/>
<source>Configure</source>
- <translation>Konfigurer</translation>
+ <translation>Konfigurér</translation>
</message>
<message>
<location filename="../src/ui/qml/OfflineStateItem.qml" line="115"/>
@@ -386,13 +386,13 @@
<message>
<location filename="../src/ui/qml/OfflineStateItem.qml" line="143"/>
<source>Connection failed</source>
- <translation>Forbindelse mislykkedes</translation>
+ <translation>Forbindelsen mislykkedes</translation>
</message>
<message>
<location filename="../src/ui/qml/OfflineStateItem.qml" line="169"/>
<source>Connecting…</source>
<extracomment>\u2026 is ellipsis</extracomment>
- <translation>Forbinder...</translation>
+ <translation>Forbinder…</translation>
</message>
</context>
<context>
@@ -400,7 +400,7 @@
<message>
<location filename="../src/ui/qml/OpenBrowserDialog.qml" line="40"/>
<source><b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead.</source>
- <translation><b>Advarsel!</b> Ved at åbne links med din browser vil du forøge din sikkerhed og anonymitet.<br><br>Du kan <a href='.'>kopiere adressen til clipboardet</a> i stedet.</translation>
+ <translation><b>Advarsel!</b> Ved at åbne links med din browser kan du forringe din sikkerhed og anonymitet.<br><br>Du kan <a href='.'>kopiere adressen til clipboardet</a> i stedet.</translation>
</message>
<message>
<location filename="../src/ui/qml/OpenBrowserDialog.qml" line="59"/>
@@ -415,12 +415,12 @@
<message>
<location filename="../src/ui/qml/OpenBrowserDialog.qml" line="72"/>
<source>Open Browser</source>
- <translation>Åben Browser</translation>
+ <translation>Åbn Browser</translation>
</message>
<message>
<location filename="../src/ui/qml/OpenBrowserDialog.qml" line="84"/>
<source>Cancel</source>
- <translation>Annuller</translation>
+ <translation>Annullér</translation>
</message>
</context>
<context>
@@ -483,7 +483,7 @@
<location filename="../src/ui/qml/TorBootstrapStatus.qml" line="17"/>
<source>Connecting to the Tor network…</source>
<extracomment>\u2026 is ellipsis</extracomment>
- <translation>Forbinder til Tor-netværket...</translation>
+ <translation>Forbinder til Tor-netværket…</translation>
</message>
<message>
<location filename="../src/ui/qml/TorBootstrapStatus.qml" line="51"/>
@@ -578,7 +578,7 @@
<message>
<location filename="../src/ui/qml/TorConfigurationPage.qml" line="197"/>
<source>Enter one or more bridge relays (one per line):</source>
- <translation>Indtast et eller flere bridge relæer (et per linje):</translation>
+ <translation>Indsæt én eller flere bridgerelæer (én per linje):</translation>
</message>
<message>
<location filename="../src/ui/qml/TorConfigurationPage.qml" line="212"/>
@@ -672,13 +672,13 @@
<location filename="../src/ui/qml/TorStateWidget.qml" line="22"/>
<source>Connecting…</source>
<extracomment>\u2026 is ellipsis</extracomment>
- <translation>Forbinder...</translation>
+ <translation>Forbinder…</translation>
</message>
<message>
<location filename="../src/ui/qml/TorStateWidget.qml" line="25"/>
<source>Connecting… (%1%)</source>
<extracomment>%1 is progress percentage, e.g. 100</extracomment>
- <translation>Forbinder... (%1)</translation>
+ <translation>Forbinder… (%1)</translation>
</message>
<message>
<location filename="../src/ui/qml/TorStateWidget.qml" line="32"/>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/ricochet-im.git
More information about the Pkg-privacy-commits
mailing list