From 29da16a26d933cc27820e1923979c75bbfcb3975 Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Wed, 10 Jun 2026 22:25:15 +0000 Subject: [PATCH 1/7] attester: add RA_PERF performance measurement logging Add timing instrumentation across the attestation stack so that the total attestation time and the duration of each major event (memory hashing, CBOR/COSE encoding, ECDSA/CAAM signing, black-key processing) can be collected for performance evaluation. Every measurement is one machine-parsable line: RA_PERF||||key= PTA (microsecond resolution, ARM generic timer): ocotp_srk, ocotp_lifecycle, instance_id, hash_ta, hash_tee, cbor_encode, sign_key_setup, sign_tbs_hash, sign_ecdsa, sign_verify, cose_sign1, cmd_total, plus keypair_generate / blackkey_encap / convert_total for the black-key provisioning commands. Events are buffered and flushed in one batch after all timestamps are taken, because the secure console is synchronous and printing between measured operations would skew them; the flush reports its own cost as perf_flush. sign_ecdsa is the bracket for the CAAM black-key on/off comparison (sign_verify only runs on the embedded-key path and is reported separately so it cannot bias it). TA (millisecond resolution, TEE_GetSystemTime): pta_invoke, cmd_total. Host (clock_gettime CLOCK_MONOTONIC): teec_init, teec_open_session, veraison_new_session, evidence_get, print_evidence, veraison_post_evidence, total, blackkey_generate, blackkey_convert. New client options: --perf (or RA_PERF=1) enables the host log lines, --loop N repeats the attestation for statistics, --no-server attests a local random nonce without a relying party (useful on devices without network access to a verifier). The firmware side is compiled in only with CFG_REMOTE_ATTESTATION_PERF=y (QEMU: pass it to make check; Yocto: set the variable for optee-os and veraison-attestation in local.conf) and default builds are unaffected. The i.MX bbappend raises CFG_TEE_CORE_LOG_LEVEL to 2 together with the flag because meta-freescale's optee-os-common-fslc-imx.inc forces it to 0, which would compile out all core trace including the RA_PERF lines. --- .../optee/optee-os_%.imx.bbappend | 12 + .../veraison-attestation_1.0.bb | 7 +- .../remote_attestation/perf.c | 66 ++++++ .../remote_attestation/perf.h | 74 ++++++ .../remote_attestation/remote_attestation.c | 96 ++++++-- .../remote_attestation/sign.c | 21 ++ .../remote_attestation/sub.mk | 1 + attester/remote_attestation/host/client.c | 5 + attester/remote_attestation/host/main.c | 212 +++++++++++++----- attester/remote_attestation/host/perf.h | 21 ++ .../ta/remote_attestation_ta.c | 51 +++++ attester/remote_attestation/ta/sub.mk | 3 + 12 files changed, 492 insertions(+), 77 deletions(-) create mode 100644 attester/pta_remote_attestation/remote_attestation/perf.c create mode 100644 attester/pta_remote_attestation/remote_attestation/perf.h create mode 100644 attester/remote_attestation/host/perf.h diff --git a/attester/container-imx/meta-veraison-attestation/recipes-security/optee/optee-os_%.imx.bbappend b/attester/container-imx/meta-veraison-attestation/recipes-security/optee/optee-os_%.imx.bbappend index 29669de5..c423be9b 100644 --- a/attester/container-imx/meta-veraison-attestation/recipes-security/optee/optee-os_%.imx.bbappend +++ b/attester/container-imx/meta-veraison-attestation/recipes-security/optee/optee-os_%.imx.bbappend @@ -8,6 +8,16 @@ PTA_EXTERNAL_SRC ?= "/attester/pta_remote_attestation" # Enable the custom remote_attestation PTA EXTRA_OEMAKE:append = " CFG_REMOTE_ATTESTATION_PTA=y" +# Performance measurement logging. Enable from local.conf with: +# CFG_REMOTE_ATTESTATION_PERF:pn-optee-os = "y" +# CFG_REMOTE_ATTESTATION_PERF:pn-veraison-attestation = "y" +# CFG_TEE_CORE_LOG_LEVEL must be raised together with the flag because +# meta-freescale's optee-os-common-fslc-imx.inc forces it to 0 (all core +# trace compiled out, RA_PERF lines included); this append is parsed after +# the .inc, so the later assignment wins on the make command line. +CFG_REMOTE_ATTESTATION_PERF ?= "n" +EXTRA_OEMAKE:append = "${@' CFG_REMOTE_ATTESTATION_PERF=y CFG_TEE_CORE_LOG_LEVEL=2' if d.getVar('CFG_REMOTE_ATTESTATION_PERF') == 'y' else ''}" + # Copy PTA source to OP-TEE OS source tree before compile do_configure:append() { # Create PTA directory in OP-TEE OS source @@ -32,6 +42,8 @@ do_configure:append() { cp ${PTA_SRC}/hash.h ${S}/core/pta/remote_attestation/ cp ${PTA_SRC}/sign.c ${S}/core/pta/remote_attestation/ cp ${PTA_SRC}/sign.h ${S}/core/pta/remote_attestation/ + cp ${PTA_SRC}/perf.c ${S}/core/pta/remote_attestation/ + cp ${PTA_SRC}/perf.h ${S}/core/pta/remote_attestation/ cp ${PTA_SRC}/base64.c ${S}/core/pta/remote_attestation/ cp ${PTA_SRC}/base64.h ${S}/core/pta/remote_attestation/ cp ${PTA_SRC}/ocotp.c ${S}/core/pta/remote_attestation/ diff --git a/attester/container-imx/meta-veraison-attestation/recipes-security/veraison-attestation/veraison-attestation_1.0.bb b/attester/container-imx/meta-veraison-attestation/recipes-security/veraison-attestation/veraison-attestation_1.0.bb index dcbe7e33..a6bb25c3 100644 --- a/attester/container-imx/meta-veraison-attestation/recipes-security/veraison-attestation/veraison-attestation_1.0.bb +++ b/attester/container-imx/meta-veraison-attestation/recipes-security/veraison-attestation/veraison-attestation_1.0.bb @@ -22,6 +22,10 @@ S = "${WORKDIR}/src" TA_DEV_KIT_DIR = "${STAGING_INCDIR}/optee/export-user_ta" TEEC_EXPORT = "${STAGING_DIR_HOST}${prefix}" +# Performance measurement logging in the TA (set "y" in local.conf, together +# with the optee-os flag; see optee-os_%.imx.bbappend) +CFG_REMOTE_ATTESTATION_PERF ?= "n" + # Rust configuration - rust-apiclient FFI library CARGO_SRC_DIR = "${S}/host/rust-ffi/coserv-rs" CARGO_MANIFEST_PATH = "${S}/host/rust-ffi/coserv-rs/Cargo.toml" @@ -95,7 +99,8 @@ do_compile() { oe_runmake V=1 \ TA_DEV_KIT_DIR=${TA_DEV_KIT_DIR} \ CROSS_COMPILE=${HOST_PREFIX} \ - LIBGCC_LOCATE_CFLAGS="--sysroot=${STAGING_DIR_HOST}" + LIBGCC_LOCATE_CFLAGS="--sysroot=${STAGING_DIR_HOST}" \ + CFG_REMOTE_ATTESTATION_PERF=${CFG_REMOTE_ATTESTATION_PERF} # Build host application cd ${S}/host diff --git a/attester/pta_remote_attestation/remote_attestation/perf.c b/attester/pta_remote_attestation/remote_attestation/perf.c new file mode 100644 index 00000000..5c87fac2 --- /dev/null +++ b/attester/pta_remote_attestation/remote_attestation/perf.c @@ -0,0 +1,66 @@ +#include "perf.h" + +#include +#include + +#define RA_PERF_MAX_EVENTS 16 + +struct ra_perf_event { + const char *event; + const char *keymode; + uint64_t ticks; +}; + +/* + * Simple per-command event buffer. Measurement runs are expected to be + * sequential (one attestation client at a time); concurrent PTA invocations + * would interleave their events. + */ +static struct ra_perf_event ra_perf_events[RA_PERF_MAX_EVENTS]; +static size_t ra_perf_num_events; + +void ra_perf_reset(void) +{ + ra_perf_num_events = 0; +} + +void ra_perf_record(const char *event, uint64_t start_ticks, + uint64_t end_ticks, const char *keymode) +{ + struct ra_perf_event *e = NULL; + + if (ra_perf_num_events >= RA_PERF_MAX_EVENTS) + return; + + e = &ra_perf_events[ra_perf_num_events++]; + e->event = event; + e->keymode = keymode; + e->ticks = end_ticks - start_ticks; +} + +void ra_perf_flush(void) +{ + uint32_t freq = read_cntfrq(); + uint64_t flush_start = ra_perf_now(); + size_t i = 0; + + /* Report the counter frequency once so tick math can be verified */ + IMSG("RA_PERF|pta|cntfrq|%" PRIu32 "|key=-", freq); + + for (i = 0; i < ra_perf_num_events; i++) { + struct ra_perf_event *e = &ra_perf_events[i]; + uint64_t us = e->ticks * 1000000ULL / freq; + + IMSG("RA_PERF|pta|%s|%" PRIu64 "|key=%s", e->event, us, e->keymode); + } + ra_perf_num_events = 0; + + /* + * Console writes are synchronous, so this flush is visible in the + * brackets measured by the layers above (TA pta_invoke, host + * evidence_get). Report its own cost so it can be subtracted; only + * the final line below remains unaccounted for. + */ + IMSG("RA_PERF|pta|perf_flush|%" PRIu64 "|key=-", + (ra_perf_now() - flush_start) * 1000000ULL / freq); +} diff --git a/attester/pta_remote_attestation/remote_attestation/perf.h b/attester/pta_remote_attestation/remote_attestation/perf.h new file mode 100644 index 00000000..228f7a79 --- /dev/null +++ b/attester/pta_remote_attestation/remote_attestation/perf.h @@ -0,0 +1,74 @@ +/* + * Performance measurement logging for the remote attestation PTA. + * + * Enabled with CFG_REMOTE_ATTESTATION_PERF=y. Durations are taken from the + * ARM generic timer (CNTPCT via barrier_read_counter_timer()) and reported + * in microseconds as: + * + * RA_PERF|pta|||key= + * + * Events are buffered with ra_perf_record() and emitted in one batch by + * ra_perf_flush(). Buffering matters: the secure console UART is synchronous + * and slow, so printing between two measured operations would inflate the + * later measurements. + */ +#ifndef PTA_REMOTE_ATTESTATION_PERF_H +#define PTA_REMOTE_ATTESTATION_PERF_H + +#ifdef CFG_REMOTE_ATTESTATION_PERF + +#include +#include +#include + +static inline uint64_t ra_perf_now(void) +{ + return barrier_read_counter_timer(); +} + +/* + * Key-path label used to split measurement runs in the logs: + * embedded = no external key, embedded test key + * plain = external plain private key (32 bytes) + * black = external serialized CAAM black key (> 32 bytes) + * Note: on i.MX with CFG_NXP_CAAM_ECC_DRV all three paths sign on CAAM + * hardware; "black" additionally pays blob decapsulation + CCM key import. + */ +static inline const char *ra_perf_keymode(const uint8_t *key, size_t key_len) +{ + if (!key || !key_len) + return "embedded"; + return key_len > 32 ? "black" : "plain"; +} + +void ra_perf_reset(void); +void ra_perf_record(const char *event, uint64_t start_ticks, + uint64_t end_ticks, const char *keymode); +void ra_perf_flush(void); + +#define RA_PERF_DECL(v) uint64_t v = 0 +#define RA_PERF_RESET() ra_perf_reset() +#define RA_PERF_START(v) ((v) = ra_perf_now()) +#define RA_PERF_STOP(v, event, keymode) \ + ra_perf_record((event), (v), ra_perf_now(), (keymode)) +#define RA_PERF_FLUSH() ra_perf_flush() + +#else /* CFG_REMOTE_ATTESTATION_PERF */ + +#define RA_PERF_DECL(v) +#define RA_PERF_RESET() \ + do { \ + } while (0) +#define RA_PERF_START(v) \ + do { \ + } while (0) +#define RA_PERF_STOP(v, event, keymode) \ + do { \ + } while (0) +#define RA_PERF_FLUSH() \ + do { \ + } while (0) + +#endif /* CFG_REMOTE_ATTESTATION_PERF */ + +#endif /* PTA_REMOTE_ATTESTATION_PERF_H */ diff --git a/attester/pta_remote_attestation/remote_attestation/remote_attestation.c b/attester/pta_remote_attestation/remote_attestation/remote_attestation.c index d7611ed9..78d8831e 100644 --- a/attester/pta_remote_attestation/remote_attestation/remote_attestation.c +++ b/attester/pta_remote_attestation/remote_attestation/remote_attestation.c @@ -5,11 +5,16 @@ #include "base64.h" #include "cbor.h" #include "hash.h" +#include "perf.h" #include "sign.h" #include #include #include +/* Perf-log key-path label, valid inside cmd_get_cbor_evidence() */ +#define RA_PERF_KM \ + ra_perf_keymode(serialized_black_key, serialized_black_key_len) + #ifdef CFG_NXP_CAAM #include #include @@ -99,6 +104,12 @@ static int32_t derive_client_id(const uint8_t *ta_uuid, size_t uuid_len) { static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, TEE_Param params[TEE_NUM_PARAMS]) { + RA_PERF_DECL(t_cmd); + RA_PERF_DECL(t); + + RA_PERF_RESET(); + RA_PERF_START(t_cmd); + const uint8_t *nonce = params[0].memref.buffer; const size_t nonce_sz = params[0].memref.size; uint8_t *output_buffer = params[1].memref.buffer; @@ -152,27 +163,6 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, if (!output_buffer || !(*output_buffer_len)) return TEE_ERROR_BAD_PARAMETERS; - /* Populate signer-id and lifecycle from platform fuses or fallback */ -#ifdef CFG_NXP_CAAM - status = ocotp_read_srk_hash(signer_id); - if (status != TEE_SUCCESS) - return status; - - { - uint32_t lifecycle_val = 0; - status = ocotp_get_lifecycle(&lifecycle_val); - if (status != TEE_SUCCESS) - return status; - psa_security_lifecycle = (int)lifecycle_val; - } -#else - { - const uint8_t fallback_signer[SIGNER_ID_LEN] = {SIGNER_ID_TEST_VALUE}; - memcpy(signer_id, fallback_signer, SIGNER_ID_LEN); - psa_security_lifecycle = LIFECYCLE_DEFAULT; - } -#endif - /* * param[3] wire format (optional): * PubX(32 bytes) || PubY(32 bytes) || key_blob(N bytes) @@ -182,6 +172,9 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, * instance_id = 0x01 || SHA-256(0x04 || PubX || PubY) * * When absent, the embedded test key's public coordinates are used. + * + * Parsed before any measured operation so the perf logs can label + * every event with the key path (RA_PERF_KM). */ if (TEE_PARAM_TYPE_GET(param_types, 3) == TEE_PARAM_TYPE_MEMREF_INPUT) { const uint8_t *p3 = params[3].memref.buffer; @@ -201,15 +194,45 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, return status; } + /* Populate signer-id and lifecycle from platform fuses or fallback */ +#ifdef CFG_NXP_CAAM + RA_PERF_START(t); + status = ocotp_read_srk_hash(signer_id); + if (status != TEE_SUCCESS) + return status; + RA_PERF_STOP(t, "ocotp_srk", RA_PERF_KM); + + { + uint32_t lifecycle_val = 0; + + RA_PERF_START(t); + status = ocotp_get_lifecycle(&lifecycle_val); + if (status != TEE_SUCCESS) + return status; + RA_PERF_STOP(t, "ocotp_lifecycle", RA_PERF_KM); + psa_security_lifecycle = (int)lifecycle_val; + } +#else + { + const uint8_t fallback_signer[SIGNER_ID_LEN] = {SIGNER_ID_TEST_VALUE}; + memcpy(signer_id, fallback_signer, SIGNER_ID_LEN); + psa_security_lifecycle = LIFECYCLE_DEFAULT; + } +#endif + /* Compute PSA instance-id from public key */ + RA_PERF_START(t); status = compute_instance_id(pub_x, pub_y, psa_instance_id); if (status != TEE_SUCCESS) return status; + RA_PERF_STOP(t, "instance_id", RA_PERF_KM); /* Calculate measurement hash of memory */ + RA_PERF_START(t); status = get_hash_ta_memory(measurement_value, TEE_SHA256_HASH_SIZE); if (status != TEE_SUCCESS) return status; + RA_PERF_STOP(t, "hash_ta", RA_PERF_KM); /* For debug print */ if (base64_encode(measurement_value, TEE_SHA256_HASH_SIZE, @@ -221,9 +244,11 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, DMSG("b64_measurement_value: %s", b64_measurement_value); /* Measure the OP-TEE OS (core .text + .rodata) for the PRoT component */ + RA_PERF_START(t); status = get_hash_tee_memory(tee_measurement, TEE_SHA256_HASH_SIZE); if (status != TEE_SUCCESS) return status; + RA_PERF_STOP(t, "hash_tee", RA_PERF_KM); /* * OP-TEE OS version: first whitespace-delimited token of core_v_str, @@ -291,6 +316,7 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, }; /* Encode evidence to CBOR */ + RA_PERF_START(t); UsefulBufC ubc_cbor_evidence = encode_evidence_to_cbor( eat_profile, psa_client_id, psa_security_lifecycle, psa_implementation_id, psa_implementation_id_len, components, @@ -302,8 +328,10 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, free(heap_cose); return TEE_ERROR_GENERIC; } + RA_PERF_STOP(t, "cbor_encode", RA_PERF_KM); /* Sign the CBOR and generate a COSE evidence */ + RA_PERF_START(t); UsefulBufC cose_evidence = generate_cose(ubc_cbor_evidence, buffer_for_cose, serialized_black_key, serialized_black_key_len); @@ -313,6 +341,7 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, free(heap_cose); return TEE_ERROR_GENERIC; } + RA_PERF_STOP(t, "cose_sign1", RA_PERF_KM); /* Copy COSE evidence for return buffer */ memcpy(output_buffer, cose_evidence.ptr, cose_evidence.len); @@ -320,6 +349,14 @@ static TEE_Result cmd_get_cbor_evidence(uint32_t param_types, free(heap_cbor); free(heap_cose); + + /* + * All timestamps are taken before any RA_PERF line is printed: the + * (slow, synchronous) console writes happen only in the flush below. + */ + RA_PERF_STOP(t_cmd, "cmd_total", RA_PERF_KM); + RA_PERF_FLUSH(); + return TEE_SUCCESS; } @@ -341,18 +378,24 @@ static TEE_Result cmd_generate_keypair(uint32_t param_types, size_t d_len, x_len, y_len; const size_t sec_size = 32; /* P-256 */ + RA_PERF_DECL(t); + if (param_types != exp_pt) return TEE_ERROR_BAD_PARAMETERS; + RA_PERF_RESET(); + res = crypto_acipher_alloc_ecc_keypair(&key, TEE_TYPE_ECDSA_KEYPAIR, sec_size * 8); if (res != TEE_SUCCESS) return res; key.curve = TEE_ECC_CURVE_NIST_P256; + RA_PERF_START(t); res = crypto_acipher_gen_ecc_key(&key, sec_size * 8); if (res != TEE_SUCCESS) goto out_free; + RA_PERF_STOP(t, "keypair_generate", "-"); d_len = crypto_bignum_num_bytes(key.d); x_len = crypto_bignum_num_bytes(key.x); @@ -389,6 +432,7 @@ static TEE_Result cmd_generate_keypair(uint32_t param_types, crypto_bignum_free(&key.d); crypto_bignum_free(&key.x); crypto_bignum_free(&key.y); + RA_PERF_FLUSH(); return res; } @@ -408,12 +452,18 @@ static TEE_Result cmd_convert_to_blackkey(uint32_t param_types, size_t need_size = 0; const size_t sec_size = 32; /* P-256 */ + RA_PERF_DECL(t_cmd); + RA_PERF_DECL(t); + if (param_types != exp_pt) return TEE_ERROR_BAD_PARAMETERS; if (!plain_d || plain_d_size != sec_size) return TEE_ERROR_BAD_PARAMETERS; + RA_PERF_RESET(); + RA_PERF_START(t_cmd); + caam_key.key_type = CAAM_KEY_PLAIN_TEXT; caam_key.sec_size = plain_d_size; caam_key.is_blob = false; @@ -424,11 +474,13 @@ static TEE_Result cmd_convert_to_blackkey(uint32_t param_types, memcpy(caam_key.buf.data, plain_d, plain_d_size); + RA_PERF_START(t); caam_res = caam_key_black_encapsulation(&caam_key, CAAM_KEY_BLACK_CCM); if (caam_res != CAAM_NO_ERROR) { res = caam_to_tee_status(caam_res); goto out; } + RA_PERF_STOP(t, "blackkey_encap", "-"); caam_res = caam_key_serialized_size(&caam_key, &need_size); if (caam_res != CAAM_NO_ERROR) { @@ -454,6 +506,8 @@ static TEE_Result cmd_convert_to_blackkey(uint32_t param_types, out: caam_key_free(&caam_key); + RA_PERF_STOP(t_cmd, "convert_total", "-"); + RA_PERF_FLUSH(); return res; } #endif /* CFG_NXP_CAAM */ diff --git a/attester/pta_remote_attestation/remote_attestation/sign.c b/attester/pta_remote_attestation/remote_attestation/sign.c index 98c24167..e7063b80 100644 --- a/attester/pta_remote_attestation/remote_attestation/sign.c +++ b/attester/pta_remote_attestation/remote_attestation/sign.c @@ -1,3 +1,4 @@ +#include "perf.h" #include "sign.h" #include #include @@ -59,6 +60,10 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, const uint8_t public_key_x[] = {PUBLIC_KEY_X}; const uint8_t public_key_y[] = {PUBLIC_KEY_Y}; + RA_PERF_DECL(t); + + RA_PERF_START(t); + /* Allocate the key pair */ key = calloc(1, sizeof(*key)); if (key == NULL) { @@ -112,19 +117,32 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, } } + RA_PERF_STOP(t, "sign_key_setup", + ra_perf_keymode(serialized_black_key, + serialized_black_key_len)); + /* Hash the msg */ + RA_PERF_START(t); res = hash_sha256(msg, msg_len, hash_msg); if (res != TEE_SUCCESS) goto free_pubkey; + RA_PERF_STOP(t, "sign_tbs_hash", + ra_perf_keymode(serialized_black_key, + serialized_black_key_len)); /* Sign the hashed msg by the key pair */ + RA_PERF_START(t); res = crypto_acipher_ecc_sign(TEE_ALG_ECDSA_SHA256, key, hash_msg, TEE_SHA256_HASH_SIZE, sig, sig_len); if (res != TEE_SUCCESS) goto free_pubkey; + RA_PERF_STOP(t, "sign_ecdsa", + ra_perf_keymode(serialized_black_key, + serialized_black_key_len)); /* Verify the signature if we have the public key */ if (pubkey) { + RA_PERF_START(t); res = crypto_acipher_ecc_verify(TEE_ALG_ECDSA_SHA256, pubkey, hash_msg, TEE_SHA256_HASH_SIZE, sig, *sig_len); if (res == TEE_SUCCESS) { @@ -134,6 +152,9 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, } /* Reset res to success even if verify fails - we still signed */ res = TEE_SUCCESS; + RA_PERF_STOP(t, "sign_verify", + ra_perf_keymode(serialized_black_key, + serialized_black_key_len)); } free_pubkey: diff --git a/attester/pta_remote_attestation/remote_attestation/sub.mk b/attester/pta_remote_attestation/remote_attestation/sub.mk index 21093d78..345876c1 100644 --- a/attester/pta_remote_attestation/remote_attestation/sub.mk +++ b/attester/pta_remote_attestation/remote_attestation/sub.mk @@ -3,6 +3,7 @@ srcs-$(CFG_REMOTE_ATTESTATION_PTA) += base64.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += cbor.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += hash.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += sign.c +srcs-$(CFG_REMOTE_ATTESTATION_PERF) += perf.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += qcbor/qcbor_encode.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += qcbor/ieee754.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += qcbor/UsefulBuf.c diff --git a/attester/remote_attestation/host/client.c b/attester/remote_attestation/host/client.c index 57915510..941e4f38 100644 --- a/attester/remote_attestation/host/client.c +++ b/attester/remote_attestation/host/client.c @@ -3,6 +3,7 @@ #include #include "client.h" +#include "perf.h" int find_psa_media_type_index(const ChallengeResponseSession *session) { for (int i = 0; i < session->accept_type_count; i++) { @@ -22,6 +23,7 @@ ChallengeResponseSession *open_session() { "%s/challenge-response/v1/newSession", base_url); /* Now run the challenge response session, using the discovered endpoint */ + uint64_t t0 = ra_perf_now_us(); VeraisonResult status = open_challenge_response_session( new_session_endpoint, 32, /* Nonce size */ NULL, &session); @@ -30,6 +32,7 @@ ChallengeResponseSession *open_session() { printf("Failed to allocate Veraison client session.\n"); goto cleanup; } + ra_perf_log("veraison_new_session", ra_perf_now_us() - t0); printf("\nOpened new Veraison client session at %s\n", session->session_url); @@ -73,6 +76,7 @@ VeraisonResult post_evidence(ChallengeResponseSession *session, printf("Supplying the generated evidence to the server.\n"); /* Supply our evidence. */ + uint64_t t0 = ra_perf_now_us(); VeraisonResult status = challenge_response( session, evidence_len, evidence, session->accept_type_list[find_psa_media_type_index(session)]); @@ -81,6 +85,7 @@ VeraisonResult post_evidence(ChallengeResponseSession *session, printf("Failed to supply evidence to server.\n"); goto cleanup; } + ra_perf_log("veraison_post_evidence", ra_perf_now_us() - t0); printf("\nReceived the attestation result from the server.\n"); // And, finally, display the server's response, which will be a JWT diff --git a/attester/remote_attestation/host/main.c b/attester/remote_attestation/host/main.c index edc648bc..3961822a 100644 --- a/attester/remote_attestation/host/main.c +++ b/attester/remote_attestation/host/main.c @@ -1,8 +1,10 @@ #include +#include #include #include #include #include +#include /* OP-TEE TEE client API (built by optee_client) */ #include @@ -11,6 +13,35 @@ #include #include "client.h" +#include "perf.h" + +/* Performance measurement logging (see perf.h) */ +int ra_perf_enabled = 0; +const char *ra_perf_keymode = "-"; + +uint64_t ra_perf_now_us(void) { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000; +} + +void ra_perf_log(const char *event, uint64_t duration_us) { + if (ra_perf_enabled) + printf("RA_PERF|host|%s|%" PRIu64 "|key=%s\n", event, duration_us, + ra_perf_keymode); +} + +static int get_random_nonce(uint8_t *nonce, size_t sz) { + FILE *f = fopen("/dev/urandom", "rb"); + size_t n = 0; + + if (!f) + return -1; + n = fread(nonce, 1, sz, f); + fclose(f); + return n == sz ? 0 : -1; +} void print_binary_in_hex(uint8_t *buf, size_t sz) { int i = 0; @@ -59,6 +90,9 @@ static void print_usage(const char *prog) { printf(" --key-hex HEX Use key blob in hex for signing\n"); printf(" --pubx-hex HEX Public key X coordinate (32 bytes hex)\n"); printf(" --puby-hex HEX Public key Y coordinate (32 bytes hex)\n"); + printf(" --perf Print RA_PERF timing logs (or RA_PERF=1)\n"); + printf(" --loop N Repeat the attestation N times\n"); + printf(" --no-server Skip the verifier; use a local random nonce\n"); printf("\n"); printf("When using --key-hex with a CAAM black key, also supply --pubx-hex\n"); printf("and --puby-hex so the PTA can compute the correct PSA instance-id.\n"); @@ -85,13 +119,26 @@ int main(int argc, char *argv[]) { size_t host_pub_y_len = 0; bool mode_generate = false; bool mode_convert = false; + bool no_server = false; + long loop_count = 1; + + { + const char *env_perf = getenv("RA_PERF"); + + if (env_perf && strcmp(env_perf, "0") != 0) + ra_perf_enabled = 1; + } if (argc > 1 && strcmp(argv[1], "--generate-blackkey") == 0) { - if (argc != 2) - errx(1, "--generate-blackkey takes no arguments"); + if (argc == 3 && strcmp(argv[2], "--perf") == 0) + ra_perf_enabled = 1; + else if (argc != 2) + errx(1, "--generate-blackkey takes no arguments (except --perf)"); mode_generate = true; } else if (argc > 1 && strcmp(argv[1], "--convert-key") == 0) { - if (argc != 3) + if (argc == 4 && strcmp(argv[3], "--perf") == 0) + ra_perf_enabled = 1; + else if (argc != 3) errx(1, "--convert-key requires a hex key argument"); mode_convert = true; } @@ -132,6 +179,17 @@ int main(int argc, char *argv[]) { errx(1, "--puby-hex must be exactly 32 bytes"); has_puby = true; i++; + } else if (strcmp(argv[i], "--perf") == 0) { + ra_perf_enabled = 1; + } else if (strcmp(argv[i], "--loop") == 0) { + if (i + 1 >= argc) + errx(1, "Missing value for --loop"); + loop_count = strtol(argv[i + 1], NULL, 10); + if (loop_count < 1) + errx(1, "Invalid --loop value"); + i++; + } else if (strcmp(argv[i], "--no-server") == 0) { + no_server = true; } else { errx(1, "Unknown argument: %s", argv[i]); } @@ -143,16 +201,22 @@ int main(int argc, char *argv[]) { errx(1, "--pubx-hex and --puby-hex must be specified together"); } + uint64_t t0 = 0; + /* Initialize a context connecting us to the TEE */ + t0 = ra_perf_now_us(); res = TEEC_InitializeContext(NULL, &ctx); if (res != TEEC_SUCCESS) errx(1, "TEEC_InitializeContext failed with code 0x%x", res); + ra_perf_log("teec_init", ra_perf_now_us() - t0); + t0 = ra_perf_now_us(); res = TEEC_OpenSession(&ctx, &sess, &ta_uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin); if (res != TEEC_SUCCESS) errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x", res, err_origin); + ra_perf_log("teec_open_session", ra_perf_now_us() - t0); /* Convert plain key to black key */ if (mode_convert) { @@ -178,12 +242,14 @@ int main(int argc, char *argv[]) { op_c.params[1].tmpref.size = sizeof(black_key); printf("\nConverting plain key to black key...\n"); + t0 = ra_perf_now_us(); res = TEEC_InvokeCommand(&sess, TA_REMOTE_ATTESTATION_CMD_CONVERT_TO_BLACKKEY, &op_c, &err_origin); if (res != TEEC_SUCCESS) { free(plain_key); errx(1, "Convert key failed 0x%x origin 0x%x", res, err_origin); } + ra_perf_log("blackkey_convert", ra_perf_now_us() - t0); printf("BlackKey(hex): "); print_binary_in_hex(black_key, op_c.params[1].tmpref.size); @@ -209,6 +275,7 @@ int main(int argc, char *argv[]) { op_p.params[2].tmpref.buffer = pub_y; op_p.params[2].tmpref.size = sizeof(pub_y); printf("\nGenerating new black key...\n"); + t0 = ra_perf_now_us(); res = TEEC_InvokeCommand(&sess, TA_REMOTE_ATTESTATION_CMD_GENERATE_BLACKKEY, &op_p, &err_origin); if (res != TEEC_ERROR_SHORT_BUFFER && res != TEEC_SUCCESS) @@ -226,6 +293,8 @@ int main(int argc, char *argv[]) { free(blob); errx(1, "Generate black key (fetch) failed 0x%x origin 0x%x", res, err_origin); } + /* Includes both invocations (size probe + fetch = two keygens) */ + ra_perf_log("blackkey_generate", ra_perf_now_us() - t0); printf("BlackKey(hex): "); print_binary_in_hex(blob, op_p.params[0].tmpref.size); @@ -253,13 +322,6 @@ int main(int argc, char *argv[]) { return 0; } - /* Connect to the server and establish a session */ - ChallengeResponseSession *session = open_session(); - if (session == NULL) { - printf("Failed to open session.\n"); - return 1; - } - /* Optional: pass private key from host (SW 'd' or CAAM black key) */ /* @@ -292,53 +354,93 @@ int main(int argc, char *argv[]) { } } - /* Request TA to issue evidence based on a given nonce */ - /* The buffer allocated here must be large enough to hold the CBOR evidece - */ - uint8_t cbor_evidence[1024] = {0}; - TEEC_Operation op = {0}; - - if (packed_key_param && packed_key_param_len > 0) { - /* Params: nonce(in), output(out), packed_key(in) */ - op.paramTypes = TEEC_PARAM_TYPES( - TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, - TEEC_MEMREF_TEMP_INPUT, TEEC_NONE); - } else { - /* Params: nonce(in), output(out) */ - op.paramTypes = TEEC_PARAM_TYPES( - TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, - TEEC_NONE, TEEC_NONE); - } - op.params[0].tmpref.buffer = (uint8_t *)session->nonce; - op.params[0].tmpref.size = session->nonce_size; - op.params[1].tmpref.buffer = cbor_evidence; - op.params[1].tmpref.size = sizeof(cbor_evidence); - /* param[2] is packed key: PubX(32) || PubY(32) || key_blob(N) */ - if (packed_key_param && packed_key_param_len > 0) { - op.params[2].tmpref.buffer = packed_key_param; - op.params[2].tmpref.size = packed_key_param_len; - } - - printf("\nInvoke TA.\n"); - res = TEEC_InvokeCommand(&sess, TA_REMOTE_ATTESTATOIN_CMD_GEN_CBOR_EVIDENCE, - &op, &err_origin); - if (res != TEEC_SUCCESS) - errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x", res, - err_origin); - - printf("Invoked TA successfully.\n\n\n"); - - /* Receive CBOR(COSE) evidence from PTA */ - printf("Received evidence of CBOR (COSE) format from PTA.\n\n"); + /* Key-path label for the RA_PERF log lines */ + if (packed_key_param && packed_key_param_len > 0) + ra_perf_keymode = packed_key_param_len > 96 ? "black" : "plain"; + else + ra_perf_keymode = "embedded"; + + for (long iter = 0; iter < loop_count; iter++) { + ChallengeResponseSession *session = NULL; + uint8_t local_nonce[32] = {0}; + const uint8_t *nonce = NULL; + size_t nonce_sz = 0; + uint64_t t_total = ra_perf_now_us(); + + if (no_server) { + /* No verifier round trip: attest against a local random nonce */ + if (get_random_nonce(local_nonce, sizeof(local_nonce)) != 0) + errx(1, "Failed to generate a local nonce"); + nonce = local_nonce; + nonce_sz = sizeof(local_nonce); + } else { + /* Connect to the server and establish a session */ + session = open_session(); + if (session == NULL) { + printf("Failed to open session.\n"); + return 1; + } + nonce = (const uint8_t *)session->nonce; + nonce_sz = session->nonce_size; + } - printf("CBOR(COSE) size: %ld\n", op.params[1].tmpref.size); - printf("CBOR(COSE): "); - print_binary_in_hex(op.params[1].tmpref.buffer, op.params[1].tmpref.size); - printf("\n\n"); + /* Request TA to issue evidence based on a given nonce */ + /* The buffer allocated here must be large enough to hold the CBOR + * evidece */ + uint8_t cbor_evidence[1024] = {0}; + TEEC_Operation op = {0}; + + if (packed_key_param && packed_key_param_len > 0) { + /* Params: nonce(in), output(out), packed_key(in) */ + op.paramTypes = TEEC_PARAM_TYPES( + TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, + TEEC_MEMREF_TEMP_INPUT, TEEC_NONE); + } else { + /* Params: nonce(in), output(out) */ + op.paramTypes = TEEC_PARAM_TYPES( + TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, + TEEC_NONE, TEEC_NONE); + } + op.params[0].tmpref.buffer = (uint8_t *)nonce; + op.params[0].tmpref.size = nonce_sz; + op.params[1].tmpref.buffer = cbor_evidence; + op.params[1].tmpref.size = sizeof(cbor_evidence); + /* param[2] is packed key: PubX(32) || PubY(32) || key_blob(N) */ + if (packed_key_param && packed_key_param_len > 0) { + op.params[2].tmpref.buffer = packed_key_param; + op.params[2].tmpref.size = packed_key_param_len; + } - /* Send the generated evidence to the session just established */ - post_evidence(session, op.params[1].tmpref.buffer, - op.params[1].tmpref.size); + printf("\nInvoke TA.\n"); + t0 = ra_perf_now_us(); + res = TEEC_InvokeCommand(&sess, + TA_REMOTE_ATTESTATOIN_CMD_GEN_CBOR_EVIDENCE, + &op, &err_origin); + if (res != TEEC_SUCCESS) + errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x", + res, err_origin); + ra_perf_log("evidence_get", ra_perf_now_us() - t0); + + printf("Invoked TA successfully.\n\n\n"); + + /* Receive CBOR(COSE) evidence from PTA */ + printf("Received evidence of CBOR (COSE) format from PTA.\n\n"); + + t0 = ra_perf_now_us(); + printf("CBOR(COSE) size: %ld\n", op.params[1].tmpref.size); + printf("CBOR(COSE): "); + print_binary_in_hex(op.params[1].tmpref.buffer, + op.params[1].tmpref.size); + printf("\n\n"); + ra_perf_log("print_evidence", ra_perf_now_us() - t0); + + /* Send the generated evidence to the session just established */ + if (!no_server) + post_evidence(session, op.params[1].tmpref.buffer, + op.params[1].tmpref.size); + + ra_perf_log("total", ra_perf_now_us() - t_total); + } if (packed_key_param && packed_key_param != host_key) free(packed_key_param); diff --git a/attester/remote_attestation/host/perf.h b/attester/remote_attestation/host/perf.h new file mode 100644 index 00000000..8a79f27e --- /dev/null +++ b/attester/remote_attestation/host/perf.h @@ -0,0 +1,21 @@ +/* + * Performance measurement logging for the attestation host client. + * + * Enabled at runtime with --perf or the RA_PERF=1 environment variable. + * Durations come from clock_gettime(CLOCK_MONOTONIC) and are printed to + * stdout as: + * + * RA_PERF|host|||key= + */ +#ifndef HOST_RA_PERF_H +#define HOST_RA_PERF_H + +#include + +extern int ra_perf_enabled; +extern const char *ra_perf_keymode; + +uint64_t ra_perf_now_us(void); +void ra_perf_log(const char *event, uint64_t duration_us); + +#endif /* HOST_RA_PERF_H */ diff --git a/attester/remote_attestation/ta/remote_attestation_ta.c b/attester/remote_attestation/ta/remote_attestation_ta.c index 78852406..548434b5 100755 --- a/attester/remote_attestation/ta/remote_attestation_ta.c +++ b/attester/remote_attestation/ta/remote_attestation_ta.c @@ -1,12 +1,36 @@ #include #include +#include #include #include #include #include +#ifdef CFG_REMOTE_ATTESTATION_PERF +/* + * Performance measurement logging (CFG_REMOTE_ATTESTATION_PERF=y). + * User TAs have no access to a high-resolution counter, so TA-level + * durations come from TEE_GetSystemTime and are accurate to ~1 ms only. + * Reported in microseconds for consistency with the PTA/host log lines: + * RA_PERF|ta|||key= + */ +static uint64_t ra_perf_now_us(void) { + TEE_Time t = {}; + + TEE_GetSystemTime(&t); + return ((uint64_t)t.seconds * 1000 + t.millis) * 1000; +} + +/* packed_key_len is the params[2] size: PubX(32) || PubY(32) || blob(N) */ +static const char *ra_perf_keymode(size_t packed_key_len) { + if (packed_key_len == 0) + return "embedded"; + return packed_key_len > 96 ? "black" : "plain"; +} +#endif + TEE_Result call_pta_for_cbor_evidence(uint32_t param_types, TEE_Param params[4]) { TEE_TASessionHandle sess = TEE_HANDLE_NULL; @@ -19,6 +43,16 @@ TEE_Result call_pta_for_cbor_evidence(uint32_t param_types, size_t nonce_len = 0; size_t out_len = 0; +#ifdef CFG_REMOTE_ATTESTATION_PERF + uint64_t perf_cmd_start = ra_perf_now_us(); + uint64_t perf_invoke_start = 0; + uint64_t perf_invoke_us = 0; + const char *perf_keymode = "embedded"; + + if (TEE_PARAM_TYPE_GET(param_types, 2) == TEE_PARAM_TYPE_MEMREF_INPUT) + perf_keymode = ra_perf_keymode(params[2].memref.size); +#endif + res = TEE_OpenTASession(&att_uuid, TEE_TIMEOUT_INFINITE, 0, NULL, &sess, &ret_orig); if (res != TEE_SUCCESS) { @@ -119,9 +153,15 @@ TEE_Result call_pta_for_cbor_evidence(uint32_t param_types, TEE_PARAM_TYPE_NONE); } +#ifdef CFG_REMOTE_ATTESTATION_PERF + perf_invoke_start = ra_perf_now_us(); +#endif res = TEE_InvokeTACommand(sess, TEE_TIMEOUT_INFINITE, PTA_REMOTE_ATTESTATION_GET_CBOR_EVIDENCE, pta_param_types, pta_params, &ret_orig); +#ifdef CFG_REMOTE_ATTESTATION_PERF + perf_invoke_us = ra_perf_now_us() - perf_invoke_start; +#endif if (res != TEE_SUCCESS) { EMSG("TEE_InvokeTACommand failed\n"); goto cleanup_return; @@ -142,6 +182,17 @@ TEE_Result call_pta_for_cbor_evidence(uint32_t param_types, if (nonce_buf) TEE_Free(nonce_buf); TEE_CloseTASession(sess); +#ifdef CFG_REMOTE_ATTESTATION_PERF + /* Durations measured before printing; ~1 ms resolution (see above) */ + if (perf_invoke_us) { + uint64_t perf_cmd_us = ra_perf_now_us() - perf_cmd_start; + + IMSG("RA_PERF|ta|pta_invoke|%" PRIu64 "|key=%s", perf_invoke_us, + perf_keymode); + IMSG("RA_PERF|ta|cmd_total|%" PRIu64 "|key=%s", perf_cmd_us, + perf_keymode); + } +#endif return res; } diff --git a/attester/remote_attestation/ta/sub.mk b/attester/remote_attestation/ta/sub.mk index 3cf56710..6662dd8e 100644 --- a/attester/remote_attestation/ta/sub.mk +++ b/attester/remote_attestation/ta/sub.mk @@ -1,2 +1,5 @@ global-incdirs-y += include srcs-y += remote_attestation_ta.c + +# Performance measurement logging (pass CFG_REMOTE_ATTESTATION_PERF=y) +cflags-$(CFG_REMOTE_ATTESTATION_PERF) += -DCFG_REMOTE_ATTESTATION_PERF=1 From 91fff685f6c476889689c1ccf3b93aeb607e1e0a Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Wed, 10 Jun 2026 22:25:25 +0000 Subject: [PATCH 2/7] docs: add performance measurement guide Document the RA_PERF timing logs: log format and clock sources per layer, the full log-point list, how to enable the instrumentation on QEMU and on the i.MX Yocto build, a recommended measurement protocol (repetitions, aggregation script), and how to compare CAAM signing with and without the black key (compare pta|sign_ecdsa only; on i.MX both paths sign on CAAM hardware, the black-key path additionally pays blob decapsulation + CCM key import). Includes the security note that running without the black key leaves the signing key unprotected by the CAAM, so those numbers are for performance comparison only. --- README-j.md | 12 ++ README.md | 12 ++ docs/performance-measurement-j.md | 200 +++++++++++++++++++++++++++++ docs/performance-measurement.md | 201 ++++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 docs/performance-measurement-j.md create mode 100644 docs/performance-measurement.md diff --git a/README-j.md b/README-j.md index 5310dcfc..984527bb 100644 --- a/README-j.md +++ b/README-j.md @@ -807,5 +807,17 @@ optee_remote_attestation --key-hex --pubx-hex --puby-h 期待される結果: `"ear.status": "affirming"` +## 性能計測ログ (RA_PERF) + +アテステーション処理全体の時間と主要イベント(メモリハッシュ、CBOR/COSE +エンコード、ECDSA/CAAM 署名、ブラックキー処理)ごとの所要時間を、機械可読な +ログ(`RA_PERF||||key=`)として出力でき +ます。ファームウェア側は `CFG_REMOTE_ATTESTATION_PERF=y` でコンパイルし、 +クライアント側は実行時に `optee_remote_attestation --perf`(データ収集用に +`--loop N` / `--no-server` も利用可)で有効化します。ログポイント一覧、 +QEMU/i.MX での有効化手順、CAAM ブラックキー使用有無の比較を含む推奨計測 +プロトコルは [docs/performance-measurement-j.md](docs/performance-measurement-j.md) +を参照してください。 + ## 謝辞 研究は、JST、CREST、JPMJCR21M3 ([Zero Trust IoT プロジェクト](https://zt-iot.nii.ac.jp/)) の支援を受けたものです。 diff --git a/README.md b/README.md index 1b50ba86..e09becf1 100644 --- a/README.md +++ b/README.md @@ -868,6 +868,18 @@ optee_remote_attestation --key-hex --pubx-hex --puby-h Expected result: `"ear.status": "affirming"` +## Performance Measurement Logging (RA_PERF) + +The attestation stack can emit machine-parsable timing logs +(`RA_PERF||||key=`) covering the total +attestation time and each major event (memory hashing, CBOR/COSE encoding, +ECDSA/CAAM signing, black-key processing). The firmware side is compiled in +with `CFG_REMOTE_ATTESTATION_PERF=y`; the client side is enabled at runtime +with `optee_remote_attestation --perf` (plus `--loop N` / `--no-server` for +data collection). See [docs/performance-measurement.md](docs/performance-measurement.md) +for the log-point list, how to enable it on QEMU/i.MX, and the recommended +measurement protocol including the CAAM black-key on/off comparison. + ## Acknowlegement This work was supported by JST, CREST Grant Number JPMJCR21M3 ([ZeroTrust IoT Project](https://zt-iot.nii.ac.jp/en/)), Japan. diff --git a/docs/performance-measurement-j.md b/docs/performance-measurement-j.md new file mode 100644 index 00000000..f1248ac1 --- /dev/null +++ b/docs/performance-measurement-j.md @@ -0,0 +1,200 @@ +# 性能計測ログ (`RA_PERF`) + +リモートアテステーション全体の処理時間と、主要イベント(ハッシュ計算、 +CBOR/COSE エンコード、CAAM 署名、ブラックキー処理など)ごとの所要時間を +ログとして出力し、性能評価データを収集できます。 + +英語版は [performance-measurement.md](performance-measurement.md) を参照 +してください。 + +## ログフォーマット + +計測 1 件につき 1 行を出力します: + +``` +RA_PERF||||key= +``` + +| フィールド | 意味 | +| ------------- | -------------------------------------------------------------------------- | +| `layer` | `host`(Linux クライアント)、`ta`(ユーザ TA)、`pta`(OP-TEE コア内の PTA) | +| `event` | 後述のログポイント一覧を参照 | +| `duration_us` | 所要時間(マイクロ秒、整数) | +| `keymode` | 実行時の鍵パス: `embedded`(組込みテストキー)/ `plain`(外部平文鍵)/ `black`(CAAM ブラックキー)/ `-`(非該当) | + +クロックソースと出力先: + +| Layer | クロック | 分解能 | 出力先 | +| ------ | ------------------------------------------------ | ------- | ---------------------------------------- | +| `host` | `clock_gettime(CLOCK_MONOTONIC)` | 約 1 µs | `optee_remote_attestation` の標準出力 | +| `ta` | `TEE_GetSystemTime` | 約 1 ms | セキュアコンソール(`I/TA:` プレフィクス)| +| `pta` | ARM ジェネリックタイマ(`CNTPCT`、周波数 `CNTFRQ`)| µs 未満 | セキュアコンソール(`I/TC:` プレフィクス)| + +QEMU ではセキュアコンソールは `serial1.log`(`make check` 時)または +soc_term ウィンドウ、i.MX 8M Plus EVK ではデバッグ UART です。`dmesg` は +使用しません。PTA は実行ごとに擬似イベント `cntfrq` でカウンタ周波数を +1 回出力するので、tick→µs 換算の検証に使えます(i.MX8MP: 8 MHz、QEMU +virt: 約 62.5 MHz)。 + +## ログポイント一覧 + +### `host`(標準出力) + +| イベント | 計測対象 | +| ------------------------ | -------------------------------------------------------------------- | +| `teec_init` | `TEEC_InitializeContext`(`/dev/tee0` オープン) | +| `teec_open_session` | `TEEC_OpenSession` — 毎回 TA のロード/認証を含む | +| `veraison_new_session` | relying party への HTTP ラウンドトリップ(`newSession`、nonce 受領) | +| `evidence_get` | `TEEC_InvokeCommand` — REE から見た secure 側エビデンス生成の全体 | +| `print_evidence` | エビデンスの 16 進ダンプ出力(`total` から差し引けるように計測) | +| `veraison_post_evidence` | HTTP ラウンドトリップ: エビデンス POST → アテステーション結果(EAR JWT)| +| `total` | アテステーション 1 回分全体(セッション開始 → 結果受領) | +| `blackkey_generate` | `--generate-blackkey` 全体(PTA 呼び出し 2 回: サイズ確認 + 取得) | +| `blackkey_convert` | `--convert-key` の呼び出し | + +### `ta`(セキュアコンソール、分解能約 1 ms) + +| イベント | 計測対象 | +| ------------ | ----------------------------------------------- | +| `pta_invoke` | `TEE_InvokeTACommand` による PTA 呼び出し往復 | +| `cmd_total` | TA コマンド全体(パラメータ準備・コピーバック含む)| + +### `pta`(セキュアコンソール、µs 分解能) + +| イベント | 計測対象 | +| ------------------ | ----------------------------------------------------------------------------- | +| `cntfrq` | (擬似イベント)ジェネリックタイマ周波数 [Hz]。時間ではない | +| `ocotp_srk` | signer-id 用 OCOTP SRK ハッシュのヒューズ読み出し(i.MX のみ) | +| `ocotp_lifecycle` | OCOTP ライフサイクル読み出し(i.MX のみ。内部で SRK ワードを再読する) | +| `instance_id` | PSA instance-id の導出(公開鍵の SHA-256) | +| `hash_ta` | ARoT 計測値: 呼び出し元 TA の読み取り専用メモリの SHA-256 | +| `hash_tee` | PRoT 計測値: OP-TEE コア `.text` + `.rodata` の SHA-256 | +| `cbor_encode` | PSA クレームの QCBOR エンコード | +| `sign_key_setup` | 署名鍵の確保とインポート(ブラックキー blob または組込み鍵 + 公開鍵) | +| `sign_tbs_hash` | COSE 署名対象(TBS)の SHA-256 | +| `sign_ecdsa` | `crypto_acipher_ecc_sign` — **ブラックキー比較用ブラケット**(後述) | +| `sign_verify` | 署名のセルフチェック検証(組込み鍵パスのみ) | +| `cose_sign1` | COSE_Sign1 生成全体(上記 `sign_*` 4 イベントを含む) | +| `cmd_total` | `GET_CBOR_EVIDENCE` PTA コマンド全体 | +| `perf_flush` | バッファした RA_PERF 行の出力自体のコスト(注意事項参照) | +| `keypair_generate` | `crypto_acipher_gen_ecc_key`(i.MX では CAAM 鍵生成)。`--generate-blackkey` 1 回につき 2 回記録(サイズ確認 + 取得) | +| `blackkey_encap` | `--convert-key` 内の `caam_key_black_encapsulation`(CCM) | +| `convert_total` | `CONVERT_TO_BLACKKEY` PTA コマンド全体 | + +## ログの有効化 + +ファームウェア側の計測コードは `CFG_REMOTE_ATTESTATION_PERF=y` のときだけ +コンパイルされます。デフォルト(リリースビルド)には影響しません。 + +**QEMU**(attester コンテナ内): + +```bash +make check CFG_REMOTE_ATTESTATION_PTA=y CFG_REMOTE_ATTESTATION_PERF=y +``` + +**i.MX 8M Plus(Yocto)** — `local.conf` に追加: + +``` +CFG_REMOTE_ATTESTATION_PERF:pn-optee-os = "y" +CFG_REMOTE_ATTESTATION_PERF:pn-veraison-attestation = "y" +``` + +その後 `optee-os` を cleansstate から再ビルドし、`veraison-attestation`、 +イメージのビルド、再署名を行ってください。注意: NXP の +`optee-os-common-fslc-imx.inc` は `CFG_TEE_CORE_LOG_LEVEL=0`(コアのトレース +を全てコンパイル時に除去)を強制するため、本リポジトリの bbappend は perf +フラグと同時にコアログレベルを 2 へ自動的に引き上げます — これがないと +`pta` の行は UART に一切出力されません。 + +**ホストクライアント** — ファームウェアフラグとは独立したランタイム指定: + +```bash +optee_remote_attestation --perf # または RA_PERF=1 optee_remote_attestation +``` + +データ収集向けの追加オプション: + +| オプション | 用途 | +| ------------- | ------------------------------------------------------------------------ | +| `--loop N` | アテステーション全体を N 回繰り返す(反復ごとに `total` 行を出力) | +| `--no-server` | 検証器との通信を省略しローカル乱数 nonce でアテステーションを実行 — relying party に到達できない環境や、ネットワーク変動と切り離して TEE 側だけを測りたい場合に有効 | + +## 推奨計測プロトコル + +1. 環境を固定して記録する: ボード(i.MX8MPEVK)、CPU クロック/ガバナ、 + ビルド設定、`cntfrq` 行、ソフトウェアのバージョン、N。 +2. 設定ごとに 30 回以上繰り返し、初回(コールドキャッシュ・初回 TA ロード) + は除外する: + +```bash +# 検証器あり(エンドツーエンド): +optee_remote_attestation --perf --loop 31 | tee host-embedded.log + +# TEE 側のみ(ネットワークなし): +optee_remote_attestation --perf --no-server --loop 31 | tee host-offline.log + +# CAAM ブラックキー(i.MX のみ): 1 回生成してから計測 +optee_remote_attestation --generate-blackkey --perf # blackkey_generate を記録 +optee_remote_attestation --perf --loop 31 --key-hex | tee host-black.log +``` + +3. `pta`/`ta` の行はセキュアコンソール(UART / `serial1.log`)から回収する。 +4. 集計(イベント × 鍵パスごとの平均/最小/最大): + +```bash +grep -h -o 'RA_PERF|.*' serial1.log host-*.log | awk -F'|' ' + { split($5, k, "="); id = $2 "|" $3 "|" k[2]; + n[id]++; s[id] += $4; + if (min[id] == "" || $4 < min[id]) min[id] = $4; + if ($4 > max[id]) max[id] = $4 } + END { for (i in n) printf "%-44s n=%-3d mean=%10.1f min=%8d max=%8d\n", + i, n[i], s[i]/n[i], min[i], max[i] }' | sort +``` + +## CAAM 署名のブラックキー使用有無の比較 + +ブラックキーの使用有無は、同一の i.MX ビルド上での**ランタイム**の選択です: + +* **ブラックキーあり**: `--key-hex ` を指定(FullKey = + `PubX(32) || PubY(32) || シリアライズ済み CAAM ブラックキー blob`。 + `--generate-blackkey` または `--convert-key` で取得)。ログは `key=black`。 +* **ブラックキーなし**: 鍵引数を指定しない — 組込みテストキーを使用。 + ログは `key=embedded`。 + +比較には `pta|sign_ecdsa` イベント**のみ**を使ってください: + +* `CFG_NXP_CAAM_ECC_DRV=y` の i.MX では**どちらのパスも** CAAM ハードウェア + で ECDSA 署名を実行します。ブラックキーパスはそれに加えて鍵 blob の + デカプセル化とエンジン内 CCM 鍵インポートのコストを払うため、この差分が + まさに比較で分離したい量になります。(QEMU 実行はソフトウェア署名のみの + 第 3 のデータ点になります。) +* `sign_verify` は組込み鍵パス**のみ**で実行されます(署名のセルフチェック)。 + 比較にバイアスを与えないよう別イベントとして計測しています。ブラックキー + 差分の評価に `cose_sign1` や `cmd_total` を使わないでください。 + +> **セキュリティに関する注意。** ブラックキーを使わない場合、署名鍵は +> OP-TEE 内で平文として扱われ(組込みテストキーの場合はバイナリにも含まれ)、 +> CAAM による鍵保護は働きません。セキュアブートを起点とした鍵保護の保証が +> 成り立たないため、ブラックキーなしの数値は性能比較のためだけのものであり、 +> 本番構成で使用してはいけません。 + +## 数値解釈上の注意 + +* PTA のタイムスタンプはすべて RA_PERF 行の出力**前**に取得され、PTA + コマンドの最後に一括出力されます。コンソール書き込みは同期的なので、 + この出力コストは上位レイヤのブラケット(`ta|pta_invoke`、 + `host|evidence_get`、`host|total`)には**含まれます**。出力コスト自体は + `pta|perf_flush` として報告されるため、レイヤ間の対応を取るときは差し引いて + ください(最後の `perf_flush` 行 1 行分だけは計上されません)。 +* 純粋なエビデンス生成時間には `pta|cmd_total` を使ってください。 + `host|evidence_get − ta|cmd_total` ≈ REE↔TEE 遷移 + libteec のオーバヘッド、 + `ta|pta_invoke − pta|cmd_total − pta|perf_flush` ≈ secure 側ディスパッチの + オーバヘッドです。 +* `host|total` はクライアントの冗長な表示を含みます。コンソールダンプ分は + `print_evidence` として計測しているので差し引けます。 +* `ta` レイヤの分解能は約 1 ms です(`TEE_GetSystemTime`。ユーザ TA は + サイクルカウンタへ直接アクセスできません)。 +* `hash_ta` は SHA-256 だけでなく、TA の読み取り専用リージョンの + `qsort`(`memcmp` 比較。ASLR に対して順序を安定化)を含みます。 +* `ocotp_lifecycle` は `ocotp_srk` が読んだ SRK ヒューズワードを内部で + 再読します。この重複は意図的にそのまま計測しています。 diff --git a/docs/performance-measurement.md b/docs/performance-measurement.md new file mode 100644 index 00000000..2095c1a3 --- /dev/null +++ b/docs/performance-measurement.md @@ -0,0 +1,201 @@ +# Performance Measurement Logging (`RA_PERF`) + +This repository can emit timing logs for the whole remote attestation flow so +that the total processing time and the duration of each major event (hash +computation, CBOR/COSE encoding, CAAM signing, black-key processing, …) can be +collected for performance evaluation. + +The Japanese version of this document is +[performance-measurement-j.md](performance-measurement-j.md). + +## Log format + +Every measurement is a single line: + +``` +RA_PERF||||key= +``` + +| Field | Meaning | +| ------------- | ---------------------------------------------------------------------------------------- | +| `layer` | `host` (Linux client), `ta` (user TA), `pta` (PTA inside the OP-TEE core) | +| `event` | See the log point tables below | +| `duration_us` | Duration in microseconds (integer) | +| `keymode` | Key path of the run: `embedded` (built-in test key), `plain` (external plain key), `black` (CAAM black key), `-` (not applicable) | + +Clock sources and where the lines appear: + +| Layer | Clock | Resolution | Output | +| ------ | ---------------------------------------------- | ---------- | ----------------------------------------------- | +| `host` | `clock_gettime(CLOCK_MONOTONIC)` | ~1 µs | stdout of `optee_remote_attestation` | +| `ta` | `TEE_GetSystemTime` | ~1 ms | secure console (`I/TA:` prefix) | +| `pta` | ARM generic timer (`CNTPCT`), frequency `CNTFRQ` | sub-µs | secure console (`I/TC:` prefix) | + +On QEMU the secure console is `serial1.log` (with `make check`) or the +soc_term window; on the i.MX 8M Plus EVK it is the debug UART. `dmesg` is not +involved. The PTA reports the counter frequency once per run as the +`cntfrq` pseudo-event so the tick-to-µs conversion can be verified +(i.MX8MP: 8 MHz, QEMU virt: ~62.5 MHz). + +## Log points + +### `host` (stdout) + +| Event | Measures | +| ----------------------- | --------------------------------------------------------------------- | +| `teec_init` | `TEEC_InitializeContext` (open `/dev/tee0`) | +| `teec_open_session` | `TEEC_OpenSession` — includes TA load/authentication on every run | +| `veraison_new_session` | HTTP round trip to the relying party (`newSession`, nonce receipt) | +| `evidence_get` | `TEEC_InvokeCommand` — entire secure-side evidence generation as seen from the REE | +| `print_evidence` | Console hex dump of the evidence (so it can be subtracted from `total`)| +| `veraison_post_evidence`| HTTP round trip: evidence POST → attestation result (EAR JWT) | +| `total` | One whole attestation transaction (session open → result received) | +| `blackkey_generate` | `--generate-blackkey` total (two PTA invocations: size probe + fetch) | +| `blackkey_convert` | `--convert-key` invocation | + +### `ta` (secure console, ~1 ms resolution) + +| Event | Measures | +| ------------ | ---------------------------------------------------- | +| `pta_invoke` | `TEE_InvokeTACommand` round trip into the PTA | +| `cmd_total` | Whole TA command incl. parameter staging and copy-back | + +### `pta` (secure console, µs resolution) + +| Event | Measures | +| ------------------ | ---------------------------------------------------------------------------- | +| `cntfrq` | (pseudo-event) generic timer frequency in Hz, not a duration | +| `ocotp_srk` | OCOTP SRK-hash fuse read for signer-id (i.MX only) | +| `ocotp_lifecycle` | OCOTP lifecycle read (i.MX only; re-reads the SRK words internally) | +| `instance_id` | PSA instance-id derivation (SHA-256 of the public key) | +| `hash_ta` | ARoT measurement: SHA-256 over the calling TA's read-only memory | +| `hash_tee` | PRoT measurement: SHA-256 over the OP-TEE core `.text` + `.rodata` | +| `cbor_encode` | QCBOR encoding of the PSA claims | +| `sign_key_setup` | Signing key allocation and import (black-key blob or embedded key + pubkey) | +| `sign_tbs_hash` | SHA-256 of the COSE to-be-signed structure | +| `sign_ecdsa` | `crypto_acipher_ecc_sign` — **the black-key comparison bracket** (see below) | +| `sign_verify` | Self-check `crypto_acipher_ecc_verify` (embedded-key path only) | +| `cose_sign1` | Whole COSE_Sign1 generation (includes the four `sign_*` events) | +| `cmd_total` | Whole `GET_CBOR_EVIDENCE` PTA command | +| `perf_flush` | Cost of printing the buffered RA_PERF lines themselves (see Caveats) | +| `keypair_generate` | `crypto_acipher_gen_ecc_key` (CAAM keygen on i.MX); logged twice per `--generate-blackkey` (size probe + fetch) | +| `blackkey_encap` | `caam_key_black_encapsulation` (CCM) in `--convert-key` | +| `convert_total` | Whole `CONVERT_TO_BLACKKEY` PTA command | + +## Enabling the logs + +The firmware-side instrumentation is compiled in only with +`CFG_REMOTE_ATTESTATION_PERF=y`; release builds are unaffected by default. + +**QEMU** (inside the attester container): + +```bash +make check CFG_REMOTE_ATTESTATION_PTA=y CFG_REMOTE_ATTESTATION_PERF=y +``` + +**i.MX 8M Plus (Yocto)** — add to `local.conf` (or pass through your build +wrapper): + +``` +CFG_REMOTE_ATTESTATION_PERF:pn-optee-os = "y" +CFG_REMOTE_ATTESTATION_PERF:pn-veraison-attestation = "y" +``` + +then rebuild `optee-os` (cleansstate), `veraison-attestation`, the image, and +re-sign. Note: NXP's `optee-os-common-fslc-imx.inc` forces +`CFG_TEE_CORE_LOG_LEVEL=0` (all core trace compiled out), so the bbappend in +this repository automatically raises the core log level to 2 together with the +perf flag — without it no `pta` line would ever reach the UART. + +**Host client** — runtime opt-in, independent of the firmware flag: + +```bash +optee_remote_attestation --perf # or RA_PERF=1 optee_remote_attestation +``` + +Additional client options for data collection: + +| Option | Purpose | +| ------------- | ------------------------------------------------------------------------ | +| `--loop N` | Repeat the whole attestation N times (per-iteration `total` lines) | +| `--no-server` | Skip the verifier round trips and attest a local random nonce — useful on devices that cannot reach a relying party, and for isolating TEE-side numbers from network jitter | + +## Recommended measurement protocol + +1. Fix the environment and record it: board (i.MX8MPEVK), governor/CPU clock, + build config, `cntfrq` line, software versions, N. +2. Collect ≥ 30 iterations per configuration and discard the first iteration + (cold caches, first TA load): + +```bash +# with verifier (end-to-end): +optee_remote_attestation --perf --loop 31 | tee host-embedded.log + +# TEE-side only (no network): +optee_remote_attestation --perf --no-server --loop 31 | tee host-offline.log + +# CAAM black key (i.MX only): generate once, then measure +optee_remote_attestation --generate-blackkey --perf # records blackkey_generate +optee_remote_attestation --perf --loop 31 --key-hex | tee host-black.log +``` + +3. Capture the secure console (UART/`serial1.log`) for the `pta`/`ta` lines. +4. Aggregate (mean/min/max per event and key path): + +```bash +grep -h -o 'RA_PERF|.*' serial1.log host-*.log | awk -F'|' ' + { split($5, k, "="); id = $2 "|" $3 "|" k[2]; + n[id]++; s[id] += $4; + if (min[id] == "" || $4 < min[id]) min[id] = $4; + if ($4 > max[id]) max[id] = $4 } + END { for (i in n) printf "%-44s n=%-3d mean=%10.1f min=%8d max=%8d\n", + i, n[i], s[i]/n[i], min[i], max[i] }' | sort +``` + +## Comparing CAAM signing with and without the black key + +The black-key usage is a **runtime** choice on a single i.MX build: + +* **with black key**: pass `--key-hex ` (FullKey = + `PubX(32) || PubY(32) || serialized CAAM black-key blob`, produced by + `--generate-blackkey` or `--convert-key`). Log lines carry `key=black`. +* **without black key**: pass no key arguments — the embedded test key is + used. Log lines carry `key=embedded`. + +Compare the `pta|sign_ecdsa` event only: + +* On i.MX with `CFG_NXP_CAAM_ECC_DRV=y` **both** paths execute the ECDSA sign + on the CAAM hardware; the black-key path additionally pays the key-blob + decapsulation and in-engine CCM key import, which is exactly the delta the + comparison isolates. (A QEMU run provides a third, software-only datapoint.) +* `sign_verify` runs **only** on the embedded-key path (signature self-check); + it is reported separately precisely so that it does not bias the comparison. + Do not use `cose_sign1` or `cmd_total` for the black-key delta. + +> **Security note.** Running without the black key means the signing key is +> handled as plaintext inside OP-TEE (and, for the embedded test key, is part +> of the binary). It is **not** protected by the CAAM and the +> secure-boot-rooted key-protection guarantee does not hold. The +> non-black-key numbers are for performance comparison only and must not be +> used in a production configuration. + +## Caveats for interpreting the numbers + +* All PTA timestamps are taken **before** any RA_PERF line is printed; the + lines are flushed in one batch at the end of the PTA command. Console + writes are synchronous, so this flush *is* visible in the brackets of the + layers above (`ta|pta_invoke`, `host|evidence_get`, `host|total`). The + flush reports its own cost as `pta|perf_flush` — subtract it when relating + layers (only the final `perf_flush` line itself remains unaccounted). +* Use `pta|cmd_total` as the pure evidence-generation time; + `host|evidence_get − ta|cmd_total` ≈ REE↔TEE transition + libteec overhead, + `ta|pta_invoke − pta|cmd_total − pta|perf_flush` ≈ secure-world dispatch + overhead. +* `host|total` includes all verbose client prints; `print_evidence` is + measured so the console dump can be subtracted. +* The `ta` layer has only ~1 ms resolution (`TEE_GetSystemTime`); user TAs + have no direct access to the cycle counter. +* `hash_ta` includes a `qsort` with `memcmp` of the TA's read-only regions + (ASLR-stable ordering), not just the SHA-256 itself. +* `ocotp_lifecycle` internally re-reads the SRK fuse words already read by + `ocotp_srk`; the duplication is intentionally measured as-is. From 4765a6fa513e2bbce6976c54bb2be173f74bf53d Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Tue, 23 Jun 2026 08:47:54 +0000 Subject: [PATCH 3/7] docs: make environment pinning executable and flag untrimmed aggregation Turn the measurement-protocol's "governor/CPU clock" mention into a concrete pin-and-record step (scaling_governor=performance + record scaling_cur_freq), since sub-ms events swing with DVFS and nothing else fixed it. Note that the aggregation one-liner counts all iterations (including the cold first one the protocol says to discard), how to trim it offline, and that teec_init/teec_open_session are measured once before the loop and so are already outside the per-iteration aggregates. --- docs/performance-measurement-j.md | 23 +++++++++++++++++++++-- docs/performance-measurement.md | 23 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/performance-measurement-j.md b/docs/performance-measurement-j.md index f1248ac1..9aab1d85 100644 --- a/docs/performance-measurement-j.md +++ b/docs/performance-measurement-j.md @@ -121,8 +121,20 @@ optee_remote_attestation --perf # または RA_PERF=1 optee_remote_atte ## 推奨計測プロトコル -1. 環境を固定して記録する: ボード(i.MX8MPEVK)、CPU クロック/ガバナ、 - ビルド設定、`cntfrq` 行、ソフトウェアのバージョン、N。 +1. 環境を固定して記録する: ボード(i.MX8MPEVK)、ビルド設定、`cntfrq` 行、 + ソフトウェアのバージョン、N。sub-ms のイベント(署名・ハッシュ)は DVFS で + ぶれるので、**CPU ガバナを固定し実クロックを記録**する: + + ```bash + for g in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do + echo performance > "$g" 2>/dev/null + done + cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor + cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq + ``` + + 固定できない場合も、`scaling_governor` と `scaling_cur_freq` を読み取って + 記録すれば、後で DVFS による変動幅を論文で明示できる。 2. 設定ごとに 30 回以上繰り返し、初回(コールドキャッシュ・初回 TA ロード) は除外する: @@ -151,6 +163,13 @@ grep -h -o 'RA_PERF|.*' serial1.log host-*.log | awk -F'|' ' i, n[i], s[i]/n[i], min[i], max[i] }' | sort ``` +この one-liner は**全イテレーションを集計**するため、`n` には手順 2 で除外 +すべき初回(コールド)実行が含まれる。出力は「未トリム(n=N)」として扱うか、 +集計前に各 `layer|event|keymode` の先頭 1 件を落とすこと(生ログはイテレーション +順を保持しているので `tail -n +2` 相当でトリム可)。なお `teec_init` / +`teec_open_session` はループ前に 1 回だけ計測されるので、各イテレーションの +集計には元々含まれない。 + ## CAAM 署名のブラックキー使用有無の比較 ブラックキーの使用有無は、同一の i.MX ビルド上での**ランタイム**の選択です: diff --git a/docs/performance-measurement.md b/docs/performance-measurement.md index 2095c1a3..edd7b441 100644 --- a/docs/performance-measurement.md +++ b/docs/performance-measurement.md @@ -122,8 +122,20 @@ Additional client options for data collection: ## Recommended measurement protocol -1. Fix the environment and record it: board (i.MX8MPEVK), governor/CPU clock, - build config, `cntfrq` line, software versions, N. +1. Fix the environment and record it: board (i.MX8MPEVK), build config, + `cntfrq` line, software versions, N. Pin the CPU governor and record the + actual frequency — the sub-ms events (signing, hashing) swing with DVFS: + + ```bash + for g in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do + echo performance > "$g" 2>/dev/null + done + cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor + cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq + ``` + + If pinning is not possible, at least read and record `scaling_governor` + and `scaling_cur_freq` so the DVFS-induced variance can be bounded. 2. Collect ≥ 30 iterations per configuration and discard the first iteration (cold caches, first TA load): @@ -152,6 +164,13 @@ grep -h -o 'RA_PERF|.*' serial1.log host-*.log | awk -F'|' ' i, n[i], s[i]/n[i], min[i], max[i] }' | sort ``` +This one-liner aggregates **all** iterations, so its `n` includes the cold +first iteration that step 2 says to discard. Either treat its output as +"untrimmed (n=N)" or drop the first occurrence per `layer|event|keymode` +before aggregating (the raw log preserves iteration order, so `tail -n +2` +per event works). Note `teec_init`/`teec_open_session` are measured once +before the loop, so they are already outside the per-iteration aggregates. + ## Comparing CAAM signing with and without the black key The black-key usage is a **runtime** choice on a single i.MX build: From 4fb03e84cb977081bf600a5819d45f6290190286 Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Tue, 28 Jul 2026 06:28:27 +0000 Subject: [PATCH 4/7] attester: remove the signature self-verify from the PTA sign path Drop the embedded-key-only crypto_acipher_ecc_verify self-check (and the public-key setup that existed only to feed it) from sign_ecdsa_sha256. It was a debug-time diagnostic: its result was ignored (res was reset to success either way), it never ran on the black-key path, and it made the embedded-key path pay an extra ~6 ms verify that had to be excluded from every performance comparison. With it gone the embedded-key and plain-key paths execute the same code, which the July device measurements confirmed (their sign_ecdsa times are statistically identical). The sign_verify RA_PERF event and its special-casing in the measurement guide are removed accordingly. --- .../remote_attestation/sign.c | 65 +------------------ docs/performance-measurement-j.md | 9 +-- docs/performance-measurement.md | 9 +-- 3 files changed, 8 insertions(+), 75 deletions(-) diff --git a/attester/pta_remote_attestation/remote_attestation/sign.c b/attester/pta_remote_attestation/remote_attestation/sign.c index e7063b80..ade2d0fe 100644 --- a/attester/pta_remote_attestation/remote_attestation/sign.c +++ b/attester/pta_remote_attestation/remote_attestation/sign.c @@ -46,7 +46,6 @@ The key information has been extracted using the command: static TEE_Result hash_sha256(const uint8_t *msg, size_t msg_len, uint8_t *hash); static void free_keypair(struct ecc_keypair *keypair); -static void free_pubkey(struct ecc_public_key *pk); TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, size_t *sig_len, @@ -55,10 +54,7 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, TEE_Result res = TEE_SUCCESS; uint8_t hash_msg[TEE_SHA256_HASH_SIZE]; struct ecc_keypair *key = NULL; - struct ecc_public_key *pubkey = NULL; const uint8_t private_key[] = {PRIVATE_KEY}; - const uint8_t public_key_x[] = {PUBLIC_KEY_X}; - const uint8_t public_key_y[] = {PUBLIC_KEY_Y}; RA_PERF_DECL(t); @@ -91,30 +87,6 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, if (res != TEE_SUCCESS) { goto free_key; } - - /* Allocate a public key storage for verification */ - pubkey = calloc(1, sizeof(*pubkey)); - if (pubkey == NULL) { - res = TEE_ERROR_OUT_OF_MEMORY; - goto free_key; - } - - res = crypto_acipher_alloc_ecc_public_key(pubkey, TEE_TYPE_ECDSA_PUBLIC_KEY, - KEY_SIZE_BIT); - if (res != TEE_SUCCESS) { - goto free_pubkey; - } - pubkey->curve = TEE_ECC_CURVE_NIST_P256; - - /* Copy the public key */ - res = crypto_bignum_bin2bn(public_key_x, KEY_SIZE, pubkey->x); - if (res != TEE_SUCCESS) { - goto free_pubkey; - } - res = crypto_bignum_bin2bn(public_key_y, KEY_SIZE, pubkey->y); - if (res != TEE_SUCCESS) { - goto free_pubkey; - } } RA_PERF_STOP(t, "sign_key_setup", @@ -125,7 +97,7 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, RA_PERF_START(t); res = hash_sha256(msg, msg_len, hash_msg); if (res != TEE_SUCCESS) - goto free_pubkey; + goto free_key; RA_PERF_STOP(t, "sign_tbs_hash", ra_perf_keymode(serialized_black_key, serialized_black_key_len)); @@ -135,31 +107,11 @@ TEE_Result sign_ecdsa_sha256(const uint8_t *msg, size_t msg_len, uint8_t *sig, res = crypto_acipher_ecc_sign(TEE_ALG_ECDSA_SHA256, key, hash_msg, TEE_SHA256_HASH_SIZE, sig, sig_len); if (res != TEE_SUCCESS) - goto free_pubkey; + goto free_key; RA_PERF_STOP(t, "sign_ecdsa", ra_perf_keymode(serialized_black_key, serialized_black_key_len)); - /* Verify the signature if we have the public key */ - if (pubkey) { - RA_PERF_START(t); - res = crypto_acipher_ecc_verify(TEE_ALG_ECDSA_SHA256, pubkey, hash_msg, - TEE_SHA256_HASH_SIZE, sig, *sig_len); - if (res == TEE_SUCCESS) { - DMSG("Success to verify"); - } else { - DMSG("Failed to verify"); - } - /* Reset res to success even if verify fails - we still signed */ - res = TEE_SUCCESS; - RA_PERF_STOP(t, "sign_verify", - ra_perf_keymode(serialized_black_key, - serialized_black_key_len)); - } - -free_pubkey: - if (pubkey) - free_pubkey(pubkey); free_key: if (key) free_keypair(key); @@ -203,19 +155,6 @@ static void free_keypair(struct ecc_keypair *keypair) { free(keypair); } -static void free_pubkey(struct ecc_public_key *pk) { - if (!pk) - return; - - if (pk->x) - crypto_bignum_free(&pk->x); - if (pk->y) - crypto_bignum_free(&pk->y); - - memset(pk, 0, sizeof(*pk)); - free(pk); -} - /* * Compute PSA instance-id per: * PSA Attestation Token: draft-tschofenig-rats-psa-token, Section 4.2.1 diff --git a/docs/performance-measurement-j.md b/docs/performance-measurement-j.md index 9aab1d85..77a5ecec 100644 --- a/docs/performance-measurement-j.md +++ b/docs/performance-measurement-j.md @@ -70,11 +70,10 @@ virt: 約 62.5 MHz)。 | `hash_ta` | ARoT 計測値: 呼び出し元 TA の読み取り専用メモリの SHA-256 | | `hash_tee` | PRoT 計測値: OP-TEE コア `.text` + `.rodata` の SHA-256 | | `cbor_encode` | PSA クレームの QCBOR エンコード | -| `sign_key_setup` | 署名鍵の確保とインポート(ブラックキー blob または組込み鍵 + 公開鍵) | +| `sign_key_setup` | 署名鍵の確保とインポート(ブラックキー blob または組込み鍵) | | `sign_tbs_hash` | COSE 署名対象(TBS)の SHA-256 | | `sign_ecdsa` | `crypto_acipher_ecc_sign` — **ブラックキー比較用ブラケット**(後述) | -| `sign_verify` | 署名のセルフチェック検証(組込み鍵パスのみ) | -| `cose_sign1` | COSE_Sign1 生成全体(上記 `sign_*` 4 イベントを含む) | +| `cose_sign1` | COSE_Sign1 生成全体(上記 `sign_*` 3 イベントを含む) | | `cmd_total` | `GET_CBOR_EVIDENCE` PTA コマンド全体 | | `perf_flush` | バッファした RA_PERF 行の出力自体のコスト(注意事項参照) | | `keypair_generate` | `crypto_acipher_gen_ecc_key`(i.MX では CAAM 鍵生成)。`--generate-blackkey` 1 回につき 2 回記録(サイズ確認 + 取得) | @@ -187,9 +186,7 @@ grep -h -o 'RA_PERF|.*' serial1.log host-*.log | awk -F'|' ' デカプセル化とエンジン内 CCM 鍵インポートのコストを払うため、この差分が まさに比較で分離したい量になります。(QEMU 実行はソフトウェア署名のみの 第 3 のデータ点になります。) -* `sign_verify` は組込み鍵パス**のみ**で実行されます(署名のセルフチェック)。 - 比較にバイアスを与えないよう別イベントとして計測しています。ブラックキー - 差分の評価に `cose_sign1` や `cmd_total` を使わないでください。 +* ブラックキー差分の評価に `cose_sign1` や `cmd_total` を使わないでください。 > **セキュリティに関する注意。** ブラックキーを使わない場合、署名鍵は > OP-TEE 内で平文として扱われ(組込みテストキーの場合はバイナリにも含まれ)、 diff --git a/docs/performance-measurement.md b/docs/performance-measurement.md index edd7b441..f9d77c74 100644 --- a/docs/performance-measurement.md +++ b/docs/performance-measurement.md @@ -71,11 +71,10 @@ involved. The PTA reports the counter frequency once per run as the | `hash_ta` | ARoT measurement: SHA-256 over the calling TA's read-only memory | | `hash_tee` | PRoT measurement: SHA-256 over the OP-TEE core `.text` + `.rodata` | | `cbor_encode` | QCBOR encoding of the PSA claims | -| `sign_key_setup` | Signing key allocation and import (black-key blob or embedded key + pubkey) | +| `sign_key_setup` | Signing key allocation and import (black-key blob or embedded key) | | `sign_tbs_hash` | SHA-256 of the COSE to-be-signed structure | | `sign_ecdsa` | `crypto_acipher_ecc_sign` — **the black-key comparison bracket** (see below) | -| `sign_verify` | Self-check `crypto_acipher_ecc_verify` (embedded-key path only) | -| `cose_sign1` | Whole COSE_Sign1 generation (includes the four `sign_*` events) | +| `cose_sign1` | Whole COSE_Sign1 generation (includes the three `sign_*` events) | | `cmd_total` | Whole `GET_CBOR_EVIDENCE` PTA command | | `perf_flush` | Cost of printing the buffered RA_PERF lines themselves (see Caveats) | | `keypair_generate` | `crypto_acipher_gen_ecc_key` (CAAM keygen on i.MX); logged twice per `--generate-blackkey` (size probe + fetch) | @@ -187,9 +186,7 @@ Compare the `pta|sign_ecdsa` event only: on the CAAM hardware; the black-key path additionally pays the key-blob decapsulation and in-engine CCM key import, which is exactly the delta the comparison isolates. (A QEMU run provides a third, software-only datapoint.) -* `sign_verify` runs **only** on the embedded-key path (signature self-check); - it is reported separately precisely so that it does not bias the comparison. - Do not use `cose_sign1` or `cmd_total` for the black-key delta. +* Do not use `cose_sign1` or `cmd_total` for the black-key delta. > **Security note.** Running without the black key means the signing key is > handled as plaintext inside OP-TEE (and, for the embedded test key, is part From 8b64a3e0f409812d8eb2161fbe114cbc40eca094 Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Tue, 28 Jul 2026 06:28:35 +0000 Subject: [PATCH 5/7] attester: fix CAAM include path guard for builds without the ECC driver ocotp.c is compiled whenever CFG_NXP_CAAM=y, but the CAAM include directory was only added with CFG_NXP_CAAM_ECC_DRV=y, so a build with the CAAM enabled and the ECC driver disabled (the software-signing configuration used for the CPU-vs-CAAM comparison) failed to find caam_key.h. Key the incdirs on CFG_NXP_CAAM to match the sources. --- attester/pta_remote_attestation/remote_attestation/sub.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attester/pta_remote_attestation/remote_attestation/sub.mk b/attester/pta_remote_attestation/remote_attestation/sub.mk index 345876c1..a1f75896 100644 --- a/attester/pta_remote_attestation/remote_attestation/sub.mk +++ b/attester/pta_remote_attestation/remote_attestation/sub.mk @@ -9,7 +9,7 @@ srcs-$(CFG_REMOTE_ATTESTATION_PTA) += qcbor/ieee754.c srcs-$(CFG_REMOTE_ATTESTATION_PTA) += qcbor/UsefulBuf.c srcs-$(CFG_NXP_CAAM) += ocotp.c -incdirs-$(CFG_NXP_CAAM_ECC_DRV) += ../../drivers/crypto/caam/include +incdirs-$(CFG_NXP_CAAM) += ../../drivers/crypto/caam/include cflags-$(CFG_REMOTE_ATTESTATION_PTA) += -Wno-declaration-after-statement cflags-$(CFG_REMOTE_ATTESTATION_PTA) += -Wno-redundant-decls From 7d680e41dc1bfc1a72a9b7ab3615bbe6aa2379f6 Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Tue, 28 Jul 2026 06:28:50 +0000 Subject: [PATCH 6/7] provisioning: add reference values for the i.MX CAAM and CPU builds Add per-build CoMID templates for the two i.MX8MP images used in the signing-performance comparison: imx-caam (CFG_NXP_CAAM_ECC_DRV=y, ECDSA on the CAAM) and imx-cpu (ECC driver disabled, software ECDSA). The ARoT reference value is identical for both (same TA binary); the PRoT value differs per build because the OP-TEE core differs. Each variant gets its own tag-identity id and a matching comid-psa-ta file so that "./run.sh imx-caam" / "./run.sh imx-cpu" work as-is. --- .../data/comid-psa-refval-imx-caam.json | 64 +++++++++++++++++++ .../data/comid-psa-refval-imx-cpu.json | 64 +++++++++++++++++++ provisoning/data/comid-psa-ta-imx-caam.json | 44 +++++++++++++ provisoning/data/comid-psa-ta-imx-cpu.json | 44 +++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 provisoning/data/comid-psa-refval-imx-caam.json create mode 100644 provisoning/data/comid-psa-refval-imx-cpu.json create mode 100644 provisoning/data/comid-psa-ta-imx-caam.json create mode 100644 provisoning/data/comid-psa-ta-imx-cpu.json diff --git a/provisoning/data/comid-psa-refval-imx-caam.json b/provisoning/data/comid-psa-refval-imx-caam.json new file mode 100644 index 00000000..4950e778 --- /dev/null +++ b/provisoning/data/comid-psa-refval-imx-caam.json @@ -0,0 +1,64 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B18", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "aW14OG1wLW9wdGVlLXJhLTAwMDAwMDAwMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "ARoT", + "signer-id": "427zIO3QE+zwfmqkGRPzdhocbmeEF9Bpn5Lk/mBzgf4=" + } + }, + "value": { + "digests": [ + "sha-256;FjIXXyuEaEG2FsLTRZjCck573aBBlnJT0sed9M9nRwA=" + ] + } + }, + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "signer-id": "427zIO3QE+zwfmqkGRPzdhocbmeEF9Bpn5Lk/mBzgf4=" + } + }, + "value": { + "digests": [ + "sha-256;gwmn34vboH/hXniANvQYJ4imFpvOmILM7iPgtNen/YI=" + ] + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/provisoning/data/comid-psa-refval-imx-cpu.json b/provisoning/data/comid-psa-refval-imx-cpu.json new file mode 100644 index 00000000..215faf03 --- /dev/null +++ b/provisoning/data/comid-psa-refval-imx-cpu.json @@ -0,0 +1,64 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B17", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "aW14OG1wLW9wdGVlLXJhLTAwMDAwMDAwMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "ARoT", + "signer-id": "427zIO3QE+zwfmqkGRPzdhocbmeEF9Bpn5Lk/mBzgf4=" + } + }, + "value": { + "digests": [ + "sha-256;FjIXXyuEaEG2FsLTRZjCck573aBBlnJT0sed9M9nRwA=" + ] + } + }, + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "signer-id": "427zIO3QE+zwfmqkGRPzdhocbmeEF9Bpn5Lk/mBzgf4=" + } + }, + "value": { + "digests": [ + "sha-256;E3zh/dMCQdC5VTamh+ipGFfNAeXfQrwOi13izZVxQqE=" + ] + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/provisoning/data/comid-psa-ta-imx-caam.json b/provisoning/data/comid-psa-ta-imx-caam.json new file mode 100644 index 00000000..8aadb392 --- /dev/null +++ b/provisoning/data/comid-psa-ta-imx-caam.json @@ -0,0 +1,44 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "366D0A0A-5988-45ED-8488-2F2A544F6242", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "attester-verification-keys": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "aW14OG1wLW9wdGVlLXJhLTAwMDAwMDAwMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + }, + "instance": { + "type": "ueid", + "value": "AZDHHoAwT5jWVWpALAWTszqArL0I5K/5xAKfbhfhA5lR" + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMKBCTNIcKUSDii11ySs3526iDZ8A\niTo7Tu6KPAqv7D7gS2XpJFbZiItSs3m9+9Ue6GnvHw/GW2ZZaVtszggXIw==\n-----END PUBLIC KEY-----" + } + ] + } + ] + } +} diff --git a/provisoning/data/comid-psa-ta-imx-cpu.json b/provisoning/data/comid-psa-ta-imx-cpu.json new file mode 100644 index 00000000..8aadb392 --- /dev/null +++ b/provisoning/data/comid-psa-ta-imx-cpu.json @@ -0,0 +1,44 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "366D0A0A-5988-45ED-8488-2F2A544F6242", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "attester-verification-keys": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "aW14OG1wLW9wdGVlLXJhLTAwMDAwMDAwMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + }, + "instance": { + "type": "ueid", + "value": "AZDHHoAwT5jWVWpALAWTszqArL0I5K/5xAKfbhfhA5lR" + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMKBCTNIcKUSDii11ySs3526iDZ8A\niTo7Tu6KPAqv7D7gS2XpJFbZiItSs3m9+9Ue6GnvHw/GW2ZZaVtszggXIw==\n-----END PUBLIC KEY-----" + } + ] + } + ] + } +} From 01f3c9ced56bc200972a4d24714a434010efda82 Mon Sep 17 00:00:00 2001 From: Yuichi Sugiyama Date: Tue, 28 Jul 2026 07:04:48 +0000 Subject: [PATCH 7/7] provisioning: document the imx-caam/imx-cpu environments run.sh already accepts any environment with a matching data-file pair, but its comment and error message still listed only qemu/imx, and the READMEs did not mention the two per-build variants added for the CAAM-vs-CPU signing comparison. List them in both READMEs and make the error message enumerate the actually-available environments. --- provisoning/README-j.md | 8 +++++++- provisoning/README.md | 7 ++++++- provisoning/run.sh | 6 ++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/provisoning/README-j.md b/provisoning/README-j.md index 7a1abe5d..1ed9f7b6 100644 --- a/provisoning/README-j.md +++ b/provisoning/README-j.md @@ -83,9 +83,15 @@ veraison clear-stores ./run.sh qemu # i.MX 8M Plus ./run.sh imx +# i.MX 8M Plus のビルド別バリアント(CAAM vs CPU 署名比較用、 +# docs/performance-measurement-j.md 参照) +./run.sh imx-caam +./run.sh imx-cpu ``` -引数を省略した場合は `qemu` が使われます。 +引数を省略した場合は `qemu` が使われます。環境名は +`data/comid-psa-refval-.json` / `data/comid-psa-ta-.json` の +ファイルペアに対応します。 ## 参照値のオフライン計算 diff --git a/provisoning/README.md b/provisoning/README.md index 0b7f802e..5fe72bf1 100644 --- a/provisoning/README.md +++ b/provisoning/README.md @@ -85,9 +85,14 @@ You can automatically execute the above steps by running the following command. ./run.sh qemu # i.MX 8M Plus ./run.sh imx +# i.MX 8M Plus per-build variants (CAAM vs CPU signing comparison, +# see docs/performance-measurement.md) +./run.sh imx-caam +./run.sh imx-cpu ``` -If you omit the argument, `qemu` is used. +If you omit the argument, `qemu` is used. Each environment name maps to a +`data/comid-psa-refval-.json` / `data/comid-psa-ta-.json` pair. ## Computing the Reference Values Offline diff --git a/provisoning/run.sh b/provisoning/run.sh index 6a4f78b7..1394f50b 100755 --- a/provisoning/run.sh +++ b/provisoning/run.sh @@ -6,13 +6,15 @@ VERAISON=${THIS_DIR}/../services/deployments/docker/veraison source ${THIS_DIR}/../services/deployments/docker/env.bash -# Select environment: qemu (default) or imx +# Select environment: qemu (default), imx, or a per-build variant such as +# imx-caam / imx-cpu (any name with a data/comid-psa-{refval,ta}-.json pair) ENV=${1:-qemu} REFVAL_FILE="${THIS_DIR}/data/comid-psa-refval-${ENV}.json" TA_FILE="${THIS_DIR}/data/comid-psa-ta-${ENV}.json" if [[ ! -f "$REFVAL_FILE" ]] || [[ ! -f "$TA_FILE" ]]; then - echo "Error: Unknown environment '$ENV'. Use 'qemu' or 'imx'." + echo "Error: Unknown environment '$ENV'. Available:" + ls "${THIS_DIR}"/data/comid-psa-refval-*.json | sed 's/.*comid-psa-refval-\(.*\)\.json/ \1/' exit 1 fi