updated tor manager/control to save/parse keys in new format

This commit is contained in:
csoler 2020-10-05 21:53:51 +02:00
parent 788bd81b41
commit 210518db80
7 changed files with 144 additions and 57 deletions

View file

@ -53,10 +53,10 @@ QByteArray AddOnionCommand::build()
QByteArray out("ADD_ONION"); QByteArray out("ADD_ONION");
if (m_service->privateKey().isLoaded()) { if (m_service->privateKey().isLoaded()) {
out += " RSA1024:"; out += " ";
out += m_service->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64(); out += m_service->privateKey().bytes();
} else { } else {
out += " NEW:RSA1024"; out += " NEW:RSA1024"; // this is v2. For v3, use NEW:BEST, or NEW:ED25519-V3
} }
foreach (const HiddenService::Target &target, m_service->targets()) { foreach (const HiddenService::Target &target, m_service->targets()) {
@ -80,12 +80,21 @@ void AddOnionCommand::onReply(int statusCode, const QByteArray &data)
return; return;
} }
const QByteArray keyPrefix("PrivateKey=RSA1024:"); const QByteArray keyPrefix("PrivateKey=");
const QByteArray sidPrefix("ServiceID=");
if(data.startsWith("ServiceID=")){
QByteArray service_id = data.mid(sidPrefix.size());
m_service->setServiceId(service_id);
}
if (data.startsWith(keyPrefix)) { if (data.startsWith(keyPrefix)) {
QByteArray keyData(QByteArray::fromBase64(data.mid(keyPrefix.size())));
QByteArray keyData(data.mid(keyPrefix.size()));
CryptoKey key; CryptoKey key;
if (!key.loadFromData(keyData, CryptoKey::PrivateKey, CryptoKey::DER)) {
m_errorMessage = QStringLiteral("Key decoding failed"); if (!key.loadFromTorMessage(keyData)) {
m_errorMessage = QStringLiteral("Key structure check failed");
return; return;
} }

View file

@ -30,11 +30,14 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <iostream>
#include "CryptoKey.h" #include "CryptoKey.h"
#include "SecureRNG.h" #include "SecureRNG.h"
#include "Useful.h" #include "Useful.h"
#include <QtDebug> #include <QtDebug>
#include <QFile> #include <QFile>
#include <QByteArray>
#include <openssl/bn.h> #include <openssl/bn.h>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/pem.h> #include <openssl/pem.h>
@ -48,8 +51,10 @@ void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
#define RSA_bits(o) (BN_num_bits((o)->n)) #define RSA_bits(o) (BN_num_bits((o)->n))
#endif #endif
#ifdef TO_REMOVE
void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen); void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen);
bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen); bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen);
#endif
CryptoKey::CryptoKey() CryptoKey::CryptoKey()
{ {
@ -60,6 +65,7 @@ CryptoKey::~CryptoKey()
clear(); clear();
} }
#ifdef TO_REMOVE
CryptoKey::Data::~Data() CryptoKey::Data::~Data()
{ {
if (key) if (key)
@ -68,12 +74,14 @@ CryptoKey::Data::~Data()
key = 0; key = 0;
} }
} }
#endif
void CryptoKey::clear() void CryptoKey::clear()
{ {
d = 0; key_data.clear();
} }
#ifdef TO_REMOVE
bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat format) bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat format)
{ {
RSA *key = NULL; RSA *key = NULL;
@ -110,23 +118,85 @@ bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat for
d = new Data(key); d = new Data(key);
return true; return true;
} }
#endif
bool CryptoKey::loadFromFile(const QString &path, KeyType type, KeyFormat format) bool CryptoKey::loadFromFile(const QString& path)
{ {
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly))
{ {
qWarning() << "Failed to open" << (type == PrivateKey ? "private" : "public") << "key from" qWarning() << "Failed to open Tor key file " << path << ": " << file.errorString();
<< path << "-" << file.errorString();
return false; return false;
} }
QByteArray data = file.readAll(); QByteArray data = file.readAll();
file.close(); file.close();
return loadFromData(data, type, format); if(data.startsWith("-----"))
std::cerr << "Note: Reading/converting Tor v2 key format." << std::endl;
// This to be compliant with old format. New format is oblivious to the type of key so we dont need a header
data = data.replace("-----BEGIN RSA PRIVATE KEY-----",nullptr);
data = data.replace("-----END RSA PRIVATE KEY-----",nullptr);
std::cerr << "Have read the following key: " << std::endl;
std::cerr << QString(data).toStdString() << std::endl;
key_data = data;
return true;
} }
bool CryptoKey::loadFromTorMessage(const QByteArray& b)
{
// note: We should probably check the structure a bit more, for security.
std::cerr << "Loading new key:" << std::endl;
if(b.startsWith("RSA1024"))
std::cerr << " type: RSA-1024 (Tor v2)" << std::endl;
else if(b.startsWith("ED25529-V3"))
std::cerr << " type: ED25519-V3 (Tor v3)" << std::endl;
else
{
std::cerr << " unknown type \"" << b.left(b.indexOf(':')).toStdString() << "\"" << std::endl;
return false;
}
key_data = b;
return true;
}
/* Cryptographic hash of a password as expected by Tor's HashedControlPassword */
QByteArray torControlHashedPassword(const QByteArray &password)
{
QByteArray salt = SecureRNG::random(8);
if (salt.isNull())
return QByteArray();
int count = ((quint32)16 + (96 & 15)) << ((96 >> 4) + 6);
SHA_CTX hash;
SHA1_Init(&hash);
QByteArray tmp = salt + password;
while (count)
{
int c = qMin(count, tmp.size());
SHA1_Update(&hash, reinterpret_cast<const void*>(tmp.constData()), c);
count -= c;
}
unsigned char md[20];
SHA1_Final(md, &hash);
/* 60 is the hex-encoded value of 96, which is a constant used by Tor's algorithm. */
return QByteArray("16:") + salt.toHex().toUpper() + QByteArray("60") +
QByteArray::fromRawData(reinterpret_cast<const char*>(md), 20).toHex().toUpper();
}
#ifdef TO_REMOVE
bool CryptoKey::isPrivate() const bool CryptoKey::isPrivate() const
{ {
if (!isLoaded()) { if (!isLoaded()) {
@ -326,34 +396,6 @@ bool CryptoKey::verifySHA256(const QByteArray &digest, QByteArray signature) con
return true; return true;
} }
/* Cryptographic hash of a password as expected by Tor's HashedControlPassword */
QByteArray torControlHashedPassword(const QByteArray &password)
{
QByteArray salt = SecureRNG::random(8);
if (salt.isNull())
return QByteArray();
int count = ((quint32)16 + (96 & 15)) << ((96 >> 4) + 6);
SHA_CTX hash;
SHA1_Init(&hash);
QByteArray tmp = salt + password;
while (count)
{
int c = qMin(count, tmp.size());
SHA1_Update(&hash, reinterpret_cast<const void*>(tmp.constData()), c);
count -= c;
}
unsigned char md[20];
SHA1_Final(md, &hash);
/* 60 is the hex-encoded value of 96, which is a constant used by Tor's algorithm. */
return QByteArray("16:") + salt.toHex().toUpper() + QByteArray("60") +
QByteArray::fromRawData(reinterpret_cast<const char*>(md), 20).toHex().toUpper();
}
/* Copyright (c) 2001-2004, Roger Dingledine /* Copyright (c) 2001-2004, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
* Copyright (c) 2007-2010, The Tor Project, Inc. * Copyright (c) 2007-2010, The Tor Project, Inc.
@ -475,3 +517,5 @@ bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srcle
delete[] tmp; delete[] tmp;
return true; return true;
} }
#endif

View file

@ -51,14 +51,19 @@ public:
}; };
CryptoKey(); CryptoKey();
CryptoKey(const CryptoKey &other) : d(other.d) { }
~CryptoKey(); ~CryptoKey();
#ifdef TO_REMOVE
bool loadFromData(const QByteArray &data, KeyType type, KeyFormat format = PEM); bool loadFromData(const QByteArray &data, KeyType type, KeyFormat format = PEM);
bool loadFromFile(const QString &path, KeyType type, KeyFormat format = PEM); bool loadFromFile(const QString &path, KeyType type, KeyFormat format = PEM);
#endif
bool loadFromFile(const QString &path);
void clear(); void clear();
bool isLoaded() const { return d.data() && d->key != 0; } const QByteArray bytes() const { return key_data; }
bool loadFromTorMessage(const QByteArray& b);
bool isLoaded() const { return !key_data.isNull(); }
#ifdef TO_REMOVE
bool isPrivate() const; bool isPrivate() const;
QByteArray publicKeyDigest() const; QByteArray publicKeyDigest() const;
@ -76,8 +81,10 @@ public:
QByteArray signSHA256(const QByteArray &digest) const; QByteArray signSHA256(const QByteArray &digest) const;
// Verify a signature as per signSHA256 // Verify a signature as per signSHA256
bool verifySHA256(const QByteArray &digest, QByteArray signature) const; bool verifySHA256(const QByteArray &digest, QByteArray signature) const;
#endif
private: private:
#ifdef TO_REMOVE
struct Data : public QSharedData struct Data : public QSharedData
{ {
typedef struct rsa_st RSA; typedef struct rsa_st RSA;
@ -86,8 +93,12 @@ private:
Data(RSA *k = 0) : key(k) { } Data(RSA *k = 0) : key(k) { }
~Data(); ~Data();
}; };
#endif
QByteArray key_data;
#ifdef TO_REMOVE
QExplicitlySharedDataPointer<Data> d; QExplicitlySharedDataPointer<Data> d;
#endif
}; };
QByteArray torControlHashedPassword(const QByteArray &password); QByteArray torControlHashedPassword(const QByteArray &password);

View file

@ -90,6 +90,13 @@ void HiddenService::addTarget(quint16 servicePort, QHostAddress targetAddress, q
m_targets.append(t); m_targets.append(t);
} }
void HiddenService::setServiceId(const QByteArray& sid)
{
m_service_id = sid;
m_hostname = sid + ".onion";
emit hostnameChanged();
}
void HiddenService::setPrivateKey(const CryptoKey &key) void HiddenService::setPrivateKey(const CryptoKey &key)
{ {
if (m_privateKey.isLoaded()) { if (m_privateKey.isLoaded()) {
@ -97,13 +104,15 @@ void HiddenService::setPrivateKey(const CryptoKey &key)
return; return;
} }
#ifdef TO_REMOVE
if (!key.isPrivate()) { if (!key.isPrivate()) {
BUG() << "Cannot create a hidden service with a public key"; BUG() << "Cannot create a hidden service with a public key";
return; return;
} }
#endif
m_privateKey = key; m_privateKey = key;
m_hostname = m_privateKey.torServiceID() + QStringLiteral(".onion");
emit privateKeyChanged(); emit privateKeyChanged();
} }
@ -112,13 +121,13 @@ void HiddenService::loadPrivateKey()
if (m_privateKey.isLoaded() || m_dataPath.isEmpty()) if (m_privateKey.isLoaded() || m_dataPath.isEmpty())
return; return;
bool ok = m_privateKey.loadFromFile(m_dataPath + QLatin1String("/private_key"), CryptoKey::PrivateKey); bool ok = m_privateKey.loadFromFile(m_dataPath + QLatin1String("/private_key"));
if (!ok) { if (!ok) {
qWarning() << "Failed to load hidden service key"; qWarning() << "Failed to load hidden service key";
return; return;
} }
m_hostname = m_privateKey.torServiceID();
emit privateKeyChanged(); emit privateKeyChanged();
} }

View file

@ -70,11 +70,13 @@ public:
Status status() const { return m_status; } Status status() const { return m_status; }
const QString &hostname() const { return m_hostname; } const QString& hostname() const { return m_hostname; }
const QString &dataPath() const { return m_dataPath; } const QString serviceId() const { return QString(m_service_id); }
const QString& dataPath() const { return m_dataPath; }
CryptoKey privateKey() { return m_privateKey; } CryptoKey privateKey() { return m_privateKey; }
void setPrivateKey(const CryptoKey &privateKey); void setPrivateKey(const CryptoKey &privateKey);
void setServiceId(const QByteArray& sid);
const QList<Target> &targets() const { return m_targets; } const QList<Target> &targets() const { return m_targets; }
void addTarget(const Target &target); void addTarget(const Target &target);
@ -84,6 +86,7 @@ signals:
void statusChanged(int newStatus, int oldStatus); void statusChanged(int newStatus, int oldStatus);
void serviceOnline(); void serviceOnline();
void privateKeyChanged(); void privateKeyChanged();
void hostnameChanged();
private slots: private slots:
void servicePublished(); void servicePublished();
@ -94,6 +97,7 @@ private:
QString m_hostname; QString m_hostname;
Status m_status; Status m_status;
CryptoKey m_privateKey; CryptoKey m_privateKey;
QByteArray m_service_id;
void loadPrivateKey(); void loadPrivateKey();
void setStatus(Status newStatus); void setStatus(Status newStatus);

View file

@ -174,17 +174,16 @@ bool TorManager::setupHiddenService()
std::cerr << "Attempting to load key from legacy filesystem format in " << legacyDir.toStdString() << std::endl; std::cerr << "Attempting to load key from legacy filesystem format in " << legacyDir.toStdString() << std::endl;
CryptoKey key; CryptoKey key;
if (!key.loadFromFile(legacyDir + QLatin1String("/private_key"), CryptoKey::PrivateKey)) if (!key.loadFromFile(legacyDir + QLatin1String("/private_key")))
{ {
qWarning() << "Cannot load legacy format key from" << legacyDir << "for conversion"; qWarning() << "Cannot load legacy format key from" << legacyDir << "for conversion";
return false; return false;
} }
keyData = QString::fromLatin1(key.encodedPrivateKey(CryptoKey::DER).toBase64());
d->hiddenService = new Tor::HiddenService(key, legacyDir, this); d->hiddenService = new Tor::HiddenService(key, legacyDir, this);
std::cerr << "Got key from legacy dir: " << std::endl; std::cerr << "Got key from legacy dir: " << std::endl;
std::cerr << keyData.toStdString() << std::endl; std::cerr << key.bytes().toStdString() << std::endl;
} }
else else
{ {
@ -193,6 +192,7 @@ bool TorManager::setupHiddenService()
std::cerr << "Creating new hidden service." << std::endl; std::cerr << "Creating new hidden service." << std::endl;
connect(d->hiddenService, SIGNAL(privateKeyChanged()), this, SLOT(hiddenServicePrivateKeyChanged())) ; connect(d->hiddenService, SIGNAL(privateKeyChanged()), this, SLOT(hiddenServicePrivateKeyChanged())) ;
connect(d->hiddenService, SIGNAL(hostnameChanged()), this, SLOT(hiddenServiceHostnameChanged())) ;
} }
Q_ASSERT(d->hiddenService); Q_ASSERT(d->hiddenService);
@ -230,31 +230,40 @@ void TorManager::hiddenServiceStatusChanged(int old_status,int new_status)
void TorManager::hiddenServicePrivateKeyChanged() void TorManager::hiddenServicePrivateKeyChanged()
{ {
QString key = QString::fromLatin1(d->hiddenService->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64()); QString key = QString::fromLatin1(d->hiddenService->privateKey().bytes());
QFile outfile(d->hiddenServiceDir + QLatin1String("/private_key")) ; QFile outfile(d->hiddenServiceDir + QLatin1String("/private_key")) ;
outfile.open( QIODevice::WriteOnly | QIODevice::Text ); outfile.open( QIODevice::WriteOnly | QIODevice::Text );
QTextStream s(&outfile); QTextStream s(&outfile);
#ifdef TO_REMOVE
s << "-----BEGIN RSA PRIVATE KEY-----" << endl; s << "-----BEGIN RSA PRIVATE KEY-----" << endl;
for(uint32_t i=0;i<key.length();i+=64) for(int i=0;i<key.length();i+=64)
s << key.mid(i,64) << endl ; s << key.mid(i,64) << endl ;
s << "-----END RSA PRIVATE KEY-----" << endl; s << "-----END RSA PRIVATE KEY-----" << endl;
#endif
s << key ;
outfile.close(); outfile.close();
std::cerr << "Hidden service private key changed!" << std::endl; std::cerr << "Hidden service private key changed!" << std::endl;
std::cerr << key.toStdString() << std::endl; std::cerr << key.toStdString() << std::endl;
}
QFile outfile2(d->hiddenServiceDir + QLatin1String("/hostname")) ; void TorManager::hiddenServiceHostnameChanged()
{
QFile outfile2(d->hiddenServiceDir + QLatin1String("/hostname")) ;
outfile2.open( QIODevice::WriteOnly | QIODevice::Text ); outfile2.open( QIODevice::WriteOnly | QIODevice::Text );
QTextStream t(&outfile2); QTextStream t(&outfile2);
t << d->hiddenService->hostname() << endl; QString hostname(d->hiddenService->hostname());
outfile2.close(); t << hostname << endl;
outfile2.close();
std::cerr << "Hidden service hostname changed: " << hostname.toStdString() << std::endl;
} }
bool TorManager::configurationNeeded() const bool TorManager::configurationNeeded() const
@ -381,7 +390,7 @@ bool TorManager::getHiddenServiceInfo(QString& service_id,QString& service_onion
for(auto it(hidden_services.begin());it!=hidden_services.end();++it) for(auto it(hidden_services.begin());it!=hidden_services.end();++it)
{ {
service_onion_address = (*it)->hostname(); service_onion_address = (*it)->hostname();
service_id = (*it)->privateKey().torServiceID(); service_id = (*it)->serviceId();
for(auto it2((*it)->targets().begin());it2!=(*it)->targets().end();++it2) for(auto it2((*it)->targets().begin());it2!=(*it)->targets().end();++it2)
{ {

View file

@ -93,7 +93,8 @@ public slots:
private slots: private slots:
void hiddenServicePrivateKeyChanged(); void hiddenServicePrivateKeyChanged();
void hiddenServiceStatusChanged(int old_status,int new_status); void hiddenServiceHostnameChanged();
void hiddenServiceStatusChanged(int old_status,int new_status);
signals: signals:
void configurationNeededChanged(); void configurationNeededChanged();