228 lines
8.2 KiB
C
228 lines
8.2 KiB
C
#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;
|
|
}
|