v0.0.25 - Add --allow-all flag and short aliases for CLI options
This commit is contained in:
@@ -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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -390,7 +390,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -386,7 +386,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -390,7 +390,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
@@ -527,6 +527,14 @@ int main(void) {
|
||||
check_condition("parse_preapprove_spec maps nonzero nostr_index role", strcmp(parsed.roles[0], "nostr_idx_2") == 0);
|
||||
check_condition("parse_preapprove_spec stores nonzero nostr_index", parsed_nostr_index == 2);
|
||||
|
||||
rc = parse_preapprove_spec("caller=qubes:personal+pubkey:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef,role=main",
|
||||
&parsed,
|
||||
&parsed_nostr_index);
|
||||
check_condition("parse_preapprove_spec accepts composite qubes+pubkey caller", rc == 0);
|
||||
check_condition("parse_preapprove_spec stores composite caller",
|
||||
strcmp(parsed.caller,
|
||||
"qubes:personal+pubkey:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") == 0);
|
||||
|
||||
rc = parse_preapprove_spec("role=main", &parsed, &parsed_nostr_index);
|
||||
check_condition("parse_preapprove_spec rejects missing caller", rc == -1);
|
||||
rc = parse_preapprove_spec("caller=uid:1000", &parsed, &parsed_nostr_index);
|
||||
|
||||
369
tests/test_qrexec_auth.c
Normal file
369
tests/test_qrexec_auth.c
Normal file
@@ -0,0 +1,369 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <nostr_core/nostr_common.h>
|
||||
|
||||
#include "../src/auth_envelope.h"
|
||||
|
||||
#define MAX_MSG_SIZE 65536
|
||||
|
||||
static int g_failures = 0;
|
||||
|
||||
static void check_condition(const char *name, int condition) {
|
||||
if (condition) {
|
||||
printf("PASS: %s\n", name);
|
||||
} else {
|
||||
printf("FAIL: %s\n", name);
|
||||
g_failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static int write_full(int fd, const void *buf, size_t len) {
|
||||
const unsigned char *p = (const unsigned char *)buf;
|
||||
size_t off = 0;
|
||||
|
||||
while (off < len) {
|
||||
ssize_t n = write(fd, p + off, len - off);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
off += (size_t)n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_framed(int fd, const char *payload) {
|
||||
uint32_t len = (uint32_t)strlen(payload);
|
||||
uint32_t be_len = htonl(len);
|
||||
|
||||
if (write_full(fd, &be_len, sizeof(be_len)) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (write_full(fd, payload, len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int recv_framed(int fd, char **out_payload) {
|
||||
unsigned char *buf = NULL;
|
||||
size_t used = 0;
|
||||
size_t cap = 0;
|
||||
|
||||
if (out_payload == NULL) {
|
||||
return -1;
|
||||
}
|
||||
*out_payload = NULL;
|
||||
|
||||
for (;;) {
|
||||
unsigned char chunk[1024];
|
||||
ssize_t n = read(fd, chunk, sizeof(chunk));
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (used + (size_t)n > cap) {
|
||||
size_t new_cap = (cap == 0) ? 2048 : cap * 2;
|
||||
while (new_cap < used + (size_t)n) {
|
||||
new_cap *= 2;
|
||||
}
|
||||
{
|
||||
unsigned char *tmp = (unsigned char *)realloc(buf, new_cap);
|
||||
if (tmp == NULL) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
buf = tmp;
|
||||
cap = new_cap;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(buf + used, chunk, (size_t)n);
|
||||
used += (size_t)n;
|
||||
}
|
||||
|
||||
if (used < 4) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
size_t off;
|
||||
for (off = 0; off + 4 <= used; ++off) {
|
||||
uint32_t be_len;
|
||||
uint32_t len;
|
||||
|
||||
memcpy(&be_len, buf + off, sizeof(be_len));
|
||||
len = ntohl(be_len);
|
||||
if (len == 0 || len > MAX_MSG_SIZE) {
|
||||
continue;
|
||||
}
|
||||
if (off + 4U + (size_t)len > used) {
|
||||
continue;
|
||||
}
|
||||
if (buf[off + 4] != '{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
char *payload = (char *)malloc((size_t)len + 1U);
|
||||
if (payload == NULL) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
memcpy(payload, buf + off + 4, (size_t)len);
|
||||
payload[len] = '\0';
|
||||
*out_payload = payload;
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *build_auth_request(const unsigned char privkey[32],
|
||||
const char *req_id,
|
||||
const char *method) {
|
||||
cJSON *root = NULL;
|
||||
cJSON *params = NULL;
|
||||
cJSON *auth = NULL;
|
||||
char *printed = NULL;
|
||||
|
||||
params = cJSON_CreateArray();
|
||||
if (params == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(""));
|
||||
|
||||
if (auth_envelope_build_for_request(req_id,
|
||||
method,
|
||||
params,
|
||||
privkey,
|
||||
"qrexec-test",
|
||||
time(NULL),
|
||||
&auth) != 0) {
|
||||
cJSON_Delete(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
cJSON_Delete(params);
|
||||
cJSON_Delete(auth);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddStringToObject(root, "id", req_id);
|
||||
cJSON_AddStringToObject(root, "method", method);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
cJSON_AddItemToObject(root, "auth", auth);
|
||||
|
||||
printed = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
return printed;
|
||||
}
|
||||
|
||||
static int run_qrexec_once(const char *request_json, char **out_response) {
|
||||
int in_pipe[2];
|
||||
int out_pipe[2];
|
||||
pid_t child;
|
||||
int status;
|
||||
const char *mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n";
|
||||
|
||||
if (out_response == NULL || request_json == NULL) {
|
||||
return -1;
|
||||
}
|
||||
*out_response = NULL;
|
||||
|
||||
if (pipe(in_pipe) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (pipe(out_pipe) != 0) {
|
||||
close(in_pipe[0]);
|
||||
close(in_pipe[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
child = fork();
|
||||
if (child < 0) {
|
||||
close(in_pipe[0]);
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
close(out_pipe[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (child == 0) {
|
||||
int null_fd = open("/dev/null", O_WRONLY);
|
||||
dup2(in_pipe[0], STDIN_FILENO);
|
||||
dup2(out_pipe[1], STDOUT_FILENO);
|
||||
if (null_fd >= 0) {
|
||||
dup2(null_fd, STDERR_FILENO);
|
||||
close(null_fd);
|
||||
}
|
||||
|
||||
close(in_pipe[0]);
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
close(out_pipe[1]);
|
||||
|
||||
(void)setenv("QREXEC_REMOTE_DOMAIN", "personal", 1);
|
||||
|
||||
execl("./build/nsigner",
|
||||
"./build/nsigner",
|
||||
"--listen", "qrexec",
|
||||
"--auth", "optional",
|
||||
"--preapprove", "caller=qubes:personal,role=main",
|
||||
(char *)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
close(in_pipe[0]);
|
||||
close(out_pipe[1]);
|
||||
|
||||
if (write_full(in_pipe[1], mnemonic, strlen(mnemonic)) != 0) {
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
(void)kill(child, SIGKILL);
|
||||
(void)waitpid(child, &status, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Give prompt_load_mnemonic()/fgets() time to consume only the mnemonic line
|
||||
* before we write framed request bytes to the same stdin stream. */
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 250 * 1000 * 1000;
|
||||
(void)nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
if (send_framed(in_pipe[1], request_json) != 0) {
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
(void)kill(child, SIGKILL);
|
||||
(void)waitpid(child, &status, 0);
|
||||
return -1;
|
||||
}
|
||||
close(in_pipe[1]);
|
||||
|
||||
if (recv_framed(out_pipe[0], out_response) != 0) {
|
||||
close(out_pipe[0]);
|
||||
(void)kill(child, SIGKILL);
|
||||
(void)waitpid(child, &status, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(out_pipe[0]);
|
||||
if (waitpid(child, &status, 0) <= 0) {
|
||||
free(*out_response);
|
||||
*out_response = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *resp = NULL;
|
||||
char *auth_req = NULL;
|
||||
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
|
||||
};
|
||||
|
||||
check_condition("nostr_init for auth signing", nostr_init() == 0);
|
||||
|
||||
/* no auth => legacy qubes:personal preapprove should match */
|
||||
if (run_qrexec_once("{\"id\":\"1\",\"method\":\"get_public_key\",\"params\":[\"\"]}", &resp) == 0) {
|
||||
check_condition("qrexec optional/no-auth still allows legacy qubes caller preapprove",
|
||||
strstr(resp, "\"result\":") != NULL);
|
||||
free(resp);
|
||||
resp = NULL;
|
||||
} else {
|
||||
check_condition("qrexec optional/no-auth request roundtrip", 0);
|
||||
}
|
||||
|
||||
auth_req = build_auth_request(privkey, "2", "get_public_key");
|
||||
check_condition("build qrexec auth request", auth_req != NULL);
|
||||
if (auth_req != NULL) {
|
||||
if (run_qrexec_once(auth_req, &resp) == 0) {
|
||||
check_condition("qrexec optional/auth request uses composite identity and misses legacy preapprove",
|
||||
strstr(resp, "\"code\":2001") != NULL && strstr(resp, "policy_denied") != NULL);
|
||||
free(resp);
|
||||
resp = NULL;
|
||||
} else {
|
||||
check_condition("qrexec optional/auth request roundtrip", 0);
|
||||
}
|
||||
free(auth_req);
|
||||
auth_req = NULL;
|
||||
}
|
||||
|
||||
auth_req = build_auth_request(privkey, "3", "get_public_key");
|
||||
check_condition("build second qrexec auth request", auth_req != NULL);
|
||||
if (auth_req != NULL) {
|
||||
cJSON *root = cJSON_Parse(auth_req);
|
||||
char *tampered = NULL;
|
||||
cJSON *method_item;
|
||||
|
||||
if (root != NULL) {
|
||||
method_item = cJSON_GetObjectItemCaseSensitive(root, "method");
|
||||
if (cJSON_IsString(method_item)) {
|
||||
cJSON_SetValuestring(method_item, "sign_event");
|
||||
}
|
||||
tampered = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
if (tampered != NULL && run_qrexec_once(tampered, &resp) == 0) {
|
||||
check_condition("qrexec optional/malformed-auth rejects with auth-layer code",
|
||||
strstr(resp, "\"code\":2015") != NULL && strstr(resp, "auth_envelope_mismatch") != NULL);
|
||||
free(resp);
|
||||
resp = NULL;
|
||||
} else {
|
||||
check_condition("qrexec optional/malformed-auth request roundtrip", 0);
|
||||
}
|
||||
|
||||
free(tampered);
|
||||
free(auth_req);
|
||||
}
|
||||
|
||||
nostr_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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -387,7 +387,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "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 80
|
||||
#define POLICY_CALLER_MAX_LEN 160
|
||||
|
||||
/* Prompt behavior */
|
||||
typedef enum {
|
||||
@@ -384,7 +384,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
char caller_id[80]; /* "uid:<n>" */
|
||||
char caller_id[POLICY_CALLER_MAX_LEN]; /* "uid:<n>" */
|
||||
} caller_identity_t;
|
||||
|
||||
/* Server context */
|
||||
|
||||
Reference in New Issue
Block a user