nostrdb: nostrdb: calculate id in ndb_note_verify
Rogue relays could in theory attack nostrdb by replaying ids and signatures from other notes. This fixes this weakness by calculating the id again in ndb_note_verify. There is no known relays exploiting this, but lets get ahead of it before we switch to the outbox model in damus iOS/notedeck Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
committed by
Daniel D’Aquino
parent
3a37a6c18e
commit
fa2d240ddf
@@ -185,6 +185,8 @@ struct ndb_ingester {
|
|||||||
struct prot_queue *writer_inbox;
|
struct prot_queue *writer_inbox;
|
||||||
void *filter_context;
|
void *filter_context;
|
||||||
ndb_ingest_filter_fn filter;
|
ndb_ingest_filter_fn filter;
|
||||||
|
|
||||||
|
int scratch_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ndb_filter_group {
|
struct ndb_filter_group {
|
||||||
@@ -2321,17 +2323,34 @@ int ndb_end_query(struct ndb_txn *txn)
|
|||||||
return mdb_txn_commit(txn->mdb_txn) == 0;
|
return mdb_txn_commit(txn->mdb_txn) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ndb_note_verify(void *ctx, unsigned char pubkey[32], unsigned char id[32],
|
int ndb_note_verify(void *ctx, unsigned char *scratch, size_t scratch_size,
|
||||||
unsigned char sig[64])
|
struct ndb_note *note)
|
||||||
{
|
{
|
||||||
|
unsigned char id[32];
|
||||||
secp256k1_xonly_pubkey xonly_pubkey;
|
secp256k1_xonly_pubkey xonly_pubkey;
|
||||||
int ok;
|
int ok;
|
||||||
|
|
||||||
ok = secp256k1_xonly_pubkey_parse((secp256k1_context*)ctx, &xonly_pubkey,
|
// first, we ensure the id is valid by calculating the id independently
|
||||||
pubkey) != 0;
|
// from what is given to us
|
||||||
|
if (!ndb_calculate_id(note, scratch, scratch_size, id)) {
|
||||||
|
ndb_debug("ndb_note_verify: scratch buffer size too small");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(id, note->id, 32)) {
|
||||||
|
ndb_debug("ndb_note_verify: note id does not match!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// id is ok, let's check signature
|
||||||
|
|
||||||
|
ok = secp256k1_xonly_pubkey_parse((secp256k1_context*)ctx,
|
||||||
|
&xonly_pubkey,
|
||||||
|
ndb_note_pubkey(note)) != 0;
|
||||||
if (!ok) return 0;
|
if (!ok) return 0;
|
||||||
|
|
||||||
ok = secp256k1_schnorrsig_verify((secp256k1_context*)ctx, sig, id, 32,
|
ok = secp256k1_schnorrsig_verify((secp256k1_context*)ctx,
|
||||||
|
ndb_note_sig(note), id, 32,
|
||||||
&xonly_pubkey) > 0;
|
&xonly_pubkey) > 0;
|
||||||
if (!ok) return 0;
|
if (!ok) return 0;
|
||||||
|
|
||||||
@@ -2754,6 +2773,7 @@ static int ndb_ingester_process_note(secp256k1_context *ctx,
|
|||||||
size_t note_size,
|
size_t note_size,
|
||||||
struct ndb_writer_msg *out,
|
struct ndb_writer_msg *out,
|
||||||
struct ndb_ingester *ingester,
|
struct ndb_ingester *ingester,
|
||||||
|
unsigned char *scratch,
|
||||||
const char *relay)
|
const char *relay)
|
||||||
{
|
{
|
||||||
enum ndb_ingest_filter_action action;
|
enum ndb_ingest_filter_action action;
|
||||||
@@ -2774,8 +2794,8 @@ static int ndb_ingester_process_note(secp256k1_context *ctx,
|
|||||||
} else {
|
} else {
|
||||||
// verify! If it's an invalid note we don't need to
|
// verify! If it's an invalid note we don't need to
|
||||||
// bother writing it to the database
|
// bother writing it to the database
|
||||||
if (!ndb_note_verify(ctx, note->pubkey, note->id, note->sig)) {
|
if (!ndb_note_verify(ctx, scratch, ingester->scratch_size, note)) {
|
||||||
ndb_debug("signature verification failed\n");
|
ndb_debug("note verification failed\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2871,6 +2891,7 @@ static int ndb_ingester_process_event(secp256k1_context *ctx,
|
|||||||
struct ndb_ingester *ingester,
|
struct ndb_ingester *ingester,
|
||||||
struct ndb_ingester_event *ev,
|
struct ndb_ingester_event *ev,
|
||||||
struct ndb_writer_msg *out,
|
struct ndb_writer_msg *out,
|
||||||
|
unsigned char *scratch,
|
||||||
MDB_txn *read_txn)
|
MDB_txn *read_txn)
|
||||||
{
|
{
|
||||||
struct ndb_tce tce;
|
struct ndb_tce tce;
|
||||||
@@ -2945,7 +2966,7 @@ static int ndb_ingester_process_event(secp256k1_context *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ndb_ingester_process_note(ctx, note, note_size,
|
if (!ndb_ingester_process_note(ctx, note, note_size,
|
||||||
out, ingester,
|
out, ingester, scratch,
|
||||||
ev->relay)) {
|
ev->relay)) {
|
||||||
ndb_debug("failed to process note\n");
|
ndb_debug("failed to process note\n");
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -2967,7 +2988,7 @@ static int ndb_ingester_process_event(secp256k1_context *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ndb_ingester_process_note(ctx, note, note_size,
|
if (!ndb_ingester_process_note(ctx, note, note_size,
|
||||||
out, ingester,
|
out, ingester, scratch,
|
||||||
ev->relay)) {
|
ev->relay)) {
|
||||||
ndb_debug("failed to process note\n");
|
ndb_debug("failed to process note\n");
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -5599,8 +5620,13 @@ static void *ndb_ingester_thread(void *data)
|
|||||||
struct ndb_writer_msg outs[THREAD_QUEUE_BATCH], *out;
|
struct ndb_writer_msg outs[THREAD_QUEUE_BATCH], *out;
|
||||||
int i, to_write, popped, done, any_event;
|
int i, to_write, popped, done, any_event;
|
||||||
MDB_txn *read_txn = NULL;
|
MDB_txn *read_txn = NULL;
|
||||||
|
unsigned char *scratch;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
// this is used in note verification and anything else that
|
||||||
|
// needs a temporary buffer
|
||||||
|
scratch = malloc(ingester->scratch_size);
|
||||||
|
|
||||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
||||||
//ndb_debug("started ingester thread\n");
|
//ndb_debug("started ingester thread\n");
|
||||||
|
|
||||||
@@ -5638,6 +5664,7 @@ static void *ndb_ingester_thread(void *data)
|
|||||||
out = &outs[to_write];
|
out = &outs[to_write];
|
||||||
if (ndb_ingester_process_event(ctx, ingester,
|
if (ndb_ingester_process_event(ctx, ingester,
|
||||||
&msg->event, out,
|
&msg->event, out,
|
||||||
|
scratch,
|
||||||
read_txn)) {
|
read_txn)) {
|
||||||
to_write++;
|
to_write++;
|
||||||
}
|
}
|
||||||
@@ -5657,6 +5684,7 @@ static void *ndb_ingester_thread(void *data)
|
|||||||
|
|
||||||
ndb_debug("quitting ingester thread\n");
|
ndb_debug("quitting ingester thread\n");
|
||||||
secp256k1_context_destroy(ctx);
|
secp256k1_context_destroy(ctx);
|
||||||
|
free(scratch);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5694,6 +5722,7 @@ static int ndb_writer_init(struct ndb_writer *writer, struct ndb_lmdb *lmdb,
|
|||||||
static int ndb_ingester_init(struct ndb_ingester *ingester,
|
static int ndb_ingester_init(struct ndb_ingester *ingester,
|
||||||
struct ndb_lmdb *lmdb,
|
struct ndb_lmdb *lmdb,
|
||||||
struct prot_queue *writer_inbox,
|
struct prot_queue *writer_inbox,
|
||||||
|
int scratch_size,
|
||||||
const struct ndb_config *config)
|
const struct ndb_config *config)
|
||||||
{
|
{
|
||||||
int elem_size, num_elems;
|
int elem_size, num_elems;
|
||||||
@@ -5703,6 +5732,7 @@ static int ndb_ingester_init(struct ndb_ingester *ingester,
|
|||||||
elem_size = sizeof(struct ndb_ingester_msg);
|
elem_size = sizeof(struct ndb_ingester_msg);
|
||||||
num_elems = DEFAULT_QUEUE_SIZE;
|
num_elems = DEFAULT_QUEUE_SIZE;
|
||||||
|
|
||||||
|
ingester->scratch_size = scratch_size;
|
||||||
ingester->writer_inbox = writer_inbox;
|
ingester->writer_inbox = writer_inbox;
|
||||||
ingester->lmdb = lmdb;
|
ingester->lmdb = lmdb;
|
||||||
ingester->flags = config->flags;
|
ingester->flags = config->flags;
|
||||||
@@ -5979,7 +6009,8 @@ int ndb_init(struct ndb **pndb, const char *filename, const struct ndb_config *c
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ndb_ingester_init(&ndb->ingester, &ndb->lmdb, &ndb->writer.inbox, config)) {
|
if (!ndb_ingester_init(&ndb->ingester, &ndb->lmdb, &ndb->writer.inbox,
|
||||||
|
config->writer_scratch_buffer_size, config)) {
|
||||||
fprintf(stderr, "failed to initialize %d ingester thread(s)\n",
|
fprintf(stderr, "failed to initialize %d ingester thread(s)\n",
|
||||||
config->ingester_threads);
|
config->ingester_threads);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -6612,7 +6643,7 @@ int ndb_filter_json(const struct ndb_filter *filter, char *buf, int buflen)
|
|||||||
return cur.p - cur.start;
|
return cur.p - cur.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen) {
|
int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen, unsigned char *id) {
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
if (!(len = ndb_event_commitment(note, buf, buflen)))
|
if (!(len = ndb_event_commitment(note, buf, buflen)))
|
||||||
@@ -6620,7 +6651,7 @@ int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen) {
|
|||||||
|
|
||||||
//fprintf(stderr, "%.*s\n", len, buf);
|
//fprintf(stderr, "%.*s\n", len, buf);
|
||||||
|
|
||||||
sha256((struct sha256*)note->id, buf, len);
|
sha256((struct sha256*)id, buf, len);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -6696,7 +6727,8 @@ int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note,
|
|||||||
|
|
||||||
ndb_builder_set_pubkey(builder, keypair->pubkey);
|
ndb_builder_set_pubkey(builder, keypair->pubkey);
|
||||||
|
|
||||||
if (!ndb_calculate_id(builder->note, start, end - start))
|
if (!ndb_calculate_id(builder->note, start, end - start,
|
||||||
|
builder->note->id))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!ndb_sign_id(keypair, (*note)->id, (*note)->sig))
|
if (!ndb_sign_id(keypair, (*note)->id, (*note)->sig))
|
||||||
|
|||||||
@@ -493,11 +493,11 @@ void ndb_config_set_subscription_callback(struct ndb_config *config, ndb_sub_fn
|
|||||||
void ndb_config_set_writer_scratch_buffer_size(struct ndb_config *config, int scratch_size);
|
void ndb_config_set_writer_scratch_buffer_size(struct ndb_config *config, int scratch_size);
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen);
|
int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen, unsigned char *id);
|
||||||
int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32], unsigned char sig[64]);
|
int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32], unsigned char sig[64]);
|
||||||
int ndb_create_keypair(struct ndb_keypair *key);
|
int ndb_create_keypair(struct ndb_keypair *key);
|
||||||
int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair);
|
int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair);
|
||||||
int ndb_note_verify(void *secp_ctx, unsigned char pubkey[32], unsigned char id[32], unsigned char signature[64]);
|
int ndb_note_verify(void *secp_ctx, unsigned char *scratch, size_t scratch_size, struct ndb_note *note);
|
||||||
|
|
||||||
// NDB
|
// NDB
|
||||||
int ndb_init(struct ndb **ndb, const char *dbdir, const struct ndb_config *);
|
int ndb_init(struct ndb **ndb, const char *dbdir, const struct ndb_config *);
|
||||||
|
|||||||
Reference in New Issue
Block a user