Add nat20 integration test suite for linux examples#105
Conversation
Add a kernel module that provides libnat20 functionality to linux kernel modules. Also add a configuration to build a minimal linux image with buildroot and run in on qemu and a workflow to test build nat20lib.ko
This module creates a new character device class intended to implement the nat20 service protocol implementing DICE based device state attestation and an embedded CA.
The nat20crypto module implements the libnat20 crypto interface in terms of linux kernel crypto primitives. The module implements - deterministic ECDSA with curves P256 and P384. - Bytewise SHA-2 224/256/384/512 - HMAC - HKDF ED25519 is currently not supported.
…/linux_example_nat20device
…urm/linux_example_nat20crypto
This commandline tool provides a primitive interface to communicate with a nat20 device.
783f03c to
eaf904a
Compare
LCOV of commit
|
There was a problem hiding this comment.
Pull request overview
Adds a Buildroot-packaged Linux/QEMU integration test suite for the nat20 service stack, including a new C test binary that exercises /dev/nat200, certificate issuance, signatures, parent paths, and promote behavior.
Changes:
- Adds
nat20_integration_testplus OpenSSL/libnat20-based verification helpers. - Adds QEMU/rootfs test launch scripts and Buildroot package wiring.
- Extends CI to build the rootfs and run the integration test in QEMU.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
examples/linux/nat20test/test/test_helpers.h |
Declares helper APIs for test cryptographic verification and parsing. |
examples/linux/nat20test/test/test_helpers.c |
Implements OpenSSL, CBOR, COSE, and compression helpers. |
examples/linux/nat20test/test/nat20_integration_test.c |
Adds the main nat20 service integration test suite. |
examples/linux/nat20test/nat20test.sh |
Adds the test runner script for loading the module and launching the binary. |
examples/linux/nat20test/nat20_qemu_init.sh |
Adds the QEMU init wrapper for running tests and emitting result markers. |
examples/linux/nat20test/CMakeLists.txt |
Builds and installs the integration test binary and scripts. |
examples/linux/br_external/utils/envsetup.sh |
Adds nat20test override and rebuild support. |
examples/linux/br_external/package/nat20test/nat20test.mk |
Adds the Buildroot package definition for nat20test. |
examples/linux/br_external/package/nat20test/Config.in |
Adds the Buildroot config option for nat20test. |
examples/linux/br_external/package/nat20cli/nat20cli.mk |
Documents nat20cli override-source behavior. |
examples/linux/br_external/configs/qemu_br_defconfig |
Enables nat20test in the QEMU Buildroot config. |
examples/linux/br_external/Config.in |
Sources the new nat20test Buildroot config. |
.github/workflows/linux-kmod-build.yml |
Adds rootfs build and QEMU integration-test execution steps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds a C integration test binary (nat20_integration_test) that exercises
the full DICE service stack via /dev/nat200. The test generates certificate
chains across all supported key type (P-256, P-384) and format (X.509,
COSE) permutations, verifies cryptographic signatures at each link, and
confirms that parent_path-based issuance produces identical results to
direct issuance after promote.
Test structure:
- Phase 1 (level 1): Generate CDI1, CDI2, ECA, ECA_EE certs and
signatures using parent paths of varying depth from the UDS level.
Verify all X.509 and COSE chains cryptographically.
- Phase 2 (level 2): After one promote, regenerate CDI2/ECA/ECA_EE/sign
with reduced parent path depth and assert byte-for-byte equality.
- Phase 3 (level 3): After second promote, regenerate ECA/ECA_EE/sign
with no parent path and assert equality.
Also includes:
- test_helpers.c: OpenSSL-based X.509 signature verification, public key
extraction, COSE_Sign1 parsing and verification, CWT subject public
key extraction, and compressed input computation.
- nat20_qemu_init.sh: init wrapper for running tests in QEMU CI.
- GitHub Action steps to build the rootfs and run the test suite in QEMU.
- Buildroot package (nat20test) with OpenSSL dependency.
eaf904a to
47500a2
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This reverts commit 0612f25.
| - name: Build rootfs image | ||
| env: | ||
| NAT20LIB_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| NAT20DEVICE_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| NAT20CRYPTO_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| NAT20SW_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| LIBNAT20_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| NAT20TEST_OVERRIDE_SRCDIR: ${{ github.workspace }} | ||
| run: make -C ${{ runner.temp }}/buildroot.build/buildroot -j $(( $(nproc) + 1 )) | ||
|
|
||
| - name: Run integration tests in QEMU | ||
| timeout-minutes: 5 | ||
| run: | | ||
| BUILDROOT_DIR="${{ runner.temp }}/buildroot.build/buildroot" | ||
| KERNEL="${BUILDROOT_DIR}/output/images/bzImage" | ||
| ROOTFS="${BUILDROOT_DIR}/output/images/rootfs.ext2" | ||
|
|
||
| qemu-system-x86_64 \ | ||
| -M pc \ | ||
| -kernel "${KERNEL}" \ | ||
| -drive file="${ROOTFS}",if=virtio,format=raw \ | ||
| -append "rootwait root=/dev/vda console=ttyS0 init=/usr/bin/nat20test_qemu_init.sh" \ | ||
| -nographic \ | ||
| -no-reboot \ | ||
| -net none \ | ||
| 2>&1 | tee qemu_output.log | ||
|
|
||
| if grep -q "INTEGRATION_TESTS_PASSED" qemu_output.log; then | ||
| echo "Integration tests passed." | ||
| else | ||
| echo "Integration tests failed. QEMU output:" | ||
| cat qemu_output.log | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
These changes are also present in #104. Based on the PR title, I think they should be here.. can you please separate them out so it's more clear?
| uint8_t* compressed_out, | ||
| size_t compressed_out_size) { | ||
| n20_crypto_digest_context_t* digest_ctx = NULL; | ||
| n20_error_t err = n20_crypto_nat20_open(&digest_ctx); |
There was a problem hiding this comment.
Basically re-iterating what copilot said below. We don't close this in the success path or in any of the error paths.
| /* CDI1: subject_key_type × format, issuer = P-256, no parent path */ | ||
| for (size_t si = 0; si < NUM_KEY_TYPES; si++) { | ||
| for (size_t fi = 0; fi < NUM_CDI_FORMATS; fi++) { | ||
| level1_artifacts.cdi1_valid[si][fi] = issue_cdi_cert(n20_crypto_key_type_secp256r1_e, |
There was a problem hiding this comment.
From the code below it looks like we are OK with some of these failing and other succeeding, but it's hard to derive the actual expectations just by looking at the code. It could be that the expectations will vary across devices/implementations, but this integration test is already pretty tightly coupled to the reference implementation.
Obviously Copilots suggestion of expecting them all to succeed is wrong, do it would be nice if we could at least validate that the expectations are met.
The same comment applies to cdi2_valid, eca_valid, eca_ee_valid, and signature valid.
There was a problem hiding this comment.
why is copilots expectation of them all succeeding wrong? we should be able to successfully create all subject key types and formats.
There was a problem hiding this comment.
I was just basing that on the following code that seemed OK (i.e. didn't fail) if valid is false.
| for (size_t fi = 0; fi < NUM_CDI_FORMATS; fi++) { | ||
| cert_buffer_t cert; | ||
| bool ok = | ||
| issue_cdi_cert(KEY_TYPES[ii], KEY_TYPES[si], CDI_FORMATS[fi], no_path, &cert); |
There was a problem hiding this comment.
So each of these cases are expected to pass? Aren't these the same calls as were made in the level 1 test? Or does the promotion change their behavior from level1?
| return err; | ||
| } | ||
|
|
||
| ssize_t received = dispatch_request(msg_buffer + (sizeof(msg_buffer) - msg_size), |
There was a problem hiding this comment.
does n20_msg_request_write work with a null buffer? then you can just run it twice so you can put the message at the start of the buffer.
| TEST_PASS(); | ||
| } | ||
|
|
||
| static void test_cdi_cert_x509_p256(void) { |
There was a problem hiding this comment.
should this test have a check with N20_WITH_X509 == 1 around it?
| } | ||
| #endif | ||
|
|
||
| static void test_eca_cert_x509_p256(void) { |
There was a problem hiding this comment.
same question as above and guarding with N20_WITH_X509 == 1
| fprintf(stderr, " cdi-cert error: 0x%x\n", cert_response.error_code); | ||
| return false; | ||
| } | ||
| if (cert_response.certificate.size > sizeof(out->data)) return false; |
| fprintf(stderr, " eca-cert error: 0x%x\n", cert_response.error_code); | ||
| return false; | ||
| } | ||
| if (cert_response.certificate.size > sizeof(out->data)) return false; |
| fprintf(stderr, " eca-ee-cert error: 0x%x\n", cert_response.error_code); | ||
| return false; | ||
| } | ||
| if (cert_response.certificate.size > sizeof(out->data)) return false; |
| fprintf(stderr, " eca-ee-sign error: 0x%x\n", sign_response.error_code); | ||
| return false; | ||
| } | ||
| if (sign_response.signature.size > sizeof(out->data)) return false; |
|
|
||
| static bool read_uds_cert(cert_buffer_t* out) { | ||
| int fd = open(DICE_CHAIN_PATH, O_RDONLY); | ||
| if (fd < 0) return false; |
| /* CDI1: subject_key_type × format, issuer = P-256, no parent path */ | ||
| for (size_t si = 0; si < NUM_KEY_TYPES; si++) { | ||
| for (size_t fi = 0; fi < NUM_CDI_FORMATS; fi++) { | ||
| level1_artifacts.cdi1_valid[si][fi] = issue_cdi_cert(n20_crypto_key_type_secp256r1_e, |
There was a problem hiding this comment.
why is copilots expectation of them all succeeding wrong? we should be able to successfully create all subject key types and formats.
| no_path, | ||
| &level1_artifacts.cdi1[si][fi]); | ||
| } | ||
| } |
timhirsh
left a comment
There was a problem hiding this comment.
Approving GH actions changes.
Adds a C integration test binary (nat20_integration_test) that exercises
the full DICE service stack via /dev/nat200. The test generates certificate
chains across all supported key type (P-256, P-384) and format (X.509,
COSE) permutations, verifies cryptographic signatures at each link, and
confirms that parent_path-based issuance produces identical results to
direct issuance after promote.
Test structure:
signatures using parent paths of varying depth from the UDS level.
Verify all X.509 and COSE chains cryptographically.
with reduced parent path depth and assert byte-for-byte equality.
with no parent path and assert equality.
Also includes:
extraction, COSE_Sign1 parsing and verification, CWT subject public
key extraction, and compressed input computation.