upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-09 02:17:07 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-23 04:37:36 +0000
commit89964636055e0856b2ff138edc4b7f45e0099f1b (patch)
treed37f3fab481315f6ae51f0f6db3ffb61bb6f2849 /src
parent7297216f20e81935dec4e612f90f3a5f0e9ebea9 (diff)
nostr: verify relay certificates -- wss:// was fully MITM-able
SSL_CTX_new leaves verification at SSL_VERIFY_NONE and loads no trust store, and nothing here ever changed that. SNI was set, a certificate was received, and it was then ignored: every wss:// relay connection this node has ever made would have completed against an attacker's certificate. Signed events limit the damage -- a MITM cannot forge an announcement attributable to our key -- but it can drop the rendezvous registration so miners never find the pool, censor subscriptions, or answer them with arbitrary content, which is the input the allocation bounds exist to contain. Load the system trust store, require SSL_VERIFY_PEER, and bind the check to the host we dialed with SSL_set1_host, since chain validation alone would accept any certificate a trusted CA ever issued for any name. Floor the protocol at TLS 1.2. Verification failures report the specific X509 reason rather than a generic handshake error, and a missing CA store is a hard failure instead of a silent downgrade to trusting everything. (cherry picked from commit 6020740e68463deb1cc3c5b477372fcd1a245adb)
Diffstat (limited to 'src')
-rw-r--r--src/nostr.cpp29
1 files changed, 29 insertions, 0 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 47f001c..fbae1a6 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// We link libsecp256k1 statically (without this the header assumes DLL import). 10// We link libsecp256k1 statically (without this the header assumes DLL import).
10// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from 11// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from
11// warning while leaving this file correct if built without the flag. 12// warning while leaving this file correct if built without the flag.
@@ -568,11 +569,39 @@ public:
568 { 569 {
569 sslctx = SSL_CTX_new(TLS_client_method()); 570 sslctx = SSL_CTX_new(TLS_client_method());
570 if (!sslctx) return false; 571 if (!sslctx) return false;
572
573 // SSL_CTX_new defaults to SSL_VERIFY_NONE with no trust store
574 // loaded, so until now every wss:// relay connection was accepted
575 // without looking at the certificate at all -- any on-path
576 // attacker could impersonate a relay. Our own events are signed,
577 // so a MITM cannot forge them, but it could drop the rendezvous
578 // registration (leaving the pool unreachable to miners) or answer
579 // subscriptions with whatever it liked.
580 if (!SSL_CTX_set_default_verify_paths(sslctx))
581 return error("Nostr: no system CA store available, refusing %s", host.c_str());
582 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL);
583 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION);
584
571 ssl = SSL_new(sslctx); 585 ssl = SSL_new(sslctx);
586 if (!ssl) return false;
572 SSL_set_fd(ssl, (int)hSocket); 587 SSL_set_fd(ssl, (int)hSocket);
573 SSL_set_tlsext_host_name(ssl, host.c_str()); // SNI 588 SSL_set_tlsext_host_name(ssl, host.c_str()); // SNI
589
590 // Verifying the chain is not enough on its own: without this the
591 // relay could present any certificate a trusted CA ever issued,
592 // for any name. SSL_set1_host binds the check to the host we asked
593 // for.
594 if (!SSL_set1_host(ssl, host.c_str()))
595 return error("Nostr: cannot pin verification to %s", host.c_str());
596
574 if (SSL_connect(ssl) != 1) 597 if (SSL_connect(ssl) != 1)
598 {
599 long nVerify = SSL_get_verify_result(ssl);
600 if (nVerify != X509_V_OK)
601 return error("Nostr: certificate for %s rejected: %s",
602 host.c_str(), X509_verify_cert_error_string(nVerify));
575 return error("Nostr: TLS handshake with %s failed", host.c_str()); 603 return error("Nostr: TLS handshake with %s failed", host.c_str());
604 }
576 } 605 }
577 606
578 // WebSocket handshake (HTTP Upgrade) 607 // WebSocket handshake (HTTP Upgrade)