v0.1.11 - Fix named path-role display and allow fixed paths without range/set
This commit is contained in:
@@ -55,6 +55,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
typedef struct { role_entry_t entries[ROLE_TABLE_MAX_ENTRIES]; int count; } role_table_t;
|
||||
void role_table_init(role_table_t *table);
|
||||
|
||||
+12
-1
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -157,7 +162,11 @@ const char *role_curve_to_str(role_curve_t c);
|
||||
/* Register a nostr-index role if missing. Returns 0 on success, -1 on error. */
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
/* Register a SELECTOR_ROLE_PATH role bound to an explicit derivation path template. */
|
||||
int role_table_register_role_path(role_table_t *table, const char *name, const char *path,
|
||||
role_purpose_t purpose, role_curve_t curve,
|
||||
int range_lo, int range_hi, int default_index,
|
||||
const int *allowed_indices, int allowed_count);
|
||||
/* from selector.h */
|
||||
|
||||
|
||||
@@ -177,6 +186,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -101,6 +101,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -118,7 +123,7 @@ role_purpose_t role_purpose_from_str(const char *s);
|
||||
role_curve_t role_curve_from_str(const char *s);
|
||||
const char *role_purpose_to_str(role_purpose_t p);
|
||||
const char *role_curve_to_str(role_curve_t c);
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
|
||||
/* from selector.h */
|
||||
@@ -136,6 +141,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -174,6 +179,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -118,6 +118,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -176,6 +181,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -80,6 +80,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -97,7 +102,7 @@ role_purpose_t role_purpose_from_str(const char *s);
|
||||
role_curve_t role_curve_from_str(const char *s);
|
||||
const char *role_purpose_to_str(role_purpose_t p);
|
||||
const char *role_curve_to_str(role_curve_t c);
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
/* from selector.h */
|
||||
|
||||
@@ -113,6 +118,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -97,6 +97,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -128,6 +133,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -73,6 +73,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -101,6 +106,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -174,6 +179,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* test_path_whitelist.c — tests for the path-template whitelist and
|
||||
* named path-role functionality.
|
||||
*
|
||||
* Covers:
|
||||
* - server_set_path_whitelist parsing (integer + path-template tokens)
|
||||
* - server_path_whitelist_allows matching
|
||||
* - role_table_register_role_path (idempotent, range fields)
|
||||
* - derive_secp256k1_from_path (BIP-44 path parsing + derivation)
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_BEGIN */
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <cJSON.h>
|
||||
|
||||
/* from secure_mem.h */
|
||||
typedef struct {
|
||||
void *data;
|
||||
size_t size;
|
||||
int locked;
|
||||
} secure_buf_t;
|
||||
|
||||
int secure_buf_alloc(secure_buf_t *buf, size_t size);
|
||||
void secure_buf_free(secure_buf_t *buf);
|
||||
void secure_memzero(void *ptr, size_t len);
|
||||
|
||||
/* from mnemonic.h */
|
||||
#define MNEMONIC_MAX_LEN 256
|
||||
typedef struct {
|
||||
secure_buf_t buf;
|
||||
int loaded;
|
||||
int word_count;
|
||||
} mnemonic_state_t;
|
||||
|
||||
void mnemonic_init(mnemonic_state_t *state);
|
||||
int mnemonic_load(mnemonic_state_t *state, const char *phrase);
|
||||
void mnemonic_unload(mnemonic_state_t *state);
|
||||
int mnemonic_is_loaded(const mnemonic_state_t *state);
|
||||
const char *mnemonic_get_phrase(const mnemonic_state_t *state);
|
||||
|
||||
/* from role_table.h */
|
||||
#define ROLE_NAME_MAX 64
|
||||
#define ROLE_PATH_MAX 128
|
||||
#define ROLE_PURPOSE_MAX 32
|
||||
#define ROLE_CURVE_MAX 16
|
||||
#define ROLE_PUBKEY_HEX_MAX 66
|
||||
#define ROLE_TABLE_MAX_ENTRIES 256
|
||||
|
||||
typedef enum {
|
||||
PURPOSE_NOSTR = 0,
|
||||
PURPOSE_BITCOIN,
|
||||
PURPOSE_SSH,
|
||||
PURPOSE_AGE,
|
||||
PURPOSE_FIPS,
|
||||
PURPOSE_PQ_SIG,
|
||||
PURPOSE_PQ_KEM,
|
||||
PURPOSE_UNKNOWN
|
||||
} role_purpose_t;
|
||||
|
||||
typedef enum {
|
||||
CURVE_SECP256K1 = 0,
|
||||
CURVE_ED25519,
|
||||
CURVE_X25519,
|
||||
CURVE_ML_DSA_65,
|
||||
CURVE_SLH_DSA_128S,
|
||||
CURVE_ML_KEM_768,
|
||||
CURVE_UNKNOWN
|
||||
} role_curve_t;
|
||||
|
||||
typedef enum {
|
||||
SELECTOR_NOSTR_INDEX,
|
||||
SELECTOR_ROLE_PATH
|
||||
} role_selector_type_t;
|
||||
|
||||
typedef struct {
|
||||
char name[ROLE_NAME_MAX];
|
||||
char purpose_str[ROLE_PURPOSE_MAX];
|
||||
char curve_str[ROLE_CURVE_MAX];
|
||||
role_purpose_t purpose;
|
||||
role_curve_t curve;
|
||||
role_selector_type_t selector_type;
|
||||
int nostr_index;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo;
|
||||
int path_range_hi;
|
||||
int path_default_index;
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
role_entry_t entries[ROLE_TABLE_MAX_ENTRIES];
|
||||
int count;
|
||||
} role_table_t;
|
||||
|
||||
void role_table_init(role_table_t *table);
|
||||
int role_table_add(role_table_t *table, const role_entry_t *entry);
|
||||
role_entry_t *role_table_find_by_name(role_table_t *table, const char *name);
|
||||
role_entry_t *role_table_find_by_nostr_index(role_table_t *table, int index);
|
||||
role_entry_t *role_table_find_by_path(role_table_t *table, const char *path);
|
||||
role_purpose_t role_purpose_from_str(const char *s);
|
||||
role_curve_t role_curve_from_str(const char *s);
|
||||
const char *role_purpose_to_str(role_purpose_t p);
|
||||
const char *role_curve_to_str(role_curve_t c);
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
/* Register a SELECTOR_ROLE_PATH role bound to an explicit derivation path template. */
|
||||
int role_table_register_role_path(role_table_t *table, const char *name, const char *path,
|
||||
role_purpose_t purpose, role_curve_t curve,
|
||||
int range_lo, int range_hi, int default_index,
|
||||
const int *allowed_indices, int allowed_count);
|
||||
|
||||
/* from selector.h */
|
||||
#define SELECTOR_OK 0
|
||||
#define SELECTOR_ERR_AMBIGUOUS -1
|
||||
#define SELECTOR_ERR_NOT_FOUND -2
|
||||
#define SELECTOR_ERR_NO_DEFAULT -3
|
||||
|
||||
typedef struct {
|
||||
int has_role;
|
||||
char role_name[ROLE_NAME_MAX];
|
||||
int has_nostr_index;
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index;
|
||||
int index;
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
int selector_resolve(const selector_request_t *req, role_table_t *table, role_entry_t **out);
|
||||
|
||||
/* from enforcement.h */
|
||||
#define ENFORCE_OK 0
|
||||
#define ENFORCE_ERR_PURPOSE -1
|
||||
#define ENFORCE_ERR_CURVE -2
|
||||
#define ENFORCE_ERR_UNKNOWN_VERB -3
|
||||
#define ENFORCE_ERR_ALGORITHM -4
|
||||
|
||||
#define VERB_SIGN "sign"
|
||||
#define VERB_VERIFY "verify"
|
||||
#define VERB_ENCAPSULATE "encapsulate"
|
||||
#define VERB_DECAPSULATE "decapsulate"
|
||||
#define VERB_DERIVE_SHARED "derive_shared_secret"
|
||||
#define VERB_DERIVE "derive"
|
||||
#define VERB_GET_PUBLIC_KEY "get_public_key"
|
||||
|
||||
#define VERB_NOSTR_GET_PUBLIC_KEY "nostr_get_public_key"
|
||||
#define VERB_NOSTR_SIGN_EVENT "nostr_sign_event"
|
||||
#define VERB_NOSTR_MINE_EVENT "nostr_mine_event"
|
||||
#define VERB_NOSTR_NIP44_ENCRYPT "nostr_nip44_encrypt"
|
||||
#define VERB_NOSTR_NIP44_DECRYPT "nostr_nip44_decrypt"
|
||||
#define VERB_NOSTR_NIP04_ENCRYPT "nostr_nip04_encrypt"
|
||||
#define VERB_NOSTR_NIP04_DECRYPT "nostr_nip04_decrypt"
|
||||
|
||||
#define VERB_ENCRYPT "encrypt"
|
||||
#define VERB_DECRYPT "decrypt"
|
||||
|
||||
int enforce_verb_role(const char *verb, const role_entry_t *role);
|
||||
|
||||
/* from pq_crypto.h */
|
||||
typedef enum {
|
||||
CRYPTO_ALG_SECP256K1 = 0,
|
||||
CRYPTO_ALG_ED25519,
|
||||
CRYPTO_ALG_X25519,
|
||||
CRYPTO_ALG_ML_DSA_65,
|
||||
CRYPTO_ALG_SLH_DSA_128S,
|
||||
CRYPTO_ALG_ML_KEM_768,
|
||||
CRYPTO_ALG_UNKNOWN
|
||||
} crypto_alg_t;
|
||||
|
||||
typedef struct {
|
||||
size_t priv_key_len;
|
||||
size_t pub_key_len;
|
||||
size_t sig_len;
|
||||
size_t ciphertext_len;
|
||||
size_t shared_secret_len;
|
||||
} crypto_alg_sizes_t;
|
||||
|
||||
const crypto_alg_sizes_t *crypto_alg_get_sizes(crypto_alg_t alg);
|
||||
crypto_alg_t crypto_alg_from_role(role_curve_t curve, role_purpose_t purpose);
|
||||
const char *crypto_alg_to_str(crypto_alg_t alg);
|
||||
crypto_alg_t crypto_alg_from_str(const char *s);
|
||||
|
||||
/* from key_store.h */
|
||||
#define KEY_STORE_MAX_ROLES ROLE_TABLE_MAX_ENTRIES
|
||||
|
||||
typedef struct {
|
||||
secure_buf_t private_key;
|
||||
secure_buf_t public_key;
|
||||
char pubkey_hex[8192]; /* hex-encoded public key (PQ pubkeys are large) */
|
||||
char npub[128]; /* bech32 npub (secp256k1 only, empty for others) */
|
||||
crypto_alg_t alg;
|
||||
int valid;
|
||||
} derived_key_t;
|
||||
|
||||
typedef struct {
|
||||
derived_key_t keys[KEY_STORE_MAX_ROLES];
|
||||
int count;
|
||||
} key_store_t;
|
||||
|
||||
int crypto_derive_all(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic);
|
||||
int crypto_derive_one(key_store_t *store, role_table_t *table, const mnemonic_state_t *mnemonic, int role_index);
|
||||
|
||||
/* nostr init/cleanup */
|
||||
int nostr_init(void);
|
||||
void nostr_cleanup(void);
|
||||
|
||||
/* from server.h (minimal subset for whitelist tests) */
|
||||
#define SERVER_SOCKET_NAME_MAX 108
|
||||
#define INDEX_WHITELIST_MAX 256
|
||||
#define INDEX_WHITELIST_BITMAP_SIZE (INDEX_WHITELIST_MAX / 8)
|
||||
|
||||
#define PATH_WHITELIST_MAX_TEMPLATES 16
|
||||
#define PATH_TEMPLATE_MAX_LEN 128
|
||||
|
||||
typedef struct {
|
||||
char template[PATH_TEMPLATE_MAX_LEN];
|
||||
int range_lo;
|
||||
int range_hi;
|
||||
int allowed_indices[64];
|
||||
int allowed_count;
|
||||
} path_template_t;
|
||||
|
||||
typedef struct {
|
||||
int active;
|
||||
int count;
|
||||
path_template_t templates[PATH_WHITELIST_MAX_TEMPLATES];
|
||||
} path_whitelist_t;
|
||||
|
||||
typedef struct {
|
||||
char socket_name[SERVER_SOCKET_NAME_MAX];
|
||||
char last_error[256];
|
||||
int listen_fd;
|
||||
int running;
|
||||
int listen_mode;
|
||||
int stdio_handled;
|
||||
void *dispatcher; /* dummy */
|
||||
void *policy; /* dummy */
|
||||
int socket_name_explicit;
|
||||
int auth_mode;
|
||||
int auth_skew_seconds;
|
||||
int bridge_source_trusted;
|
||||
int index_whitelist_active;
|
||||
unsigned char index_whitelist[INDEX_WHITELIST_BITMAP_SIZE];
|
||||
path_whitelist_t path_whitelist;
|
||||
} server_ctx_t;
|
||||
|
||||
int server_set_index_whitelist(server_ctx_t *ctx, const char *spec);
|
||||
int server_set_path_whitelist(server_ctx_t *ctx, const char *spec);
|
||||
int server_path_whitelist_allows(const server_ctx_t *ctx, const char *role_path);
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int tests_run = 0;
|
||||
static int tests_passed = 0;
|
||||
|
||||
static void check(const char *desc, int condition) {
|
||||
tests_run++;
|
||||
if (condition) {
|
||||
tests_passed++;
|
||||
printf("PASS: %s\n", desc);
|
||||
} else {
|
||||
printf("FAIL: %s\n", desc);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
server_ctx_t ctx;
|
||||
|
||||
/* ---- Test 1: server_set_path_whitelist with "all" ---- */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
check("set_path_whitelist 'all' returns 0",
|
||||
server_set_path_whitelist(&ctx, "all") == 0);
|
||||
check("'all' sets index_whitelist_active=0",
|
||||
ctx.index_whitelist_active == 0);
|
||||
check("'all' sets path_whitelist.active=0",
|
||||
ctx.path_whitelist.active == 0);
|
||||
|
||||
/* ---- Test 2: integer-only spec (backward compat) ---- */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
check("set_path_whitelist '0-3' returns 0",
|
||||
server_set_path_whitelist(&ctx, "0-3") == 0);
|
||||
check("'0-3' sets index_whitelist_active=1",
|
||||
ctx.index_whitelist_active == 1);
|
||||
check("'0-3' does not set path_whitelist.active",
|
||||
ctx.path_whitelist.active == 0);
|
||||
|
||||
/* ---- Test 3: path-template spec ---- */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
check("set_path_whitelist 'm/44\\'/1237\\'/0-3/1/0' returns 0",
|
||||
server_set_path_whitelist(&ctx, "m/44'/1237'/0-3/1/0") == 0);
|
||||
check("path template sets path_whitelist.active=1",
|
||||
ctx.path_whitelist.active == 1);
|
||||
check("path template count=1",
|
||||
ctx.path_whitelist.count == 1);
|
||||
check("path template range_lo=0",
|
||||
ctx.path_whitelist.templates[0].range_lo == 0);
|
||||
check("path template range_hi=3",
|
||||
ctx.path_whitelist.templates[0].range_hi == 3);
|
||||
|
||||
/* ---- Test 4: server_path_whitelist_allows matching ---- */
|
||||
check("path_whitelist_allows m/44'/1237'/1/1/0 (in range)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/1/1/0") == 1);
|
||||
check("path_whitelist_allows m/44'/1237'/0/1/0 (in range)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/0/1/0") == 1);
|
||||
check("path_whitelist_allows m/44'/1237'/3/1/0 (in range)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/3/1/0") == 1);
|
||||
check("path_whitelist denies m/44'/1237'/4/1/0 (out of range)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/4/1/0") == 0);
|
||||
check("path_whitelist denies m/44'/1237'/1/0/0 (wrong change)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/1/0/0") == 0);
|
||||
|
||||
/* ---- Test 5: multiple path templates ---- */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
check("set_path_whitelist with two templates returns 0",
|
||||
server_set_path_whitelist(&ctx,
|
||||
"m/44'/1237'/0-3/0/0,m/44'/1237'/0-3/1/0") == 0);
|
||||
check("two templates: count=2",
|
||||
ctx.path_whitelist.count == 2);
|
||||
check("two templates: allows m/44'/1237'/2/0/0",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/2/0/0") == 1);
|
||||
check("two templates: allows m/44'/1237'/2/1/0",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/2/1/0") == 1);
|
||||
check("two templates: denies m/44'/1237'/2/2/0",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/2/2/0") == 0);
|
||||
|
||||
/* ---- Test 6: no path whitelist configured → deny ---- */
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
check("no path whitelist denies all paths (fail-closed)",
|
||||
server_path_whitelist_allows(&ctx, "m/44'/1237'/1/1/0") == 0);
|
||||
|
||||
/* ---- Test 7: role_table_register_role_path ---- */
|
||||
{
|
||||
role_table_t table;
|
||||
role_table_init(&table);
|
||||
check("register_role_path returns 0",
|
||||
role_table_register_role_path(&table, "myrole",
|
||||
"m/44'/1237'/%d/1/0",
|
||||
PURPOSE_NOSTR, CURVE_SECP256K1,
|
||||
0, 3, 1, NULL, 0) == 0);
|
||||
role_entry_t *r = role_table_find_by_name(&table, "myrole");
|
||||
check("registered role found by name", r != NULL);
|
||||
check("registered role is SELECTOR_ROLE_PATH",
|
||||
r != NULL && r->selector_type == SELECTOR_ROLE_PATH);
|
||||
check("registered role path_range_lo=0",
|
||||
r != NULL && r->path_range_lo == 0);
|
||||
check("registered role path_range_hi=3",
|
||||
r != NULL && r->path_range_hi == 3);
|
||||
check("registered role path_default_index=1",
|
||||
r != NULL && r->path_default_index == 1);
|
||||
check("registered role purpose=NOSTR",
|
||||
r != NULL && r->purpose == PURPOSE_NOSTR);
|
||||
check("registered role curve=SECP256K1",
|
||||
r != NULL && r->curve == CURVE_SECP256K1);
|
||||
|
||||
/* Idempotent: registering the same path again returns 0, no duplicate */
|
||||
check("register_role_path idempotent returns 0",
|
||||
role_table_register_role_path(&table, "other",
|
||||
"m/44'/1237'/%d/1/0",
|
||||
PURPOSE_NOSTR, CURVE_SECP256K1,
|
||||
0, 3, 1, NULL, 0) == 0);
|
||||
check("idempotent: no duplicate added",
|
||||
table.count == 1);
|
||||
}
|
||||
|
||||
/* ---- Test 8: end-to-end derivation with role_path ---- */
|
||||
{
|
||||
role_table_t table;
|
||||
key_store_t key_store;
|
||||
mnemonic_state_t mnemonic;
|
||||
const char *valid_12 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
||||
int rc;
|
||||
|
||||
role_table_init(&table);
|
||||
mnemonic_init(&mnemonic);
|
||||
|
||||
/* Load mnemonic */
|
||||
rc = mnemonic_load(&mnemonic, valid_12);
|
||||
check("mnemonic load succeeds", rc == 0);
|
||||
|
||||
/* Register a fixed-path role (no %d) */
|
||||
rc = role_table_register_role_path(&table, "testpath",
|
||||
"m/44'/1237'/1/1/0",
|
||||
PURPOSE_NOSTR, CURVE_SECP256K1,
|
||||
0, 0, -1, NULL, 0);
|
||||
check("register fixed-path role returns 0", rc == 0);
|
||||
|
||||
/* Derive all keys */
|
||||
if (nostr_init() != 0) {
|
||||
check("nostr_init succeeds", 0);
|
||||
mnemonic_unload(&mnemonic);
|
||||
printf("\n%d/%d tests passed\n", tests_passed, tests_run);
|
||||
return (tests_passed == tests_run) ? 0 : 1;
|
||||
}
|
||||
check("nostr_init succeeds", 1);
|
||||
memset(&key_store, 0, sizeof(key_store));
|
||||
rc = crypto_derive_all(&key_store, &table, &mnemonic);
|
||||
check("crypto_derive_all with path role succeeds", rc >= 0);
|
||||
|
||||
/* Find the role and check it was derived */
|
||||
role_entry_t *r = role_table_find_by_name(&table, "testpath");
|
||||
check("testpath role found", r != NULL);
|
||||
check("testpath role derived", r != NULL && r->derived == 1);
|
||||
check("testpath pubkey is 64 hex chars",
|
||||
r != NULL && strlen(r->pubkey_hex) == 64);
|
||||
|
||||
/* Verify the pubkey matches the expected NIP-06 index-1 derivation
|
||||
* (m/44'/1237'/1'/0/0) — this is a sanity check that the path
|
||||
* derivation produces a valid key. The path m/44'/1237'/1/1/0 is
|
||||
* different from NIP-06 so the pubkey should differ from index 1. */
|
||||
{
|
||||
role_table_t nip06_table;
|
||||
key_store_t nip06_store;
|
||||
role_table_init(&nip06_table);
|
||||
role_table_register_nostr_index(&nip06_table, 1);
|
||||
memset(&nip06_store, 0, sizeof(nip06_store));
|
||||
crypto_derive_all(&nip06_store, &nip06_table, &mnemonic);
|
||||
role_entry_t *nip06_r = role_table_find_by_nostr_index(&nip06_table, 1);
|
||||
check("NIP-06 index 1 derived",
|
||||
nip06_r != NULL && nip06_r->derived == 1);
|
||||
check("path m/44'/1237'/1/1/0 differs from NIP-06 index 1 (m/44'/1237'/1'/0/0)",
|
||||
r != NULL && nip06_r != NULL &&
|
||||
strcmp(r->pubkey_hex, nip06_r->pubkey_hex) != 0);
|
||||
}
|
||||
|
||||
mnemonic_unload(&mnemonic);
|
||||
}
|
||||
|
||||
nostr_cleanup();
|
||||
printf("\n%d/%d tests passed\n", tests_passed, tests_run);
|
||||
return (tests_passed == tests_run) ? 0 : 1;
|
||||
}
|
||||
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -174,6 +179,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -98,6 +98,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -115,7 +120,7 @@ role_purpose_t role_purpose_from_str(const char *s);
|
||||
role_curve_t role_curve_from_str(const char *s);
|
||||
const char *role_purpose_to_str(role_purpose_t p);
|
||||
const char *role_curve_to_str(role_curve_t c);
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
|
||||
/* from selector.h */
|
||||
@@ -133,6 +138,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -95,6 +95,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -112,7 +117,7 @@ role_purpose_t role_purpose_from_str(const char *s);
|
||||
role_curve_t role_curve_from_str(const char *s);
|
||||
const char *role_purpose_to_str(role_purpose_t p);
|
||||
const char *role_curve_to_str(role_curve_t c);
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
|
||||
/* from selector.h */
|
||||
@@ -130,6 +135,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
+12
-1
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -157,7 +162,11 @@ const char *role_curve_to_str(role_curve_t c);
|
||||
/* Register a nostr-index role if missing. Returns 0 on success, -1 on error. */
|
||||
int role_table_register_nostr_index(role_table_t *table, int nostr_index);
|
||||
|
||||
|
||||
/* Register a SELECTOR_ROLE_PATH role bound to an explicit derivation path template. */
|
||||
int role_table_register_role_path(role_table_t *table, const char *name, const char *path,
|
||||
role_purpose_t purpose, role_curve_t curve,
|
||||
int range_lo, int range_hi, int default_index,
|
||||
const int *allowed_indices, int allowed_count);
|
||||
/* from selector.h */
|
||||
|
||||
|
||||
@@ -177,6 +186,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -174,6 +179,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
@@ -97,6 +97,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX];
|
||||
int derived;
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -128,6 +133,8 @@ typedef struct {
|
||||
int nostr_index;
|
||||
int has_role_path;
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
void selector_request_init(selector_request_t *req);
|
||||
|
||||
@@ -116,6 +116,11 @@ typedef struct {
|
||||
char role_path[ROLE_PATH_MAX]; /* valid if selector_type == SELECTOR_ROLE_PATH */
|
||||
char pubkey_hex[ROLE_PUBKEY_HEX_MAX]; /* filled after derivation, empty until then */
|
||||
int derived; /* 1 if pubkey_hex has been populated */
|
||||
int path_range_lo; /* for SELECTOR_ROLE_PATH: inclusive lower bound for %d; -1 = fixed path */
|
||||
int path_range_hi; /* inclusive upper bound; == path_range_lo for single */
|
||||
int path_default_index; /* default index when client sends {"role":...} without "index"; -1 = require explicit */
|
||||
int path_allowed_indices[64]; /* explicit set of allowed indices (for sets); 0 = use range */
|
||||
int path_allowed_count; /* 0 = use range_lo/range_hi; >0 = use allowed_indices */
|
||||
} role_entry_t;
|
||||
|
||||
/* The role table */
|
||||
@@ -174,6 +179,8 @@ typedef struct {
|
||||
|
||||
int has_role_path; /* 1 if "role_path" field was present */
|
||||
char role_path[ROLE_PATH_MAX];
|
||||
int has_index; /* 1 if "index" field was present (for named path-roles) */
|
||||
int index; /* index value for named path-role template */
|
||||
} selector_request_t;
|
||||
|
||||
/* Initialize a selector request (all fields zeroed/unset) */
|
||||
|
||||
Reference in New Issue
Block a user