upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-07 10:55:48 -0300
committerBitflash <admin@bitflash.network>2026-08-07 11:05:08 -0300
commit09ff5751264de6101f1bf0981accac0123351a7a (patch)
treeddcf0c279cea96f0f6dcda11c6c7d44fe24369c3
parentc3f6b68f907b03865d2c417ee39fe9526787196f (diff)
nostr: verify relay TLS and lock peer counts (#1)
-rw-r--r--src/nostr.cpp85
1 files changed, 77 insertions, 8 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 1fd60ad..9bc538f 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -6,6 +6,10 @@
6#include "headers.h" 6#include "headers.h"
7#include <openssl/ssl.h> 7#include <openssl/ssl.h>
8#include <openssl/err.h> 8#include <openssl/err.h>
9#include <openssl/x509v3.h>
10#ifdef _WIN32
11#include <wincrypt.h>
12#endif
9// We link libsecp256k1 statically (without this the header assumes DLL import). 13// We link libsecp256k1 statically (without this the header assumes DLL import).
10// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from 14// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from
11// warning while leaving this file correct if built without the flag. 15// warning while leaving this file correct if built without the flag.
@@ -446,6 +450,41 @@ static bool BuildSignedEvent(CNostrKey& key, int kind, const json& tags,
446static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame 450static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame
447static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled 451static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled
448 452
453static int BtfConnectedNodeCount()
454{
455 int nNodes = 0;
456 CRITICAL_BLOCK(cs_vNodes)
457 {
458 nNodes = (int)vNodes.size();
459 }
460 return nNodes;
461}
462
463#ifdef _WIN32
464static bool BtfLoadWindowsRootCertificates(SSL_CTX* sslctx)
465{
466 HCERTSTORE hStore = CertOpenSystemStoreA(0, "ROOT");
467 if (!hStore)
468 return false;
469
470 bool fLoaded = false;
471 X509_STORE* x509store = SSL_CTX_get_cert_store(sslctx);
472 PCCERT_CONTEXT pCert = NULL;
473 while ((pCert = CertEnumCertificatesInStore(hStore, pCert)) != NULL)
474 {
475 const unsigned char* p = pCert->pbCertEncoded;
476 X509* x509 = d2i_X509(NULL, &p, pCert->cbCertEncoded);
477 if (!x509)
478 continue;
479 if (X509_STORE_add_cert(x509store, x509) == 1)
480 fLoaded = true;
481 X509_free(x509);
482 }
483 CertCloseStore(hStore, 0);
484 return fLoaded;
485}
486#endif
487
449class CWebSocket 488class CWebSocket
450{ 489{
451public: 490public:
@@ -555,11 +594,26 @@ public:
555 { 594 {
556 sslctx = SSL_CTX_new(TLS_client_method()); 595 sslctx = SSL_CTX_new(TLS_client_method());
557 if (!sslctx) return false; 596 if (!sslctx) return false;
597 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL);
598 bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1);
599#ifdef _WIN32
600 fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded;
601#endif
602 if (!fTrustLoaded)
603 return error("Nostr: failed to load TLS trust store");
558 ssl = SSL_new(sslctx); 604 ssl = SSL_new(sslctx);
605 if (!ssl) return false;
559 SSL_set_fd(ssl, (int)hSocket); 606 SSL_set_fd(ssl, (int)hSocket);
560 SSL_set_tlsext_host_name(ssl, host.c_str()); // SNI 607 if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1)
608 return error("Nostr: failed to set TLS SNI for %s", host.c_str());
609 if (SSL_set1_host(ssl, host.c_str()) != 1)
610 return error("Nostr: failed to set TLS hostname for %s", host.c_str());
561 if (SSL_connect(ssl) != 1) 611 if (SSL_connect(ssl) != 1)
562 return error("Nostr: TLS handshake with %s failed", host.c_str()); 612 return error("Nostr: TLS handshake with %s failed", host.c_str());
613 long nVerify = SSL_get_verify_result(ssl);
614 if (nVerify != X509_V_OK)
615 return error("Nostr: TLS certificate verification failed for %s: %s",
616 host.c_str(), X509_verify_cert_error_string(nVerify));
563 } 617 }
564 618
565 // WebSocket handshake (HTTP Upgrade) 619 // WebSocket handshake (HTTP Upgrade)
@@ -1016,6 +1070,7 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1016 json filter = json::object(); 1070 json filter = json::object();
1017 filter["authors"] = json::array({ HexEncode(pk, 32) }); 1071 filter["authors"] = json::array({ HexEncode(pk, 32) });
1018 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1072 filter["kinds"] = json::array({ BTF_DESC_KIND });
1073 filter["#d"] = json::array({ BTF_DESC_DTAG });
1019 filter["limit"] = 1; 1074 filter["limit"] = 1;
1020 json req = json::array({ "REQ", "btf-resolve", filter }); 1075 json req = json::array({ "REQ", "btf-resolve", filter });
1021 if (!ws.SendText(req.dump())) 1076 if (!ws.SendText(req.dump()))
@@ -1126,6 +1181,7 @@ bool BtfResolveMany(const std::vector<std::string>& btfAddrs,
1126 json filter = json::object(); 1181 json filter = json::object();
1127 filter["authors"] = authors; 1182 filter["authors"] = authors;
1128 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1183 filter["kinds"] = json::array({ BTF_DESC_KIND });
1184 filter["#d"] = json::array({ BTF_DESC_DTAG });
1129 filter["limit"] = (int)authors.size(); 1185 filter["limit"] = (int)authors.size();
1130 json req = json::array({ "REQ", "btf-resolve-many", filter }); 1186 json req = json::array({ "REQ", "btf-resolve-many", filter });
1131 if (!ws.SendText(req.dump())) 1187 if (!ws.SendText(req.dump()))
@@ -1536,8 +1592,9 @@ static void ConnectDiscoveredBtfPeers()
1536 // plus a random sample, not one or the other. 1592 // plus a random sample, not one or the other.
1537 int64 now = GetTime(); 1593 int64 now = GetTime();
1538 int nBudget = (int)BTF_DIALS_PER_PASS; 1594 int nBudget = (int)BTF_DIALS_PER_PASS;
1595 int nNodeCount = BtfConnectedNodeCount();
1539 { 1596 {
1540 int nRoom = (int)BTF_TARGET_CONN - (int)vNodes.size(); 1597 int nRoom = (int)BTF_TARGET_CONN - nNodeCount;
1541 if (nRoom < nBudget) nBudget = nRoom; 1598 if (nRoom < nBudget) nBudget = nRoom;
1542 } 1599 }
1543 if (nBudget < 0) nBudget = 0; 1600 if (nBudget < 0) nBudget = 0;
@@ -1551,7 +1608,6 @@ static void ConnectDiscoveredBtfPeers()
1551 // Reliability slice: walk the tiered (good -> untried -> bad) order. 1608 // Reliability slice: walk the tiered (good -> untried -> bad) order.
1552 for (const string& addr : peers) 1609 for (const string& addr : peers)
1553 { 1610 {
1554 if ((int)vNodes.size() >= (int)BTF_TARGET_CONN) break;
1555 if ((int)candidates.size() >= nSortedSlots) break; 1611 if ((int)candidates.size() >= nSortedSlots) break;
1556 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr); 1612 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr);
1557 if (bi != g_btfPeerBackoff.end() && now < bi->second) 1613 if (bi != g_btfPeerBackoff.end() && now < bi->second)
@@ -1576,7 +1632,7 @@ static void ConnectDiscoveredBtfPeers()
1576 int nWant = min(nRandomSlots, (int)pool.size()); 1632 int nWant = min(nRandomSlots, (int)pool.size());
1577 for (int i = 0; i < nWant; i++) 1633 for (int i = 0; i < nWant; i++)
1578 { 1634 {
1579 if ((int)vNodes.size() + (int)candidates.size() >= (int)BTF_TARGET_CONN) break; 1635 if (nNodeCount + (int)candidates.size() >= (int)BTF_TARGET_CONN) break;
1580 if ((int)candidates.size() >= nBudget) break; 1636 if ((int)candidates.size() >= nBudget) break;
1581 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i)); 1637 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i));
1582 std::swap(pool[i], pool[j]); 1638 std::swap(pool[i], pool[j]);
@@ -1631,12 +1687,10 @@ static void ConnectDiscoveredBtfPeers()
1631 // not the shared Nostr relays, so there's no thundering-herd effect here. 1687 // not the shared Nostr relays, so there's no thundering-herd effect here.
1632 // Each thread writes its result back via shared state guarded by cs_btfPeerState. 1688 // Each thread writes its result back via shared state guarded by cs_btfPeerState.
1633 struct DialCtx { string addr; BtfResolvedPeer rp; }; 1689 struct DialCtx { string addr; BtfResolvedPeer rp; };
1634 vector<DialCtx*> ctxs;
1635 for (const string& addr : toDial) 1690 for (const string& addr : toDial)
1636 { 1691 {
1637 DialCtx* ctx = new DialCtx{addr, resolved[addr]}; 1692 DialCtx* ctx = new DialCtx{addr, resolved[addr]};
1638 ctxs.push_back(ctx); 1693 uintptr_t tid = _beginthread([](void* arg) {
1639 _beginthread([](void* arg) {
1640 DialCtx* ctx = (DialCtx*)arg; 1694 DialCtx* ctx = (DialCtx*)arg;
1641 string addr = ctx->addr; 1695 string addr = ctx->addr;
1642 string meeting = ctx->rp.meetingHostPort; 1696 string meeting = ctx->rp.meetingHostPort;
@@ -1669,6 +1723,21 @@ static void ConnectDiscoveredBtfPeers()
1669 } 1723 }
1670 } 1724 }
1671 }, 0, ctx); 1725 }, 0, ctx);
1726 if (tid == (uintptr_t)-1)
1727 {
1728 LogPrint("nostr", "Nostr: failed to create .btf dial thread for %s\n", addr.c_str());
1729 CRITICAL_BLOCK(cs_btfPeerState)
1730 {
1731 int n = ++g_btfPeerFails[addr];
1732 int64 cap = (n >= BTF_CHRONIC_FAIL_THRESHOLD) ? BTF_BACKOFF_CAP_SECS_DEAD
1733 : BTF_BACKOFF_CAP_SECS;
1734 int64 secs = 30;
1735 for (int k = 1; k < n && secs < cap; k++) secs *= 2;
1736 if (secs > cap) secs = cap;
1737 g_btfPeerBackoff[addr] = GetTime() + secs;
1738 }
1739 delete ctx;
1740 }
1672 } 1741 }
1673 1742
1674 // Wait long enough for all parallel dials to finish (each has a 10s socket 1743 // Wait long enough for all parallel dials to finish (each has a 10s socket
@@ -1768,7 +1837,7 @@ void ThreadNostrSeed(void* parg)
1768 1837
1769 // Re-discover periodically. Sleep less aggressively when 1838 // Re-discover periodically. Sleep less aggressively when
1770 // we have no connections yet so we find peers faster on startup. 1839 // we have no connections yet so we find peers faster on startup.
1771 int nSleepSecs = vNodes.empty() ? 20 : 60; 1840 int nSleepSecs = (BtfConnectedNodeCount() == 0) ? 20 : 60;
1772 for (int i = 0; i < nSleepSecs && !fShutdown; i++) 1841 for (int i = 0; i < nSleepSecs && !fShutdown; i++)
1773 Sleep(1000); 1842 Sleep(1000);
1774 } 1843 }