SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
C4 — mTLS: ตัวตนของ TLS ที่ยึดกับ OPTIGA

เป้าหมายของหัวข้อนี้

กุญแจส่วนตัวอยู่ที่ใด (อยู่ภายใน Trust M ใน slot ที่มันไม่เคยออกมา) การจับมือ (handshake) ของ TLS บน CM33_NS ไปถึงกุญแจนั้นได้อย่างไร (opaque key ของ PSA → ไดรเวอร์ของ secure element (ชิปนิรภัยแยกส่วน) → trustm_ecdsa_sign) และมีอะไรบ้างที่ถูก patch ในทรีของ ModusToolbox เพื่อให้สิ่งนี้เป็นไปได้ — รวมถึงวิธีนำ patch ทั้ง 11 รายการที่ส่งมากับแพ็กเกจนี้ไปใช้ และวิธียืนยันว่าลงถูกต้อง

ลำดับการทำงานจริงของเฟิร์มแวร์

แผนที่ OID

/* ...context: inside mqtt_mtls_setup() ... */
/* Bootstrap on the Infineon factory pair, as the reference firmware does.
*
* This used to try the device pair (0xE0E1/0xE0F1) first and fall back to
* the factory pair if the read came back empty. That fallback can never
* run: reading an unprovisioned slot does not return zero bytes, it never
* completes, and the wait inside read_certificate_from_optiga() spins
* without a timeout — so a board that has not been enrolled yet hangs
* CM33_NS instead of falling back. Observed 2026-08-04 on a Dev Kit whose
* 0xE0E1 is empty.
*
* official_pse84_trustm_mTLS_tesaiot reads 0xE0E0 unconditionally at boot
* (main.c:493) for the same reason, and tesaiot_select_mqtt_certificate()
* there (tesaiot_optiga_trust_m.c:1349-1374) forces the factory pair even
* when a device cert exists, because after a reset the key in 0xE0F1 may no
* longer match the certificate in 0xE0E1 — which is exactly what happens
* once a CSR has generated a fresh key. Until enrolment installs a matching
* pair and something proves the match, the factory pair is the only one
* that can be trusted to work.
*
* Per the Infineon pre-provisioning map, 0xE0E0/0xE0F0 are the IFX-
* provisioned certificate and key; 0xE0E1/0xE0F1 are TESAIoT's device pair;
* 0xE0E9 holds the TESA CA. */
uint16_t cert_oid = 0xE0E0; /* IFX-provisioned factory certificate */
uint16_t key_oid = 0xE0F0; /* IFX-provisioned factory key */

มีสองคู่ และหนึ่ง CA:

OID เนื้อหา ใครใช้
0xE0E0 / 0xE0F0 ใบรับรอง (certificate) / กุญแจจากโรงงานของ Infineon บทนี้ — ตัวตนของ TLS
0xE0E1 / 0xE0F1 ใบรับรอง / กุญแจของอุปกรณ์ฝั่ง TESAIoT การลงทะเบียน (enrolment) และ Protected Update — บท D2
0xE0E9 TESA CA (trust anchor ของคู่กุญแจฝั่งอุปกรณ์) D2

mqtt_mtls_setup_optiga() เลือก cert_oid = 0xE0E0 และ key_oid = 0xE0F0 หากลงทะเบียนคู่ของ TESAIoT (D2) แล้วต้องการให้ TLS ใช้คู่นั้น นี่คือบรรทัดที่ต้องแก้ ไม่มีอย่างอื่นที่เลือกตัวตนนี้

ใบรับรองจากโรงงานพิสูจน์อะไร และไม่พิสูจน์อะไร ใบรับรองใน 0xE0E0 มี subject เป็น CN=InfineonIoTNode เท่ากันทุกชิ้นบนชิป OPTIGA Trust M ทุกตัว (ถอดรหัสจริงจาก board-factory-cert-cd16334d.pem ด้วย openssl x509 เมื่อ 2026-08-29 ได้ issuer Infineon OPTIGA(TM) Trust M CA 300 อายุ 2025-10-21 ถึง 2045-10-21 ไม่มี SAN และไม่มีฟิลด์ระบุอุปกรณ์) ฟิลด์ที่ต่างกันไปในแต่ละชิปมีเพียง serial number และกุญแจสาธารณะ

สิ่งที่ handshake นี้พิสูจน์จึงคือ อุปกรณ์ปลายทางถือ Trust M ของแท้จาก Infineon เท่านั้น ไม่ได้พิสูจน์ว่าอุปกรณ์นั้นคือเครื่องใด ในขณะเดียวกัน MQTT client id ในโหมด mTLS มาจาก cfg.factory_uid และหัวข้อคำสั่งที่สมัครรับมาจาก cfg.device_id (mqtt_client_config.c:143-144, subscriber_task.c:92-93) ซึ่งทั้งสองค่าเป็นสตริงที่ตั้งได้จาก MicroPython ด้วย tesaiot.config_set(...) และถูกบันทึกลงไฟล์ตั้งค่า (tesaiot_config_store.c:174-176)

ผลที่ตามมาต้องระบุให้ตรง: ในระดับเฟิร์มแวร์ไม่มีสิ่งใดผูกตัวตนที่อุปกรณ์ประกาศเข้ากับใบรับรองที่อุปกรณ์นั้นแสดง การป้องกันที่ชี้ขาดอยู่ที่ฝั่ง broker — ต้องมี ACL ที่อนุญาตหัวข้อ device/{id}/# เฉพาะกับ client ที่ใบรับรองของมันถูกผูกไว้กับ id นั้น (ผูกด้วย fingerprint ของกุญแจสาธารณะหรือ serial number ของใบรับรอง ไม่ใช่ด้วย subject ซึ่งเหมือนกันหมด) ให้ตรวจสอบ ACL นั้นก่อนนำขั้นตอนการลงทะเบียนในบท D2 ไปใช้จริง

การตั้งค่า ตามลำดับ

mqtt_client_config_init() (บท C3) เรียก mqtt_mtls_setup_optiga() เมื่อ tls_mode == TESAIOT_MODE_MTLS ฟังก์ชันนั้น (mqtt_mtls_setup.c:61-310) ทำดังนี้:

  1. จับ touch hold ไว้พร้อมระบุเหตุผล — touch_pause_for("Preparing secure element for mTLS") → ชิปกับตัวควบคุมการสัมผัส (touch) ใช้บล็อก I2C เดียวกัน (บท D1)
  2. optiga_trust_init() แล้วเปิด application
  3. optiga_manager_init()ก่อนงาน TLS ใด ๆ เพราะเส้นทางการลงลายเซ็นของ handshake เริ่มด้วย optiga_manager_lock() ซึ่งล้มเหลวทันทีเมื่อ manager ไม่เคยถูกเริ่มใช้งาน การข้ามขั้นนี้ทำให้ CertificateVerify ล้มเหลวทุกครั้ง:
/* ...context: inside mqtt_mtls_setup() ... */
printf("[mTLS] optiga_manager_init failed — signing would be impossible\n");
return false;
}
  1. อ่านใบรับรองออกมาจาก 0xE0E0 ในรูป PEM แล้วส่งให้ cy_tls_set_client_cert() (บวก +1 สำหรับ NUL และ is_pem = 1)
  2. optiga_psa_register() แล้วตามด้วย psa_crypto_init() — ต้องทำซ้ำที่นี่เพราะการรื้อ TLS ล้างที่เก็บกุญแจของ PSA ทิ้ง (:235-247)
  3. สร้าง attribute ของ opaque key: คู่กุญแจ ECC SECP256R1 ขนาด 256 บิต, PSA_KEY_USAGE_SIGN_HASH, PSA_ALG_ECDSA(PSA_ALG_ANY_HASH), อายุการใช้งานแบบ volatile ที่ PSA_KEY_LOCATION_OPTIGA ((psa_key_location_t)1, optiga_psa_se.c:17) psa_generate_key() ไม่ได้สร้างอะไรขึ้นบนชิปเลย — ด้วย location นั้น มันเพียงลงทะเบียน handle ที่ไดรเวอร์ของ secure element จับคู่เข้ากับ key_oid
  4. cy_tls_set_optiga_key_id(s_psa_key_id) — จุดส่งงานต่อเข้าสู่ TLS stack ที่ผ่านการ patch แล้ว

handshake ไปถึงชิป

ระหว่างการจับมือของ TLS ตัว cy_tls.c ที่ผ่านการ patch จะสร้าง mbedtls_pk แบบ opaque จาก key id แล้วติดตั้งด้วย mbedtls_ssl_conf_own_cert() (cy_tls.c:1755-1809 ใน secure-sockets ที่ผ่านการ patch — ดูข้อควรระวังด้านล่าง) เมื่อฝั่งเซิร์ฟเวอร์ขอ CertificateVerify mbedTLS จะเรียก PSA, PSA ส่งต่อไปยังไดรเวอร์ของ secure element ที่ลงทะเบียนไว้ และ optiga_psa_sign() ของไดรเวอร์ (optiga_psa_se.c:315 ลงทะเบียนที่ :151 และ :235-236) เรียก trustm_ecdsa_sign(map->oid, hash, …) ฟังก์ชันนั้นจับ optiga_manager_lock() แล้วจับ optiga_manager_touch_hold() ตลอดทั้ง transaction (optiga_trust_helpers.c:1665, :1673) — วินัยของบท D1 ที่ถูกใช้จริงทุกครั้งที่เชื่อมต่อ

ชุดรหัสแบบ SHA-384: ไดรเวอร์ตัด hash ให้เหลือ 256 บิตซ้ายสุด (optiga_psa_se.c:332-343) นั่นคือสิ่งที่ทำให้ PSA_ALG_ANY_HASH ใช้งานได้

อะไรถูก patch และอะไรที่ส่งมอบมา

การผูก cy_tls เข้ากับ OPTIGA ไม่ได้อยู่ในเทมเพลต แต่อยู่ใน mtb_shared/ ซึ่งเป็นทรีของบุคคลที่สาม แพ็กเกจนี้จึงส่ง third_party_patches/ มาให้ครบ ประกอบด้วย diff จำนวน 11 รายการใน asset 5 ตัว, แฟ้ม series ที่กำหนดลำดับการลง และ PATCHED.sha256 ซึ่งระบุพาธไฟล์ 11 รายการใต้ mtb_shared/ พร้อมค่า SHA-256 ที่แต่ละไฟล์ต้องมี รวมถึง secure-sockets/…/cy_tls.c, cy_tls_optiga_key.c และ cy_tls_optiga_key.h

การส่ง diff มาด้วยเป็นข้อยุติที่ตัดสินเมื่อ 28 สิงหาคม 2569 บนหลักปฏิบัติเดียวกับที่ Buildroot ใช้กับแพ็กเกจที่ห้ามแจกจ่ายซ้ำ และที่ Infineon เองใช้ในเลเยอร์ meta-freescale กล่าวคือ ตัว patch ถือลิขสิทธิ์ตามงานที่ถูกแก้ไข และหากขาด patch ลูกค้าจะ build ไม่ได้เลย

patch เหล่านั้นถูกนำไปใช้แล้วในขั้นตอน patch ของบท A1/A2 และตรวจสอบด้วย shasum -a 256 -c PATCHED.sha256 หากการตรวจนั้นไม่ผ่าน mTLS จะทำงานไม่ได้: cy_tls_set_optiga_key_id() จะไม่มีอยู่และ build จะล้มเหลวตอนลิงก์ — หรือในกรณีที่ cy_tls.c ที่ยังไม่ได้ patch บังเอิญลิงก์ผ่าน การจับมือจะไม่ใช้ใบรับรองฝั่งไคลเอนต์เลย และ broker จะปฏิเสธ

ทีละขั้น

ขั้นที่ 0 — ยืนยันสถานะของ patch

cd <template>/third_party_patches && shasum -a 256 -c PATCHED.sha256

สิ่งที่ควรสังเกต ทุกบรรทัดต้องขึ้น OK หากพบ FAILED แม้บรรทัดเดียว แสดงว่าทรีนี้ไม่ใช่ทรีที่เฟิร์มแวร์ชุดนี้ถูกสร้างขึ้นมาสำหรับมัน ให้หยุดที่ตรงนี้

ขั้นที่ 1 — สลับ config ไปเป็น mTLS

ตั้ง tls_mode=0 ใน /.tesaiot_config (บท C3) ปล่อย port= ไว้ตามเดิม เพราะไม่ถูกใช้ โหมดเป็นตัวเลือกพอร์ต 8883 เอง

สิ่งที่ควรสังเกต ยังไม่มีอะไร

ขั้นที่ 2 — เชื่อมต่อ

เรียก tesaiot.connect() หรือกดปุ่ม Connect บนหน้าจอ

สิ่งที่ควรสังเกต บน UART พิมพ์จริง ตามลำดับ:

[MQTT] Waiting for WiFi...
[MQTT] WiFi connected
[MQTT] Start request received
[MQTT-Config] Mode=0, Broker=%s:8883, Client=%s, User=%s, PassLen=%u
[mTLS] Setting up OPTIGA Trust M (cert=0xE0E0, key=0xE0F0)
[mTLS] Certificate read: %u bytes PEM
[mTLS] OPTIGA Trust M setup complete (key_id=%lu)
[MQTT] Instance created
[MQTT] Connecting to '%s:8883' as '%s'...
[PSA-Sign] Using Key OID 0xE0F0 for TLS CertificateVerify (slot=%lu)
[MQTT] Connected to broker

ค่า Client= ในบรรทัด [MQTT-Config] ตอนนี้คือ factory UID ไม่ใช่ device_id

บรรทัด [PSA-Sign] (optiga_psa_se.c:364-365) ต้องอ่านอย่างแม่นยำ บรรทัดนี้พิมพ์ ก่อนที่จะพยายามลงลายเซ็น จึงหมายถึง "พยายามลงลายเซ็นด้วย OID ที่คลี่ออกมาได้" ไม่ใช่ "ลงลายเซ็นสำเร็จ" ความสำเร็จยังต้องอาศัยการไม่ปรากฏของบรรทัด ERROR ที่ตามมาในฟังก์ชันเดียวกันด้วย:

[PSA-Sign] ERROR: trustm_ecdsa_sign status=0x%04X
optiga_lib_status_t trustm_ecdsa_sign(optiga_key_id_t oid, const uint8_t *digest, uint16_t digest_len, uint8_t *sig_raw, uint16_t *sig_raw_length)

(optiga_psa_se.c:372) สัญญาณของความสำเร็จคือคู่ต่อไปนี้ — มีบรรทัด Using Key OID ไม่มีบรรทัด ERROR: trustm_ecdsa_sign แล้วตามด้วย [MQTT] Connected to broker บรรทัด ERROR ของ [PSA-Sign] ในอีกสี่รูปแบบ (:328, :346, :352, :359) แปลว่าไดรเวอร์ไปไม่ถึงชิปเลย

บนหน้าจอ: หน้า TESAIoT แสดงว่าเชื่อมต่อแล้ว และในโหมดนี้ยังแสดง optiga_state = PROVISIONED กับ cert_state = LOADED ด้วย (tesaiot_mqtt.c:60-64)

หาก secure-sockets ที่ผ่านการ patch ถูก build โดยนิยาม TLS_DIAG ไว้ (cy_tls.c:168) จะมีอีก 2 บรรทัดปรากฏระหว่างการตั้งค่ากับการลงลายเซ็น: [TLS-DIAG] optiga_key=lu cert_ready=d และ [TLS-DIAG] mbedtls_pk_setup_opaque = d (-0x%04X) ทั้งสองอยู่ในโค้ดที่ผ่านการ patch และปิดไว้เป็นค่าปริยาย มีไว้ก็มีประโยชน์ แต่ไม่จำเป็นต่อการตรวจสอบข้างต้น

ขั้นที่ 3 — พิสูจน์ว่าชิปเป็นผู้ลงลายเซ็น

ถอด I2C ของ Trust M ออก (หรือบนบอร์ดที่มี debug header ให้กดชิปค้างไว้ในสถานะรีเซ็ต) แล้วต่อกลับ

สิ่งที่ควรสังเกต [mTLS] Certificate read จะไม่ปรากฏเลย แต่จะได้อย่างใดอย่างหนึ่งใน 3 บรรทัดนี้แทน: [mTLS] OPTIGA application would not open — aborting mTLS setup, [mTLS] optiga_manager_init failed — signing would be impossible หรือ [mTLS] No usable certificate in OID 0x%04X (got u bytes) (:182, :208, :223) ตามด้วย [MQTT-Config] mTLS OPTIGA setup failed และ [MQTT] Configuration failed — not attempting to connect ไม่มีกุญแจส่วนตัวอยู่ที่ใดในหน่วยความจำแฟลชให้ถอยไปใช้แทน นั่นคือประเด็นทั้งหมด

ขั้นที่ 4 — เชื่อมต่อใหม่หลังการรื้อระบบ

ตัดการเชื่อมต่อจาก broker แล้วเชื่อมต่อใหม่โดยไม่รีบูต

สิ่งที่ควรสังเกต [mTLS] Re-binding existing OPTIGA key (key_id=lu, cert=u bytes) (:107) — หรือหากการรื้อระบบล้างกุญแจของ PSA ไปแล้ว จะได้ [mTLS] PSA key lu no longer exists — the TLS teardown wiped … (:95) ตามด้วยการตั้งค่าใหม่ทั้งชุด ทั้ง 2 แบบเป็นเรื่องปกติ และแบบที่สองคือเหตุผลที่ขั้นที่ 5 ของการตั้งค่าต้องลงทะเบียนไดรเวอร์ใหม่ทุกครั้ง

กับดัก

กับดัก 1 — คิดว่า make getlibs ลง patch ให้เอง
ไม่ลงให้ การเรียก make getlibs ใหม่จะเขียนทับ mtb_shared/ ด้วยของเดิมที่ยังไม่ผ่านการ patch และ gate (ด่านกั้น) ตรวจค่า checksum ของ build จะไม่ยอมคอมไพล์จนกว่าไฟล์ทั้งสิบเอ็ดจะตรงกัน ให้ลง patch ใหม่จาก third_party_patches/ ตามลำดับในแฟ้ม series โดยใช้ patch -p1 -F0 เสมอ แล้วตรวจซ้ำด้วย PATCHED.sha256
กับดัก 2 — อ่าน [PSA-Sign] Using Key OID … ว่าเป็นความสำเร็จ
บรรทัดนี้พิมพ์ก่อนการลงลายเซ็น ความสำเร็จ = บรรทัดนั้น บวก การไม่มี [PSA-Sign] ERROR: trustm_ecdsa_sign status= ตามหลัง บวก [MQTT] Connected to broker
กับดัก 3 — ข้าม optiga_manager_init() ก่อน TLS
คำสั่งแรกของเส้นทางการลงลายเซ็นคือ optiga_manager_lock() ซึ่งคืนค่า false เมื่อ manager ไม่เคยถูกเริ่มใช้งาน CertificateVerify ทุกครั้งจะล้มเหลวด้วยสถานะ hardware failure ของ PSA และ broker จะปิด socket บท D1 อธิบายว่าเหตุใด optiga_chip_enter() จึงไม่สามารถดักจับกรณีนี้ได้
กับดัก 4 — เปิด application ของ OPTIGA ขณะที่ถือ lock ของ manager อยู่
ให้เปิดก่อน แล้วจึงจับ lock (optiga_trust_helpers.c:4604-4622) ฟังก์ชันตั้งค่าทำตามลำดับนี้ ให้รักษาลำดับนี้ไว้หากมีการจัดเรียงอะไรใหม่
กับดัก 5 — cfg.port
ยังคงไม่ถูกใช้ mTLS คือ 8883 ตาม switch ใน mqtt_client_config.c เท่านั้น
กับดัก 6 — คาดหวังว่าคู่กุญแจของ TESAIoT จะถูกใช้
TLS ใช้ 0xE0E0/0xE0F0 (ของโรงงาน) ส่วนคู่ 0xE0E1/0xE0F1 ที่การลงทะเบียนเขียนไว้จะไม่ถูกนำมาใช้ เว้นแต่จะแก้ 2 บรรทัดในแผนที่ OID ผลข้างเคียงที่ตามมาคือใบรับรองที่แสดงต่อ broker ไม่มีฟิลด์ใดระบุอุปกรณ์เลย ดูหัวข้อ "ใบรับรองจากโรงงานพิสูจน์อะไร" ด้านบน

Variant

variant ที่ใช้ได้
mtb-mpy และ mtb-only

libbento_hsm.a ไม่ต้องใช้ symbol ของ MicroPython เลย (variants/mtb-only.mk:14) และ mqtt_mtls_setup.c, optiga_psa_se.c กับ optiga_trust_helpers.c คอมไพล์ได้ทั้งสอง variant สิ่งเดียวในบทนี้ที่เป็นของ mtb-mpy โดยเฉพาะคือจุดเริ่ม tesaiot.connect() ส่วนลำดับการลงทะเบียนไดรเวอร์ PSA ตอนบูต — optiga_psa_register() แล้วจึง psa_crypto_init() — อยู่ที่ proj_cm33_ns/main.c:228-235 เหมือนกันทั้งสอง variant บรรทัดแจ้งความล้มเหลว [BOOT] optiga_psa_register failed: d และ [BOOT] psa_crypto_init failed: d พิมพ์จริง ส่วนความสำเร็จนั้นเงียบ