v0.0.20 - Implement TCP auth envelope verification with signed caller pubkey identity, replay protection, tests, and deferred NIP-46 bunker plan
This commit is contained in:
227
tests/test_auth_envelope.c
Normal file
227
tests/test_auth_envelope.c
Normal file
@@ -0,0 +1,227 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <nostr_core/nip001.h>
|
||||
#include <nostr_core/utils.h>
|
||||
|
||||
#include "../src/auth_envelope.h"
|
||||
|
||||
static int g_failures = 0;
|
||||
|
||||
static void check_condition(const char *name, int ok) {
|
||||
if (ok) {
|
||||
printf("[PASS] %s\n", name);
|
||||
} else {
|
||||
printf("[FAIL] %s\n", name);
|
||||
g_failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static int compute_params_hash_hex(cJSON *params, char out_hex[65]) {
|
||||
char *compact;
|
||||
unsigned char hash[32];
|
||||
|
||||
if (params == NULL || out_hex == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
compact = cJSON_PrintUnformatted(params);
|
||||
if (compact == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nostr_sha256((const unsigned char *)compact, strlen(compact), hash) != 0) {
|
||||
free(compact);
|
||||
return -1;
|
||||
}
|
||||
|
||||
nostr_bytes_to_hex(hash, 32, out_hex);
|
||||
out_hex[64] = '\0';
|
||||
free(compact);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cJSON *tag_pair(const char *k, const char *v) {
|
||||
cJSON *arr = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(arr, cJSON_CreateString(k));
|
||||
cJSON_AddItemToArray(arr, cJSON_CreateString(v));
|
||||
return arr;
|
||||
}
|
||||
|
||||
static char *make_request_json(const unsigned char privkey[32],
|
||||
const char *req_id,
|
||||
const char *method,
|
||||
int created_at) {
|
||||
cJSON *params = cJSON_CreateArray();
|
||||
cJSON *options = cJSON_CreateObject();
|
||||
cJSON *tags = cJSON_CreateArray();
|
||||
cJSON *auth;
|
||||
cJSON *root;
|
||||
char body_hash[65];
|
||||
char *out;
|
||||
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString("{\"kind\":1,\"content\":\"x\",\"tags\":[],\"created_at\":1700000000}"));
|
||||
cJSON_AddStringToObject(options, "role", "main");
|
||||
cJSON_AddItemToArray(params, options);
|
||||
|
||||
if (compute_params_hash_hex(params, body_hash) != 0) {
|
||||
cJSON_Delete(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(tags, tag_pair("nsigner_rpc", req_id));
|
||||
cJSON_AddItemToArray(tags, tag_pair("nsigner_method", method));
|
||||
cJSON_AddItemToArray(tags, tag_pair("nsigner_body_hash", body_hash));
|
||||
|
||||
auth = nostr_create_and_sign_event(AUTH_EVENT_KIND, "test-client", tags, privkey, (time_t)created_at);
|
||||
if (auth == NULL) {
|
||||
cJSON_Delete(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "id", req_id);
|
||||
cJSON_AddStringToObject(root, "method", method);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
cJSON_AddItemToObject(root, "auth", auth);
|
||||
|
||||
out = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
auth_nonce_cache_t cache;
|
||||
unsigned char privkey[32] = {
|
||||
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
char pubkey_hex[65];
|
||||
char label[64];
|
||||
const char *err_msg = NULL;
|
||||
int err_code = 0;
|
||||
char *req;
|
||||
|
||||
check_condition("nostr_crypto_init", nostr_crypto_init() == 0);
|
||||
auth_nonce_cache_init(&cache);
|
||||
|
||||
req = make_request_json(privkey, "req-1", "sign_event", (int)time(NULL));
|
||||
check_condition("build valid request", req != NULL);
|
||||
if (req != NULL) {
|
||||
check_condition("valid auth envelope accepted",
|
||||
auth_envelope_verify_request(req,
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg) == 0);
|
||||
|
||||
check_condition("pubkey output length 64", strlen(pubkey_hex) == 64);
|
||||
free(req);
|
||||
}
|
||||
|
||||
check_condition("missing auth rejected with required code",
|
||||
auth_envelope_verify_request("{\"id\":\"x\",\"method\":\"get_public_key\",\"params\":[]}",
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg) != 0 &&
|
||||
err_code == AUTH_ERR_ENVELOPE_REQUIRED);
|
||||
|
||||
req = make_request_json(privkey, "req-2", "get_public_key", (int)time(NULL));
|
||||
check_condition("build second request", req != NULL);
|
||||
if (req != NULL) {
|
||||
cJSON *root = cJSON_Parse(req);
|
||||
cJSON *method_item;
|
||||
char *tampered;
|
||||
|
||||
check_condition("parse second request", root != NULL);
|
||||
method_item = (root != NULL) ? cJSON_GetObjectItemCaseSensitive(root, "method") : NULL;
|
||||
if (method_item != NULL) {
|
||||
cJSON_SetValuestring(method_item, "sign_event");
|
||||
}
|
||||
tampered = (root != NULL) ? cJSON_PrintUnformatted(root) : NULL;
|
||||
cJSON_Delete(root);
|
||||
|
||||
check_condition("tamper method mismatch rejected",
|
||||
tampered != NULL &&
|
||||
auth_envelope_verify_request(tampered,
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg) != 0 &&
|
||||
err_code == AUTH_ERR_ENVELOPE_MISMATCH);
|
||||
|
||||
free(tampered);
|
||||
free(req);
|
||||
}
|
||||
|
||||
req = make_request_json(privkey, "req-3", "sign_event", (int)time(NULL));
|
||||
check_condition("build replay request", req != NULL);
|
||||
if (req != NULL) {
|
||||
int first_ok = auth_envelope_verify_request(req,
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg);
|
||||
int second_ok = auth_envelope_verify_request(req,
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg);
|
||||
check_condition("first replay request accepted", first_ok == 0);
|
||||
check_condition("second replay request rejected", second_ok != 0 && err_code == AUTH_ERR_REPLAY_DETECTED);
|
||||
free(req);
|
||||
}
|
||||
|
||||
req = make_request_json(privkey, "req-4", "sign_event", (int)time(NULL) - 1000);
|
||||
check_condition("build stale request", req != NULL);
|
||||
if (req != NULL) {
|
||||
check_condition("stale request rejected",
|
||||
auth_envelope_verify_request(req,
|
||||
&cache,
|
||||
AUTH_DEFAULT_SKEW_SECONDS,
|
||||
pubkey_hex,
|
||||
sizeof(pubkey_hex),
|
||||
label,
|
||||
sizeof(label),
|
||||
&err_code,
|
||||
&err_msg) != 0 &&
|
||||
err_code == AUTH_ERR_ENVELOPE_STALE);
|
||||
free(req);
|
||||
}
|
||||
|
||||
nostr_crypto_cleanup();
|
||||
|
||||
if (g_failures == 0) {
|
||||
printf("ALL TESTS PASSED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("TESTS FAILED: %d\n", g_failures);
|
||||
return 1;
|
||||
}
|
||||
@@ -232,7 +232,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -390,7 +390,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -229,7 +229,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -231,7 +231,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -386,7 +386,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -229,7 +229,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -229,7 +229,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -390,7 +390,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -232,7 +232,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -387,7 +387,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -229,7 +229,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
@@ -229,7 +229,7 @@ const char *enforce_strerror(int err);
|
||||
#define POLICY_MAX_ROLES 16
|
||||
#define POLICY_MAX_PURPOSES 8
|
||||
#define POLICY_VERB_MAX_LEN 32
|
||||
#define POLICY_CALLER_MAX_LEN 64
|
||||
#define POLICY_CALLER_MAX_LEN 80
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[64]; /* "uid:<n>" */
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
Reference in New Issue
Block a user