Commit 8f9db001 authored by Adrien Béraud's avatar Adrien Béraud Committed by gerrit2
Browse files

security: allow to define global certificates

Issue: #77219
Change-Id: I5bf71251212343e71f5e04081615a5890f7aa345
parent 88a8f467
......@@ -237,9 +237,19 @@ pinRemoteCertificate(const std::string& accountId, const std::string& certId)
bool
setCertificateStatus(const std::string& accountId, const std::string& certId, const std::string& ststr)
{
auto status = ring::tls::TrustStore::statusFromStr(ststr.c_str());
if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId))
return acc->setCertificateStatus(certId, status);
try {
if (accountId.empty()) {
ring::tls::CertificateStore::instance().setTrustedCertificate(certId, ring::tls::trustStatusFromStr(ststr.c_str()));
} else if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) {
try {
auto status = ring::tls::TrustStore::statusFromStr(ststr.c_str());
return acc->setCertificateStatus(certId, status);
} catch (const std::out_of_range&) {
auto status = ring::tls::trustStatusFromStr(ststr.c_str());
return acc->setCertificateStatus(certId, status);
}
}
} catch (const std::out_of_range&) {}
return false;
}
......
......@@ -30,6 +30,11 @@ constexpr static char ALLOWED [] = "ALLOWED";
constexpr static char BANNED [] = "BANNED";
} //namespace Dring::Certificate::Status
namespace TrustStatus {
constexpr static char UNTRUSTED [] = "UNTRUSTED";
constexpr static char TRUSTED [] = "TRUSTED";
} //namespace Dring::Certificate::TrustStatus
/**
* Those constantes are used by the ConfigurationManager.validateCertificate method
*/
......
......@@ -195,7 +195,7 @@ RingAccount::newOutgoingCall(const std::string& toUrl)
auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT};
// TODO: for now, we automatically trust all explicitly called peers
setCertificateStatus(toUri, tls::TrustStore::Status::ALLOWED);
setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
std::weak_ptr<SIPCall> weak_call = call;
manager.addTask([shared_this, weak_call, ice, iceInitTimeout, toUri] {
......@@ -809,7 +809,7 @@ RingAccount::doRegister_()
// quick check in case we already explicilty banned this public key
auto trustStatus = this_.trust_.getCertificateStatus(msg.from.toString());
if (trustStatus == tls::TrustStore::Status::BANNED) {
if (trustStatus == tls::TrustStore::PermissionStatus::BANNED) {
RING_WARN("Discarding incoming DHT call request from banned peer %s", msg.from.toString().c_str());
return true;
}
......@@ -819,7 +819,7 @@ RingAccount::doRegister_()
if (!res.second)
return true;
if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::Status::ALLOWED) {
if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::ALLOWED) {
this_.findCertificate(
msg.from,
[shared, msg](const std::shared_ptr<dht::crypto::Certificate> cert) mutable {
......@@ -832,7 +832,7 @@ RingAccount::doRegister_()
tls::CertificateStore::instance().pinCertificate(cert);
auto& this_ = *shared;
if (!this_.trust_.isTrusted(*cert)) {
if (!this_.trust_.isAllowed(*cert)) {
RING_WARN("Discarding incoming DHT call from untrusted peer %s.",
msg.from.toString().c_str());
return;
......@@ -843,7 +843,7 @@ RingAccount::doRegister_()
);
return true;
}
else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::Status::BANNED) {
else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::BANNED) {
this_.findCertificate(msg.from.toString().c_str());
}
// public incoming calls allowed or we explicitly authorised this public key
......@@ -990,7 +990,7 @@ RingAccount::findCertificate(const std::string& crt_id)
}
bool
RingAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStore::Status status)
RingAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status)
{
findCertificate(cert_id);
bool done = trust_.setCertificateStatus(cert_id, status);
......@@ -999,8 +999,18 @@ RingAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStore::S
return done;
}
bool
RingAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStatus status)
{
findCertificate(cert_id);
bool done = trust_.setCertificateStatus(cert_id, status);
if (done)
emitSignal<DRing::ConfigurationSignal::CertificateStateChanged>(getAccountID(), cert_id, tls::statusToStr(status));
return done;
}
std::vector<std::string>
RingAccount::getCertificatesByStatus(tls::TrustStore::Status status)
RingAccount::getCertificatesByStatus(tls::TrustStore::PermissionStatus status)
{
return trust_.getCertificatesByStatus(status);
}
......@@ -1253,7 +1263,7 @@ RingAccount::acceptTrustRequest(const std::string& from)
dht::InfoHash f(from);
for (auto i = std::begin(trustRequests_); i != std::end(trustRequests_); ++i) {
if (i->from == f) {
trust_.setCertificateStatus(from, tls::TrustStore::Status::ALLOWED);
trust_.setCertificateStatus(from, tls::TrustStore::PermissionStatus::ALLOWED);
trustRequests_.erase(i);
return true;
}
......@@ -1277,7 +1287,7 @@ RingAccount::discardTrustRequest(const std::string& from)
void
RingAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
{
setCertificateStatus(to, tls::TrustStore::Status::ALLOWED);
setCertificateStatus(to, tls::TrustStore::PermissionStatus::ALLOWED);
dht_.putEncrypted(dht::InfoHash::get("inbox:"+to),
dht::InfoHash(to),
dht::TrustRequest(DHT_TYPE_NS, payload));
......
......@@ -243,8 +243,10 @@ class RingAccount : public SIPAccountBase {
return false;
}
bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::Status status);
std::vector<std::string> getCertificatesByStatus(tls::TrustStore::Status status);
bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status);
bool setCertificateStatus(const std::string& cert_id, tls::TrustStatus status);
std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status);
bool findCertificate(const std::string& id);
bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb = {});
......
......@@ -177,9 +177,9 @@ readCertificates(const std::string& path)
}
void
CertificateStore::pinCertificatePath(const std::string& path)
CertificateStore::pinCertificatePath(const std::string& path, std::function<void(const std::vector<std::string>&)> cb)
{
std::thread([&, path]() {
std::thread([&, path, cb]() {
auto certs = readCertificates(path);
std::vector<std::string> ids;
std::vector<std::weak_ptr<crypto::Certificate>> scerts;
......@@ -198,6 +198,8 @@ CertificateStore::pinCertificatePath(const std::string& path)
}
RING_DBG("CertificateStore: loaded %lu certificates from %s.",
certs.size(), path.c_str());
if (cb)
cb(ids);
emitSignal<DRing::ConfigurationSignal::CertificatePathPinned>(path, ids);
}).detach();
}
......@@ -272,54 +274,111 @@ CertificateStore::unpinCertificate(const std::string& id)
return remove((certPath_+DIR_SEPARATOR_CH+id).c_str()) == 0;
}
TrustStore::Status
bool
CertificateStore::setTrustedCertificate(const std::string& id, TrustStatus status)
{
if (status == TrustStatus::TRUSTED) {
if (auto crt = getCertificate(id)) {
trustedCerts_.emplace_back(crt);
return true;
}
} else {
auto tc = std::find_if(trustedCerts_.begin(), trustedCerts_.end(),
[&](const std::shared_ptr<crypto::Certificate>& crt){
return crt->getId().toString() == id;
});
if (tc != trustedCerts_.end()) {
trustedCerts_.erase(tc);
return true;
}
}
return false;
}
std::vector<gnutls_x509_crt_t>
CertificateStore::getTrustedCertificates() const
{
std::vector<gnutls_x509_crt_t> crts;
crts.reserve(trustedCerts_.size());
for (auto& crt : trustedCerts_)
crts.emplace_back(crt->cert);
return crts;
}
TrustStore::PermissionStatus
TrustStore::statusFromStr(const char* str)
{
if (!std::strcmp(str, DRing::Certificate::Status::ALLOWED))
return Status::ALLOWED;
return PermissionStatus::ALLOWED;
if (!std::strcmp(str, DRing::Certificate::Status::BANNED))
return Status::BANNED;
return Status::UNDEFINED;
return PermissionStatus::BANNED;
return PermissionStatus::UNDEFINED;
}
const char*
TrustStore::statusToStr(TrustStore::Status s)
TrustStore::statusToStr(TrustStore::PermissionStatus s)
{
switch (s) {
case Status::ALLOWED:
case PermissionStatus::ALLOWED:
return DRing::Certificate::Status::ALLOWED;
case Status::BANNED:
case PermissionStatus::BANNED:
return DRing::Certificate::Status::BANNED;
case Status::UNDEFINED:
case PermissionStatus::UNDEFINED:
default:
return DRing::Certificate::Status::UNDEFINED;
}
}
TrustStatus
trustStatusFromStr(const char* str)
{
if (!std::strcmp(str, DRing::Certificate::TrustStatus::TRUSTED))
return TrustStatus::TRUSTED;
return TrustStatus::UNTRUSTED;
}
const char*
statusToStr(TrustStatus s)
{
switch (s) {
case TrustStatus::TRUSTED:
return DRing::Certificate::TrustStatus::TRUSTED;
case TrustStatus::UNTRUSTED:
default:
return DRing::Certificate::TrustStatus::UNTRUSTED;
}
}
TrustStore::TrustStore()
{
gnutls_x509_trust_list_init(&trust_, 0);
//gnutls_x509_trust_list_init(&trust_, 0);
gnutls_x509_trust_list_init(&allowed_, 0);
}
TrustStore::~TrustStore()
{
gnutls_x509_trust_list_deinit(trust_, false);
//gnutls_x509_trust_list_deinit(trust_, false);
gnutls_x509_trust_list_deinit(allowed_, false);
}
bool
TrustStore::setCertificateStatus(const std::string& cert_id,
const TrustStore::Status status)
const TrustStore::PermissionStatus status)
{
updateKnownCerts();
auto s = certStatus_.find(cert_id);
if (s == std::end(certStatus_)) {
if (auto cert = CertificateStore::instance().getCertificate(cert_id)) {
certStatus_[cert->getId().toString()] = {cert, status};
auto& crt_status = certStatus_[cert->getId().toString()];
if (not crt_status.first)
crt_status.first = cert;
crt_status.second.allowed = (status == PermissionStatus::ALLOWED);
setStoreCertStatus(*cert, status);
} else
unknownCertStatus_[cert_id] = status;
unknownCertStatus_[cert_id].allowed = (status == PermissionStatus::ALLOWED);
} else {
s->second.second = status;
s->second.second.allowed = (status == PermissionStatus::ALLOWED);
setStoreCertStatus(*s->second.first, status);
}
return true;
......@@ -327,60 +386,123 @@ TrustStore::setCertificateStatus(const std::string& cert_id,
bool
TrustStore::setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert,
const TrustStore::Status status, bool local)
const TrustStore::PermissionStatus status, bool local)
{
CertificateStore::instance().pinCertificate(cert, local);
certStatus_[cert->getId().toString()] = {cert, status};
auto& crt_status = certStatus_[cert->getId().toString()];
if (not crt_status.first)
crt_status.first = cert;
crt_status.second.allowed = (status == PermissionStatus::ALLOWED);
setStoreCertStatus(*cert, status);
return true;
}
TrustStore::Status
bool
TrustStore::setCertificateStatus(const std::string& cert_id, const TrustStatus status)
{
updateKnownCerts();
auto s = certStatus_.find(cert_id);
if (s == std::end(certStatus_)) {
if (auto cert = CertificateStore::instance().getCertificate(cert_id)) {
auto& crt_status = certStatus_[cert->getId().toString()];
if (not crt_status.first)
crt_status.first = cert;
crt_status.second.trusted = (status == TrustStatus::TRUSTED);
} else
unknownCertStatus_[cert_id].trusted = (status == TrustStatus::TRUSTED);
} else {
s->second.second.trusted = (status == TrustStatus::TRUSTED);
}
return true;
}
bool
TrustStore::setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert,
const TrustStatus status, bool local)
{
CertificateStore::instance().pinCertificate(cert, local);
auto& crt_status = certStatus_[cert->getId().toString()];
if (not crt_status.first)
crt_status.first = cert;
crt_status.second.trusted = (status == TrustStatus::TRUSTED);
return true;
}
TrustStore::PermissionStatus
TrustStore::getCertificateStatus(const std::string& cert_id) const
{
auto s = certStatus_.find(cert_id);
if (s == std::end(certStatus_)) {
auto us = unknownCertStatus_.find(cert_id);
if (us == std::end(unknownCertStatus_))
return Status::UNDEFINED;
return us->second;
return PermissionStatus::UNDEFINED;
return us->second.allowed ? PermissionStatus::ALLOWED : PermissionStatus::BANNED;
}
return s->second.second.allowed ? PermissionStatus::ALLOWED : PermissionStatus::BANNED;
}
TrustStatus
TrustStore::getCertificateTrustStatus(const std::string& cert_id) const
{
auto s = certStatus_.find(cert_id);
if (s == std::end(certStatus_)) {
auto us = unknownCertStatus_.find(cert_id);
if (us == std::end(unknownCertStatus_))
return TrustStatus::UNTRUSTED;
return us->second.trusted ? TrustStatus::TRUSTED : TrustStatus::UNTRUSTED;
}
return s->second.second;
return s->second.second.trusted ? TrustStatus::TRUSTED : TrustStatus::UNTRUSTED;
}
std::vector<std::string>
TrustStore::getCertificatesByStatus(TrustStore::Status status)
TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status)
{
std::vector<std::string> ret;
for (const auto& i : certStatus_)
if (i.second.second == status)
if (i.second.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
ret.emplace_back(i.first);
for (const auto& i : unknownCertStatus_)
if (i.second == status)
if (i.second.allowed == (status == TrustStore::PermissionStatus::ALLOWED))
ret.emplace_back(i.first);
return ret;
}
bool
TrustStore::isTrusted(const crypto::Certificate& crt)
TrustStore::isAllowed(const crypto::Certificate& crt)
{
if (getCertificateStatus(crt.getId().toString()) == Status::ALLOWED)
if (getCertificateStatus(crt.getId().toString()) == PermissionStatus::ALLOWED)
return true;
updateKnownCerts();
auto crts = getChain(crt);
return matchTrustStore(getChain(crt), allowed_);
}
std::vector<gnutls_x509_crt_t>
TrustStore::getTrustedCertificates() const
{
auto cas = CertificateStore::instance().getTrustedCertificates();
for (const auto& i : certStatus_)
if (i.second.second.trusted)
cas.emplace_back(i.second.first->cert);
return cas;
}
bool
TrustStore::matchTrustStore(std::vector<gnutls_x509_crt_t>&& crts, gnutls_x509_trust_list_st* store)
{
unsigned result = 0;
#if GNUTLS_VERSION_NUMBER > 0x030308
auto ret = gnutls_x509_trust_list_verify_crt2(
trust_,
store,
crts.data(), crts.size(),
nullptr, 0,
GNUTLS_PROFILE_TO_VFLAGS(GNUTLS_PROFILE_MEDIUM),
&result, nullptr);
#else
auto ret = gnutls_x509_trust_list_verify_crt(
trust_,
store,
crts.data(), crts.size(),
0,
&result, nullptr);
......@@ -395,7 +517,7 @@ TrustStore::isTrusted(const crypto::Certificate& crt)
}
std::vector<gnutls_x509_crt_t>
TrustStore::getChain(const crypto::Certificate& crt)
getChain(const crypto::Certificate& crt)
{
std::vector<gnutls_x509_crt_t> crts;
auto c = &crt;
......@@ -413,7 +535,7 @@ TrustStore::updateKnownCerts()
while (i != std::end(unknownCertStatus_)) {
if (auto crt = CertificateStore::instance().getCertificate(i->first)) {
certStatus_.emplace(i->first, std::make_pair(crt, i->second));
setStoreCertStatus(*crt, i->second);
setStoreCertStatus(*crt, i->second.allowed ? PermissionStatus::ALLOWED : PermissionStatus::UNDEFINED);
i = unknownCertStatus_.erase(i);
} else
++i;
......@@ -421,34 +543,19 @@ TrustStore::updateKnownCerts()
}
void
TrustStore::setStoreCertStatus(const crypto::Certificate& crt,
TrustStore::Status status)
TrustStore::setStoreCertStatus(const crypto::Certificate& crt, TrustStore::PermissionStatus status)
{
if (not crt.isCA())
return;
if (status == Status::ALLOWED)
gnutls_x509_trust_list_add_cas(trust_, &crt.cert, 1, 0);
else if (status == Status::BANNED)
gnutls_x509_trust_list_remove_cas(trust_, &crt.cert, 1);
if (status == PermissionStatus::ALLOWED)
gnutls_x509_trust_list_add_cas(allowed_, &crt.cert, 1, 0);
else
gnutls_x509_trust_list_remove_cas(allowed_, &crt.cert, 1);
RING_DBG("TrustStore: setting %s status to %s.",
crt.getId().toString().c_str(),
status == TrustStore::Status::ALLOWED ? "ALLOWED" : "BANNED");
}
#if 0
void
TrustStore::generateCAList(const std::string& out_path)
{
updateKnownCerts();
std::ostringstream ss;
for (const auto& cert : certStatus_)
ss << cert.second.first->toString();
auto res = ss.str();
fileutils::saveFile(out_path, {std::begin(res), std::end(res)});
status == PermissionStatus::ALLOWED ? "ALLOWED" : "NOT ALLOWED");
}
#endif
}} // namespace ring::tls
......@@ -38,6 +38,18 @@ namespace ring { namespace tls {
namespace crypto = dht::crypto;
enum class TrustStatus {
UNTRUSTED = 0,
TRUSTED
};
TrustStatus trustStatusFromStr(const char* str);
const char* statusToStr(TrustStatus s);
/**
* Global certificate store.
* Stores system root CAs and any other encountred certificate
*/
class CertificateStore {
public:
static CertificateStore& instance();
......@@ -56,9 +68,12 @@ public:
std::vector<std::string> pinCertificate(std::shared_ptr<crypto::Certificate> crt, bool local = true);
bool unpinCertificate(const std::string&);
void pinCertificatePath(const std::string& path);
void pinCertificatePath(const std::string& path, std::function<void(const std::vector<std::string>&)> cb = {});
unsigned unpinCertificatePath(const std::string&);
bool setTrustedCertificate(const std::string& id, TrustStatus status);
std::vector<gnutls_x509_crt_t> getTrustedCertificates() const;
private:
NON_COPYABLE(CertificateStore);
......@@ -69,42 +84,67 @@ private:
mutable std::mutex lock_;
std::map<std::string, std::shared_ptr<crypto::Certificate>> certs_;
std::map<std::string, std::vector<std::weak_ptr<crypto::Certificate>>> paths_;
};
// globally trusted certificates (root CAs)
std::vector<std::shared_ptr<crypto::Certificate>> trustedCerts_;
};
/**
* Keeps track of the allowed and trust status of certificates
* Trusted is the status of top certificates we trust to build our
* certificate chain: root CAs and other configured CAs.
*
* Allowed is the status of certificates we accept for incoming
* connections.
*/
class TrustStore {
public:
TrustStore();
virtual ~TrustStore();
enum class Status {
enum class PermissionStatus {
UNDEFINED = 0,
ALLOWED,
BANNED
};
static Status statusFromStr(const char* str);
static const char* statusToStr(Status s);
static PermissionStatus statusFromStr(const char* str);
static const char* statusToStr(PermissionStatus s);
bool setCertificateStatus(const std::string& cert_id, const Status status);
bool setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert, const Status status, bool local = true);
Status getCertificateStatus(const std::string& cert_id) const;
std::vector<std::string> getCertificatesByStatus(Status status);
bool setCertificateStatus(const std::string& cert_id, const PermissionStatus status);
bool setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert, PermissionStatus status, bool local = true);
bool isTrusted(const crypto::Certificate& crt);
bool setCertificateStatus(const std::string& cert_id, const TrustStatus status);
bool setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert, TrustStatus status, bool local = true);
PermissionStatus getCertificateStatus(const std::string& cert_id) const;
TrustStatus getCertificateTrustStatus(const std::string& cert_id) const;
std::vector<std::string> getCertificatesByStatus(PermissionStatus status);
bool isAllowed(const crypto::Certificate& crt);
std::vector<gnutls_x509_crt_t> getTrustedCertificates() const;
private:
NON_COPYABLE(TrustStore);
static std::vector<gnutls_x509_crt_t> getChain(const crypto::Certificate& crt);
void updateKnownCerts();
void setStoreCertStatus(const crypto::Certificate& crt, Status status);
void setStoreCertStatus(const crypto::Certificate& crt, PermissionStatus status);
static bool matchTrustStore(std::vector<gnutls_x509_crt_t>&& crts, gnutls_x509_trust_list_st* store);
struct Status {
bool allowed : 1;
bool trusted : 1;
};
// unknown certificates with known status
std::map<std::string, Status> unknownCertStatus_;
std::map<std::string, std::pair<std::shared_ptr<crypto::Certificate>, Status>> certStatus_;
gnutls_x509_trust_list_st* trust_;
gnutls_x509_trust_list_st* allowed_;
};
std::vector<gnutls_x509_crt_t> getChain(const crypto::Certificate& crt);
}} // namespace ring::tls
......@@ -438,17 +438,17 @@ unsigned int TlsValidator::compareToCa()
return caValidationOutput_;
// build the certificate chain