From 578aa0986063597e2113ed90ec8be1e58759426f Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 24 Jan 2026 03:03:54 +0100 Subject: [PATCH] Extract capability token module --- CMakeLists.txt | 2 +- src/amduatd.c | 1802 +++------------------------------------- src/amduatd_caps.c | 1947 ++++++++++++++++++++++++++++++++++++++++++++ src/amduatd_caps.h | 48 ++ src/amduatd_ui.h | 10 + 5 files changed, 2099 insertions(+), 1710 deletions(-) create mode 100644 src/amduatd_caps.c create mode 100644 src/amduatd_caps.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e025648..c0cb87b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ target_link_libraries(amduat_federation PRIVATE amduat_asl amduat_enc amduat_util amduat_fed ) -set(amduatd_sources src/amduatd.c) +set(amduatd_sources src/amduatd.c src/amduatd_caps.c) if(AMDUATD_ENABLE_UI) list(APPEND amduatd_sources src/amduatd_ui.c) endif() diff --git a/src/amduatd.c b/src/amduatd.c index a3fe872..e2429d0 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -34,6 +34,7 @@ #include "amduat/util/log.h" #include "amduat/hash/asl1.h" #include "amduatd_ui.h" +#include "amduatd_caps.h" #include #include @@ -61,16 +62,16 @@ typedef struct amduatd_strbuf { size_t cap; } amduatd_strbuf_t; -static bool amduatd_http_send_json(int fd, - int code, - const char *reason, - const char *json, - bool head_only); +bool amduatd_http_send_json(int fd, + int code, + const char *reason, + const char *json, + bool head_only); -static bool amduatd_send_json_error(int fd, - int code, - const char *reason, - const char *msg); +bool amduatd_send_json_error(int fd, + int code, + const char *reason, + const char *msg); #if AMDUATD_ENABLE_UI bool amduatd_seed_ui_html(amduat_asl_store_t *store, @@ -98,20 +99,6 @@ static const uint64_t AMDUATD_FED_TICK_MS = 1000u; static const uint32_t AMDUATD_EDGE_VIEW_BATCH = 1024u; static const uint16_t AMDUATD_EDGE_COLLECTION_KIND = 1u; -static const uint64_t AMDUATD_CAP_MAX_EXPIRY_SECONDS = 7u * 24u * 60u * 60u; - -typedef struct { - const char *space_id; - bool space_enabled; - bool migrate_unscoped_edges; -} amduatd_cfg_t; - -typedef struct { - bool enabled; - bool reads_enabled; - uint8_t hmac_key[32]; - size_t hmac_key_len; -} amduatd_cap_state_t; typedef struct { amduat_reference_t record_ref; @@ -187,7 +174,7 @@ static bool amduatd_edge_list_push(amduatd_edge_list_t *list, return true; } -typedef struct { +typedef struct amduatd_concepts_t { const char *root_path; char edges_path[1024]; char *edge_collection_name; @@ -243,21 +230,6 @@ static const uint8_t k_amduatd_edge_magic[AMDUATD_EDGE_MAGIC_LEN] = { 'A', 'S', 'L', 'E', 'D', 'G', '1', '\0' }; -enum { - AMDUATD_CAP_MAGIC_LEN = 8, - AMDUATD_CAP_VERSION = 1 -}; - -enum { - AMDUATD_CAP_KIND_ARTIFACT_REF = 1, - AMDUATD_CAP_KIND_POINTER_NAME = 2, - AMDUATD_CAP_KIND_PEL_VIEW = 3 -}; - -static const uint8_t k_amduatd_cap_magic[AMDUATD_CAP_MAGIC_LEN] = { - 'A', 'S', 'L', 'C', 'A', 'P', '1', '\0' -}; - static const char *const AMDUATD_EDGE_SCHEMA = "tgk/edge"; static const char *const AMDUATD_REL_ALIAS = "alias"; @@ -1102,7 +1074,7 @@ static void amduatd_on_signal(int signo) { amduatd_should_exit = 1; } -static bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { +bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = write(fd, buf + off, len - off); @@ -1120,7 +1092,7 @@ static bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { return true; } -static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len); +bool amduatd_read_exact(int fd, uint8_t *buf, size_t len); static bool amduatd_read_file(const char *path, uint8_t **out, size_t *out_len) { uint8_t *buf = NULL; @@ -1171,7 +1143,7 @@ static bool amduatd_read_file(const char *path, uint8_t **out, size_t *out_len) return true; } -static bool amduatd_read_urandom(uint8_t *out, size_t len) { +bool amduatd_read_urandom(uint8_t *out, size_t len) { int fd; if (out == NULL || len == 0) { return false; @@ -1188,246 +1160,6 @@ static bool amduatd_read_urandom(uint8_t *out, size_t len) { return true; } -static bool amduatd_ct_memcmp(const uint8_t *a, - const uint8_t *b, - size_t len) { - uint8_t diff = 0u; - if (a == NULL || b == NULL) { - return false; - } - for (size_t i = 0u; i < len; ++i) { - diff |= (uint8_t)(a[i] ^ b[i]); - } - return diff == 0u; -} - -static bool amduatd_sha256_digest(amduat_octets_t input, uint8_t out[32]) { - return amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, - input, - out, - 32u); -} - -static bool amduatd_hmac_sha256(const uint8_t *key, - size_t key_len, - const uint8_t *data, - size_t data_len, - uint8_t out[32]) { - uint8_t key_block[64]; - uint8_t ipad[64]; - uint8_t opad[64]; - uint8_t inner[32]; - size_t i; - amduat_hash_asl1_stream_t stream; - - if (key == NULL || data == NULL || out == NULL) { - return false; - } - memset(key_block, 0, sizeof(key_block)); - if (key_len > sizeof(key_block)) { - if (!amduatd_sha256_digest(amduat_octets(key, key_len), key_block)) { - return false; - } - } else if (key_len != 0u) { - memcpy(key_block, key, key_len); - } - - for (i = 0u; i < sizeof(key_block); ++i) { - ipad[i] = (uint8_t)(key_block[i] ^ 0x36u); - opad[i] = (uint8_t)(key_block[i] ^ 0x5cu); - } - - if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { - return false; - } - if (!amduat_hash_asl1_stream_update(&stream, - amduat_octets(ipad, sizeof(ipad))) || - !amduat_hash_asl1_stream_update(&stream, - amduat_octets(data, data_len)) || - !amduat_hash_asl1_stream_final(&stream, inner, sizeof(inner))) { - amduat_hash_asl1_stream_destroy(&stream); - return false; - } - amduat_hash_asl1_stream_destroy(&stream); - - if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { - return false; - } - if (!amduat_hash_asl1_stream_update(&stream, - amduat_octets(opad, sizeof(opad))) || - !amduat_hash_asl1_stream_update(&stream, - amduat_octets(inner, sizeof(inner))) || - !amduat_hash_asl1_stream_final(&stream, out, 32u)) { - amduat_hash_asl1_stream_destroy(&stream); - return false; - } - amduat_hash_asl1_stream_destroy(&stream); - return true; -} - -static int amduatd_b64url_value(char c) { - if (c >= 'A' && c <= 'Z') { - return c - 'A'; - } - if (c >= 'a' && c <= 'z') { - return c - 'a' + 26; - } - if (c >= '0' && c <= '9') { - return c - '0' + 52; - } - if (c == '-') { - return 62; - } - if (c == '_') { - return 63; - } - return -1; -} - -static bool amduatd_b64url_encode(const uint8_t *data, - size_t len, - char **out) { - static const char k_table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - size_t out_len; - size_t i; - size_t o = 0; - char *buf; - - if (out != NULL) { - *out = NULL; - } - if (data == NULL || out == NULL) { - return false; - } - out_len = (len / 3u) * 4u; - if (len % 3u == 1u) { - out_len += 2u; - } else if (len % 3u == 2u) { - out_len += 3u; - } - if (out_len > SIZE_MAX - 1u) { - return false; - } - buf = (char *)malloc(out_len + 1u); - if (buf == NULL) { - return false; - } - - for (i = 0u; i + 2u < len; i += 3u) { - uint32_t v = ((uint32_t)data[i] << 16) | - ((uint32_t)data[i + 1u] << 8) | - (uint32_t)data[i + 2u]; - buf[o++] = k_table[(v >> 18) & 0x3fu]; - buf[o++] = k_table[(v >> 12) & 0x3fu]; - buf[o++] = k_table[(v >> 6) & 0x3fu]; - buf[o++] = k_table[v & 0x3fu]; - } - if (len % 3u == 1u) { - uint32_t v = (uint32_t)data[i] << 16; - buf[o++] = k_table[(v >> 18) & 0x3fu]; - buf[o++] = k_table[(v >> 12) & 0x3fu]; - } else if (len % 3u == 2u) { - uint32_t v = ((uint32_t)data[i] << 16) | - ((uint32_t)data[i + 1u] << 8); - buf[o++] = k_table[(v >> 18) & 0x3fu]; - buf[o++] = k_table[(v >> 12) & 0x3fu]; - buf[o++] = k_table[(v >> 6) & 0x3fu]; - } - buf[o] = '\0'; - *out = buf; - return true; -} - -static bool amduatd_b64url_decode(const char *text, - uint8_t **out, - size_t *out_len) { - size_t len; - size_t clean_len; - size_t i = 0u; - size_t o = 0u; - uint8_t *buf; - - if (out != NULL) { - *out = NULL; - } - if (out_len != NULL) { - *out_len = 0; - } - if (text == NULL || out == NULL || out_len == NULL) { - return false; - } - len = strlen(text); - clean_len = len; - while (clean_len > 0u && text[clean_len - 1u] == '=') { - clean_len--; - } - if (clean_len % 4u == 1u) { - return false; - } - *out_len = (clean_len / 4u) * 3u; - if (clean_len % 4u == 2u) { - *out_len += 1u; - } else if (clean_len % 4u == 3u) { - *out_len += 2u; - } - if (*out_len > SIZE_MAX) { - return false; - } - buf = (uint8_t *)malloc(*out_len); - if (buf == NULL) { - return false; - } - - while (i + 4u <= clean_len) { - int v0 = amduatd_b64url_value(text[i]); - int v1 = amduatd_b64url_value(text[i + 1u]); - int v2 = amduatd_b64url_value(text[i + 2u]); - int v3 = amduatd_b64url_value(text[i + 3u]); - uint32_t v; - if (v0 < 0 || v1 < 0 || v2 < 0 || v3 < 0) { - free(buf); - return false; - } - v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | - ((uint32_t)v2 << 6) | (uint32_t)v3; - buf[o++] = (uint8_t)((v >> 16) & 0xffu); - buf[o++] = (uint8_t)((v >> 8) & 0xffu); - buf[o++] = (uint8_t)(v & 0xffu); - i += 4u; - } - if (clean_len % 4u == 2u) { - int v0 = amduatd_b64url_value(text[i]); - int v1 = amduatd_b64url_value(text[i + 1u]); - uint32_t v; - if (v0 < 0 || v1 < 0) { - free(buf); - return false; - } - v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12); - buf[o++] = (uint8_t)((v >> 16) & 0xffu); - } else if (clean_len % 4u == 3u) { - int v0 = amduatd_b64url_value(text[i]); - int v1 = amduatd_b64url_value(text[i + 1u]); - int v2 = amduatd_b64url_value(text[i + 2u]); - uint32_t v; - if (v0 < 0 || v1 < 0 || v2 < 0) { - free(buf); - return false; - } - v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | ((uint32_t)v2 << 6); - buf[o++] = (uint8_t)((v >> 16) & 0xffu); - buf[o++] = (uint8_t)((v >> 8) & 0xffu); - } - - if (o != *out_len) { - free(buf); - return false; - } - *out = buf; - return true; -} - static bool amduatd_name_valid(const char *name) { size_t i; size_t len; @@ -1636,685 +1368,6 @@ static bool amduatd_unscoped_name(const amduatd_cfg_t *cfg, return true; } -typedef struct { - uint8_t kind; - char *space; - size_t space_len; - uint64_t expiry_unix; - amduat_reference_t artifact_ref; - char *pointer_name; - amduat_reference_t program_ref; - amduat_reference_t *input_refs; - size_t input_refs_len; - bool has_params_ref; - amduat_reference_t params_ref; -} amduatd_cap_token_t; - -static void amduatd_cap_token_free(amduatd_cap_token_t *token) { - if (token == NULL) { - return; - } - free(token->space); - token->space = NULL; - token->space_len = 0u; - amduat_reference_free(&token->artifact_ref); - free(token->pointer_name); - token->pointer_name = NULL; - amduat_reference_free(&token->program_ref); - if (token->input_refs != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - amduat_reference_free(&token->input_refs[i]); - } - free(token->input_refs); - } - token->input_refs = NULL; - token->input_refs_len = 0u; - if (token->has_params_ref) { - amduat_reference_free(&token->params_ref); - } - token->has_params_ref = false; - memset(token, 0, sizeof(*token)); -} - -static bool amduatd_pointer_name_scoped_for_space(const amduatd_cfg_t *dcfg, - const char *name) { - size_t space_len; - if (dcfg == NULL || name == NULL) { - return false; - } - if (!amduat_asl_pointer_name_is_valid(name)) { - return false; - } - if (!dcfg->space_enabled || dcfg->space_id == NULL) { - return true; - } - space_len = strlen(dcfg->space_id); - if (strncmp(name, "space/", 6u) != 0) { - return false; - } - if (strncmp(name + 6u, dcfg->space_id, space_len) != 0) { - return false; - } - if (name[6u + space_len] != '/') { - return false; - } - return true; -} - -static const char *amduatd_cap_kind_name(uint8_t kind) { - switch (kind) { - case AMDUATD_CAP_KIND_ARTIFACT_REF: - return "artifact_ref"; - case AMDUATD_CAP_KIND_POINTER_NAME: - return "pointer_name"; - case AMDUATD_CAP_KIND_PEL_VIEW: - return "pel_view"; - default: - return "unknown"; - } -} - -static void amduatd_cap_token_hash_prefix(const uint8_t *bytes, - size_t len, - char out[9]) { - uint8_t digest[32]; - if (out == NULL) { - return; - } - if (bytes == NULL || len == 0u || - !amduatd_sha256_digest(amduat_octets(bytes, len), digest)) { - strncpy(out, "unknown", 8u); - out[8] = '\0'; - return; - } - if (!amduat_hex_encode_lower(digest, 4u, out, 9u)) { - strncpy(out, "unknown", 8u); - out[8] = '\0'; - return; - } - out[8] = '\0'; -} - -static bool amduatd_cap_init(const char *root_path, amduatd_cap_state_t *cap) { - char secrets_path[1024]; - char key_path[1060]; - struct stat st; - int fd = -1; - uint8_t key_bytes[32]; - - if (cap == NULL) { - return false; - } - memset(cap, 0, sizeof(*cap)); - if (root_path == NULL) { - return false; - } - { - int n = snprintf(secrets_path, sizeof(secrets_path), - "%s/secrets", root_path); - if (n <= 0 || (size_t)n >= sizeof(secrets_path)) { - return false; - } - } - if (stat(secrets_path, &st) != 0) { - if (errno != ENOENT) { - return false; - } - if (mkdir(secrets_path, 0700) != 0) { - return false; - } - } else if (!S_ISDIR(st.st_mode)) { - return false; - } - if (stat(secrets_path, &st) != 0) { - return false; - } - if ((st.st_mode & 0077u) != 0u) { - amduat_log(AMDUAT_LOG_WARN, - "capabilities disabled: secrets dir permissions too open"); - return false; - } - - { - int n = snprintf(key_path, sizeof(key_path), - "%s/secrets/cap_hmac_key", root_path); - if (n <= 0 || (size_t)n >= sizeof(key_path)) { - return false; - } - } - if (stat(key_path, &st) != 0) { - if (errno != ENOENT) { - return false; - } - fd = open(key_path, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) { - return false; - } - if (!amduatd_read_urandom(key_bytes, sizeof(key_bytes))) { - close(fd); - return false; - } - if (!amduatd_write_all(fd, key_bytes, sizeof(key_bytes))) { - close(fd); - return false; - } - close(fd); - } - - if (stat(key_path, &st) != 0) { - return false; - } - if (!S_ISREG(st.st_mode) || (st.st_mode & 0077u) != 0u || - st.st_size != (off_t)sizeof(key_bytes)) { - amduat_log(AMDUAT_LOG_WARN, - "capabilities disabled: cap_hmac_key permissions invalid"); - return false; - } - - fd = open(key_path, O_RDONLY); - if (fd < 0) { - return false; - } - if (!amduatd_read_exact(fd, key_bytes, sizeof(key_bytes))) { - close(fd); - return false; - } - close(fd); - - memcpy(cap->hmac_key, key_bytes, sizeof(key_bytes)); - cap->hmac_key_len = sizeof(key_bytes); - cap->enabled = true; - return true; -} - -static bool amduatd_cap_build_token(const amduatd_cap_token_t *token, - const uint8_t *key, - size_t key_len, - uint8_t **out, - size_t *out_len) { - size_t total = 0u; - size_t payload_len = 0u; - size_t offset = 0u; - size_t hmac_offset = 0u; - uint8_t *buf = NULL; - uint8_t hmac[32]; - amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); - amduat_octets_t program_bytes = amduat_octets(NULL, 0u); - amduat_octets_t *input_bytes = NULL; - amduat_octets_t params_bytes = amduat_octets(NULL, 0u); - - if (out != NULL) { - *out = NULL; - } - if (out_len != NULL) { - *out_len = 0u; - } - if (token == NULL || out == NULL || out_len == NULL || - key == NULL || key_len == 0u) { - return false; - } - - if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { - if (!amduat_enc_asl1_core_encode_reference_v1(token->artifact_ref, - &ref_bytes)) { - return false; - } - payload_len = 4u + ref_bytes.len; - } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { - if (token->pointer_name == NULL) { - return false; - } - payload_len = 4u + strlen(token->pointer_name); - } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { - size_t inputs_total = 0u; - if (!amduat_enc_asl1_core_encode_reference_v1(token->program_ref, - &program_bytes)) { - return false; - } - if (token->input_refs_len != 0u) { - input_bytes = (amduat_octets_t *)calloc(token->input_refs_len, - sizeof(*input_bytes)); - if (input_bytes == NULL) { - free((void *)program_bytes.data); - return false; - } - for (size_t i = 0u; i < token->input_refs_len; ++i) { - if (!amduat_enc_asl1_core_encode_reference_v1(token->input_refs[i], - &input_bytes[i])) { - free((void *)program_bytes.data); - for (size_t j = 0u; j < i; ++j) { - free((void *)input_bytes[j].data); - } - free(input_bytes); - return false; - } - if (!amduatd_add_size(&inputs_total, 4u + input_bytes[i].len)) { - free((void *)program_bytes.data); - for (size_t j = 0u; j <= i; ++j) { - free((void *)input_bytes[j].data); - } - free(input_bytes); - return false; - } - } - } - if (token->has_params_ref) { - if (!amduat_enc_asl1_core_encode_reference_v1(token->params_ref, - ¶ms_bytes)) { - free((void *)program_bytes.data); - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - free((void *)input_bytes[i].data); - } - free(input_bytes); - } - return false; - } - } - payload_len = 4u + program_bytes.len + 4u + inputs_total + 1u; - if (token->has_params_ref) { - payload_len += 4u + params_bytes.len; - } - } else { - return false; - } - - if (!amduatd_add_size(&total, AMDUATD_CAP_MAGIC_LEN) || - !amduatd_add_size(&total, 4u) || - !amduatd_add_size(&total, 4u + token->space_len) || - !amduatd_add_size(&total, 8u) || - !amduatd_add_size(&total, 1u) || - !amduatd_add_size(&total, payload_len) || - !amduatd_add_size(&total, 4u) || - !amduatd_add_size(&total, sizeof(hmac))) { - free((void *)ref_bytes.data); - free((void *)program_bytes.data); - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - free((void *)input_bytes[i].data); - } - free(input_bytes); - } - free((void *)params_bytes.data); - return false; - } - - buf = (uint8_t *)malloc(total); - if (buf == NULL) { - free((void *)ref_bytes.data); - free((void *)program_bytes.data); - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - free((void *)input_bytes[i].data); - } - free(input_bytes); - } - free((void *)params_bytes.data); - return false; - } - - memcpy(buf + offset, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN); - offset += AMDUATD_CAP_MAGIC_LEN; - amduatd_store_u32_le(buf + offset, AMDUATD_CAP_VERSION); - offset += 4u; - amduatd_store_u32_le(buf + offset, (uint32_t)token->space_len); - offset += 4u; - if (token->space_len != 0u && token->space != NULL) { - memcpy(buf + offset, token->space, token->space_len); - offset += token->space_len; - } - amduatd_store_u64_le(buf + offset, token->expiry_unix); - offset += 8u; - buf[offset++] = token->kind; - - if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { - amduatd_store_u32_le(buf + offset, (uint32_t)ref_bytes.len); - offset += 4u; - if (ref_bytes.len != 0u) { - memcpy(buf + offset, ref_bytes.data, ref_bytes.len); - offset += ref_bytes.len; - } - } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { - uint32_t name_len = (uint32_t)strlen(token->pointer_name); - amduatd_store_u32_le(buf + offset, name_len); - offset += 4u; - if (name_len != 0u) { - memcpy(buf + offset, token->pointer_name, name_len); - offset += name_len; - } - } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { - amduatd_store_u32_le(buf + offset, (uint32_t)program_bytes.len); - offset += 4u; - if (program_bytes.len != 0u) { - memcpy(buf + offset, program_bytes.data, program_bytes.len); - offset += program_bytes.len; - } - amduatd_store_u32_le(buf + offset, (uint32_t)token->input_refs_len); - offset += 4u; - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - amduatd_store_u32_le(buf + offset, (uint32_t)input_bytes[i].len); - offset += 4u; - if (input_bytes[i].len != 0u) { - memcpy(buf + offset, input_bytes[i].data, input_bytes[i].len); - offset += input_bytes[i].len; - } - } - } - buf[offset++] = token->has_params_ref ? 1u : 0u; - if (token->has_params_ref) { - amduatd_store_u32_le(buf + offset, (uint32_t)params_bytes.len); - offset += 4u; - if (params_bytes.len != 0u) { - memcpy(buf + offset, params_bytes.data, params_bytes.len); - offset += params_bytes.len; - } - } - } - - hmac_offset = offset; - amduatd_store_u32_le(buf + offset, (uint32_t)sizeof(hmac)); - offset += 4u; - - if (!amduatd_hmac_sha256(key, key_len, buf, hmac_offset, hmac)) { - free(buf); - free((void *)ref_bytes.data); - free((void *)program_bytes.data); - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - free((void *)input_bytes[i].data); - } - free(input_bytes); - } - free((void *)params_bytes.data); - return false; - } - memcpy(buf + offset, hmac, sizeof(hmac)); - offset += sizeof(hmac); - - free((void *)ref_bytes.data); - free((void *)program_bytes.data); - if (input_bytes != NULL) { - for (size_t i = 0u; i < token->input_refs_len; ++i) { - free((void *)input_bytes[i].data); - } - free(input_bytes); - } - free((void *)params_bytes.data); - - if (offset != total) { - free(buf); - return false; - } - *out = buf; - *out_len = total; - return true; -} - -static bool amduatd_cap_decode_token(const uint8_t *bytes, - size_t len, - amduatd_cap_token_t *out_token, - size_t *out_hmac_offset, - uint32_t *out_hmac_len, - const uint8_t **out_hmac) { - size_t offset = 0u; - uint32_t version = 0u; - uint32_t space_len = 0u; - uint32_t ref_len = 0u; - uint32_t input_count = 0u; - uint32_t hmac_len = 0u; - uint64_t expiry = 0u; - uint8_t kind = 0u; - amduatd_cap_token_t token; - const uint8_t *hmac_ptr = NULL; - - if (out_token == NULL || out_hmac_offset == NULL || - out_hmac_len == NULL || out_hmac == NULL) { - return false; - } - memset(&token, 0, sizeof(token)); - - if (len < AMDUATD_CAP_MAGIC_LEN + 4u + 4u + 8u + 1u + 4u + 32u) { - return false; - } - if (memcmp(bytes, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN) != 0) { - return false; - } - offset += AMDUATD_CAP_MAGIC_LEN; - if (!amduatd_read_u32_le(bytes, len, &offset, &version) || - version != AMDUATD_CAP_VERSION) { - return false; - } - if (!amduatd_read_u32_le(bytes, len, &offset, &space_len)) { - return false; - } - if (len - offset < space_len) { - return false; - } - if (space_len != 0u) { - token.space = (char *)malloc(space_len + 1u); - if (token.space == NULL) { - return false; - } - memcpy(token.space, bytes + offset, space_len); - token.space[space_len] = '\0'; - token.space_len = space_len; - } - offset += space_len; - if (!amduatd_read_u64_le(bytes, len, &offset, &expiry)) { - amduatd_cap_token_free(&token); - return false; - } - token.expiry_unix = expiry; - if (offset >= len) { - amduatd_cap_token_free(&token); - return false; - } - kind = bytes[offset++]; - token.kind = kind; - - if (kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { - if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { - amduatd_cap_token_free(&token); - return false; - } - if (len - offset < ref_len) { - amduatd_cap_token_free(&token); - return false; - } - if (!amduat_enc_asl1_core_decode_reference_v1( - amduat_octets(bytes + offset, ref_len), &token.artifact_ref)) { - amduatd_cap_token_free(&token); - return false; - } - offset += ref_len; - } else if (kind == AMDUATD_CAP_KIND_POINTER_NAME) { - uint32_t name_len = 0u; - if (!amduatd_read_u32_le(bytes, len, &offset, &name_len)) { - amduatd_cap_token_free(&token); - return false; - } - if (len - offset < name_len) { - amduatd_cap_token_free(&token); - return false; - } - token.pointer_name = (char *)malloc(name_len + 1u); - if (token.pointer_name == NULL) { - amduatd_cap_token_free(&token); - return false; - } - memcpy(token.pointer_name, bytes + offset, name_len); - token.pointer_name[name_len] = '\0'; - offset += name_len; - if (!amduat_asl_pointer_name_is_valid(token.pointer_name)) { - amduatd_cap_token_free(&token); - return false; - } - } else if (kind == AMDUATD_CAP_KIND_PEL_VIEW) { - if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { - amduatd_cap_token_free(&token); - return false; - } - if (len - offset < ref_len) { - amduatd_cap_token_free(&token); - return false; - } - if (!amduat_enc_asl1_core_decode_reference_v1( - amduat_octets(bytes + offset, ref_len), &token.program_ref)) { - amduatd_cap_token_free(&token); - return false; - } - offset += ref_len; - if (!amduatd_read_u32_le(bytes, len, &offset, &input_count)) { - amduatd_cap_token_free(&token); - return false; - } - if (input_count != 0u) { - token.input_refs = (amduat_reference_t *)calloc( - input_count, sizeof(*token.input_refs)); - if (token.input_refs == NULL) { - amduatd_cap_token_free(&token); - return false; - } - token.input_refs_len = input_count; - for (uint32_t i = 0u; i < input_count; ++i) { - if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || - len - offset < ref_len || - !amduat_enc_asl1_core_decode_reference_v1( - amduat_octets(bytes + offset, ref_len), - &token.input_refs[i])) { - amduatd_cap_token_free(&token); - return false; - } - offset += ref_len; - } - } - if (offset >= len) { - amduatd_cap_token_free(&token); - return false; - } - token.has_params_ref = bytes[offset++] != 0u; - if (token.has_params_ref) { - if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || - len - offset < ref_len || - !amduat_enc_asl1_core_decode_reference_v1( - amduat_octets(bytes + offset, ref_len), - &token.params_ref)) { - amduatd_cap_token_free(&token); - return false; - } - offset += ref_len; - } - } else { - amduatd_cap_token_free(&token); - return false; - } - - *out_hmac_offset = offset; - if (!amduatd_read_u32_le(bytes, len, &offset, &hmac_len)) { - amduatd_cap_token_free(&token); - return false; - } - if (len - offset < hmac_len) { - amduatd_cap_token_free(&token); - return false; - } - hmac_ptr = bytes + offset; - offset += hmac_len; - if (offset != len) { - amduatd_cap_token_free(&token); - return false; - } - - *out_hmac_len = hmac_len; - *out_hmac = hmac_ptr; - *out_token = token; - return true; -} - -static bool amduatd_cap_verify_token(const amduatd_cap_state_t *cap, - const amduatd_cfg_t *dcfg, - const uint8_t *bytes, - size_t len, - amduatd_cap_token_t *out_token, - const char **out_reason) { - amduatd_cap_token_t token; - size_t hmac_offset = 0u; - uint32_t hmac_len = 0u; - const uint8_t *hmac = NULL; - uint8_t expected[32]; - time_t now; - - if (out_reason != NULL) { - *out_reason = "invalid"; - } - if (cap == NULL || dcfg == NULL || bytes == NULL || out_token == NULL) { - return false; - } - if (!amduatd_cap_decode_token(bytes, len, &token, - &hmac_offset, &hmac_len, &hmac)) { - return false; - } - if (hmac_len != sizeof(expected)) { - *out_token = token; - return false; - } - if (!amduatd_hmac_sha256(cap->hmac_key, - cap->hmac_key_len, - bytes, - hmac_offset, - expected) || - !amduatd_ct_memcmp(expected, hmac, sizeof(expected))) { - if (out_reason != NULL) { - *out_reason = "bad-mac"; - } - *out_token = token; - return false; - } - - now = time(NULL); - if (now < 0 || token.expiry_unix < (uint64_t)now) { - if (out_reason != NULL) { - *out_reason = "expired"; - } - *out_token = token; - return false; - } - - if (dcfg->space_enabled) { - if (token.space_len == 0u || token.space == NULL || - strcmp(token.space, dcfg->space_id) != 0) { - if (out_reason != NULL) { - *out_reason = "wrong-space"; - } - *out_token = token; - return false; - } - } else { - if (token.space_len != 0u) { - if (out_reason != NULL) { - *out_reason = "wrong-space"; - } - *out_token = token; - return false; - } - } - if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME && - !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { - if (out_reason != NULL) { - *out_reason = "wrong-space"; - } - *out_token = token; - return false; - } - - *out_token = token; - if (out_reason != NULL) { - *out_reason = "ok"; - } - return true; -} - static bool amduatd_build_prefixed_bytes(const char *prefix, const char *text, uint8_t **out, @@ -3465,7 +2518,7 @@ static bool amduatd_handle_get_concept(int fd, } } -static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) { +bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = read(fd, buf + off, len - off); @@ -3761,11 +2814,11 @@ bool amduatd_http_send_text(int fd, fd, code, reason, "text/plain; charset=utf-8", body, len, head_only); } -static bool amduatd_http_send_json(int fd, - int code, - const char *reason, - const char *json, - bool head_only) { +bool amduatd_http_send_json(int fd, + int code, + const char *reason, + const char *json, + bool head_only) { const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}"); size_t len = json != NULL ? strlen(json) : 2; return amduatd_http_send_status( @@ -3844,10 +2897,10 @@ static bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } -static const char *amduatd_query_param(const char *path, - const char *key, - char *out_value, - size_t out_cap) { +const char *amduatd_query_param(const char *path, + const char *key, + char *out_value, + size_t out_cap) { const char *q = strchr(path, '?'); size_t key_len = 0; if (out_value != NULL && out_cap != 0) { @@ -3984,7 +3037,7 @@ static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c) { return amduatd_strbuf_append(b, &c, 1u); } -static const char *amduatd_json_skip_ws(const char *p, const char *end) { +const char *amduatd_json_skip_ws(const char *p, const char *end) { while (p < end) { char c = *p; if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { @@ -3995,9 +3048,9 @@ static const char *amduatd_json_skip_ws(const char *p, const char *end) { return p; } -static bool amduatd_json_expect(const char **p, - const char *end, - char expected) { +bool amduatd_json_expect(const char **p, + const char *end, + char expected) { const char *cur; if (p == NULL || *p == NULL) { return false; @@ -4010,10 +3063,10 @@ static bool amduatd_json_expect(const char **p, return true; } -static bool amduatd_json_parse_string_noesc(const char **p, - const char *end, - const char **out_start, - size_t *out_len) { +bool amduatd_json_parse_string_noesc(const char **p, + const char *end, + const char **out_start, + size_t *out_len) { const char *cur; const char *s; if (out_start != NULL) { @@ -4098,9 +3151,9 @@ static bool amduatd_json_parse_u32(const char **p, return true; } -static bool amduatd_json_parse_u64(const char **p, - const char *end, - uint64_t *out) { +bool amduatd_json_parse_u64(const char **p, + const char *end, + uint64_t *out) { const char *cur; const char *start; unsigned long long v; @@ -4266,13 +3319,13 @@ static bool amduatd_json_skip_string(const char **p, const char *end) { return false; } -static bool amduatd_json_skip_value(const char **p, - const char *end, - int depth); +bool amduatd_json_skip_value(const char **p, + const char *end, + int depth); static bool amduatd_json_skip_array(const char **p, - const char *end, - int depth) { + const char *end, + int depth) { const char *cur; if (!amduatd_json_expect(p, end, '[')) { return false; @@ -4340,9 +3393,9 @@ static bool amduatd_json_skip_object(const char **p, } } -static bool amduatd_json_skip_value(const char **p, - const char *end, - int depth) { +bool amduatd_json_skip_value(const char **p, + const char *end, + int depth) { const char *cur; if (p == NULL || *p == NULL) { return false; @@ -4392,9 +3445,9 @@ static bool amduatd_json_skip_value(const char **p, return false; } -static bool amduatd_copy_json_str(const char *s, - size_t len, - char **out) { +bool amduatd_copy_json_str(const char *s, + size_t len, + char **out) { char *buf; if (out == NULL) { return false; @@ -4432,14 +3485,7 @@ static bool amduatd_decode_ref_hex_str(const char *s, return ok; } -typedef enum { - AMDUATD_REF_OK = 0, - AMDUATD_REF_ERR_INVALID = 1, - AMDUATD_REF_ERR_NOT_FOUND = 2, - AMDUATD_REF_ERR_INTERNAL = 3 -} amduatd_ref_status_t; - -static amduatd_ref_status_t amduatd_decode_ref_or_name_latest( +amduatd_ref_status_t amduatd_decode_ref_or_name_latest( amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const amduatd_concepts_t *concepts, @@ -4492,10 +3538,10 @@ static amduatd_ref_status_t amduatd_decode_ref_or_name_latest( return AMDUATD_REF_OK; } -static bool amduatd_send_json_error(int fd, - int code, - const char *reason, - const char *msg) { +bool amduatd_send_json_error(int fd, + int code, + const char *reason, + const char *msg) { amduatd_strbuf_t b; memset(&b, 0, sizeof(b)); if (!amduatd_strbuf_append_cstr(&b, "{\"error\":\"") || @@ -5356,7 +4402,7 @@ seed_cleanup: return ok; } -static bool amduatd_handle_get_artifact(int fd, +bool amduatd_handle_get_artifact(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req, const char *path, @@ -8798,688 +7844,6 @@ static bool amduatd_handle_get_resolve(int fd, return amduatd_http_send_json(fd, 200, "OK", json, false); } -static bool amduatd_handle_post_capabilities( - int fd, - amduat_asl_store_t *store, - const amduat_asl_store_fs_config_t *cfg, - const amduatd_concepts_t *concepts, - const amduatd_cfg_t *dcfg, - const amduatd_cap_state_t *cap, - const amduatd_http_req_t *req) { - uint8_t *body = NULL; - const char *p = NULL; - const char *end = NULL; - bool have_kind = false; - bool have_target = false; - bool have_expiry = false; - char *kind = NULL; - uint64_t expiry_seconds = 0u; - amduatd_cap_token_t token; - bool target_have_ref = false; - bool target_have_name = false; - bool target_have_program_ref = false; - bool target_have_inputs = false; - bool target_have_params_ref = false; - size_t input_refs_cap = 0u; - uint8_t *token_bytes = NULL; - size_t token_len = 0u; - char *token_b64 = NULL; - char *token_text = NULL; - char token_hash[9]; - char json[4096]; - bool ok = false; - const char *space = NULL; - size_t space_len = 0u; - time_t now; - - memset(&token, 0, sizeof(token)); - memset(token_hash, 0, sizeof(token_hash)); - - if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || - cap == NULL || req == NULL) { - return amduatd_send_json_error(fd, 500, "Internal Server Error", - "internal error"); - } - if (!cap->enabled) { - return amduatd_send_json_error(fd, 403, "Forbidden", - "capabilities disabled"); - } - if (req->content_length == 0) { - return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); - } - if (req->content_length > (256u * 1024u)) { - return amduatd_send_json_error(fd, 413, "Payload Too Large", - "payload too large"); - } - - body = (uint8_t *)malloc(req->content_length); - if (body == NULL) { - return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); - } - if (!amduatd_read_exact(fd, body, req->content_length)) { - free(body); - return false; - } - p = (const char *)body; - end = (const char *)body + req->content_length; - if (!amduatd_json_expect(&p, end, '{')) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); - goto cap_mint_cleanup; - } - - for (;;) { - const char *key = NULL; - size_t key_len = 0; - const char *cur = NULL; - - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == '}') { - p = cur + 1; - break; - } - if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || - !amduatd_json_expect(&p, end, ':')) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); - goto cap_mint_cleanup; - } - - if (key_len == strlen("kind") && memcmp(key, "kind", key_len) == 0) { - const char *sv = NULL; - size_t sv_len = 0; - if (have_kind || - !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || - !amduatd_copy_json_str(sv, sv_len, &kind)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); - goto cap_mint_cleanup; - } - have_kind = true; - } else if (key_len == strlen("expiry_seconds") && - memcmp(key, "expiry_seconds", key_len) == 0) { - if (have_expiry || !amduatd_json_parse_u64(&p, end, &expiry_seconds)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid expiry_seconds"); - goto cap_mint_cleanup; - } - have_expiry = true; - } else if (key_len == strlen("target") && - memcmp(key, "target", key_len) == 0) { - if (have_target || !amduatd_json_expect(&p, end, '{')) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); - goto cap_mint_cleanup; - } - for (;;) { - const char *tkey = NULL; - size_t tkey_len = 0; - const char *sv = NULL; - size_t sv_len = 0; - - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == '}') { - p = cur + 1; - break; - } - if (!amduatd_json_parse_string_noesc(&p, end, &tkey, &tkey_len) || - !amduatd_json_expect(&p, end, ':')) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid target"); - goto cap_mint_cleanup; - } - - if (tkey_len == strlen("ref") && memcmp(tkey, "ref", tkey_len) == 0) { - amduatd_ref_status_t st; - if (target_have_ref || - !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid ref"); - goto cap_mint_cleanup; - } - st = amduatd_decode_ref_or_name_latest(store, - cfg, - concepts, - dcfg, - sv, - sv_len, - &token.artifact_ref); - if (st != AMDUATD_REF_OK) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid ref"); - goto cap_mint_cleanup; - } - target_have_ref = true; - } else if (tkey_len == strlen("name") && - memcmp(tkey, "name", tkey_len) == 0) { - if (target_have_name || - !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || - !amduatd_copy_json_str(sv, sv_len, &token.pointer_name)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid name"); - goto cap_mint_cleanup; - } - target_have_name = true; - } else if (tkey_len == strlen("program_ref") && - memcmp(tkey, "program_ref", tkey_len) == 0) { - amduatd_ref_status_t st; - if (target_have_program_ref || - !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid program_ref"); - goto cap_mint_cleanup; - } - st = amduatd_decode_ref_or_name_latest(store, - cfg, - concepts, - dcfg, - sv, - sv_len, - &token.program_ref); - if (st != AMDUATD_REF_OK) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid program_ref"); - goto cap_mint_cleanup; - } - target_have_program_ref = true; - } else if (tkey_len == strlen("input_refs") && - memcmp(tkey, "input_refs", tkey_len) == 0) { - if (target_have_inputs || !amduatd_json_expect(&p, end, '[')) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid input_refs"); - goto cap_mint_cleanup; - } - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == ']') { - p = cur + 1; - target_have_inputs = true; - } else { - for (;;) { - amduat_reference_t ref; - amduatd_ref_status_t st; - if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid input_refs"); - goto cap_mint_cleanup; - } - memset(&ref, 0, sizeof(ref)); - st = amduatd_decode_ref_or_name_latest(store, - cfg, - concepts, - dcfg, - sv, - sv_len, - &ref); - if (st != AMDUATD_REF_OK) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid input_ref"); - goto cap_mint_cleanup; - } - if (token.input_refs_len == 0u) { - input_refs_cap = 4u; - token.input_refs = (amduat_reference_t *)calloc( - input_refs_cap, sizeof(*token.input_refs)); - if (token.input_refs == NULL) { - amduat_reference_free(&ref); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "oom"); - goto cap_mint_cleanup; - } - token.input_refs_len = 0u; - } else if (token.input_refs_len == input_refs_cap) { - size_t next_cap = input_refs_cap * 2u; - amduat_reference_t *next = (amduat_reference_t *)realloc( - token.input_refs, next_cap * sizeof(*token.input_refs)); - if (next == NULL) { - amduat_reference_free(&ref); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "oom"); - goto cap_mint_cleanup; - } - token.input_refs = next; - input_refs_cap = next_cap; - } - token.input_refs[token.input_refs_len++] = ref; - target_have_inputs = true; - - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == ',') { - p = cur + 1; - continue; - } - if (cur < end && *cur == ']') { - p = cur + 1; - target_have_inputs = true; - break; - } - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid input_refs"); - goto cap_mint_cleanup; - } - } - } else if (tkey_len == strlen("params_ref") && - memcmp(tkey, "params_ref", tkey_len) == 0) { - amduatd_ref_status_t st; - if (target_have_params_ref || - !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid params_ref"); - goto cap_mint_cleanup; - } - st = amduatd_decode_ref_or_name_latest(store, - cfg, - concepts, - dcfg, - sv, - sv_len, - &token.params_ref); - if (st != AMDUATD_REF_OK) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid params_ref"); - goto cap_mint_cleanup; - } - token.has_params_ref = true; - target_have_params_ref = true; - } else { - if (!amduatd_json_skip_value(&p, end, 0)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid target"); - goto cap_mint_cleanup; - } - } - - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == ',') { - p = cur + 1; - continue; - } - if (cur < end && *cur == '}') { - p = cur + 1; - break; - } - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid target"); - goto cap_mint_cleanup; - } - have_target = true; - } else { - if (!amduatd_json_skip_value(&p, end, 0)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); - goto cap_mint_cleanup; - } - } - - cur = amduatd_json_skip_ws(p, end); - if (cur < end && *cur == ',') { - p = cur + 1; - continue; - } - if (cur < end && *cur == '}') { - p = cur + 1; - break; - } - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); - goto cap_mint_cleanup; - } - - if (!have_kind || !have_target || !have_expiry) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "missing fields"); - goto cap_mint_cleanup; - } - if (expiry_seconds == 0u || expiry_seconds > AMDUATD_CAP_MAX_EXPIRY_SECONDS) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "expiry_seconds out of range"); - goto cap_mint_cleanup; - } - now = time(NULL); - if (now < 0 || - expiry_seconds > (uint64_t)(UINT64_MAX - (uint64_t)now)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "expiry_seconds out of range"); - goto cap_mint_cleanup; - } - token.expiry_unix = (uint64_t)now + expiry_seconds; - - if (strcmp(kind, "artifact_ref") == 0) { - token.kind = AMDUATD_CAP_KIND_ARTIFACT_REF; - if (token.artifact_ref.hash_id == 0 || - token.artifact_ref.digest.data == NULL) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "missing ref"); - goto cap_mint_cleanup; - } - } else if (strcmp(kind, "pointer_name") == 0) { - token.kind = AMDUATD_CAP_KIND_POINTER_NAME; - if (token.pointer_name == NULL || - !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "invalid name"); - goto cap_mint_cleanup; - } - } else if (strcmp(kind, "pel_view") == 0) { - token.kind = AMDUATD_CAP_KIND_PEL_VIEW; - if (token.program_ref.hash_id == 0 || - token.program_ref.digest.data == NULL) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "missing program_ref"); - goto cap_mint_cleanup; - } - if (!target_have_inputs) { - ok = amduatd_send_json_error(fd, 400, "Bad Request", - "missing input_refs"); - goto cap_mint_cleanup; - } - } else { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); - goto cap_mint_cleanup; - } - - if (dcfg->space_enabled && dcfg->space_id != NULL) { - space = dcfg->space_id; - space_len = strlen(space); - token.space = (char *)malloc(space_len + 1u); - if (token.space == NULL) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); - goto cap_mint_cleanup; - } - memcpy(token.space, space, space_len); - token.space[space_len] = '\0'; - token.space_len = space_len; - } - - if (!amduatd_cap_build_token(&token, - cap->hmac_key, - cap->hmac_key_len, - &token_bytes, - &token_len)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "token encode failed"); - goto cap_mint_cleanup; - } - if (!amduatd_b64url_encode(token_bytes, token_len, &token_b64)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "token encode failed"); - goto cap_mint_cleanup; - } - token_text = (char *)malloc(strlen(token_b64) + 5u); - if (token_text == NULL) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); - goto cap_mint_cleanup; - } - snprintf(token_text, strlen(token_b64) + 5u, "cap_%s", token_b64); - - amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); - { - char uid_buf[32]; - if (req->has_uid) { - snprintf(uid_buf, sizeof(uid_buf), "%u", (unsigned int)req->uid); - } else { - strncpy(uid_buf, "unknown", sizeof(uid_buf) - 1u); - uid_buf[sizeof(uid_buf) - 1u] = '\0'; - } - amduat_log(AMDUAT_LOG_INFO, - "cap mint uid=%s kind=%s expiry=%llu space=%s token=%s", - uid_buf, - amduatd_cap_kind_name(token.kind), - (unsigned long long)token.expiry_unix, - token.space_len != 0u ? token.space : "none", - token_hash); - } - - { - int n = snprintf(json, sizeof(json), "{\"token\":\"%s\"}\n", token_text); - if (n <= 0 || (size_t)n >= sizeof(json)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); - goto cap_mint_cleanup; - } - ok = amduatd_http_send_json(fd, 200, "OK", json, false); - } - -cap_mint_cleanup: - free(body); - free(kind); - free(token_bytes); - free(token_b64); - free(token_text); - amduatd_cap_token_free(&token); - return ok; -} - -static bool amduatd_handle_get_cap_resolve( - int fd, - amduat_asl_store_t *store, - const amduat_asl_store_fs_config_t *cfg, - const amduatd_concepts_t *concepts, - const amduatd_cfg_t *dcfg, - const amduatd_cap_state_t *cap, - const char *root_path, - const amduatd_http_req_t *req) { - const char *cap_header = NULL; - uint8_t *token_bytes = NULL; - size_t token_len = 0u; - amduatd_cap_token_t token; - const char *reason = "invalid"; - char token_hash[9]; - bool ok = false; - - memset(&token, 0, sizeof(token)); - memset(token_hash, 0, sizeof(token_hash)); - - if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || - cap == NULL || req == NULL || root_path == NULL) { - return amduatd_send_json_error(fd, 500, "Internal Server Error", - "internal error"); - } - (void)cfg; - (void)concepts; - if (!cap->enabled || !cap->reads_enabled) { - return amduatd_send_json_error(fd, 403, "Forbidden", - "cap reads disabled"); - } - - cap_header = req->x_capability; - if (cap_header == NULL || cap_header[0] == '\0') { - amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=missing token=none"); - return amduatd_send_json_error(fd, 401, "Unauthorized", - "missing capability"); - } - if (strncmp(cap_header, "cap_", 4u) != 0) { - amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-prefix token=none"); - return amduatd_send_json_error(fd, 401, "Unauthorized", - "invalid capability"); - } - if (!amduatd_b64url_decode(cap_header + 4u, &token_bytes, &token_len)) { - amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-base64 token=none"); - return amduatd_send_json_error(fd, 401, "Unauthorized", - "invalid capability"); - } - - amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); - if (!amduatd_cap_verify_token(cap, dcfg, token_bytes, token_len, - &token, &reason)) { - amduat_log(AMDUAT_LOG_INFO, - "cap use fail reason=%s kind=%s space=%s token=%s", - reason, - token.kind != 0u ? amduatd_cap_kind_name(token.kind) : "unknown", - token.space_len != 0u && token.space != NULL ? token.space : "none", - token_hash); - free(token_bytes); - amduatd_cap_token_free(&token); - return amduatd_send_json_error(fd, 403, "Forbidden", - "invalid capability"); - } - - amduat_log(AMDUAT_LOG_INFO, - "cap use ok kind=%s space=%s token=%s", - amduatd_cap_kind_name(token.kind), - token.space_len != 0u ? token.space : "none", - token_hash); - - if (token.kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { - char *ref_hex = NULL; - char path[1024]; - if (!amduat_asl_ref_encode_hex(token.artifact_ref, &ref_hex)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "encode error"); - goto cap_resolve_cleanup; - } - if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || - strlen(path) >= sizeof(path)) { - free(ref_hex); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); - goto cap_resolve_cleanup; - } - ok = amduatd_handle_get_artifact(fd, store, req, path, false); - free(ref_hex); - goto cap_resolve_cleanup; - } else if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME) { - amduat_asl_pointer_store_t pointer_store; - amduat_reference_t ref; - bool exists = false; - char format[32]; - memset(&ref, 0, sizeof(ref)); - if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "pointer store error"); - goto cap_resolve_cleanup; - } - if (amduat_asl_pointer_get(&pointer_store, token.pointer_name, - &exists, &ref) != AMDUAT_ASL_POINTER_OK) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "pointer read failed"); - goto cap_resolve_cleanup; - } - if (!exists) { - ok = amduatd_send_json_error(fd, 404, "Not Found", "not found"); - goto cap_resolve_cleanup; - } - memset(format, 0, sizeof(format)); - if (amduatd_query_param(req->path, "format", format, sizeof(format)) != NULL && - strcmp(format, "artifact") == 0) { - char *ref_hex = NULL; - char path[1024]; - if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "encode error"); - amduat_reference_free(&ref); - goto cap_resolve_cleanup; - } - if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || - strlen(path) >= sizeof(path)) { - free(ref_hex); - amduat_reference_free(&ref); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "error"); - goto cap_resolve_cleanup; - } - ok = amduatd_handle_get_artifact(fd, store, req, path, false); - free(ref_hex); - amduat_reference_free(&ref); - goto cap_resolve_cleanup; - } else { - char *ref_hex = NULL; - char json_out[2048]; - int n; - if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { - amduat_reference_free(&ref); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "encode error"); - goto cap_resolve_cleanup; - } - n = snprintf(json_out, sizeof(json_out), - "{\"ref\":\"%s\"}\n", ref_hex); - free(ref_hex); - amduat_reference_free(&ref); - if (n <= 0 || (size_t)n >= sizeof(json_out)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "error"); - goto cap_resolve_cleanup; - } - ok = amduatd_http_send_json(fd, 200, "OK", json_out, false); - goto cap_resolve_cleanup; - } - } else if (token.kind == AMDUATD_CAP_KIND_PEL_VIEW) { - amduat_pel_run_result_t run_result; - char *result_hex = NULL; - const char *status = "UNKNOWN"; - amduatd_strbuf_t resp; - size_t i; - memset(&run_result, 0, sizeof(run_result)); - memset(&resp, 0, sizeof(resp)); - if (!amduat_pel_surf_run_with_result( - store, - amduat_pel_program_dag_scheme_ref(), - token.program_ref, - token.input_refs, - token.input_refs_len, - token.has_params_ref, - token.params_ref, - &run_result)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "pel run failed"); - goto cap_view_cleanup; - } - if (!run_result.has_result_value) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "pel run failed"); - goto cap_view_cleanup; - } - status = amduat_format_pel_status_name( - run_result.result_value.core_result.status); - if (!amduat_asl_ref_encode_hex(run_result.result_ref, &result_hex)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "encode error"); - goto cap_view_cleanup; - } - if (!amduatd_strbuf_append_cstr(&resp, "{\"result_ref\":\"") || - !amduatd_strbuf_append_cstr(&resp, result_hex) || - !amduatd_strbuf_append_cstr(&resp, "\",\"output_refs\":[")) { - free(result_hex); - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); - goto cap_view_cleanup; - } - free(result_hex); - for (i = 0; i < run_result.output_refs_len; ++i) { - char *out_hex = NULL; - if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", - "encode error"); - goto cap_view_cleanup; - } - if (i != 0) { - (void)amduatd_strbuf_append_char(&resp, ','); - } - (void)amduatd_strbuf_append_cstr(&resp, "\""); - (void)amduatd_strbuf_append_cstr(&resp, out_hex); - (void)amduatd_strbuf_append_cstr(&resp, "\""); - free(out_hex); - } - if (!amduatd_strbuf_append_cstr(&resp, "],\"status\":\"") || - !amduatd_strbuf_append_cstr(&resp, status) || - !amduatd_strbuf_append_cstr(&resp, "\"}\n")) { - ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); - goto cap_view_cleanup; - } - ok = amduatd_http_send_json(fd, 200, "OK", resp.data, false); - goto cap_view_cleanup; -cap_view_cleanup: - amduatd_strbuf_free(&resp); - if (run_result.has_result_value) { - amduat_enc_pel1_result_free(&run_result.result_value); - } - if (run_result.output_refs != NULL) { - amduat_pel_surf_free_refs(run_result.output_refs, - run_result.output_refs_len); - } - amduat_pel_surf_free_ref(&run_result.result_ref); - goto cap_resolve_cleanup; - } else { - ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid capability"); - } - -cap_resolve_cleanup: - free(token_bytes); - amduatd_cap_token_free(&token); - return ok; -} - static bool amduatd_handle_conn(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, @@ -9489,7 +7853,7 @@ static bool amduatd_handle_conn(int fd, const amduatd_cfg_t *dcfg, const amduat_fed_coord_t *coord, const amduatd_allowlist_t *allowlist, - const amduatd_cap_state_t *cap_state, + amduatd_caps_t *caps, const char *root_path) { amduatd_http_req_t req; char no_query[1024]; @@ -9531,6 +7895,11 @@ static bool amduatd_handle_conn(int fd, ui_ctx.store = store; ui_ctx.ui_ref = ui_ref; + ui_ctx.store_cfg = cfg; + ui_ctx.concepts = concepts; + ui_ctx.daemon_cfg = dcfg; + ui_ctx.root_path = root_path; + ui_ctx.caps = caps; ui_resp.fd = fd; ui_resp.ok = false; if (amduatd_ui_can_handle(&req)) { @@ -9570,13 +7939,21 @@ static bool amduatd_handle_conn(int fd, } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/capabilities") == 0) { - ok = amduatd_handle_post_capabilities(fd, - store, - cfg, - concepts, - dcfg, - cap_state, - &req); + amduatd_ctx_t caps_ctx; + amduatd_http_resp_t caps_resp; + + caps_ctx.store = store; + caps_ctx.ui_ref = ui_ref; + caps_ctx.store_cfg = cfg; + caps_ctx.concepts = concepts; + caps_ctx.daemon_cfg = dcfg; + caps_ctx.root_path = root_path; + caps_ctx.caps = caps; + caps_resp.fd = fd; + caps_resp.ok = false; + if (amduatd_caps_handle(&caps_ctx, &req, &caps_resp)) { + ok = caps_resp.ok; + } goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) { @@ -9651,14 +8028,21 @@ static bool amduatd_handle_conn(int fd, } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/cap/resolve") == 0) { - ok = amduatd_handle_get_cap_resolve(fd, - store, - cfg, - concepts, - dcfg, - cap_state, - root_path, - &req); + amduatd_ctx_t caps_ctx; + amduatd_http_resp_t caps_resp; + + caps_ctx.store = store; + caps_ctx.ui_ref = ui_ref; + caps_ctx.store_cfg = cfg; + caps_ctx.concepts = concepts; + caps_ctx.daemon_cfg = dcfg; + caps_ctx.root_path = root_path; + caps_ctx.caps = caps; + caps_resp.fd = fd; + caps_resp.ok = false; + if (amduatd_caps_handle(&caps_ctx, &req, &caps_resp)) { + ok = caps_resp.ok; + } goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && @@ -9701,7 +8085,7 @@ int main(int argc, char **argv) { bool migrate_unscoped_edges = false; bool enable_cap_reads = false; amduatd_cfg_t dcfg; - amduatd_cap_state_t cap_state; + amduatd_caps_t caps; amduat_asl_store_fs_config_t cfg; amduat_asl_store_fs_t fs; amduat_asl_store_t store; @@ -9721,7 +8105,7 @@ int main(int argc, char **argv) { memset(&concepts, 0, sizeof(concepts)); memset(&allowlist, 0, sizeof(allowlist)); memset(&dcfg, 0, sizeof(dcfg)); - memset(&cap_state, 0, sizeof(cap_state)); + memset(&caps, 0, sizeof(caps)); for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--root") == 0) { @@ -9806,10 +8190,10 @@ int main(int argc, char **argv) { return 8; } amduat_asl_store_init(&store, cfg.config, amduat_asl_store_fs_ops(), &fs); - if (!amduatd_cap_init(root, &cap_state)) { + if (!amduatd_caps_init(&caps, &dcfg, root)) { amduat_log(AMDUAT_LOG_WARN, "capabilities unavailable"); } - cap_state.reads_enabled = enable_cap_reads; + caps.enable_cap_reads = enable_cap_reads; if (!amduatd_seed_api_contract(&store, &cfg, &api_contract_ref)) { fprintf(stderr, "error: failed to seed api contract\n"); @@ -9944,7 +8328,7 @@ int main(int argc, char **argv) { &dcfg, fed_coord, &allowlist, - &cap_state, + &caps, root); (void)close(cfd); } diff --git a/src/amduatd_caps.c b/src/amduatd_caps.c new file mode 100644 index 0000000..51063cf --- /dev/null +++ b/src/amduatd_caps.c @@ -0,0 +1,1947 @@ +#include "amduatd_caps.h" + +#include "amduat/asl/asl_pointer_fs.h" +#include "amduat/asl/ref_text.h" +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/enc/pel1_result.h" +#include "amduat/format/pel.h" +#include "amduat/hash/asl1.h" +#include "amduat/pel/program_dag_desc.h" +#include "amduat/pel/run.h" +#include "amduat/util/hex.h" +#include "amduat/util/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool amduatd_write_all(int fd, const uint8_t *buf, size_t len); +bool amduatd_read_exact(int fd, uint8_t *buf, size_t len); +bool amduatd_read_urandom(uint8_t *out, size_t len); + +bool amduatd_http_send_json(int fd, + int code, + const char *reason, + const char *json, + bool head_only); + +bool amduatd_send_json_error(int fd, + int code, + const char *reason, + const char *msg); + +const char *amduatd_query_param(const char *path, + const char *key, + char *out_value, + size_t out_cap); + +const char *amduatd_json_skip_ws(const char *p, const char *end); + +bool amduatd_json_expect(const char **p, + const char *end, + char expected); + +bool amduatd_json_parse_string_noesc(const char **p, + const char *end, + const char **out, + size_t *out_len); + +bool amduatd_json_parse_u64(const char **p, const char *end, uint64_t *out); + +bool amduatd_json_skip_value(const char **p, + const char *end, + int depth); + +bool amduatd_copy_json_str(const char *s, size_t len, char **out); + +amduatd_ref_status_t amduatd_decode_ref_or_name_latest( + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, + const amduatd_cfg_t *dcfg, + const char *s, + size_t len, + amduat_reference_t *out_ref); + +bool amduatd_handle_get_artifact(int fd, + amduat_asl_store_t *store, + const amduatd_http_req_t *req, + const char *path, + bool head_only); + +enum { + AMDUATD_CAP_MAGIC_LEN = 8, + AMDUATD_CAP_VERSION = 1 +}; + +enum { + AMDUATD_CAP_KIND_ARTIFACT_REF = 1, + AMDUATD_CAP_KIND_POINTER_NAME = 2, + AMDUATD_CAP_KIND_PEL_VIEW = 3 +}; + +static const uint64_t AMDUATD_CAP_MAX_EXPIRY_SECONDS = + 7u * 24u * 60u * 60u; + +static const uint8_t k_amduatd_cap_magic[AMDUATD_CAP_MAGIC_LEN] = { + 'A', 'S', 'L', 'C', 'A', 'P', '1', '\0' +}; + +typedef struct { + char *data; + size_t len; + size_t cap; +} amduatd_caps_strbuf_t; + +static void amduatd_caps_strbuf_free(amduatd_caps_strbuf_t *b) { + if (b == NULL) { + return; + } + free(b->data); + b->data = NULL; + b->len = 0u; + b->cap = 0u; +} + +static bool amduatd_caps_strbuf_reserve(amduatd_caps_strbuf_t *b, + size_t extra) { + char *next = NULL; + size_t need = 0u; + size_t next_cap; + if (b == NULL) { + return false; + } + if (extra == 0u) { + return true; + } + if (b->len > SIZE_MAX - extra) { + return false; + } + need = b->len + extra; + if (need <= b->cap) { + return true; + } + next_cap = b->cap != 0 ? b->cap : 256u; + while (next_cap < need) { + if (next_cap > (SIZE_MAX / 2u)) { + next_cap = need; + break; + } + next_cap *= 2u; + } + next = (char *)realloc(b->data, next_cap); + if (next == NULL) { + return false; + } + b->data = next; + b->cap = next_cap; + return true; +} + +static bool amduatd_caps_strbuf_append(amduatd_caps_strbuf_t *b, + const char *data, + size_t len) { + size_t n; + if (b == NULL) { + return false; + } + if (data == NULL) { + len = 0u; + } + n = len != 0u ? len : 0u; + if (!amduatd_caps_strbuf_reserve(b, n + 1u)) { + return false; + } + if (n != 0u) { + memcpy(b->data + b->len, data, n); + } + b->len += n; + b->data[b->len] = '\0'; + return true; +} + +static bool amduatd_caps_strbuf_append_cstr(amduatd_caps_strbuf_t *b, + const char *s) { + return amduatd_caps_strbuf_append(b, s != NULL ? s : "", + s != NULL ? strlen(s) : 0u); +} + +static bool amduatd_caps_strbuf_append_char(amduatd_caps_strbuf_t *b, + char c) { + return amduatd_caps_strbuf_append(b, &c, 1u); +} + +static bool amduatd_ct_memcmp(const uint8_t *a, + const uint8_t *b, + size_t len) { + uint8_t diff = 0; + if (a == NULL || b == NULL) { + return false; + } + for (size_t i = 0u; i < len; ++i) { + diff |= (uint8_t)(a[i] ^ b[i]); + } + return diff == 0u; +} + +static bool amduatd_sha256_digest(amduat_octets_t input, uint8_t out[32]) { + return amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, + input, + out, + 32u); +} + +static bool amduatd_hmac_sha256(const uint8_t *key, + size_t key_len, + const uint8_t *data, + size_t data_len, + uint8_t out[32]) { + uint8_t key_block[64]; + uint8_t ipad[64]; + uint8_t opad[64]; + uint8_t inner[32]; + size_t i; + amduat_hash_asl1_stream_t stream; + + if (key == NULL || data == NULL || out == NULL) { + return false; + } + memset(key_block, 0, sizeof(key_block)); + if (key_len > sizeof(key_block)) { + if (!amduatd_sha256_digest(amduat_octets(key, key_len), key_block)) { + return false; + } + } else if (key_len != 0u) { + memcpy(key_block, key, key_len); + } + + for (i = 0u; i < sizeof(key_block); ++i) { + ipad[i] = (uint8_t)(key_block[i] ^ 0x36u); + opad[i] = (uint8_t)(key_block[i] ^ 0x5cu); + } + + if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { + return false; + } + if (!amduat_hash_asl1_stream_update(&stream, + amduat_octets(ipad, sizeof(ipad))) || + !amduat_hash_asl1_stream_update(&stream, + amduat_octets(data, data_len)) || + !amduat_hash_asl1_stream_final(&stream, inner, sizeof(inner))) { + amduat_hash_asl1_stream_destroy(&stream); + return false; + } + amduat_hash_asl1_stream_destroy(&stream); + + if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) { + return false; + } + if (!amduat_hash_asl1_stream_update(&stream, + amduat_octets(opad, sizeof(opad))) || + !amduat_hash_asl1_stream_update(&stream, + amduat_octets(inner, sizeof(inner))) || + !amduat_hash_asl1_stream_final(&stream, out, 32u)) { + amduat_hash_asl1_stream_destroy(&stream); + return false; + } + amduat_hash_asl1_stream_destroy(&stream); + return true; +} + +static int amduatd_b64url_value(char c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } + if (c >= 'a' && c <= 'z') { + return c - 'a' + 26; + } + if (c >= '0' && c <= '9') { + return c - '0' + 52; + } + if (c == '-') { + return 62; + } + if (c == '_') { + return 63; + } + return -1; +} + +static bool amduatd_b64url_encode(const uint8_t *data, + size_t len, + char **out) { + static const char k_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + size_t out_len; + size_t i; + size_t o = 0; + char *buf; + + if (out != NULL) { + *out = NULL; + } + if (data == NULL || out == NULL) { + return false; + } + out_len = (len / 3u) * 4u; + if (len % 3u == 1u) { + out_len += 2u; + } else if (len % 3u == 2u) { + out_len += 3u; + } + if (out_len > SIZE_MAX - 1u) { + return false; + } + buf = (char *)malloc(out_len + 1u); + if (buf == NULL) { + return false; + } + + for (i = 0u; i + 2u < len; i += 3u) { + uint32_t v = ((uint32_t)data[i] << 16) | + ((uint32_t)data[i + 1u] << 8) | + (uint32_t)data[i + 2u]; + buf[o++] = k_table[(v >> 18) & 0x3fu]; + buf[o++] = k_table[(v >> 12) & 0x3fu]; + buf[o++] = k_table[(v >> 6) & 0x3fu]; + buf[o++] = k_table[v & 0x3fu]; + } + if (len % 3u == 1u) { + uint32_t v = (uint32_t)data[i] << 16; + buf[o++] = k_table[(v >> 18) & 0x3fu]; + buf[o++] = k_table[(v >> 12) & 0x3fu]; + } else if (len % 3u == 2u) { + uint32_t v = ((uint32_t)data[i] << 16) | + ((uint32_t)data[i + 1u] << 8); + buf[o++] = k_table[(v >> 18) & 0x3fu]; + buf[o++] = k_table[(v >> 12) & 0x3fu]; + buf[o++] = k_table[(v >> 6) & 0x3fu]; + } + buf[o] = '\0'; + *out = buf; + return true; +} + +static bool amduatd_b64url_decode(const char *text, + uint8_t **out, + size_t *out_len) { + size_t len; + size_t clean_len; + size_t i = 0u; + size_t o = 0u; + uint8_t *buf; + + if (out != NULL) { + *out = NULL; + } + if (out_len != NULL) { + *out_len = 0; + } + if (text == NULL || out == NULL || out_len == NULL) { + return false; + } + len = strlen(text); + clean_len = len; + while (clean_len > 0u && text[clean_len - 1u] == '=') { + clean_len--; + } + if (clean_len % 4u == 1u) { + return false; + } + *out_len = (clean_len / 4u) * 3u; + if (clean_len % 4u == 2u) { + *out_len += 1u; + } else if (clean_len % 4u == 3u) { + *out_len += 2u; + } + if (*out_len > SIZE_MAX) { + return false; + } + buf = (uint8_t *)malloc(*out_len); + if (buf == NULL) { + return false; + } + + while (i + 4u <= clean_len) { + int v0 = amduatd_b64url_value(text[i]); + int v1 = amduatd_b64url_value(text[i + 1u]); + int v2 = amduatd_b64url_value(text[i + 2u]); + int v3 = amduatd_b64url_value(text[i + 3u]); + uint32_t v; + if (v0 < 0 || v1 < 0 || v2 < 0 || v3 < 0) { + free(buf); + return false; + } + v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | + ((uint32_t)v2 << 6) | (uint32_t)v3; + buf[o++] = (uint8_t)((v >> 16) & 0xffu); + buf[o++] = (uint8_t)((v >> 8) & 0xffu); + buf[o++] = (uint8_t)(v & 0xffu); + i += 4u; + } + if (clean_len % 4u == 2u) { + int v0 = amduatd_b64url_value(text[i]); + int v1 = amduatd_b64url_value(text[i + 1u]); + uint32_t v; + if (v0 < 0 || v1 < 0) { + free(buf); + return false; + } + v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12); + buf[o++] = (uint8_t)((v >> 16) & 0xffu); + } else if (clean_len % 4u == 3u) { + int v0 = amduatd_b64url_value(text[i]); + int v1 = amduatd_b64url_value(text[i + 1u]); + int v2 = amduatd_b64url_value(text[i + 2u]); + uint32_t v; + if (v0 < 0 || v1 < 0 || v2 < 0) { + free(buf); + return false; + } + v = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12) | ((uint32_t)v2 << 6); + buf[o++] = (uint8_t)((v >> 16) & 0xffu); + buf[o++] = (uint8_t)((v >> 8) & 0xffu); + } + + if (o != *out_len) { + free(buf); + return false; + } + *out = buf; + return true; +} + +static void amduatd_store_u32_le(uint8_t *out, uint32_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); + out[2] = (uint8_t)((value >> 16) & 0xffu); + out[3] = (uint8_t)((value >> 24) & 0xffu); +} + +static bool amduatd_read_u32_le(const uint8_t *data, + size_t len, + size_t *offset, + uint32_t *out) { + if (len - *offset < 4u) { + return false; + } + *out = (uint32_t)data[*offset] | + ((uint32_t)data[*offset + 1u] << 8) | + ((uint32_t)data[*offset + 2u] << 16) | + ((uint32_t)data[*offset + 3u] << 24); + *offset += 4u; + return true; +} + +static void amduatd_store_u64_le(uint8_t *out, uint64_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); + out[2] = (uint8_t)((value >> 16) & 0xffu); + out[3] = (uint8_t)((value >> 24) & 0xffu); + out[4] = (uint8_t)((value >> 32) & 0xffu); + out[5] = (uint8_t)((value >> 40) & 0xffu); + out[6] = (uint8_t)((value >> 48) & 0xffu); + out[7] = (uint8_t)((value >> 56) & 0xffu); +} + +static bool amduatd_read_u64_le(const uint8_t *data, + size_t len, + size_t *offset, + uint64_t *out) { + if (len - *offset < 8u) { + return false; + } + *out = (uint64_t)data[*offset] | + ((uint64_t)data[*offset + 1u] << 8) | + ((uint64_t)data[*offset + 2u] << 16) | + ((uint64_t)data[*offset + 3u] << 24) | + ((uint64_t)data[*offset + 4u] << 32) | + ((uint64_t)data[*offset + 5u] << 40) | + ((uint64_t)data[*offset + 6u] << 48) | + ((uint64_t)data[*offset + 7u] << 56); + *offset += 8u; + return true; +} + +static bool amduatd_add_size(size_t *acc, size_t add) { + if (*acc > SIZE_MAX - add) { + return false; + } + *acc += add; + return true; +} + +typedef struct { + uint8_t kind; + char *space; + size_t space_len; + uint64_t expiry_unix; + amduat_reference_t artifact_ref; + char *pointer_name; + amduat_reference_t program_ref; + amduat_reference_t *input_refs; + size_t input_refs_len; + bool has_params_ref; + amduat_reference_t params_ref; +} amduatd_cap_token_t; + +static void amduatd_cap_token_free(amduatd_cap_token_t *token) { + if (token == NULL) { + return; + } + free(token->space); + token->space = NULL; + token->space_len = 0u; + amduat_reference_free(&token->artifact_ref); + free(token->pointer_name); + token->pointer_name = NULL; + amduat_reference_free(&token->program_ref); + if (token->input_refs != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + amduat_reference_free(&token->input_refs[i]); + } + free(token->input_refs); + } + token->input_refs = NULL; + token->input_refs_len = 0u; + if (token->has_params_ref) { + amduat_reference_free(&token->params_ref); + } + token->has_params_ref = false; + memset(token, 0, sizeof(*token)); +} + +static bool amduatd_pointer_name_scoped_for_space(const amduatd_cfg_t *dcfg, + const char *name) { + size_t space_len; + if (dcfg == NULL || name == NULL) { + return false; + } + if (!amduat_asl_pointer_name_is_valid(name)) { + return false; + } + if (!dcfg->space_enabled || dcfg->space_id == NULL) { + return true; + } + space_len = strlen(dcfg->space_id); + if (strncmp(name, "space/", 6u) != 0) { + return false; + } + if (strncmp(name + 6u, dcfg->space_id, space_len) != 0) { + return false; + } + if (name[6u + space_len] != '/') { + return false; + } + return true; +} + +static const char *amduatd_cap_kind_name(uint8_t kind) { + switch (kind) { + case AMDUATD_CAP_KIND_ARTIFACT_REF: + return "artifact_ref"; + case AMDUATD_CAP_KIND_POINTER_NAME: + return "pointer_name"; + case AMDUATD_CAP_KIND_PEL_VIEW: + return "pel_view"; + default: + return "unknown"; + } +} + +static void amduatd_cap_token_hash_prefix(const uint8_t *bytes, + size_t len, + char out[9]) { + uint8_t digest[32]; + if (out == NULL) { + return; + } + if (bytes == NULL || len == 0u || + !amduatd_sha256_digest(amduat_octets(bytes, len), digest)) { + strncpy(out, "unknown", 8u); + out[8] = '\0'; + return; + } + if (!amduat_hex_encode_lower(digest, 4u, out, 9u)) { + strncpy(out, "unknown", 8u); + out[8] = '\0'; + return; + } + out[8] = '\0'; +} + +bool amduatd_caps_init(amduatd_caps_t *caps, + const amduatd_cfg_t *cfg, + const char *root_path) { + char secrets_path[1024]; + char key_path[1060]; + struct stat st; + int fd = -1; + uint8_t key_bytes[32]; + uint8_t *key_copy = NULL; + + if (caps == NULL) { + return false; + } + memset(caps, 0, sizeof(*caps)); + (void)cfg; + if (root_path == NULL) { + return false; + } + { + int n = snprintf(secrets_path, sizeof(secrets_path), + "%s/secrets", root_path); + if (n <= 0 || (size_t)n >= sizeof(secrets_path)) { + return false; + } + } + if (stat(secrets_path, &st) != 0) { + if (errno != ENOENT) { + return false; + } + if (mkdir(secrets_path, 0700) != 0) { + return false; + } + } else if (!S_ISDIR(st.st_mode)) { + return false; + } + if (stat(secrets_path, &st) != 0) { + return false; + } + if ((st.st_mode & 0077u) != 0u) { + amduat_log(AMDUAT_LOG_WARN, + "capabilities disabled: secrets dir permissions too open"); + return false; + } + + { + int n = snprintf(key_path, sizeof(key_path), + "%s/secrets/cap_hmac_key", root_path); + if (n <= 0 || (size_t)n >= sizeof(key_path)) { + return false; + } + } + if (stat(key_path, &st) != 0) { + if (errno != ENOENT) { + return false; + } + fd = open(key_path, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + return false; + } + if (!amduatd_read_urandom(key_bytes, sizeof(key_bytes))) { + close(fd); + return false; + } + if (!amduatd_write_all(fd, key_bytes, sizeof(key_bytes))) { + close(fd); + return false; + } + close(fd); + } + + if (stat(key_path, &st) != 0) { + return false; + } + if (!S_ISREG(st.st_mode) || (st.st_mode & 0077u) != 0u || + st.st_size != (off_t)sizeof(key_bytes)) { + amduat_log(AMDUAT_LOG_WARN, + "capabilities disabled: cap_hmac_key permissions invalid"); + return false; + } + + fd = open(key_path, O_RDONLY); + if (fd < 0) { + return false; + } + if (!amduatd_read_exact(fd, key_bytes, sizeof(key_bytes))) { + close(fd); + return false; + } + close(fd); + + key_copy = (uint8_t *)malloc(sizeof(key_bytes)); + if (key_copy == NULL) { + return false; + } + memcpy(key_copy, key_bytes, sizeof(key_bytes)); + caps->hmac_key = amduat_octets(key_copy, sizeof(key_bytes)); + caps->enabled = true; + return true; +} + +void amduatd_caps_free(amduatd_caps_t *caps) { + if (caps == NULL) { + return; + } + free((void *)caps->hmac_key.data); + caps->hmac_key = amduat_octets(NULL, 0u); + caps->enabled = false; + caps->enable_cap_reads = false; +} + +static bool amduatd_cap_build_token(const amduatd_cap_token_t *token, + const uint8_t *key, + size_t key_len, + uint8_t **out, + size_t *out_len) { + size_t total = 0u; + size_t payload_len = 0u; + size_t offset = 0u; + size_t hmac_offset = 0u; + uint8_t *buf = NULL; + uint8_t hmac[32]; + amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); + amduat_octets_t program_bytes = amduat_octets(NULL, 0u); + amduat_octets_t *input_bytes = NULL; + amduat_octets_t params_bytes = amduat_octets(NULL, 0u); + + if (out != NULL) { + *out = NULL; + } + if (out_len != NULL) { + *out_len = 0u; + } + if (token == NULL || out == NULL || out_len == NULL || + key == NULL || key_len == 0u) { + return false; + } + + if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { + if (!amduat_enc_asl1_core_encode_reference_v1(token->artifact_ref, + &ref_bytes)) { + return false; + } + payload_len = 4u + ref_bytes.len; + } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { + if (token->pointer_name == NULL) { + return false; + } + payload_len = 4u + strlen(token->pointer_name); + } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { + size_t inputs_total = 0u; + if (!amduat_enc_asl1_core_encode_reference_v1(token->program_ref, + &program_bytes)) { + return false; + } + if (token->input_refs_len != 0u) { + input_bytes = (amduat_octets_t *)calloc(token->input_refs_len, + sizeof(*input_bytes)); + if (input_bytes == NULL) { + free((void *)program_bytes.data); + return false; + } + for (size_t i = 0u; i < token->input_refs_len; ++i) { + if (!amduat_enc_asl1_core_encode_reference_v1(token->input_refs[i], + &input_bytes[i])) { + free((void *)program_bytes.data); + for (size_t j = 0u; j < i; ++j) { + free((void *)input_bytes[j].data); + } + free(input_bytes); + return false; + } + if (!amduatd_add_size(&inputs_total, 4u + input_bytes[i].len)) { + free((void *)program_bytes.data); + for (size_t j = 0u; j <= i; ++j) { + free((void *)input_bytes[j].data); + } + free(input_bytes); + return false; + } + } + } + if (token->has_params_ref) { + if (!amduat_enc_asl1_core_encode_reference_v1(token->params_ref, + ¶ms_bytes)) { + free((void *)program_bytes.data); + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + free((void *)input_bytes[i].data); + } + free(input_bytes); + } + return false; + } + } + payload_len = 4u + program_bytes.len + 4u + inputs_total + 1u; + if (token->has_params_ref) { + payload_len += 4u + params_bytes.len; + } + } else { + return false; + } + + if (!amduatd_add_size(&total, AMDUATD_CAP_MAGIC_LEN) || + !amduatd_add_size(&total, 4u) || + !amduatd_add_size(&total, 4u + token->space_len) || + !amduatd_add_size(&total, 8u) || + !amduatd_add_size(&total, 1u) || + !amduatd_add_size(&total, payload_len) || + !amduatd_add_size(&total, 4u) || + !amduatd_add_size(&total, sizeof(hmac))) { + free((void *)ref_bytes.data); + free((void *)program_bytes.data); + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + free((void *)input_bytes[i].data); + } + free(input_bytes); + } + free((void *)params_bytes.data); + return false; + } + + buf = (uint8_t *)malloc(total); + if (buf == NULL) { + free((void *)ref_bytes.data); + free((void *)program_bytes.data); + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + free((void *)input_bytes[i].data); + } + free(input_bytes); + } + free((void *)params_bytes.data); + return false; + } + + memcpy(buf + offset, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN); + offset += AMDUATD_CAP_MAGIC_LEN; + amduatd_store_u32_le(buf + offset, AMDUATD_CAP_VERSION); + offset += 4u; + amduatd_store_u32_le(buf + offset, (uint32_t)token->space_len); + offset += 4u; + if (token->space_len != 0u && token->space != NULL) { + memcpy(buf + offset, token->space, token->space_len); + offset += token->space_len; + } + amduatd_store_u64_le(buf + offset, token->expiry_unix); + offset += 8u; + buf[offset++] = token->kind; + + if (token->kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { + amduatd_store_u32_le(buf + offset, (uint32_t)ref_bytes.len); + offset += 4u; + if (ref_bytes.len != 0u) { + memcpy(buf + offset, ref_bytes.data, ref_bytes.len); + offset += ref_bytes.len; + } + } else if (token->kind == AMDUATD_CAP_KIND_POINTER_NAME) { + uint32_t name_len = (uint32_t)strlen(token->pointer_name); + amduatd_store_u32_le(buf + offset, name_len); + offset += 4u; + if (name_len != 0u) { + memcpy(buf + offset, token->pointer_name, name_len); + offset += name_len; + } + } else if (token->kind == AMDUATD_CAP_KIND_PEL_VIEW) { + amduatd_store_u32_le(buf + offset, (uint32_t)program_bytes.len); + offset += 4u; + if (program_bytes.len != 0u) { + memcpy(buf + offset, program_bytes.data, program_bytes.len); + offset += program_bytes.len; + } + amduatd_store_u32_le(buf + offset, (uint32_t)token->input_refs_len); + offset += 4u; + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + amduatd_store_u32_le(buf + offset, (uint32_t)input_bytes[i].len); + offset += 4u; + if (input_bytes[i].len != 0u) { + memcpy(buf + offset, input_bytes[i].data, input_bytes[i].len); + offset += input_bytes[i].len; + } + } + } + buf[offset++] = token->has_params_ref ? 1u : 0u; + if (token->has_params_ref) { + amduatd_store_u32_le(buf + offset, (uint32_t)params_bytes.len); + offset += 4u; + if (params_bytes.len != 0u) { + memcpy(buf + offset, params_bytes.data, params_bytes.len); + offset += params_bytes.len; + } + } + } + + hmac_offset = offset; + amduatd_store_u32_le(buf + offset, (uint32_t)sizeof(hmac)); + offset += 4u; + + if (!amduatd_hmac_sha256(key, key_len, buf, hmac_offset, hmac)) { + free(buf); + free((void *)ref_bytes.data); + free((void *)program_bytes.data); + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + free((void *)input_bytes[i].data); + } + free(input_bytes); + } + free((void *)params_bytes.data); + return false; + } + memcpy(buf + offset, hmac, sizeof(hmac)); + offset += sizeof(hmac); + + free((void *)ref_bytes.data); + free((void *)program_bytes.data); + if (input_bytes != NULL) { + for (size_t i = 0u; i < token->input_refs_len; ++i) { + free((void *)input_bytes[i].data); + } + free(input_bytes); + } + free((void *)params_bytes.data); + + if (offset != total) { + free(buf); + return false; + } + *out = buf; + *out_len = total; + return true; +} + +static bool amduatd_cap_decode_token(const uint8_t *bytes, + size_t len, + amduatd_cap_token_t *out_token, + size_t *out_hmac_offset, + uint32_t *out_hmac_len, + const uint8_t **out_hmac) { + size_t offset = 0u; + uint32_t version = 0u; + uint32_t space_len = 0u; + uint32_t ref_len = 0u; + uint32_t input_count = 0u; + uint32_t hmac_len = 0u; + uint64_t expiry = 0u; + uint8_t kind = 0u; + amduatd_cap_token_t token; + const uint8_t *hmac_ptr = NULL; + + if (out_token == NULL || out_hmac_offset == NULL || + out_hmac_len == NULL || out_hmac == NULL) { + return false; + } + memset(&token, 0, sizeof(token)); + + if (len < AMDUATD_CAP_MAGIC_LEN + 4u + 4u + 8u + 1u + 4u + 32u) { + return false; + } + if (memcmp(bytes, k_amduatd_cap_magic, AMDUATD_CAP_MAGIC_LEN) != 0) { + return false; + } + offset += AMDUATD_CAP_MAGIC_LEN; + if (!amduatd_read_u32_le(bytes, len, &offset, &version) || + version != AMDUATD_CAP_VERSION) { + return false; + } + if (!amduatd_read_u32_le(bytes, len, &offset, &space_len)) { + return false; + } + if (len - offset < space_len) { + return false; + } + if (space_len != 0u) { + token.space = (char *)malloc(space_len + 1u); + if (token.space == NULL) { + return false; + } + memcpy(token.space, bytes + offset, space_len); + token.space[space_len] = '\0'; + token.space_len = space_len; + } + offset += space_len; + if (!amduatd_read_u64_le(bytes, len, &offset, &expiry)) { + amduatd_cap_token_free(&token); + return false; + } + token.expiry_unix = expiry; + if (offset >= len) { + amduatd_cap_token_free(&token); + return false; + } + kind = bytes[offset++]; + token.kind = kind; + + if (kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { + if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { + amduatd_cap_token_free(&token); + return false; + } + if (len - offset < ref_len) { + amduatd_cap_token_free(&token); + return false; + } + if (!amduat_enc_asl1_core_decode_reference_v1( + amduat_octets(bytes + offset, ref_len), &token.artifact_ref)) { + amduatd_cap_token_free(&token); + return false; + } + offset += ref_len; + } else if (kind == AMDUATD_CAP_KIND_POINTER_NAME) { + uint32_t name_len = 0u; + if (!amduatd_read_u32_le(bytes, len, &offset, &name_len)) { + amduatd_cap_token_free(&token); + return false; + } + if (len - offset < name_len) { + amduatd_cap_token_free(&token); + return false; + } + token.pointer_name = (char *)malloc(name_len + 1u); + if (token.pointer_name == NULL) { + amduatd_cap_token_free(&token); + return false; + } + memcpy(token.pointer_name, bytes + offset, name_len); + token.pointer_name[name_len] = '\0'; + offset += name_len; + if (!amduat_asl_pointer_name_is_valid(token.pointer_name)) { + amduatd_cap_token_free(&token); + return false; + } + } else if (kind == AMDUATD_CAP_KIND_PEL_VIEW) { + if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len)) { + amduatd_cap_token_free(&token); + return false; + } + if (len - offset < ref_len) { + amduatd_cap_token_free(&token); + return false; + } + if (!amduat_enc_asl1_core_decode_reference_v1( + amduat_octets(bytes + offset, ref_len), &token.program_ref)) { + amduatd_cap_token_free(&token); + return false; + } + offset += ref_len; + if (!amduatd_read_u32_le(bytes, len, &offset, &input_count)) { + amduatd_cap_token_free(&token); + return false; + } + if (input_count != 0u) { + token.input_refs = (amduat_reference_t *)calloc( + input_count, sizeof(*token.input_refs)); + if (token.input_refs == NULL) { + amduatd_cap_token_free(&token); + return false; + } + token.input_refs_len = input_count; + for (uint32_t i = 0u; i < input_count; ++i) { + if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || + len - offset < ref_len || + !amduat_enc_asl1_core_decode_reference_v1( + amduat_octets(bytes + offset, ref_len), + &token.input_refs[i])) { + amduatd_cap_token_free(&token); + return false; + } + offset += ref_len; + } + } + if (offset >= len) { + amduatd_cap_token_free(&token); + return false; + } + token.has_params_ref = bytes[offset++] != 0u; + if (token.has_params_ref) { + if (!amduatd_read_u32_le(bytes, len, &offset, &ref_len) || + len - offset < ref_len || + !amduat_enc_asl1_core_decode_reference_v1( + amduat_octets(bytes + offset, ref_len), + &token.params_ref)) { + amduatd_cap_token_free(&token); + return false; + } + offset += ref_len; + } + } else { + amduatd_cap_token_free(&token); + return false; + } + + *out_hmac_offset = offset; + if (!amduatd_read_u32_le(bytes, len, &offset, &hmac_len)) { + amduatd_cap_token_free(&token); + return false; + } + if (len - offset < hmac_len) { + amduatd_cap_token_free(&token); + return false; + } + hmac_ptr = bytes + offset; + offset += hmac_len; + if (offset != len) { + amduatd_cap_token_free(&token); + return false; + } + + *out_hmac_len = hmac_len; + *out_hmac = hmac_ptr; + *out_token = token; + return true; +} + +static bool amduatd_cap_verify_token(const amduatd_caps_t *cap, + const amduatd_cfg_t *dcfg, + const uint8_t *bytes, + size_t len, + amduatd_cap_token_t *out_token, + const char **out_reason) { + amduatd_cap_token_t token; + size_t hmac_offset = 0u; + uint32_t hmac_len = 0u; + const uint8_t *hmac = NULL; + uint8_t expected[32]; + time_t now; + + if (out_reason != NULL) { + *out_reason = "invalid"; + } + if (cap == NULL || dcfg == NULL || bytes == NULL || out_token == NULL) { + return false; + } + if (!amduatd_cap_decode_token(bytes, len, &token, + &hmac_offset, &hmac_len, &hmac)) { + return false; + } + if (hmac_len != sizeof(expected)) { + *out_token = token; + return false; + } + if (!amduatd_hmac_sha256((const uint8_t *)cap->hmac_key.data, + cap->hmac_key.len, + bytes, + hmac_offset, + expected) || + !amduatd_ct_memcmp(expected, hmac, sizeof(expected))) { + if (out_reason != NULL) { + *out_reason = "bad-mac"; + } + *out_token = token; + return false; + } + + now = time(NULL); + if (now < 0 || token.expiry_unix < (uint64_t)now) { + if (out_reason != NULL) { + *out_reason = "expired"; + } + *out_token = token; + return false; + } + + if (dcfg->space_enabled) { + if (token.space_len == 0u || token.space == NULL || + strcmp(token.space, dcfg->space_id) != 0) { + if (out_reason != NULL) { + *out_reason = "wrong-space"; + } + *out_token = token; + return false; + } + } else { + if (token.space_len != 0u) { + if (out_reason != NULL) { + *out_reason = "wrong-space"; + } + *out_token = token; + return false; + } + } + if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME && + !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { + if (out_reason != NULL) { + *out_reason = "wrong-space"; + } + *out_token = token; + return false; + } + + *out_token = token; + if (out_reason != NULL) { + *out_reason = "ok"; + } + return true; +} + +static bool amduatd_caps_path_without_query(const char *path, + char *out, + size_t cap) { + const char *q = NULL; + size_t n = 0; + if (out == NULL || cap == 0) { + return false; + } + out[0] = '\0'; + if (path == NULL) { + return false; + } + q = strchr(path, '?'); + n = q != NULL ? (size_t)(q - path) : strlen(path); + if (n >= cap) { + n = cap - 1; + } + memcpy(out, path, n); + out[n] = '\0'; + return true; +} + +static bool amduatd_handle_post_capabilities( + int fd, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, + const amduatd_cfg_t *dcfg, + const amduatd_caps_t *cap, + const amduatd_http_req_t *req) { + uint8_t *body = NULL; + const char *p = NULL; + const char *end = NULL; + bool have_kind = false; + bool have_target = false; + bool have_expiry = false; + char *kind = NULL; + uint64_t expiry_seconds = 0u; + amduatd_cap_token_t token; + bool target_have_ref = false; + bool target_have_name = false; + bool target_have_program_ref = false; + bool target_have_inputs = false; + bool target_have_params_ref = false; + size_t input_refs_cap = 0u; + uint8_t *token_bytes = NULL; + size_t token_len = 0u; + char *token_b64 = NULL; + char *token_text = NULL; + char token_hash[9]; + char json[4096]; + bool ok = false; + const char *space = NULL; + size_t space_len = 0u; + time_t now; + + memset(&token, 0, sizeof(token)); + memset(token_hash, 0, sizeof(token_hash)); + + if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || + cap == NULL || req == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } + if (!cap->enabled) { + return amduatd_send_json_error(fd, 403, "Forbidden", + "capabilities disabled"); + } + if (req->content_length == 0) { + return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); + } + if (req->content_length > (256u * 1024u)) { + return amduatd_send_json_error(fd, 413, "Payload Too Large", + "payload too large"); + } + + body = (uint8_t *)malloc(req->content_length); + if (body == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + } + if (!amduatd_read_exact(fd, body, req->content_length)) { + free(body); + return false; + } + p = (const char *)body; + end = (const char *)body + req->content_length; + if (!amduatd_json_expect(&p, end, '{')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto cap_mint_cleanup; + } + + for (;;) { + const char *key = NULL; + size_t key_len = 0; + const char *cur = NULL; + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || + !amduatd_json_expect(&p, end, ':')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto cap_mint_cleanup; + } + + if (key_len == strlen("kind") && memcmp(key, "kind", key_len) == 0) { + const char *sv = NULL; + size_t sv_len = 0; + if (have_kind || + !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || + !amduatd_copy_json_str(sv, sv_len, &kind)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); + goto cap_mint_cleanup; + } + have_kind = true; + } else if (key_len == strlen("expiry_seconds") && + memcmp(key, "expiry_seconds", key_len) == 0) { + if (have_expiry || !amduatd_json_parse_u64(&p, end, &expiry_seconds)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid expiry_seconds"); + goto cap_mint_cleanup; + } + have_expiry = true; + } else if (key_len == strlen("target") && + memcmp(key, "target", key_len) == 0) { + if (have_target || !amduatd_json_expect(&p, end, '{')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid target"); + goto cap_mint_cleanup; + } + for (;;) { + const char *tkey = NULL; + size_t tkey_len = 0; + const char *sv = NULL; + size_t sv_len = 0; + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + if (!amduatd_json_parse_string_noesc(&p, end, &tkey, &tkey_len) || + !amduatd_json_expect(&p, end, ':')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid target"); + goto cap_mint_cleanup; + } + + if (tkey_len == strlen("ref") && memcmp(tkey, "ref", tkey_len) == 0) { + amduatd_ref_status_t st; + if (target_have_ref || + !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid ref"); + goto cap_mint_cleanup; + } + st = amduatd_decode_ref_or_name_latest(store, + cfg, + concepts, + dcfg, + sv, + sv_len, + &token.artifact_ref); + if (st != AMDUATD_REF_OK) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid ref"); + goto cap_mint_cleanup; + } + target_have_ref = true; + } else if (tkey_len == strlen("name") && + memcmp(tkey, "name", tkey_len) == 0) { + if (target_have_name || + !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || + !amduatd_copy_json_str(sv, sv_len, &token.pointer_name)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid name"); + goto cap_mint_cleanup; + } + target_have_name = true; + } else if (tkey_len == strlen("program_ref") && + memcmp(tkey, "program_ref", tkey_len) == 0) { + amduatd_ref_status_t st; + if (target_have_program_ref || + !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid program_ref"); + goto cap_mint_cleanup; + } + st = amduatd_decode_ref_or_name_latest(store, + cfg, + concepts, + dcfg, + sv, + sv_len, + &token.program_ref); + if (st != AMDUATD_REF_OK) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid program_ref"); + goto cap_mint_cleanup; + } + target_have_program_ref = true; + } else if (tkey_len == strlen("input_refs") && + memcmp(tkey, "input_refs", tkey_len) == 0) { + if (target_have_inputs || !amduatd_json_expect(&p, end, '[')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid input_refs"); + goto cap_mint_cleanup; + } + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ']') { + p = cur + 1; + target_have_inputs = true; + } else { + for (;;) { + amduat_reference_t ref; + amduatd_ref_status_t st; + if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid input_refs"); + goto cap_mint_cleanup; + } + memset(&ref, 0, sizeof(ref)); + st = amduatd_decode_ref_or_name_latest(store, + cfg, + concepts, + dcfg, + sv, + sv_len, + &ref); + if (st != AMDUATD_REF_OK) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid input_ref"); + goto cap_mint_cleanup; + } + if (token.input_refs_len == 0u) { + input_refs_cap = 4u; + token.input_refs = (amduat_reference_t *)calloc( + input_refs_cap, sizeof(*token.input_refs)); + if (token.input_refs == NULL) { + amduat_reference_free(&ref); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "oom"); + goto cap_mint_cleanup; + } + token.input_refs_len = 0u; + } else if (token.input_refs_len == input_refs_cap) { + size_t next_cap = input_refs_cap * 2u; + amduat_reference_t *next = (amduat_reference_t *)realloc( + token.input_refs, next_cap * sizeof(*token.input_refs)); + if (next == NULL) { + amduat_reference_free(&ref); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "oom"); + goto cap_mint_cleanup; + } + token.input_refs = next; + input_refs_cap = next_cap; + } + token.input_refs[token.input_refs_len++] = ref; + target_have_inputs = true; + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ',') { + p = cur + 1; + continue; + } + if (cur < end && *cur == ']') { + p = cur + 1; + target_have_inputs = true; + break; + } + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid input_refs"); + goto cap_mint_cleanup; + } + } + } else if (tkey_len == strlen("params_ref") && + memcmp(tkey, "params_ref", tkey_len) == 0) { + amduatd_ref_status_t st; + if (target_have_params_ref || + !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid params_ref"); + goto cap_mint_cleanup; + } + st = amduatd_decode_ref_or_name_latest(store, + cfg, + concepts, + dcfg, + sv, + sv_len, + &token.params_ref); + if (st != AMDUATD_REF_OK) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid params_ref"); + goto cap_mint_cleanup; + } + token.has_params_ref = true; + target_have_params_ref = true; + } else { + if (!amduatd_json_skip_value(&p, end, 0)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid target"); + goto cap_mint_cleanup; + } + } + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ',') { + p = cur + 1; + continue; + } + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid target"); + goto cap_mint_cleanup; + } + have_target = true; + } else { + if (!amduatd_json_skip_value(&p, end, 0)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto cap_mint_cleanup; + } + } + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ',') { + p = cur + 1; + continue; + } + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto cap_mint_cleanup; + } + + if (!have_kind || !have_target || !have_expiry) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "missing fields"); + goto cap_mint_cleanup; + } + if (expiry_seconds == 0u || expiry_seconds > AMDUATD_CAP_MAX_EXPIRY_SECONDS) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "expiry_seconds out of range"); + goto cap_mint_cleanup; + } + now = time(NULL); + if (now < 0 || + expiry_seconds > (uint64_t)(UINT64_MAX - (uint64_t)now)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "expiry_seconds out of range"); + goto cap_mint_cleanup; + } + token.expiry_unix = (uint64_t)now + expiry_seconds; + + if (strcmp(kind, "artifact_ref") == 0) { + token.kind = AMDUATD_CAP_KIND_ARTIFACT_REF; + if (token.artifact_ref.hash_id == 0 || + token.artifact_ref.digest.data == NULL) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "missing ref"); + goto cap_mint_cleanup; + } + } else if (strcmp(kind, "pointer_name") == 0) { + token.kind = AMDUATD_CAP_KIND_POINTER_NAME; + if (token.pointer_name == NULL || + !amduatd_pointer_name_scoped_for_space(dcfg, token.pointer_name)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid name"); + goto cap_mint_cleanup; + } + } else if (strcmp(kind, "pel_view") == 0) { + token.kind = AMDUATD_CAP_KIND_PEL_VIEW; + if (token.program_ref.hash_id == 0 || + token.program_ref.digest.data == NULL) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "missing program_ref"); + goto cap_mint_cleanup; + } + if (!target_have_inputs) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "missing input_refs"); + goto cap_mint_cleanup; + } + } else { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid kind"); + goto cap_mint_cleanup; + } + + if (dcfg->space_enabled && dcfg->space_id != NULL) { + space = dcfg->space_id; + space_len = strlen(space); + token.space = (char *)malloc(space_len + 1u); + if (token.space == NULL) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + goto cap_mint_cleanup; + } + memcpy(token.space, space, space_len); + token.space[space_len] = '\0'; + token.space_len = space_len; + } + + if (!amduatd_cap_build_token(&token, + cap->hmac_key.data, + cap->hmac_key.len, + &token_bytes, + &token_len)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "token encode failed"); + goto cap_mint_cleanup; + } + if (!amduatd_b64url_encode(token_bytes, token_len, &token_b64)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "token encode failed"); + goto cap_mint_cleanup; + } + token_text = (char *)malloc(strlen(token_b64) + 5u); + if (token_text == NULL) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + goto cap_mint_cleanup; + } + snprintf(token_text, strlen(token_b64) + 5u, "cap_%s", token_b64); + + amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); + { + char uid_buf[32]; + if (req->has_uid) { + snprintf(uid_buf, sizeof(uid_buf), "%u", (unsigned int)req->uid); + } else { + strncpy(uid_buf, "unknown", sizeof(uid_buf) - 1u); + uid_buf[sizeof(uid_buf) - 1u] = '\0'; + } + amduat_log(AMDUAT_LOG_INFO, + "cap mint uid=%s kind=%s expiry=%llu space=%s token=%s", + uid_buf, + amduatd_cap_kind_name(token.kind), + (unsigned long long)token.expiry_unix, + token.space_len != 0u ? token.space : "none", + token_hash); + } + + { + int n = snprintf(json, sizeof(json), "{\"token\":\"%s\"}\n", token_text); + if (n <= 0 || (size_t)n >= sizeof(json)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); + goto cap_mint_cleanup; + } + ok = amduatd_http_send_json(fd, 200, "OK", json, false); + } + +cap_mint_cleanup: + free(body); + free(kind); + free(token_bytes); + free(token_b64); + free(token_text); + amduatd_cap_token_free(&token); + return ok; +} + +static bool amduatd_handle_get_cap_resolve( + int fd, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, + const amduatd_cfg_t *dcfg, + const amduatd_caps_t *cap, + const char *root_path, + const amduatd_http_req_t *req) { + const char *cap_header = NULL; + uint8_t *token_bytes = NULL; + size_t token_len = 0u; + amduatd_cap_token_t token; + const char *reason = "invalid"; + char token_hash[9]; + bool ok = false; + + memset(&token, 0, sizeof(token)); + memset(token_hash, 0, sizeof(token_hash)); + + if (store == NULL || cfg == NULL || concepts == NULL || dcfg == NULL || + cap == NULL || req == NULL || root_path == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } + (void)cfg; + (void)concepts; + if (!cap->enabled || !cap->enable_cap_reads) { + return amduatd_send_json_error(fd, 403, "Forbidden", + "cap reads disabled"); + } + + cap_header = req->x_capability; + if (cap_header == NULL || cap_header[0] == '\0') { + amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=missing token=none"); + return amduatd_send_json_error(fd, 401, "Unauthorized", + "missing capability"); + } + if (strncmp(cap_header, "cap_", 4u) != 0) { + amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-prefix token=none"); + return amduatd_send_json_error(fd, 401, "Unauthorized", + "invalid capability"); + } + if (!amduatd_b64url_decode(cap_header + 4u, &token_bytes, &token_len)) { + amduat_log(AMDUAT_LOG_INFO, "cap use fail reason=bad-base64 token=none"); + return amduatd_send_json_error(fd, 401, "Unauthorized", + "invalid capability"); + } + + amduatd_cap_token_hash_prefix(token_bytes, token_len, token_hash); + if (!amduatd_cap_verify_token(cap, dcfg, token_bytes, token_len, + &token, &reason)) { + amduat_log(AMDUAT_LOG_INFO, + "cap use fail reason=%s kind=%s space=%s token=%s", + reason, + token.kind != 0u ? amduatd_cap_kind_name(token.kind) : "unknown", + token.space_len != 0u && token.space != NULL ? token.space : "none", + token_hash); + free(token_bytes); + amduatd_cap_token_free(&token); + return amduatd_send_json_error(fd, 403, "Forbidden", + "invalid capability"); + } + + amduat_log(AMDUAT_LOG_INFO, + "cap use ok kind=%s space=%s token=%s", + amduatd_cap_kind_name(token.kind), + token.space_len != 0u ? token.space : "none", + token_hash); + + if (token.kind == AMDUATD_CAP_KIND_ARTIFACT_REF) { + char *ref_hex = NULL; + char path[1024]; + if (!amduat_asl_ref_encode_hex(token.artifact_ref, &ref_hex)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + goto cap_resolve_cleanup; + } + if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || + strlen(path) >= sizeof(path)) { + free(ref_hex); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); + goto cap_resolve_cleanup; + } + ok = amduatd_handle_get_artifact(fd, store, req, path, false); + free(ref_hex); + goto cap_resolve_cleanup; + } else if (token.kind == AMDUATD_CAP_KIND_POINTER_NAME) { + amduat_asl_pointer_store_t pointer_store; + amduat_reference_t ref; + bool exists = false; + char format[32]; + memset(&ref, 0, sizeof(ref)); + if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "pointer store error"); + goto cap_resolve_cleanup; + } + if (amduat_asl_pointer_get(&pointer_store, token.pointer_name, + &exists, &ref) != AMDUAT_ASL_POINTER_OK) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "pointer read failed"); + goto cap_resolve_cleanup; + } + if (!exists) { + ok = amduatd_send_json_error(fd, 404, "Not Found", "not found"); + goto cap_resolve_cleanup; + } + memset(format, 0, sizeof(format)); + if (amduatd_query_param(req->path, "format", format, sizeof(format)) != NULL && + strcmp(format, "artifact") == 0) { + char *ref_hex = NULL; + char path[1024]; + if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + amduat_reference_free(&ref); + goto cap_resolve_cleanup; + } + if (snprintf(path, sizeof(path), "/v1/artifacts/%s", ref_hex) <= 0 || + strlen(path) >= sizeof(path)) { + free(ref_hex); + amduat_reference_free(&ref); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "error"); + goto cap_resolve_cleanup; + } + ok = amduatd_handle_get_artifact(fd, store, req, path, false); + free(ref_hex); + amduat_reference_free(&ref); + goto cap_resolve_cleanup; + } else { + char *ref_hex = NULL; + char json_out[2048]; + int n; + if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { + amduat_reference_free(&ref); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + goto cap_resolve_cleanup; + } + n = snprintf(json_out, sizeof(json_out), + "{\"ref\":\"%s\"}\n", ref_hex); + free(ref_hex); + amduat_reference_free(&ref); + if (n <= 0 || (size_t)n >= sizeof(json_out)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "error"); + goto cap_resolve_cleanup; + } + ok = amduatd_http_send_json(fd, 200, "OK", json_out, false); + goto cap_resolve_cleanup; + } + } else if (token.kind == AMDUATD_CAP_KIND_PEL_VIEW) { + amduat_pel_run_result_t run_result; + char *result_hex = NULL; + const char *status = "UNKNOWN"; + amduatd_caps_strbuf_t resp; + size_t i; + memset(&run_result, 0, sizeof(run_result)); + memset(&resp, 0, sizeof(resp)); + if (!amduat_pel_surf_run_with_result( + store, + amduat_pel_program_dag_scheme_ref(), + token.program_ref, + token.input_refs, + token.input_refs_len, + token.has_params_ref, + token.params_ref, + &run_result)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "pel run failed"); + goto cap_view_cleanup; + } + if (!run_result.has_result_value) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "pel run failed"); + goto cap_view_cleanup; + } + status = amduat_format_pel_status_name( + run_result.result_value.core_result.status); + if (!amduat_asl_ref_encode_hex(run_result.result_ref, &result_hex)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + goto cap_view_cleanup; + } + if (!amduatd_caps_strbuf_append_cstr(&resp, "{\"result_ref\":\"") || + !amduatd_caps_strbuf_append_cstr(&resp, result_hex) || + !amduatd_caps_strbuf_append_cstr(&resp, "\",\"output_refs\":[")) { + free(result_hex); + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + goto cap_view_cleanup; + } + free(result_hex); + for (i = 0; i < run_result.output_refs_len; ++i) { + char *out_hex = NULL; + if (!amduat_asl_ref_encode_hex(run_result.output_refs[i], &out_hex)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + goto cap_view_cleanup; + } + if (i != 0) { + (void)amduatd_caps_strbuf_append_char(&resp, ','); + } + (void)amduatd_caps_strbuf_append_cstr(&resp, "\""); + (void)amduatd_caps_strbuf_append_cstr(&resp, out_hex); + (void)amduatd_caps_strbuf_append_cstr(&resp, "\""); + free(out_hex); + } + if (!amduatd_caps_strbuf_append_cstr(&resp, "],\"status\":\"") || + !amduatd_caps_strbuf_append_cstr(&resp, status) || + !amduatd_caps_strbuf_append_cstr(&resp, "\"}\n")) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + goto cap_view_cleanup; + } + ok = amduatd_http_send_json(fd, 200, "OK", resp.data, false); + goto cap_view_cleanup; +cap_view_cleanup: + amduatd_caps_strbuf_free(&resp); + if (run_result.has_result_value) { + amduat_enc_pel1_result_free(&run_result.result_value); + } + if (run_result.output_refs != NULL) { + amduat_pel_surf_free_refs(run_result.output_refs, + run_result.output_refs_len); + } + amduat_pel_surf_free_ref(&run_result.result_ref); + goto cap_resolve_cleanup; + } else { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid capability"); + } + +cap_resolve_cleanup: + free(token_bytes); + amduatd_cap_token_free(&token); + return ok; +} + +bool amduatd_caps_can_handle(const amduatd_http_req_t *req) { + char no_query[1024]; + + if (req == NULL) { + return false; + } + if (!amduatd_caps_path_without_query(req->path, no_query, sizeof(no_query))) { + return false; + } + if (strcmp(req->method, "POST") == 0 && + strcmp(no_query, "/v1/capabilities") == 0) { + return true; + } + if (strcmp(req->method, "GET") == 0 && + strcmp(no_query, "/v1/cap/resolve") == 0) { + return true; + } + return false; +} + +bool amduatd_caps_handle(amduatd_ctx_t *ctx, + const amduatd_http_req_t *req, + amduatd_http_resp_t *resp) { + char no_query[1024]; + + if (ctx == NULL || req == NULL || resp == NULL) { + return false; + } + if (!amduatd_caps_can_handle(req)) { + return false; + } + if (!amduatd_caps_path_without_query(req->path, no_query, sizeof(no_query))) { + resp->ok = amduatd_send_json_error(resp->fd, + 400, + "Bad Request", + "invalid path"); + return true; + } + + if (strcmp(req->method, "POST") == 0 && + strcmp(no_query, "/v1/capabilities") == 0) { + resp->ok = amduatd_handle_post_capabilities(resp->fd, + ctx->store, + ctx->store_cfg, + ctx->concepts, + ctx->daemon_cfg, + ctx->caps, + req); + return true; + } + if (strcmp(req->method, "GET") == 0 && + strcmp(no_query, "/v1/cap/resolve") == 0) { + resp->ok = amduatd_handle_get_cap_resolve(resp->fd, + ctx->store, + ctx->store_cfg, + ctx->concepts, + ctx->daemon_cfg, + ctx->caps, + ctx->root_path, + req); + return true; + } + + return false; +} diff --git a/src/amduatd_caps.h b/src/amduatd_caps.h new file mode 100644 index 0000000..ab0a542 --- /dev/null +++ b/src/amduatd_caps.h @@ -0,0 +1,48 @@ +#ifndef AMDUATD_CAPS_H +#define AMDUATD_CAPS_H + +#include "amduatd_ui.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct amduatd_cfg_t { + const char *space_id; + bool space_enabled; + bool migrate_unscoped_edges; +} amduatd_cfg_t; + +typedef enum { + AMDUATD_REF_OK = 0, + AMDUATD_REF_ERR_INVALID = 1, + AMDUATD_REF_ERR_NOT_FOUND = 2, + AMDUATD_REF_ERR_INTERNAL = 3 +} amduatd_ref_status_t; + +typedef struct amduatd_caps_t { + bool enabled; + bool enable_cap_reads; + amduat_octets_t hmac_key; +} amduatd_caps_t; + +bool amduatd_caps_init(amduatd_caps_t *caps, + const amduatd_cfg_t *cfg, + const char *root_path); + +void amduatd_caps_free(amduatd_caps_t *caps); + +bool amduatd_caps_can_handle(const amduatd_http_req_t *req); + +bool amduatd_caps_handle(amduatd_ctx_t *ctx, + const amduatd_http_req_t *req, + amduatd_http_resp_t *resp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUATD_CAPS_H */ diff --git a/src/amduatd_ui.h b/src/amduatd_ui.h index f3bbedd..2dd9120 100644 --- a/src/amduatd_ui.h +++ b/src/amduatd_ui.h @@ -1,6 +1,7 @@ #ifndef AMDUATD_UI_H #define AMDUATD_UI_H +#include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/store.h" #include @@ -11,6 +12,10 @@ #define AMDUATD_ENABLE_UI 1 #endif +typedef struct amduatd_caps_t amduatd_caps_t; +typedef struct amduatd_cfg_t amduatd_cfg_t; +typedef struct amduatd_concepts_t amduatd_concepts_t; + typedef struct { char method[8]; char path[1024]; @@ -33,6 +38,11 @@ typedef struct { typedef struct { amduat_asl_store_t *store; amduat_reference_t ui_ref; + const amduat_asl_store_fs_config_t *store_cfg; + amduatd_concepts_t *concepts; + const amduatd_cfg_t *daemon_cfg; + const char *root_path; + amduatd_caps_t *caps; } amduatd_ctx_t; #if AMDUATD_ENABLE_UI