upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr.cpp')
-rw-r--r--src/nostr.cpp123
1 files changed, 103 insertions, 20 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 35fde43..e2d839c 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -6,6 +6,7 @@
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/x509.h>
9#include <openssl/x509v3.h> 10#include <openssl/x509v3.h>
10#ifdef _WIN32 11#ifdef _WIN32
11#include <wincrypt.h> 12#include <wincrypt.h>
@@ -82,9 +83,20 @@ static CCriticalSection cs_btfPeers;
82// independent relays. Kept only one Cloudflare-fronted relay (damus.io, the 83// independent relays. Kept only one Cloudflare-fronted relay (damus.io, the
83// most established) and filled the rest with relays on different hosts 84// most established) and filled the rest with relays on different hosts
84// (Hetzner, various independents) so no single upstream provider is a 85// (Hetzner, various independents) so no single upstream provider is a
85// single point of failure for peer discovery. 7 relays lands inside the 86// single point of failure for peer discovery. 8 relays lands inside the
86// commonly recommended 5-10 range for redundancy without keeping an 87// commonly recommended 5-10 range for redundancy without keeping an
87// excessive number of persistent sockets open per node. 88// excessive number of persistent sockets open per node.
89//
90// Four entries were removed after probing all twelve from two independent
91// networks (a residential line and the pool's VPS, over both v4 and v6):
92// relay.current.fyi, relay.nostr.bg -- no A record, and the AAAA for both
93// lands on the same parked host (2a04:c300:1000::381) serving a
94// certificate that fails verification. The domains are gone.
95// relay.nostr.band, nostr.fmt.wiz.biz -- TCP connects, TLS handshake never
96// completes, from both networks. Whether they are down or refusing
97// non-browser clients, this node cannot use them.
98// A dead relay is not free: each one holds a connect/retry slot and delays
99// discovery, so carrying it costs latency for no redundancy.
88const char* pszNostrRelays[] = { 100const char* pszNostrRelays[] = {
89 "wss://relay.damus.io", 101 "wss://relay.damus.io",
90 "wss://nos.lol", 102 "wss://nos.lol",
@@ -97,6 +109,8 @@ const char* pszNostrRelays[] = {
97 "wss://offchain.pub", 109 "wss://offchain.pub",
98 "wss://nostr.mom", 110 "wss://nostr.mom",
99 "wss://relay.primal.net", 111 "wss://relay.primal.net",
112 "wss://nostr.oxtr.dev",
113 "wss://relay.nos.social",
100}; 114};
101const int nNostrRelays = ARRAYLEN(pszNostrRelays); 115const int nNostrRelays = ARRAYLEN(pszNostrRelays);
102 116
@@ -110,6 +124,12 @@ vector<string> vBtfMeetingRelays = {
110 "31.44.4.249:8434", // New Jersey, US 124 "31.44.4.249:8434", // New Jersey, US
111 "90.156.222.107:8434", // Almaty, KZ 125 "90.156.222.107:8434", // Almaty, KZ
112}; 126};
127// True when the operator pinned the relay list with /rvrelay: discovered
128// relays must NOT be merged in (BtfAllRelays), otherwise one accept thread
129// per discovered relay is spawned and each one optimistically overwrites the
130// advertised meeting node (BtfSetActiveRelay) before registering -- a
131// discovered but unreachable relay then flaps the published .btf descriptor.
132bool fBtfRelayPinned = false;
113string strBtfActiveRelay; 133string strBtfActiveRelay;
114static CCriticalSection cs_activeRelay; 134static CCriticalSection cs_activeRelay;
115static string strBtfOnionEndpoint; 135static string strBtfOnionEndpoint;
@@ -166,7 +186,7 @@ static map<string, BtfPoolAnnouncement> g_discoveredPools;
166static CCriticalSection cs_discoveredPools; 186static CCriticalSection cs_discoveredPools;
167 187
168// Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host. 188// Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host.
169static bool LooksLikeRelay(const string& r) 189bool LooksLikeRelay(const string& r)
170{ 190{
171 size_t colon = r.rfind(':'); 191 size_t colon = r.rfind(':');
172 if (colon == string::npos || colon == 0 || colon + 1 >= r.size()) 192 if (colon == string::npos || colon == 0 || colon + 1 >= r.size())
@@ -194,6 +214,8 @@ static bool LooksLikeRelay(const string& r)
194vector<string> BtfAllRelays() 214vector<string> BtfAllRelays()
195{ 215{
196 vector<string> all = vBtfMeetingRelays; 216 vector<string> all = vBtfMeetingRelays;
217 if (fBtfRelayPinned)
218 return all;
197 CRITICAL_BLOCK(cs_discoveredRelays) 219 CRITICAL_BLOCK(cs_discoveredRelays)
198 foreach(const string& r, g_discoveredRelays) 220 foreach(const string& r, g_discoveredRelays)
199 if (find(all.begin(), all.end(), r) == all.end()) 221 if (find(all.begin(), all.end(), r) == all.end())
@@ -578,26 +600,52 @@ public:
578 { 600 {
579 sslctx = SSL_CTX_new(TLS_client_method()); 601 sslctx = SSL_CTX_new(TLS_client_method());
580 if (!sslctx) return false; 602 if (!sslctx) return false;
603
604 // SSL_CTX_new defaults to SSL_VERIFY_NONE with no trust store
605 // loaded, so until now every wss:// relay connection was accepted
606 // without looking at the certificate at all -- any on-path
607 // attacker could impersonate a relay. Our own events are signed,
608 // so a MITM cannot forge them, but it could drop the rendezvous
609 // registration (leaving the pool unreachable to miners) or answer
610 // subscriptions with whatever it liked.
581 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL); 611 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL);
612
613 // OpenSSL's default verify paths are usually empty on Windows, so
614 // the system ROOT store is loaded too -- without it verification
615 // fails closed there and every relay becomes unreachable.
582 bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1); 616 bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1);
583#ifdef _WIN32 617#ifdef _WIN32
584 fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded; 618 fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded;
585#endif 619#endif
586 if (!fTrustLoaded) 620 if (!fTrustLoaded)
587 return error("Nostr: failed to load TLS trust store"); 621 return error("Nostr: failed to load TLS trust store");
622
623 // Floor the protocol version. Verification says who the peer is,
624 // not how weak a cipher suite we will agree to; without this a
625 // relay offering nothing better still gets TLS 1.0.
626 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION);
627
588 ssl = SSL_new(sslctx); 628 ssl = SSL_new(sslctx);
589 if (!ssl) return false; 629 if (!ssl) return false;
590 SSL_set_fd(ssl, (int)hSocket); 630 SSL_set_fd(ssl, (int)hSocket);
591 if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) 631 if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) // SNI
592 return error("Nostr: failed to set TLS SNI for %s", host.c_str()); 632 return error("Nostr: failed to set TLS SNI for %s", host.c_str());
633
634 // Verifying the chain is not enough on its own: without this the
635 // relay could present any certificate a trusted CA ever issued,
636 // for any name. SSL_set1_host binds the check to the host we asked
637 // for.
593 if (SSL_set1_host(ssl, host.c_str()) != 1) 638 if (SSL_set1_host(ssl, host.c_str()) != 1)
594 return error("Nostr: failed to set TLS hostname for %s", host.c_str()); 639 return error("Nostr: failed to set TLS hostname for %s", host.c_str());
640
595 if (SSL_connect(ssl) != 1) 641 if (SSL_connect(ssl) != 1)
642 {
643 long nVerify = SSL_get_verify_result(ssl);
644 if (nVerify != X509_V_OK)
645 return error("Nostr: certificate for %s rejected: %s",
646 host.c_str(), X509_verify_cert_error_string(nVerify));
596 return error("Nostr: TLS handshake with %s failed", host.c_str()); 647 return error("Nostr: TLS handshake with %s failed", host.c_str());
597 long nVerify = SSL_get_verify_result(ssl); 648 }
598 if (nVerify != X509_V_OK)
599 return error("Nostr: TLS certificate verification failed for %s: %s",
600 host.c_str(), X509_verify_cert_error_string(nVerify));
601 } 649 }
602 650
603 // WebSocket handshake (HTTP Upgrade) 651 // WebSocket handshake (HTTP Upgrade)
@@ -1049,9 +1097,15 @@ static void HandlePoolAnnouncement(const json& ev)
1049 1097
1050// Resolve a `.btf` address via a relay: fetch the descriptor published by that 1098// Resolve a `.btf` address via a relay: fetch the descriptor published by that
1051// address's key and verify it self-certifies under the decoded pubkey. 1099// address's key and verify it self-certifies under the decoded pubkey.
1100// fExchangeOk reports whether the relay actually finished the conversation
1101// (an EOSE arrived) as opposed to the socket dying mid-read. The caller pools
1102// this socket for reuse, and only a completed exchange leaves it in a state
1103// the next caller can trust.
1052static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, 1104static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1053 btf::Descriptor& out, string* rawOut = NULL) 1105 btf::Descriptor& out, bool& fExchangeOk,
1106 string* rawOut = NULL)
1054{ 1107{
1108 fExchangeOk = false;
1055 unsigned char pk[32]; 1109 unsigned char pk[32];
1056 if (!btf::ParseAddress(btfAddr, pk)) 1110 if (!btf::ParseAddress(btfAddr, pk))
1057 return false; 1111 return false;
@@ -1063,30 +1117,46 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1063 json req = json::array({ "REQ", "btf-resolve", filter }); 1117 json req = json::array({ "REQ", "btf-resolve", filter });
1064 if (!ws.SendText(req.dump())) 1118 if (!ws.SendText(req.dump()))
1065 return false; 1119 return false;
1120 bool fFound = false;
1066 for (;;) 1121 for (;;)
1067 { 1122 {
1068 string msg; 1123 string msg;
1069 if (!ws.RecvText(msg)) 1124 if (!ws.RecvText(msg))
1070 break; 1125 return fFound; // socket died mid-exchange; fExchangeOk stays false
1071 json j; 1126 json j;
1072 try { j = json::parse(msg); } catch (...) { continue; } 1127 try { j = json::parse(msg); } catch (...) { continue; }
1073 if (!j.is_array() || j.empty() || !j[0].is_string()) continue; 1128 if (!j.is_array() || j.empty() || !j[0].is_string()) continue;
1074 string t = j[0].get<string>(); 1129 string t = j[0].get<string>();
1130 // Every relay message names the subscription it belongs to. This socket
1131 // is pooled and reused, so a message left over from an earlier exchange
1132 // is the ordinary case here, not an exotic one -- and an unmatched EOSE
1133 // ends this query before it has begun, which is indistinguishable from
1134 // "that descriptor does not exist". Check the id.
1135 if (j.size() >= 2 && j[1].is_string() && j[1].get<string>() != "btf-resolve")
1136 continue;
1075 if (t == "EVENT" && j.size() >= 3) 1137 if (t == "EVENT" && j.size() >= 3)
1076 { 1138 {
1077 const json& ev = j[2]; 1139 const json& ev = j[2];
1078 if (ev.contains("content") && 1140 if (!fFound && ev.contains("content") &&
1079 btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) 1141 btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out))
1080 { 1142 {
1143 fFound = true;
1081 if (rawOut) 1144 if (rawOut)
1082 *rawOut = ev["content"].get<string>(); 1145 *rawOut = ev["content"].get<string>();
1083 return true;
1084 } 1146 }
1147 // Deliberately keep reading to EOSE rather than returning here.
1148 // Returning early leaves the rest of the relay's answer queued on a
1149 // socket that goes straight back into the pool, and the next caller
1150 // reads our leftovers as its own -- one message out of step, for
1151 // the life of the process.
1085 } 1152 }
1086 else if (t == "EOSE") 1153 else if (t == "EOSE")
1154 {
1155 fExchangeOk = true; // relay said "that is all": socket is clean
1087 break; 1156 break;
1157 }
1088 } 1158 }
1089 return false; 1159 return fFound;
1090} 1160}
1091 1161
1092// Resolve a `.btf` address into its rendezvous coordinates by querying the 1162// Resolve a `.btf` address into its rendezvous coordinates by querying the
@@ -1107,12 +1177,22 @@ bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort,
1107 if (!lease.Connected()) 1177 if (!lease.Connected())
1108 continue; 1178 continue;
1109 btf::Descriptor d; 1179 btf::Descriptor d;
1110 if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d)) 1180 bool fExchangeOk = false;
1181 if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d, fExchangeOk))
1111 { 1182 {
1112 lease.fOk = true; // exchange completed, just no match 1183 // Only keep the pooled socket if the relay actually finished
1184 // the conversation. This used to say "exchange completed, just
1185 // no match" on every failure -- including a dead socket -- so
1186 // ~CRelayLease never closed it and fConnected was never
1187 // cleared. A relay connection that died was then pinned in the
1188 // pool, reported healthy, and reused for every later lookup,
1189 // which is why "no Nostr descriptor found" persisted for a
1190 // descriptor that was demonstrably published, and why only a
1191 // restart cleared it.
1192 lease.fOk = fExchangeOk;
1113 continue; 1193 continue;
1114 } 1194 }
1115 lease.fOk = true; 1195 lease.fOk = fExchangeOk;
1116 if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") 1196 if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending")
1117 { 1197 {
1118 if (!onionHostPort || d.onion.empty()) 1198 if (!onionHostPort || d.onion.empty())
@@ -1450,7 +1530,7 @@ void ThreadBtfPoolAnnouncer(void*)
1450 LogPrint("pool", "ThreadBtfPoolAnnouncer started\n"); 1530 LogPrint("pool", "ThreadBtfPoolAnnouncer started\n");
1451 while (!fShutdown) 1531 while (!fShutdown)
1452 { 1532 {
1453 if (nMineMode == MINE_OPERATOR && gPoolRunning) 1533 if (nMineMode == MINE_OPERATOR && gPoolRunning && !fPoolNoAnnounce)
1454 { 1534 {
1455 BtfPoolAnnouncement ann; 1535 BtfPoolAnnouncement ann;
1456 ann.btfAddress = BtfLocalAddress(); 1536 ann.btfAddress = BtfLocalAddress();
@@ -1785,14 +1865,17 @@ void ThreadNostrSeed(void* parg)
1785 PublishDescriptor(ws, key); 1865 PublishDescriptor(ws, key);
1786 Sleep(700); // let the relay store the replaceable event 1866 Sleep(700); // let the relay store the replaceable event
1787 btf::Descriptor d; 1867 btf::Descriptor d;
1788 if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d)) 1868 bool fExchangeOk = false;
1869 if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d, fExchangeOk))
1789 { 1870 {
1790 LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n", 1871 LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n",
1791 pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str()); 1872 pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str());
1792 lease.fOk = true; 1873 lease.fOk = fExchangeOk;
1793 break; 1874 break;
1794 } 1875 }
1795 lease.fOk = true; 1876 // Same rule as BtfResolve: a socket only goes back in the pool if
1877 // the relay finished the conversation on it.
1878 lease.fOk = fExchangeOk;
1796 } 1879 }
1797 CATCH_PRINT_EXCEPTION("btf self-test") 1880 CATCH_PRINT_EXCEPTION("btf self-test")
1798 } 1881 }
@@ -1824,7 +1907,7 @@ void ThreadNostrSeed(void* parg)
1824 } 1907 }
1825 CATCH_PRINT_EXCEPTION("ConnectDiscoveredBtfPeers") 1908 CATCH_PRINT_EXCEPTION("ConnectDiscoveredBtfPeers")
1826 1909
1827 if (nMineMode == MINE_OPERATOR && gPoolRunning) 1910 if (nMineMode == MINE_OPERATOR && gPoolRunning && !fPoolNoAnnounce)
1828 { 1911 {
1829 BtfPoolAnnouncement ann; 1912 BtfPoolAnnouncement ann;
1830 ann.btfAddress = BtfLocalAddress(); 1913 ann.btfAddress = BtfLocalAddress();