ยืนยันตัวตนทั้งสองฝั่งด้วย mTLS
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”- ส่ง telemetry ด้วย mTLS โดยใช้ client certificate และ private key ของอุปกรณ์
- เปรียบเทียบ Server-TLS กับ mTLS ในด้านความปลอดภัยและการดูแลกุญแจ
mTLS คืออะไร: อุปกรณ์ต้องพิสูจน์ว่าถือ private key คู่กับใบรับรองของตัวเอง
หัวข้อที่มีชื่อว่า “mTLS คืออะไร: อุปกรณ์ต้องพิสูจน์ว่าถือ private key คู่กับใบรับรองของตัวเอง”mTLS (mutual TLS หรือ TLS สองทาง) เพิ่มขั้นตอนเข้าไปใน handshake ปกติ: หลังจากเซิร์ฟเวอร์ยื่นใบรับรองของตัวเองให้อุปกรณ์ตรวจแล้ว (เหมือน Server-TLS ในบทเรียนก่อน) เซิร์ฟเวอร์จะขอใบรับรองของอุปกรณ์กลับด้วย และอุปกรณ์ต้องพิสูจน์ว่าถือ private key ที่จับคู่กับใบรับรองนั้นจริง (โดยเซ็นข้อมูลบางส่วนของ handshake ด้วยกุญแจนั้น) จุดสำคัญคือ private key ไม่เคยถูกส่งผ่านเครือข่ายเลย มีแต่ “ลายเซ็น” ที่พิสูจน์ว่าอุปกรณ์ถือกุญแจอยู่เท่านั้น — ต่างจาก Server-TLS ที่อุปกรณ์ต้องส่งความลับ (API key หรือ password) ผ่านอุโมงค์ทุกครั้งที่เชื่อมต่อ
ไฟล์ที่ต้องมี และโค้ดตรวจก่อนเชื่อมต่อเสมอ
หัวข้อที่มีชื่อว่า “ไฟล์ที่ต้องมี และโค้ดตรวจก่อนเชื่อมต่อเสมอ”ตัวอย่างนี้ (device-mtls) ต้องมีไฟล์ client_cert.pem และ client_key.pem อยู่ใน certs_credentials/ เสมอ main.c เช็กด้วย file_exists(crt) && file_exists(key) ก่อนเริ่มส่งข้อมูลทุกครั้ง ถ้าไม่ครบจะพิมพ์ error แล้วออกจากโปรแกรมทันที ไม่มีการลองเชื่อมต่อแบบไม่มีใบรับรอง เมื่อไฟล์ครบ โค้ดตั้ง tls.client_cert = crt; tls.client_key = key; และยังคงเปิด tls.verify_peer = 1U เหมือนบทเรียนก่อน — อุปกรณ์ยังตรวจเซิร์ฟเวอร์เหมือนเดิม เพิ่มเติมคือต้องยื่นใบรับรองของตัวเองด้วย
อุปกรณ์แบบ CSR: กุญแจไม่เคยออกจากเครื่องที่สร้างมันเลย
หัวข้อที่มีชื่อว่า “อุปกรณ์แบบ CSR: กุญแจไม่เคยออกจากเครื่องที่สร้างมันเลย”สำหรับอุปกรณ์ที่ขอใบรับรองผ่าน CSR (Certificate Signing Request) README ของตัวอย่างระบุว่า bundle ที่ดาวน์โหลดจาก Admin Portal จะไม่มี private key รวมมาด้วย เพราะกุญแจถูกสร้างขึ้นที่ฝั่งอุปกรณ์เองตอนสร้าง CSR (ด้วย scripts/generate_csr.sh ที่เรียก openssl แล้ว chmod 600 ทันที) แพลตฟอร์มได้รับแค่ CSR ที่มี public key ไปลงนามเป็นใบรับรอง กุญแจส่วนตัวจึงไม่เคยเดินทางออกจากเครื่องที่สร้างมันเลย ถึง bundle ที่ดาวน์โหลดจะรั่วก็ไม่ทำให้กุญแจรั่วตามไปด้วย ผู้ใช้ต้องคัดลอกกุญแจนี้มาวางเป็น certs_credentials/client_key.pem เองก่อนใช้งาน (มีสคริปต์ช่วย sync_csr_key.sh <DEVICE_ID>)
CA ที่ใช้ตรวจเซิร์ฟเวอร์ยังเป็น system trust เหมือนเดิม ไม่ใช้ ca-chain.pem ของ bundle
หัวข้อที่มีชื่อว่า “CA ที่ใช้ตรวจเซิร์ฟเวอร์ยังเป็น system trust เหมือนเดิม ไม่ใช้ ca-chain.pem ของ bundle”ต่างจากบทเรียนก่อนที่สาขา MQTTS ใช้ ca-chain.pem ของอุปกรณ์ตรวจ broker ตัวอย่าง mTLS นี้ตั้ง tls.ca_chain = NULL; ในทั้งสองสาขา (HTTPS และ MQTTS) พร้อมคอมเมนต์ในซอร์สว่าให้ใช้ system trust และห้ามบังคับใช้ CA ของอุปกรณ์เอง เพราะปลายทางทั้งสองยังใช้ใบรับรองจาก public CA เหมือนเดิม สิ่งที่ mTLS เพิ่มเข้ามาไม่ใช่วิธีตรวจเซิร์ฟเวอร์ (เหมือนเดิมทุกประการ) แต่คือการที่อุปกรณ์ต้องยื่นใบรับรองของตัวเองกลับไปด้วย
พอร์ตของ mTLS ต่างจาก Server-TLS
หัวข้อที่มีชื่อว่า “พอร์ตของ mTLS ต่างจาก Server-TLS”แพลตฟอร์มแยก listener ของ mTLS ออกจาก Server-TLS ชัดเจน mTLS ใช้ HTTPS พอร์ต 9444 และ MQTTS พอร์ต 8883 (ตามค่า default ใน config.h: DEFAULT_API_BASE_URL ลงท้ายด้วย :9444, DEFAULT_MQTT_PORT = 8883U) ขณะที่ Server-TLS ใช้ 443 และ 8884 README เตือนไว้ว่าถ้าส่ง HTTPS mTLS ไปที่พอร์ต 443 แทน 9444 ปลายทางจะไม่ใช่ endpoint ของ mTLS และจะเจอปัญหา SAN/โดเมนไม่ตรงตามที่คาด
เปรียบเทียบ Server-TLS กับ mTLS: จุดแลกเปลี่ยนด้านความปลอดภัยกับภาระดูแลกุญแจ
หัวข้อที่มีชื่อว่า “เปรียบเทียบ Server-TLS กับ mTLS: จุดแลกเปลี่ยนด้านความปลอดภัยกับภาระดูแลกุญแจ”ทั้งสองแบบเข้ารหัสช่องทางเท่ากันและอุปกรณ์ตรวจเซิร์ฟเวอร์เหมือนกัน (verify_peer = 1 เสมอ) ความต่างอยู่ที่วิธีพิสูจน์ตัวตนของอุปกรณ์ Server-TLS เริ่มต้นง่ายกว่าเพราะแค่มีความลับหนึ่งค่า แต่ความลับนั้นเดินทางผ่านเครือข่ายทุกครั้งที่เชื่อมต่อ (แม้จะอยู่ในอุโมงค์ที่เข้ารหัสแล้วก็ตาม) และถ้าความลับหลุด ผู้โจมตีก็ปลอมเป็นอุปกรณ์ได้ทันทีโดยไม่ต้องมีอะไรเพิ่ม ส่วน mTLS ไม่มีความลับเดินทางผ่านเครือข่ายระหว่างพิสูจน์ตัวตนเลย แต่แลกกับภาระดูแลใบรับรองและกุญแจ: ต้องมีระบบออกใบรับรอง ต่ออายุก่อนหมดอายุ และเก็บ private key ให้ปลอดภัย — ถ้า private key ของอุปกรณ์หนึ่งรั่ว ผู้โจมตีปลอมเป็นอุปกรณ์ตัวนั้นได้จนกว่าใบรับรองจะถูกเพิกถอนและออกคู่กุญแจใหม่ แต่ผลกระทบจำกัดอยู่ที่อุปกรณ์ตัวนั้นเพราะแต่ละตัวมีใบรับรองและ ACL ของ topic เป็นของตัวเอง (device/<device_id>/…) การไม่ให้กุญแจออกจากอุปกรณ์เลยตั้งแต่แรก อย่างที่ทำด้วย secure element ใน OPTIGA Trust M (บทเรียน 5.3) คือทางลดความเสี่ยงนี้ไปอีกขั้น
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”ก่อนรันตัวอย่าง (ตรวจเมื่อ 26 ก.ย. 2026):
config.hของตัวอย่างที่ commit นี้ยังตั้งค่าเริ่มต้นเป็นโดเมนเดิมของแพลตฟอร์มที่ลงท้ายด้วย .com ซึ่งย้ายไปเป็น tesaiot.dev แล้ว โดเมนเดิมของ API ไม่ resolve แล้ว ส่วนของ MQTT ยังใช้ได้ชั่วคราว ให้ตั้งDEFAULT_API_BASE_URLเป็นhttps://tesaiot.dev:9444และDEFAULT_MQTT_HOSTเป็นmqtt.tesaiot.dev(บันทึกไว้ที่ developer-hub issue #3)
ตัวอย่างนี้เป็นภาษา C ที่รันบนคอมพิวเตอร์ก่อน (GCC/Clang + OpenSSL, mbedTLS หรือ wolfSSL หรือ libcurl) เพื่อให้เห็นโปรโตคอลชัด แล้วจึงนำแนวคิดเดียวกันไปใช้บนบอร์ด โค้ดด้านล่างคัดลอกจากไฟล์จริง (Apache-2.0, tesaiot/developer-hub, commit d2ed42c)
main.c — ต้องมีทั้งใบรับรองและกุญแจของอุปกรณ์ก่อนเริ่มทำงาน:
char ca[512], crt[512], key[512]; join_path(ca, sizeof(ca), certs_dir, FILE_CA_CHAIN); join_path(crt, sizeof(crt), certs_dir, FILE_CLIENT_CERT); join_path(key, sizeof(key), certs_dir, FILE_CLIENT_KEY); if (!(file_exists(crt) && file_exists(key))) { (void)fprintf(stderr, "mTLS requires client_cert.pem and client_key.pem in %s\n", certs_dir); return 1; }main.c — TLS conf ผูกใบรับรอง/กุญแจของอุปกรณ์ ยังตรวจเซิร์ฟเวอร์เหมือนเดิม:
iot_tls_conf_t tls; (void)memset(&tls, 0, sizeof(tls)); tls.client_cert = crt; tls.client_key = key; tls.verify_peer = 1U; tls.connect_timeout_ms = (uint32_t)(HTTP_CONNECT_TIMEOUT_SEC * 1000L); tls.total_timeout_ms = (uint32_t)(HTTP_TOTAL_TIMEOUT_SEC * 1000L);main.c — สาขา MQTTS: ไม่มี username/password เลย เพราะใบรับรองทำหน้าที่พิสูจน์ตัวตนแทน:
if (is_mode_mqtts() != 0) { /* Send via MQTTS (mTLS) */ /* ... */ tls.ca_chain = NULL; tls.sni_name = mqtt_host; char topic[MAX_TOPIC_SIZE]; (void)snprintf(topic, sizeof(topic), "device/%s/telemetry", device_id); iot_mqtt_req_t mreq; (void)memset(&mreq, 0, sizeof(mreq)); mreq.host = mqtt_host; mreq.port = (uint16_t)mqtt_port; mreq.client_id = device_id; mreq.username = NULL; mreq.password = NULL; mreq.topic = topic; mreq.payload = json_buf; mreq.payload_len = strlen(json_buf); mreq.qos = 1U; mreq.retain = 0U; mreq.keepalive_sec = 30U; mreq.timeout_ms = (uint32_t)(HTTP_TOTAL_TIMEOUT_SEC * 1000L);- README ของตัวอย่าง · โฟลเดอร์โค้ด · commit
d2ed42c - ต้องมี credential ของอุปกรณ์จาก TESAIoT Platform ตามขั้นตอนใน README ห้ามนำ credential จริงขึ้น repo สาธารณะ
จุดที่มักพลาด
หัวข้อที่มีชื่อว่า “จุดที่มักพลาด”- ลืมว่า bundle ของอุปกรณ์แบบ CSR ไม่มี private key มาด้วย — ถ้าเห็นข้อความ error ว่าไม่มีกุญแจ ต้องรัน
./scripts/sync_csr_key.sh <DEVICE_ID>เพื่อคัดลอกกุญแจจากเครื่องที่สร้าง CSR เอง ไม่ใช่ไปขอกุญแจใหม่จากแพลตฟอร์ม เพราะแพลตฟอร์มไม่เคยมีกุญแจนี้ตั้งแต่แรก - ใช้พอร์ตของ Server-TLS กับ credential แบบ mTLS หรือกลับกัน — mTLS ใช้ 9444 (HTTPS) และ 8883 (MQTTS) ถ้าไปที่ 443 หรือ 8884 ปลายทางจะไม่ใช่ endpoint ของ mTLS และจะเจอปัญหา SAN/โดเมนไม่ตรง
- คิดว่า mTLS ทำให้ไม่ต้องตรวจใบรับรองของเซิร์ฟเวอร์อีกต่อไป —
tls.verify_peer = 1Uยังเปิดอยู่เหมือน Server-TLS ทุกประการ mTLS แค่เพิ่มการที่อุปกรณ์ต้องพิสูจน์ตัวเองด้วย ไม่ได้ลดการตรวจฝั่งเซิร์ฟเวอร์ลงเลย - เข้าใจว่า Server-TLS กับ mTLS ใช้
X-API-KEYเหมือนกัน — mTLS ไม่ใช้ API key เลย (hreq.api_key = NULL) ถ้าเจอ HTTPS ตอบรหัสผิดพลาดในโหมด mTLS ต้องตรวจใบรับรอง/กุญแจของอุปกรณ์ ไม่ใช่ไปตรวจ API key
- ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
- แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
- ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”- mTLS เพิ่มอะไรจาก Server-TLS
- ถ้า private key ของอุปกรณ์รั่ว ผลกระทบคืออะไร
คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- ตัวอย่างบน Developer Hub
- ตัวอย่างอยู่ใน tesaiot/developer-hub (Apache-2.0) และอ้างอิงด้วยลิงก์
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
mTLS เพิ่มอะไรจาก Server-TLS (เป้าหมายข้อ 2)
- เข้ารหัสข้อมูลแรงขึ้น
- ไม่ต้องตรวจใบรับรองของเซิร์ฟเวอร์อีก
- ใช้ API key สองชั้น
- ระหว่าง handshake เซิร์ฟเวอร์ขอใบรับรองของอุปกรณ์ และอุปกรณ์ต้องพิสูจน์ว่าถือ private key คู่กับใบรับรองนั้น จึงไม่ต้องส่งรหัสผ่านหรือ API key
ดูเฉลย
คำตอบ: D. ระหว่าง handshake เซิร์ฟเวอร์ขอใบรับรองของอุปกรณ์ และอุปกรณ์ต้องพิสูจน์ว่าถือ private key คู่กับใบรับรองนั้น จึงไม่ต้องส่งรหัสผ่านหรือ API key
ใน main.c ของ mTLS tls.client_cert และ tls.client_key ชี้ไปยังไฟล์ของอุปกรณ์ ส่วน username และ password เป็น NULL และ verify_peer ยังเป็น 1 อุปกรณ์จึงยังตรวจเซิร์ฟเวอร์เหมือนเดิม ความแรงของการเข้ารหัสช่องทางไม่ได้ต่างกัน
-
ทำไม bundle ของอุปกรณ์ที่สร้างจาก CSR จึงไม่มี private key มาด้วย (เป้าหมายข้อ 1)
- เพราะไฟล์ใหญ่เกินไป
- เพราะกุญแจถูกสร้างฝั่งอุปกรณ์ตอนทำ CSR และไม่เคยออกจากฝั่งนั้น แพลตฟอร์มได้แค่ CSR ที่มี public key ไปลงนาม bundle ที่รั่วจึงไม่ทำให้กุญแจรั่ว
- เพราะแพลตฟอร์มลืมใส่ ต้องขอเพิ่ม
- เพราะ mTLS ไม่ใช้ private key
ดูเฉลย
คำตอบ: B. เพราะกุญแจถูกสร้างฝั่งอุปกรณ์ตอนทำ CSR และไม่เคยออกจากฝั่งนั้น แพลตฟอร์มได้แค่ CSR ที่มี public key ไปลงนาม bundle ที่รั่วจึงไม่ทำให้กุญแจรั่ว
scripts/generate_csr.sh สร้างกุญแจ (ค่าเริ่มต้น ECDSA) ด้วย openssl แล้ว chmod 600 ก่อนสร้าง CSR README เรียกสิ่งนี้ว่า security requirement และให้นำกุญแจจากเครื่องที่สร้างมาวางเองก่อนใช้งาน ถ้าไม่มีไฟล์กุญแจ โปรแกรมจะหยุดพร้อมข้อความ mTLS requires client_cert.pem and client_key.pem
-
ส่ง HTTPS แบบ mTLS ไปที่พอร์ต 443 แทน 9444 ปัญหาที่ README เตือนไว้คืออะไร (เป้าหมายข้อ 1)
- ไม่มีปัญหา แค่ช้ากว่า
- ต้องใส่ X-API-KEY เพิ่ม
- ปลายทางไม่ใช่ endpoint ของ mTLS: README ระบุว่า ingest แบบ mTLS ต้องใช้ :9444 ไม่ใช่ 443 และเตือนเรื่อง SAN/โดเมนไม่ตรง พอร์ต 443 เป็นของ Server-TLS ที่ใช้ API key
- ใบรับรองของอุปกรณ์หมดอายุทันที
ดูเฉลย
คำตอบ: C. ปลายทางไม่ใช่ endpoint ของ mTLS: README ระบุว่า ingest แบบ mTLS ต้องใช้ :9444 ไม่ใช่ 443 และเตือนเรื่อง SAN/โดเมนไม่ตรง พอร์ต 443 เป็นของ Server-TLS ที่ใช้ API key
แพลตฟอร์มแยก listener ตามวิธีพิสูจน์ตัวตน mTLS ใช้ HTTPS 9444 และ MQTTS 8883 ส่วน Server-TLS ใช้ HTTPS 443 และ MQTTS 8884 เมื่อเชื่อมผิดพอร์ต ปลายทางจะไม่ขอหรือไม่ตรวจใบรับรองของอุปกรณ์ตามที่คาด การแก้ที่ถูกคือแก้ปลายทาง ไม่ใช่ปิดการตรวจ TLS
-
ถ้า private key ของอุปกรณ์หนึ่งรั่ว ผลกระทบคืออะไร (เป้าหมายข้อ 2)
- ผู้โจมตีปลอมเป็นอุปกรณ์ตัวนั้นได้ (เชื่อมต่อและ publish ในนามของมัน) จนกว่าใบรับรองจะถูกเพิกถอนและออกคู่กุญแจใหม่ ผลจำกัดอยู่ที่อุปกรณ์ตัวนั้น เพราะแต่ละตัวมีใบรับรองและ topic ของตัวเอง
- อุปกรณ์ทุกตัวบนแพลตฟอร์มถูกปลอมได้
- ไม่มีผล เพราะยังต้องมี CA certificate
- ผู้โจมตีถอดรหัสข้อมูลของอุปกรณ์อื่นได้
ดูเฉลย
คำตอบ: A. ผู้โจมตีปลอมเป็นอุปกรณ์ตัวนั้นได้ (เชื่อมต่อและ publish ในนามของมัน) จนกว่าใบรับรองจะถูกเพิกถอนและออกคู่กุญแจใหม่ ผลจำกัดอยู่ที่อุปกรณ์ตัวนั้น เพราะแต่ละตัวมีใบรับรองและ topic ของตัวเอง
ใบรับรองผูกกับ public key ของอุปกรณ์ ใครถือ private key คู่กันก็ผ่าน handshake ได้ ACL จำกัด topic เป็น device/<device_id>/… จึงจำกัดความเสียหาย การไม่ให้กุญแจออกจากชิปเลย (OPTIGA Trust M ในบทเรียน 5.3) คือทางลดความเสี่ยงนี้
-
เปรียบเทียบ Server-TLS กับ mTLS ข้อใดถูก (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 2)
- Server-TLS: อุปกรณ์ต้องเก็บรหัสผ่านหรือ API key เป็นความลับ และส่งมันในอุโมงค์ TLS ทุกครั้งที่เชื่อมต่อ
- mTLS: ไม่มีความลับถูกส่งผ่านเครือข่ายระหว่างพิสูจน์ตัวตน อุปกรณ์แค่แสดงว่าถือกุญแจ
- mTLS ทำให้อุปกรณ์ไม่ต้องตรวจใบรับรองของเซิร์ฟเวอร์
- mTLS มีภาระดูแลกุญแจ: ใบรับรองหมดอายุ ต้องต่ออายุหรือหมุนเวียน และต้องเก็บ private key ให้ปลอดภัย
- Server-TLS เข้ารหัสข้อมูลได้น้อยกว่า mTLS
ดูเฉลย
คำตอบ: A. Server-TLS: อุปกรณ์ต้องเก็บรหัสผ่านหรือ API key เป็นความลับ และส่งมันในอุโมงค์ TLS ทุกครั้งที่เชื่อมต่อ · B. mTLS: ไม่มีความลับถูกส่งผ่านเครือข่ายระหว่างพิสูจน์ตัวตน อุปกรณ์แค่แสดงว่าถือกุญแจ · D. mTLS มีภาระดูแลกุญแจ: ใบรับรองหมดอายุ ต้องต่ออายุหรือหมุนเวียน และต้องเก็บ private key ให้ปลอดภัย
ทั้งสองโหมดเข้ารหัสช่องทางเท่ากัน และอุปกรณ์ตรวจเซิร์ฟเวอร์เหมือนกัน (verify_peer = 1) ความต่างอยู่ที่วิธีพิสูจน์ตัวตนของอุปกรณ์ Server-TLS เริ่มง่ายแต่ความลับเดินทางทุกครั้ง mTLS แข็งแรงกว่าแต่ต้องมีระบบออกและต่ออายุใบรับรอง
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"ยืนยันตัวตนทั้งสองฝั่งด้วย mTLS" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Mutual authentication with mTLS" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/tesaiot-firmware-stack/m05-connect-to-platform/l02-mtls/
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/d2ed42c4a31232f553b6b8cef9ee7373db348c21/examples/embedded-devices/intermediate/device-mtls · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA