mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
475 lines
14 KiB
C++
475 lines
14 KiB
C++
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
|
// SPDX-License-Identifier: MIT
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "securedht.h"
|
|
#include "rng.h"
|
|
|
|
#include "default_types.h"
|
|
|
|
extern "C" {
|
|
#include <gnutls/gnutls.h>
|
|
#include <gnutls/abstract.h>
|
|
#include <gnutls/x509.h>
|
|
}
|
|
|
|
#include <random>
|
|
|
|
namespace dht {
|
|
|
|
SecureDht::SecureDht(std::unique_ptr<DhtInterface> dht,
|
|
SecureDht::Config conf,
|
|
IdentityAnnouncedCb iacb,
|
|
const std::shared_ptr<Logger>& l)
|
|
: DhtInterface(l)
|
|
, dht_(std::move(dht))
|
|
, key_(conf.id.first)
|
|
, certificate_(conf.id.second)
|
|
, enableCache_(conf.cert_cache_all)
|
|
{
|
|
if (!dht_)
|
|
return;
|
|
for (const auto& type : DEFAULT_TYPES)
|
|
registerType(type);
|
|
|
|
for (const auto& type : DEFAULT_INSECURE_TYPES)
|
|
registerInsecureType(type);
|
|
|
|
registerInsecureType(CERTIFICATE_TYPE);
|
|
|
|
if (certificate_) {
|
|
auto certLongId = certificate_->getLongId();
|
|
if (key_ and (certLongId != key_->getPublicKey().getLongId()))
|
|
throw DhtException("SecureDht: provided certificate doesn't match private key.");
|
|
|
|
dht_->addOnConnectedCallback([this, certLongId, cb = std::move(iacb)]() mutable {
|
|
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, std::move(cb), {}, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
SecureDht::~SecureDht()
|
|
{
|
|
dht_.reset();
|
|
}
|
|
|
|
ValueType
|
|
SecureDht::secureType(ValueType&& type)
|
|
{
|
|
type.storePolicy =
|
|
[sp = std::move(type.storePolicy)](InfoHash id, Sp<Value>& v, const InfoHash& nid, const SockAddr& a) {
|
|
if (v->isSigned())
|
|
return v->checkSignature();
|
|
return sp(id, v, nid, a);
|
|
};
|
|
type.editPolicy = [l = logger_, ep = std::move(type.editPolicy)](InfoHash id,
|
|
const Sp<Value>& o,
|
|
Sp<Value>& n,
|
|
const InfoHash& nid,
|
|
const SockAddr& a) {
|
|
if (not o->isSigned())
|
|
return ep(id, o, n, nid, a);
|
|
if (*o->owner != *n->owner or not n->isSigned()) {
|
|
if (l)
|
|
l->w("Edition forbidden: not signed or incorrect owner.");
|
|
return false;
|
|
}
|
|
if (not n->checkSignature()) {
|
|
if (l)
|
|
l->w("Edition forbidden: signature verification failed.");
|
|
return false;
|
|
}
|
|
if (o->seq == n->seq) {
|
|
// If the data is exactly the same,
|
|
// it can be reannounced, possibly by someone else.
|
|
if (o->getToSign() != n->getToSign()) {
|
|
if (l)
|
|
l->w("Edition forbidden: sequence number must be increasing.");
|
|
return false;
|
|
}
|
|
} else if (n->seq < o->seq)
|
|
return false;
|
|
return true;
|
|
};
|
|
return type;
|
|
}
|
|
|
|
Sp<crypto::Certificate>
|
|
SecureDht::getCertificate(const PkId& node) const
|
|
{
|
|
if (node == getLongId())
|
|
return certificate_;
|
|
auto it = nodesCertificates_.find(node);
|
|
if (it == nodesCertificates_.end())
|
|
return nullptr;
|
|
else
|
|
return it->second;
|
|
}
|
|
|
|
Sp<crypto::PublicKey>
|
|
SecureDht::getPublicKey(const PkId& node) const
|
|
{
|
|
if (node == getLongId())
|
|
return certificate_->getSharedPublicKey();
|
|
auto it = nodesPubKeys_.find(node);
|
|
if (it == nodesPubKeys_.end())
|
|
return nullptr;
|
|
else
|
|
return it->second;
|
|
}
|
|
|
|
Sp<crypto::Certificate>
|
|
SecureDht::registerCertificate(const InfoHash& node, const Blob& data)
|
|
{
|
|
Sp<crypto::Certificate> crt;
|
|
try {
|
|
crt = std::make_shared<crypto::Certificate>(data);
|
|
} catch (const std::exception& e) {
|
|
return nullptr;
|
|
}
|
|
InfoHash h = crt->getPublicKey().getId();
|
|
if (node == h) {
|
|
if (logger_)
|
|
logger_->debug("Registering certificate for {}", h.toString());
|
|
registerCertificate(crt);
|
|
return crt;
|
|
} else {
|
|
if (logger_)
|
|
logger_->warn("Certificate {} for node {} does not match node id !", h.toString(), node.toString());
|
|
return nullptr;
|
|
}
|
|
}
|
|
Sp<crypto::Certificate>
|
|
SecureDht::registerCertificate(const PkId& node, const Blob& data)
|
|
{
|
|
Sp<crypto::Certificate> crt;
|
|
try {
|
|
crt = std::make_shared<crypto::Certificate>(data);
|
|
} catch (const std::exception& e) {
|
|
return nullptr;
|
|
}
|
|
auto h = crt->getPublicKey().getLongId();
|
|
if (node == h) {
|
|
if (logger_)
|
|
logger_->debug("Registering certificate for {}", h.toString());
|
|
registerCertificate(crt);
|
|
return crt;
|
|
} else {
|
|
if (logger_)
|
|
logger_->warn("Certificate {} for node {} does not match node id !", h.toString(), node.toString());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
SecureDht::registerCertificate(const Sp<crypto::Certificate>& cert)
|
|
{
|
|
if (cert) {
|
|
nodesCertificates_[cert->getLongId()] = cert;
|
|
}
|
|
}
|
|
|
|
void
|
|
SecureDht::findCertificate(const PkId& node, const std::function<void(const Sp<crypto::Certificate>)>& cb)
|
|
{
|
|
Sp<crypto::Certificate> b = getCertificate(node);
|
|
if (b && *b) {
|
|
if (logger_)
|
|
logger_->debug("Using certificate from cache for {}", node.to_c_str());
|
|
if (cb)
|
|
cb(b);
|
|
return;
|
|
}
|
|
|
|
if (localQueryMethod_) {
|
|
auto res = localQueryMethod_(node);
|
|
if (not res.empty()) {
|
|
if (logger_)
|
|
logger_->debug("Registering certificate from local store for {}", node.to_c_str());
|
|
nodesCertificates_.emplace(node, res.front());
|
|
if (cb)
|
|
cb(res.front());
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto found = std::make_shared<bool>(false);
|
|
dht_->get(
|
|
InfoHash::get(node),
|
|
[cb, node, found, this](const std::vector<Sp<Value>>& vals) {
|
|
for (const auto& v : vals) {
|
|
if (auto cert = registerCertificate(node, v->data)) {
|
|
*found = true;
|
|
if (logger_)
|
|
logger_->debug("Found certificate for {}", node.to_c_str());
|
|
if (cb)
|
|
cb(cert);
|
|
return false;
|
|
}
|
|
}
|
|
return !*found;
|
|
},
|
|
[cb, found](bool) {
|
|
if (!*found and cb)
|
|
cb(nullptr);
|
|
},
|
|
Value::TypeFilter(CERTIFICATE_TYPE));
|
|
}
|
|
|
|
void
|
|
SecureDht::findPublicKey(const PkId& node, const std::function<void(const Sp<crypto::PublicKey>)>& cb)
|
|
{
|
|
auto pk = getPublicKey(node);
|
|
if (pk && *pk) {
|
|
if (logger_)
|
|
logger_->debug("Found public key from cache for {}", node.to_c_str());
|
|
if (cb)
|
|
cb(pk);
|
|
return;
|
|
}
|
|
findCertificate(node, [=](const Sp<crypto::Certificate>& crt) {
|
|
if (crt && *crt) {
|
|
auto pk = crt->getSharedPublicKey();
|
|
if (*pk) {
|
|
nodesPubKeys_[pk->getLongId()] = pk;
|
|
if (cb)
|
|
cb(pk);
|
|
return;
|
|
}
|
|
}
|
|
if (cb)
|
|
cb(nullptr);
|
|
});
|
|
}
|
|
|
|
Sp<Value>
|
|
SecureDht::checkValue(const Sp<Value>& v)
|
|
{
|
|
// Decrypt encrypted values
|
|
if (v->isEncrypted()) {
|
|
if (not key_) {
|
|
#ifdef OPENDHT_PROXY_SERVER
|
|
if (forward_all_) // We are currently a proxy, send messages to clients.
|
|
return v;
|
|
#endif
|
|
return {};
|
|
}
|
|
try {
|
|
auto isDecrypted = v->isDecrypted();
|
|
if (auto decrypted_val = v->decrypt(*key_)) {
|
|
auto cacheValue = not isDecrypted and decrypted_val->owner;
|
|
if (cacheValue) {
|
|
nodesPubKeys_[decrypted_val->owner->getLongId()] = decrypted_val->owner;
|
|
}
|
|
return decrypted_val;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
if (logger_)
|
|
logger_->warn("Could not decrypt value {} : {}", v->toString(), e.what());
|
|
}
|
|
}
|
|
// Check signed values
|
|
else if (v->isSigned()) {
|
|
auto cacheValue = not v->isSignatureChecked() and enableCache_ and v->owner;
|
|
if (v->checkSignature()) {
|
|
if (cacheValue) {
|
|
nodesPubKeys_[v->owner->getLongId()] = v->owner;
|
|
}
|
|
return v;
|
|
} else if (logger_)
|
|
logger_->warn("Signature verification failed for {}", v->toString());
|
|
}
|
|
// Forward normal values
|
|
else {
|
|
return v;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ValueCallback
|
|
SecureDht::getCallbackFilter(const ValueCallback& cb, Value::Filter&& filter)
|
|
{
|
|
return [=](const std::vector<Sp<Value>>& values, bool expired) {
|
|
std::vector<Sp<Value>> tmpvals {};
|
|
if (not filter)
|
|
tmpvals.reserve(values.size());
|
|
for (const auto& v : values) {
|
|
if (auto nv = checkValue(v))
|
|
if (not filter or filter(*nv))
|
|
tmpvals.emplace_back(std::move(nv));
|
|
}
|
|
if (cb and not tmpvals.empty())
|
|
return cb(tmpvals, expired);
|
|
return true;
|
|
};
|
|
}
|
|
|
|
GetCallback
|
|
SecureDht::getCallbackFilter(const GetCallback& cb, Value::Filter&& filter)
|
|
{
|
|
return [this, cb, filter = std::move(filter)](const std::vector<Sp<Value>>& values) {
|
|
std::vector<Sp<Value>> tmpvals {};
|
|
if (not filter)
|
|
tmpvals.reserve(values.size());
|
|
for (const auto& v : values) {
|
|
if (auto nv = checkValue(v))
|
|
if (not filter or filter(*nv))
|
|
tmpvals.emplace_back(std::move(nv));
|
|
}
|
|
if (cb and not tmpvals.empty())
|
|
return cb(tmpvals);
|
|
return true;
|
|
};
|
|
}
|
|
|
|
void
|
|
SecureDht::get(const InfoHash& id, GetCallback cb, DoneCallback donecb, Value::Filter&& f, Where&& w)
|
|
{
|
|
dht_->get(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), donecb, {}, std::forward<Where>(w));
|
|
}
|
|
|
|
size_t
|
|
SecureDht::listen(const InfoHash& id, ValueCallback cb, Value::Filter f, Where w)
|
|
{
|
|
return dht_->listen(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), {}, std::forward<Where>(w));
|
|
}
|
|
|
|
size_t
|
|
SecureDht::listen(const InfoHash& id, GetCallback cb, Value::Filter f, Where w)
|
|
{
|
|
return dht_->listen(id, getCallbackFilter(cb, std::forward<Value::Filter>(f)), {}, std::forward<Where>(w));
|
|
}
|
|
|
|
void
|
|
SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback, bool permanent)
|
|
{
|
|
if (not key_ or not hash or not val) {
|
|
if (callback)
|
|
callback(false, {});
|
|
return;
|
|
}
|
|
if (val->id == Value::INVALID_ID) {
|
|
std::random_device rdev;
|
|
std::uniform_int_distribution<Value::Id> rand_id;
|
|
val->id = rand_id(rdev);
|
|
}
|
|
|
|
// Check if we are already announcing a value
|
|
auto p = dht_->getPut(hash, val->id);
|
|
if (p && val->seq <= p->seq) {
|
|
val->seq = p->seq + 1;
|
|
}
|
|
|
|
// Check if data already exists on the dht
|
|
get(
|
|
hash,
|
|
[val, this](const std::vector<Sp<Value>>& vals) {
|
|
if (logger_)
|
|
logger_->debug("Found online previous value being announced.");
|
|
for (const auto& v : vals) {
|
|
if (!v->isSigned()) {
|
|
if (logger_)
|
|
logger_->error("Existing non-signed value seems to exists at this location.");
|
|
} else if (not v->owner or v->owner->getId() != getId()) {
|
|
if (logger_)
|
|
logger_->error(
|
|
"Existing signed value belonging to someone else seems to exists at this location.");
|
|
} else if (val->seq <= v->seq)
|
|
val->seq = v->seq + 1;
|
|
}
|
|
return true;
|
|
},
|
|
[hash, val, this, callback, permanent](bool /* ok */) {
|
|
sign(*val);
|
|
dht_->put(hash, val, callback, time_point::max(), permanent);
|
|
},
|
|
Value::IdFilter(val->id),
|
|
Where().id(val->id));
|
|
}
|
|
|
|
void
|
|
SecureDht::putEncrypted(const InfoHash& hash, const PkId& to, Sp<Value> val, DoneCallback callback, bool permanent)
|
|
{
|
|
if (not key_) {
|
|
if (callback)
|
|
callback(false, {});
|
|
return;
|
|
}
|
|
findPublicKey(to,
|
|
[this, hash, val = std::move(val), callback = std::move(callback), permanent](
|
|
const Sp<crypto::PublicKey>& pk) {
|
|
if (!pk || !*pk) {
|
|
if (callback)
|
|
callback(false, {});
|
|
return;
|
|
}
|
|
if (logger_)
|
|
logger_->warn("Encrypting data for PK: {}", pk->getLongId());
|
|
try {
|
|
dht_->put(hash, encrypt(*val, *pk), callback, time_point::max(), permanent);
|
|
} catch (const std::exception& e) {
|
|
if (logger_)
|
|
logger_->error("Error putting encrypted data: {}", e.what());
|
|
if (callback)
|
|
callback(false, {});
|
|
}
|
|
});
|
|
}
|
|
|
|
void
|
|
SecureDht::putEncrypted(
|
|
const InfoHash& hash, const crypto::PublicKey& pk, Sp<Value> val, DoneCallback callback, bool permanent)
|
|
{
|
|
if (not key_) {
|
|
if (callback)
|
|
callback(false, {});
|
|
return;
|
|
}
|
|
if (logger_)
|
|
logger_->warn("Encrypting data for PK: {}", pk.getLongId());
|
|
try {
|
|
dht_->put(hash, encrypt(*val, pk), callback, time_point::max(), permanent);
|
|
} catch (const std::exception& e) {
|
|
if (logger_)
|
|
logger_->error("Error putting encrypted data: {}", e.what());
|
|
if (callback)
|
|
callback(false, {});
|
|
}
|
|
}
|
|
|
|
void
|
|
SecureDht::sign(Value& v) const
|
|
{
|
|
v.sign(*key_);
|
|
}
|
|
|
|
Value
|
|
SecureDht::encrypt(Value& v, const crypto::PublicKey& to) const
|
|
{
|
|
return v.encrypt(*key_, to);
|
|
}
|
|
|
|
Value
|
|
SecureDht::decrypt(const Value& v)
|
|
{
|
|
if (not v.isEncrypted())
|
|
throw DhtException("Data is not encrypted.");
|
|
|
|
auto decrypted = key_->decrypt(v.cypher);
|
|
|
|
Value ret {v.id};
|
|
auto msg = msgpack::unpack((const char*) decrypted.data(), decrypted.size());
|
|
ret.msgpack_unpack_body(msg.get());
|
|
|
|
if (ret.recipient != getId())
|
|
throw crypto::DecryptError("Recipient mismatch");
|
|
if (not ret.owner or not ret.owner->checkSignature(ret.getToSign(), ret.signature))
|
|
throw crypto::DecryptError("Signature mismatch");
|
|
|
|
return ret;
|
|
}
|
|
|
|
} // namespace dht
|