Wiznet makers

Lihan__

Published August 03, 2026 ©

84 UCC

8 WCC

3 VAR

0 Contests

0 Followers

0 Following

Original Link

trustzone_tropic01_test

.

COMPONENTS
PROJECT DESCRIPTION

TrustZone × TROPIC01 × W5500 — Splitting a Cortex-M33 in Two, and Giving Each Half Its Own Hardware Engine

#TrustZone #CortexM33 #STM32L552 #SecureElement #TROPIC01 #W5500 #TOE #Ed25519 #libtropic #HardwareRootOfTrust #GTZC

📚 Context: Security research / evaluation project — Arm TrustZone-M partitioning combined with a tamper-resistant secure element and a hardwired TCP/IP controller. ✅ Verification status: Both worlds proven on real silicon. Secure world produces a live Ed25519 signature verified against a TROPIC01-generated key; Non-secure world completes DHCP → DNS → TCP → HTTP/1.0 200 OK against a live server. Full UART logs published.


01 — What is this project?

What is TrustZone? An Arm hardware security technology that physically splits one MCU into a Secure World and a Non-secure World. Non-secure code cannot access memory or peripherals marked Secure — the hardware blocks it, not a software rule. So even if the Non-secure side is fully compromised, it never had a path to the Secure side's data (keys, etc.) in the first place.

What is a Secure Element Chip? A separate, tamper-resistant chip dedicated to storing cryptographic keys and performing crypto operations. Unlike a key stored in ordinary MCU flash, a key inside a secure element is generated and used entirely within that chip — it never leaves, and no amount of software access to the host MCU can extract it. Physical tampering (probing, decapsulation) is also actively resisted by the chip's own hardware defenses.

Most "secure IoT device" projects stop at one of two half-measures. Either they store keys in MCU flash and hope nobody attaches a debug probe, or they bolt on a secure element but still run the crypto calls from the same undifferentiated firmware blob that also parses untrusted network packets. Both approaches share the same flaw: there is no architectural wall between the code that holds the secret and the code that talks to the internet.

This project builds that wall — and then equips both sides of it properly.

Running on a NUCLEO-L552ZEQ (STM32L552, Arm Cortex-M33), the firmware uses Arm TrustZone for Cortex-M to partition a single MCU into two isolated execution environments:

  • Secure World — owns SPI1, the on-chip RNG, and the UART. It drives a TROPIC01 tamper-resistant secure element through the libtropic SDK, using TrezorCrypto as the Cryptographic Abstraction Layer. Here it establishes an encrypted session with the SE, pulls hardware entropy, generates an Ed25519 keypair inside the secure element, signs a message with a private key that never leaves the chip, and verifies the signature with ed25519-donna.
  • Non-secure World — owns SPI2 and nothing security-critical. It runs the ordinary user application: bring up Ethernet, obtain an address via DHCP, configure DNS, open a TCP socket, and perform an HTTP GET. All of this over a WIZnet W5500.

The result is a working demonstration of a principle that is easy to state and hard to build: the network stack and the private key should not be able to reach each other. On this board, they physically cannot — they sit on different SPI buses, in different security states, enforced by the GTZC peripheral firewall and the SAU.


02 — Why TrustZone + a Secure Element?

🔷 TrustZone alone does not resist physical attack

TrustZone-M is a logical isolation mechanism. It is excellent at stopping a buffer overflow in the HTTP parser from reading the signing key. It does nothing about an attacker with a soldering iron, a glitch injector, or a decapsulation lab. Keys stored in secure-partition flash are still keys stored in flash.

🔷 A secure element alone does not resist software attack

Conversely, a secure element protects the key at rest and during use — but if the only thing standing between an attacker-controlled TCP buffer and the sign() call is ordinary C code in a flat address space, the attacker doesn't need the key. They just need to make the SE sign whatever they want.

🔷 Together, they close both doors

This project deliberately combines them:

ThreatMitigated by
Remote code execution in network stack reads the keyTrustZone — key material is never mapped into the non-secure address space
Attacker desolders flash and dumps itTROPIC01 — the private key is generated in and never leaves the SE
Attacker glitches the MCU to skip an auth checkTROPIC01 secure session — the host↔SE channel is encrypted and authenticated by pairing key
Malicious peripheral access from non-secure codeGTZC/TZSC — SPI1, RNG, USART2, UART5 are hardware-locked to Secure

The GTZC configuration is explicit in the code: HAL_GTZC_TZSC_ConfigPeriphAttributes() marks SPI1, RNG, USART2, UART5, VREFBUF and ICACHE as GTZC_TZSC_PERIPH_SEC, while MPCBB descriptors carve SRAM1 and SRAM2 into secure and non-secure blocks. Non-secure code attempting to touch the TROPIC01 SPI bus does not get a wrong answer — it gets a SecureFault.

🔷 And this is exactly where the network chip choice becomes a security decision

Once the partition exists, every kilobyte of code in the non-secure world is attack surface, and every kilobyte in the secure world is trusted computing base. A conventional MAC+PHY design would force a full software TCP/IP stack — LwIP or equivalent — into one of those two partitions. Neither option is comfortable. That constraint is what makes the W5500 the interesting part of this design, not merely a convenient part.


03 — System architecture

Execution order is meaningful: the Secure World completes its entire cryptographic sequence — session, RNG, key generation, signing, verification, session teardown — and only then calls NonSecure_Init() to hand control to the application world. The secure boot path never depends on the network being up.


04 — Why WIZnet W5500? ⭐

🔷 The technical core: a network stack that isn't code

The W5500 implements TCP, UDP, IPv4, ICMP, ARP, IGMP and PPPoE in hardware, exposing 8 independent sockets with 32 KB of internal TX/RX buffer over a simple SPI interface. On this board the firmware allocates it evenly — uint8_t memsize[2][8] = {{2,2,...},{2,2,...}} — 2 KB TX and 2 KB RX per socket.

In an ordinary project that's a convenience. In a TrustZone project it is an architectural asset, and here is why.

TrustZone forces the developer to answer a question that flat-address-space firmware never has to face: which partition does the TCP/IP stack live in?

  • Put LwIP in the Secure World → tens of thousands of lines of packet-parsing code, fed directly by hostile input from the network, become part of the Trusted Computing Base sitting next to the signing key. This defeats the entire point of the partition.
  • Put LwIP in the Non-secure World → architecturally correct, but now the non-secure partition must absorb a stack that typically costs on the order of tens of KB of flash and a heap of RAM buffers, on a device whose 512 KB flash / 256 KB SRAM has already been split in two by the SAU.

The W5500 dissolves the dilemma. The TCP/IP stack does not live in either partition — it lives in a separate chip. What remains in the non-secure world is a thin driver: wizchip_port.c is 192 lines, and the whole ioLibrary footprint is socket.c, w5500.c, dhcp.c, dns.c and wizchip_conf.c. That is the entire networking attack surface inside the MCU.

The security consequence is sharp: hostile packet parsing happens outside the security perimeter entirely. A malformed TCP segment from the network is processed by the W5500's fixed-function logic, not by a C state machine running inside a TrustZone partition. There is no "TCP reassembly bug in the non-secure world" to pivot from, because there is no TCP reassembly code in the MCU at all.

🔷 Which W5500 mode does this project use? — TOE TCP socket mode (Sn_MR_TCP)

Confirmed directly in NonSecure/Core/Src/main.c:

ret = socket(sock, Sn_MR_TCP, 50000, 0);   // TOE TCP mode
ret = connect(sock, server_ip, server_port);

The HTTP client opens a TCP (TOE) socket on local port 50000 and connects out to the server — full hardware offload of handshake, sequencing, retransmission, ACK and window management. The firmware only writes an HTTP request string and reads the response.

The project simultaneously exercises the W5500's UDP path, since ioLibrary's DHCP and DNS clients open UDP sockets underneath:

SocketModeRoleBuffer
0TCP (TOE) Sn_MR_TCPHTTP GET client, local port 500002 KB / 2 KB
6UDP Sn_MR_UDP (via DNS_init)DNS resolution512 B app buffer
7UDP Sn_MR_UDP (via DHCP_init)DHCP DISCOVER/OFFER/REQUEST/ACK548 B app buffer

Three of the eight hardware sockets are in use across two different transport modes, concurrently, with no protothread scheduler and no stack to arbitrate them. The chip does the arbitration.

🔷 Compared with the alternatives

ApproachTCP/IP stack locationNon-secure attack surfaceFit with TrustZone
W5500 (this project)Inside the network chipThin SPI driver (~192-line port layer)✅ Stack is outside the MCU entirely
MAC + LAN8742A PHY + LwIPInside one MCU partitionFull LwIP parsing hostile input⚠️ Must be non-secure; large TCB-adjacent surface
ENC28J60 + software stackInside one MCU partitionFull stack plus MAC-level driver work❌ Worst of both — more code, less offload
Wi-Fi module w/ AT commandsInside moduleParser for AT/serial protocol⚠️ Offloads stack but adds a fragile text protocol

There is a second, quieter advantage. STM32L552 does not have an integrated Ethernet MAC at all. Reaching wired Ethernet on this exact MCU essentially requires an external controller — and among external controllers, the W5500 is the only common option that also removes the software stack from the equation. The chip that made Ethernet possible here is the same chip that made the security architecture clean. That is not a coincidence a designer gets often.

🔷 Verified evidence ✅

From the published Non-secure UART log — every stage of W5500_Init() and HTTP_Test() confirmed on hardware:

WIZCHIP Initialized              ← ctlwizchip(CW_INIT_WIZCHIP) OK, VERSIONR == 0x04
Checking Link Status..
Link: DOWN Retrying : 0/1/2
Link: UP                         ← CW_GET_PHYLINK polling loop
Using DHCP.. Please Wait..
DHCP IP assigned successfully    ← socket 7, UDP
Configuring DNS..                ← socket 6, UDP
IP: 10.14.1.114  SUBNET: 255.255.0.0
GATEWAY: 10.14.11.30  DNS: 10.14.11.1
Opening socket... Connecting... Connected   ← socket 0, Sn_MR_TCP
HTTP/1.0 200 OK
hello from python server.
HTTP test done

The driver even validates chip identity before proceeding — getVERSIONR() must return 0x04, otherwise initialization aborts with an explicit error. The link-up wait is a bounded 10-retry × 500 ms loop, and DHCP failure falls back to a static wiz_NetInfo rather than hanging. This is defensive driver code, not a happy-path demo.


04.5 — What TrustZone Actually Implements Here

It's worth being precise about scope: this project implements hardware-enforced isolation, not yet cross-boundary secure services. Here is exactly what's in place today.

✅ Implemented

FeatureMechanismWhere in code
World separationSAU enabled, ALLNS = 0 — everything is Secure unless explicitly carved out as Non-securepartition_stm32l552xx.h
Peripheral lockdownSPI1, RNG, USART2, UART5, VREFBUF, ICACHE forced to GTZC_TZSC_PERIPH_SECMX_GTZC_S_Init(), Secure/Core/Src/main.c
The critical asymmetrySPI1 (→ TROPIC01) is Secure-locked; SPI2 (→ W5500) is left Non-secureSame function — this is the load-bearing design decision
Memory partitioningSRAM1/SRAM2 split into Secure/Non-secure blocks via MPCBB descriptorsHAL_GTZC_MPCBB_ConfigMem()
NSC entry point (scaffold)SECURE_RegisterCallback() — registers fault/error callbacks across the boundarysecure_nsc.c

⚠️ Not yet implemented

  • No custom cross-boundary service call. The only NSC-callable function present is SECURE_RegisterCallback(), which is the stock ST TrustZone template — it registers SecureFault/GTZC-error callbacks and is not project-specific logic.
  • The two worlds run independently, not together. Secure World completes its full crypto sequence, then hands off to Non-secure World, which runs its own HTTP test. Nothing in the Non-secure application currently requests a TROPIC01 signature across the boundary — the isolation is proven, but a signing veneer callable from Non-secure code has not been built yet.

In short: the wall is real and hardware-enforced — SPI1 and SPI2 sit on opposite sides of it, provably. What doesn't exist yet is a door in that wall for the Non-secure network code to ask the Secure world to sign something on its behalf. That's the natural next milestone (see Conclusion).


05 — Key components

🌐 WIZnet W5500 — TOE TCP (Sn_MR_TCP) + UDP for DHCP/DNS, on SPI2, Non-secure

Hardwired TCP/IP offload controller with 8 sockets and 32 KB internal buffer. Driven here through WIZnet's official ioLibrary (socket.c, wizchip_conf.c, w5500.c, plus the DHCP/ and DNS/ application modules) with a custom 192-line wizchip_port.c supplying:

  • W5500_Select() / W5500_Unselect() → CS GPIO, registered via reg_wizchip_cs_cbfunc()
  • W5500_ReadByte() / W5500_WriteByte()HAL_SPI_TransmitReceive() on SPI2, registered via reg_wizchip_spi_cbfunc()
  • Hardware reset sequence: RESET low 50 ms → high 200 ms
  • MAC AA:BB:CC:DD:EE:FF, NETINFO_DHCP with static fallback

Crucially, SPI2 is left non-secure while SPI1 is locked to Secure by GTZC. The security state of the network interface is enforced by the MCU's peripheral firewall, not by convention.

🔒 TROPIC01 Secure Element — on SPI1, Secure-only

Tamper-resistant secure element accessed via the libtropic SDK. Verified operations from the Secure-world log: handle init, lt_reboot(), lt_verify_chip_and_start_secure_session() on pairing key slot 0, lt_ping() echo test, lt_random_value_get() returning 32 bytes of hardware entropy, lt_ecc_key_generate() on ECC slot 0 with TR01_CURVE_ED25519, lt_ecc_key_read(), lt_ecc_eddsa_sign() producing a 64-byte signature, then clean lt_session_abort() / lt_deinit().

A nice practical detail: the code handles a non-empty key slot gracefully — it detects the occupied slot, calls lt_ecc_key_erase(), and retries generation. The published log captures exactly that path executing on a second boot.

🧮 TrezorCrypto — Cryptographic Abstraction Layer

libtropic's CAL is backed by TrezorCrypto (ed25519-donna, aes, chacha20poly1305, monero). Host-side verification uses ed25519_sign_open() against the public key read back from the SE — so the signature is independently checked, not merely trusted because the SE said OK.

🛡️ STM32L552 GTZC / SAU — the enforcement layer

MX_GTZC_S_Init() sets SPI1, RNG, USART2, UART5, VREFBUF and ICACHE to secure/non-privileged, and configures MPCBB block attributes across SRAM1 and SRAM2. SAU regions and the NSC region are defined in partition_stm32l552xx.h with SAU_INIT_CTRL_ENABLE = 1 and ALLNS = 0 — i.e. everything is secure unless explicitly declared otherwise.


06 — Application scenarios

01. Signed telemetry from field equipment

An industrial sensor node signs each measurement with a key that exists only inside TROPIC01, then ships it over W5500 TCP to a collector. Even full compromise of the non-secure application yields no key — at worst an attacker signs bad data, which upstream rate-limiting and sequence checks can catch, rather than minting arbitrary future messages.

02. Secure OTA with hardware-anchored verification

Firmware images arrive over the W5500 TCP socket in the non-secure world and are verified against a public key anchored in the secure world before any flash write is authorized. Because the download path never enters the Secure partition, a malformed-image parsing bug cannot reach the verification key.

03. Device identity / attestation endpoint for factory provisioning

On the production line, each unit generates its Ed25519 keypair inside TROPIC01 at first boot — exactly as the demo does — publishes only the public key over Ethernet, and enrolls itself with the manufacturing PKI. The private key is never transmitted, never stored on the host, and never exists in MCU RAM.

04. Hardware-root-of-trust gateway for legacy machinery

A retrofit box sits between an old serial machine and a plant network: W5500 handles the Ethernet side, the Secure world signs and timestamps every command accepted from the network, and TrustZone guarantees the legacy protocol translator can never touch the signing material.


Conclusion

When you cut an MCU in half with TrustZone, every line of code has to justify which half it lives in. This project's answer for the TCP/IP stack is the most elegant one available: neither half — put it in the W5500.

  • ✅ Arm TrustZone-M partitioning fully configured on STM32L552 (SAU + GTZC/TZSC + MPCBB)
  • ✅ TROPIC01 secure element integrated in the Secure World via libtropic + TrezorCrypto
  • ✅ Encrypted secure session, hardware RNG, in-SE Ed25519 key generation and signing — verified VERIFY OK
  • ✅ Occupied-key-slot recovery path implemented and captured in logs
  • ✅ W5500 driven from the Non-secure World over SPI2 with a compact 192-line port layer
  • TOE TCP socket (Sn_MR_TCP) HTTP client verified end-to-end against a live server (200 OK)
  • ✅ Concurrent UDP sockets for DHCP and DNS across the same hardware stack
  • ✅ Peripheral-level security enforcement: SPI1 secure, SPI2 non-secure, hardware-arbitrated
  • ✅ Complete, buildable STM32CubeIDE project published — .ioc, both .cprojects, linker scripts, HAL, libtropic and ioLibrary all included

Natural next step: exposing a signing veneer across the NSC boundary, so the non-secure HTTP client can request a TROPIC01 signature over its payload without ever seeing the key — turning two verified halves into one signed-telemetry pipeline.


Q&A

Q. Why does the W5500 sit in the Non-secure world rather than the Secure world? Deliberately. The network interface consumes untrusted input, so it belongs outside the Trusted Computing Base. GTZC enforces this: SPI1 (TROPIC01) is marked secure, SPI2 (W5500) is not. Non-secure code physically cannot address the secure element's bus.

Q. Which W5500 socket mode does the project use? TCP TOE mode — socket(sock, Sn_MR_TCP, 50000, 0) — for the HTTP client on socket 0, plus UDP sockets on 6 and 7 opened internally by ioLibrary's DNS and DHCP modules. Three of eight hardware sockets, two transport modes, concurrently.

Q. Wouldn't LwIP on an Ethernet MAC be cheaper in BOM terms? STM32L552 has no integrated Ethernet MAC, so that path isn't available on this MCU without a different part. But even where it is available, the trade is unfavorable under TrustZone: you save a chip and pay for it in trusted-boundary-adjacent code, RAM inside an already-split memory map, and packet-parsing attack surface. The W5500 keeps all three costs off the die.

Q. Does the 2 KB per-socket buffer limit throughput? For request/response protocols like HTTP, DHCP and DNS, no — the W5500's window management handles flow control in hardware. For bulk transfer, ioLibrary's ctlwizchip(CW_INIT_WIZCHIP) call accepts asymmetric allocations, so a project can give socket 0 8 KB and starve unused sockets. This project uses the even split because its traffic profile doesn't need more.

Q. Is the key ever exposed to the network side? No. The Ed25519 private key is generated inside TROPIC01 (origin=1 in the log confirms on-chip generation) and never leaves it. Only the 32-byte public key is read out. The non-secure world, where the W5500 driver lives, has no path to either.



TrustZone × TROPIC01 × W5500 — Cortex-M33을 둘로 가르고, 양쪽 모두에 전용 하드웨어 엔진을 붙이다

#TrustZone #CortexM33 #STM32L552 #시큐어엘리먼트 #TROPIC01 #W5500 #TOE #Ed25519 #libtropic #하드웨어신뢰근원 #GTZC

📚 성격: 보안 연구/평가 프로젝트 — Arm TrustZone-M 파티셔닝 + 내탬퍼 시큐어 엘리먼트 + 하드와이어드 TCP/IP 컨트롤러의 결합 ✅ 검증 상태: 양쪽 월드 모두 실제 실리콘에서 동작 확인. Secure World는 TROPIC01이 생성한 키로 Ed25519 서명 후 검증 성공, Non-secure World는 DHCP → DNS → TCP → HTTP/1.0 200 OK까지 완주. UART 로그 전문 공개.


01 — 어떤 프로젝트인가

TrustZone이란? Arm이 만든 하드웨어 보안 기술로, MCU 하나를 **Secure World(보안 영역)**와 **Non-secure World(일반 영역)**로 물리적으로 분리한다. Non-secure 쪽 코드는 Secure로 지정된 메모리나 주변장치에 아예 접근할 수 없다 — 소프트웨어 규칙이 아니라 하드웨어가 직접 차단한다. 그래서 일반 영역에서 버그나 해킹이 터져도, 애초에 접근 권한이 없던 보안 영역의 데이터(키 등)는 안전하다.

시큐어 엘리먼트Chip란? 암호키를 저장하고 암호 연산을 전담하는, 물리적 변조에 강한 별도의 칩이다. 일반 MCU 플래시에 저장된 키와 달리, 시큐어 엘리먼트 안의 키는 그 칩 내부에서 생성되고 사용되며 절대 밖으로 나오지 않는다 — 호스트 MCU를 아무리 소프트웨어적으로 장악해도 꺼낼 수 없다. 또한 프로빙이나 디캡슐레이션 같은 물리적 공격에도 칩 자체의 하드웨어 방어 기제가 저항한다.

"보안 IoT 디바이스"를 표방하는 프로젝트 대부분은 두 가지 어중간한 지점 중 하나에서 멈춘다. 키를 MCU 플래시에 넣어두고 아무도 디버그 프로브를 물리지 않기를 바라거나, 시큐어 엘리먼트를 붙이긴 했지만 그 암호 API를 호출하는 코드가 신뢰할 수 없는 네트워크 패킷을 파싱하는 코드와 같은 펌웨어 덩어리 안에 뒤섞여 있거나. 두 방식의 결함은 같다. 비밀을 쥔 코드와 인터넷과 대화하는 코드 사이에 구조적인 벽이 없다.

이 프로젝트는 그 벽을 세우고, 벽 양쪽에 각각 제대로 된 하드웨어를 붙인다.

NUCLEO-L552ZEQ(STM32L552, Arm Cortex-M33) 위에서 Arm TrustZone for Cortex-M을 사용해 MCU 하나를 두 개의 격리된 실행 환경으로 나눈다.

  • Secure World — SPI1, 내장 RNG, UART를 소유한다. libtropic SDK를 통해 TROPIC01 내탬퍼 시큐어 엘리먼트를 구동하고, CAL(Cryptographic Abstraction Layer)로는 TrezorCrypt를 쓴다. SE와 암호화 세션을 수립하고, 하드웨어 엔트로피를 뽑고, Ed25519 키페어를 SE 내부에서 생성하고, 칩 밖으로 절대 나오지 않는 개인키로 서명한 뒤 ed25519-donna로 검증한다.
  • Non-secure World — SPI2를 소유하고, 보안에 민감한 자산은 아무것도 갖지 않는다. 평범한 사용자 애플리케이션이 돌아간다. 이더넷 링크 확립, DHCP로 주소 획득, DNS 설정, TCP 소켓 개방, HTTP GET 수행. 전부 WIZnet W5500 위에서.

말하기는 쉽고 만들기는 어려운 원칙 하나를 실제로 구현한 결과물이다. 네트워크 스택과 개인키는 서로에게 닿을 수 없어야 한다. 이 보드에서 둘은 물리적으로 닿을 수 없다. 서로 다른 SPI 버스, 서로 다른 보안 상태에 있고, 그 경계는 GTZC 주변장치 방화벽과 SAU가 하드웨어로 강제한다.


02 — 왜 TrustZone + 시큐어 엘리먼트인가

🔷 TrustZone만으로는 물리 공격을 막지 못한다

TrustZone-M은 논리적 격리 수단이다. HTTP 파서의 버퍼 오버플로우가 서명키를 읽어가는 것을 막는 데는 탁월하다. 하지만 인두기와 글리치 인젝터, 디캡 장비를 든 공격자 앞에서는 아무 역할도 하지 못한다. Secure 파티션 플래시에 저장된 키도 결국 플래시에 저장된 키다.

🔷 시큐어 엘리먼트만으로는 소프트웨어 공격을 막지 못한다

반대로 SE는 저장 중·사용 중 키를 지켜준다. 그런데 공격자가 장악한 TCP 버퍼와 sign() 호출 사이에 평평한 주소공간의 평범한 C 코드밖에 없다면, 공격자는 키를 훔칠 필요가 없다. SE에게 자기가 원하는 것을 서명시키면 그만이다.

🔷 둘을 합치면 양쪽 문이 다 닫힌다

이 프로젝트는 의도적으로 둘을 겹쳐 쌓는다.

위협방어 주체
네트워크 스택 RCE로 키 탈취TrustZone — 키 자료가 Non-secure 주소공간에 아예 매핑되지 않음
플래시 탈착 후 덤프TROPIC01 — 개인키는 SE 안에서 생성되고 밖으로 나오지 않음
글리치로 인증 검사 스킵TROPIC01 시큐어 세션 — 호스트↔SE 채널이 페어링 키로 암호화·인증됨
Non-secure 코드의 악의적 주변장치 접근GTZC/TZSC — SPI1, RNG, USART2, UART5가 Secure로 하드웨어 잠금

GTZC 설정은 코드에 명시적으로 드러나 있다. HAL_GTZC_TZSC_ConfigPeriphAttributes()SPI1, RNG, USART2, UART5, VREFBUF, ICACHE를 GTZC_TZSC_PERIPH_SEC 지정하고, MPCBB 디스크립터가 SRAM1/SRAM2를 secure/non-secure 블록으로 갈라놓는다. Non-secure 코드가 TROPIC01의 SPI 버스를 건드리려 하면 잘못된 값을 읽는 게 아니라 SecureFault가 난다.

🔷 그리고 바로 여기서, 네트워크 칩 선택이 보안 설계 결정이 된다

파티션이 존재하는 순간, Non-secure 월드의 코드 1KB는 전부 공격 표면이고 Secure 월드의 코드 1KB는 전부 TCB(Trusted Computing Base)다. 일반적인 MAC+PHY 구성이었다면 LwIP 같은 완전한 소프트웨어 TCP/IP 스택을 둘 중 한쪽 파티션에 반드시 넣어야 한다. 어느 쪽도 마음 편한 선택이 아니다. 이 제약이 바로 W5500을 "편리한 선택"이 아니라 **"이 설계에서 가장 흥미로운 지점"**으로 만든다.


03 — 시스템 아키텍처

실행 순서에도 의미가 있다. Secure World가 세션 수립 → RNG → 키 생성 → 서명 → 검증 → 세션 종료까지 암호 시퀀스 전체를 끝낸 뒤에야 NonSecure_Init()을 호출해 애플리케이션 월드로 제어권을 넘긴다. 시큐어 부트 경로는 네트워크가 살아 있는지에 전혀 의존하지 않는다.


04 — 왜 WIZnet W5500이어야 했나 ⭐

🔷 기술적 핵심: 코드가 아닌 네트워크 스택

W5500은 TCP, UDP, IPv4, ICMP, ARP, IGMP, PPPoE를 하드웨어로 처리하고, 32KB 내부 버퍼와 독립 소켓 8개를 단순한 SPI 인터페이스로 제공한다. 이 보드에서는 균등 할당을 쓴다 — uint8_t memsize[2][8] = {{2,2,...},{2,2,...}}, 소켓당 TX 2KB / RX 2KB.

평범한 프로젝트라면 이건 그냥 "편하다"로 끝난다. TrustZone 프로젝트에서는 아키텍처 자산이 된다. 이유는 이렇다.

TrustZone은 평평한 주소공간 펌웨어라면 던질 필요도 없는 질문을 개발자에게 강제한다. TCP/IP 스택은 어느 파티션에 사는가?

  • LwIP를 Secure World에 → 네트워크에서 들어오는 적대적 입력을 직접 먹는 수만 줄의 패킷 파싱 코드가 서명키 바로 옆에서 TCB의 일부가 된다. 파티션을 나눈 이유 자체가 무너진다.
  • LwIP를 Non-secure World에 → 구조적으로는 맞다. 그런데 512KB 플래시 / 256KB SRAM을 SAU가 이미 반으로 갈라놓은 디바이스에서, Non-secure 파티션이 수십 KB급 플래시와 상당량의 RAM 버퍼를 요구하는 스택을 통째로 떠안아야 한다.

W5500은 이 딜레마 자체를 소멸시킨다. TCP/IP 스택이 어느 파티션에도 살지 않는다 — 별도의 칩 안에 산다. Non-secure 월드에 남는 건 얇은 드라이버뿐이다. wizchip_port.c192줄이고, ioLibrary 전체 풋프린트도 socket.c, w5500.c, dhcp.c, dns.c, wizchip_conf.c가 전부다. MCU 안에 있는 네트워크 공격 표면은 딱 그만큼이다.

보안적 함의는 날카롭다. 적대적 패킷 파싱이 보안 경계 바깥에서 전부 끝난다. 네트워크에서 날아온 기형 TCP 세그먼트를 처리하는 건 TrustZone 파티션 안의 C 상태머신이 아니라 W5500의 고정 기능 로직이다. "Non-secure 월드의 TCP 재조합 버그를 발판 삼아 피벗한다"는 시나리오가 성립하지 않는다. MCU 안에 TCP 재조합 코드 자체가 없기 때문이다.

🔷 이 프로젝트가 사용한 W5500 모드 — TOE TCP 소켓 모드 (Sn_MR_TCP)

NonSecure/Core/Src/main.c에서 직접 확인된다.

ret = socket(sock, Sn_MR_TCP, 50000, 0);   // TOE TCP 모드
ret = connect(sock, server_ip, server_port);

HTTP 클라이언트가 로컬 포트 50000으로 TCP(TOE) 소켓을 열고 서버로 아웃바운드 연결한다. 핸드셰이크, 시퀀싱, 재전송, ACK, 윈도우 관리 전부 하드웨어 오프로드다. 펌웨어는 HTTP 요청 문자열을 쓰고 응답을 읽을 뿐이다.

동시에 W5500의 UDP 경로도 함께 돌아간다. ioLibrary의 DHCP/DNS 클라이언트가 내부적으로 UDP 소켓을 열기 때문이다.

소켓모드역할버퍼
0TCP (TOE) Sn_MR_TCPHTTP GET 클라이언트, 로컬 포트 500002KB / 2KB
6UDP Sn_MR_UDP (DNS_init)DNS 조회앱 버퍼 512B
7UDP Sn_MR_UDP (DHCP_init)DHCP DISCOVER/OFFER/REQUEST/ACK앱 버퍼 548B

하드웨어 소켓 8개 중 3개가 서로 다른 두 전송 모드로 동시에 살아 있다. 프로토스레드 스케줄러도, 이들을 중재할 소프트웨어 스택도 없다. 칩이 중재한다.

🔷 대체 솔루션 대비

방식TCP/IP 스택 위치Non-secure 공격 표면TrustZone 적합도
W5500 (본 프로젝트)네트워크 칩 내부얇은 SPI 드라이버(포팅 192줄)✅ 스택이 MCU 밖에 완전히 존재
MAC + LAN8742A PHY + LwIPMCU 파티션 내부적대적 입력을 파싱하는 LwIP 전체⚠️ Non-secure 필수, TCB 인접 표면 큼
ENC28J60 + 소프트웨어 스택MCU 파티션 내부스택 전체 + MAC 레벨 드라이버까지❌ 최악 — 코드는 늘고 오프로드는 없음
AT 커맨드 Wi-Fi 모듈모듈 내부AT/시리얼 프로토콜 파서⚠️ 스택은 덜지만 취약한 텍스트 프로토콜 추가

조용하지만 중요한 이점이 하나 더 있다. STM32L552에는 이더넷 MAC이 아예 없다. 이 MCU로 유선 이더넷에 도달하려면 사실상 외부 컨트롤러가 필수인데, 흔히 쓰이는 외부 컨트롤러 중 소프트웨어 스택까지 함께 걷어내 주는 선택지는 W5500뿐이다. 이더넷을 가능하게 한 칩이, 동시에 보안 아키텍처를 깔끔하게 만든 칩이다. 설계자가 자주 만나는 종류의 우연이 아니다.

🔷 검증된 증거 ✅

공개된 Non-secure UART 로그 — W5500_Init()HTTP_Test()의 모든 단계가 실기 확인됨.

WIZCHIP Initialized              ← ctlwizchip(CW_INIT_WIZCHIP) 성공, VERSIONR == 0x04
Checking Link Status..
Link: DOWN Retrying : 0/1/2
Link: UP                         ← CW_GET_PHYLINK 폴링 루프
Using DHCP.. Please Wait..
DHCP IP assigned successfully    ← 소켓 7, UDP
Configuring DNS..                ← 소켓 6, UDP
IP: 10.14.1.114  SUBNET: 255.255.0.0
GATEWAY: 10.14.11.30  DNS: 10.14.11.1
Opening socket... Connecting... Connected   ← 소켓 0, Sn_MR_TCP
HTTP/1.0 200 OK
hello from python server.
HTTP test done

드라이버는 진행 전에 칩 정체성부터 검증한다 — getVERSIONR()0x04가 아니면 명시적 에러와 함께 초기화를 중단한다. 링크 업 대기는 10회 × 500ms로 상한이 걸려 있고, DHCP 실패 시 무한 대기 대신 정적 wiz_NetInfo로 폴백한다. 해피패스 데모가 아니라 방어적으로 작성된 드라이버 코드다.


04.5 — TrustZone으로 실제 구현된 기능들

범위를 정확히 짚고 가는 게 중요하다. 이 프로젝트가 구현한 건 하드웨어로 강제되는 격리이고, 아직 경계를 넘나드는 보안 서비스 연동은 아니다. 지금 실제로 들어있는 것만 정리하면 이렇다.

✅ 구현됨

기능구현 방식코드 위치
월드 분리SAU 활성화, ALLNS = 0 — 명시적으로 Non-secure로 지정하지 않으면 전부 Securepartition_stm32l552xx.h
주변장치 잠금SPI1, RNG, USART2, UART5, VREFBUF, ICACHE를 GTZC_TZSC_PERIPH_SEC로 강제MX_GTZC_S_Init(), Secure/Core/Src/main.c
핵심 비대칭 구조SPI1(→TROPIC01)은 Secure로 잠기고, SPI2(→W5500)는 Non-secure로 남음같은 함수 내 — 이게 이 프로젝트의 핵심 설계 결정
메모리 분할MPCBB 디스크립터로 SRAM1/SRAM2를 Secure/Non-secure 블록으로 분리HAL_GTZC_MPCBB_ConfigMem()
NSC 진입점 (뼈대만)SECURE_RegisterCallback() — 경계를 넘는 fault/error 콜백 등록secure_nsc.c

⚠️ 아직 구현되지 않음

  • 경계를 넘는 커스텀 서비스 호출이 없다. 현재 NSC로 호출 가능한 함수는 SECURE_RegisterCallback() 하나뿐인데, 이건 ST가 제공하는 TrustZone 기본 템플릿 그대로다 — SecureFault/GTZC 에러 콜백 등록용이지 프로젝트 고유 로직이 아니다.
  • 두 월드가 함께 동작하는 게 아니라 각자 독립적으로 동작한다. Secure World가 암호 시퀀스 전체를 끝낸 뒤 Non-secure World로 제어권을 넘기고, Non-secure World는 자기 나름의 HTTP 테스트를 실행할 뿐이다. 지금은 Non-secure 애플리케이션이 경계 너머로 TROPIC01 서명을 요청하는 동작이 없다 — 격리는 증명됐지만, Non-secure 코드가 호출할 수 있는 서명 인터페이스(veneer)는 아직 만들어지지 않았다.

정리하면: 벽은 실재하고 하드웨어로 강제된다 — SPI1과 SPI2가 벽의 양쪽에 있다는 건 증명됐다. 아직 없는 건 그 벽에 뚫린 "문"이다. Non-secure 네트워크 코드가 그 문을 통해 Secure 월드에게 서명을 대신 부탁하는 기능은 다음 단계로 남아있다 (Conclusion 참고).


05 — 핵심 구성 요소

🌐 WIZnet W5500 — TOE TCP(Sn_MR_TCP) + DHCP/DNS용 UDP, SPI2, Non-secure

소켓 8개와 32KB 내부 버퍼를 갖춘 하드와이어드 TCP/IP 오프로드 컨트롤러. WIZnet 공식 ioLibrary(socket.c, wizchip_conf.c, w5500.c + DHCP/, DNS/ 애플리케이션 모듈)로 구동하며, 192줄짜리 커스텀 wizchip_port.c가 다음을 제공한다.

  • W5500_Select() / W5500_Unselect() → CS GPIO, reg_wizchip_cs_cbfunc()로 등록
  • W5500_ReadByte() / W5500_WriteByte() → SPI2의 HAL_SPI_TransmitReceive(), reg_wizchip_spi_cbfunc()로 등록
  • 하드웨어 리셋 시퀀스: RESET Low 50ms → High 200ms
  • MAC AA:BB:CC:DD:EE:FF, NETINFO_DHCP + 정적 폴백

여기서 결정적인 건, SPI1이 GTZC로 Secure에 잠긴 반면 SPI2는 Non-secure로 남겨졌다는 점이다. 네트워크 인터페이스의 보안 상태가 관례가 아니라 MCU의 주변장치 방화벽으로 강제된다.

🔒 TROPIC01 시큐어 엘리먼트 — SPI1, Secure 전용

libtropic SDK로 접근하는 내탬퍼 시큐어 엘리먼트. Secure World 로그에서 확인된 동작: 핸들 초기화, lt_reboot(), 페어링 키 슬롯 0으로 lt_verify_chip_and_start_secure_session(), lt_ping() 에코 테스트, 32바이트 하드웨어 엔트로피를 반환하는 lt_random_value_get(), TR01_CURVE_ED25519로 ECC 슬롯 0에 lt_ecc_key_generate(), lt_ecc_key_read(), 64바이트 서명을 만드는 lt_ecc_eddsa_sign(), 그리고 깔끔한 lt_session_abort() / lt_deinit().

실무적으로 인상적인 디테일 하나 — 키 슬롯이 비어 있지 않은 상황을 우아하게 처리한다. 점유된 슬롯을 감지해 lt_ecc_key_erase()를 호출하고 생성을 재시도한다. 공개된 로그가 정확히 그 경로(두 번째 부팅)를 담고 있다.

🧮 TrezorCrypt — Cryptographic Abstraction Layer

libtropic의 CAL을 TrezorCrypt(ed25519-donna, aes, chacha20poly1305, monero)로 뒷받침한다. 호스트 측 검증은 SE에서 읽어온 공개키에 대해 ed25519_sign_open()으로 수행한다. 즉 "SE가 OK라고 했으니 믿는다"가 아니라 독립적으로 검증한다.

🛡️ STM32L552 GTZC / SAU — 강제 계층

MX_GTZC_S_Init()이 SPI1, RNG, USART2, UART5, VREFBUF, ICACHE를 secure/non-privileged로 설정하고 SRAM1/SRAM2에 MPCBB 블록 속성을 구성한다. SAU 리전과 NSC 리전은 partition_stm32l552xx.hSAU_INIT_CTRL_ENABLE = 1, ALLNS = 0으로 정의된다 — 즉 명시적으로 선언하지 않은 모든 영역은 Secure다.


06 — 응용 시나리오

01. 현장 장비의 서명된 텔레메트리

산업용 센서 노드가 TROPIC01 내부에만 존재하는 키로 측정값마다 서명한 뒤 W5500 TCP로 수집 서버에 전송한다. Non-secure 애플리케이션이 완전히 장악당해도 키는 나오지 않는다. 최악의 경우 공격자가 잘못된 데이터를 서명시킬 수는 있지만 — 이건 상위단 레이트 리밋과 시퀀스 검사로 잡을 수 있다 — 임의의 미래 메시지를 마음대로 발행할 수는 없다.

02. 하드웨어 앵커 검증 기반 시큐어 OTA

펌웨어 이미지가 Non-secure 월드의 W5500 TCP 소켓으로 도착하고, 플래시 쓰기가 승인되기 전에 Secure 월드에 앵커된 공개키로 검증된다. 다운로드 경로가 Secure 파티션에 진입하지 않으므로, 이미지 파싱 버그가 검증 키에 도달할 수 없다.

03. 공장 프로비저닝용 디바이스 아이덴티티 / 어테스테이션

생산 라인에서 각 유닛이 첫 부팅 시 TROPIC01 내부에서 Ed25519 키페어를 생성하고 — 데모가 하는 것과 정확히 동일하게 — 공개키만 이더넷으로 게시해 제조 PKI에 등록한다. 개인키는 전송되지도, 호스트에 저장되지도, MCU RAM에 존재하지도 않는다.

04. 레거시 설비용 하드웨어 신뢰근원 게이트웨이

낡은 시리얼 장비와 공장망 사이에 놓이는 레트로핏 박스. W5500이 이더넷 측을 담당하고, Secure 월드가 네트워크에서 수용한 모든 명령에 서명·타임스탬프를 부여하며, TrustZone이 레거시 프로토콜 변환기가 서명 자료에 절대 접근할 수 없음을 보장한다.


Conclusion

TrustZone으로 MCU를 반으로 가르는 순간, 모든 코드는 자기가 어느 쪽에 살아야 하는지를 정당화해야 한다. 이 프로젝트가 TCP/IP 스택에 내놓은 답은 가장 우아한 답이다 — 어느 쪽도 아니다. W5500에 넣는다.

  • ✅ STM32L552에서 Arm TrustZone-M 파티셔닝 완전 구성 (SAU + GTZC/TZSC + MPCBB)
  • ✅ Secure World에 libtropic + TrezorCrypt로 TROPIC01 시큐어 엘리먼트 통합
  • ✅ 암호화 세션, 하드웨어 RNG, SE 내부 Ed25519 키 생성 및 서명 — VERIFY OK 확인
  • ✅ 키 슬롯 점유 상황 복구 경로 구현 및 로그로 캡처
  • ✅ Non-secure World에서 SPI2로 W5500 구동, 포팅 레이어 192줄로 압축
  • TOE TCP 소켓(Sn_MR_TCP) HTTP 클라이언트를 실서버 대상 엔드투엔드 검증 (200 OK)
  • ✅ 동일 하드웨어 스택 위에서 DHCP·DNS용 UDP 소켓 동시 운용
  • ✅ 주변장치 레벨 보안 강제: SPI1 Secure / SPI2 Non-secure, 하드웨어 중재
  • ✅ 빌드 가능한 STM32CubeIDE 프로젝트 완전 공개 — .ioc, 양쪽 .cproject, 링커 스크립트, HAL, libtropic, ioLibrary 모두 포함

자연스러운 다음 단계: NSC 경계에 서명 veneer를 노출시켜, Non-secure HTTP 클라이언트가 키를 한 번도 보지 않은 채 자기 페이로드에 대한 TROPIC01 서명을 요청할 수 있게 만드는 것. 검증된 두 개의 반쪽이 하나의 서명 텔레메트리 파이프라인이 되는 지점이다.


Q&A

Q. 왜 W5500을 Secure가 아니라 Non-secure 월드에 두었나? 의도적인 배치다. 네트워크 인터페이스는 신뢰할 수 없는 입력을 소비하므로 TCB 바깥에 있어야 한다. GTZC가 이를 강제한다 — SPI1(TROPIC01)은 secure로 표시되고 SPI2(W5500)는 아니다. Non-secure 코드는 시큐어 엘리먼트의 버스를 물리적으로 주소 지정할 수 없다.

Q. 이 프로젝트가 쓰는 W5500 소켓 모드는? TCP TOE 모드 — socket(sock, Sn_MR_TCP, 50000, 0) — 로 소켓 0에서 HTTP 클라이언트를 돌리고, ioLibrary의 DNS·DHCP 모듈이 내부적으로 소켓 6·7에 UDP 소켓을 연다. 하드웨어 소켓 8개 중 3개, 전송 모드 2종을 동시에 사용한다.

Q. 이더넷 MAC + LwIP가 BOM 관점에서 더 싸지 않나? STM32L552에는 이더넷 MAC이 없으므로 이 MCU에서는 애초에 선택지가 아니다. 설령 가능한 MCU라도 TrustZone 하에서는 거래 조건이 불리하다. 칩 하나를 아끼는 대신 신뢰 경계에 인접한 코드, 이미 둘로 쪼개진 메모리 맵 안의 RAM, 그리고 패킷 파싱 공격 표면을 지불하게 된다. W5500은 이 세 비용을 전부 다이 바깥으로 내보낸다.

Q. 소켓당 2KB 버퍼가 처리량을 제한하지 않나? HTTP·DHCP·DNS 같은 요청/응답 프로토콜에서는 아니다. W5500의 윈도우 관리가 흐름 제어를 하드웨어로 처리한다. 대용량 전송이 필요하면 ctlwizchip(CW_INIT_WIZCHIP)이 비대칭 할당을 받아주므로 소켓 0에 8KB를 몰아주고 미사용 소켓을 굶길 수 있다. 이 프로젝트는 트래픽 특성상 더 필요하지 않아 균등 분할을 택했다.

Q. 키가 네트워크 측에 노출될 여지는? 없다. Ed25519 개인키는 TROPIC01 내부에서 생성되고(로그의 origin=1이 온칩 생성을 확인해 준다) 밖으로 나오지 않는다. 읽어내는 것은 32바이트 공개키뿐이다. W5500 드라이버가 사는 Non-secure 월드에는 둘 중 어느 쪽으로도 향하는 경로가 없다.


Original Link: https://github.com/8zppQr/trustzone_tropic01_test 개발 환경: Windows 11 · STM32CubeIDE 2.0.0 · STM32CubeMX 6.16.1 · NUCLEO-L552ZEQ · TROPIC01 · W5500

Documents
Comments Write