diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-07 10:55:48 -0300 |
|---|---|---|
| committer | Bitflash <admin@bitflash.network> | 2026-08-07 11:05:08 -0300 |
| commit | 09ff5751264de6101f1bf0981accac0123351a7a (patch) | |
| tree | ddcf0c279cea96f0f6dcda11c6c7d44fe24369c3 /src/nostr.cpp | |
| parent | c3f6b68f907b03865d2c417ee39fe9526787196f (diff) | |
nostr: verify relay TLS and lock peer counts (#1)
Diffstat (limited to 'src/nostr.cpp')
| -rw-r--r-- | src/nostr.cpp | 85 |
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, | |||
| 446 | static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame | 450 | static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame |
| 447 | static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled | 451 | static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled |
| 448 | 452 | ||
| 453 | static 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 | ||
| 464 | static 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 | |||
| 449 | class CWebSocket | 488 | class CWebSocket |
| 450 | { | 489 | { |
| 451 | public: | 490 | public: |
| @@ -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 | } |