#ifndef TANG_HANDLERS_H #define TANG_HANDLERS_H #include "crypto.h" #include "cryptoauthlib.h" #include "encoding.h" #include "tang_storage.h" #include #include #include #include #include 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