[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