This commit is contained in:
2026-02-27 22:17:33 +01:00
commit 8e95149d8e
27 changed files with 4480 additions and 0 deletions

288
main/tang_handlers.h Normal file
View File

@@ -0,0 +1,288 @@
#ifndef TANG_HANDLERS_H
#define TANG_HANDLERS_H
#include "crypto.h"
#include "cryptoauthlib.h"
#include "encoding.h"
#include "tang_storage.h"
#include <cJSON.h>
#include <esp_http_server.h>
#include <esp_log.h>
#include <mbedtls/sha256.h>
#include <string.h>
static const char *TAG_HANDLERS = "tang_handlers";
// Forward declarations
extern httpd_handle_t server_http;
extern TangKeyStore keystore;
extern bool unlocked;
// GET /adv - Advertisement endpoint (signed JWK set)
static esp_err_t handle_adv(httpd_req_t *req) {
if (!unlocked) {
httpd_resp_set_status(req, "503 Service Unavailable");
httpd_resp_sendstr(req, "Server not active");
return ESP_FAIL;
}
uint8_t sig_pub[ATCA_PUB_KEY_SIZE];
if (atcab_get_pubkey(1, sig_pub) != ATCA_SUCCESS) {
ESP_LOGE(TAG_HANDLERS, "Failed to read public keys from secure element");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Hardware error");
return ESP_FAIL;
}
char sig_x_b64[64] = {0}, sig_y_b64[64] = {0};
char rec_x_b64[64] = {0}, rec_y_b64[64] = {0};
b64url_encode_buf(&sig_pub[0], 32, sig_x_b64, sizeof(sig_x_b64));
b64url_encode_buf(&sig_pub[32], 32, sig_y_b64, sizeof(sig_y_b64));
b64url_encode_buf(&keystore.exc_pub[0], 32, rec_x_b64, sizeof(rec_x_b64));
b64url_encode_buf(&keystore.exc_pub[32], 32, rec_y_b64, sizeof(rec_y_b64));
// Build JWK set payload using cJSON
cJSON *payload_root = cJSON_CreateObject();
cJSON *keys = cJSON_CreateArray();
// Signing/verification key
cJSON *sig_key = cJSON_CreateObject();
cJSON_AddStringToObject(sig_key, "kty", "EC");
cJSON_AddStringToObject(sig_key, "alg", "ES256");
cJSON_AddStringToObject(sig_key, "crv", "P-256");
cJSON_AddStringToObject(sig_key, "x", sig_x_b64);
cJSON_AddStringToObject(sig_key, "y", sig_y_b64);
cJSON *sig_key_ops = cJSON_CreateArray();
cJSON_AddItemToArray(sig_key_ops, cJSON_CreateString("verify"));
cJSON_AddItemToObject(sig_key, "key_ops", sig_key_ops);
cJSON_AddItemToArray(keys, sig_key);
cJSON *rec_key = cJSON_CreateObject();
cJSON_AddStringToObject(rec_key, "alg", "ECMR");
cJSON_AddStringToObject(rec_key, "kty", "EC");
cJSON_AddStringToObject(rec_key, "crv", "P-256");
cJSON_AddStringToObject(rec_key, "x", rec_x_b64);
cJSON_AddStringToObject(rec_key, "y", rec_y_b64);
cJSON *rec_key_ops = cJSON_CreateArray();
cJSON_AddItemToArray(rec_key_ops, cJSON_CreateString("deriveKey"));
cJSON_AddItemToObject(rec_key, "key_ops", rec_key_ops);
cJSON_AddItemToArray(keys, rec_key);
cJSON_AddItemToObject(payload_root, "keys", keys);
// Print payload JSON and encode to B64 dynamically
char *payload_json = cJSON_PrintUnformatted(payload_root);
size_t payload_len = strlen(payload_json);
size_t payload_b64_size = ((payload_len + 2) / 3) * 4 + 1;
char *payload_b64 = (char *)malloc(payload_b64_size);
b64url_encode_buf((uint8_t *)payload_json, payload_len, payload_b64,
payload_b64_size);
free(payload_json);
cJSON_Delete(payload_root);
// Create protected header
cJSON *protected_root = cJSON_CreateObject();
cJSON_AddStringToObject(protected_root, "alg", "ES256");
cJSON_AddStringToObject(protected_root, "cty", "jwk-set+json");
char *protected_json = cJSON_PrintUnformatted(protected_root);
size_t protected_len = strlen(protected_json);
size_t protected_b64_size = ((protected_len + 2) / 3) * 4 + 1;
char *protected_b64 = (char *)malloc(protected_b64_size);
b64url_encode_buf((uint8_t *)protected_json, protected_len, protected_b64,
protected_b64_size);
free(protected_json);
cJSON_Delete(protected_root);
// Sign the payload
size_t signing_input_size =
strlen(protected_b64) + 1 + strlen(payload_b64) + 1;
char *signing_input = (char *)malloc(signing_input_size);
snprintf(signing_input, signing_input_size, "%s.%s", protected_b64,
payload_b64);
uint8_t hash[32];
mbedtls_sha256((const uint8_t *)signing_input, strlen(signing_input), hash,
0);
free(signing_input);
uint8_t signature[ATCA_ECCP256_SIG_SIZE];
if (atcab_sign(1, hash, signature) != ATCA_SUCCESS) {
ESP_LOGE(TAG_HANDLERS, "Hardware signing failed");
free(payload_b64);
free(protected_b64);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Signing failed");
return ESP_FAIL;
}
char sig_b64[128] = {0};
b64url_encode_buf(signature, sizeof(signature), sig_b64, sizeof(sig_b64));
cJSON *jws_root = cJSON_CreateObject();
cJSON_AddStringToObject(jws_root, "payload", payload_b64);
cJSON_AddStringToObject(jws_root, "protected", protected_b64);
cJSON_AddStringToObject(jws_root, "signature", sig_b64);
char *response = cJSON_PrintUnformatted(jws_root);
cJSON_Delete(jws_root);
free(payload_b64);
free(protected_b64);
httpd_resp_set_type(
req, "application/jose+json"); // Tang expects this specific type
httpd_resp_sendstr(req, response);
free(response);
ESP_LOGI(TAG_HANDLERS, "Served /adv");
return ESP_OK;
}
// POST /rec or /rec/{kid} - Recovery endpoint
static esp_err_t handle_rec(httpd_req_t *req) {
if (!unlocked) {
httpd_resp_set_status(req, "503 Service Unavailable");
httpd_resp_sendstr(req, "Server not active");
return ESP_FAIL;
}
// Read request body
char *buf = (char *)malloc(req->content_len + 1);
if (!buf) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Memory allocation failed");
return ESP_FAIL;
}
int ret = httpd_req_recv(req, buf, req->content_len);
if (ret <= 0) {
free(buf);
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
buf[ret] = '\0';
// Parse JSON
cJSON *req_doc = cJSON_Parse(buf);
free(buf);
if (!req_doc) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// Extract client's ephemeral public key
cJSON *x_item = cJSON_GetObjectItem(req_doc, "x");
cJSON *y_item = cJSON_GetObjectItem(req_doc, "y");
if (!x_item || !y_item || !cJSON_IsString(x_item) ||
!cJSON_IsString(y_item)) {
cJSON_Delete(req_doc);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Missing x or y coordinates");
return ESP_FAIL;
}
uint8_t client_pub_key[P256_PUBLIC_KEY_SIZE];
// Use the new helper to decode and strictly validate the length of both
// coordinates
if (!b64url_decode_buf(x_item->valuestring, &client_pub_key[0],
P256_COORDINATE_SIZE) ||
!b64url_decode_buf(y_item->valuestring,
&client_pub_key[P256_COORDINATE_SIZE],
P256_COORDINATE_SIZE)) {
cJSON_Delete(req_doc);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid key coordinates");
return ESP_FAIL;
}
cJSON_Delete(req_doc);
// Perform ECDH to get shared point
uint8_t shared_point[P256_PUBLIC_KEY_SIZE];
if (!P256::ecdh_compute_shared_point(client_pub_key, keystore.exc_priv,
shared_point, true)) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"ECDH computation failed");
return ESP_FAIL;
}
char shared_x_b64[64] = {0};
char shared_y_b64[64] = {0};
b64url_encode_buf(&shared_point[0], P256_COORDINATE_SIZE, shared_x_b64,
sizeof(shared_x_b64));
b64url_encode_buf(&shared_point[P256_COORDINATE_SIZE], P256_COORDINATE_SIZE,
shared_y_b64, sizeof(shared_y_b64));
// Return shared point as JWK
cJSON *resp_root = cJSON_CreateObject();
cJSON_AddStringToObject(resp_root, "alg", "ECMR");
cJSON_AddStringToObject(resp_root, "kty", "EC");
cJSON_AddStringToObject(resp_root, "crv", "P-256");
cJSON_AddStringToObject(resp_root, "x", shared_x_b64);
cJSON_AddStringToObject(resp_root, "y", shared_y_b64);
cJSON *key_ops = cJSON_CreateArray();
cJSON_AddItemToArray(key_ops, cJSON_CreateString("deriveKey"));
cJSON_AddItemToObject(resp_root, "key_ops", key_ops);
char *response = cJSON_PrintUnformatted(resp_root);
cJSON_Delete(resp_root);
httpd_resp_set_type(req, "application/jose+json");
httpd_resp_sendstr(req, response);
free(response);
ESP_LOGI(TAG_HANDLERS, "Served %s", req->uri);
return ESP_OK;
}
// --- Administration Handlers ---
// GET /config - Get ATECC608B configuration
static esp_err_t handle_config(httpd_req_t *req) {
ESP_LOGI(TAG_HANDLERS, "Serving ATECC608B config");
char *json_str = atecc608B_get_config_json();
if (!json_str) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Config data not available");
return ESP_FAIL;
}
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, json_str);
free(json_str);
ESP_LOGI(TAG_HANDLERS, "Served /config");
return ESP_OK;
}
// GET /reboot - Reboot device
static esp_err_t handle_reboot(httpd_req_t *req) {
httpd_resp_sendstr(req, "Rebooting...");
vTaskDelay(pdMS_TO_TICKS(1000));
esp_restart();
return ESP_OK;
}
// 404 handler - Handle /rec/{kid} by routing to handle_rec
static esp_err_t handle_not_found(httpd_req_t *req, httpd_err_code_t err) {
// Check if it's a POST to /rec/{kid}
if (req->method == HTTP_POST && strncmp(req->uri, "/rec/", 5) == 0) {
return handle_rec(req);
}
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Not found");
return ESP_FAIL;
}
#endif // TANG_HANDLERS_H