284 lines
15 KiB
C
284 lines
15 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "tools_internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cjson/cJSON.h"
|
|
#include "../debug.h"
|
|
#include "../nostr_handler.h"
|
|
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
|
|
|
|
static char* json_error_local(const char* msg) {
|
|
cJSON* root = cJSON_CreateObject();
|
|
if (!root) return NULL;
|
|
cJSON_AddBoolToObject(root, "success", 0);
|
|
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
|
|
char* out = cJSON_PrintUnformatted(root);
|
|
cJSON_Delete(root);
|
|
return out;
|
|
}
|
|
|
|
/* Unified signer_crypto tool — exposes all algorithm-based n_signer verbs
|
|
* through a single tool with an "operation" enum. Requires a remote n_signer;
|
|
* returns a clear error in local mode since the local backend does not support
|
|
* algorithm-based verbs (PQ crypto, OTP, etc.). */
|
|
char* execute_signer_crypto(tools_context_t* ctx, const char* args_json) {
|
|
if (!ctx) return json_error_local("tool context unavailable");
|
|
|
|
nostr_signer_t* signer = ctx->signer;
|
|
if (!signer) {
|
|
/* Fall back to the process-lifetime signer if the tools context
|
|
* doesn't have one wired (e.g. during startup). */
|
|
signer = nostr_handler_get_signer();
|
|
}
|
|
if (!signer) {
|
|
return json_error_local("signer_crypto requires a remote n_signer "
|
|
"(local mode does not support algorithm-based verbs)");
|
|
}
|
|
|
|
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
|
|
if (!args) return json_error_local("invalid arguments JSON");
|
|
|
|
cJSON* op_json = cJSON_GetObjectItemCaseSensitive(args, "operation");
|
|
if (!op_json || !cJSON_IsString(op_json) || !op_json->valuestring || op_json->valuestring[0] == '\0') {
|
|
cJSON_Delete(args);
|
|
return json_error_local("signer_crypto requires a string 'operation' field");
|
|
}
|
|
const char* operation = op_json->valuestring;
|
|
|
|
cJSON* algorithm = cJSON_GetObjectItemCaseSensitive(args, "algorithm");
|
|
const char* alg_str = (algorithm && cJSON_IsString(algorithm) && algorithm->valuestring)
|
|
? algorithm->valuestring : NULL;
|
|
cJSON* index_json = cJSON_GetObjectItemCaseSensitive(args, "index");
|
|
int index = (index_json && cJSON_IsNumber(index_json)) ? (int)index_json->valuedouble : 0;
|
|
cJSON* scheme_json = cJSON_GetObjectItemCaseSensitive(args, "scheme");
|
|
const char* scheme = (scheme_json && cJSON_IsString(scheme_json) && scheme_json->valuestring)
|
|
? scheme_json->valuestring : NULL;
|
|
|
|
cJSON* result = cJSON_CreateObject();
|
|
if (!result) { cJSON_Delete(args); return NULL; }
|
|
cJSON_AddBoolToObject(result, "success", 1);
|
|
cJSON_AddStringToObject(result, "operation", operation);
|
|
|
|
int rc;
|
|
char* result_str = NULL;
|
|
cJSON* info_out = NULL;
|
|
int valid_out = 0;
|
|
|
|
if (strcmp(operation, "get_info") == 0) {
|
|
rc = nostr_signer_get_info(signer, &info_out);
|
|
if (rc == NOSTR_SUCCESS && info_out) {
|
|
char* info_json = cJSON_PrintUnformatted(info_out);
|
|
cJSON_AddStringToObject(result, "info", info_json ? info_json : "{}");
|
|
free(info_json);
|
|
cJSON_Delete(info_out);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "get_info failed");
|
|
}
|
|
} else if (strcmp(operation, "get_public_key") == 0) {
|
|
if (!alg_str) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "get_public_key requires 'algorithm' (e.g. secp256k1, ed25519, ml-dsa-65)");
|
|
} else {
|
|
rc = nostr_signer_get_public_key_alg(signer, alg_str, index, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "get_public_key failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "sign") == 0) {
|
|
cJSON* msg_json = cJSON_GetObjectItemCaseSensitive(args, "message_hex");
|
|
if (!msg_json || !cJSON_IsString(msg_json) || !msg_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "sign requires 'message_hex' (hex-encoded message bytes)");
|
|
} else if (!alg_str) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "sign requires 'algorithm' (e.g. secp256k1, ed25519, ml-dsa-65, slh-dsa-128s)");
|
|
} else {
|
|
size_t msg_hex_len = strlen(msg_json->valuestring);
|
|
size_t msg_bytes_len = msg_hex_len / 2;
|
|
unsigned char* msg_bytes = (unsigned char*)malloc(msg_bytes_len + 1);
|
|
if (!msg_bytes) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "memory allocation failed");
|
|
} else if (nostr_hex_to_bytes(msg_json->valuestring, msg_bytes, (int)msg_bytes_len) != 0) {
|
|
free(msg_bytes);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "invalid message_hex");
|
|
} else {
|
|
rc = nostr_signer_sign(signer, alg_str, index, scheme, msg_bytes, msg_bytes_len, &result_str);
|
|
free(msg_bytes);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "sign failed");
|
|
}
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "verify") == 0) {
|
|
cJSON* msg_json = cJSON_GetObjectItemCaseSensitive(args, "message_hex");
|
|
cJSON* sig_json = cJSON_GetObjectItemCaseSensitive(args, "signature_hex");
|
|
if (!msg_json || !cJSON_IsString(msg_json) || !msg_json->valuestring ||
|
|
!sig_json || !cJSON_IsString(sig_json) || !sig_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "verify requires 'message_hex' and 'signature_hex'");
|
|
} else if (!alg_str) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "verify requires 'algorithm'");
|
|
} else {
|
|
size_t msg_hex_len = strlen(msg_json->valuestring);
|
|
size_t msg_bytes_len = msg_hex_len / 2;
|
|
unsigned char* msg_bytes = (unsigned char*)malloc(msg_bytes_len + 1);
|
|
size_t sig_hex_len = strlen(sig_json->valuestring);
|
|
size_t sig_bytes_len = sig_hex_len / 2;
|
|
unsigned char* sig_bytes = (unsigned char*)malloc(sig_bytes_len + 1);
|
|
if (!msg_bytes || !sig_bytes) {
|
|
free(msg_bytes); free(sig_bytes);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "memory allocation failed");
|
|
} else if (nostr_hex_to_bytes(msg_json->valuestring, msg_bytes, (int)msg_bytes_len) != 0 ||
|
|
nostr_hex_to_bytes(sig_json->valuestring, sig_bytes, (int)sig_bytes_len) != 0) {
|
|
free(msg_bytes); free(sig_bytes);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "invalid hex in message_hex or signature_hex");
|
|
} else {
|
|
rc = nostr_signer_verify(signer, alg_str, index, scheme,
|
|
msg_bytes, msg_bytes_len,
|
|
sig_bytes, sig_bytes_len,
|
|
&valid_out);
|
|
free(msg_bytes); free(sig_bytes);
|
|
if (rc == NOSTR_SUCCESS) {
|
|
cJSON_AddBoolToObject(result, "valid", valid_out);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "verify failed");
|
|
}
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "encapsulate") == 0) {
|
|
cJSON* peer_json = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey_hex");
|
|
if (!peer_json || !cJSON_IsString(peer_json) || !peer_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "encapsulate requires 'peer_pubkey_hex' (ML-KEM-768 public key)");
|
|
} else {
|
|
rc = nostr_signer_encapsulate(signer, peer_json->valuestring, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "encapsulate failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "decapsulate") == 0) {
|
|
cJSON* ct_json = cJSON_GetObjectItemCaseSensitive(args, "ciphertext_hex");
|
|
if (!ct_json || !cJSON_IsString(ct_json) || !ct_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "decapsulate requires 'ciphertext_hex' and 'index'");
|
|
} else {
|
|
rc = nostr_signer_decapsulate(signer, index, ct_json->valuestring, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "decapsulate failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "derive_shared_secret") == 0) {
|
|
cJSON* peer_json = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey_hex");
|
|
if (!peer_json || !cJSON_IsString(peer_json) || !peer_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "derive_shared_secret requires 'peer_pubkey_hex' (X25519) and 'index'");
|
|
} else {
|
|
rc = nostr_signer_derive_shared_secret(signer, index, peer_json->valuestring, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "derive_shared_secret failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "derive_hmac") == 0) {
|
|
cJSON* data_json = cJSON_GetObjectItemCaseSensitive(args, "data");
|
|
if (!data_json || !cJSON_IsString(data_json) || !data_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "derive_hmac requires 'data' (UTF-8 string)");
|
|
} else {
|
|
rc = nostr_signer_derive_hmac(signer, data_json->valuestring, (char[65]){0});
|
|
if (rc == NOSTR_SUCCESS) {
|
|
char digest_hex[65] = {0};
|
|
nostr_signer_derive_hmac(signer, data_json->valuestring, digest_hex);
|
|
cJSON_AddStringToObject(result, "digest_hex", digest_hex);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "derive_hmac failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "otp_encrypt") == 0) {
|
|
cJSON* pt_json = cJSON_GetObjectItemCaseSensitive(args, "plaintext_b64");
|
|
cJSON* enc_json = cJSON_GetObjectItemCaseSensitive(args, "encoding");
|
|
const char* encoding = (enc_json && cJSON_IsString(enc_json) && enc_json->valuestring)
|
|
? enc_json->valuestring : NULL;
|
|
if (!pt_json || !cJSON_IsString(pt_json) || !pt_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "otp_encrypt requires 'plaintext_b64' (base64-encoded plaintext)");
|
|
} else {
|
|
rc = nostr_signer_otp_encrypt(signer, pt_json->valuestring, encoding, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "otp_encrypt failed");
|
|
}
|
|
}
|
|
} else if (strcmp(operation, "otp_decrypt") == 0) {
|
|
cJSON* ct_json = cJSON_GetObjectItemCaseSensitive(args, "ciphertext");
|
|
cJSON* enc_json = cJSON_GetObjectItemCaseSensitive(args, "encoding");
|
|
const char* encoding = (enc_json && cJSON_IsString(enc_json) && enc_json->valuestring)
|
|
? enc_json->valuestring : NULL;
|
|
if (!ct_json || !cJSON_IsString(ct_json) || !ct_json->valuestring) {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "otp_decrypt requires 'ciphertext' (ASCII-armored or base64 blob)");
|
|
} else {
|
|
rc = nostr_signer_otp_decrypt(signer, ct_json->valuestring, encoding, &result_str);
|
|
if (rc == NOSTR_SUCCESS && result_str) {
|
|
cJSON_AddStringToObject(result, "result", result_str);
|
|
free(result_str);
|
|
} else {
|
|
const char* le = nostr_signer_last_error(signer);
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "otp_decrypt failed");
|
|
}
|
|
}
|
|
} else {
|
|
cJSON_AddBoolToObject(result, "success", 0);
|
|
cJSON_AddStringToObject(result, "error", "unknown operation");
|
|
}
|
|
|
|
cJSON_Delete(args);
|
|
char* json = cJSON_PrintUnformatted(result);
|
|
cJSON_Delete(result);
|
|
return json;
|
|
}
|